Discussion – FreeRTOS

FreeRTOS

Ryan Robucci

Table of Contents

References

Pre Discussion: Provided RTOS

Customized for ATmega 169p

git clone --recurse-submodules https://covail.cs.umbc.edu/gitlab/robucci/cmpe311-2022-spring-freertos.git

In MPLAB, File->Open Project… Navigate to subdirectory Demo and there will be a project (directory) ending in _MPLAB

In README, connection of LEDs is described.

One sudent reported the need to install the ATmega_DFP v. 2.3.126 Pack (using the [resolve] link next to it) in order to change the compiler version I was using (XC8 v. 2.32 -> 2.35).

I updated my version in the repo to 2.4 since then.

FreeRTOS

freertos.org

FreeRTOS™ Real-time operating system for microcontrollers
Developed in partnership with the world’s leading chip companies over an 18-year period, and now downloaded every 170 seconds, FreeRTOS is a market-leading real-time operating system (RTOS) for microcontrollers and small microprocessors. Distributed freely under the MIT open source license, FreeRTOS includes a kernel and a growing set of IoT libraries suitable for use across all industry sectors. FreeRTOS is built with an emphasis on reliability and ease of use.

https://www.freertos.org/Documentation/RTOS_book.html

Getting Started

Features parts of OS for discussion

Customization

The OS is configured using a customized configuration file and a small number of "port" files.

Static Vs Dynamic

May configure static and/or dynamic memory configuration

Dynamic

  • memory for tasks is dynamically allocated and freed from heap
    • advantage: memory is used as needed and freed when not, allowing more efficient use of memory and potentially more tasks since they can be created and destroyed as needed
  • #define configSUPPORT_DYNAMIC_ALLOCATION 1
  • API functions
    • xTaskCreate()
    • xQueueCreate()
    • xTimerCreate()
    • xEventGroupCreate()
    • xSemaphoreCreateBinary()
    • xSemaphoreCreateCounting()
    • xSemaphoreCreateMutex()
    • xSemaphoreCreateRecursiveMutex()

Static

  • must explicitly create buffers for storage and pass to task, semaphore, timer functions
  • advantage: by creating statically-allocated buffers, memory usage is reported at compile time
  • #define configSUPPORT_STATIC_ALLOCATION as 1
  • API functions
    • xTaskCreateStatic()
    • xQueueCreateStatic()
    • xTimerCreateStatic()
    • xEventGroupCreateStatic()
    • xSemaphoreCreateBinaryStatic()
    • xSemaphoreCreateCountingStatic()
    • xSemaphoreCreateMutexStatic()
    • xSemaphoreCreateRecursiveMutexStatic()

Tasks and Coroutines

Task Control: https://www.freertos.org/a00019.html

Task Utilities

https://www.freertos.org/a00021.html

task.h

Notes:

Kernel Control

https://www.freertos.org/a00020.html

Notes

Memory management and Dynamic Allocation

https://www.freertos.org/a00111.html

Quoted from that webpage:

Mutex

https://www.freertos.org/fr-content-src/uploads/2018/07/mutexes.gif

API

Note that there are variants to be called from ISRs

Example

SemaphoreHandle_t xSemaphore = NULL;

/* A task that creates a semaphore. */
void vATask( void * pvParameters )
{
    /* Create the semaphore to guard a shared resource.  As we are using
    the semaphore for mutual exclusion we create a mutex semaphore
    rather than a binary semaphore. */
    xSemaphore = xSemaphoreCreateMutex();
}

Static:

 SemaphoreHandle_t xSemaphore = NULL;
 StaticSemaphore_t xMutexBuffer;

 void vATask( void * pvParameters )
 {
    /* Create a mutex semaphore without using any dynamic memory
    allocation.  The mutex's data structures will be saved into
    the xMutexBuffer variable. */
    xSemaphore = xSemaphoreCreateMutexStatic( &xMutexBuffer );

    /* The pxMutexBuffer was not NULL, so it is expected that the
    handle will not be NULL. */
    configASSERT( xSemaphore );

    /* Rest of the task code goes here. */
 }

Example usage:

