Skip to content
luni64 edited this page Apr 27, 2020 · 12 revisions

This tutorial shows some alternatives to the traditional pattern to implement a callback mechanism by function pointers. It ended up being a bit long but using modern c++ is so much fun that it might be worth following through anyway :-). Complete and compiling examples can be downloaded from the GitHub repo

Contents

Traditional Callbacks

The traditional way to provide a callback mechanism is to store the address of a user supplied callback function in a member variable of the callback provider. The provider can then invoke the callback by simply calling it using its stored address.

using callback_t = void (*)(void); // c++11 version of  typedef void(*callback_t)() 

class Provider
{
public:
  Provider(callback_t cb)
  {
    callback = cb;  // store the address of the passed in function
  }

  void doSomething()
  {
    // some code which determines if the invocation of the callback is necessary
    // ...
    if(needed)
    {
      callback();  // invoke the callback through its stored address.
    }
  }

protected:
  callback_t callback;
  bool needed = false;
};

Using the Provider class requires to pass the address of a valid callback function in the constructor:

void myCallback()
{
  Serial.println("called");
}

Provider provider(myCallback);
//....

While this of course works, it has some nasty drawbacks. E.g., passing context, or using a member function as a callback can only be achieved by some ugly workarounds.

Fun with existing libraries

To be done...

Simple Timer Class as Callback Provider

In the course of this tutorial we will use the following simple software timer as an example of a class providing a callback mechanism. Here the class declaration (the complete sketch can be found here):

using callback_t = void (*)(void);   // c++ way to define a function pointer (since c++11)
//typedef void (*callback_t)(void);  // traditional C typedef way works as well

class SimpleTimer
{
 public:
    void begin(unsigned period, callback_t callback);
    void tick(); // call this as often as possible

 protected:
    unsigned period;
    elapsedMicros timer;
    callback_t callback;
};

...and here the corresponding definition:

void SimpleTimer::begin(unsigned _period, callback_t _callback)
{
    period = _period;
    callback = _callback;
    timer = 0;
}

void SimpleTimer::tick()
{
    if (timer >= period)
    {
        timer -= period;
        callback();
    }
}

How does it work?

  • The begin function takes and stores the timer period and the callback function and resets the elapsedMicros based timer variable.

  • The tick() function does the actual timing. You should call it as often as possible. It simply checks if the elapsed time is larger than the timer period and, in this case, invokes the callback function and resets the timer variable.

You use the timer class as shown below. Here we define the callback function onTimer() and attach it to the timer which will call it every 100ms.

SimpleTimer timer;

void setup()
{
    timer.begin(100'000, onTimer);
}

void loop()
{
    timer.tick(); // update
}

void onTimer()    // callback Function
{
    Serial.printf("Called at %d ms\n", millis());
}

Which will print out:

Called at 500 ms
Called at 600 ms
Called at 700 ms
Called at 800 ms
Called at 900 ms
...

A Frequency Generator using the SimpleTimer Class

Now, let's use our SimpleTimer class to implement a frequency generator. The fact that we use a SimpleTimer to generate the frequency can be considered an implementation detail. Thus, we should try to hide the SimpleTimer away from the user and encapsulate it in the frequency generator class instead.

Here the declaration:

class FrequencyGenerator
{
 public:
    FrequencyGenerator(unsigned pin);

    void setFrequency(float Hz);

 protected:
    SimpleTimer timer;  // encapsulate the timer
    unsigned pin;

    void onTimer();     // callback function
};

and here the definition:

FrequencyGenerator::FrequencyGenerator(unsigned _pin)
{
    pin = _pin;
    pinMode(pin, OUTPUT);
}

void FrequencyGenerator::setFrequency(float frequency)
{
    unsigned period = 0.5f * 1E6f / frequency;
    timer.begin(period, onTimer); // attach callback
}

void FrequencyGenerator::onTimer()
{
    digitalWriteFast(pin, !digitalReadFast(pin));
}

The design idea is to define a member function onTimer() which simply toggles the output pin and then attach this function as callback to the encapsulated timer.

Unfortunately, the compiler doesn't like our smart idea at all and complains that we try to pass the address of a non-static member function to timer.begin, which expects an address of a void function returning void;

FrequencyGenerator.cpp: In member function 'void FrequencyGenerator::setFrequency(float)':
FrequencyGenerator.cpp:12:32: error: invalid use of non-static member function
     timer.begin(period, onTimer); // attach callback
                                ^

The root cause for this compiler error is, that all non static member functions carry an implicit, invisible, compiler generated parameter which is needed to identify the actual object the member function belongs to. Thus, the real signature of the onTimer function doesn't fit to the required type (void function returning void). We could of course fix this by making onTimer() static. But, this would make it impossible to use more than one FrequencyGenerator at the same time, since all objects would share the same static onTimer() callback.

Using std::function instead of function pointers

Opposed to a traditional function pointer, a std::function typed variable accepts pretty much any object which can be called. (If you are interested in more details there is a nice writeup on stackoverflow Should I use std::function or a function pointer in C++?)

Before implementing this scheme for the frequency generator class, we'll have a look at some examples demonstrating what we can do with our new toy.

Functors - Add Context to Callbacks

Functors are normal c++ classes with an overridden function call operator. Here a very simple example (the complete code can be downloaded here):

class PulseGenerator
{
 public:
    PulseGenerator(unsigned _pin, unsigned _pulseLength)
        : pin(_pin), pulseLength(_pulseLength)
    {
        pinMode(pin, OUTPUT);
    }

    void operator()()
    {
        digitalWriteFast(pin, HIGH);
        delayMicroseconds(pulseLength);
        digitalWriteFast(pin, LOW);
    }

 protected:
    unsigned pin, pulseLength;
};

The PulseGenerator constructor just stores the passed in pin number and pulse length. The actual pulse generation is done in the overloaded operator(). Since std::function<> variables accept functors we can, without any change in the SimpleTimer class, use a PulseGenerator object as callback. Whenever the timer invokes its callback the overloaded operator() of the pulse generator will called.

SimpleTimer timer;

void setup()
{
    timer.begin(10'000, PulseGenerator(0,2000)); // use a functor as callback and call it every 10ms
}

void loop()
{
    timer.tick();
}

As expected, this sketch produces a train of 2ms pulses with 10ms period.

image

That's nice, but we can do even better.

TBD

Lambda Expressions and Encapsulation

TBD

A simple FunctionGenerator Class

WIP WIP WIP

So, let's improve our timer example by using a std::function<>. Actually, all we have to do is to exchange the type of the callback_t alias at the top of SimpleTimer.h from a function pointer to a std::function<>:

...
#include <functional>

using callback_t = std::function<void(void)>; // define a "void foo(void)" typed std::function
//using callback_t = void (*)(void);          // function pointer

class SimpleTimer
{
 public:
 ...

image

Work in progress...

Clone this wiki locally