- Keep your Interrupt Service Routine (ISR) short. Ideally half a page of C code max. If you must use assembly code, keep it to one page max. Long ISRs cause timing problems, often in surprising ways.
- Keep ISR execution time very short. 100-200 clock cycles tops, although there is room for discussion on the exact number. If you have a lot of work to do, shovel the data into a holding buffer and let the main loop or a non-ISR task do the rest.
- Know the worst case ISR execution time so you can do real-time scheduling. Avoid loops, because these make worst case trickier, and an indefinite loop might hang once in a while due to something you didn't think of.
- Actually do the real time scheduling, which is a bit tricky because ISRs are non-preemptive within the same ISR priority level. (My book chapter on this works out the math in gory detail.)
- Don't waste time in an ISR (for example, don't put in a wait loop for some hardware response).
- Save the registers you modify if your hardware doesn't already do that for you. (Seems obvious, but if you have a lot of registers it might take a lot of testing to catch the one place where a register is used in the main code and the ISR clobbers it.)
- Acknowledge the interrupt source at the beginning of the ISR (right after you save registers). It makes code reviews easier if it is always in the same place.
- Don't re-enable interrupts within an ISR. That's just asking for subtle race condition and stack overflow problems.
There also some system-level issues having to do with playing well with ISRs:
- Make sure to disable interrupts when accessing a variable shared with an ISR. Do so for the shortest possible time. Do this even if you "know" it is safe (compiler optimizer behavior is difficult to predict, and code generation may change with a new compiler version). Ideally, protect those variables with access methods so you only have to get this right in one place in the code.
- Declare any shared ISR/non-ISR variables as volatile.
- When you do timing analysis, don't forget that ISRs consume time too.
- When you do stack depth analysis, don't forget worst-case stack-up of interrupts all occurring at the same time (especially if your processor supports multiple levels of interrupts).
- Make sure that all interrupt vectors are initialized, even if you don't plan on using them.
- Only use Non-Maskable Interrupts for a catastrophic system event such as system reset.
- Be sure to initialize all your interrupt-related data structures and hardware that can generate interrupts before you enable interrupts during the boot-up cycle.
- Once you turn on the watchdog timer, don't ever mask its interrupt.
- If you find yourself doing something weird within an ISR, go back and fix the root cause of the problem. Weird ISRs spell trouble.
If you've been bitten by an interrupt in a way that isn't covered above, let me know!
thanks for this post, in my eyes a good summary of what to consider when using interrupts (so basically everytime in embedded C, cause using interrupts is almost must-have).
Only thing I'm missing here:
How do you disable and enable interrupts in the rest of the code? (In case you have a few lines of code which shall not be interrupted).
I am thinking about this a lot, how to disable and enable interrupts. First approach was a function which influences the "global enable bit" which is available in almost all uC.
But I think this is not good, because the call of a function takes a few instructions and maybe right in this time an interrupt happens.
Then I thought, I should use a define which is pasting the line (disabling/enabling interrupts) directly into the code. Somehting like this:
#define DISABLE_ALL_INTERRUPTS (GIE=0)
#define ENABLE_ALL_INTERRUPTS (GIE=1)
What's your opinion on this?
I would like to hear your thoughts or your approach on how to work in the rest of the code.
PS: I hope this blog is even active...this post is a few years old...let's see....
Many newer development tool chains have a built-in function to enable/disable interrupts. Sometimes they are a macro, but it's even better to use an in-line function instead of a macro if you can do so. That should eliminate the call overhead you're concerned about.ReplyDelete
If you're disabling interrupts to access some shared resource, it's usually better to build an access function that also does the interrupt disable so you're less likely to forget to disable interrupts when accessing the shared resource.
There are other posts on this blog on macros and getting rid of global variables that touch on these topics.