Close

Write better Arduino code with advanced C++ features

frank-bussFrank Buss wrote 08/09/2018 at 22:19 • 3 min read • Like

The Arduino IDE allows to use advanced C++ features like classes and operator overloading. There are not many Arduino sketches that uses these features, but it can help to make your code more readable and maintainable.

This is an example how to make the standard "blink" example easier to read:

// general class to simplify the syntax to write to a pin
class PinOut {
  public:
    PinOut(uint8_t pin): m_pin(pin) {
      pinMode(pin, OUTPUT);
    }

    PinOut& operator= (uint8_t state) {
      digitalWrite(m_pin, state ? HIGH : LOW);
      return *this;
    }

  private:
    uint8_t m_pin;
};

// example usage: create a "led" object, which uses the pin number LED_BUILTIN
PinOut led(LED_BUILTIN);

void setup() {}

// much easier to read "blinking" example
void loop() {
  led = 1;
  delay(1000);
  led = 0;
  delay(1000);
}

So instead of writing "digitalWrite(LED_BUILTIN, HIGH)" whenever you want to light up the LED, now you can just write "led = 1". You can create more objects for other pins as well, which makes your program much easier to read.

Left as an exercise to the reader to implement a class for reading pins.

You could outsource the class in a separate library, then you need just an include to use the class, without always copying the class declaration in your sketch.

This example was inspired by the PortOut class of the mbed framework:

https://os.mbed.com/handbook/PortOut

It can be even faster than the standard function. cosnider this code:

void loop() {
  while (true) {
    digitalWrite(31, HIGH);
    digitalWrite(31, LOW);
  }
}

On an Arduino Zero it creates a square wave of 330 kHz on pin 31.

In this posting someone wrote a digitalWrite_fast function. This improves the speed and it runs with about 1.4 MHz. But with a C++ class, the lookup for the register can be cached in the constructor. And with overloading the "int" cast operator, the value of the port can be read by just specifying the object (this class assumes the pinMode is set outside as usual) :

// class definition
class FastPin {
  public:
    // cache the ports and bit mask to change and read the port
    FastPin(int pin) {
      m_portSet = &PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg;
      m_portClear = &PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg;
      m_inReg = &PORT->Group[g_APinDescription[pin].ulPort].IN.reg;
      m_mask = (1ul << g_APinDescription[pin].ulPin);
    }

    // operator= overload, to write to the pin
    inline FastPin& operator= (uint8_t state) {
      if (state) *m_portSet = m_mask; else *m_portClear = m_mask;
      return *this;
    }

    // int cast operator overload, to read the pin
    inline operator int() const { return ((*m_inReg) & m_mask) > 0; }
    
  private:
    volatile uint32_t* m_portSet;
    volatile uint32_t* m_portClear;
    volatile uint32_t* m_inReg;
    uint32_t m_mask;
};

// example how to use it
#define BUTTON_PIN 24
#define TOGGLE_PIN 31
FastPin someButton(BUTTON_PIN);
FastPin myPin(TOGGLE_PIN);

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(TOGGLE_PIN, OUTPUT);
}

void loop() {
  if (someButton == LOW) {
    while (true) {
      myPin = HIGH;
      myPin = LOW;
    }
  }
}

This toggles the pin with 3.4 MHz, when the button is pressed.

Like

Discussions