Click here to Skip to main content
13,198,322 members (54,187 online)
Click here to Skip to main content
Add your own
alternative version

Stats

10.7K views
14 bookmarked
Posted 29 Feb 2016

C++ and microcontrollers: using and testing

, 29 Feb 2016
Rate this:
Please Sign up or sign in to vote.
My expiriance 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 allready supporting C++. However, this approach is still quite rare. Not so long ago I tried to use C++ for my next project. This experience I'll discuss in this article.

Most part of my work with microcontrollers is written on 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++ is not encountered me for a long time. Even release of new version of the Keil MDK with C++ support do not confused 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 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 with using of C++. Impressed by this, I decided to try 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 object-oriented language. Well-known encapsulation, polymorphism and inheritance are commonplace here. C is a procedural language. There is only the 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:

#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:

#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:

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, 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 languege 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 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 to write two nearly identical file. For example

#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:

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

and two implementations:

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:

 

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 using C you have two main solutions:

  1. Use #define as shown above. This option is not very good because it "blur" the module responsibility. It turns out that it is responsible for several project versions. When count of these files grow support them becomes quite difficult.
  2. Make 2 module 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 rice higher absraction 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 need 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 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 have 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 more dynamic language. Typically, it involves active using of memoty allocation and deallocation. This is big danger of C++. The microcontrollers have very few resources, so controling of 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:

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:

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

Easily can happen situation 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:

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

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:

Suppose we have 2 classes:

#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 copyes of bar class. Then memory for foo class can’t be allocated:

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 copyes of bar class foo class can be created too:

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 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 compleated because of the fragmentation of the heap:

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++ can not 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 anysignificant 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 cover 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 have a very powerful and easy to use code editor. Secondly, Keil mVision have not 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 and.

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

Classes, abstracted from the hardware (eg, 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:

#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 do a couple of stubs, the code that uses the periphery may well be compiled in VisualStudio. 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 peripherial code:

#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 loke this:

#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 VisualStudio 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 so big difference between C and C++ for work with microcontrollers.  So 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 Visaul 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.

License

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

Share

About the Author

Alexandr Surkov
Software Developer (Senior)
Russian Federation Russian Federation
Microsoft MVP, Moscow IoT community leader.

You may also be interested in...

Comments and Discussions

 
PraiseC vs C++ (KISS) Pin
Jan Zumwalt7-Mar-16 10:07
memberJan Zumwalt7-Mar-16 10:07 
SuggestionMythbusting Pin
SpacemanSpliff3-Mar-16 4:09
memberSpacemanSpliff3-Mar-16 4:09 
AnswerRe: Mythbusting Pin
Alexandr Surkov5-Mar-16 21:40
memberAlexandr Surkov5-Mar-16 21:40 
GeneralRe: Mythbusting Pin
Alister Morton21-Mar-16 5:24
memberAlister Morton21-Mar-16 5:24 
GeneralRe: Mythbusting Pin
Alexandr Surkov21-Mar-16 9:38
memberAlexandr Surkov21-Mar-16 9:38 
GeneralRe: Mythbusting Pin
Alister Morton22-Mar-16 6:33
memberAlister Morton22-Mar-16 6:33 
GeneralRe: Mythbusting Pin
Alexandr Surkov22-Mar-16 7:19
memberAlexandr Surkov22-Mar-16 7:19 
QuestionResulting code size of C++ can be an issue. Pin
Member 111906291-Mar-16 7:58
memberMember 111906291-Mar-16 7:58 
AnswerRe: Resulting code size of C++ can be an issue. Pin
Alexandr Surkov1-Mar-16 21:32
memberAlexandr Surkov1-Mar-16 21:32 
PraiseGood work, keep it up Pin
rather_b_sailing1-Mar-16 6:41
memberrather_b_sailing1-Mar-16 6:41 
GeneralRe: Good work, keep it up Pin
Alexandr Surkov1-Mar-16 21:23
memberAlexandr Surkov1-Mar-16 21:23 
QuestionGrat article Pin
IlumioApp29-Feb-16 2:19
professionalIlumioApp29-Feb-16 2:19 
AnswerRe: Grat article Pin
Alexandr Surkov1-Mar-16 21:23
memberAlexandr Surkov1-Mar-16 21:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.171020.1 | Last Updated 29 Feb 2016
Article Copyright 2016 by Alexandr Surkov
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid