I. OS & RTOS

a. What is an OS?

Software system that manages computer hardware and software resources and provides common services for computer programs. It is today always time-sharing (tasks are schedule for efficient use of the system and may also include accounting software for cost allocation of processor time, mass storage, printing, and other resources)

To remember:

  • An OS can handle each "program" as an unique entity.
  • The different entities can communicate with each other or access 3rd resources.

b. What is a RTOS?

It intended to serve real-time applications that process data as it comes in, typically without buffer delays. Processing time requirements (including any OS delay) are measured in tenths of seconds or shorter increments of time. A real time system is a time bound system which has well defined fixed time constraints. Processing must be done within the defined constraints or the system will fail. They either are event driven or time sharing. Event driven systems switch between tasks based on their priorities while time sharing systems switch the task based on clock interrupts.

They are hard real-time systems and soft real real-time systems. A hard real-time system guarantees that critical tasks are ran and completed on time. A soft real-time system is less restrictive, a "critical" real-time task gets priority over other tasks and retains the priority until it is completed.

To remember:

  • RTOS means we can react to a specific event within a defined time (ex: within 1 ms)
  • The time constraints between the event, and the execution of an action will define if your system has hard or soft real-time constraints.

c. What is a kernel?

The kernel is the core of a computer's operating system with complete control over everything in the system. When we call it, we name it "system calls". There are different type of kernels:

  • Monolithic kernel: All OS services run along with the main kernel thread, thus also residing in the same memory area. Advantage: rich & powerful hardware access. Disadvantage: dependencies between components, a bug in the device driver may crash the entire system.
  • Microkernel: Keep just the "minimal" set of functionalities in the kernel, all others are gather in "servers" running in the "user space". Advantage: maintenance easier, set of minimal system calls available. Disadvantage: larger running memory footprints, high amount of system calls.
  • Hybrid kernel: Similar to micro kernels, except they include some additional code in kernel-space to increase performance.

To remember:

  • FreeRTOS is a Microkernel.

II. RTOS & Kernel Functionality

a. What is a scheduler?

Scheduling is the method to assign work specified by some means to a resources that completes that work. The work may be virtual computation elements such as threads, processes or data flows, which are in turn scheduled onto hardware resources such as processors, network links or expansion cards. The different scheduling methods have names: non pre-emptive, pre-emptive or collaborative scheduling.

b. What is a task?

A task is a pure software resource executing a specific function in a loop. Its hardware used resources will be handle by the scheduler.

A task can be usually found in the following states:

  • Suspended: After the creation of a task (before the call of the scheduler initialization), all tasks are in suspended mode. After the initialization, we can put some tasks in suspend mode by calling the associated system call (vTaskSuspend()) and resume them with, for example vTaskResume(). A suspended task cannot be called and executed by the scheduler without an explicit request from the user.
  • Ready: Ready tasks are those that are able to execute (they are not in the Blocked or Suspended state) but are not currently executing because a different task of equal or higher priority is already in the Running state.
  • Running: When a task is actually executing it is said to be in the Running state. It is currently utilising the processor. If the processor on which the RTOS is running only has a single core then there can only be one task in the Running state at any given time.
  • Blocked: A task is said to be in the Blocked state if it is currently waiting for either a temporal (RTOS delay, timerTask,...) or external event (semaphore, queue, ...).

The resources needed by the task, also called TCB (Task Control Block) are usually assigned by the kernel and are opaque (it means you cannot access them). They are usually assigned based on a specific kernel configuration(heap configuration). The TCB is usually composed by:

  • Code start address
  • Current execution address in the task
  • Size of the pile
  • Current address on the pile
  • State of the task
  • Resource needed to continue the execution
  • Task priority
  • CPU registers

It can also sometime contain some other information. Ex: buffer overflow checker. It is also commonly called "Execution context" or "task context".

There are usually 2 type of tasks: idle and the others.

c. What is pre-emptive / non pre-emptive?

Non pre-emptive system (also called: cooperative because the tasks need to cooperate between each other):

  • The scheduler leaves to the task the control. Only when a task calls the scheduler (taskYIELD()), the scheduler will run and decide which task will be the next running.
  • Advantage: Spend less time in the scheduler defining which task will run as next.
  • Disadvantage: Tasks with high priority needs to wait until the task in current execution calls the yield function. It is basically made for soft real time systems.

