Using FreeRTOS Semaphores in Arduino IDE

Using FreeRTOS Semaphores in Arduino IDE © GPL3+

FreeRTOS, simple, easy, robust, and optimised for Arduino IDE. Using FreeRTOS Semaphores to share and protect physical resources.

  • 13,698 views
  • 9 comments
  • 22 respects

Components and supplies

Apps and online services

About this project

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.

Background

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.

Let's Get Started

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'.

In the 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.

Next Steps

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.

Code

AnalogRead_DigitalRead.inoArduino
Example code, using FreeRTOS Semaphores to protect the Serial port, so that two tasks can share the one hardware resource.
#include <Arduino_FreeRTOS.h>
#include <semphr.h>  // add the FreeRTOS functions for Semaphores (or Flags).

// Declare a mutex Semaphore Handle which we will use to manage the Serial Port.
// It will be used to ensure only only one Task is accessing this resource at any time.
SemaphoreHandle_t xSerialSemaphore;

// define two Tasks for DigitalRead & AnalogRead
void TaskDigitalRead( void *pvParameters );
void TaskAnalogRead( void *pvParameters );

// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);

  // Semaphores are useful to stop a Task proceeding, where it should be paused to wait,
  // because it is sharing a resource, such as the Serial port.
  // Semaphores should only be used whilst the scheduler is running, but we can set it up here.
  if ( xSerialSemaphore == NULL )  // Check to confirm that the Serial Semaphore has not already been created.
  {
    xSerialSemaphore = xSemaphoreCreateMutex();  // Create a mutex semaphore we will use to manage the Serial Port
    if ( ( xSerialSemaphore ) != NULL )
      xSemaphoreGive( ( xSerialSemaphore ) );  // Make the Serial Port available for use, by "Giving" the Semaphore.
  }

  // Now set up two Tasks to run independently.
  xTaskCreate(
    TaskDigitalRead
    ,  (const portCHAR *)"DigitalRead"  // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2  // Priority, with 1 being the highest, and 4 being the lowest.
    ,  NULL );

  xTaskCreate(
    TaskAnalogRead
    ,  (const portCHAR *) "AnalogRead"
    ,  128  // Stack size
    ,  NULL
    ,  1  // Priority
    ,  NULL );

  // Now the Task scheduler, which takes over control of scheduling individual Tasks, is automatically started.
}

void loop()
{
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskDigitalRead( void *pvParameters __attribute__((unused)) )  // This is a Task.
{
  /*
    DigitalReadSerial
    Reads a digital input on pin 2, prints the result to the serial monitor

    This example code is in the public domain.
  */

  // digital pin 2 has a pushbutton attached to it. Give it a name:
  uint8_t pushButton = 2;

  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);

  for (;;) // A Task shall never return or exit.
  {
    // read the input pin:
    int buttonState = digitalRead(pushButton);

    // See if we can obtain or "Take" the Serial Semaphore.
    // If the semaphore is not available, wait 5 ticks of the Scheduler to see if it becomes free.
    if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
    {
      // We were able to obtain or "Take" the semaphore and can now access the shared resource.
      // We want to have the Serial Port for us alone, as it takes some time to print,
      // so we don't want it getting stolen during the middle of a conversion.
      // print out the state of the button:
      Serial.println(buttonState);

      xSemaphoreGive( xSerialSemaphore ); // Now free or "Give" the Serial Port for others.
    }

    vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
  }
}

void TaskAnalogRead( void *pvParameters __attribute__((unused)) )  // This is a Task.
{

  for (;;)
  {
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);

    // See if we can obtain or "Take" the Serial Semaphore.
    // If the semaphore is not available, wait 5 ticks of the Scheduler to see if it becomes free.
    if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE )
    {
      // We were able to obtain or "Take" the semaphore and can now access the shared resource.
      // We want to have the Serial Port for us alone, as it takes some time to print,
      // so we don't want it getting stolen during the middle of a conversion.
      // print out the value you read:
      Serial.println(sensorValue);

      xSemaphoreGive( xSerialSemaphore ); // Now free or "Give" the Serial Port for others.
    }

    vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
  }
}

Schematics

Schematic
Connections for a Pushbutton to D2 and a Potentiometer to A0
Analogserial digitalserial

Comments

Similar projects you might like

Using FreeRTOS multi-tasking in Arduino

by Phillip Stevens

  • 125,287 views
  • 43 comments
  • 123 respects

Plugin Blockly@rduino for Arduino IDE

by Team LibrEduc

  • 12,699 views
  • 3 comments
  • 28 respects

Integrated Solar ChargeController, Inverter, PowerBank, Lamp

Project tutorial by Shahariar

  • 7,771 views
  • 16 comments
  • 30 respects

Looking Under The Hood Of The Arduino IDE

by rdpoor

  • 2,463 views
  • 8 comments
  • 10 respects

Android App-Based Home Automation System Using IOT

Project tutorial by Team Autoshack

  • 24,853 views
  • 17 comments
  • 75 respects
Add projectSign up / Login