Loading header...

Interrupts, Shared Data, and Atomic Operations

Interrupts let firmware respond quickly to hardware events. They also introduce concurrency: an interrupt service routine can run between two instructions in main code.

Learning Objectives

By the end of this lesson, you should be able to:

  • explain why ISR-shared data needs special care;
  • use volatile for visibility without confusing it with atomicity;
  • protect multi-step updates with critical sections;
  • design simple interrupt-to-main communication.

The Interrupt Model

sequenceDiagram participant Main participant IRQ as Interrupt Main->>Main: execute foreground code IRQ-->>Main: hardware event arrives Main->>IRQ: CPU runs ISR IRQ-->>Main: return from interrupt Main->>Main: foreground resumes

The foreground code did not choose the interruption point.

Volatile Flags

static volatile bool uart_rx_ready;

void UART_IRQHandler(void)
{
    uart_rx_ready = true;
}

int main(void)
{
    while (!uart_rx_ready) {
        /* sleep or poll */
    }
}

volatile ensures the main loop reloads the flag. It does not make complex operations atomic.

Atomicity

An operation is atomic if it cannot be interrupted halfway in a way that exposes a broken intermediate state.

On an 8-bit CPU, reading a 16-bit counter may require two byte reads. An ISR could update the counter between those reads.

static volatile uint16_t tick_count;

uint16_t ticks_snapshot(void)
{
    uint16_t snapshot;
    disable_interrupts();
    snapshot = tick_count;
    enable_interrupts();
    return snapshot;
}

Keep critical sections short.

Shared Ring Buffer Pattern

flowchart LR UART["UART RX interrupt"] --> PUSH["push byte into ring buffer"] PUSH --> BUF["fixed-size buffer"] BUF --> POP["main loop pops bytes"] POP --> PARSE["command parser"]

Only one context should update each index when possible. For example, the ISR updates head, while main updates tail.

ISR Rules

  • Keep ISRs short.
  • Avoid blocking waits.
  • Avoid heavy formatting and dynamic allocation.
  • Clear or acknowledge the interrupt source correctly.
  • Defer processing to the main loop or a task when possible.

Common Mistakes

  • Assuming volatile count++ is atomic.
  • Calling slow drivers from an ISR.
  • Forgetting to clear the interrupt flag.
  • Sharing multi-byte variables without a critical section.
  • Disabling interrupts for too long.

Summary

Interrupts are powerful because they react quickly, but they create shared-data problems. Use volatile for visibility, critical sections for atomic snapshots or updates, and small ISRs that hand work to foreground code.

Further Reading