In this lesson, you will learn how to systematically identify and resolve intermittent data corruption caused by non-atomic interactions between hardware interrupts and background tasks.
The Anatomy of the Race
When a task and an Interrupt Service Routine (ISR) share a variable, you face a classic race condition. Because an interrupt can pre-empt a task at any machine-code instruction, a simple operation like shared_counter++—which involves a read, an increment, and a write—can be sliced in half. If the ISR triggers during that window, it may read a stale value, update it, and then have its work overwritten when the task resumes and completes its original write operation.
Diagnostic Techniques
To debug these intermittent failures, start by identifying "critical regions" where the shared variable is accessed. If you suspect a specific variable is being corrupted:
- Hardware Watchpoints: If your debugger supports it, set a "write watchpoint" on the address of the shared variable. If the value changes unexpectedly while the task is executing, check the call stack to see if an ISR triggered at that exact moment.
- Instrumentation: Use a spare GPIO pin to toggle high when entering a critical section and low upon exit. Monitor this signal alongside your data variable on a logic analyzer to see if the ISR triggers during the "high" window.
- Atomic Verification: If the logic seems sound but errors persist, look for non-atomic operations. Even
x = ycan be non-atomic ifyis a 32-bit value on an 8-bit architecture.
Consider this vulnerable code:
// Task-level code
void update_data(void) {
// If an interrupt fires here, it may modify 'buffer'
// while we are only halfway through writing to it.
buffer.timestamp = get_time();
buffer.value = current_reading;
}
To resolve this, you must ensure atomicity by disabling interrupts or utilizing an interrupt-safe locking primitive before accessing the data. If the bug is truly intermittent, verify that you aren't missing a "hidden" path where the ISR triggers due to shared flag registers or peripheral status updates that you haven't accounted for in your critical section logic.
Key Takeaway: Race conditions in interrupt-driven systems occur when the hardware preempts an atomic sequence; always treat shared variables as critical sections by masking interrupts or using atomic primitives during every access.