FreeRTOS

Richard Barry

http://www.freertos.org/

CHAPTER 1

TASK MANAGEMENT

1/ Introduction and scope

2/ Task functions

3/ Task creation

4/ Task priorities

5/ Idle task and idle task hook

6/ Task deletion

1/ Introduction and scope

- Each thread of execution is called a 'task'

- Allocates processing to each task?

- Chooses task for execution?

- Relative priority of each task to system behavior

2/ Task function

- Tasks are implemented as C function

- Prototype:

void vTaskFunction (void *pvParameter);

- Run forever with infinite loop

- Definition:

void vTaskFunction (void *pvParameter)

{

        int iVariableExample = 0;

        for ( ; ; ) {}

        vTaskDelete();

}

3/ Task creation

- Using xTaskCreate() API function

- Prototype:

portBASE_TYPE xTaskCreate (

                           pdTASK_CODE pvTaskCode,

                           const signed portCHAR * const pcName,

                           unsigned portSHORT usStackDepth,

                           void *pvParameter,

                           unsigned portBASE_TYPE uxPriority,

                           xTaskHandle *pxCreateTask

                          );

Example

void vTask1( void *pvParameters )
{
    const char *pcTaskName = "Task 1 is running\r\n";
    volatile unsigned long ul;
    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ;; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );
        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
        /* This loop is just a very crude delay implementation. There is
        nothing to do in here. Later examples will replace this crude
        loop with a proper delay/sleep function. */
        }
    }
}
int main( void )
{
    /* Create one of the two tasks. Note that a real application should check
    the return value of the xTaskCreate() call to ensure the task was created
    successfully. */
    xTaskCreate( vTask1, /* Pointer to the function that implements the task. */
                 "Task 1",/* Text name for the task. This is to facilitate debugging
                            only. */
                 1000, /* Stack depth - most small microcontrollers will use much
                            less stack than this. */
                 NULL, /* We are not using the task parameter. */
                 1, /* This task will run at priority 1. */
                 NULL ); /* We are not going to use the task handle. */
    /* Create the other task in exactly the same way and at the same priority. */
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
    /* Start the scheduler so the tasks start executing. */
    vTaskStartScheduler();
    /* If all is well then main() will never reach here as the scheduler will
    now be running the tasks. If main() does reach here then it is likely that
    there was insufficient heap memory available for the idle task to be created.
    CHAPTER 5 provides more information on memory management. */
    for( ;; );
}

Result

4/ Task priorities

- xTaskCreate() function assigns an initial priority for task being created.

- Be changed after scheduler via vTaskPrioritySet() API

- Range: 0 to configMAX_PRIORITIES - 1

- Highest priority task able to run is task will be selected

Experiment

static const char *pcTextForTask1 = “Task 1 is running\r\n”;
static const char *pcTextForTask2 = “Task 2 is running\t\n”;
int main( void )
{
    /* Create one of the two tasks. */
    xTaskCreate( vTaskFunction, /* Pointer to the function that implements
                                   the task. */
                 "Task 1", /* Text name for the task. This is to
                              facilitate debugging only. */
                 1000, /* Stack depth - most small microcontrollers
                          will use much less stack than this. */
                 (void*)pcTextForTask1, /* Pass the text to be printed into the task
                                           using the task parameter. */
                 1, /* This task will run at priority 1. */
                 NULL ); /* We are not using the task handle. */
    /* Create the other task in exactly the same way. Note this time that multiple
    tasks are being created from the SAME task implementation (vTaskFunction). Only
    the value passed in the parameter is different. Two instances of the same
    task are being created. */
    xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );
    /* Start the scheduler so our tasks start executing. */
    vTaskStartScheduler();
    /* If all is well then main() will never reach here as the scheduler will
    now be running the tasks. If main() does reach here then it is likely that
    there was insufficient heap memory available for the idle task to be created.
    CHAPTER 5 provides more information on memory management. */
    for( ;; );
}

Result

Expanding 'not running' state

- In the example presented, each task created always had processing, specific delay loop, and it prevent lower priority tasks ever running at all.

=> has any mechanisms for this problem??

5/ Idle task and idle task hook

- Processor always need something to execute -> what?

- Idle task has lowest possible priority, that is 0.

- To guarantee Idle task will be immediately transitioned out of the Running state when have higher priority task enter to Ready state

IDLE TASK:

IDLE TASK HOOK FUNCTION

- Call during each circle of idle task

- Extends idle task functionality:

      + Putting system into power-down mode

      + Should not call a blocking API

- An alternative is to implement extra functionality of task

