I'm attending a university course on real time systems and we are using the STM32F10xxx MCU family for labs and examples. Every GPIO port has an Output Data Register (ODR) and a set/reset register (BSRR). In the manual it says that BSRR should be used to atomically set/reset single bits, "so there is no risk an IRQ occurs between the read and modify access". If I understand this statement correctly it means that if I do something like GPIOB->ODR |= (1UL<<3) a read and write on the ODR is necessary, so an interrupt might occur in between and, to avoid that, it would be better to use BSRR to set the relevant bits... am I right?

The question is: what if I want to set all the pins on the ODR register in an atomic way? For example does the instruction GPIOB->ODR = 0x00000002 run atomically? In this case there is no need to read the value of the register, I'm simply setting all the bits to a specific value... is this write operation atomical or can it be interrupted? The only thing I found on the manual is "the I/O port registers have to be accessed as 32-bit words", but I don't know if this implies something about atomicity...

Can you help me? I'm completely new to this, so forgive me if the answer is trivial. I've found a lot of similar questions, but none of them clarified my doubts.



5 Answers 5


BSRR performs atomic operations on ODR. One half sets and the other half resets the bits in ODR.

I wrote this from memory so the polarity might be reversed, but the idea is the same.

For example this two statements are functionally equivalent, with the exception of BSRR write being atomic:

GPIOB->ODR |= 1UL<<3;

similarly this two:

GPIOB->ODR &= ~(1UL<<3)
GPIOB->BSRR = 1UL<<(3 + 16)

the statement GPIOB->ODR = 0x00000002 is a store(STR) instruction and accoring to this technical reference manual, is a single cycle execution, therefore atomic. please note that GPIOB->ODR = myVariable is not. for finding out which instruction is atomic and which on is not, you have to find out which instructions your C code generates, then use provided references to see if they are executed in single cycle or not.

when you are writing to a register, you are loading a system bus with 32bit data at CPU[DMA] side and unloading it at a peripheral register. all the 32 bits in the bus are either 0 or 1. so you can't keep the previous value for some bits in the peripheral register. the workaround is two things. the first you are familiar with :

GPIOB->ODR |= (1UL<<3);

which is a shorted form of a longer statement :


as you can see, it means read GPIOB->ODR, OR it with (1<<3) and write the new value to the GPIOB->ODR. so you read it, modify it, and write it.

there is no way to do single bit operations with this structure [programatically]. so there was a problem when fast bit manupulations were needed.

here comes the bit banding:

in this method which is used for frequently used registers (e.g. IO) each bit is hardware-mapped to another memory location (which is 32bit wide, a whole register). so when you write in this register, a single bit would be changed accordingly. as it's obvious, it seems inefficient in case of hardware because you need a full register for just another single bit in a peripheral register : enter image description here

so for GPIOB you need 16x32bit memory locations, for 16 pins! this part of memory is called bit band alias region. it's just an ARM Coretex M3,M4 specific feature.

  • \$\begingroup\$ Thank you for your kind answer, very informative! Actually my biggest concern is to know if the instruction GPIOB->ODR = 0x00000002 is run atomically... could you answer this? My guess is yes because it's not needed to read the register content like with the |= operation. However I'm not aware of how the low level things work, so I'm afraid that even a simple 32bit write might not be performed atomically. I hope the question is clear, thanks again! \$\endgroup\$ Dec 4, 2020 at 12:03
  • \$\begingroup\$ yes it is. it's just a single write to a register which zeros all pins expect the second one. the bit band method is used just when you need to manipulate single bit, without touching other bits, in a single write operation. it doesn't have a benefit when you are writing GPIOB->ODR = 0x00000002 or GPIOB->BSRR = 0x00000002; \$\endgroup\$ Dec 4, 2020 at 12:06
  • \$\begingroup\$ Perfect, that's exactly what I wanted to know. Thank you! \$\endgroup\$ Dec 4, 2020 at 12:07
  • \$\begingroup\$ That's all interesting and good in response to other questions, but the question actually asked was if the simply write of the entire ODR is atomic. \$\endgroup\$ Dec 4, 2020 at 21:53
  • \$\begingroup\$ @ChrisStratton yes I updated the answer. \$\endgroup\$ Dec 5, 2020 at 10:29

An atomic operation is one that cannot be interrupted as it is considered to be a single instruction.

To modify bits in a register without any specially crafted instructions requires a read - modify - write cycle which is at least 3 separate instructions:

As a very simplistic example:

uint32_t data, portreg;

data = portreg; read the port register
data |= 0x1; // set bit 0
portreg = data; write data back out

Should an interrupt occur during the initial read (for example), this instruction will complete but then the interrupt will be taken (assuming it is enabled) and the following instructions will only be executed after the interrupt handler returns.

For the instructions to not be interrupted you can use the handy instruction provided.

