A blog by Devendra Tewari
FreeRTOS is a cross-platform, real-time operating system that has been ported to several architectures. It has a simple but powerful API to deal with tasks, scheduler, queues, semaphores and timers. Learning it is an enriching experience and a knowledge that can be reused widely due to its portability. Its source code is available under the MIT license and allows commercial use without having to pay royalties.
The code snippet below demonstrates how a task can be created. Task creation may fail if there is not enough heap space available to allocate task structures and stack.
#include "FreeRTOS.h"
#include "task.h"
// a task
static void run(void *pvParameters)
{
for (;;)
{
// do something useful, then block
}
}
// create task
xTaskHandle h;
portBASE_TYPE p;
p = xTaskCreate(run,
(const signed portCHAR *)"task",
configMINIMAL_STACK_SIZE,
NULL,
tskIDLE_PRIORITY,
&h);
if ( p != pdPASS )
{
// handle error
}
FreeRTOS divides processor time equally among tasks with the same priority. Tasks with higher priority always preempt lower priority tasks, no other lower priority task will run until the higher priority task blocks. Tasks can block on calls such as vTaskDelay
, vTaskDelayUntil
, xQueueSend
, xQueuePeek
, xQueueReceive
, and xSemaphoreTake
.
To delete a task when done
// delete task
vTaskDelete(h);
FreeRTOS has three different memory allocation implementations, available in separate code files called heap_1.c
, heap_2.c, and heap_3.c
. You have to choose one of the three. If you choose heap_3.c
, the C compiler’s implementation of malloc
and free
are used, the heap space is determined based on the linker configuration. If interested in AVR32, see AVR32795: Using the GNU Linker Scripts on AVR UC3 Devices.
A task needs to perform activities. Most tasks are implemented as endless loops that wake up on some event, perform some activity and go back to waiting. An advantage of using queues is that they are multitask safe and thus an effective synchronization mechanism between tasks. You can use them to queue activities that a task must perform, serializing their execution. This is useful if multiple tasks must have access to a shared resource controlled by a single task.
The code snippet below demonstrates how to create a queue
xQueueHandle queue = 0;
queue = xQueueCreate(10, sizeof(unsigned int));
To queue a message (from any task)
unsigned int id = 0;
xQueueSend(xQueueIO, (void *)&id, (portTickType)0);
To retrieve a message from the queue and process it (usually in a dedicated task)
portBASE_TYPE p;
p = xQueueReceive(queue, &id, (portTickType)0);
if (p == pdPASS)
{
}
To delete the queue when done
vQueueDelete(queue);
Timers are a useful mechanism to stop long running activities and to start activities at periodic intervals.
The code snippet below demonstrates how to use the tick counter maintained by FreeRTOS to implement timers. The portTICK_RATE_MS macro is used to convert milliseconds to ticks. If the port of FreeRTOS for your board is not configured properly the value of portTICK_RATE_MS may not be correct.
The following code sets up some declarations and variables required later
#include "FreeRTOS.h"
static portTickType tickUntil;
static portTickType startTick;
// assign tick count when timer elapses i.e. after 1000 ms
startTick = xTaskGetTickCount();
tickUntil = startTick + (portTickType)(1000 / portTICK_RATE_MS);
In the task function, you’ll have code similar to
currentTick = xTaskGetTickCount();
if (tickUntil > startTick
&& (currentTick > tickUntil
|| (currentTick < startTick
&& currentTick < tickUntil))) // no overflow of tickUntil
{
// timer elapsed, do something
}
else if (currentTick < startTick && currentTick > tickUntil) // overflow of tickUntil
{
// timer elapsed, do something
}
In the ticklines below, when tick count enters the red region after tickUntil
, we consider the timer to have elapsed. The second tickline is for the case when tickUntil
overflows.