Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / unit-testing

C++ and Microcontrollers: Using and Testing

5.00/5 (10 votes)
29 Feb 2016CPOL10 min read 55.5K  
My experience of using C++ with microcontrollers

Introduction

Historically, the primary language for work with the microcontrollers is C. Many large projects are written on it. But life does not stand still. Modern development tools for embedded systems are already supporting C++. However, this approach is still quite rare. Not so long ago, I tried to use C++ for my next project. I'll discuss this experience in this article.

Most part of my work with microcontrollers is written in C. First, it was the customer's requirements, and then it became a habit. At the same time, when I wrote applications for Windows, I used C++ as well as C#.

Questions about selection between C and C++ have not been encountered by me for a long time. Even release of new version of the Keil MDK with C++ support did not confuse me. If you look at Keil demo projects, you will see that everything is written in C. C++ example is placed in a separate folder like a Blinky-project. CMSIS, LPCOpen also written in C. And if "all" are using C, then there is some reason for that.

This situation was changed by .NET Micro Framework. If someone does not know, it is the realization of .NET that allows to write applications for microcontrollers with C# in Visual Studio. More information about it can be found in these articles.

.NET Micro Framework is written using C++. Impressed by this, I decided to try to create another project in C++. I must say that I could not find unambiguous arguments in favor of C ++, but there are some interesting and useful points in this approach.

What is the Difference Between Projects on C and C++?

One of the main differences between C and C++ is that the second is an object-oriented language. Well-known encapsulation, polymorphism and inheritance are commonplace here. C is a procedural language. There are only functions and procedures, and the modules (a pair .h + .c) are used for logical grouping of code. But if you look at how C is used in microcontrollers, we can see the usual object-oriented approach.

Let's look at the code for LEDs from Keil’s example for MCB1000 (Keil_v5\ARM\ Boards\Keil\MCB1000\MCB11C14\CAN_Demo):

LED.h:

C++
#ifndef __LED_H
#define __LED_H

/* LED Definitions */
#define LED_NUM     8                        /* Number of user LEDs          */

extern void LED_init(void);
extern void LED_on  (uint8_t led);
extern void LED_off (uint8_t led);
extern void LED_out (uint8_t led);

#endif

LED.c:

C++
#include "LPC11xx.h"                    /* LPC11xx definitions                */
#include "LED.h"

const unsigned long led_mask[] = {1UL << 0, 1UL << 1, 1UL << 2, 1UL << 3,
                                 1UL << 4, 1UL << 5, 1UL << 6, 1UL << 7 };

/*----------------------------------------------------------------------------
initialize LED Pins
*----------------------------------------------------------------------------*/
void LED_init (void) {

 LPC_SYSCON->SYSAHBCLKCTRL |= (1UL <<  6);     /* enable clock for GPIO      */

 /* configure GPIO as output */
LPC_GPIO2->DIR  |=  (led_mask[0] | led_mask[1] | led_mask[2] | led_mask[3] |
                      led_mask[4] | led_mask[5] | led_mask[6] | led_mask[7] );

 LPC_GPIO2->DATA &= ~(led_mask[0] | led_mask[1] | led_mask[2] | led_mask[3] |
                      led_mask[4] | led_mask[5] | led_mask[6] | led_mask[7] );
}

/*----------------------------------------------------------------------------
Function that turns on requested LED
*----------------------------------------------------------------------------*/
void LED_on (uint8_t num) {

 LPC_GPIO2->DATA |=  led_mask[num];
}

/*----------------------------------------------------------------------------
Function that turns off requested LED
*----------------------------------------------------------------------------*/
void LED_off (uint8_t num) {

 LPC_GPIO2->DATA &= ~led_mask[num];
}

/*----------------------------------------------------------------------------
Output value to LEDs
*----------------------------------------------------------------------------*/
void LED_out(uint8_t value) {
int i;

 for (i = 0; i < LED_NUM; i++) {
   if (value & (1<<i)) {
     LED_on (i);
   } else {
     LED_off(i);
   }
   }
}

If you look closely, you can draw an analogy with the object oriented programming. LED is an object that has a public constant, constructor, three public method and one private field:

