Common Mistakes in Embedded C Development: Assuming atomicity for multi-byte data

📘 Introduction
This is Part 5 of our 5-part series on concurrency and timing mistakes in Embedded C. In Part 4, we covered the dangers of misusing timer callbacks or interrupt routines.
Now we’ll explore a subtle yet dangerous mistake: assuming that reads and writes of multi-byte variables (like uint32_t) are atomic. On many embedded systems, especially 8-bit and 16-bit MCUs, they’re not. This misunderstanding can lead to corrupted data and unpredictable bugs, especially when shared between an ISR and the main loop.
🧠 Assuming atomicity for multi-byte data
🐞 The Problem
Let’s say you have a 32-bit counter shared between an interrupt and the main loop. On a 32-bit CPU like Cortex-M4, uint32_t accesses are atomic. But on an 8-bit AVR, or a 16-bit MSP430, accessing a 32-bit variable requires multiple instructions and can be interrupted mid-access.
#include <stdint.h>
#include <stdbool.h>
volatile uint32_t pulse_count = 0;
void IRAM_ATTR pulse_isr(void* arg) {
pulse_count++; // ISR increments shared variable
}
void loop() {
if (pulse_count > 1000) { // Main loop reads it
pulse_count = 0;
handle_overflow(); // Take action on overflow
}
}
This seems safe, but it isn’t. On systems where uint32_t accesses are not atomic, the main loop could read a half-written value while the ISR updates it. This causes incorrect logic or missed events.
Solution: Use Interrupt Disable/Enable Around Access
To protect multi-byte variables in bare-metal C code, disable interrupts before accessing the shared variable, then restore them afterwards. This creates an atomic section by preventing the ISR from interrupting the access.
#include <stdint.h>
#include <stdbool.h>
extern void disable_interrupts();
extern void enable_interrupts();
volatile uint32_t pulse_count = 0;
void IRAM_ATTR pulse_isr(void* arg) {
pulse_count++;
}
void loop() {
while (true) {
uint32_t local_count;
disable_interrupts();
local_count = pulse_count;
if (local_count > 1000) {
pulse_count = 0;
}
enable_interrupts();
if (local_count > 1000) {
handle_overflow();
}
// Other main loop logic...
}
}
Why This Works:
The interrupt is disabled during read/write of pulse_count, so no ISR can interrupt mid-access.
We first copy pulse_count to a local variable to avoid repeating the protected access.
The logic is now safe and consistent, no partial reads or lost updates.
💡 Takeaway
Don’t assume multi-byte data access is atomic; it depends on the CPU architecture.
Always use critical sections (disable/enable interrupts) when sharing multi-byte variables between ISRs and foreground code.
Alternatively, use atomic access APIs if available in your toolchain or RTOS.
🏁 Conclusion
Timing and concurrency mistakes are some of the most common and dangerous bugs in Embedded C. From unsafe ISRs to misuse of shared variables, small oversights can cause big failures. Thankfully, with a disciplined approach and awareness of the pitfalls, they’re also avoidable.
At Itransition, we build IoT solutions with all these challenges in mind, ensuring our clients receive reliable, scalable systems with minimal maintenance overhead. Learn more about our approach at https://www.itransition.com/iot.