/* A task that uses the semaphore. */
void vAnotherTask( void * pvParameters )
{
    /* ... Do other things. */

    if( xSemaphore != NULL )
    {
        /* See if we can obtain the semaphore.  If the semaphore is not
        available wait 10 ticks to see if it becomes free. */
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
        {
            /* We were able to obtain the semaphore and can now access the
            shared resource. */

            /* ... */

            /* We have finished accessing the shared resource.  Release the
            semaphore. */
            xSemaphoreGive( xSemaphore );
        }
        else
        {
            /* We could not obtain the semaphore and can therefore not access
            the shared resource safely. */
        }

Timers

https://www.freertos.org/FreeRTOS-Software-Timer-API-Functions.html

Notes

TimerHandle_t xTimerCreateStatic
                 ( const char * const pcTimerName,
                   const TickType_t xTimerPeriod,
                   const UBaseType_t uxAutoReload,
                   void * const pvTimerID,
                   TimerCallbackFunction_t pxCallbackFunction
                   StaticTimer_t *pxTimerBuffer );

 pxTimerBuffer #define NUM_TIMERS 5

 /* An array to hold handles to the created timers. */
 TimerHandle_t xTimers[ NUM_TIMERS ];

 /* An array of StaticTimer_t structures, which are used to store
 the state of each created timer. */
 StaticTimer_t xTimerBuffers[ NUM_TIMERS ];

 /* Define a callback function that will be used by multiple timer
 instances.  The callback function does nothing but count the number
 of times the associated timer expires, and stop the timer once the
 timer has expired 10 times.  The count is saved as the ID of the
 timer. */
 void vTimerCallback( TimerHandle_t xTimer )
 {
 const uint32_t ulMaxExpiryCountBeforeStopping = 10;
 uint32_t ulCount;

    /* Optionally do something if the pxTimer parameter is NULL. */
    configASSERT( pxTimer );

    /* The number of times this timer has expired is saved as the
    timer's ID.  Obtain the count. */
    ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer );

    /* Increment the count, then test to see if the timer has expired
    ulMaxExpiryCountBeforeStopping yet. */
    ulCount++;

    /* If the timer has expired 10 times then stop it from running. */
    if( ulCount >= ulMaxExpiryCountBeforeStopping )
    {
        /* Do not use a block time if calling a timer API function
        from a timer callback function, as doing so could cause a
        deadlock! */
        xTimerStop( xTimer, 0 );
    }
    else
    {
       /* Store the incremented count back into the timer's ID field
       so it can be read back again the next time this software timer
       expires. */
       vTimerSetTimerID( xTimer, ( void * ) ulCount );
    }
 }

 void main( void )
 {
 long x;

    /* Create then start some timers.  Starting the timers before
    the RTOS scheduler has been started means the timers will start
    running immediately that the RTOS scheduler starts. */
    for( x = 0; x < NUM_TIMERS; x++ )
    {
        xTimers[ x ] = xTimerCreateStatic
                  ( /* Just a text name, not used by the RTOS
                    kernel. */
                    "Timer",
                    /* The timer period in ticks, must be
                    greater than 0. */
                    ( 100 * x ) + 100,
                    /* The timers will auto-reload themselves
                    when they expire. */
                    pdTRUE,
                    /* The ID is used to store a count of the
                    number of times the timer has expired, which
                    is initialised to 0. */
                    ( void * ) 0,
                    /* Each timer calls the same callback when
                    it expires. */
                    vTimerCallback,
                    /* Pass in the address of a StaticTimer_t
                    variable, which will hold the data associated with
                    the timer being created. */
                    &( xTimerBuffers[ x ] );
                  );

        if( xTimers[ x ] == NULL )
        {
            /* The timer was not created. */
        }
        else
        {
            /* Start the timer.  No block time is specified, and
            even if one was it would be ignored because the RTOS
            scheduler has not yet been started. */
            if( xTimerStart( xTimers[ x ], 0 ) != pdPASS )
            {
                /* The timer could not be set into the Active
                state. */
            }
        }
    }

    /* ...
    Create tasks here.
    ... */

    /* Starting the RTOS scheduler will start the timers running
    as they have already been set into the active state. */
    vTaskStartScheduler();

    /* Should not reach here. */
    for( ;; );
 }

Tutorial

Creating a new project

Use of vTaskDelay

void prvMyTask0 ( void *pvParameters )
{
	( void ) pvParameters;
    while(1) {
      vTaskDelay(500);    
      PORTB^=1;
      //taskYIELD();
    }
}

void prvMyTask1( void *pvParameters )
{    
	( void ) pvParameters;
    vTaskDelay(250);    
    while(1) {
      vTaskDelay(500);    
      PORTD^=1;
      //taskYIELD();
    }
}

Use of Mutex

In FreeRTOSConfig.h:

#define configUSE_MUTEXES               1
#define configUSE_TASK_NOTIFICATIONS    0

In main.c

#include "semphr.h" //mutex

before main

/*----------------------------------------------------------------------------*/
/* Mutex */                                                                   //
SemaphoreHandle_t myMutex;                                                    //
StaticSemaphore_t myMutexBuffer;                                              //
/*----------------------------------------------------------------------------*/

in main

    /* Mutex */
    myMutex = xSemaphoreCreateMutexStatic(&myMutexBuffer);
    if( myMutex != NULL )
    {
        /* The semaphore was created successfully and
       can be used. */
    
      /* In this port, to use preemptive scheduler define configUSE_PREEMPTION
      as 1 in portmacro.h.  To use the cooperative scheduler define
      configUSE_PREEMPTION as 0. */
      vTaskStartScheduler();
    }

New Tasks at end of main.c

static void prvMyTask0( void *pvParameters )
{
	( void ) pvParameters;

    while(1){
        vTaskDelay(250);
        xSemaphoreGive( myMutex);
        vTaskDelay(250);
        PORTB^=1;
    }
}

static void prvMyTask1( void *pvParameters )
{    
	( void ) pvParameters;

    while(1){
        xSemaphoreTake( myMutex,portMAX_DELAY); //**
        PORTD^=1;
    }
}

If INCLUDE_vTaskSuspend is set to '1' then specifying the block time as portMAX_DELAY will cause the task to block indefinitely (without a timeout).

Use of Software Tick Timer

In FreeRTOSConfig.h:

/* Timers and Mutexes */
#define configUSE_TIMERS                1
#define configTIMER_TASK_PRIORITY       (tskIDLE_PRIORITY+1)
#define configTIMER_QUEUE_LENGTH        1
#define configTIMER_TASK_STACK_DEPTH    configMINIMAL_STACK_SIZE+20
#define INCLUDE_xTimerPendFunctionCall  0

main.c:

#include "timers.h"

Before main

/*----------------------------------------------------------------------------*/
                                                                              //
static StaticTask_t xTimerTaskTCB;                                            //
static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];          //
                                                                              //
