What is the clean way to include an ISR in a library?

I have a decent know how about C and microcontrollers but have only read a book on C++. That book was not microcontroller oriented and does not speak about integrating Interrupt Service Routines (ISR’s) in a C++ Class definition.

I have written a program for a high end RC transmitter specialized in surface racing (cars, boats and robots). I have written that code originally in C (Atmel Studio so GCC) and recently also in BASIC (BASCOM). Now I want to port my code to the Arduino.

The core of an RC transmitter is a Pulse Place Modulation (PPM) generator. I want to implement that PPM-generator as a library that defines a C++ class PPM_generator. I want to offer that Library to the Arduino community so that other people can also easily develop their own transmitter code but also to make my code easy to maintain.

Ultimately the PPM_gererator would be used from my main program using something like this

#include <PPM_generator.h>
PPMG PPM_Generator;   //Create an object from the Class
PPMG.init(4);  	      //Initialise the PPM generator for 4 channels (8 would be the maximum)
PPMG.pulse (Channel, Length); //Set the pulse for channel “Channel” to Length “Length”

My PPM generator uses Timer1 that is configured for timer reset at compare match with OCR1A. It uses two ISR’s: One on OCR1A (to get a rock solid pulse train starting every 20 ms) and one on OCR1B that actually times the pulse length for each channel. Almost all the intelligence is inside these ISR’s so that is where all the variables are manipulated. (I have the working in an Arduino sketch)
In C it is common (since you cannot pass variables on to an ISR) to define the variables that are manipulated inside an ISR as global variables at the start of the program. I could do that here too and that works. However… I want to write clean code that is easy to maintain so I want to make the ISR’s use PRIVATE variables that are encapsulated inside the Class in the library. How do I do that?

Jan Huygh

make the ISR a friend function of the class so it can access protected data members. Class member functions cannot be ISR handlers, you will need some reference to an instance of the class that is accessible by the ISR, maybe have a static pointer in the class signifying which instance is currently being used.

If you only intend to have one instance running, you could even make the class a wrapper of static functionality ( comparable to a namespace ), no need to initialise an object then.

I would suggest having a quick look at either the pinchangeint library or the servo library both of these are well used class based libraries that include ISRs.

The basic technique is to define a static member function, any class member variables you wish to access inside the ISR should also be defined static. You can then call the static member from inside you ISR handler.

I have an example that is about as clean and simple as it gets here -

http://rcarduino.blogspot.com/2012/08/arduino-serial-servos.html

Duane B

rcarduino.blogspot.com

You probably meant it this way:

#include <PPM_Generator.h>
PPM_Generator PPMG;   //Create an object from the Class
PPMG.init(4);  	      //Initialise the PPM generator for 4 channels (8 would be the maximum)

It’s good practice to name the include file the same as the class…
… and to use the correct case for file names, it’s just windows that does not need it.

DuaneB: either the pinchangeint library or the servo library both of these are well used class based libraries that include ISRs.

Pinchange and software serial cannot be used together because they both have link-time definitions of how they want PCINT0..PCINT3 vectors set up. There are other similar interrupt vector collisions in different libraries. Pinchangeint does let you have compile time control of which vectors it takes over -- if you know. But software serial just uses them all.

SoftwaeeSerial.cpp

#if defined(PCINT0_vect)
ISR(PCINT0_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT1_vect)
ISR(PCINT1_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT2_vect)
ISR(PCINT2_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT3_vect)
ISR(PCINT3_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

PinChangeInt.h

#ifndef NO_PORTB_PINCHANGES
ISR(PCINT0_vect) {
    PCintPort::curr = portB.portInputReg; // version 1.6
    portB.PCint();
}
#endif

#ifndef NO_PORTC_PINCHANGES
ISR(PCINT1_vect) {
    PCintPort::curr = portC.portInputReg; // version 1.6
    portC.PCint();
}
#endif

#ifndef NO_PORTD_PINCHANGES
ISR(PCINT2_vect){ 
    PCintPort::curr = portD.portInputReg; // version 1.6
    portD.PCint();
}
#endif

At least PinChangeInt let's you strategically define NO_PORTx_PINCHANGES, if you need it to leave some vector alone. SoftwareSerial is not so friendly.

My advise is that, if you need to define a handler for one of the interrupts that "attachInterrupt()" doesn't know about, then please (1) document the dependency on what interrupt vectors you want, and (2) provide a means, as does pinchangeint library to give you some compile time control.

What might be really cool would be to have the wiring foundation extended to provide run-time controllable vectors for all the interrupts somehow.

@ pYro_65 Thank you for the input. Reading your answer I had to look up a few terms to understand you. (All that means is that I’m obviously not so advanced is C either). And indeed I intend to have only one instance so I will make the class a wrapper of static functionality and avoid that users need to initialize. But I will have to study a bit before I get that done.

@DuaneB Thank you for your input. I will study the pinchangeint and servo library in depth. I did look at your code and that was indeed comprehensive. Nice site by the way… there is more on your site that I find very interesting (e.g. transponder lap timer)

@michael_x You are right, that is how I intended it

@gardner For me your input is not so “on topic” BUT I do understand why you posted it… it will help others who read this topic based on the title of the topic.

@ALL I thought I had working code… but it turns out that my debugging help is needed in the code to make that one work... I will make another post on that to keep the title of the post correct. And when that is fixed I will return to this post to hopefully show may code in library form using your advice. (Assuming it will be helpful for others who try the same in the future)

Jan Huygh

Hi, The lap timer has turned out to be good fun, we are using it for a lap record contest our club track in Dubai.

Duane B

rcarduino.blogspot.com

DuaneB: I would suggest having a quick look at either the pinchangeint library or the servo library both of these are well used class based libraries that include ISRs.

The basic technique is to define a static member function, any class member variables you wish to access inside the ISR should also be defined static. You can then call the static member from inside you ISR handler.

I have an example that is about as clean and simple as it gets here -

http://rcarduino.blogspot.com/2012/08/arduino-serial-servos.html

Duane B

rcarduino.blogspot.com

Duane B, I hate to resurrect such an old thread, but I just want to thank you for this post. It has been immensely useful to me. I was really struggling with figuring out how to let an ISR access member variables too, without making them public, and I really like this technique. It is finally starting to make sense to me. Also, I finally have at least an upper-level understanding of how the servo library works now too, which is very nice.

What I found most useful from your link above was this right here:


// Timer1 Output Compare A interrupt service routine // call out class member function OCR1A_ISR so that we can // access out member variables ISR(TIMER1_COMPA_vect) { CRCArduinoSerialServos::OCR1A_ISR();

}

Simply seeing you make the member function "OCR1A_ISR()" public, and then calling that from the interrupt was the key for me. I plan on massively expanding my eRCaGuy_Timer2_Counter library (also linked here under the "Timing" heading), giving it a whole slew of hardware and software PWM and timed interrupt functionality, and now I will be able to use your technique above to make it happen.

Thanks again!

Gabriel Staples