Saturday 29 July 2017

Wirtting code in C++ for small MCUs

When you think about C++ what's in your mind? I thought about those fancy complicated powerful GUI programs on PC like you until one day when I was looking at the IAR MSP430 IDE and realized it has been suported C++ for a long time.

If you look around you'll find that many compilers for MCU has the ability to support C++, for example the IAR, Keil, and the GCC. Wild ranges of MCUs can be developed in C++, like TI's MSP430, Atmel's AVR, ARM series from any manufacture, and even the MCS-51 if you choose the right compiler.

First of all, C++ doesn't mean big code size. Programs with GUI on PC becomes large because they use a lot of libraries, C++ itself is as efficient as C, if not better than. I had done a commercial project with MSP430F2312, which has only 8KB of FLASH. I wrote the entire code in C++ and it turns out to be one of the most efficient code I have ever done. Until now I still couldn't believe I had packed so many functionalities in such a small chip.

C++ has 3 major benefits over C, inheritance, polymorphism, and templates. Unfortunately, many C++ compilers for MCUs do not support templates, but polymorphism alone is a thing good enough for you to consider using it. Imagining you wrote a software I2C interface, and you can easily change the configuration to assign which I/O port to assign it, or even changing the mapping I/O on the fly!

Here I'll give an easy example with STM32F103 using Keil's SDK. The code is very simple, it just blink a LED on an I/O pin. Not using any RTOS, not using any timer, interrupt, or any library.

First there is the plain C version:
#include <stm32f10x.h>

#define LED1        0x2000      // PC13

void RCC_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
}

void blink()
{
    unsigned long i;
    while(1)
    {
        GPIOC->ODR ^= LED1;
        for (i=0; i<0x400000; i++);
    }
}    

int main(void)
{
    RCC_Init();
    GPIOC->CRH = 0x44244444;
    blink();
    return 0;
}

The C++ version is a little bit longer than the C code, because there need to be the definitions of the class:
file gpio.h:
#ifndef GPIO_H
#define GPIO_H

class GPIO
{
    public:
    enum Mode
    {
        FLOAT_INPUT = 0x04,
        PUSH_PULL_50M = 0x03,
        PUSH_PULL_2M = 0x02,
        PUSH_PULL_10M = 0x01,
        ANALOG_INPUT = 0x00,
        ALT_PUSH_PULL_50M = 0x0B,
        ALT_PUSH_PULL_2M = 0x0A,
        ALT_PUSH_PULL_10M = 0x09,
        PULL_UP_DOWN_INPUT = 0x08
    };
    GPIO() {}
    virtual ~GPIO() {}
    virtual void Set1() = 0;
    virtual void Set0() = 0;
    virtual void Toggle() = 0;
};

#endif

File main.cpp:
#include "stm32f10x.h"
#include "gpio.h"

class PortCPin : public GPIO
{
    private:
    uint16_t pin;
    public:
    PortCPin( unsigned char pin_number, GPIO::Mode mode = FLOAT_INPUT)
    {
        pin = (uint16_t)1<<pin_number;
        if (pin_number >= 8)
        {
            pin_number -= 8;
            GPIOC->CRH = (GPIOC->CRH & (~((uint32_t)0x0F << (pin_number*4)))) | ((uint32_t)mode << (pin_number*4));
        }
        else
        {
            GPIOC->CRL = (GPIOC->CRL & (~((uint32_t)0x0F << (pin_number*4)))) | ((uint32_t)mode << (pin_number*4));
        }
    }
    ~PortCPin()
    {
    }
    void Set1()
    {
        GPIOC->BSRR = pin;
    }
    void Set0()
    {
        GPIOC->BRR = pin;
    }
    void Toggle()
    {
        GPIOC->ODR ^= pin;
    }
};

void RCC_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
}

void blink(GPIO& pin)
{
    while(1)
    {
        pin.Toggle();
        for (unsigned long i=0; i<0x400000; ++i);
    }
}

int main()
{
    RCC_Init();
    PortCPin    PC13(13, GPIO::PUSH_PULL_2M);
    blink(PC13);
    return 0;
}

The idea behind the code is, the GPIO is a base class of all the independent I/O pins, it provides a common interface for all the I/O operations (here I only included Set1(), Set0(), Toggle() 3 operations, there should be more like read() change_mode() etc). A specific I/O pin should be an instance of a derived class of GPIO. All the functions in the code should use these common interface to operate I/Os, then by power of polymorphism, once these functions are written, they can be used on any I/O pins.

One thing should be of caution is, the definition of PC13 must be behind the RCC_Init() code, it can not be declared as a global, because then the constructor will be executed before the main() then before the RCC is initialized.

If you are curious about the code efficiency, the plain C code is 744 bytes long (mainly because of the system initialization code not listed here), while the C++ version is 788 bytes.

