cancel
Showing results for 
Search instead for 
Did you mean: 

Atomic register write from cmsis by stm32HAL, what's the point?

SeyyedMohammad
Senior III

On the code below:

/* Enable the DMA transfer for transmit request by setting the DMAT bit
		    in the UART CR3 register */
		    ATOMIC_SET_BIT((&huart1)->Instance->CR3, USART_CR3_DMAT);

The HAL used:

/* Use of CMSIS compiler intrinsics for register exclusive access */
/* Atomic 32-bit register access macro to set one or several bits */
#define ATOMIC_SET_BIT(REG, BIT)                             \
  do {                                                       \
    uint32_t val;                                            \
    do {                                                     \
      val = __LDREXW((__IO uint32_t *)&(REG)) | (BIT);       \
    } while ((__STREXW(val,(__IO uint32_t *)&(REG))) != 0U); \
  } while(0)

But how it, and it's subassembly codes really works, and why such wierd instruction to modify a register?

13 REPLIES 13

@SeyyedMohammad​ It won't freeze unless the MCU is broken. Use of ldrex/strex on device registers is not obvious, but hopefully we can trust ST for this.

Given that only one core can access the UART (or there's no other cores) and this core has a limited number of nested interrupts, guess the max. number of loops in worst case.

A little bit complicated confusing. After all: We don't know how it works, we just trust ST that this is a solution for multithread safety???

I may regret this, but....

We DO know exactly how this works. These are standard ARM/Cortex instructions, documented in the ST Cortex M4 programmer's manual (PM0214) and on the ARM site (I'm too lazy to find the URL).

This construct DOES presume that there will be a limited number of interrupts that also attempt to use that memory location. Therefore, the while() loop is presumed to only execute a small number of times (typically 1 or 2 times). However, it WILL keep looping as long as an interrupt happens (1) between the LDREX and STREX instructions AND (2) that interrupt accesses the same memory location as the LDREX instruction.

So as long as you don't have a flood of interrupts all trying to access that one memory location, this code will never "freeze".

We do know how exactly ldrex/strex works on "normal" memory, AND we trust ST in that it works in the same way on their (proprietary) peripherals registers.

AND we trust ourselves not to cause weird interrupt storms.

Otherwise, the macro can be changed to check for the (almost impossible) failure case:

#define ATOMIC_SET_BIT(REG, BIT)                             \
  do {                                                       \
    uint32_t val;                                            \
    unsigned cnt = 0; \
    do {                                                     \
      if (++cnt > 16) {    \
         ErrorHandler(); \
      }  \
      val = __LDREXW((__IO uint32_t *)&(REG)) | (BIT);       \
    } while ((__STREXW(val,(__IO uint32_t *)&(REG))) != 0U); \
  } while(0)

But then stubborn reviewers will start asking questions: why 16? what is ErrorHandler?

Instead of changing the macro we rather tell them that the watchdog will take care of endless loops, including this one.