Don’t threat me!
When working on a class which can handle bus arbitration on an embedded system a thought occurred to me: is an interrupt of the microcontroller a thread or something else?
In simple form: an interrupt can be seen as a signal to the microcontroller to suspend the current instruction sequence and (temporarily) do something else. This “else” is the ISR (interrupt service routine) which is typically used to handle some work related to the source of the interrupt. When the ISR finishes, the suspended work is resumed. An example would be a GPIO (General Purpose Input/Output) or pin interrupt from a button, where the handling could be to turn on a LED or sound a buzzer for a short while (*).
This can be made a bit more advanced by introducing priorities: allow a higher (or lower) priority interrupt to interrupt the currently running interrupt – also by suspending that one. Such a mechanism would allow more fine-grained control over the order in which the interrupts are handled.
Another useful feature is that a microcontroller which is put into a sleep mode can be woken by an interrupt. Knowing that most microcontrollers are battery powered, being able to let a microcontroller sleep to save power until something needs to be done (a peripheral, GPIO pin interrupting and thus waking the device) can be quite handy.
From computer language point of view the interrupts are quite similar to threads. A thread here can be seen as a sequence of instructions, or a function handling some work. On a multi core processor such a thread can run in parallel with other code being executed, but on a single core processor it will have to suspend the currently running code to be able to run. Usually an operating system will handle this scheduling: suspending code to run a task for a given amount of time and then resume previous task.
Threads can also be given a priority – should the scheduler support this. It functions quite similar to the interrupt priorities above: a higher priority thread will get preference over a lower priority one.
But what would happen if you run a thread on a microcontroller and get an interrupt – the button, for example? Since interrupts are handled by the hardware of the microcontroller they will get preference over the threads, even the highest priority thread. This means that synchronization constructs used with threads (or rather concurrency) are of little use here. Only when interrupts are (temporarily) blocked from occurring – by telling the hardware “not now” can we use these safely again.
Usually a microcontroller can disable interrupts globally, this means the configured interrupts still occur, but the code which should handle them is not (yet) executed. Only after they are globally enabled this link is restored and interrupts work as described before. By globally disabling interrupts (for a short while) we can ‘flatten’ their behaviour, or rather prevent a switch to a different interrupt context for the time the interrupts are globally disabled. This leaves the microcontroller with only ‘regular’ threads to worry about, meaning constructs as ‘atomic’ and ‘mutex’ work as expected – they are not ‘bypassed’ by the interrupts. A word of caution: while interrupts are globally disabled, they can still occur, but are not handled. It could be a new interrupt occurs before the (delayed) first was handled – masking the first interrupt entirely. As general advice: always keep interrupts short, and try to disable global interrupts as seldom as possible – if so do it for a short amount of time (to prevent the masking).
An example where this technique is used in practice is in an Arbiter class. Imagine an application which communicates with 2 or more peripherals on an I2C bus. These devices are represented as class objects with specific behaviour, communicating via a common I2C driver. To prevent overlapping communication from these 2 classes, an Arbiter class controls when these transactions over the I2C bus take place. An article over this bus arbitration can be found here, together with example code.
(*) Note: to be more correct: signal that an interrupt has occurred, then handle the work ‘later’, outside the ISR context.