The Arduino-compatible IDE or integrated development environment for the LaunchPads is called Energia. Energia also includes Energia MT for selected boards, including the MSP432 LaunchPad, the CC3200 LaunchPad and the CC2650 SensorTag.
Energia MT stands for Energia Multi-Tasking and is based on Texas Instruments RTOS, aka. Real Time Operating System. Real-time means each task is completed within a determined period of time, and RTOS is an operating system built on it.
I've developed libraries which encapsulate the RTOS elements into classes with a consistent and minimal set of functions. The goal is to provide libraries with the same ease of use as the standard classes (e.g. SPI, I²C, Serial) provided by the Wiring / Arduino framework. All the libraries are bundled in one single package called Galaxia, and are included in the Energia distribution.
Problem: How to Manage one Single Resource Across Different Tasks?Let's consider a project with 3 different tasks, and let's use either solution exposed on Multi-Tasking with Energia MT and Galaxia Library.
Each task prints a message on the Serial console.
3641 : mySemaphore3 3 (-)
3660 : mySemaphore1 1 (-)
3699 : mySemaphore2 2 (-)
3781 : mySemap32 hore1 1 (-)
3813 : mySemaphore3 3 (-)
3862 : mySemaphore2 2 (-)
3904 : mySemaphore1 1 (-)
3944 : mySemaphore2 2 (-)
3985 : mySemaphore3 3 (-)
4022Syrr
)
4107 : mySemaphore2 2 (-)
4147 : mySemaphore1 1 (-)
4157 : mySemaphore3 3 (-)
4189 : mySemaphore2 2 (-)
4269 : mySemaphore1 1 4-: mySemaphore2 2 (-)
4329 : mySemaphore3 3 (-)
4353 : mySemaphore2 2 (-)
What's happening?
- The three tasks send text to Serial.print(), but collide and the result is scrambled.
The result we'd like is each message printed without interferences.
0 : mySemaphore1 1 (0)
52 : mySemaphore2 2 (0)
104 : mySemaphore3 3 (0)
156 : mySemaphore1 1 (0)
208 : mySemaphore2 2 (0)
276 : mySemaphore3 3 (0)
328 : mySemaphore1 1 (0)
380 : mySemaphore2 2 (0)
448 : mySemaphore3 3 (0)
500 : mySemaphore1 1 (0)
552 : mySemaphore2 2 (0)
620 : mySemaphore3 3 (0)
672 : mySemaphore1 1 (0)
724 : mySemaphore2 2 (0)
792 : mySemaphore3 3 (0)
844 : mySemaphore1 1 (0)
896 : mySemaphore2 2 (0)
964 : mySemaphore3 3 (0)
To do so, we are going to use a semaphore. A semaphore is like a key: each task waits for the key, takes it when it is available, gets exclusive access to the resource, and then releases it so another task can use it. Only one task can takes and holds the key at a given time.
- With a semaphore, only one task can send text to Serial.print(), and the result is clean.
First, the semaphore mySemaphore is declared.
Semaphore mySemaphore;
Then begin(1) initialises the semaphore with one single slot, as there's only on Serial port.
mySemaphore.begin(1);
Finally, each task
- waits for the semaphore with waitFor(),
- executes and prints,
- frees the semaphore with post() so other tasks can use it.
mySemaphore.waitFor();
Serial.print(millis(), DEC);
Serial.print("\t: mySemaphore2 2 (");
Serial.print(mySemaphore.available(), DEC);
Serial.print("-");
Serial.println(")");
mySemaphore.post();
Three Tasks and One Single Serial PortThe project includes four files: a header file .h and three sketches .ino.
The header file rtosGlobals.h contains the global variables, here the semaphore mySemaphore, shared across the three tasks.
// *** Header rtosGlobals.h
// Core library
#include "Energia.h"
#ifndef rtosGlobals_h
#define rtosGlobals_h
// Include application, user and local libraries
#include "Semaphore.h"
// Semaphore for Serial
Semaphore mySemaphore;
#endif
The three sketches are very similar.
The setup() function on the main sketch SemaphoreLibrary.ino initialises the Serial port and the semaphore mySemaphore.
The parameter 1 means there's only one resource to share, here the Serial port.
// *** Sketch SemaphoreLibrary.ino
// Core library
#include "Energia.h"
// Include application, user and local libraries
#include "rtosGlobals.h"
// Setup
void Semaphore_setup()
{
// 1 is the recommended value as there is one single Serial port.
// Try with 3 to show the remaining count
mySemaphore.begin(1);
Serial.begin(115200);
}
On the loop() function, there are two key elements.
- mySemaphore.waitFor() waits for the semaphore to be available and then turns it as busy,
- mySemaphore.posts() frees the semaphore and makes it available to the other tasks.
// Loop
void Semaphore_loop()
{
mySemaphore.waitFor();
Serial.print(millis(), DEC);
Serial.print("\t: mySemaphore2 2 (");
Serial.print(mySemaphore.available(), DEC);
Serial.print("-");
Serial.println(")");
mySemaphore.post();
delay(30);
}
The second and third sketches are identical to the first one, except
- on the setup() function, the semaphore isn't initialised again —once suffices— and a delay() has been added, and
- on the loop() function, the message and the delay are adapted.
// *** Sketch Semaphore2.ino
// *** Same for Semaphore3.ino with 3 instead of 2
// Core library
#include "Energia.h"
// Include application, user and local libraries
#include "rtosGlobals.h"
// Setup
void Semaphore2_setup()
{
Serial.begin(115200);
}
// Loop
void Semaphore2_loop()
{
mySemaphore.waitFor();
Serial.print(millis(), DEC);
Serial.print("\t: mySemaphore2 2 (");
Serial.print(mySemaphore.available(), DEC);
Serial.print("-");
Serial.println(")");
mySemaphore.post();
delay(30);
}
ConclusionTry another value, for example 3, on mySemaphore.begin(3) and see the results!
Remember, the value is the number of resources available for and to be shared by the tasks.
A semaphore with three resources is like a restaurant with 3 tables: each customer is a task. The first customer comes in: she goes to the 1st table. So does the second customer on the 2nd table and the 3rd customer on the 3rd table. A forth customer comes in: no table is available, so she has to wait.
Comments