Skip to content

Fun with modern Cpp

luni64 edited this page Sep 9, 2020 · 16 revisions

Here a few things from the more modern corners of c++ which can be useful in embedded applications.

Initializing arrays of class objects

If you have a class with a default constructor (parameter less constructor) you can easily declare arrays of objects of this class. Here an example showing how to do this with Servos

#include "Servo.h"

Servo servos[5];         // array of 5 Servo objects

void setup()
{
   servos[0].attach(3);  // attach pins to the Servos
   servos[1].attach(7);
   //...
}

However, if the constructor of your class needs parameters you need to pass them as shown below for the Encoder class. The Encoder constructor requires two pin numbers for phase A and phase B respectively.

#include "Encoder.h"

Encoder encoders[]{{1,2}, {4,7}, {0,15}}; // constructs 3 encoders at pins (1,2), (4,7) and (0,15)
constexpr int nrOfEncoders = sizeof(encoders) / sizeof(encoders[0]);

void setup(){}

void loop()
{
    for (int i = 0; i < nrOfEncoders; i++)
    {
        Serial.println(encoders[i].read());
    }
    delay(200);
}

Using initializer lists

Initializer lists can be useful if you need to do something for a list of arbitrary objects. The following example shows how to set the pinMode of a bunch of pins to output:

void setup()
{
    constexpr uint8_t pinA = 3, LED = 13, STP = 7;

    for(uint8_t pin : {pinA, LED, STP}) // for each pin in the initializer list
    {
        pinMode(pin, OUTPUT);
    }
}

You can also use initializer lists as parameters to functions. Let's define a enhancement for the pinMode function which takes a list of pins instead of only one.

void pinMode(std::initializer_list<uint8_t> pins, uint8_t mode){
    for(uint8_t pin : pins){
        pinMode(pin, mode);
    }
}

// usage:
void setup(){
    constexpr uint8_t pinA = 3, LED = 13, STP = 7;
    pinMode({pinA, LED, STP}, OUTPUT);
}

Pimp my callbacks

In the Arduino ecosystem callbacks are often required to be of type void(*)(). I.e., simple pointers to void functions. If, for example, you want to attach callbacks to pin interrupts you'd do something like:

void myCallback_0() {
    Serial.println("pin0");
}

void myCallback_1(){
    Serial.println("pin1");
}

void setup(){
    pinMode(0, INPUT_PULLUP);
    pinMode(1, INPUT_PULLUP);

    attachInterrupt(0, myCallback_0, FALLING);
    attachInterrupt(1, myCallback_1, FALLING);
}

void loop(){
}

Passing constant information to callbacks

While the code shown above works, it might get tedious if we need to attach a lot of interrupts. Would be good to have one callback which would get information about the pin passed in. However, since such a callback requires a the pin as a parameter it is not compatible to the void(*)() functions required by attachInterrupt. Thus, it can not be attached to the pin interrupt directly. We can of course work around this by defining some relay functions:

void myCallback(int pin) {
    Serial.printf("pin %d\n", pin);
}

void relay0(){
    myCallback(0);
}

void relay1(){
    myCallback(1);
}

void setup(){
    pinMode(0, INPUT_PULLUP);
    pinMode(1, INPUT_PULLUP);

    attachInterrupt(0, relay0, FALLING);
    attachInterrupt(1, relay1, FALLING);
}

void loop(){
}

Hm, not really better than the first one. But, we can improve this pattern a lot by letting the compiler do all the work by using anonymous functions aka lambda expressions:

void myCallback(int pin) {
    Serial.print("pin");
    Serial.println(pin);
}

void setup(){
    pinMode(0, INPUT_PULLUP);
    pinMode(1, INPUT_PULLUP);

    attachInterrupt(0, [] { myCallback(0); }, FALLING);
    attachInterrupt(1, [] { myCallback(1); }, FALLING);
}

void loop(){
}

Basically, the lambda expression []{myCallback(0);} tells the compiler to

  • generate an anonymous function which takes no arguments
  • use the expression between the braces as function body and
  • return a pointer to this function.

Which is exactly what we did manually with our relay functions.

Here another example, showing how to blink an LED with an IntervalTimer using a lambda expression.

IntervalTimer t1;

void setup(){
    pinMode(LED_BUILTIN, OUTPUT);
    t1.begin([] { digitalToggleFast(LED_BUILTIN); }, 250'000);
}

void loop(){
}

In case you don't need to change or stop the timer later you can even dispose of the global reference to it by using:

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    (new IntervalTimer())->begin([] { digitalToggleFast(LED_BUILTIN); }, 250'000);
}

void loop(){
}

Which will construct the timer on the heap where it happily blinks the LED in the background forever.

... To be continued...

Clone this wiki locally