Don’t Interrupt Me!

Resources

Don’t Interrupt Me!

An interrupt is a software routine which is executed whenever a specified hardware event occurs. They are often used in real-time microcontroller based subsystems of medical devices to ensure that the data associated with an event is processed in a timely manner. Typically the microcontroller has many event sources such as UARTs, ADCs, SPI ports, digital IO, etc. Often handling of all of these events using separate interrupts can lead to a bunch of problems.

Many interrupts may inject large, unpredictable latencies in the processing of other code. Each hardware system has a minimum rate that it must be serviced for it to continue working properly but when a number of event sources fire close together, there may not be sufficient time to service them. Bugs associated with the interactions of multiple interrupts often occur infrequently making bug hunting difficult. Debugging with a scope can be difficult: triggering is challenging and the data tends to jump around wildly even under normal circumstances. It’s difficult to analyze all the ways that may lead to problems. Finally, determining the true execution time of an interrupt is difficult, either by analysis or measurement.

So what can we do? Most event sources can be analysed ahead of time to determine their minimum required servicing rate under worst case conditions. Instead of having a separate interrupt for each event source, we can have one, timer-based interrupt running at a rate chosen so that all event sources can be processed. The rest of your code – which is usually slow-to-execute stuff like display routines or big calculations – can happen in your mainline loop.

This one-interrupt system is totally deterministic. Each event source is polled at the same time in the cycle. When you scope, the servicing code is rock steady on the trace. It’s relatively easy to tell if some of the servicing code is taking too long for its allotted time because the next cycle of the interrupt will be late. You’ll see this easily on the scope and it is also readily tested and flagged within the firmware. It’s even possible to cycle-count every possible path through the assembly or disassembly to analytically determine absolutely if there is enough time.

One thing you have to watch out for even with this strategy is that there is enough time to execute your mainline code as well. I find that a good rule of thumb is to aim for the microcontroller to spend about half of its time running interrupts and half on the mainline code. Additionally, that mainline code must loop sufficiently quickly under all circumstances so that each of its constituent routines gets the airtime it requires. For example, you can’t send a whole page of text off to your LCD and still expect your PID code to perform as expected.

That’s not to say that the one-interrupt strategy is foolproof or is suitable for all circumstances. I would say though, if you find you have to use more than one interrupt, make sure you think very carefully about all the circumstances where they might run back to back and whether that leaves them enough time so they don’t miss out on the next event.

I have used this one-interrupt architecture in every firmware project I have ever worked on and found every time that I’ve been able to squeeze just about every last drop of goodness from my microcontroller and debugging has been straight forward.