In this lesson, you will learn how to prevent data corruption when your main application loop and interrupt service routines (ISRs) need to access the same variable simultaneously.
The Shared Data Problem
When you read or write a variable in your main() loop, the compiler optimizes that code, often keeping the value in a CPU register for speed. However, an interrupt can trigger at any moment, forcing the CPU to jump to an ISR. If that ISR modifies the same variable, your main() loop will be unaware of the change because it is still working with the old, "cached" value in its register. This mismatch leads to race conditions and unpredictable system behavior.
Using volatile
To fix the compiler optimization issue, we use the volatile keyword. Marking a variable as volatile tells the compiler: "This value can change outside the normal flow of the program, so do not cache it in a register." Every time you access a volatile variable, the CPU must fetch the actual value from its memory address.
// Force the compiler to re-read 'sensor_value' from memory every time
volatile uint16_t sensor_value;
void ADC_IRQHandler(void) {
// The ISR updates the data directly
sensor_value = ADC_GetResult();
}
Protecting Critical Sections
volatile ensures the CPU sees the most recent value, but it does not prevent a "read-modify-write" error. If your main() loop is performing a calculation on a variable (like count++) and an interrupt fires halfway through the operation, the interrupt might overwrite the value before main() finishes, causing your update to be lost.
To prevent this, you must protect the variable by disabling interrupts briefly—a process called creating a critical section.
uint16_t current_data;
// Inside main loop:
__disable_irq(); // 1. Block interrupts
current_data = sensor_value; // 2. Access the shared data safely
__enable_irq(); // 3. Re-enable interrupts
By wrapping the access in these blocks, you guarantee that the ISR cannot interrupt the operation, ensuring the data remains consistent and reliable throughout the execution of your program.
Key Takeaway: Use volatile to prevent compiler register-caching and create critical sections with interrupt masking to ensure data integrity during shared access.