Pre-emptive system:

  • The scheduler has the control and decide which task should run or not. It is usually called every tick (default configuration by freeRTOS: 1ms). Tasks with lower priority are automatically put on block and the one of higher priorities are executed.
  • Advantage: Fast reaction (within a tick precision) for the execution of a higher priority task when needed.
  • Disadvantage: Less execution time due to scheduler call happening every 1 ms.

System with time slicing:

  • Time slicing means that a equal number of time will be given to tasks running on the same priority level. If time slicing is not activated, the system call portYIELD() must be used to jump to the other task of equal priority.

System with non time slicing:

  • Behaviour depend on the implementation. Usually, it will be similar as a cooperative system.

System priority based:

  • When a task with higher priority is "ready", the scheduler will call it as early as possible.

System non priority based:

  • Behaviour depend on other factors. Similar to two tasks with same priority.

d. How does RTOSs manage the memory?

The kernel is handling some RAM to be fully functional. In case of FreeRTOS, this memory management is called heap. The heap can be configured in different modes:

  • heap_1 - the very simplest, does not permit memory to be freed
  • heap_2 - permits memory to be freed, but does not coalescence adjacent free blocks.
  • heap_3 - simply wraps the standard malloc() and free() for thread safety
  • heap_4 - coalescences adjacent free blocks to avoid fragmentation. Includes absolute address placement option
  • heap_5 - as per heap_4, with the ability to span the heap across multiple non-adjacent memory areas

FreeRTOS assure that memory allocation takes the same time every time and also assure an overflow is not possible.

To remember:

  • FreeRTOS allows "dynamic" allocation. "dynamic", in this context means: defined sizes can be allocated in the defined heap. Tasks, queues, semaphore belong to the objects that will have their resources allocated in the heap. The heap size is static and cannot overflow.

III. FreeRTOS Communication & Synchronisation Mechanisms

a. "1.0.0"

1. Critical sections:

  • Code section that shall be executed without any interruptions. It simply disable interruptions, either globally, or up to a specific interrupt priority level. https://www.freertos.org/a00020.html
  • Example: (pre-emptive + priority based + time slice + tick=1ms)
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        taskENTER_CRITICAL();       
        /* Do something during 10 seconds */
        taskEXIT_CRITICAL();
    }
}

void task2Function( void * pvParameters )
{
    for( ;; )
    {
        /* Do something during 1 second */
	vTaskDelay(1000); /* 1000 ticks = ? */
    }
}

/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;

    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);

    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);

    /* Start scheduler */
    vTaskStartScheduler();
}

Result: https://photos.app.goo.gl/BV8Acwry2iMbzx7R7

2. Co-routines:

  • Similar as tasks but with the following difference: All co-routines set share the same stack. Different set have different priorities and can also be pre-empted. https://www.freertos.org/taskandcr.html
  • Example: (pre-emptive + priority based + time slice + tick=1sec)
 void vFlashCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
 {
    crSTART( xHandle );

    for( ;; )
    {
        if( uxIndex == 1 )
        /* do something during 2,9999999 seconds + sleep 2 second ( crDELAY( xHandle, 1 ); )*/
        else if( uxIndex == 0 )
        /* do something during 1,9999999 seconds + sleep 2 second*/
    }
    crEND();
 }

 void main( void )
 {
    uint8_t uxIndex = 0;
    TaskHandle_t xHandle;

    for( uxIndex = 0; uxIndex < 2; uxIndex++ )
    {
        xCoRoutineCreate( vFlashCoRoutine, 0 /* prio */, uxIndex );
    }  

    /* Start scheduler */
    vTaskStartScheduler();
}

Result: https://photos.app.goo.gl/FX4Hb6XokJn5AV9N6

3. Semaphore / Mutexes:

  • Binary semaphores and simple mutexes are very similar but have some subtle differences: Mutexes include a priority inheritance mechanism, binary semaphores do not. This makes binary semaphores the better choice for implementing synchronisation (between tasks or between tasks and an interrupt), and mutexes the better choice for implementing simple mutual exclusion. https://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html
  • We also have counting semaphore & Reentrant mutex that allows multiple takes and gives.
  • Example: (pre-emptive + priority scheduling + time slice + tick=1sec)
