How to create tasks on FreeRTOS

https://yalneb.blogspot.com/2017/05/how-to-create-tasks-on-freertos.html

FreeRTOS is a great tool for those of you who want to get the most out of their microcontrollers and do something more than blink a LED. It enables you to run several tasks concurrently (i.e. simultaneously, but no in parallel) and do things like, while one process is waiting for your I²C interface to finish (the task is blocked) let different process take advantage of the waiting time to do work.

In this post we will take a look at how to create two simple FreeRTOS tasks that run concurrently on Arduino, although this is perfectly applicable to any other microcontroller you have lying around.


Setting up your project



If you are following my example, you will be using Arduino. Otherwise skip right to the next section.

To create a FreeRTOS program on your Arduino, we will use Atmel Studio to program the ATmega328P (the MCU of the Arduino Uno) directly. If you have never done it before, take a look at this post explaining how to program Arduino with Atmel Studio.

Once we can program the ATmega328P without need for the Arduino IDE, you will also want to run FreeRTOS on it. If you have not done so before, take also a look at this post explaining how to run FreeRTOS on Arduino.

Once you have everything set up properly, create a new project for FreeRTOS, which should look something like this.

/* https://yalneb.blogspot.com/ FreeRTOS example */

#include "FreeRTOS.h"
#include "task.h"
#include <avr/io.h>

int main(void)
{
    while(1)
    {
          // YOUR CODE GOES HERE
    }
    return 0;
}


void vApplicationIdleHook(void)
{

}


Write the code that the tasks will run



A FreeRTOS task is simply a function that never returns. In other words, a function that contains an infinite loop within, similar to how int main(void) is usually written so that it never finishes. Also, it is a function that needs to accept a void pointer void* as argument.

Let's create two simple tasks (functions) that will interact with a LED. One will turn a LED constantly on, while the other task will be turning the LED off. The combined result to an external observer is that the LED will be blinking.

Note that this is the only place where my code will depend on the ATmega328P. If you use a different microcontroller, you only have to change the line of code that interacts with the LED to your needs. The rest of this example will work regardless of the underlying MCU.

void taskLedON(void* arguments)
{
    // ENTER TASK'S LOOP
    while(1)
    {
        PORTB |= (1 << PB5);    // Turn LED on.
        vTaskDelay(500);        // Wait 500ms
    }
}


void taskLedOFF(void* arguments)
{
    // ENTER TASK'S LOOP
    while(1)
    {
        PORTB &= ~(1 << PB5);    // Turn LED on.
        vTaskDelay(250);        // Wait 250ms
    }
}

You will have noticed that the tasks' loops contain a call to vTaskDelay, which lets the task sleep for the given ammount of time, but also frees the CPU if another task wants to use it in the meantime.

Also notice that we wait 500ms and 250ms respectively. This creates an offset so that we can see the LED blink. If both waited the same amount of time, they would run so close to each other that we would not be able to see the LED blink. It would either stay on or off depending on which runs the last.


Run your tasks



After you have written your task-functions, it is time to run them. We do this by telling FreeRTOS to run both functions as tasks with xTaskCreate as follows. But for it to work, you must also make sure that you have configSUPPORT_DYNAMIC_ALLOCATION set to 1 in your FreeRTOSconfig.h (which you usually have by default, so don't worry).

xTaskCreate takes the following arguments (source):
  • pvTaskCode: a reference to a task entry function (like the ones we have just written).
  • pcName: a descriptive name for your task, to help you debug among others. The maximum length of a task's name is set using the configMAX_TASK_NAME_LEN parameter in FreeRTOSConfig.h.
  • usStackDepth: the size of the task's stack, where it to store its configuration and register values when not running. We'll set it to configMINIMAL_STACK_SIZE because we don't have to store anything big in our example. 
  • pvParameters: a value that will be passed to our task entry function (remember that it has to accept a void* pointer?). We don't use it in this example, so we'll set it to NULL as recommended by FreeRTOS. 
  • uxPriority: like on any operating system, threads have priority. In case two tasks want to run simultaneously, the one with the higher priority will take preference. Will set both of our tasks to the lowest priority, tskIDLE_PRIORITY.
  • pxCreatedTask: this is used to manage task handles, a way to control the tasks. We won't use it, so we set it to NULL.

We use xTaskCreate to create the tasks we want to run, once once finished, we call vTaskStartScheduler to start their execution. Note that vTaskStartScheduler never finishes, so that from this point on only our two tasks will be running. taskLedOFF has a higher priority (note the +1 below) for the blinking effect to happen.

int main(void)
{

    // CONFIGURE PORT B, PIN 5 AS OUTPUT (my LED is here)
    DDRB |= (1<<PB5);

    // CREATE ON TASK
    xTaskCreate(taskLedON,

                "ON",
                configMINIMAL_STACK_SIZE, 
                NULL, 
                tskIDLE_PRIORITY,
                NULL);

    // CREATE OFF TASK   
    xTaskCreate(taskLedOFF, 

                "OFF", 
                configMINIMAL_STACK_SIZE, 
                NULL,  
                tskIDLE_PRIORITY+1, 
                NULL);
 



    // START SCHEDULER
    vTaskStartScheduler();

    return 0;
}



Finished code



If you have followed this steps, your code should look something like this and compile fine. Send it over to your Arduino and see if the LED blinks.

/* https://yalneb.blogspot.com/ FreeRTOS example */

#include "FreeRTOS.h"
#include "task.h"
#include <avr/io.h>


void taskLedON(void* arguments)
{
    // ENTER TASK'S LOOP
    while(1)
    {
        PORTB |= (1 << PB5);    // Turn LED on.
        vTaskDelay(500);        // Wait 500ms
    }
}


void taskLedOFF(void* arguments)
{
    // ENTER TASK'S LOOP
    while(1)
    {
        PORTB &= ~(1 << PB5);    // Turn LED on.
        vTaskDelay(250);        // Wait 250ms
    }
}


int main(void)
{

    // CONFIGURE PORT B, PIN 5 AS OUTPUT (my LED is here)
    DDRB |= (1<<PB5);

    // CREATE ON TASK
    xTaskCreate(taskLedON,

                "ON",
                configMINIMAL_STACK_SIZE, 
                NULL, 
                tskIDLE_PRIORITY,
                NULL);

    // CREATE OFF TASK   
    xTaskCreate(taskLedOFF, 

                "OFF", 
                configMINIMAL_STACK_SIZE, 
                NULL,  
                tskIDLE_PRIORITY+1, 
                NULL);
 



    // START SCHEDULER
    vTaskStartScheduler();

    return 0;
}


void vApplicationIdleHook(void)
{
    // THIS RUNS WHILE NO OTHER TASK RUNS
}


If you have any question, see a bug, or need help; don't hesitate to post below.

No comments :

Post a Comment