8 comments:

  1. Dynamic polimorphism has been considered tabu for microcontrollers, mainly because the vtable overhead. That's almost true if one considers that some things won't never change in an embedded system. For example, the I2C that you mentioned in your article. We can also enum: UARTs, GPIOs, SPIs, etc. All of them certanly will never change while the system is running; however their definitions (chip port, for example) will change from chip to chip. So, why then do we need to overload our code with the dynamic polymorphism stuff when we can instead use static polymorphism? There is a design pattern that uses templates and metaprogramming:

    https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

    ASAP I'll write an article describing its use for microcontrollers.

    ReplyDelete
  2. Hi, thanks for the comments. Partially agree with you. Using C++ definitely will bring overheads, just as shown in the article, maybe that one of the reasons that C is still the dominant language for embedded world. But it's not unacceptable, considering the RAM are getting much larger and cheaper in micros now.
    Using C++ makes more sense when you need to reuse the code, or you're creating a platform to let further development, a good example is Arduino. To let users to configure which pin is for I2C and which is for UART, C++ will handle it much better than C.
    For project developers using C++ is not very attractive unless they are going to do many and wants the code to be easily reused.
    Anyway I'm not a C++ expert, it's nice to hear your opinion, and I'm very glad to see that you are going to write something about the relative topic and looking forward to seeing it soon, please let me know when it's available. The static polymorphism thing sounds interesting and it's new to me, I'll spend more time to study in it.

    ReplyDelete
  3. Please don't misunderstand me, I wish more people were using C++. This is as fast as C, and we all know, it's even better.

    We also know that in an embedded system nice things like dynamic polymorphism are almost forbidden, or at least, it's not recommended. So, what can we do in order to implement it in the most efficient way? That question led me to the pattern mentioned above. Generic programming is another powerful mechanism that should be used more frequently.

    Among all nice features of OOP, dynamic polymorphism is the one that seems to have drawbacks. Some systems might not notice the overhead at all. Are the keyboard, or the LCD, or the UART port ever change in a particular system? I don't think so. But if this system is a part of a broader family, and one wants to reuse code, at higher levels of abstraction, dynamic polymorphism starts to make sense. But for a regular embedded system static polymorphism might be the best answer.

    In conclusion, everyone should be using C++ !!!

    ReplyDelete
  4. Another idea is to use C++ template. For example

    template
    class GPIO
    {
    // Add all your processor code
    };

    Now, the GPIOADDR is for the GPIO group. So now you can still use C++, code reuse, class etc. But it removes the overhead of virtual functions. Does it solve everything, now, but, just another C++ tool .



    ReplyDelete
  5. So of the code did not make it, but the general idea is use the address of the GPIO in the template.

    ReplyDelete
  6. Hi, I wanted to share with you some of the work I've been talking about, but the overall project needs more space than the available in this post. However, let me show you the general idea:

    int main () {
    xDigitalPin LedGreen (17, true, false);
    xDigitalPin clk (12, false);
    xDigitalPin data (15, false);
    // ... more code
    }

    In this example, xDigitalPin is the pin abstraction, whereas is the underlying code. When porting the framework into another chip, say the LPC1114 or the ATMEGA328, the those lines will become:

    xDigitalPin LedGreen (17, true, false);
    xDigitalPin clk (12, false);
    xDigitalPin data (15, false);

    or

    xDigitalPin LedGreen (17, true, false);
    xDigitalPin clk (12, false);
    xDigitalPin data (15, false);


    and all this at almost zero cost. I know the sintaxys is kind of ugly, but some #define's vanilla it will become clearer and the underlying chip can be hidden, as in my portdefs.hpp file:

    #ifdef LPC812_PORT
    #define DigitalPin xDigitalPin

    #elif LPC1227_PORT
    #define DigitalPin xDigitalPin

    #else
    #error "YOU NEED TO SPECIFY A PLATFORM!"
    #endif


    Hope it's interesting for you

    ReplyDelete
    Replies
    1. Ups, your blog deleted the template syntax!

      xDigitalPin¡¡Pin_LPC812!! LedGreen (17, true, false);
      xDigitalPin¡¡Pin_LPC812!! clk (12, false);
      xDigitalPin¡¡Pin_LPC812!! data (15, false);

      ¡¡ means "opening angle braces"
      !! means "closeing angle braces"

      Definetely I need to write a formal article =)

      Delete
  7. Thanks for everyone above, your ideas are very inspiring and worth me to look carefully into.
    This blog is built on Blogger, seems there are some restrictions in the writing format (like the braces :)), I couldn't even find a nice way to paste source code...

    I'm pretty a novice of C++, you guys are all talking about using static polymorphism, to my understanding, it's a sort of ways of using templates, am I right?

    Templates has a problem, the code could not be changed. If you use virtual functions, you can implement complete different code for the derived version thus fit any varieties in a product series or even complete different chips, this would be great if you want to reuse the code.

    Talking about the overhead, I feel it's not too bad, in the example it only adds up about 40 bytes in code. But I haven't checked the use of stack memory, maybe the vtable consumes much?

    Javier I will be very glad to see your article in more details about this.

    ReplyDelete