SemaphoreHandle_t xSemaphore;
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        (void)xSemaphoreTake( xSemaphore, portMAX_DELAY );
        /* Do something 4 seconds */
        (void)xSemaphoreGive( xSemaphore );
    }
}
 
void task2Function( void * pvParameters )
{
    for( ;; )
    {
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 1 ) == pdTRUE )
        {
            /* Do something 1 second */
            xSemaphoreGive( xSemaphore );
        }
        else
        {
            /* Do something 3 seconds */
        }
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Create binary semaphore */
    xSemaphore = xSemaphoreCreateBinary();
 
    /* Start scheduler */
    vTaskStartScheduler();
}
SemaphoreHandle_t xSemaphore;
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        /* Do something 3,9999 seconds */
        (void)xSemaphoreGive( xSemaphore );
    }
}
 
void task2Function( void * pvParameters )
{
    for( ;; )
    {
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 1 ) == pdTRUE )
        {
            /* Do something 1 second */
        }
        else
        {
            /* Do something 3 seconds */
        }
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Create binary semaphore */
    xSemaphore = xSemaphoreCreateBinary();
 
    /* Start scheduler */
    vTaskStartScheduler();
}
  • Result: https://photos.app.goo.gl/ezdn3HgqoYjnzLtN8 → FreeRTOS implementation check the timer first. It means, the message ptFalse will be send if the time between a semaphore request and the receive and the jump back into the task takes more than the semaphore timeout.

4. Queues:

  • Queues are the primary form of intertask communications. They can be used to send messages between tasks, and between interrupts and tasks. In most cases they are used as thread safe FIFO (First In First Out) buffers with new data being sent to the back of the queue. https://www.freertos.org/Embedded-RTOS-Queues.html
  • Example: (pre-emptive + priority based + time slice + tick=1sec)
QueueHandle_t xQueue1;
 
void task1Function( void * pvParameters )
{
    uint16_t ulVar = 0;
    for( ;; )
    {
        (void)xQueueReceive( xQueue1, &( ulVar ), portMAX_DELAY )
            /* Do something during 4 */
    }
}
 
void task2Function( void * pvParameters )
{
    uint16_t ulVar = x;
    for( ;; )
    {
        if( xQueueSend( xQueue1,( void * ) &ulVar,( TickType_t ) 2 ) != pdPASS )
            /* Wait (vTaskDelay())3 second */
        else
            /* Do something for 1 second */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Create queue */
    xQueue1 = xQueueCreate( 3, sizeof( uint16_t ) );
 
    /* Start scheduler */
    vTaskStartScheduler();
}
QueueHandle_t xQueue1;
 
void task1Function( void * pvParameters )
{
    uint16_t ulVar = 0;
    for( ;; )
    {
        if( xQueueReceive( xQueue1, &( ulVar ), ( TickType_t ) 10 ) )
            /* Do something during 1 second */
    }
     
}
 
void task2Function( void * pvParameters )
{
    uint16_t ulVar = x;
    for( ;; )
    {
        if( xQueueSend( xQueue1,( void * ) &ulVar,( TickType_t ) 2 ) == pdPASS )
            /* Wait (vTaskDelay())3 second */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Create queue */
    xQueue1 = xQueueCreate( 3, sizeof( uint16_t ) );
 
    /* Start scheduler */
    vTaskStartScheduler();
}

5. Software timer:

  • A software timer (or just a 'timer') allows a function to be executed at a set time in the future. The function executed by the timer is called the timer's callback function. The time between a timer being started, and its callback function being executed, is called the timer's period. Put simply, the timer's callback function is executed when the timer's period expires. https://www.freertos.org/RTOS-software-timer.html
  • Example: (pre-emptive + priority based + time slice + tick=1sec)
TimerHandle_t Timer;
QueueHandle_t xQueue1;
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        (void)xQueueReceive( xQueue, &( ulVar ), portMAX_DELAY );
        /* Do something for 1 second */
    }
}
 
