C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
210: Interrupt Service Routines Concept
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_countstayed at 0 even though I knew the button was being pressed. The ISR was firing, but themainloop 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 modifiedbutton_count. To save time, the compiler decided to loadbutton_countinto 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
volatilekeyword. 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 longdelay, 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 boolflag namedsystem_haltedinitialized tofalse. - An ISR function named
emergency_stop_handlerthat toggles thesystem_haltedflag. - A
mainloop that simulates "moving the arm" (e.g., printing "Arm moving...") but only ifsystem_haltedisfalse. If the flag istrue, 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.
There are no comments for now.