MicrocontrollerMicros

Arduino Trick: How To Share Interrupt Service Routines(ISR) between Libraries and Application

The Problem

One limitation of using Arduino core and libraries is the collision that may appear when using some libraries and explicitly the collision of using interrupts (ISR). This could occur when a library uses an interrupt service routine inside its source code, and the developer later  may need to use the same ISR for another reason which will lead to  a compilation error because of defining the same ISR twice:

(.text+0x0): multiple definition of `__vector_x

For example: if SoftwareSerial, the built-in Arduino core library, is going to be included in the main sketch, then any of the three available ISRs for PCINT (Pin Change Interrupt) can’t be reused later (in Atmega328 for instance). These are used for detecting the change of input logical level (rise or fall) and a good amount of applications need both; the SoftwareSerial and PCINT ISRs as well. There are many other examples for losing access  to very common and used ISRs like timers ISRs.

In this micro-blog, we’re going to discuss a suggested solution to keep the spirit of simplicity which Arduino SDK provides without losing every ISR a library use. This can be applied to the built-in libraries and core files and also to the future developed third-party libraries.

Suggested Solution: Handler (Hook) Functions

It wouldn’t be an elegant way to define the ISR by developer in the main application as the library must be a one-stop solution. The idea is to add a way to extend the ISR code later by adding a handler (A.K.A hook or call-back) function. This can be done by adding hook or handler functions available to be attached to any defined function by developer in the main code.

Let’s have an example library: a demo library for Arduino UNO that defines an ISR for TIMER1_COMPA_vect to make interrupt every 1 second using Timer1. We will define an ISR and add something to it to make it extendable later.

This will be done by using what’s called a function pointer in C. It’s a pointer that points to a function instead of a variable. To know more about function pointers in C, read Geeksforgeeks handy introduction.

To implement this in a demo library:

1- Define a function pointer in the header

2- Add a method in the library class to assign the function defined in the developer main code to the previous variable.

3- Add a call to the function pointer in the ISR.

Complete Demo Example

demoLib.h

demoLib.cpp

Application.ino

Real Examples

  • Wire library shipped with Arduino core use this trick to enable the developer to define his/her own on receive/request handlers for I2C bus. See: Wire.onReceive(handler) & Wire.onRequest(handler).
  • Some third-party libraries like Timer library which implements a basic scheduler. The basic idea behind this library is also to define function pointers and process them inside the timer ISR and decide which handler(task) should be executed in each interrupt.

Yahya Tawil

Embedded Hardware Engineer interested in open hardware and was born in the same year as Linux. Yahya is the editor-in-chief of Atadiat and believes in the importance of sharing free, practical, spam-free and high quality written content with others. His experience with Embedded Systems includes developing firmware with bare-metal C and Arduino, designing PCB&schematic and content creation.

Related Articles

7 Comments

  1. Another option to implement such a hook is a “weak function”. An example is yield() in https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/hooks.c where yield is defined as:

    void yield(void) __attribute__ ((weak, alias(“__empty”)));

    If an application define yield, the applications yield() will be used instead of __empty(). The decision is made by the compiler at compile time and no extra variable to store the callback is needed.

    1. Thanks for the input Jens.

      You’re right, but sometimes you may need a special function for each interrupt according to the context. Another concern, yield function is called by the delay function in Arduino core (wiring.c).

      1. I was imprecise. “yield” is just an example for a weak function used in arduino.
        The idea is to use a weak function for __timer1Hook instead of a function pointer.

        demoLib.cpp:
        void timer1Hook(void) __attribute__ ((weak, alias(“__empty”)));
        ISR (TIMER1_COMPA_vect)
        {
        Serial.println(“TIMER1 ISR”);
        timer1Hook();
        }

        And an application could “register” a hook just by defining timer1Hook:
        Application.ino:

        void timer1Hook(void) {
        // will be called (if defined) from TIMER1_COMPA_vect without a registration like “t1.timer1Hook(timer1extend);”
        Serial.println(“Extended”);
        }

  2. One problem with this is that calling a function from an ISR on AVR is significantly more expensive that putting the code directly into the ISR (even without redirectability.) For example, given the C program:
    volatile int a;
    extern void incrementb();
    ISR(INT0_vect) {
    a++;
    }
    ISR(INT1_vect) {
    incrementb();
    }

    The INT0 is ISR is about 20 instructions, while the INT1 ISR is about 35 instructions (and hasn’t even done the increment.)
    This is because of the AVR ABI, which says about a dozen registers need no be saved by a called function. When an ISR happens, if the ISR KNOWS it doesn’t use those registers at all, it doesn’t need to save/restore them, either. But as soon as it calls some other function, it has to assume that that function MIGHT change one of those registers, which means an extra dozen push and pop instructions 🙁

    Aside from that, you’ve also described how the arduino implements “attachInterrupt()”, and that code might be used as an example as well.

    1. 100% true. Thansks for your input.

      Many aspects of Arduino waste the MCU resources especially functions related to GPIO. That’s why I avoid talking about optimization when I talk about Arduino APIs =).

    2. Really? Are you kidding me? Are you talking about high performance in the Arduino platform? LOL. For what kind of applications using Arduino 15 machine cycles “is more expensive”, 4k processing? Come’on.

      Both solutions, callbacks and with the week isr, are nice. Why didn’t Arduino’s designers takes this need into consideration? For any embedded system a tick is a must.

Leave a Reply

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