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.