June 17, 2025
Exploring Interrupts: Enhancing Performance in Embedded Systems

Exploring Interrupts: Enhancing Performance in Embedded Systems

Interrupts are a fundamental concept in microcontroller programming that enable efficient handling of events without continuously polling for changes. They allow a microcontroller to respond quickly to specific signals, enhancing responsiveness and performance in embedded systems. In this article, we’ll explore what interrupts are, how they work, and how to effectively use them in your projects.

What Are Interrupts?

An interrupt is a signal to the processor emitted by hardware or software indicating an event that needs immediate attention. When an interrupt is received, the current execution of the program is paused, and control is transferred to a special routine called an “interrupt handler” or “interrupt service routine” (ISR). Once the ISR has finished executing, the processor resumes the program from where it was interrupted.

Types of Interrupts

  • Hardware Interrupts: Triggered by external hardware events, such as pressing a button, receiving data over I2C, or a timer reaching a specific count. These interrupts can come from various sources, including GPIO pins, timers, and communication interfaces.
  • Software Interrupts: Generated by the program itself, typically used for debugging or signaling an event in software. In many programming environments, these are not as common but can be useful in certain scenarios.

How Interrupts Work

When an interrupt occurs, the following steps generally take place:

  • Interrupt Signal: A device sends an interrupt signal to the microcontroller, indicating that it requires attention.
  • Saving State: The current state of the program (including the program counter and registers) is saved so that the microcontroller can return to it later.
  • Executing the ISR:The microcontroller jumps to the ISR associated with the interrupt. This routine is where the handling of the interrupt event occurs.
  • Restoring State: Once the ISR completes, the microcontroller restores the saved state and resumes normal program execution.

Advantages of Using Interrupts

  • Efficiency: Instead of constantly checking the status of a device (polling), interrupts allow the CPU to perform other tasks until an event occurs.
  • Responsiveness: Interrupts enable immediate reaction to critical events, improving the responsiveness of applications, especially in real-time systems.
  • Reduced Power Consumption: By allowing the CPU to sleep until an interrupt occurs, power consumption can be significantly reduced.

Types of Interrupts Supported by Arduino / Esp32

  • Pin Change Interrupts: These are available on all digital pins, allowing you to monitor any pin for state changes.
  • Timer Interrupts : You can set up timer interrupts using the built-in timer libraries. These interrupts can be used for tasks that require periodic execution.
  • Communication protocol interrupts : These include interrupts on sending or receiving data on Uart , Spi , I2c , can peripherals.

Implementing Interrupts

Lets implement few interrupts in Arduino ide

Gpio interrupt :

Here’s a simple example of how to use interrupts in an Arduino project to toggle an LED when a button is pressed.

const int buttonPin = 1; // Pin for the button
const int ledPin = 2;   // Pin for the LED
volatile bool ledState = false; // LED state

void setup() {
    pinMode(ledPin, OUTPUT);
    pinMode(buttonPin, INPUT_PULLUP); // Use internal pull-up resistor
    attachInterrupt(digitalPinToInterrupt(buttonPin), toggleLED, FALLING); // Trigger on falling edge
}

void loop() {
    // Main code can run here while waiting for interrupts
}

// Interrupt Service Routine to toggle LED state
void toggleLED() {
    ledState = !ledState; // Toggle the state
    digitalWrite(ledPin, ledState); // Update LED
}

if you look closely then you will notice that your loop is not doing anything . The toggleLED function is only fired when a button is pressed . This makes our code more efficient and responsive.

Explanation

  • Setup:
    • The button pin is set as an input with an internal pull-up resistor.
    • The attachInterrupt() function links the button pin to the toggleLED() ISR, triggering on a falling edge (button press).
  • Loop:
    • The main loop can perform other tasks, knowing that the LED will toggle automatically when the button is pressed.
  • ISR:
    • In the toggleLED() function, the LED state is toggled and the output pin is updated.

I2c interrupts :

I2c Slave

To toggle an LED on an I2C receive interrupt in Arduino, you’ll need to set up the Arduino as an I2C slave device. When data is received over I2C, an interrupt will trigger, and you can toggle the LED in the interrupt handler. Here’s how you can do this:

#include <Wire.h>

const int ledPin = 9; // Pin for the LED

volatile bool ledState = false; // LED state

void setup() {
    pinMode(ledPin, OUTPUT); // Set LED pin as output
    digitalWrite(ledPin, ledState); // Initialize LED state

    Wire.begin(8); // Join I2C bus with address #8
    Wire.onReceive(receiveEvent); // Register receive event
}

void loop() {
    // Main loop does nothing, all work is done in the interrupt
}

// This function is called when data is received
void receiveEvent(int numBytes) {
    // Toggle LED state
    ledState = !ledState; 
    digitalWrite(ledPin, ledState); // Set LED to new state
}

I2c Master

#include <Wire.h>

void setup() {
    Wire.begin(); // Join I2C bus as master
}

void loop() {
    Wire.beginTransmission(8); // Address of the slave device
    Wire.write(1); // Send a byte (any value to trigger the toggle)
    Wire.endTransmission(); // Stop transmitting

    delay(1000); // Wait 1 second before sending again
}

Timer interrupt

There is no universal method for implementing timers in the Arduino IDE because the timer peripheral’s count and features vary across the different microcontroller boards supported by the IDE.

For Avr based Arduino board you can use the TimerOne library (not pre-installed) . Here is the working example of the library.


#include <TimerOne.h>

const int led = LED_BUILTIN;  // Pin for the LED
int ledState = LOW; // LED state

void setup() {
    pinMode(led, OUTPUT);
    Timer1.initialize(150000); // Set timer for 0.15 seconds
    Timer1.attachInterrupt(toggleLED); // Attach the toggle function
}

void toggleLED() {
    ledState = !ledState; // Toggle LED state
    digitalWrite(led, ledState); // Update the LED
}

void loop() {
    // Main loop does nothing; LED is toggled by the interrupt
}

Best Practices for Using Interrupts

  • Keep ISRs Short: Since ISRs block other interrupts, keep them as short as possible to avoid missing important events.
  • Use Volatile Variables: Declare shared variables used in both the ISR and the main program as volatile to prevent unexpected behavior due to compiler optimizations.
  • Avoid Delays and Complex Operations: Do not use functions like delay() inside an ISR, as this can lead to unpredictable behavior.
  • Debouncing: For mechanical switches, implement debouncing techniques to prevent multiple interrupts from a single button press.

Conclusion

Interrupts are a powerful tool in microcontroller programming that enhance the efficiency and responsiveness of applications. By understanding and implementing interrupts correctly, you can create more sophisticated and reliable embedded systems. Whether you’re building a simple project or a complex real-time application, mastering interrupts is essential for any aspiring developer in the field of embedded systems.

Leave a Reply

Your email address will not be published. Required fields are marked *