C++
class LED
{
private:
       const unsigned long led_mask[] = {1UL << 0, 1UL << 1, 1UL << 2, 1UL << 3,
                                 1UL << 4, 1UL << 5, 1UL << 6, 1UL << 7 };

public:
       unsigned char LED_NUM=8;
public:
       LED(); //Аналог LED_init
       void on  (uint8_t led);
       void off (uint8_t led);
       void out (uint8_t led);
}

Even if the code is written in C, it uses object-oriented programming paradigm. .c file is an object that allows encapsulation mechanisms in the implementation of public methods described in the .h file. But there is no inheritance and polymorphism.

Most of the code in the projects that I have seen, is written in the same style. And if you are using OOP approach, why not use the language, fully supports it? And if you want to change language from C to C++, you will need to change only syntax, but not the design principles.

Consider another example. Suppose we have a device that uses a temperature sensor connected via I2C. But there was a new revision of the device and the same sensor is now connected to SPI. What to do? You should support the first and second revision of the device, it means that the code should be flexible to take into account these changes. In C, you can use the #define predefinitions, in order to not write two nearly identical files. For example:

C++
#ifdef REV1
#include "i2c.h"
#endif
#ifdef REV2
#include "spi.h"
#endif

void TEMPERATURE_init()
{
#ifdef REV1
       I2C_int()
#endif
#ifdef REV2
       SPI_int()
#endif
}

and so on. In C++, you can solve this problem much more elegantly. Make interface:

C++
class ITemperature
{
public:
      virtual unsigned char GetValue() = 0;
}

and two implementations:

C++
class Temperature_I2C: public ITemperature
{
public:
      virtual unsigned char GetValue();
}

class Temperature_SPI: public ITemperature
{
public:
      virtual unsigned char GetValue();
}

Then, you can use a particular implementation depending on the revision:

C++
class TemperatureGetter
{
private:
        ITemperature* _temperature;

public:
        Init(ITemperature* temperature)
        {
                _temperature = temperature;
        }

private:
       void GetTemperature()
       {
               _temperature->GetValue();
       }

#ifdef REV1
Temperature_I2C temperature;
#endif
#ifdef REV2
Temperature_SPI temperature;
#endif

TemperatureGetter tGetter;

void main()
{
       tGetter.Init(&temperature);
}

It seems that the difference is not very large between the code in C and C++. Object-oriented version looks even more cumbersome. But it allows you to make a more flexible solution.

When you are using C, you have two main solutions:

  1. Use #define as shown above. This option is not very good because it "blurs" the module responsibility. It turns out that it is responsible for several project versions. When count of these files grow, support for them becomes quite difficult.
  2. Make two modules as well as in C++. In this solution, the "blur" does not occur, but use of these modules will be more complicated. Since they do not have a single interface, the use of each method of the pair should be enclosed in #ifdef. This degrades the readability and code maintainability. And when separation of the place will raise higher abstraction level, then the code will be more unwieldy. Thus, it is necessary to think of another name for the function of each module, so that they do not overlap, it is also fraught with the deterioration of the readability of the code.

Using of polymorphism gives a beautiful result. On the one hand, each class decides clear atomic problem, on the other hand, the code is not littered.

It is still needed to branch code for board revision in both cases, but use of polymorphism makes it easier to move the place of the branch between the layers of the program with minimum using of #ifdef.

Using of polymorphism makes it easy to make an even more interesting solution.

Suppose there was a new revision, which contains both temperature sensors.

The same code with minimal changes allows you to select SPI or I2C implementation in real time, simply by using Init(&temperature) method.

This example of a very simple, but in a real project, I used the same approach in order to realize the same protocol on top of two different physical data interfaces. This made it easy to make the choice of interface in the device settings.

However, with all the above facts, the difference between using of C and C ++ is not very big. The benefits of C ++, associated with the OOP are not so obvious and are from the category of personal preferences. But the use of C ++ in microcontrollers has some serious problems.

What is the Danger of Using C++?

The second important difference between C and C++ is manner of using memory. C is static in most part. All functions and procedures have fixed addresses and heap is using only when it is necessary. C++ is a more dynamic language. Typically, it involves active using of memory allocation and deallocation. This is a big danger of C++. The microcontrollers have very few resources, so controlling them is very important. The uncontrolled use of RAM is fraught with corruption of data stored there. Many developers are faced with such problems.

If you look carefully at the examples above, it may be noted that the classes do not have constructors and destructors. This is because they have never created dynamically.

Using dynamic memory (even using new keyword) always leads to calling malloc function, which allocates the required number of bytes from the heap. Even if you have thought of everything and will carefully monitor using of memory, you may encounter a problem of its fragmentation.

The heap can be represented as an array. For example, there are 20 bytes in the heap:

Image 1

Every memory allocation leads to review all the memory (from left to right or right to left - is not so important) for the presence of a predetermined amount of idle bytes. Moreover, these bytes must all be located together:

Image 2

When the memory is no longer needed, it is returned to its original state:

Image 3

A situation can easily happen when there are a sufficient number of available bytes, but they are not arranged in a row. Let there be allocated 10 zones with 2 bytes in each zone:

Image 4

Then 2, 4, 6, 8, 10 zones will be released:

Image 5

Formally, half of the heap (10 bytes) remains free. However, you can't allocate memory with size of 3 bytes, since there is no array of 3 located together free cells. This is called memory fragmentation.

This situation can be easily faked. I did this on LPC11C24 with Keil mVision.

Let's set the heap size to 256 bytes:

Image 6

Suppose we have two classes:

C++
#include <stdint.h>

class foo
{
private:
       int32_t _pr1;
       int32_t _pr2;
       int32_t _pr3;
       int32_t _pr4;

