What I can and can't do in an interrupt service routine?

Status
Not open for further replies.

Gibbedy

Well-known member
Hello.
I've been working on an LED Cube.
I'm driving my led matrix with i2c io expander boards MCP23017's.

I'm using a nano at the moment and will be switching to a teensy but I think the problems I'm running into will be the same....

I was hoping to run my code for generating cube state in the loop() and just have an ISR display the cube at specified intervals by writing to i2c io expander chips, however this does not work. I believe there are limitations to what can be done inside interrupt service routines.

I can successfully use ISR to set a bool 'updateCube' flag, and catch this in my loop(), however now I must insure checks on this flag are done inside my cube code.

If anyone has any suggestions how to proceed feel free to comment.
Thanks.
 
In general, do as little as possible in an interrupt routine. You do NOT want to do anything blocking (such as delay, writing to i2c which in turn uses delay), etc. You don't want to call anything that uses malloc/new to allocate memory.

If you are setting a variable, be sure to add the keyword 'volatile' to the declaration. This tells the compiler that the value can change due to outside forces, and to not to optimize references to the variable.

If your cube code is done in 'loop' and it returns after each iteration, just add checks for 'updateCube' being true at the top of 'loop'. Be sure to reset the variable back to false when you do the matrix. If your 'loop' function does not return, you would need to add the appropriate checks within your code.
 
I update my LED displays using intervaltimer. These interrupts can be fairly large but it is essential they happen at a regular interval

I used to update their state in loop() but now I use an interrupt to trigger a DMA transfer

With the Teensy 3.x you can set interrupt priorities which allow you to have control over nested interrupts. This functionality is not avaliable to the nano
 
I will look into dma transfer.
I have been using timerone.
Edit:any pointers on where to start on this.
I'm starting at Wikipedia for dma transfer which is a bad sign, but it's getting me excited.
Thankyou.
 
Last edited:
be sure to declare variables accessed by both ISR and app as volatile.
And on 8 bit AVRs, multibyte variables like int and long need special code to prevent an interrupt in the midst of loads and stores.

ISR should rarely / never call a function or library unless you really know what you are doing (reentrant, execution time, side effects) and that called function was designed to be "ISR safe".
 
I'm driving my led matrix with i2c io expander boards MCP23017's.

I'm using a nano at the moment...

...have an ISR display the cube at specified intervals by writing to i2c io expander chips, however this does not work.

On AVR, the Wire library master-mode functions can not be used from within an ISR. Those functions depend on using the I2C ISR, which can't run if your ISR is running and has the global interrupt bit disabled. You could try re-enabling interrupts, but on AVR that's a very risky design, because your code can get stuck in an infinitely recursive interrupt loop if your interrupt occurs again before you're done.

On Teensy LC & 3.2, this might/should work, because those functions don't depend on an interrupt. Honestly though, very little testing of the Wire lib has been done with usage from within an interrupt, partly because few people ever do that, and mostly because no such code exists in the Arduino world because Arduino's AVR Wire library locks up in that case!
 
In general, do as little as possible in an interrupt routine.

This is worth repeating. In many cases, you can have the interrupt routine set a flag and then have the foreground routine act on the flag. In some cases, DMA works, usually even better than an ISR.

On the other hand, reading/toggling a pin or two and some small delays (microseconds) works fine in an ISR.
 
Last edited:
I was wanting to keep my object orientated code for my led/cube and transformations separate from the hardware.

I'll plug in my teensy 3.2 and see what happens.
 
Object oriented code is fine, but accessing ISR shared variables from an app's object instance is a special case due to name mangling.

And ISRs are usually written in C for the same reason.. linker issues.

It can be done but you'll need special coding techniques.
 
PJRC has pulled in code I did twice that exposed the CPP static *this data to an ISR - based on the method FrankB showed me. This note is speculative - and isn't meant to say anything else written above isn't true or important.

TouchScreen case just sets a flag used in the normal CPP calls to modify behavior when the interrupt detects a touch. The most recent in Talkie does more (time critical) work than it should during a 125 us timer interrupt - but made the code non blocking keeping the sound playing when it is called after every 200 interrupts - extending that timer interrupt - removing what was a blocking delay(25) for the duration of the sound playback when the '.say()' code was called to render speech. It ran fine on T_LC and T_3.2 at 24 MHz, but it was written for an 8 bit AVR when PJRC found it where it may not run right as it stands.

So you can do minimal work setting a flag or needed object structure updates in an ISR - but any real work - like actually calling out on the SPI bus based on a touch - is left to the core methods when the user calls from loop().

Reading PJRC post #7 above - perhaps on Teensy - In your case taking time to talk to I2C might be pushing it - and will be trouble if any place else in the code those I2C lines are ever in use when the isr() code interrupts. If all I2C calls were queued for a single isr() - and nothing else was time critical and the Teensy wire library tests out ? ? ?
 
