Read-only memory-mapped registers defined with `volatile const` in C but only `volatile` in C++

StackOverflow https://stackoverflow.com/questions/15415158

  •  23-03-2022
  •  | 
  •  

Question

While working on an embedded systems project using an Atmel SAM3X8E, I noticed the following bit of code in some of the CMSIS header files.

#ifndef __cplusplus
typedef volatile const uint32_t RoReg; /**< Read only 32-bit register (volatile const unsigned int) */
#else
typedef volatile       uint32_t RoReg; /**< Read only 32-bit register (volatile const unsigned int) */
#endif

Why does the typedef for C++ not include const? I saw somewhere a mention that C++ does not store integer const variables in runtime memory, which if true would mean the const would need to be removed because of how microcontroller registers are memory-mapped, but I can't seem to find anything else saying that C++ does that (though my search was admittedly pretty brief). Not having much experience with C++, I also thought it might be that C++ doesn't allow const struct members, as those typedefs are mostly used in struct typedefs for collections of registers, but that doesn't seem to be the case either.

Was it helpful?

Solution 2

Because no RoReg object is ever instantiated, there is no good reason to omit the const qualifier in the typedef.

Every use of RoReg is in either a macro that defines a pointer to the type...

#define REG_WDT_SR (*(RoReg*)0x400E1A58U) /**< \brief (WDT) Status Register */

...or a struct declaration that is accessed using a similar macro.

typedef struct {
  WoReg WDT_CR; /**< \brief (Wdt Offset: 0x00) Control Register */
  RwReg WDT_MR; /**< \brief (Wdt Offset: 0x04) Mode Register */
  RoReg WDT_SR; /**< \brief (Wdt Offset: 0x08) Status Register */
} Wdt;

#define WDT        ((Wdt    *)0x400E1A50U) /**< \brief (WDT) Base Address */

Even with the const qualifier, the code should behave the same in both C and C++.

Perhaps the author misinterpreted the standard. To guarantee that a C++ struct has the same layout as in C, it requires that the class "has the same access control (Clause 11) for all non-static data members." The author may have mistaken const and volatile for access control specifiers. If they were, then you would want all the struct members to have the same cv-qualifiers in order to ensure compatibility between the C and C++ (and hardware) layouts. But it's public, protected, and private that define access control.

OTHER TIPS

If you declare with const, C++ standard will obligate you to initialize the contents of the variable. In the case of micro-controller register, you do not want to do that.

As mentioned by @fanl, const does indeed change the default linkage of globals in C++, and does prevent defining a variable without initialization.

But there are better ways to get external linkage than removing const. The usage of reserved arrays in the header file Chris linked is also very fragile. I would say this code leaves a lot of room for improvement -- don't emulate it.

And furthermore these variables don't get defined (that would cause the compiler and linker to select an address), they are always accessed via pointers, with the address fixed according to the memory map.

For headers intended purely for use by C++, this is how I do it (memory map matching a TI Stellaris chip).

Looks complicated, but the optimizing compiler reduces it down to a single instruction per access. And the address offsets are coded in, not dependent on the order and padding of fields inside a structure, so it's much less fragile and easier to verify against the datasheet.

template<uintptr_t extent>
struct memory_mapped_peripheral
{
    char data[extent];
    volatile       uint32_t* offset( uintptr_t off )       { return reinterpret_cast<volatile       uint32_t*>(data+off); }
    volatile const uint32_t* offset( uintptr_t off ) const { return reinterpret_cast<volatile const uint32_t*>(data+off); }
};

struct LM3S_SYSTICK : private memory_mapped_peripheral<0x1000>
{
    volatile       uint32_t& CTRL   (void)             { return offset(0x010)[0]; }
    volatile       uint32_t& RELOAD (void)             { return offset(0x014)[0]; }
    volatile       uint32_t& CURRENT(void)             { return offset(0x018)[0]; }
}* const SYSTICK = reinterpret_cast<LM3S_SYSTICK*>(0xE000E000);

struct LM3S_NVIC : private memory_mapped_peripheral<0x1000>
{
    volatile       uint32_t& EN    (uintptr_t i)       { return offset(0x100)[i]; }
    volatile       uint32_t& DIS   (uintptr_t i)       { return offset(0x180)[i]; }
    volatile       uint32_t& PEND  (uintptr_t i)       { return offset(0x200)[i]; }
    volatile       uint32_t& UNPEND(uintptr_t i)       { return offset(0x280)[i]; }
    volatile const uint32_t& ACTIVE(uintptr_t i) const { return offset(0x300)[i]; }
    volatile       uint32_t& PRI   (uintptr_t i)       { return offset(0x400)[i]; }
    volatile       uint32_t& SWTRIG(void)              { return offset(0xF00)[0]; }
}* const NVIC = reinterpret_cast<LM3S_NVIC*>(0xE000E000);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top