Electronic – Accessing stm32 registers via structs in GCC

armcgccstm32

I'm trying to roll my own USB device using an STM32F103 blue pill board in C with the GCC arm-none-eabi compiler, but I've run into some strange behavior that I'm trying to understand.

I've created a struct that models the endpoint buffer descriptor tables associated with the USB peripheral:

typedef struct _EP_BUF_DSCR {
    uint32_t ADDR_TX;
    uint32_t COUNT_TX;
    uint32_t ADDR_RX;
    uint32_t COUNT_RX;
} EP_BUF_DSCR;

And I've created a pointer to one of these structs and set it to address the beginning of the USB packet memory area of the MCU:

EP_BUF_DSCR *EP0_DSCR = (EP_BUF_DSCR *) 0x40006000;

So now I can set the ADDR_TX and ADDR_RX fields by just dereferencing the appropriate fields and assigning them values. However, when I try to do this with the COUNT_RX field, it appears to have no effect:

// debugger memory view shows register as set to 0 after running this:
EP0_DSCR->COUNT_RX = 0x8400;  

But I am able to change the value in this register by creating a pointer directly to it and dereferencing it:

// debugger memory view shows register set to correct value after running:
*((uint32_t *)(0x40006000 + 12)) = 0x8400;  

Could someone provide some insight as to why this happens? Have I done something bone-headed, or am I unintentionally relying on undocumented compiler behavior?

Best Answer

Typically memory mapped hardware special function registers must be declared volatile so that the hardware knows that something may need or alter their state beyond the local thread of execution - otherwise it is free to cache values in registers or neglect to even compute apparently unused ones at all.

On the STM32, certain SFRs may also be restricted to a particular width of access.

If what you are trying to do is legitimate, there is likely already a suitable definition within the vendor (or alternate) driver or HAL files. And even if there isn't, you can learn a lot by reading those to see how something simple like a GPIO block is declared.

Of course it's also possible that there is something specifically wrong with the particular operation you are trying to perform - for example, that might not be writable, or only be meaningfully writable when the hardware is in a particular state, which the debugger lucks out in achieving while your program usually does not.