FreeRTOS
FreeRTOS RTOS libraries
William Greiman
November 12, 2015

Introduction

These libraries use FreeRTOS version 8.2.3.

I have disabled backward compatibility. You can enable it by editing this line in FreeRTOSConfig.h

#define configENABLE_BACKWARD_COMPATIBILITY 1

See this link for documentation on symbols in FreeRTOSConfig.h

http://www.freertos.org/a00110.html

It was necessary to disable ISR priority checking for Due since Due uses zero (the highest interrupt priority) interrrupts.

This was done in port.c of the ARM library.

void vPortValidateInterruptPriority( void )
{
#ifndef CORE_TEENSY
// Due uses priority zero interrupts so ignore problems.
return;
#endif // CORE_TEENSY

This package contains versions of the FreeRTOS systems for AVR Arduinos, Due Arduino, and Teensy 3.0, 3.1.

Information about Teensy 3.1 can be found here:

http://www.pjrc.com/teensy/

These systems are packaged as the Arduino libraries FreeRTOS_ARM and FreeRTOS_AVR. In addition the support library SdFat for ARM/AVR is included.

Two original FreeRTOS files were modified in each library to adapt FreeRTOS to Arduino. See FreeRTOSConfig.h and port.c in the utilities folder.

SdFat is a FAT16/FAT32 file system for SD cards. More information is available here:

https://github.com/greiman/SdFat

The documentation for FreeRTOS is located here:

http://www.freertos.org/

Also explore the above tabs for port information.

Threadsafe and reentrant functions

If this is your first exposure to a RTOS you will likely feel some pain.

The normal Arduino environment is single-threaded so code does not need to be reentrant or threadsafe. With a preemptive RTOS, the same resources may be accessed concurrently by several threads.

Many arduino libraries and functions are not reentrant or threadsafe.

To protect resource integrity, code written for multithreaded programs must be reentrant and threadsafe.

Reentrance and thread safety are both related to the way that functions handle resources. Reentrance and thread safety are separate concepts: a function can be either reentrant, threadsafe, both, or neither.

A reentrant function does not hold static data over successive calls, nor does it return a pointer to static data. A reentrant function must not call non-reentrant functions.

A threadsafe function protects shared resources from concurrent access by locks. Only one thread can be executing at a time.

The dynamic memory functions malloc and free are not threadsafe. This means that libraries like String and SD.h are not thread safe since they use malloc/free.

SdFat does not use malloc but is not threadsafe. Notice that I put all access to the SD in the low priority loop thread to avoid problems.

Blocking, deadlocks and priority

You must not use Arduino delay() in other than the lowest priority task. delay() will block all lower priority threads.

Two of the most common design problems for embedded developers are the deadlock and the priority inversion problem. You should start with very simple designs to avoid these subtle problems.

Crash error codes

I use the Arduino LED on pin 13 to blink error codes. The blink pattern is a number of short flashes repeated every two seconds.

If a configASSERT fails, one short flash every two seconds.

You can disable configASSERT by commenting out this line in FreeRTOSConfig.h.

#define configASSERT( x ) if( ( x ) == 0 ) assertBlink()

If malloc fails, two short flashes every two seconds.

You can disable this by editing this line in FreeRTOSConfig.h.

#define configUSE_MALLOC_FAILED_HOOK 0

If stack overflow is detected, three short flashes every two seconds.

You can disable this by editing this line in FreeRTOSConfig.h.

#define configCHECK_FOR_STACK_OVERFLOW 0

The ARM version has these additional codes are:

Hard fault - blink four short flashes every two seconds

Bus fault - blink five short flashes every two seconds

Usage fault - blink six short flashes every two seconds

Examples

There are a number examples in each RTOS library. These are blink/print, context switch, time jitter, fifo SD data logger, Liu Leyland Rate Monotonic Scheduling, and FreeRTOS book examples.

Two Thread Blink

The frBlink.ino example demonstrates thread definition, semaphores, and thread sleep.

Thread 1 waits on a semaphore and turns the LED off when signalled by thread 2.

Thread 2 turns the LED on, sleeps for a period, signals thread 1 to turn the LED off, and sleeps for another period.

Blink print example

The blink/print example in each library is frBlinkPrint.ino.

Each of the blink/print examples has three threads. A high priority thread blinks an LED, a medium priority thread prints a counter every second, and a low priority thread increments the counter.

The low priority thread also checks Serial for input. If the input is available, the low priority will stop the print thread and display stack usage information for each thread.

An interesting experiment is to observe the nonatomic behavior of incrementing count in loop().

Comment out noInterrupts() and interrupts() like this:

// noInterrupts();
count++;
// interrupts();

You will then see occasional large counts when the print thread tries to zero count while the loop() thread is incrementing count.

Semaphore context switch time

You need an oscilloscope to run this example. This example is chContextTime.ino.

To run this example, connect the scope to pin 13. You will see two pulses. Measure difference in time between first pulse with no context switch and the second pulse started in ledControl and ended in ledOffTask.

The difference is the time for the semaphore and a context switch.

Delay jitter time

The frJitter.ino example delays for one tick and measures the time difference in micros between delay calls.

The min and max times are printed by a lower priority task.

Fast Data logger

The fast data logger example is frFifoDataLogger.ino. This example require connection to an SD socket.

Two semaphores are used to implement a FIFO for data records. This uncouples the data acquisition task from the SD write task. SD card have unpredictable write latencies that can be over 100 milliseconds.

You need a quality SD card to avoid data overrun errors. Overruns could be avoided by allocating more memory to the buffer queue.

This example logs a counter as dummy data. You can replace this with data from an analog pin or your sensor.

Type any character to terminate the example. Memory usage information will be printed.

Liu Leyland Rate Monotonic Scheduling

This example is an illustration of Rate Monotonic Scheduling from the 1973 Liu and Layland paper.

Rate Monotonic Scheduling for a set of repeating tasks gives higher priority to a task with a smaller period.

Theorem Liu and Layland 1973. Given a preemptive, fixed priority scheduler and a finite set of repeating tasks T = {T1; T2; ...; Tn} with associated periods {p1; p2 ...; pn} and no precedence constraints, if any priority assignment yields a feasible schedule, then the rate monotonic priority assignment yields a feasible schedule.

Liu and Layland also derived a bound on CPU utilization that guarantees there will be a feasible Rate Monotonic Schedule when a set of n tasks have CPU utilization less than the bound.

The Liu Layland bound = 100*n*(2^(1/n) - 1) in percent. For large n this approaches ln(2) or 69.3%. The extra CPU time can be used by lower priority tasks that do not have hard deadlines.

Note that it may be possible to run a given set of tasks with higher CPU utilization, depending on task parameters. The Liu Layland bound works for every set of tasks independent of task parameters.

FreeRTOS Book Examples

The folder examples/FreeRTOSBook has sixteen examples.

These examples are described in the book:

Using The FreeRTOS Real Time Kernel - a Practical Guide

http://shop.freertos.org/RTOS_primer_books_and_manual_s/1819.htm

The following examples are fully described in the above book. I have modified The examples to run as Arduino sketches. The original code is here.

http://www.freertos.org/Documentation/code/

Warning: The interrupt examples use attachInterrupt() to generate interrupts. The pin for interrupt zero must be free for use as an output on AVR.

On ARM Due and Teensy 3.x you must connect pin 2 to pin 3.

Input characters will stop an example.

Example 1. Creating Tasks
Example 2. Using the Task Parameter
Example 3. Experimenting with priorities
Example 4. Using the Blocked state to create a delay
Example 5. Converting the example tasks to use vTaskDelayUntil()
Example 6. Combining blocking and non-blocking tasks
Example 7. Defining an Idle Task Hook Function
Example 8. Changing task priorities
Example 9. Deleting tasks
Example 10. Blocking When Receiving From a Queue
Example 11. Blocking When Sending to a Queue / Sending Structures on a Queue
Example 12. Using a Binary Semaphore to Synchronize a Task with an Interrupt
Example 13. Using a Counting Semaphore to Synchronize a Task with an Interrupt
Example 14. Sending and Receiving on a Queue from Within an Interrupt
Example 15. Rewriting vPrintString() to Use a Semaphore
Example 16. Re-writing vPrintString() to Use a Gatekeeper Task