In this lesson, you will learn how to prevent data corruption by ensuring that your register modifications cannot be interrupted by other system tasks.
The Race Condition Problem
When you modify a peripheral register—like setting a single bit in a GPIO control register—the processor usually performs a "Read-Modify-Write" cycle. It reads the current value into a CPU register, changes the bits, and writes the result back.
In a bare-metal system, an interrupt can trigger exactly between the read and the write. If your interrupt service routine (ISR) modifies the same register, your main code might overwrite that change when it finally performs its write-back. This is a race condition, and it often leads to unpredictable hardware behavior that is incredibly difficult to debug.
Ensuring Atomicity
To fix this, we must make the modification atomic—meaning it appears to happen as a single, indivisible unit that cannot be interrupted. We achieve this by creating a "critical section." By disabling interrupts briefly, we guarantee that our Read-Modify-Write cycle completes without interference.
// Traditional (Dangerous) approach:
GPIO_PORTA->MODER |= (1 << 5);
// Atomic approach (Safe):
__disable_irq(); // Disable interrupts globally
GPIO_PORTA->MODER |= (1 << 5); // Read-Modify-Write safely
__enable_irq(); // Re-enable interrupts
Why This Matters
If you are toggling a pin in your main() loop while an ADC interrupt is trying to configure the same port, the register could end up in a corrupted state. By wrapping the operation in an atomic block, you ensure that the peripheral configuration remains consistent, even when your system is juggling multiple tasks and hardware events.
Always keep these sections as short as possible to avoid delaying other critical interrupts, like those managing your timers or ADC sampling.
Key Takeaway: Atomic operations prevent race conditions during Read-Modify-Write cycles by disabling interrupts to ensure that peripheral register updates occur as an uninterrupted, single event.