Embedded real-time systems have to take actions in response to events that originate from the environment. Non-trivial systems will have to service events that originate from multiple sources, all of which will have differing processing overhead  a response time requirements.

  1. How should the event be detected (polling or interrupts)
  2. When interrupts are used, how much processing should be performed inside the ISR
  3. How can events can be communicate to the main code

Binary Semaphores

Binary semaphores can be used to unblock a task each time a particular interrupt occurs, effectively synchronizing the task with the interrupt. This approach allows the majority of the interrupt event processing to be implemented within a synchronized task, with only a very fast and short portion mainly in the ISR.

The synchronized task is called Handler Task. If the interrupt processing is particular time critical, then the handler task priority can be set to ensure that the handler task always preempts the other tasks in the system. This has the effect of ensuring that the entire event processing executes continuously in time.

Semaphore Functionality

The semaphore is not available…. so the task is blocked waiting for the semaphore.

An interrupt occurs… that gives the semaphore

That now successfully takes the semaphore, so it is unavailable once more

The task can now perform its action, when complete it will once again attempt to take the semaphore which will cause it to re-enter the Blocked state

/*sempahore creation API*/
SemaphoreHandle_t xSemaphoreCreateBinary(void);

/*Semaphore take*/
BaseType_t xSemaphoreTake(
          SemaphoreHandle_t xSemaphore,
          TickType_t xTicksToWait
          );

/*Semaphore give*/
BaseType_t xSemaphoreGiveFromISR(
    SemaphoreHandle_t xSemaphore,
    BaseType_t *pxHigherPriorityTaskWoken
    );

Latching Interrupts

If another interrupt occurs before the Handler Task has completed its processing of the first interrupt, then the binary semaphore will effectively latch the event. Allowing the Handler Task to process the new event immediately after it has completed processing the original one.

The semaphore is not available…. so the task is blocked waiting for the semaphore.

An interrupt occurs… that gives the semaphore

The last action unblocks the task ( the semaphore is now available )

That now successfully takes the semaphore, so it is unavailable once more

Another interrupt occurs while the task is still processing the first event. The ISR gives the semaphore again, effectively latching the event

When processing of the original event completes the task calls xSemaphoreTake() again. Because another interrupt has already occurred the semaphore is already available

Counting Semaphores

The binary semaphore can latch almost one interrupt event. Any subsequent events occurring before the latched event has been processed will be lost. In order to avoid the last scenario counting semaphores should be used. Counting semaphores are like queues, but in this case data is not important just the number of the events.

SemaphoreHandle_t xSemaphoreCreateCounting( 
                 UBaseType_t uxMaxCount, 
                 UBaseType_t uxInitialCount 
              );

The task is blocked waiting for a semaphore

An interrupt occurs… that gives the semaphore

The last action unblocks the task ( the semaphore is now available )

That now successfully takes the semaphore, so it is unavailable once more

Another two interrupts occurs while the task is still processing the first event. The ISR gives the semaphore each time, latching both events.

From the two semaphores already available, one is taken without the task ever entering the Blocked State, leaving one more latched semaphore available

Queues within ISRs

Semaphore and Counting semaphores can be thought as a queues where data is not important, because its main goal is when event occurs. If we need to send information between an interrupt and a task handler we need to use queues, but this time using the special API function for these cases.

BaseType_t xQueueSendFromISR(
        QueueHandle_t xQueue,
        void *pvItemToQueue,
        BaseType_t *pxHigherPriorityTaskWoken
    );

BaseType_t xQueueReceiveFromISR(
        QueueHandle_t xQueue,
        void *pvItemToQueue,
        BaseType_t *pxHigherPriorityTaskWoken
    );
FreeRTOS semaphore and mutex API functions vSemaphoreCreateBinary, xSemaphoreCreateCounting, xSemaphoreCreateMutex, xSemaphoreCreateRecursiveMutex, xSemaphoreTake, xSemaphoreTakeRecursive, xSemaphoreGive, xSemaphoreGiveRecursive, xSemaphoreGiveFromISR
This page contains links to the FreeRTOS task control API function descriptions, vSemaphoreCreateBinary, xSemaphoreCreateCounting, xSemaphoreCreateMutex, xSemaphoreCreateRecursiveMutex, xSemaphoreTake, xSemaphoreTakeRecursive, xSemaphoreGive, xSemaphoreGiveRecursive, xSemaphoreGiveFromISR, FreeRTOS is a portable, open source, mini Real Time kernel. A free RTOS for small embedded systems

Code Snippets

Exercises (printf)

  1. Simulate the backlight of an LCD that is activated each time a key is pressed, and after 3 seconds if the key is not pressed the backlight should be deactivated. Simulate the key with a task that triggers an interruption, for the delay of the backlight use a FreeRTOS timer and send messages to the console.
  2. Create a program that receives arrays of characters for the serial port through an interruption ( character for character ), the interruption must communicate and pass the message received to a task “Display“, just until the message completely arrives; a message init with any character and ends with “\r“. Simulate the serial port with a task that generates interruptions and use a Queue to send the characters one by one each 10ms and a complete message each 2sec.
  3. Alter the speed of a blinking LED each time that button is pressed. By default the LED is OFF, the first time the button is pressed the LED must blink at 100ms, the second time at 300ms, and the third time at 600ms, if the button is pressed a fourth time the LED must be turned off and the sequence starts again. Simulate the button with a task that generates interruptions and the LED with messages on the screen.

Exercises

  1. Use a binary semaphore as a synchronization method. Create a program that uses a button connected to an external interruption, when the button is pressed the interruption is triggered to turn on an LED that is controlled by a task that runs concurrently each 50ms.
  2. Modify the program, now use a Queue instead of the binary semaphore, Interruption sends the LED state and task just waits to read the message to process it.
  3. Modify Exercise 1, change the binary semaphore to use a counter semaphore. each time the button is pressed is going to take a semaphore, the task must start to blink the LED when the button is pressed 3 times, if the button is pressed 2 times when the led is blinking the Task must stop the blinking LED. Use the Counter events method.
  4. Create a new program, and use a counter semaphore to synchronize task execution. Create 3 tasks, the task 1 execute and turn on LED0, executes again and turn on the LED1, and again task 1 executes and turn on the LED2, next Task 2 turn on the LED 4, the next execution turn on the LED 5, the third task going to turn off all the LEDs. All task going to have the same priority and the same period of 300ms. Use the Counter events method to control when task need to be executed.