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

static void (*__timer1Hook)(void);

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

void Timer1::timer1Hook(void (*function)(void))
{
__timer1Hook = function;
}

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

ISR (TIMER1_COMPA_vect)
{
 Serial.println("TIMER1 ISR");
 __timer1Hook();
}

Complete Demo Example

demoLib.h

#ifndef demoLib_h
#define demoLib_h

#if (ARDUINO >= 100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

static void (*__timer1Hook)(void);

class Timer1
{
 public:
 void start();
 void timer1Hook(void (*function)(void));
 private:
 void t1Init(void);
};

extern Timer1 t1;
#endif

demoLib.cpp

#include"demoLib.h"

Timer1 t1;
void Timer1::start()
{
this->t1Init();
}

void Timer1::timer1Hook(void (*function)(void))
{
__timer1Hook = function;
}

void Timer1::t1Init()
{
 TCCR1B = (1<<WGM12) |(1<<CS12)|(1<<CS10) ;
 TCCR1A = 0;  
 TCNT1 = 0;
 OCR1A = 15625; // T/C match it evey 1000ms
 TIMSK1  =   (1<<OCIE1A) | (1<<ICIE1) ;      
}

ISR (TIMER1_COMPA_vect)
{
 Serial.println("TIMER1 ISR");
 __timer1Hook();
}

Application.ino

#include "demoLib.h"

void timer1extend()
{
 Serial.println("Extended");
}


void setup() {
 // put your setup code here, to run once:
 Serial.begin(9600);
 t1.start();
 t1.timer1Hook(timer1extend);
}


void loop() {
 // put your main code here, to run repeatedly:
}

Real Examples

Exit mobile version