void timerFunction( TimerHandle_t xTimer )
{
    xQueueSend( xQueue1,( void * ) &ulVar,( TickType_t ) 0 )
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the timer */
    Timer = xTimerCreate( "Timer", 5, pdTRUE, NULL, timerFunction );
 
    /* Create queue */
    xQueue1 = xQueueCreate( 3, sizeof( uint16_t ) );
     
    /* Start scheduler */
    vTaskStartScheduler();
}

6. Event Groups: (not very useful)

Limitations due to market evolution:

  • IoT world: the data transmitted via queues are not anymore fixed. It can evolve depending of the configuration. We have also the use case where we just want to use the same queue to transmit a group of data or we have different data-sources converging to a same task (via multi-queues)
  • Architecture world: I know which tasks communicates with each other, I would like the queues to behave faster. I also would like to transmit data safely in a faster way than semaphore or queues.

b. "2.0.0"

1. Direct to task notifications:

  • Each RTOS task has a 32-bit notification value. An RTOS task notification is an event sent directly to a task that can unblock the receiving task, and optionally update the receiving task's notification value. https://www.freertos.org/RTOS-task-notifications.html
  • Example: (pre-emptive + priority based + time slice + tick=1ms)
TaskHandle_t xTaskToNotify = NULL;
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        BaseType_t token = pdFALSE;
        /* Do something during 2 second (ex: calculating value*/
        if( xTaskToNotify != NULL )
            vTaskNotifyGive( xTaskToNotify ); /* Already say which task is involved! */
    }
}
 
void task2Function( void * pvParameters )
{
    /* Say that task2 should be notified */   
    xTaskToNotify = xTaskGetCurrentTaskHandle();
    for( ;; )
    {
        /* Wait for notification */
        ulNotificationValue = ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); /* Clear token */
        /* Do something during 1 second (ex: calculating value) */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Start scheduler */
    vTaskStartScheduler();
}
TaskHandle_t xTaskToNotify = NULL;
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        BaseType_t token = pdFALSE;
        /* Do something during 1 second (ex: calculating value*/
        if( xTaskToNotify != NULL )
            /* Set bit 8 in the notification value of the task referenced by xTask1Handle. */
            xTaskNotify( xTaskToNotify, 10, eNoAction);
    }
}
 
