In this lesson, you will learn how to prevent data corruption during simultaneous read-modify-write operations on shared hardware registers in a bare-metal environment.
The Race Condition Problem
When you manipulate a register, you often follow a pattern: read the register value, modify a specific bit, and write the value back. In embedded systems, hardware peripheral status registers can change their values independently of the CPU (e.g., a timer flag setting or an interrupt trigger).
If your code reads a register, performs a calculation, and then writes back, an "interrupt" or another hardware process might change the register state in between your read and write steps. Your final write-back will then overwrite that external change, effectively "clobbering" the data and leading to erratic system behavior. This is known as a race condition.
Implementing Atomic Operations
To ensure safety, we must make these operations atomic—meaning they occur as a single, uninterruptible transaction. In bare-metal programming, the simplest way to achieve this without high-level OS primitives is to disable interrupts globally for the duration of the operation.
Consider this unsafe approach:
// UNSAFE: The register might change between reading and writing
GPIO_PORT_A->ODR |= (1 << 5);
To make this atomic, we protect the sequence:
// ATOMIC: Prevent interrupts from hijacking the operation
uint32_t primask = __get_PRIMASK(); // Save current interrupt state
__disable_irq(); // Lock interrupts
// Perform the Read-Modify-Write
GPIO_PORT_A->ODR |= (1 << 5);
__set_PRIMASK(primask); // Restore interrupts
By wrapping the operation in an interrupt lock, you guarantee that no peripheral status update or background process can preempt your CPU while it is calculating the new register state. The processor completes the entire read-modify-write cycle before any other task can touch the hardware, ensuring the register remains in a consistent, predictable state.
Key Takeaway: Atomic access ensures that the read-modify-write sequence is completed without interruption, preventing data loss when hardware registers are modified by concurrent processes.