Multitasking meets C++ in the embedded world

Last Updated July 28, 2011

Contents


  1. 1. The Idea

  2. 2. Main Components

  3. 3. The Demo

  4. i. Introduction

  5. ii. The class design

  6. iii. The FreeRTOOS wrapper class

  7. iv. The Managed class Extension

  8. v. The CHelloWorld class (an example)

  9. 4. More Details...

  10. i. C++ Limitations

  11. ii. About FreeRTOS configuration parameters

  12. iii. How to modify the linker script for a different STM32 MCU?

  13. 5. Download

  14. 6. Latest news

 

The idea


Today the multitasking approach for embedded development is robust and effective. The demo projects published in this site are the code base of many projects I followed with success ( and a little lucky! :-). So, what is the next step for the embedded world? Looking at the today MCU and imagining the roadmaps for the next years, I think that Object Oriented (OO) approach is the answer.

This demo shows how to use a set of C++ class to build a multitasking application on top of FreeRTOS.

NOTE: This work was possible thanks to the support of Francesco. My personal thanks for his contribution.

 
Projects.html

Projects  >  Multitasking meets C++ in the embedded world

Main Components


  1. STM3210C-EVAL evaluation

  2. FreeRTOS.org real time scheduler - version 7.0.0.

  3. OpenOCD  On-Chip debug solution for embedded target systems.

  4. Eclipse open development platform - version Helios SR2.

  5. Sourcery G++ C/C++ tool chain.

  6. Versaloon hardware IF.

 

The Demo


Introduction 

This demo is the classical Hello World demo for embedded application. It uses three tasks in order to blink the four user led of the evaluation board. I also have added some of the standard FreeRTOS demo tasks. The challenge was to write the source code using the C++ language and to design all tasks as a C++ class. For this purpose  I developed a set of class, the FreeRTOS Extension Class, that wrapper the FreeRTOS handle and provide an object oriented interface for the standard FreeRTOS API.

The class design

Let me start by showing the whole picture.


Fig. 1 - Architecture (UML)


Just on top of the FreeRTOS API layer there is the FreeRTOS Extension Class layer. It consists of a set of classes logically split into two groups. The lower one is the FreeRTOS Wrapper. The upper group is the Managed Class Extension. This layer aims to provide a Object Oriented framework to develop a FreeRTOS application.


The FreeRTOOS wrapper class

The main feature of these objects are:

  1. To be able to attach to and detach from a native FreeRTOS handle.

  2. To expose all standard FreeRTOS API.

  3. To minimize the memory footprint of the class hierarchy.


All class in this sublayer, but CFreeRTOS, implement the FreeRTOS protocol declared in the interface IFreeRTOSObj. The FreeRTOS protocol declare the following pure virtual method:

bool IsValid() const

void Attach(xGenericHandle handle)

xGenericHandle Detach()


When an object is allocated it is not attached to a native FreeRTOS handle. This means that FreeRTOS resources are not allocated and the IsValid method returns false. To alloc the FreeRTOS resource the application calls the Create method of the object. This method attaches the handle of the FreeRTOS resource to the object. From this moment on the object owns the handle (and hence the low level resource) and the IsValid method return true. For example the following lines of code create a mutex object:

CMutex g_aMutex // global mutex declaration.


void taskControlFunc(void *pParams) // a task control loop

{

       g_aMutex.Create();


       for (;;)

          while (g_aMutex.Take(portMAX_DELAY) != pdTRUE) {

            // do something

          }

}


When the object is disposed its owned handle is deleted and the FreeRTOS resources are freed. It is possible to keep the low level FreeRTOS handle alive by detaching it before the owner object is disposed.

All class in this layer declare just one member that is the owned handle. In this way the RAM footprint is limited to the four byte of the handle plus the virtual table. Moreover all methods wrapping the native API are inline.

For more information on FreeRTOS Wrapper Class see the html API documentation available for download in the Download area. (AGGIUNGERE IL LINK)


The Managed class Extension

This layer aims to provide a more structured and easy to use programming approach to the application. At the moment it has only one abstract class, the AManagedTask class.


Fig. 2 - AManagedClass collaboration diagram

The class inherits from the CTask class, so it is able to create and manage a task. It adds to the public interface of the CTask class the following two methods:

virtual bool HardwareInit() { return true; }

virtual void Run() =0;


To create a task using the managed extension, the application makes a subclasses of the AManagedTask class and provides the task control loop by implementing the Run method in the derived class.

class CMyTask: public AManagedTask

{

    // Task private variables.


public:

    void Run() { /* task control loop*/ };

}


Then instantiate the task object and call its Create method. That is all! The application could be designed as a set o self contained global task objects.

/**

 * Heap allocated task object

 */

CHelloWorld *g_pLed1Task;


/**

 * Global task objects.

 */

CHelloWorld g_Led2Task(GPIOE, GPIO_Pin_14, 1000);

CCheckTask g_checkTask(4000/portTICK_RATE_MS);


/**

 * Main program.

 */

int main() {


    prvSetupHardware();


    // While usually dynamic memory is not a good practice in an embedded

    // program, this task is allocated on the heap only to test if the low

    // level layer works fine.

    // (linker script and runtime support)

    g_pLed1Task = new CHelloWorld(GPIOD, GPIO_Pin_13, 2000);

    g_pLed1Task->Create("Led1", configMINIMAL_STACK_SIZE, mainFLASH_TASK_PRIORITY);


    g_Led2Task.Create("Led2", configMINIMAL_STACK_SIZE, mainFLASH_TASK_PRIORITY);


    // Static task object

    static CHelloWorld led3Task(GPIOD, GPIO_Pin_3 | GPIO_Pin_4, 3000);

    led3Task.Create("Led3", configMINIMAL_STACK_SIZE, mainFLASH_TASK_PRIORITY);


    CFreeRTOS::InitHardwareForManagedTasks();

    CFreeRTOS::StartScheduler();


    while(1);


    return 0;

}


