I was recently involved in a discussion about suitable operations for interrupt handlers. Interrupts can be an intimidating and/or tricky area of embedded systems development, so I want to share some guidelines with you.
Generally you will want to keep your interrupt handlers as simple as possible. The normal flow of our program has been interrupted, so we need to handle the interruption as quickly as possible and return to normal operational flow (or go back to sleep).
Here are some general rules of thumb for operations to avoid:
- Don’t declare any non-static variables inside the handler
- Avoid blocking function calls
- Avoid non-reentrant function calls
- Avoid any processing that takes non-trivial time
- Avoid operations with locks as you can deadlock your program in an ISR
- Avoid operations that involve dynamic memory allocations
- The implementation may require a lock
- The allocation typically takes a non-determinate amount of time
Depending on your architecture and operational model, your interrupt handler may utilize the stack of the interrupted thread or a common “interrupt stack”. Regardless of the model, I advise you to be parsimonious and to avoid stack allocations inside your interrupt handler.
In most cases, there should be little-to-no processing inside of the handler. If I am using an RTOS on my embedded system, my interrupt handlers set an event flag (or semaphore) and clear the interrupt state. The event flag will be tied to a processing thread which wakes up and handles the event in user space.
These guidelines have their exceptions, of course. If you are not using your RTOS, it is likely that your program flow is either a run loop or entirely interrupt driven. It is highly likely that locks and dynamic memory allocation aren’t utilized on those systems, reducing deadlock risk. For both of these cases, you may need to use heavier operations inside of the interrupt handler.
Do you have any useful rules of thumb for interrupt handlers? Leave a comment!
References
- Making Embedded Systems: Design Patterns for Great Software, 2nd edition, by Elecia White
We’ve already seen the most important rules surrounding ISRs: Keep ISRs short because longer ones increase your system latency. Generally avoid function calls, which may have hidden depths and increase overhead. Don’t call nonreentrant functions (such as printf), because global variables can be corrupted by interrupts. Turn off other interrupts during the ISR to avoid priority inversion problems.

It’s not the guideline but a kind of tip.
In the past i had supported the technical issue from customer.
They have designed to handle simply the both edges of data as gpio interrupt with other interrupts in single ISR.
Due to too many input stream data, sometimes ISR lost some bits data.
Even if this architecture of handling whole data ISR, there was no time to change the architecture.
So ive changed if-else statement sequence as the frequent and many input data.
So the sequence of if-else statement in ISR act as a kind of interrupt prioprity to handle several interrupts.
Hi Eddie,
I have utilized if statements in my ISR handlers as well.
Thank you for sharing your experience.
One question I have about ISR coding guidelines:
Is it okay to delete dynamically allocated objects (C++) in an ISR?
My use case is a timer ISR-driven bit-banging implementation. It’s supposed to be protocol agnostic. One can form precisely-timed pin toggles using a queue that pairs a function pointer with a microsecond delay. The ISR pops the queue, calls the function pointer (toggling a pin), and then deletes the dynamically allocated function/delay object.
I would assume it’s fine in this case since it’s a lightweight object that’s more like a C struct. But I’m worried about the potential overhead the C++ delete call incurs, if any.
Freeing dynamically allocated memory in an ISR is not usually safe. This is because many dynamic memory implementations utilize some sort of lock, which you may or may not be able to safely grab from an interrupt.
A better approach would be to have your ISR signal a thread or dispatch a function which performs the actual queue pop / call / delete operation. This would keep your ISR as minimal as possible: handle the interrupt + signal.
One of the trickiest things to watch out for are subtle issues introduced by the compiler that aren’t visible in the interrupt handler source code. I once worked on a system where a simple multiply by power of 2 got translated into a library function at the assembler level. The library function was not reentrant! It used a static variable that would occasionally get stomped on. The interrupt handler worked fine, but background functions that used multiplication occasionally had weird errors that couldn’t be explained. That one was a bear to track down.