Skip to main content

Command Palette

Search for a command to run...

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

Published
3 min read
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.

💡
While disable_interrupts() is a common and effective low-level solution, it can feel like a hack, especially as systems scale in complexity. If your toolchain or RTOS provides atomic access APIs (C11 <stdatomic.h> or taskENTER_CRITICAL() in FreeRTOS), prefer those over manual interrupt disabling. They’re often safer, more portable, and easier to maintain.

💡 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.

🚀
Call to action: Assume nothing, protect everything shared between contexts.

🏁 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.

🚀
Call to action: Next time you debug strange behaviour in your embedded project, ask yourself: “Could this be a timing or concurrency issue?” and reach for the right fix.

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.

More from this blog