Accessing ISR variables in an object can be done ... its fiddly. Basically, the way I got it to work was public static volatile variables in the object description. Casting might work. I'm trying it tonight ...

Anyway, 'mangling' is a good description of it.... and while the code works, I'm sure there are few things that could be done to make it a bit better...

In the .h

Code:
Class Switches{
public:
static volatile uint16_t pressed;
void read ();
}
then in global space in the .ino

Code:
volatile uint16_t Switches::pressed = 0;

in the ISR in global space in the ino

Code:
void SwitchesISR (void)
{
Switches::pressed = digitalRead(6);
}

Then in the Switches::read method (in the .cpp):

Code:
void Switches::read()
{
   noInterrupts(); 
   int pre = Switches::pressed; 
   interrupts(); 
 //blahblahblah
}
 
Last edited:
name mangling is the popular term for what a C++ compiler does to make class instances' statics (funcs, variables) unique.

Look at the linker load map sometime.
 
Links above p#12 points to two working solutions that copy this to a global and are just pointers to structure data.
 
I'll look at your pointer solution .... The more I use arduino ide and classes in C++ the more I reaslise I need to get to grips with compiler linking .... wandering off topic, and sorry for hijack
 
Hang it... I'm hijacking.... @defragster ... the pointer method is very elegant. Talkie is the gift that keeps on giving ... your code is very instructive ...I couldn't work it out until I saw the "this" in the main sayQ method ... a pointer to the invoking object. brilliant ...the pieces fall in place (I think ...) ...

you define a global static pointer of type "Class", and then within a "Class" method, assign "this" to the pointer. So the global static pointer points to the invoking object. The global static pointer is also visible to the ISR.

In the ISR another pointer "O" of type "Class" is defined and the global static pointer is assigned to it.... I think that a new pointer "O" of type "Class" has to be defined in the ISR because of scope ?

then using the pointer syntax "O-> " methods etc of the invoking object may be used / 'affected' in the 'real world outside the ISR, from within the ISR ...

I notice that an IntervalTimer is instanced inside a "Class" method. That was a missing piece of the puzzle for me in my own work ... its very obvious now I think about it (and solves this silly scope issue I had) .... I actually did that in a couple of places in a header file (objects as members of a different class), but kind of forgot about it for IntervalTimer.

Why do you use "IntervalTimer *t = new IntervalTimer()"; instead of "IntervalTimer t"??; Any particular reason? Is it heap vs stack??
 
Maye it won't count as a hijack if it helps @Gibbedy finds it can help the needed case.

Glad you got to the link and found useful guidance. Again that was passed to me by FrankB so I could do the same class memory object reference, first in the Touch code.

There are pieces in the .h file and the .cpp files. The key is the static copy of the 'this' pointer - saved globally during init/begin and then used in the isr() code as needed. Any code called has to be set public as the compiler rejects even pointer access to private code outside the class.

As far as details outside that - I have nothing definitive to say. I started making my own timer to run the 25ms data refresh - but that wasn't good as IntervalTimer()'s use limited hardware resources (doubly so as hardcoded for AVR)- so I just counted 200 calls of the faster timer. Since it is under "#elif defined(__arm__) && defined(CORE_TEENSY)" that was PJRC in making it work on Teensy. As far as speculation: using 'new' makes a globally static place on the heap for the instance data of the IntervalTimer to be stored that is not created until called at runtime. Doing it as shown in P#17 would create an instance via compile time on the stack when setup, if done static? Talkie is tied to a single hardware resource so multiple instances wouldn't apply - new works in that case and assume it would have to the other way as well.
 
Thanks ... good point about the calls being to public members etc.

I am finding the ISR code in Talkie to be beyond me, but like I said it is the gift that keeps giving....

I'm confused by the code "#define ISR(f) void f(void)" ...

What does it mean for the code "ISR(TIMER1_COMPA_vect)" ??

I thought "ISR(TIMER1_COMPA_vect)" is a macro?

Is "void TIMER1_COMPA_vect(void)" the result of the #define, and if so, what does that mean?

I get the the interval timer ...it calls "timerInterrupt" every 125us ....

but what does "#define ISR_RATIO (25000/ (1000000.0f / (float)FS) )" do???

Replace ISR_RATIO with 200? I think I may have missed how that is used to count a clock??

I am reading up on preprocessor directives, but that is not telling me how all this works .... Please take mercy on me and give me a hint (or a cuss if you can't be bothered ... I understand I should be doing a bit more to help myself... )
 
/*As for my particular case,unrelated to my original question, I have been unable to get my nano and i2c output expanders to display the correct data they are given if I use timerOne (with isr doing nothing)....weird.*/
edit: bug in my cube code. Array out of bounds.


Teensy from here on in.
I've converted my board to take a teensy 3.2.
From what I haven't understood in this thread I've decided to leave isr alone unless updating some volitile counter variable.

Feel free to keep discussion going.
And thanks all.
 
Last edited:
Status
Not open for further replies.
Back
Top