Why should you use the BSRR register instead of directly writing to ODR for atomic pin manipulation?
The Output Data Register (ODR) represents the current output state of all 16 pins in a GPIO port as a 16-bit value. To change a single pin via ODR, firmware must perform a read-modify-write (RMW) sequence: read the entire register, modify the target bit using AND/OR masking, and write the result back. This three-step operation is not atomic — if an interrupt fires between the read and the write, and the ISR modifies a different pin in the same port through ODR, the ISR's change is silently overwritten when the interrupted code completes its write. This is a classic race condition that produces intermittent, hard-to-reproduce bugs.
The Bit Set/Reset Register (BSRR) eliminates this problem entirely. It is a 32-bit write-only register: the lower 16 bits set individual pins (writing 1 to bit N drives pin N high), and the upper 16 bits reset individual pins (writing 1 to bit N+16 drives pin N low). Writing 0 to any bit has no effect. Because a single write to BSRR atomically modifies only the specified pins without reading or affecting any other pins, no read-modify-write is needed, and there is no window for a race condition. The operation completes in a single bus cycle.
This makes BSRR the correct choice whenever GPIO pins are manipulated from multiple execution contexts — main loop, ISRs, or RTOS tasks. Even if you believe only one context touches a port, using BSRR is a defensive habit that costs nothing and prevents future bugs when code evolves. The HAL functions HAL_GPIO_WritePin() and HAL_GPIO_TogglePin() use BSRR internally for this reason. Note that toggling via BSRR still requires a read of ODR to determine the current state, so HAL_GPIO_TogglePin() is not fully atomic — if atomicity is critical for a toggle, disable interrupts briefly or use the dedicated toggle register (available on some newer STM32 families).
Source: GPIO Q&A