static int count = 0;

void vApplicationIdleHook (void)
{
    if (count == something)
    {
        do_something here;
        count = 0;
    }
    count++;
}

CHAPTER 2

QUEUE MANAGEMENT

- Introduction and scope

- How to create a queue?

- How a queue manages the data it contain?

- How to send/receive data to/from queue?

- What it means to block on a queue?

1/ Introduction and scope

- Data storage: hold finite number of fixed size item, FIFO buffer, data is written to tail and read from head

- Acces by multiple task: not owned or assigned to any particular task

- Blocking on queue reads

- Blocking on queue writes

2/ Using a queue

- Queue must be explicitly created before it can be used

- Referenced using variables of type xQueueHandle

- Be created by xQueueCreate() API function

xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
                           unsigned portBASE_TYPE uxItemSize
                         );

uxQueueLength: The maximum number of items that the queue being
               created can hold at any one time.

uxItemSize:    The size in bytes of each data item that can be
               stored in the queue.

return value:  not-null: success, null: fail

- xQueueSendToFront() and xQueueSendToBack()

- What is different between of both below?

     + xQueueReceive() and xQueuePeek()

- In interrupt mode, which functions replace for somes above?

- uxQueueMessagesWaiting() API Function: query the number of items are currently in queue

Example

Description:

- queue being created, data being sent to the queue from multiple tasks, and data being received from the queue. The tasks that send to the queue do not specify a block time, while the task that receives from the queue does.

- The priority of task send data to queue (a) is lower than task receive data from queue (b).

 

After look at example, answer why (a) < (b) ?

static void vSenderTask( void *pvParameters )
{
    long lValueToSend;
    portBASE_TYPE xStatus;
    lValueToSend = ( long ) pvParameters;
    for( ;; )
    {
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
        if( xStatus != pdPASS )
        {
            vPrintString( "Could not send to the queue.\r\n" );
        }
        taskYIELD();
    }
}

Sender task

Receiver task

static void vReceiverTask( void *pvParameters )
{
    long lReceivedValue;
    portBASE_TYPE xStatus;
    const portTickType xTicksToWait = 100 / portTICK_RATE_MS;
    for( ;; )
    {
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!\r\n" );
        }
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
        if( xStatus == pdPASS )
        {
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}
xQueueHandle xQueue;
int main( void )
{
    xQueue = xQueueCreate( 5, sizeof( long ) );
    if( xQueue != NULL )
    {
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        /* Create the task that will read from the queue. The task is created with
        priority 2, so above the priority of the sender tasks. */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        /* Start the scheduler so the created tasks start executing. */
        vTaskStartScheduler();
    }
    else
    {
        /* The queue could not be created. */
    }
    for( ;; );
}

Result

Woking with large data

- Preferable to use the queue to transfer pointers to the data rather than copy the data itself into and out of the queue byte by byte.

- Transferring pointers is more efficient in both processing time and the amount of RAM required to create the queue

- Must be ensure:

     + The owner of the RAM being pointed to is clearly defined.

     + The RAM being pointed to remains valid.

CHAPTER 3

INTERRUPT MANAGEMENT

- Introduction and scope

- Which RTOS API function can be used within ISR ?

- How a deferred interrupt scheme can be implemented ?

- Binary semaphores and couting semaphores

- Use a queue to pass data into and out of an ISR

EVENT

- Embedded RTOS have to take actions in response to events that originate from the environment

=> STRATEGY OF EVENT PROCESSING ?

+ How should be events detected ?

+ How much processing should be performed inside of ISR?

+ How events be communicated with main code?

DEFERRED INTERRUPT PROCESSING

USING BINARY SEMAPHORE (BS) FOR SYNCHRONIZATION

- To unblock a task each time when interrupt occurs

=> synchronizing task with interrupt.

- Interrupt events processing: very fast and short portion remaining directly in ISR.

- Handler task priority can be set to ensure that always pre-empt the other tasks in system

- Queue can be used as semaphore that has max_length is 1 (hence binary)

Example

static void vPeriodicTask( void *pvParameters )
{
    for( ;; )
    {
        /* This task is just used to 'simulate' an interrupt by generating a
        software interrupt every 500ms. */
        vTaskDelay( 500 / portTICK_RATE_MS );
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

/*----------------------------------------------------------------------------*/

static void vHandlerTask( void *pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
        /* To get here the event must have occurred. Process the event. In this
        case processing is simply a matter of printing out a message. */
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}
static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;
    /* 'Give' the semaphore to unblock the task. */
    xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        portSWITCH_CONTEXT();
    }
}
/*-----------------------------------------------------------------------*/
int main( void )
{
    vSemaphoreCreateBinary( xBinarySemaphore );
    /* Install the interrupt handler. */
    _dos_setvect( 0x82, vExampleInterruptHandler );
    if( xBinarySemaphore != NULL )
    {
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
        vTaskStartScheduler();
    }

    for( ;; );
}

-Before going on counting semaphore (CS), predict situation for previous example, when it has another event occur before Handler task has completed processing the original event.

COUNTING SEMAPHORE

Purpose:

- Counting events: semaphores count value to be increment on each give and decremented on each take. Counting semaphores that are use to count events are created with an initial count value of zero.

 

- Resource management: discuss more detail in Chapter 4

Example

/* Before a semaphore is used it must be explicitly created. In this example
a counting semaphore is created. The semaphore is created to have a maximum
count value of 10, and an initial count value of 0. */
xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );

/*-------------------------------------------------------------------------*/

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
    xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
    xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        portSWITCH_CONTEXT();
    }
}

USING QUEUES WITHIN AN INTERRUPT SERVICE ROUTINE

Example at page

90 to 93

INTERRUPT NESTING

- configKERNEL_INTERRUPT_PRIORITY?

- configMAX_SYSCALL_INTERRUPT_PRIORITY?

CHAPTER 5

RESOURCE MANAGEMENT

The potential for a conflict to arise in a multitasking system if one task starts to access a resource but does not complete its access before being transitioned out of the Running state => left resource in an inconsistent state for another tasks => ????

Example

- Accessing Peripherals: two tasks attempt to write on LCD

- Read, Modify, Write Operations: "non-atomic" operation

- Non-atomic Access to Variables

- Function Reentrancy: just access the data that allocated to stack or held by register

MUTUAL EXCLUSION

- Resource is shared between tasks or task and interrupt

=> used mutex technique to ensure data consistency is maintained at all times.

 

- FreeRTOS supports several features to implement mutex, but what is the best solution ????

CRITICAL SECTIONS AND SUSPENDING THE SCHEDULER

What is critical section?

- Regions of code that are surrounded by taskENTER_CRITICAL() and taskEXIT_CRITICAL()

Example

taskENTER_CRITICAL();

PORTA |= 0x01;

taskEXIT_CRITICAL();

- Crude method, just disable interrupts

- Must be kept very short, why ???

- It is safe for critical sections to become nested, why ??

- When critical section be exited ??

SUSPENDING THE SCHEDULER

- Critical sections can also be created by suspending the scheduler

- Critical sections implemented by suspending only protect region of code from accessing by other task

- Critical sections that is too long should be replace by suspending

void vPrintString( const portCHAR *pcString )
{
    vTaskSuspendScheduler();
    {
        printf( "%s", pcString );
        fflush( stdout );
    }
    xTaskResumeScheduler();
    if( kbhit() )
    {
        vTaskEndScheduler();
    }
}

MUTEX AND BINARY SEMAPHORE

- Special type of binary semaphore

- Used to control the access to the resource that shared between tasks

- The mutex can be conceptually thought of as a token that is associated with the resource being shared

- Scenario: task to legitimately access the resource it must first successfully ‘take’ the token, when finish it must be "give" token back. Only when token was back, other task can take token and access to the shared resource

What is different between MUTEX and BINARY SEMAPHORE ???

static void prvNewPrintString( const portCHAR *pcString )
{
    xSemaphoreTake( xMutex, portMAX_DELAY );
    {
        printf( "%s", pcString );
        fflush( stdout );
    /* The mutex MUST be given back! */
    }
    xSemaphoreGive( xMutex );
    
    if( kbhit() )
    {
        vTaskEndScheduler();
    }
}
static void prvPrintTask( void *pvParameters )
{
    char *pcStringToPrint;
    pcStringToPrint = ( char * ) pvParameters;
    for( ;; )
    {
        prvNewPrintString( pcStringToPrint );
        vTaskDelay( ( rand() & 0x1FF ) );
    }
}
int main( void )
{
    xMutex = xSemaphoreCreateMutex();
    srand( 567 );

    if( xMutex != NULL )
    {
        xTaskCreate( prvPrintTask, "Print1", 1000,
        "Task 1 ******************************************\r\n", 1, NULL );
        xTaskCreate( prvPrintTask, "Print2", 1000,
        "Task 2 ------------------------------------------\r\n", 2, NULL );
        vTaskStartScheduler();
    }
    for( ;; );
}

PRIORITY INVERSION

PRIORITY INHERITANCE

Made with Slides.com