An atomic operation is one that completes entirely or not at all, with no observable intermediate state visible to any other execution context. In embedded systems, atomicity matters whenever shared data is accessed by multiple contexts such as ISRs, RTOS tasks, or multiple CPU cores.
In practice
On most 8-bit and 16-bit MCUs (PIC, AVR, MSP430), reading or writing a value wider than the native bus width is not atomic. A 16-bit variable on an 8-bit AVR, for example, requires two separate bus cycles; an ISR firing between those cycles can see a half-updated value. Even on 32-bit Cortex-M devices, operations that appear to be single C statements may compile to multiple instructions, and a read-modify-write sequence (load, modify, store) is never inherently atomic unless the hardware provides a dedicated mechanism.
ARM Cortex-M3 and later cores provide Load-Exclusive / Store-Exclusive (LDREX/STREX) instructions, which implement a hardware reservation mechanism that allows software to build atomic compare-and-swap primitives without disabling interrupts. Cortex-M0 and M0+ lack these instructions, so on those cores, critical sections typically require disabling interrupts (CPSID/CPSIE or PRIMASK manipulation) around the access. On multicore SoCs such as Cortex-A or RISC-V designs with multiple harts, even interrupt disabling is insufficient; atomicity typically requires architecture-specific atomic instructions (such as LDREX/STREX on ARM or LR/SC and AMOs on RISC-V) combined with appropriate memory ordering, depending on the architecture and access pattern.
In bare-metal single-core embedded code, the most common approach to enforcing atomicity is to wrap the shared-data access in a critical section that disables interrupts. This is simple and correct, but it increases interrupt latency for the duration of the section. Minimizing the code inside the critical section to only what is strictly necessary is important for real-time systems. CMSIS provides __disable_irq() / __enable_irq() and the __LDREXW / __STREXW intrinsics for these patterns on Cortex-M. RTOS environments (FreeRTOS, Zephyr, ThreadX) provide their own critical-section and mutex APIs that wrap the correct underlying mechanism for the target architecture.
A common pitfall is assuming that a C expression is atomic because it looks simple. The volatile keyword does not guarantee atomicity; it prevents the compiler from caching the value in a register, but it says nothing about indivisibility at the machine-instruction level. Similarly, incrementing a volatile counter (count++) typically compiles to three instructions on most architectures and is not atomic. The blog post "Scorchers, Part 3: Bare-Metal Concurrency With Double-Buffering and the Revolving Fireplace" explores practical techniques for sharing data safely between ISR and main-loop contexts without relying on incorrect atomicity assumptions.
Frequently asked
Does the volatile keyword make an access atomic?
No.
volatile tells the compiler to always read or write the variable directly in memory and not
cache it in a register, but it provides no guarantee that the operation is indivisible. A volatile read-modify-write (such as count++) on most architectures still compiles to separate load, modify, and store instructions, all of which can be interrupted between each other.
How do I make an operation atomic on a single-core bare-metal Cortex-M system?
The most straightforward method is to disable
interrupts around the access using PRIMASK (via CMSIS __disable_irq() / __enable_irq() or equivalent). On Cortex-M3 and later, LDREX/STREX-based compare-and-swap loops are an alternative that avoids masking interrupts for the duration of the
critical section, at the cost of more complex code and the possibility of retrying the loop if a concurrent access is detected. Note that exclusive-access loops are not a drop-in replacement for every kind of shared-state update. Keep critical sections as short as possible to minimize impact on interrupt
latency.
Are 32-bit reads and writes atomic on a 32-bit Cortex-M MCU?
A naturally aligned single-word load or store (LDR/STR) is architecturally guaranteed to be atomic with respect to
interrupts on Cortex-M cores, meaning no interrupt can observe a partially written value. However, this only covers the single memory access itself, not any surrounding read-modify-write sequence. Unaligned accesses and accesses wider than the bus width (such as a 64-bit double) are not guaranteed to be atomic.
Why is disabling interrupts not enough for atomicity on multicore devices?
Disabling
interrupts on one core only prevents that core's own interrupt handlers from preempting the operation. A second core running concurrently can still access shared memory simultaneously. On multicore Cortex-A or multi-hart
RISC-V systems, atomicity requires hardware-supported exclusive-access instructions (LDREX/STREX on ARM, LR/SC on RISC-V) combined with appropriate memory barriers, or higher-level synchronization primitives built on top of them.
What is a compare-and-swap (CAS) and when is it useful in embedded systems?
A compare-and-swap atomically reads a memory location, compares it to an expected value, and writes a new value only if the comparison succeeds, all without another agent being able to intervene. On Cortex-M3 and later, it is implemented with an LDREX/STREX loop. CAS is the building block for lock-free data structures and is useful when you want to update shared state without disabling
interrupts, accepting that the operation may need to retry if a concurrent access is detected.
Differentiators vs similar concepts
Atomic operation is often confused with
volatile access and with critical sections. A volatile access prevents compiler optimizations that would hide memory reads or writes, but does not make the access indivisible at the hardware level. A
critical section (
interrupt disable or
mutex lock) is a region of code protected from concurrent access; it is a mechanism used to enforce atomicity, not atomicity itself. An atomic operation is the property that results from correctly applying such a mechanism, or from a hardware instruction that is inherently indivisible.