/* configSUPPORT_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1,    //
   so the application must provide an implementation of                       //
   vApplicationGetTimerTaskMemory() to provide the memory that is used by the //
   Timer service task. */                                                     //
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,    //
                                     StackType_t **ppxTimerTaskStackBuffer,   //
                                     uint32_t *pulTimerTaskStackSize )        //
{                                                                             //
/* If the buffers to be provided to the Timer task are declared inside this   //
function then they must be declared static - otherwise they will be allocated //
on the stack and so not exists after this function exits. */                  //
//static StaticTask_t xTimerTaskTCB;                                          //
//static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];        //
                                                                              //
    /* Pass out a pointer to the StaticTask_t structure in which the Timer    //
    task's state will be stored. */                                           //
    *ppxTimerTaskTCBBuffer = &xTimerTaskTCB;                                  //
                                                                              //
    /* Pass out the array that will be used as the Timer task's stack. */     //
    *ppxTimerTaskStackBuffer = uxTimerTaskStack;                              //
                                                                              //
    /* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer. //
    Note that, as the array is necessarily of type StackType_t,               //
    configTIMER_TASK_STACK_DEPTH is specified in words, not bytes. */         //
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;                    //
}                                                                             //
                                                                              //
/*----------------------------------------------------------------------------*/

Also before Main:

/*----------------------------------------------------------------------------*/
//Timer Callback                                                              //
void myTimerCallback( TimerHandle_t xTimer );                                 //
                                                                              //
//Static Allocation                                                           //
TimerHandle_t myTimer;                                                        //
StaticTimer_t myStaticTimerState;                                             //
/*----------------------------------------------------------------------------*/

New task definitions in main.c (provided below Main):

Task 0 modified to create and start a software, Task1 will do nothing, since LED D0 will be toggle by the timer task

