Skip to Content
Course content

210: Interrupt Service Routines Concept

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've written programs that follow a linear path: the CPU does A, then B, then C. But in the real world, things happen asynchronously. A user presses a button, a sensor triggers, or a timer expires. You could check these things in a loop—which we call polling—but that's a waste of CPU cycles and can lead to missed events if your loop is too slow.

That's where Interrupt Service Routines (ISRs) come in. Think of an ISR as a "stop everything" signal. When a specific hardware event occurs, the CPU pauses its current task, jumps to a special function (the ISR), executes it, and then jumps right back to where it left off. It's efficient, but it introduces some weird concurrency issues that will bite you if you aren't careful.

Moving away from the polling loop

Let's build a simple button-press counter. In a polling world, your main loop would be constantly asking, "Is the button pressed now? How about now?" Instead, I want the hardware to wake the CPU up only when the button state actually changes.

// This is a simplified representation of an ISR setup
int button_count = 0;

// This function is our ISR. 
// The attribute tells the compiler this isn't a normal function.
void __attribute__((interrupt)) handle_button_press(void) {
    button_count++;
}

int main(void) {
    setup_interrupts(); // Configure hardware to trigger handle_button_press
    
    while(1) {
        // Do other useful work here
        printf("Current count: %d\n", button_count);
        delay(1000);
    }
}


The "why isn't it updating?" moment

I'll be honest: when I first started writing ISRs, I spent three hours staring at code exactly like the snippet above, wondering why my button_count stayed at 0 even though I knew the button was being pressed. The ISR was firing, but the main loop didn't seem to see the change.

Here is what happened: the compiler's optimizer looked at the while(1) loop and noticed that nothing inside that loop ever modified button_count. To save time, the compiler decided to load button_count into a CPU register once and just keep using that register value forever, ignoring the actual memory location.

The compiler didn't know that an external hardware event could trigger a completely different function to change that value. To fix this, we use the volatile keyword. This tells the compiler: "Don't optimize this variable; its value can change at any moment from outside the current flow of execution."

// The fix: adding 'volatile'
volatile int button_count = 0;

void __attribute__((interrupt)) handle_button_press(void) {
    button_count++;
}


Tying the ISR to the hardware

Now that we have the handler and the correct variable type, we have to tell the hardware which pin to watch and what "edge" triggers the interrupt. Do we want it to fire when the button is pressed (falling edge) or released (rising edge)?

I usually prefer falling edge triggers for buttons using pull-up resistors. Here's how I'd wrap this logic together into a cohesive unit:

#include <stdio.h>
#include <stdbool.h>

volatile int button_count = 0;

void __attribute__((interrupt)) handle_button_press(void) {
    // Keep ISRs short! No printf() or heavy logic here.
    button_count++;
}

void setup_interrupts(void) {
    // 1. Set pin as input
    // 2. Enable pull-up resistor
    // 3. Attach 'handle_button_press' to the External Interrupt line
    // 4. Set trigger to FALLING_EDGE
    // 5. Enable global interrupts
}

int main(void) {
    setup_interrupts();
    
    while(1) {
        // The main loop can now sleep or do heavy processing,
        // and the count will still update in the background.
        if (button_count > 10) {
            printf("Threshold reached!\n");
            button_count = 0; 
        }
    }
}


One last piece of advice: Never put a printf, a long delay, or any complex blocking call inside an ISR. ISRs should be "in and out." If you spend too much time in a handler, you might miss the next interrupt or crash the system because other critical background tasks are being blocked.




📋 Practical Task

Exercise: Implementing an Emergency Stop Toggle

You are designing a system for a motorized arm. You have a main loop that handles the arm's movement, but you need an emergency stop button that immediately halts all operations, regardless of where the program is in its execution.

Write a C program snippet that implements the following:

  • A volatile bool flag named system_halted initialized to false.
  • An ISR function named emergency_stop_handler that toggles the system_halted flag.
  • A main loop that simulates "moving the arm" (e.g., printing "Arm moving...") but only if system_halted is false. If the flag is true, it should print "SYSTEM HALTED - CHECK HARDWARE".

Ensure you use the correct variable qualifiers to prevent the compiler from optimizing away your flag check in the main loop.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.