Course contentsShow
← Embedded Systems & Computer Architecture
Lesson 204 of 9363. Microcontroller Fundamentals and Bare-Metal CFree lesson

Managing Shared Data Between Mainline and ISRs

Apply the volatile keyword and atomic access techniques to prevent race conditions when sharing variables between interrupts and main code.

In this lesson, you will learn how to safely share variables between your main() loop and your Interrupt Service Routines (ISRs) to prevent data corruption.

When your main code and an ISR both access the same variable, a "race condition" can occur. Imagine main is halfway through updating a 32-bit counter when an interrupt fires. If the ISR reads that counter, it might get a garbled value—half the old data and half the new. Even worse, the compiler often optimizes code by caching variables in CPU registers, meaning main might never see the change the ISR made to a variable in memory.

The volatile Keyword

To solve the caching problem, we use the volatile keyword. It tells the compiler: "This variable can change at any time due to hardware or interrupts, so do not cache it in a register."

volatile uint32_t sensor_count = 0;

void SysTick_Handler(void) {
    sensor_count++; // ISR updates the value
}

int main(void) {
    while(1) {
        // Main reads the value
        if (sensor_count > 100) {
            // Do something
        }
    }
}

Atomic Access Techniques

While volatile stops the compiler from hiding changes, it does not make an operation "atomic." An operation is atomic if it happens in a single, uninterruptible step. A simple increment (sensor_count++) often takes three steps: read, modify, write. If an interrupt occurs between the read and the write, your data will be corrupted.

To handle variables larger than one byte, or complex structures, you must protect the access by disabling interrupts globally. This ensures that the code inside the block executes without being interrupted:

void process_data(void) {
    __disable_irq(); // Enter critical section
    uint32_t temp = sensor_count;
    __enable_irq();  // Exit critical section
    
    // Now use 'temp' safely
}

By surrounding your access with these instructions, you create a "critical section," guaranteeing that the shared data remains consistent while you read it.

Key Takeaway: Use volatile to prevent compiler caching and disable interrupts to guarantee atomic access when reading or writing shared multi-byte variables.