Multitasking meets C++ in the embedded world

Last updated February 23, 2011

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.



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


Main Components


The Demo

Please refer to the instructions provided in the Eclipse demo (STM32)  to install and configure Eclipse, and note that the project is ready to be used with Eclipse Helios.

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 attach the handle of the FreeRTOS resource to the object. From this moment 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.

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 inherit 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, and 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; }

};

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 add 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:
  • 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.
  • 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.
  • 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.

A note on the FreeRTOS_ROOT environment variable

The FreeRTOS_ROOT variable must refer to the root of FreeRTOS installation. Eclipse Helios has improved the environment variable management. Follow this instruction to modify the variable:
  • Select the project folder (CppDemo) in the C/C++ Projects view.
  • Select the Properties command from the Project menu to open the Properties dialog.
  • In the three view on the left select the Paths and Symbols as showed in the following picture.

  • Select the Source Location tab on the right.
  • Press the Link Folder button on the right. The New Folder dialog is displayed.
  • Check the Link to folder in the file system check box and press the Variables button to open the Select Path Variable dialog.

 

  • Select the FreeRTOS_ROOT variable and press the Edit to change it.
It is more easy to do than to explain! :-)

The Managed Build System (MBS) Plug-in

The  workspace contains two projects (Fig. 4). The second project, CppDemo_mbs, is closed. To open the project, select it and choose the Open Project
 command from the Project menu.
Fig. 4 - Workspace Projects

This project has not a Makefile, but you can build it with the Build command. This is because this project uses the managed build system of the Eclipse framework. This means that the Makefile is automatically generated and managed by the IDE for all project files. This is a great features if you don't like to manually edit a Makefile! I configured this workspace so that the source files are the same for both projects, so it is possible to use the preferred project type, the Standard Makefile project (CppDemo) or the Managed project (CppDemo_mbs). To use the last one you have to install the GNU ARM Eclipse Plug-in, a great open source project! :-) Simply follow the The recommended way in the  plug-in installation instruction  web page.
Remember to modify the OpenOCD configuration file stm32_program_eclipse_jtag.cfg, located in the workspace folder, in order to tell to OpenOCD the location of the binary image to flash.

#flash write_image ./CppDemo/bin/firmware.elf 0x08000000 elf
flash write_image ./CppDemo_mbs/Debug/CppDemo_mbs.elf 0x08000000 elf

Uncomment one of the two script command showed above according to the active project.

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 demo file is located in the Download page of the site. To build the demo are needed the following components:
  • The FreeRTOS source files: Source.zip
  • The files shared by all demos: Common.zip
  • The demo specific files: Cortex_STM32F107_CPP.zip
Please look at the Download web page for more details.

Latest news

  • New version released The FreeRTOS Extension Class now support the Firmware Component Architecture (FCA) concept.I published a new web site in order to review my development approach without disposing all information I ...
    Posted Jul 22, 2011, 3:45 PM by Stefano Oliveri
  • Documentation update Added a section (4.5 How to modify the linker script for a different STM32 MCU?) to explain the memory organization and how it is related to the linker script
    Posted Sep 9, 2010, 2:58 AM by Stefano Oliveri
  • Documentation update Added the section The CHelloWorld class (an example)
    Posted Sep 7, 2010, 2:05 AM by Stefano Oliveri
  • Uploaded demo version v1.0.0 and API html documentation What's news:Added the CSemTest standard demo task.Renamed the anstract class CManagedTask to AManagedTask.All FreeRTOS API wrapper method are now inline.Added support for the FreeRTOS configuration ...
    Posted Aug 19, 2010, 8:48 AM by Stefano Oliveri
  • Modified the application demo page. The work on documentation go on! Every feedback to improve the reliability and to make more understandable the idea of this demo is welcome.To leave a feedback use the ...
    Posted Aug 18, 2010, 5:55 AM by Stefano Oliveri
Showing posts 1 - 5 of 8. View more »


Questions, Suggestions, Bugs, and...

Every feedback is welcome. If you have a suggestion to improve a demo or the web pages, it's ok. If you found a bug in a demo, please let me know. If you have an idea to improve a demo, I'm pleasure to discuss about it. If you have any questions about a demo, I hope to have the answer!

If you want leave a feedback, please use this web page.

Users wishing to obtain support could also use the Open Forum.

All the code is developed for test purpose and it is unsupported. The author assumes no responsibility for any damage caused by improper uses.