Mostly any embbeded system is an event-driven one, where an ISR is the event producer and the user-code is the event consumer. In this kind of system architecture it’s very important to keep the whole system responsive, whether it’s hard or soft real time. In order to accomplish such a goal we must a find out a way to adhere to the following well-known axiom among embedded/CS engineers:
An ISR should execute as quickly as possible
If we’re eager enough to fail to that axiom then we might want to process everything inside the ISR. Bad idea. When in an ISR the CPU is not able (most of the time) to process anything but the actual ISR-code, so the system will certainly lose important events. Figure 1 shows the most common escenario:
A way to adhere to the axiom is to deferred the heavy and time-consuming code out of the ISR to a task level, and synchronizing them through a (non blocking) semaphore-like mechanism:
It would be worthwhile to point out not to use a blocking code inside ISR (e.g. waiting for an event from another ISR). And following this fashion the synchronizing mechanism must be a non-blocking one as well. Otherwise the system will become stuck.
Til this point I haven’t mentioned anything related to a RTOS. How does all this task deferred stuff concern to a (real time) kernel? Well, the key is in Figure 2 with a noticeable modification in the synchronizing mechanism. As we’ll be using independent tasks (or processes) the synchronizing mechanism can (and must) be a blocking semaphore, so a task blocks itself until a new event is triggered, releasing the CPU for other tasks.
Instead of messing around with the task deferred stuff, we might create a different priority level for each task in our system, so if we have 10 tasks competing for the CPU, then we create 10 different priority levels. Saddly, is not that easy all of the time. Take as an example the very popular and open-source RTOS: FreeRTOS. It creates one linked-list for each priority level, so in such an imaginary scenario we’ll end up with 10 linked-lists. (FreeRTOS supports both pre-emptive and round-robin schedulers, while other RTOS only support one of them, or implements a different approachs.)
So the proposal here is to have three priority levels: hi, normal and low, and take advantage of the fact that FreeRTOS offers pre-emptive and round-robin schedulers mechanisms at the same time (ChibiOS, another popular and open-source RTOS, doesn’t use the round-robin scheduler):
- The deferred code must reside in the highest priotity level, so as soon as the ISR signals (through a semaphore) a new event and exits, the RTOS scheduler will search and execute the corresponding code at a task level, not an ISR level.
- The not very critical user code and regular user application should reside in the normal priority level. Some tasks at this level will be waiting for messages from the highest priority task before it executes, while other tasks will be executing a regular code like printing, reading.
- In the lowest priotity level we might place code that its no execution won’t hurt the system (or anybody), like blinking leds, battery status or non-critical house-keeping.
Using this scheme will let us organize our application in a very concise way, without worrying about the number of priotity levels, and saving precious resources, like RAM.