       int32_t _pb1;
       int32_t _pb2;
       int32_t _pb3;
       int32_t _pb4;

       int32_t _pc1;
       int32_t _pc2;
       int32_t _pc3;
       int32_t _pc4;

public:
       foo()
       {
              _pr1 = 100;
              _pr2 = 200;
              _pr3 = 300;
              _pr4 = 400;

              _pb1 = 100;
              _pb2 = 200;
              _pb3 = 300;
              _pb4 = 400;

              _pc1 = 100;
              _pc2 = 200;
              _pc3 = 300;
              _pc4 = 400;
       }

       ~foo(){};

       int32_t F1(int32_t a)
       {
              return _pr1*a;
       };

       int32_t F2(int32_t a)
       {
              return _pr1/a;
       };

       int32_t F3(int32_t a)
       {
              return _pr1+a;
       };

       int32_t F4(int32_t a)
       {
              return _pr1-a;
       };

class bar
{
private:
       int32_t _pr1;
       int8_t _pr2;

public:
       bar()
       {
              _pr1 = 100;
              _pr2 = 10;
       }

       ~bar() {};

       int32_t F1(int32_t a)
       {
              return _pr2/a;
       }

       int16_t F2(int32_t a)
       {
              return _pr2*a;
       }
};

As you can see, bar class will take up more memory than foo.

Let’s fill heap by 14 copies of bar class. Then, memory for foo class can’t be allocated:

C++
int main(void)  
{
       foo *f;
       bar *b[14];

       b[0] = new bar();
       b[1] = new bar();
       b[2] = new bar();
       b[3] = new bar();
       b[4] = new bar();
       b[5] = new bar();
       b[6] = new bar();
       b[7] = new bar();
       b[8] = new bar();
       b[9] = new bar();
       b[10] = new bar();
       b[11] = new bar();
       b[12] = new bar();
       b[13] = new bar();

       f = new foo();
}

But if we create only 7 copies of bar class, foo class can be created too:

C++
int main(void)  
{
       foo *f;
       bar *b[14];

       //b[0] = new bar();
       b[1] = new bar();
       //b[2] = new bar();
       b[3] = new bar();
       //b[4] = new bar();
       b[5] = new bar();
       //b[6] = new bar();
       b[7] = new bar();
       //b[8] = new bar();
       b[9] = new bar();
       //b[10] = new bar();
       b[11] = new bar();
       //b[12] = new bar();
       b[13] = new bar();

       f = new foo();
}

However, if you first create the 14 copies of the bar and remove 0, 2, 4, 6, 8,10 and 12 copies, then allocate memory for foo class can’t be completed because of the fragmentation of the heap:

C++
int main(void)  
{
       foo *f;
       bar *b[14];

       b[0] = new bar();
       b[1] = new bar();
       b[2] = new bar();
       b[3] = new bar();
       b[4] = new bar();
       b[5] = new bar();
       b[6] = new bar();
       b[7] = new bar();
       b[8] = new bar();
       b[9] = new bar();
       b[10] = new bar();
       b[11] = new bar();
       b[12] = new bar();
       b[13] = new bar();

       delete b[0];
       delete b[2];
       delete b[4];
       delete b[6];
       delete b[8];
       delete b[10];
       delete b[12];

       f = new foo();
}

It turns out that the full C++ cannot be fully used, and this is a significant drawback. From an architectural point of view, C++ superior to C, but only slightly. As a result, switching to C++ does not bring any significant benefits. But it does not bring any large negative moments too. Thus, because of the small difference, language choice will remain just a personal preference of the developer.

But for myself, I found a significant positive point in the use of C++. The fact is that with the right approach, C++ code for the microcontroller can be easily covered by unit tests in Visual Studio.

A Big Plus of C ++ is the Ability to Use Visual Studio

Testing code for microcontrollers has always been difficult task for me. Code is tested in various ways, but creation of a full automatic test systems always require huge costs, since it is necessary to create special hardware and write special firmware for it. Especially if we are talking about IoT distributed system consisting of hundreds of devices.

When I started writing C++ project, I wanted to try to insert code in Visual Studio and use Keil mVision only for debugging. Firstly, Visual Studio has a very powerful and easy to use code editor. Secondly, Keil mVision does not have friendly integration with version control systems but Visual Studio is all worked out to automaticity. Third, I had hoped that there will be chance to cover part of the code by unit tests, which are also well supported in Visual Studio. And fourthly, it is the new version of Resharper C++ - Visual Studio extension for C++ code which can help you to follow styling of code and to avoid many potential bugs.

Creating a project in Visual Studio, and connecting it to the version control system does not cause any problems. But with unit tests, it was not so easy.

Classes, abstracted from the hardware (e.g., protocol parsers), can be easily tested. But I wanted more. In my projects, I use the header files from Keil to work with peripherals. For example, LPC11xx.h for LPC11C24. These files describe all the registers in accordance with CMSIS standard. Definitions of a particular register is done through #define:

C++
#define LPC_I2C_BASE          (LPC_APB0_BASE + 0x00000)
#define LPC_I2C               ((LPC_I2C_TypeDef    *) LPC_I2C_BASE   )

It turned out that if override registers definitions and does a couple of stubs, the code that uses the periphery may well be compiled in Visual Studio. Moreover, if you make a static class and specify its field as the register addresses, you get a complete microcontroller emulator that allows you to test peripheral code:

C++
#include <LPC11xx.h>

class LPC11C24Emulator
{
public:
       static class Registers
       {
       public:
              static LPC_ADC_TypeDef ADC;