static void prvMyTask0( void *pvParameters )
{
	( void ) pvParameters;

            myTimer = xTimerCreateStatic (
            /* Just a text name, not used by the RTOS
            kernel. */
            "Timer",
            /* The timer period in ticks, must be
            greater than 0. */
            300,
            /* The timers will auto-reload themselves
            when they expire. */
            pdTRUE,
            (void*)0,
            /* Each timer calls the same callback when
            it expires. */
            myTimerCallback,
            &myStaticTimerState
        );
                      
            
      vTaskDelay(250);    
      BaseType_t flag = xTimerStart( myTimer, 0 );
      if (flag){
        while(1){
            vTaskDelay(250);
            xSemaphoreGive( myMutex);
            vTaskDelay(250);
            PORTB^=1;
        }
      }
      
}

static void prvMyTask1( void *pvParameters )
{    

	( void ) pvParameters;

    while(1){
        taskYIELD();
    }

}

Callback function for timer to call every 300 ticks to toggle LED

/*----------------------------------------------------------------------------*/
//Timer Callback                                                              //
void myTimerCallback( TimerHandle_t xTimer ){                                 //
    volatile static uint8_t c; //just for demonstration                       //
    PORTD^=1;                                                                 //
    c=c+1;  //just for demonstration                                          //
}                                                                             //
/*----------------------------------------------------------------------------*/

Modification of Stack Size

Task Stack Size (Static)

You will need to modify the stack size for each task according to needs
configMINIMAL_STACK_SIZE is a port-specific quantity

Therefore, the following code would support a stack with, FOR EXAMPLE, an additional TEN 2-byte int16_t for Task0 and for Task1 FIVE two-byte uint16_t and THREE uint8_t

#define STACK_SIZE_T0 configMINIMAL_STACK_SIZE+20
#define STACK_SIZE_T1 configMINIMAL_STACK_SIZE+13

StackType_t xStack0[ STACK_SIZE_T0 ];
StackType_t xStack1[ STACK_SIZE_T1 ];   

Each task creation call requires passing the allocated stack size as a parameter

    /* Create the task without using any dynamic memory allocation. */
    xHandle0 = xTaskCreateStatic(
                  prvMyTask0,       /* Function that implements the task. */
                  "T0",          /* Text name for the task. */
                  STACK_SIZE_T0,      /* Number of indexes in the xStack array. */
                  ( void * ) NULL,    /* Parameter passed into the task. */
                  tskIDLE_PRIORITY+1,/* Priority at which the task is created. */
                  xStack0,          /* Array to use as the task's stack. */
                  &xTask0TCB );  /* Variable to hold the task's data structure. */

Timer Function Stack Size (Static)

For the Timer Callback function stack, a single configuration line change is required in FreeRTOSConfig.h

#define configTIMER_TASK_STACK_DEPTH    configMINIMAL_STACK_SIZE+20

it is used both in the addition required code that allocates the buffer, static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];, and by the FreeRTOS library.

Changing Task Priority

The tasks are tracked in multiple priority queues.
Higher index priorities are higher priority and are selected over any lower priority tasks.

You can change the number of priority levels by changing this line in FreeRTOSConfig.h

#define configMAX_PRIORITIES		( 4 )

Modification of Task Priority

The task creation function accepts priority as a parameter

Here, the priority is set to 1 higher than the idleTask:

    /* Create the task without using any dynamic memory allocation. */
    xHandle0 = xTaskCreateStatic(
                  prvMyTask0,       /* Function that implements the task. */
                  "T0",          /* Text name for the task. */
                  STACK_SIZE_T0,      /* Number of indexes in the xStack array. */
                  ( void * ) NULL,    /* Parameter passed into the task. */
                  tskIDLE_PRIORITY+1,/* Priority at which the task is created. */
                  xStack0,          /* Array to use as the task's stack. */
                  &xTask0TCB );  /* Variable to hold the task's data structure. */

Note that Source/include/task.h provides the definition for tskIDLE_PRIORITY

#define tskIDLE_PRIORITY    ( ( UBaseType_t ) 0U )`

Timer Task (callback) Priority

Change this line in FreeRTOSConfig.h
In the example, the timer is set to the higest priority

#define configTIMER_TASK_PRIORITY       (configMAX_PRIORITIES - 1)

Warning on CPU Starvation

Be careful to not starve lower priority tasks by delaying or suspending higher-priority tasks at times. Be mindful of both regular tasks and timer tasks. Coroutines (not that you would to use them) are lower priority than tasks.