The Arduino IDE and environment has many drivers and libraries available within an arms reach, but the Arduino environment is limited to just setup() and loop() and doesn't support multi-tasking effectively.
This article describes the use of mutex semaphores, within an easy to use and robust FreeRTOS implementation that is included in the Arduino IDE as a Library and allows the use of the best parts of both environments.
Most operating systems appear to allow multiple programs or threads to execute at the same time. This is called multi-tasking. In reality, each processor core can only be running a single program at any given point in time. A part of the operating system called the scheduler is responsible for deciding which program to run when, and provides the illusion of simultaneous execution by rapidly switching between each program.
Traditional real time schedulers, such as the scheduler used in FreeRTOS, achieve determinism by allowing the user to assign a priority to each thread of execution. The scheduler then uses the priority to know which thread of execution to run next. In FreeRTOS, a thread of execution is called a Task.
When two or more Tasks are sharing a single hardware resource, such as a Serial Port the situation can arise that the scheduler will swap one Task out before it has completed its Serial output, and another Task will commence and may provide some Serial output, effectively scribbling over the output of the first Task.
This situation can be avoided by using a Semaphore (also called a Flag) to protect the hardware resource, and prevent it being used until it is released by the Task using it.
Mutex semaphores are binary semaphores that include a priority inheritance mechanism. Whereas binary semaphores are the better choice for implementing synchronisation (between tasks or between tasks and an interrupt), mutex semaphores are the better choice for implementing simple mutual exclusion (hence 'MUT'ual 'EX'clusion).
In this example we will use a mutex Semaphore to protect our Serial port.
When used for mutual exclusion the mutex acts like a token that is used to guard a resource. When a Task wishes to access the resource it must first obtain or 'Take' the token. When it has finished with the resource it must 'Give' the token back, allowing other Tasks an opportunity to access the same resource.
In a previous ProTip we have described how to install the FreeRTOS Library for the Arduino IDE, and to test that it is working properly. If you haven't already done this step, please do so now.
Now either load the
AnalogRead_DigitalRead.ino file into your Arduino IDE, or copy and paste it into a new file, which you should name and save appropriately.
Within the sketch there are several steps to creating and using the mutex Semaphore to protect the Serial port.
Firstly, a Handle for the mutex Semaphore needs to be declared, as a Global Variable. We do this as a Global so that all Tasks can refer to the Semaphore, and check its availability to be 'Taken'.
setup() function we check that the Semaphore has not already been created, by checking whether the Handle is
NULL. We then go ahead to create the Semaphore, confirm that it has been properly created by checking the Handle contents again, and then finally 'Give' it free to be used, once the Scheduler is running.
Now, whenever a Task desires to use the Serial port
Serial.println() functions, it should ensure that it has 'Taken' the Serial port Semaphore beforehand. Once a Task has finished with the Serial port, then it must 'Give' the Semaphore to allow other Tasks access to the port.
Now that you've created a sketch with multiple Tasks writing to the protected Serial port, try what happens if there is no Semaphore, by commenting out the code for 'Taking' and 'Giving' the Semaphore within each Task.
Try printing long lines of text within two, three or more Tasks, and see what happens with, and with-out a protective Semaphore.
Read the FreeRTOS detailed description on Queues, Mutexes, and Semaphores. There are many additional types of Semaphore available, and it is important to understand where each type should be used to best effect.