       public:
              static void Init()
              {
                     memset(&ADC, 0x00, sizeof(LPC_ADC_TypeDef));
              }
       };
}

#undef LPC_ADC
#define LPC_ADC ((LPC_ADC_TypeDef *) &LPC11C24Emulator::Registers::ADC)

And then do like this:

C++
#if defined ( _M_IX86 )
#include "..\Emulator\LPC11C24Emulator.h"
#else
#include <LPC11xx.h>
#endif

Thus it is possible to compile and test the entire code of the project for microcontrollers in Visual Studio with minimal changes.

I have written more than 300 tests covering a purely hardware aspects and code that abstracted from the hardware. In advance, I found about 20 serious errors , which, due to the size of the project, would not be easy to detect without automatic testing.

Summary

To use or not to use C++ when working with microcontrollers is a complicated question. Above, I have shown that, on the one hand, the architectural advantages of a full supported OOP is not so great, and, on the other hand, limits of using of heap is quite a big problem. Considering these aspects, there is not such a big difference between C and C++ for work with microcontrollers. So, the choice between them can be justified only by the personal preferences of the developer.

However, I found a great positive point of using C++ - using Visual Studio. This can significantly improve the reliability of the development due to work with version control systems, use of unit tests (including tests of peripherals) and other advantages of Visual Studio.

History

  • 29th February, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)