void task2Function( void * pvParameters )
{
    uint32_t ulNotifiedValue = 0;
 
    /* Say that task2 should be notified */   
    xTaskToNotify = xTaskGetCurrentTaskHandle();
    for( ;; )
    {
        /* Wait for notification */
        xTaskNotifyWait( 0x00, ULONG_MAX, &ulNotifiedValue, portMAX_DELAY );
        /* ulNotifiedValue is now 10 */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Start scheduler */
    vTaskStartScheduler();
}
  • Result: Behaviour like a queue but faster.

2. Stream Buffers:

  • Stream buffers allow a stream of bytes to be passed. A byte stream can be of arbitrary length and does not necessarily have a beginning or end. The number of bytes sent is limited by the input buffer and the stream-buffer and trigger the target task when a minimum size was reached. The number of bytes read is limited by the size of the receive buffer or what is left in the stream-buffer. The stream-buffer does not know what it contains. Stream-buffers are optimised for single reader single writer scenarios. https://www.freertos.org/RTOS-stream-buffer-example.html
  • Example: (pre-emptive + priority based + time slice + tick=1sec)
StreamBufferHandle_t xStreamBuffer;
 
const size_t xStreamBufferSizeBytes = 100;
xTriggerLevel = 10;
 
void task1Function( void * pvParameters )
{
    size_t xBytesSent;
    /* Array to send first */
    uint8_t ucArrayToSend[] = { 0, 1, 2, 3 };
    /* Array to send after */
    char *pcStringToSend = "String to send";
 
    for( ;; )
    {
        xBytesSent = xStreamBufferSend( xStreamBuffer,( void * ) ucArrayToSend, sizeof( ucArrayToSend ), 4 );
        /* if( xBytesSent != sizeof( ucArrayToSend ) ) -> Error occurred */
        xBytesSent = xStreamBufferSend( xStreamBuffer,( void * ) pcStringToSend, strlen( ucArrayToSend ), 4 );
        /* Wait 5 ticks */
    }
}
 
void task2Function( void * pvParameters )
{
    size_t xReceivedBytes;
    uint8_t ucRxData[ xStreamBufferSizeBytes ];
 
    for( ;; )
    {
        xReceivedBytes = xStreamBufferReceive( xStreamBuffer,( void * ) ucRxData, sizeof( ucRxData ), (TickType_t)0 );
        if( xReceivedBytes == 4 )
            /* process array 1 for 2 seconds */
        else if( xReceivedBytes == 15 )
            /* process array 2 for 3 seconds */
        else
            /* an error occurred */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate( task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1 );
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate( task2Function, "Task 2", 512, NULL, 2, &taskHandle2 );
 
    /* Create stream buffer */
    xStreamBuffer = xStreamBufferCreate( xStreamBufferSizeBytes, xTriggerLevel ); /* /!\ unblock receive task after xTriggerLevel is reached */
 
    /* Start scheduler */
    vTaskStartScheduler();
}
  • Result: Build over the direct to task notification feature, behaviour like a queue but faster.

3. Message Buffers:

  • Same as message buffer with the different that message-buffers know what it contains. Ex: If it received 2 messages, it knows it contains 2 messages and knows their sizes too. There are mostly usefull for passing data from an interrupt service routine to a task, or from one microcontroller core to another dual core CPU. https://www.freertos.org/RTOS-message-buffer-example.html
  • Example: (pre-emptive + priority based + time slice + tick=1sec)
MessageBufferHandle_t xMessageBuffer;
const size_t xMessageBufferSizeBytes = 100;
 
void task1Function( void * pvParameters )
{
    size_t xBytesSent;
    /* Array to send first */
    uint8_t ucArrayToSend[] = { 0, 1, 2, 3 };
    /* Array to send after */
    char *pcStringToSend = "String to send";
 
    for( ;; )
    {
        xBytesSent = xMessageBufferSend( xStreamBuffer,( void * ) ucArrayToSend, sizeof( ucArrayToSend ), 4 );
        /* if( xBytesSent != sizeof( ucArrayToSend ) ) -> Error occurred */
        xBytesSent = xMessageBufferSend( xStreamBuffer,( void * ) pcStringToSend, strlen( ucArrayToSend ), 4 );
        /* Wait 5 ticks */
    }
}
 
void task2Function( void * pvParameters )
{
    size_t xReceivedBytes;
    uint8_t ucRxData[ xStreamBufferSizeBytes ];
 
    for( ;; )
    {
        xReceivedBytes = xMessageBufferReceive( xStreamBuffer,( void * ) ucRxData, sizeof( ucRxData ), (TickType_t)0 );
        if( xReceivedBytes == 4 )
            /* process array 1 for 2 seconds */
        else if( xReceivedBytes == 15 )
            /* process array 2 for 3 seconds */
        else
            /* an error occurred */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate( task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1 );
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate( task2Function, "Task 2", 512, NULL, 2, &taskHandle2 );
 
    /* Create stream buffer */
    xMessageBuffer = xMessageBufferCreate( xMessageBufferSizeBytes );
 
    /* Start scheduler */
    vTaskStartScheduler();
}
  • Result:  Behaviour like a queue but faster.

4. Queuesets:

  • Queue sets enables a RTOS task to be blocked (pend) until it receives a message from one of the multiple queues and/or semaphores it registered too. In other words, queues and semaphores are grouped into sets, then, instead of blocking on an individual queue or semaphore, a task instead blocks on the set.
  • Example: (pre-emptive + priority based + time slice + tick=1sec)
/* Macro definitions */
#define QUEUE_SIZE 3
#define QUEUE_SET_SIZE 2*QUEUE_SIZE*2
 
/* FreeRTOS objects declaration */
QueueHandle_t xQueue1;
QueueHandle_t xQueue2;
static QueueSetHandle_t xQueueSet;
 
void task1Function( void * pvParameters )
{
    uint16_t ulVar = 10;
    for( ;; )
    {
        /* Process something for 2 second */
        (void)xQueueSend( xQueue1,( void * ) &ulVar,( TickType_t ) 0 )
    }
}
 
void task2Function( void * pvParameters )
{
    QueueSetMemberHandle_t xActivatedMember;
    uint16_t xReceivedFromQueue1;
    uint16_t xReceivedFromQueue2;
 
    for( ;; )
    {
        /* Block until something happens in one of the queues */
        xActivatedMember = xQueueSelectFromSet( xQueueSet, portMAX_DELAY );
 
        if( xActivatedMember == xQueue1 )
        {
            xQueueReceive( xActivatedMember, &xReceivedFromQueue1, 0 );
            /* Process value from queue 1, takes 2 seconds */
        }
        else if( xActivatedMember == xQueue2 )
        {
            xQueueReceive( xActivatedMember, &xReceivedFromQueue2, 0 );
            /* Process value from queue 2, takes 1 seconds */
        }
    }
}
 
void task3Function( void * pvParameters )
{
    uint16_t ulVar = 11;
    for( ;; )
    {
        /* Process something for 3 second */
        (void)xQueueSend( xQueue2,( void * ) &ulVar,( TickType_t ) 0 )
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
    TaskHandle_t taskHandle3 = NULL;
     
    /* Create the tasks, storing the handle. */
    (void)xTaskCreate( task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1 );
    (void)xTaskCreate( task2Function, "Task 2", 512, NULL, 2, &taskHandle2 );
    (void)xTaskCreate( task1Function, "Task 3", 512, NULL, tskIDLE_PRIORITY, &taskHandle3 );
 
    /* Create the queues */
    xQueue1 = xQueueCreate( QUEUE_SIZE, sizeof( uint16_t ) );
    xQueue2 = xQueueCreate( QUEUE_SIZE, sizeof( uint16_t ) );
 
    /* Create the queueset */
    xQueueSet= xQueueCreateSet( QUEUE_SET_SIZE );
 
    /* Add queues to queueset */
    xQueueAddToSet( xQueue1, xQueueSet);
    xQueueAddToSet( xQueue2, xQueueSet);
 
    /* Start scheduler */
    vTaskStartScheduler();
}

III. Survival Guide

a. Visualisation

1. Representation

  • Task / co-routine: See under
  • Queue / direct to task notifier / stream-buffer / message-buffer: See under
  • Semaphore / Mutex: See under
  • Software timer: See under

2. Runflow

b. Errors not to do

  • Deadlocks: when a race condition occurred and 2 tasks are waiting for each other synchronisation resource that at the end never will come.
SemaphoreHandle_t xSemaphore;
 
void sensorCallbackFromIsr( void )
{
    (void)xSemaphoreGive( xSemaphore );
}
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        /* Get data from sensor */
        /* Process data */
        /* Request 2 new data samples */
        /* Wait for new data via the semaphore*/
        (void)xSemaphoreTake( xSemaphore, portMAX_DELAY )
        /* wait for the second */
        (void)xSemaphoreTake( xSemaphore, portMAX_DELAY )
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create binary semaphore */
    xSemaphore = xSemaphoreCreateBinary();
 
    /* Start scheduler */
    vTaskStartScheduler();
}
  • Inversion of priorities: A higher priority task needs data from a lower priority task
SemaphoreHandle_t xSemaphore;
 
void task1Function( void * pvParameters )
{
    for( ;; )
    {
        /* Do something 4 seconds */
        (void)xSemaphoreGive( xSemaphore );
    }
}
 
void task2Function( void * pvParameters )
{
    for( ;; )
    {
        (void)xSemaphoreTake( xSemaphore, portMAX_DELAY )
        /* Do something 4 seconds */
    }
}
 
/* Function that creates a task. */
void main( void )
{
    TaskHandle_t taskHandle1 = NULL;
    TaskHandle_t taskHandle2 = NULL;
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task1Function, "Task 1", 512, NULL, tskIDLE_PRIORITY, &taskHandle1);
 
    /* Create the task, storing the handle. */
    (void)xTaskCreate(task2Function, "Task 2", 512, NULL, 2, &taskHandle2);
 
    /* Create binary semaphore */
    xSemaphore = xSemaphoreCreateBinary();
 
    /* Start scheduler */
    vTaskStartScheduler();
}
  • co-routines & Event groups: Old style (for the co-routines), Event group (race conditions issues & non-deterministic behaviour)

Links:

Leave a Reply

Your email address will not be published. Required fields are marked *