Standard advice on ISRs from the forums often includes one or more of the following:
- avoid putting too much inside an ISR - keep them lean and mean
- simply set a flag of some kind in the ISR, then poll that flag in the main loop and do the bulk of any processing required there
- dont use function calls in the ISR
- you cant use anything that uses interrupts inside the ISR, including importantly, timing functons like millis(), and
- comms using the Serial class
So why is this advice being offered, and does it always apply?
The argument seems to run t like this. Some sources of interrupts are random in the sense that there is no telling when or how often they will occur. There is therefore no guarantee that the ISR will have finished servicing one interrupt before the next interrupt of the same type occurs. To handle fully this situation of 'overlapping' interrupts within the ISR itself, the ISR code would have to be re-entrant. Since ISRs are tricky to code/debug at the best of times, and writing safe re-entrant code is even more difficult, so keep things as simple as possible by disabling interrupts inside ISRs.
Occasionally an incoming interrupt that follows close on the heels of a similar interrupt will be missed while you are busy processing the first interrupt, but in many cases this is acceptable. This is presumably the reason that the AVR processor has been designed to automatically disable further interrupts just before calling an ISR.
As a result, on the Arduino, unless you take counter-measures, all interrupts arriving during the processing of an ISR will supposedly be lost (unless they persist in some way until after the ISR has finished processing).
In that light, the standard advice starts looking very wise:
keep ISRs short (or you will miss even more interrupts)
don't use functions (they may contain code that uses interrupts, and this will fail)
dont use millis, or Serial in an ISR since these use interrupts
But notice the large price you are paying for adopting this so-called 'safe' approach
occasional lost interrupts (lost accuracy?)
occasional loss of characters during serial communications (now you may have to protect more thoroughly against
dropped characters, eg more complex protocol, slower comms)
inability to use standard timing mechanisms inside ISRs (may mean resorting to custom built timinig mechanisms)
inability to use standard comms inside ISRs (makes debugging ISRs much more difficult, may have to resort to flashing LEDs etc)
inaccuracy of timing mechanisms outside ISRs (since the millis() will not be counting while the ISR is running, and
will therefore always be running slow, by an undetermined amount)
loss of structure (avoiding function calls), and/or redundant coding (replacing function calls) for fear of using
function calls inside the ISR
In some cases, you may nevertheless conclude that this is the right solution on balance.
In many situations, the nature of the interrupts involved is such that they are guaranteed to arrive serially (one at a time), and sufficiently spaced out so that concurrent invocation of the ISRs is never an issue.
Take the situation where there are only two types of interrupts: interrupts from a timer (A), and interrupts from a serial interface (B). You may get an A and a B arriving at the same time, but never two As or two Bs at the same time. Since the interrupts from any one source are guaranteed to arrive serially, you don't require re-entrant code in the ISR for either type of interrupt. Consequently you dont require the 'simplification' of switching off further interrupts in the ISR.
You can't stop the AVR chip from switching off interrupts before calling an ISR, but you CAN switch them back on again as soon as you're in the ISR (simply make sei(); the first instruction of your ISR.)
If this situation applies to you (and I believe it to be quite common) you're life as an ISR writer will be greatly enhanced:
- you'll very rarely miss an interrupt (the time when interrupts are disabled will be extremely small), and so you either don't have to cope with that possibility, (either in terms of extra code, or lost precision) or the impacts of occasional missed interrupts are greatly reduced
- you can start using normal timing mechanisms like millis() for timing inside your ISR
- your millis timer will not run slow
- comms using Serial outside the ISR will be unaffected (since the ISR involved with Serial can now interrupt your custom ISR)
- you can use Serial comms within the ISR, whicjh is especially valuable for debugging
- you can safely use most function calls within the ISR without worrying about any embedded use of interrupts
- you dont have to worry unduly about the size of the ISR - since you're no longer under the same time pressure, and you're not missing interrupts
In fact you can see that it is the very act of inhibiting further interrupts that is making the writing of ISRs seem difficult - by keeping interrupts enabled, most of the problems evaporate.
However it all hinges on the nature of the interrupts you are trying to handle:
with random interrupts there is a real issue to solve: you can either accept the processor default (further interrupts inhibited) or you can write re-entrant code (tricky);
with serial, spaced out interrupts, there is no big issue - why not re-enable interrupts at the start of the ISR and then just programme normally.
I know it's heresy, and I'm expecting a fatwa, but I'm old and ugly - bring it on.