If the task needs to perform some hardware initialization, like to configure the GPIO pins, it overrides the HardwareInit method. The framework calls this method for each managed task before the scheduler starts. The flow control of the application starting phase is like the one showed in Fig. 3.


Fig. 3 - main flow

For an example look at the CHelloWorld class.


The CHelloWorld class (an example)

Do you remember the purpose of this demo? It is to create a task in order to blink  one or more led at a given rate. A class need some parameters to implement this task, so it could be declared in the following way:

class CHelloWorld {
    GPIO_TypeDef *m_pPort;

    uint16_t m_pin;

    bool m_bState;

    portTickType m_nFlashRate;

public:
    CHelloWorld(GPIO_TypeDef *pPort, uint16_t pin, portTickType nFlashRate);

    virtual ~CHelloWorld();

    void SetFlashRate(portTickType nFlashRate);

    bool IsOn() const { return m_bState; }

};


The object must know what pins it manage - on the STM32 this is specified by a GPIO port (m_pPort) - a pin number (m_pin) and the wanted rate (m_nFlashRate). These class members are private to hidden the class implementation. Moreover the m_bState member store the on/off state of the led. The methods IsOn and SetFlashRate allow  to retrieve information about (the first one) and to set (the last one) the internal state of the task in a controlled manner. When an object is allocated, it is configured by passing the needed parameters to the constructor CHelloWorld(GPIO_TypeDef *pPort, uint16_t pin, portTickType nFlashRate).

To perform the wanted behavior a CHelloWorld object must be also a task so that it has its own execution flow. This is easy to do using the C++ inheritance feature. Let the CHelloWorld class to derive from the AManagedTask class.

class CHelloWorld: public AManagedTask {
    GPIO_TypeDef *m_pPort;

    uint16_t m_pin;

    bool m_bState;

    portTickType m_nFlashRate;

public:
    CHelloWorld(GPIO_TypeDef *pPort, uint16_t pin, portTickType nFlashRate);

    virtual ~CHelloWorld();

    void SetFlashRate(portTickType nFlashRate);

    bool IsOn() const { return m_bState; }

    void Run();

};


The last step is to implement the task control loop. Since the class derives from the AManagedTask class this must be done by overriding  the Run method. So I added the declaration of the Run method in the CHelloWorld class declaration and following is the method definition:

void CHelloWorld::Run() {

    portTickType xLastFlashTime;

    xLastFlashTime = GetTickCount();


    for(;;)

    {

        /* Delay for half the flash period then turn the LED on. */

        DelayUntil(&xLastFlashTime, m_nFlashRate);

        GPIO_WriteBit(m_pPort, m_pin, m_bState ? Bit_SET : Bit_RESET);

        m_bState = !m_bState;


        /* Delay for half the flash period then turn the LED off. */

        DelayUntil(&xLastFlashTime, m_nFlashRate);

        GPIO_WriteBit(m_pPort, m_pin, m_bState ? Bit_SET : Bit_RESET);

        m_bState = !m_bState;

    }


}


Now the class is linked to the framework - FreeRTOS Extension Class - and ready to be used.

 

More Details...



C++ Limitations

Not all C++ features were used for this demo. Even if the embedded world is growing quickly, at the moment the MCU are not powerful enough, so I choose to not use these language features:

  1. Standard Template Library (STL). The data structures used in my projects are simple to implement without using the powerful and flexible but memory consuming STL.

  2. Run Time Type Information (RTTI).  I disabled the RTTI compiler support (-fno-rtti compiler flag) to avoid the compiler from generating extra code for each class.

  3. C++ Exception handling. I disabled the exception handling (-fno-exceptions) that produce significant data size overhead.


About FreeRTOS configuration parameters

"A number of configurable parameters exist that allow the FreeRTOS kernel to be tailored to your particular application. These items are located in a file called FreeRTOSConfig.h" as explained in this web page. For example it is possible to reduce the scheduler ROM footprint by adding the following define in the configuration file:


#define INCLUDE_xTaskGetSchedulerState 0


In this way the preprocessor excludes the function xTaskGetSchedulerState, and if the application uses that function, a compiler error is raised. How the FreeRTOS Extension Class handle the FreeRTOS configuration options? Let me show the implementation of the CTask::GetSchedulerState metod.

inline

portBASE_TYPE CTask::GetSchedulerState() {

#if ( INCLUDE_xTaskGetSchedulerState == 1 )

    return xTaskGetSchedulerState();

#else

    return 0;

#endif

}


When the INCLUDE_xTaskGetSchedulerState is defined to zero the method is declared as an empty method, so the compiler does not raise the error if the application call that method. This is the default behavior for the class library.


How to modify the linker script for a different STM32 MCU?

Francesco gives me a very interesting tutorial about the linker script and how to changing it according to the memory capability of the MCU I use. I published it in this web page. Once again, thanks a lot Francesco!

 

Download


The latest version of the the demo is located in the Download page of the site.

Related component are:

  1. FreeRTOS_EC.


Related demo are:

  1. Cortex_STM32F107_CPP


Related Template are:

  1. Cortex_STM32F1xx_CPP_Template

 

Latest news

 
Downloading RSS feed…