From your question:

GPIOB->ODR = 0x00000002

is a single write and therefore atomic.

"the I/O port registers have to be accessed as 32-bit words"

Simply means you should use an appropriately declared variable or use a typecast.

data = (uint32_t)portreg;


portreg = (uint32_t)data;
  • \$\begingroup\$ Why is the |= operation a "single write" while it's clearly a RMW?! It's equivalent to a GPIOB = GPIOB | (1UL<<3) which a read-modify-write, as GPIOB value has be read first in rvlaue part. I guess you've quoted a wrong part. \$\endgroup\$
    – NStorm
    Dec 4, 2020 at 11:18
  • \$\begingroup\$ Thank you for your answer Peter, but as @NStorm said I don't understand how the |= can be atomic. Maybe you meant that the = operation on the ODR register is atomic? \$\endgroup\$ Dec 4, 2020 at 11:33
  • 1
    \$\begingroup\$ As other say, your snippet is functionally equivalent to the one in question and not atomic at all. \$\endgroup\$
    – jaskij
    Dec 4, 2020 at 13:03
  • \$\begingroup\$ ODR is already defined to be 32-bit entity in memory. Why would it need an explicit type cast? \$\endgroup\$
    – Justme
    Dec 4, 2020 at 14:51
  • \$\begingroup\$ Being a single instruction may be sufficient for atomicity on STM32F10xxx, but in general it is not. Many systems have multiple bus masters (including but not limited to multiple CPU cores) and any instruction which causes more than one bus transaction without holding a lock is non-atomic. \$\endgroup\$
    – Ben Voigt
    Apr 29, 2021 at 19:21

Yes, you can. When you place = sign you are directly writing to a register without reading it first, like you do with |= or &=. But notice it will overwrite every bit in register, i.e. it will set every PORTB pin to this state. If you had some higher bits in this register set as 1, they will be overwritten to 0.

That is why BSRR here for. To change one bit in single atomic operation, without touching other bits. But once again if you don't care and need to write a whole register at once, you just write (=) to ODR directly.

To make sure whole 32-bit register are written in a single operation as a 32-bit word, just add a typecast to your value like this:

GPIOB->ODR = (uint32_t)0x00000002;

But because ODR are already defined as 32-bit register this aren't required.

  • 2
    \$\begingroup\$ Why would it need a type cast? ODR is a 32-bit entity to begin with. \$\endgroup\$
    – Justme
    Dec 4, 2020 at 14:48
  • \$\begingroup\$ @Justme you are right, it's not required in this case. I was think of how to explain generic situation where it might be required... but ended up giving superfluous cast. \$\endgroup\$
    – NStorm
    Dec 5, 2020 at 18:54

As stated already, you can set ODR directly, if you don't need the other bits to keep their current settings.

There is another way to set groups of 8 or 16 bits within the ODR register if they are byte or hword (16-bit) aligned. But only on those devices that allow byte or hword access to the register. You could use this:

((uint8_t*)&GPIOB->ODR)[1] = 0xAA;

To do something clever like setting a bunch of LEDs on bits 8-15. That would be 8 LEDs mapped to the second byte. Then being clever you might try the line given above, since then the other 3 bytes would be untouched. That would work if the bus allowed you byte access to the ODR register.

However, 32-bit access only. That won't work, and if you're lucky you get a bus fault. If you're unlucky you have subtle and hard to find bugs. So, read the data-sheets carefully.

To be clear, what is atomic and what isn't depends on the core in use and how good your compiler is. ARM Cores are RISC devices, |= or &= or any other RMW operation can't be atomic. But on CISC cores such as TI MSP430 chips, they can be atomic as single instructions to do exactly that exist, if you can trust the compiler to use them.

Any single instruction is almost certain to be atomic, not just single cycle instructions. This has to be the case, or interrupts won't work, since you can't interrupt a single instruction then resume it later without complicating core design enormously. Long slow instructions may make provision to be interruptible though, such as ARM core single instructions which load/store multiple registers.

  • \$\begingroup\$ There's an operator precedence problem preventing that from working as written. You can also simplify by using the identity *(p + i) == p[i] == i[p] \$\endgroup\$
    – Ben Voigt
    Apr 29, 2021 at 19:24
  • \$\begingroup\$ @Ben, true. Corrected. \$\endgroup\$
    – ilgitano
    May 1, 2021 at 8:14
  • \$\begingroup\$ @ben, i[p]? Wow char ch = 3["ABC"]; compiles! Learn something new every day. \$\endgroup\$
    – ilgitano
    May 1, 2021 at 8:20
  • \$\begingroup\$ Crazy innit? But *(p + i) == *(i + p) because i == p, and that leads to i[p] == p[i] \$\endgroup\$
    – Ben Voigt
    May 1, 2021 at 23:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.