Objectifying task creation in FreeRTOS

The goal of this post is to show how to create tasks in FreeRTOS with objects and classes, but without modifying such framework. You can grasp the surface of this technique looking at the next two code excerpts. The first one shows how to create the task, and the second one shows how the task is coded.

// Here we're creating two tasks:
Led led1{ 128, tskIDLE_PRIORITY, "led", 0, 7, configTICK_RATE_HZ / 4 };
Led led2{ 128, tskIDLE_PRIORITY, "led", 3, 3, configTICK_RATE_HZ / 2 };

The object led1 has 128 words for its task’s stack; runs at the lowest priority; has “led” as its name; it’s attached to port 0, pin 7 and will blink at 4 times per second.

// Here we have the task code:
virtual void Main() override
{
  while( 1 )
  {
    Chip_GPIO_SetPinToggle( LPC_GPIO, this->port, this->pin );
    vTaskDelay( this->ticks );
  }
}

Why so many problems to create a task in C++? Can we just use a member function as the task parameter in the FreeRTOS API function xTaskCreate()? No, we can’t. The task code function must have only one parameter (void* _params), but even function members that doesn’t receive parameters have one: the this pointer. You can’t see it, but it’s there. So, any attempt to set a function member as the task code parameter will result in an (obscure) error.

We can overpassed this limitation using static function members: they don’t have the this pointer so we can happily use them as such parameter. However, using them as is might result in logic errors: all instances from that class will share not only the same code (which is good) but the data member as well (which is not good at all).

So, what is the solution? A static function member in an abstract base class that serves as a trampolin for the actual task code that is going to be coded in inherited classes. Voilá!.

I must be honest: at first it’s hard to understand the technique. It took me so many hours to figure out how and why it works. I started studying this example from Richard Damon. But as it’s full of conditional compilation constants, yet very complete, I couldn’t follow it so I decided to start from scratch.

Abstract class

I’ve developed the example using the LPCXpresso1114 board, but any board/microcontroller is ok; the technique doesn’t depend on a specific architecture.

Let’s see the abstract base class:

Captura de pantalla de 2018-05-20 18-09-30

In this abstract base class we should observe three fundamental components:

The constructor. It creates the task using the FreeRTOS API function xTaskCreate() (line 22). The task code is actually named task and it’s static (line 28), as mentioned previously. All parameters that are sent to the API function are the standard ones, except the number 4: this. Yes, we send the this pointer as the user parameter for the task code. Keep reading.

The task code. In line 25 we can see the virtual pure member function Main() (with capital ‘M’) which is going to be implemented in the inherited classes. Note that it doesn’t have any parameter, not even for the user parameters: as we will see shortly, it doesn’t need it.

The trampolin function. The static member function task() (line 28) is the way we have to run the particular user code task. It follows the FreeRTOS user task code standard: it doesn’t return anything, and it receives a pointer to void. Line 30 casts the void pointer parameter (which actually holds the this pointer, from the constructor) to a pointer to an instance of a class derived from Thread. The this pointer has all the information that is of our concern: the data members. And this information is passed on to the particular Main() that is going to run. That’s way we don’t need that Main() receives parameters; they are already implicitly in the object.

Looking back at the line 22, as long as the FreeRTOS concerns, we’re doing this:

task( this );

And for our own concern, we’re doing this:

Led::Main( this );

As can be seen, the parameters for any of the multiple Main() functions member from the inherited classes, are held in the this pointer. In this way, any parameter that is of importance for the user task code can be defined as data members insided the inherited class as shown in lines 58 to 60 for a class Led:

Captura de pantalla de 2018-05-20 19-32-02

The main loop for a given object of type Led is coded inside the overrided method Main() (line 48). In this member function, as in the constructor, you can see that any information needed for a construction and execution is already part of the same class. Nice! With this same class we can instance any number of leds, each one in a particular port/pin and with its own blink frequency, and completely independent from each other.

Let’s go back to line 31. As the pointer p now points to a particular instance, then it already has the address of the Main() member function for that particular inherited class. In that way the correct Main() member function is called.

Inherited classes

All classes that wants to be a task (or thread) inside the FreeRTOS framework needs to inherit from the abstract class Thread (line 37). In this particular implementation of the technique, I decided not only the parameters for the base class, but also their order. Of course, you can adapt it to your needs. Line 40 takes the parameters for the base class, while line 41 takes the parameters for the Led instances. Line 42 calls the base class constructor, while line 43 initializes the data members for this particular class. As we are working with hardware, it’s constructed/initilized inside the constructor (line 45).

An inherited class from Thread must implement the Main() member function. As usual, it’s a forever loop. Lines 52 and 53 show us that we can use data members and functions from the FreeRTOS API in the user task code.

Instances

Once the Thread and its inherited classes are ready, then we can instance some objects. In this particular example there is only one inherited class, Led:

Captura de pantalla de 2018-05-20 20-13-17

Tasks can be created wherever we want, but for this example, and following the good programer practices, all task are created at the very beginning, e.g. in the main() function.

Advantages

  • IMHO it looks nicer to create threads in this way rather than in plain C, if you’re coding in C++, of course. Otherwise it makes no sense.
  • C++ is safer than C. C++ type checking is much more strict than that of C.
  • We can have as many constructors as we need. Some of them with default parameters. For example, all Led instances might need 128 words for their stack. So, we can get rid of this particular parameter. This same idea applies for the name parameter. In this way the number of parameters can be reduced.
  • If it’s allowed to delete tasks (not shown in this example), then we can take advantage of the destructor.
  • Task parameters are encapsulated. Otherwise a class/struct is going to be needed if the user wants to pass on complex data (as in this example: port/pin/frequency) to xTaskCreate() API function. Try it if you don’t believe me! (without using global variables, of course 😉 )
  • One can use nice C++ features: smart pointers, scoped enumerations, real booleans, function/operator overloading, lambdas, std::array’s. Last, but not least, classes!

Disadvantages

  • This first approach is using virtual functions. The embedded systems purists might disagree with its use. Let me say something: this approach works awesome indeed! Please see the What’s next section.
  • Depending on the complexity of a given application, we could end up with many sub-classes.
  • Program size. Nothing comes for free in life, but I haven’t noticed any dangerous program size increment with the -O0 flag for both FreeRTOS API and my classes! (I’m not using any optimization artifact right now because I’m still debuging the code.)

What’s next

  • I’m working in replacing the dynamic polymorphism with static polymorphism through the CRTP pattern. ASAP it’s ready, I’ll post it as the part 2 of this post.
  • To complete the approach (independent of the above point) adding the capabilities of deleting tasks, change the priority levels, getting the handle, etc.
  • To broaden the approach so that it includes the static tasks creation. It’s a very hard point, IMHO. (It might be the third part of this post.)

Source code

The code shown in this tutorial is too small to git it. In fact, it’s only one source file. So this very example can be downloade from here.

A version with more inherited classes and with better organization can be cloned from:

git clone https://fjrg76@bitbucket.org/fjrg76/freertoscpp.git

Greetings!

Anuncios

Un comentario sobre “Objectifying task creation in FreeRTOS

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

w

Conectando a %s