In this article, you will continue to learn freeRTOS concepts by going through practical projects with Arduino. You will learn how to use binary semaphore to defer interrupt processing to a task. You will implement a project having a button and a seven segment display. If you press the button, it will increase the number being shown on the seven segment from 0 to 9.
Introduction
You have learnt how to use queue to send data between freeRTOS tasks. In that project, you created two tasks, task_1 for generating an integer number periodically every 1 second and send it to a queue, task_2 to receive the number from the queue and display it on a seven segment. In this project, you will learn how to synchronise a task with an interrupt and defer processing to a task and keep the interrupt service routine (ISR) as small as possible. When we are saying synchronising, it means putting the task into running state right after the ISR as shown in the below figure:
The timing diagram in above figure is what we want to achieve in our project. When you press a button, the number on the seven segment is increased. To detect the button press, one can poll the status of a pin repeatedly every, for example, 40 milliseconds. However, a better solution is to use interrupt so that we don’t waste processing time polling the button pin. Each time the button is pressed, an interrupt signal is generated on a pin of the microcontroller and an ISR is called and executed. Since ISR is a hardware feature, its priority is higher than any tasks’s priorities and no task can pre-empt an ISR. In our program, we create a task named task_1 for updating the number displayed on the seven segment. task_1 should be in running state right after ISR is executed. Once it finishes updating the seven segment display, it goes to blocked state and the idle task is running most of the time.
Because we do not know when the button will be pressed, we do not know when ISR will be running, how do we make sure task_1 is running right after the ISR? To synchronise a task with an ISR, freeRTOS supports binary semaphore. The ISR gives the semaphore and the task_1 takes the semaphore. Binary semaphore is somewhat similar to a queue with size of one item. The ISR gives the semaphore is similar to the queue is written to. task_1 attempts to take the semaphore is similar to reading from the queue and taking away the item. After it finishes processing, it attempts to read again, but the queue now becomes empty, so it enters to blocked state to wait for the next ISR occurs. Let’s see how everything works by looking at semaphore APIs.
Binary semaphore APIs
Create a binary semaphore
Similar to a queue, a binary semaphore must be created before it is used. To create a binary semaphore, you use function xSemaphoreCreateBinary(). You will need to include macro #include<semphr.h> to be able to call this function. The xSemaphoreCreateBinary() does not take any parameters and returns a variable of type SemaphoreHandle_t. We will create a global variable name sem_1 to store the semaphore.
Giving a semaphore from ISR
To give a semaphore, call the function xSemaphoreGiveFromISR(). In our project, once the button is pressed, the ISR is executed. It will only give the semaphore and exit. The ISR code will look like this:
To setup ISR on Arduino, there are a few steps. Firstly, the button has to be connected to one of the pins that has interrupt. On Arduino Mega 2560, these are pins 2, 3, 18, 19, 20, 21. In our project, we will use pin 2 and connect a button to pin 2. You will need to setup internal pull up resistor and use function attachInterrupt as follow
Taking a semaphore
To take a semaphore, use API xSemaphoreTake(). This function takes two parameters. The first parameter is the semaphore to be taken, in our case is sem_1. The second parameter is xTicksToWait. This is the maximum amount of time that the task will wait in blocked state for the semaphore to become available. In our project, we will set xTicksToWait to portMAX_DELAY to make the task_1 to wait indefinitely in blocked state until the sem_1 is available. task_1 code to take the semaphore and update the seven segment display can be written as follow
The seg7_display() function is to decode the number and shows the number on seven segment which is trivial. With all the code above, compile and upload to Arduino. You should see expected behaviour: pressing the button increase the number on the seven segment.
Wrapping Up
In this post, you have learnt about binary semaphore and its freeRTOS APIs. Now you understand a technique to process interrupt and defer processing to a task.