How (not to) shoot yourself in the foot, using interrupts?
Submitted By: Jan Waclawek FAQ Last Modified: 09/26/07
- Interrupts are a very powerful tool of the embedded programmer, but, as it goes with very powerful tools in general, can very easily go wrong if not handled carefully. Symptoms range from "complete no-go" through "unexpected behaviour" up to "fails once in a month".
- Save all relevant registers (and perhaps other variables) which are modified in the interrupt. The common practice is to PUSH the registers to stack at the beginning of the ISR and POP them back at the end. Don't forget about PSW - many instruction change it (most importantly the carry flag) as a byproduct. It's a good practice to start each ISR at least by PUSH PSW then PUSH ACC (and of course, end by POP ACC, POP PSW). Don't forget that you must have one POP for each PUSH (for quick check: verify that there is the same number of PUSHes and POPs), and that the registers are POPed in reverse order than they have been PUSHed.
- Interrupts in '51 are to be ended by RETI rather than RET. Read the relevant parts of "bible" for further explanation.
- Use only one exit point from the ISR, containing all the required POPs and the RETI.
- Observe atomicity issues. If you share a variable both in "main" and "interrupt", the best practice is, when main wants to access this particular variable, to temporarily disable interrupts (or only the relevant interrupt) then restore (not reenable) them. It is also a good idea, to reduce the time while interrupts are disabled, keep the "shared" variables as short as possible (e.g. a single byte (char), where possible) (but this is a good idea in microcontrollers anyway).
- Keep your interrupt routines as short (more precisely: fast) as possible. Set flags, move bytes to/from SFR, and exit; leave the most of the work to be done for the main loop. This also means not to call subroutines from interrupt if not absolutely necessary.
- If you absolutely MUST call subroutines from an interrupt (which is a bad idea anyway), don't call the same subroutine from main (unless the subroutine is written in a special way, enabling reentrancy).
- Clear the flag causing the interrupt, if it is not cleared automatically by hardware. If there are multiple flags bound to a single interrupt vector, don't forget to check and clear all of them. Once you set up an interrupt, don't do interrupts flags setting/checking in "main", or mixed - both in main and interrupt.
- Observe the stack. Microcontrollers have limited stack space, and if interrupts are used, it is not unlikely that a stack overflow occurs due to interrupt, when the interrupt happens during a deep instruction call chain, and the ISR attempts to store the registers. Stack overflows are hard to discover and often lead to the hard-to-debug intermittent errors. You should calculate (or at least perform a qualified estimate) the worst-case stack usage of the main program and add the worst-case stack usage of all possibly nested interrupts. This is a hard task for those who use HLL (especially if the toolchain does not have tools to help calculate worst-case stack usage), nevertheless vital for stability of any but the most trivial application.
- If you share (pass) variables in interrupt and "main", and use C or other high-level language, don't forget to label the variable as volatile (or the equivalent in other HLL). This tells the compiler that the variable can be changed in an another function so it cannot optimise it out to a register or disregard completely.
- Compilers are written by humans - neither of them is perfect - and can not account for all posible combination of operations. If the ISR is anything more complicated than trivial, it is perhaps a good idea to check the result of compilation (assembler listing).
Although in programming nothing is forbidden, there are several rules, which make programming with interrupts safer and easier.
The first few rules apply only for those who program in assembly language. The microcontroller-specific compilers of high-level languages, such as C, usually have extensions (special keywords) using which a function can be assigned as an interrupt service routine (ISR). The compiler then generates a prologue and epilogue to that routine, which takes care of register storing/restorin (PUSHing/POPing) etc.; so that the programmer does not need to take care of these.
The following rules apply equally to those who use assembly language and those using HLL:
The following hints are HLL-specific:
See also the basic interrupt tutorial.
Add Information to this FAQ: If you have additional information or alternative solutions to this question, you may add to this FAQ by clicking here.