Lightweight C++ callbacks

PaulStoffregen

Well-known member
I'm considering changing all or most of Teensy callback APIs (IntervalTimer, attachInterrupt, usbMIDI, more to be added) to properly support C++ functions. But not std::function which allocates memory on the heap. Something "lightweight". Maybe one of these?

https://github.com/rosbacke/delegate

https://github.com/rosbacke/MCU-tools/tree/master/src/callback

https://github.com/Naios/function2

https://gist.github.com/twoscomplement/030818a6c38c5a983482dc3a385a3ab8

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0792r3.html
https://github.com/vittorioromeo/Experiments/blob/master/function_ref.cpp

The hard requirements are avoiding heap allocation, ability to call C functions, static C++ functions, class member functions, and MIT-like permissive license. Avoiding C++ template syntax in Arduino sketch code is highly desirable (but template syntax is ok inside libraries and their headers, and maybe we can adopt a practice of typedefs or #defines for the function def). Support for lambda functions would be nice, maybe even mutable lambda functions.

@luni - any thoughts on this?
 
I'm using std::function in the TimerTool and (modulo some teething troubles) it works without issues. The good thing is of course that it is compatible to the usual void(cb*)() callbacks. I'm sure that most users of the TimerTool don't even notice that it has a std::function interface. IIRC std::function usually don't allocate memory on the heap unless you use potentially huge objects like functors as callbacks. #including <functional> can be a bit expensive memory wise but if you use nanolib instead of newlib (which seems to be recommended for microcontrollers anyway) the memory usage shrinks significantly. For a T4 a simple example using std::function & nanolib compiles to some 2.5% ITCM and 2.5% RAM usage.

But that was not the question :)

I had a quick look at the linked libraries but I don't like the awkward syntax. Not sure if this would be used by a lot of library writers and I'm not yet sure how the calling syntax would be for library users. I remember that some time ago I stumbled over a library which claimed to have the same simple syntax as std::function but didn't use dynamic allocation at all. I'll see if I can find it again.

Anyway, did you consider using standard void(*callback)(void*) callbacks to provide state? You could overload the member functions which attach callbacks (e.g. IntervalTimer.begin, attachInterupt etc). Thus, it wouldn't break existing code and advanced users needing state can simply use the overloaded version to attach the stateful callback. This pattern should be quite cheap.

I.e., something like this.
Code:
IntervalTimer t1,t2;
//....
t1.begin(statelessCB, 10'000);
t2.begin(statefulCB, someState, 10'000)

//....

void statelessCB()
{
    Serial.printf("invoked at %d\n", millis());
}

void statefulCB(void* state)
{
    Serial.printf("invoked at %d\n", millis());
}


Your "class member functions" requirement would be fulfiled by the usual pattern:
Code:
class myClass
{
public:
    begin(int number)
    {
        nr = number;
        t1.begin(callback, this, 1000); // use the stateful version of begin
    }

 protected:
    IntervalTimer t1;
    int nr;

    static void callback(void* state)  // the usual static pattern
    {
        myClass* THIS = (myClass*)state;

        Serial.println(THIS->nr);
    }
}

It would accept simple, stateless lambdas (of course even the standard IntervalTimer does)
Code:
IntervalTimer t1;
//....
t1.begin([] { digitalToggle(LED_BUILTIN) }, 100'000);

I'll give the linked libs a closer look and try to find the one I mentioned above. But that will take a few days.
 
Yes, of course I've considered using a plain C-style callback approach where we store the function address and a context pointer. It's the easiest way. The problem is it requires that usual pattern of adding a static callback function which casts the context pointer. I want to avoid the need for that usual pattern.

Apparently std::function can't do this. For std::function to call a class member function with a specific instance, everything I've found says you either need to use std::bind or a lambda function which takes the instance and performs the member call, or of course that usual pattern of adding a static callback which casts the context pointer.

Delegate can do this, but the set() and make() syntax is pretty horrific, exactly the opposite of an Arduino style experience! Delegate also seems to lack certain assignment operators or maybe copy constructors. But I'm still experimenting and learning how it works, so perhaps this is just a limitation of my knowledge...
 
Here's a quick test of Delegate which shows it can indeed handle normal functions, member functions, and stateless lambda.

Code:
#include "delegate.h"

using myCallType = delegate<void(void)>;

// For Arduino library usage we really want an attach() function which
// stores a copy of f, then later we'll use it to call the function.
// But for simple testing, just try calling it now.
void call(myCallType f) {
  f();
}

void hello_function() {
  Serial.println(" Hello World static function");
}

class my_class {
public:
  my_class(int n) : i(n) { }
  void hello() { Serial.printf(" Hello World member, private i=%d\n", i); }
private:
  int i;
};
my_class my_inst(12345);


void setup() {
  Serial.begin(9600);
  if (CrashReport) Serial.println(CrashReport);
  Serial.print("delegate test, sizeof = ");
  Serial.println(sizeof(myCallType));

  // normal functions
  call(hello_function);

  // member functions
  [COLOR="#006400"]//call(my_inst.hello); // desired syntax[/COLOR]
  call(myCallType::make<my_class, &my_class::hello>(my_inst));

  // lambda functions
  [COLOR="#006400"]//call([]{ Serial.println(" Hello World lambda"); }); // desired syntax[/COLOR]
  myCallType f;
  f = []{ Serial.println(" Hello World lambda"); };
  call(f);

  Serial.println("end of setup");
}

void loop() {
}

But the required syntax to assign the callable function is terrible template stuff for member functions.

For lambda functions, I don't understand why Delegate only allows assigning a lambda after its constructed.

If you run this, it prints this to the serial monitor. Delegate definitely can store all 3 cases and successfully call them.

Code:
delegate test, sizeof = 8
 Hello World static function
 Hello World member, private i=12345
 Hello World lambda
end of setup
 

Attachments

  • delegate.h
    33.7 KB · Views: 76
Apparently std::function can't do this. For std::function to call a class member function with a specific instance, everything I've found says you either need to use std::bind or a lambda function which takes the instance and performs the member call, or of course that usual pattern of adding a static callback which casts the context pointer.

Yes, whatever you invent, at the end you need to pass a pointer to the instance somehow. However, std::function makes this very simple. Here an example which shows how to embedd a callback-provider (here a PeriodicTimer which provides a std::function API) to a user class. Which I think is the the most intersting use case.

Code:
#include "Arduino.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

void freeCallback()
{
  Serial.println("free callback");
}

class Test  // the class embeds a callback provider (here a PeriodicTimer)
{
public:
  void begin(unsigned microseconds)
  {
    // timer.begin(freeCallback, microseconds);                             // accepts free void(*void) directly
    timer.begin([this] { embeddedCallback(); },microseconds); // lambda with capture, e.g. to call an embeded callback
    // timer.begin([]{ Serial.println("simple lambda");}, microseconds );   // simple lambda without capture
  }

protected:
  void embeddedCallback()
  {
    Serial.println("embedded");
  }

  PeriodicTimer timer;
};

Test test;

void setup()
{
  while (!Serial) {}
  test.begin(500'000);
}

void loop()
{
}

I'll try how to implement this with deglegate.h later today.
 
Sorry,

I am obviously missing something obvious, but it is unclear to me on what problem it is that is trying to be solved?
Where would it be used? For example, are you planning on changing standard things like: attachInterrupt and attachInterruptVector, and IntervalTimer ... ?

And what are the constraints? Things like: Still feels like normal Arduino code, that the majority of Arduino users would understand?

For me (an old dog that may have hard time learning new tricks :D ) Reading the github readme for the delegate library just made my head hurt.
It looked like a gibberish that was written in a different language.

But again, I am an old C/Fortran/PL1 programmer who transitioned to c++. Who has not embraced many of the new c++ things such as templates and lambda.

So I Personally prefer the KISS approach of simply have a void* or uiint32_t at end...

But that is just me.
 
what problem it is that is trying to be solved?

My main goal is for API's like attachInterrupt() to be able to accept either ordinary functions or non-static class member functions. For novice users, it's a "void function()" they can attach it to an interrupt regardless of whether it's a normal function or inside some library they're using. If the function takes the correct number & type of inputs (often none for callbacks) and returns the correct thing (often void for callbacks) they ought to be able to attach it, without having to learn any internal design stuff about the library, not to mention internal details about how C++ actually implements things.

Personally I'm less concerned about lambdas, but they have been requested enough that lambda is on my "nice to have" list. I'm pretty sure we're only going to make stateless lambdas work.


Where would it be used? For example, are you planning on changing standard things like: attachInterrupt and attachInterruptVector, and IntervalTimer ... ?

Eventually it would be used almost everywhere. Definitely attachInterrupt & IntervalTimer. Probably FS.h for media change. But attachInterruptVector is too close to the hardware for this sort of thing.


And what are the constraints? Things like: Still feels like normal Arduino code, that the majority of Arduino users would understand?

Yes, Arduino style syntax is essential, at least in the sketch code. I fear this may be impossible with C++14. Inside library code, one "using" or "typedef" with template syntax will probably be needed to create a name of the callback type definition. A small amount of template syntax needed inside library headers is ok. A huge amount isn't. Of course, the headers defining this sort of thing will be loaded with complex template stuff.

This page give a good summary of the design choices involved with std::function.

https://quuxplusone.github.io/blog/2019/03/27/design-space-for-std-function/

I believe the main question is "Owning or non-owning?" I'm pretty sure we want non-owning. The intended usage is callbacks to either static functions or member functions where the instance lives forever. If someone dynamically creates a class instance with C++ new and then calls attachInterrupt() with a member function of that instance, it's their responsibility to call detachInterrupt() before they delete the instance.



Reading the github readme for the delegate library just made my head hurt.

Me too. I mostly think in terms of analog electronics and assembly language. Plain old C feels pretty high level to me.


So I Personally prefer the KISS approach of simply have a void* or uiint32_t at end...

Maybe I should just add plain C void pointer to each individual callback API and not bother with C++ stuff. But right now we're at the beginning of another software cycle, having just released stable version 1.57, so at this moment I have a bit more appetite for exploring possibly risky changes. If this sort of thing is going to happen, it wants to be done before we start testing beta versions and ultimately lead up to version 1.58.
 
I don't have much to add other than C++ style callbacks would be useful. For example, setting up a CAN filter in FlexCAN for a message and add a callback when that message is received to a second library to parse and handle the message.

EDIT: it could also be cool to setup some lightweight scheduler and trigger events through timers or interrupts where the callback can be a member of a class.
 
As, maybe a bit more than a newbie, this is all giving me a headache, i.e., delegate type callbacks. Still getting use to current way we do callbacks. As you said
Yes, Arduino style syntax is essential, at least in the sketch code. I fear this may be impossible with C++14. Inside library code, one "using" or "typedef" with template syntax will probably be needed to create a name of the callback type definition. A small amount of template syntax needed inside library headers is ok. A huge amount isn't. Of course, the headers defining this sort of thing will be loaded with complex template stuff.
this definitely does not feel like normal arduino style usage and going to take a bit of getting use to.

You also mentioned in your first post that you wanted
The hard requirements are avoiding heap allocation.....
is that really critical?

Also guess other concern is that with this change how many libraries are going to break or will both syntaxes still be operational delegate and function as well as current sketches? I know real newbie question but thought crossed my mind.

Over the based several hours been looking at what "Lightweight C++ Callbacks" are using Google search. And most seem to be pointing back to the rosbacke library although did find a couple of other interesting ones: etlcpp and cpp-delegate. CPP-Delegate seemed the simplest to play with so gave it a shot:
Code:
#include <Streaming.h>
#include "delegate.hpp" //https://github.com/kaidokert/cpp-delegate

#define cout Serial

//using myCallType = delegate<void(void)>;

// For Arduino library usage we really want an attach() function which
// stores a copy of f, then later we'll use it to call the function.
// But for simple testing, just try calling it now.
//void call(myCallType f) {
//  f();
//}

void hello_function() {
  Serial.println(" Hello World static function");
}

class my_class {
public:
  my_class() { }
  void hello(int param) { Serial.printf(" Hello World member, private i=%d\n", param); }
};
my_class my_inst;


class my_class1 {
public:
  my_class1(int n) : i(n) { }
  void hello() { Serial.printf(" Hello World member (myClass1), private i=%d\n", i); }
private:
  int i;
};
my_class1 my_inst1(12345);

void setup() {
  Serial.begin(9600 && millis() < 5000);
  if (CrashReport) Serial.println(CrashReport);

  // normal functions
  //call(hello_function);
  auto delegate = CREATE_DELEGATE(&hello_function);
  delegate();

  // member functions
  //call(my_inst.hello); // desired syntax
  //call(myCallType::make<my_class, &my_class::hello>(my_inst));
  auto delegate1 = CREATE_DELEGATE(&my_class::hello, &my_inst);
  delegate1(12345);

  auto delegate2 = CREATE_DELEGATE(&my_class1::hello, &my_inst1);
  delegate2();


  delegate();

  // lambda functions
  //call([]{ Serial.println(" Hello World lambda"); }); // desired syntax
  //myCallType f;
  //f = []{ Serial.println(" Hello World lambda"); };
  //call(f);


  Serial.println("end of setup");
}

void loop() {}
really crude as there are ways to resuse things, but at least it was easier to understand. Heres the output:
Code:
 Hello World static function
 Hello World member, private i=12345
 Hello World member (myClass1), private i=12345
 Hello World static function
end of setup
 
Regarding avoiding heap allocation, I really like being able to allocate everything on the stack, it gives me some confidence of the stability of my code running for long periods of time. That tends to push me to using templates a lot more though.
 
EDIT: it could also be cool to setup some lightweight scheduler and trigger events through timers or interrupts where the callback can be a member of a class.
I agree, I really wish there was some simple scheduler. There are a lot of places where the code could be a lot easier to develop and maintain the code if you could write it in a straightforward way, versus doing things like state machines and the like. I find state machine tend to get convoluted very quickly. Or alternatively you write the code in a straightforward way and use spin loops waiting for your event to happen before continuing. Which in many cases works, but preclude anything else running, except maybe something using interrupts.

Regarding avoiding heap allocation, I really like being able to allocate everything on the stack, it gives me some confidence of the stability of my code running for long periods of time. That tends to push me to using templates a lot more though.

I usually prefer the static allocate approach, along with allocate once typically at startup. In the past I tried to minimize stack usage as I found stack overruns one of the hardest bugs to debug. But I to totally avoid things like string classes or STD:: type classes that use things like malloc/free or new...

Although I do break my own rules for using malloc in places for example where maybe I am going to read in a bitmap file, as it is usually self-contained and I can detect if a malloc failed, but not sure how to check if putting the image on the stack will fit or not.

Bedsides I am only doing this for the fun of it. Most of my things run for a few minutes, before I am off to doing something else :D
 
My main goal is for API's like attachInterrupt() to be able to accept either ordinary functions or non-static class member functions. For novice users, it's a "void function()" they can attach it to an interrupt regardless of whether it's a normal function or inside some library they're using.
That can't work for a non static member function since the function invoking the callback needs to know the instance on which it shall invoke the member function. So, you need to pass that information somehow (e.g. memberfunction pointer syntax, templates, lambdas, things like delegate<> etc).

Personally I'm less concerned about lambdas, but they have been requested enough that lambda is on my "nice to have" list. I'm pretty sure we're only going to make stateless lambdas work.

There is nothing to do, stateless (i.e., non capturing) lambdas work out of the box. Here an example using the stock IntervalTimer:

Code:
IntervalTimer t;

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

void loop()
{
}

You can also use them to call nonstatic member functions of classes. Here an example using an IntervalTimer and your test class from above:
Code:
class my_class
{
 public:
    my_class(int n) : i(n) {}
    void hello() { Serial.printf(" Hello World member, private i=%d\n", i); }

 private:
    int i;
};

my_class my_inst(12345);

IntervalTimer t;

void setup()
{    
    t.begin([] { my_inst.hello(); }, 500'000);
}

void loop()
{
}

The lambda syntax needs some getting-used-to time, but actually it is quite logical and really easy to use. Plus, it gives the compiler more chances for optimization than using an opaque function pointer.

I'd say the most prominent use case for passing variable state to a callback is, if you want to embedd the callback provider (here the IntervalTimer) in another class. std::function makes this super easy since it allows lambdas with captures. (for an example see my post above).

All the static allocation workarounds I've seen so far suffer from the fact that they need to prepare the callback somehow before you can attach it. I assume that this is necessary to encapsulate the state (which can be aritrary large) in the prepared object. Since std::function does this using dynamic allocation it can be done inside std::function and there is no need to "prepare" the callback by the user.

Maybe I should just add plain C void pointer to each individual callback API and not bother with C++ stuff...

I think that this, together with the already working non-capture lambdas fulfills all your requirements, is super cheap, does not need dynamic allocation and is used by ESP32. They provide two different "attach" methods. One for the usual void(*)() callbacks and one for the void(*)(void*) stateful callbacks.

IMHO, passing variable state is most often used to embed "callback-providers" in other classes. Users doing this probably have no difficulty understanding the ubiquitous void* pattern. At least, it is much easier to understand than the weird syntax of the std::function workarounds.

Just my 2 cents.
 
Last edited:
That can't work for a non static member function since the function invoking the callback needs to know the instance on which it shall invoke the member function. So, you need to pass that information somehow (e.g. memberfunction pointer syntax, templates, lambdas, things like delegate<> etc).

I don't understand why this would be lacking required information.

call(my_inst.hello); // desired syntax (msg #4)

"my_inst" has the instance info, the "this" pointer, and it's type is known to be "class my_class". "hello" should be enough for the function pointer address. If my_class has multiple overloaded hello() functions, which is required should be implied by the call() function's input arg type is Delegate with a particular template to know which hello() applies. Maybe there are thorny issues with const, constexpr, volatile or pure vs impure which I don't understand?

But more likely, perhaps C++14 or even C++17 doesn't have powerful enough template specialization to infer the correct delegate constructor? Or maybe it is possible somehow, the author of Delegate just didn't feel the need to support nicer syntax than the make() and set() APIs?

In the horrible syntax, we have to give "my_class" twice even through it's the type of "my_inst". We have to call the static make() function to create a Delegate instance, even though the call() input arg implies the compiler should try to create a Delegate temporary.

call(myCallType::make<my_class, &my_class::hello>(my_inst));

If we could extend Delegate's constructors somehow with template specialization to allow the desired simple syntax rather than this horrible static make() API, it would be the perfect solution.
 
I don't understand why this would be lacking required information.
call(my_inst.hello); // desired syntax (msg #4)

Unfortunately (AFAIK) a free standing "my_inst.hello" (without following paranthesesis) is not a legal construct at all (would be nice but it isn't). Here some explanations and results of my trials to attach member function callbacks without using lambdas. Disclaimer: Of course there might be better solutions by real c++ programmers.

You probably know that, if you need a pointer to a member function, you need to write &MyClass::hello. To actually call it you need to call it through an instance of MyClass.
Here a simple example:

Code:
class Test
{
 public:
    void myFunc1(){
        Serial.printf("myFunc 1 %d\n", i);
    }
    void myFunc2(){
        Serial.printf("myFunc 2 %d\n", i * 2);
    }

    int i = 0;
};

void setup()
{
    while (!Serial) {}

    Test test;
    test.i = 42;

    using memberFunctionPtr = void (Test::*)();  // not as weird as it looks if you compare to a pointer to a free standing function: using fp = void (*)();
   
    memberFunctionPtr mfp = &Test::myFunc1;
    //memberFunctionPtr mfp = &Test::myFunc2;  // you can use mfp to point to any void()(void) member function of class Test

    (test.*mfp)(); // calling through the pointer needs the instance.
}

void loop(){}

The problem with this is, that the member function pointer type contains the class, so you can not simply pass a general member function pointer to another function. This is where templates can help.
Code:
class Test
{
 public:
    void myFunc1(){
        Serial.printf("myFunc 1 %d\n", i);
    }
    void myFunc2(){
        Serial.printf("myFunc 2 %d\n", i * 2);
    }

    int i = 0;
};

// call any void(*)() member of any class
template <typename T>
void call(void (T::*fp)(), T& instance)
{
    (instance.*fp)();
}

void setup()
{
    while (!Serial) {}

    Test test;
    test.i = 42;

    call(&Test::myFunc1, test);
    call(&Test::myFunc2, test);
}

void loop(){}
which prints
Code:
myFunc 1 42
myFunc 2 84

If you want to use this pattern for attaching callbacks you need to be able to store the class type and the instance somehow. When I tried to achieve this, I kind of ended up with the same delegate stuff the libraries use. Here an example

Code:
class Test{
 public:
    void myFunc1()    {
        Serial.printf("myFunc 1 %d\n", i);
    }
    void myFunc2()    {
        Serial.printf("myFunc 2 %d\n", i * 2);
    }

    int i = 0;
};

template <typename T>
class delegate
{
    using mfp_t = void (T::*)();

 public:
    delegate(void (T::*fp)(), T* instance)
    {
        mfp = fp;
        obj = instance;
    }

    mfp_t mfp;  // store the memberfunction pointer
    T* obj;     // store a pointer to the instance

    void call()
    {
        (obj->*mfp)();
    }
};

void setup()
{
    while (!Serial) {}

    Test test1, test2;
    test1.i = 42;
    test2.i = 100;

    auto d1 = delegate<Test>(&Test::myFunc1, &test1);
    auto d2 = delegate<Test>(&Test::myFunc2, &test1);

    auto d3 = delegate<Test>(&Test::myFunc1, &test2);
    auto d4 = delegate<Test>(&Test::myFunc2, &test2);

    d1.call();
    d2.call();
    d3.call();
    d4.call();
}

void loop(){}
which prints,
Code:
myFunc 1 42
myFunc 2 84
myFunc 1 100
myFunc 2 200

In your callback providers (IntervalTimer etc) you would accept the delegate as callback type, store it and use the call() function to invoke the underlying member function callback. But it still would be something ugly like

Code:
IntervalTimer.begin(delegate<Test>(&Test::myFunc1, &test), 1000)  // attach member myFunc1 of class Test called on instance test

I still find the already working
Code:
IntervalTimer.begin([]{test.myFunc1}, 1000)
nicer and easier to use :)

Looking forward to better solutions, maybe @PieterP is watching?
 
Last edited:
I was messing around with this a little this morning. Of course one approach is that we can add a static member function to any class that we might want to call back and pass the object pointer to that static function.

I wish that it was more flexible to use any member function. Currently, you'd have to define a static function to call any member function. Maybe that's ok since in a way you're defining which parts of the interface are available for callbacks, but it does seem overly limiting at first glance.

It's at least easy to understand and readability is good. Here's a brief example:

Code:
class MyClass {
 public:
  explicit MyClass(int a) : a_(a) {}
  static void PrintCallback(void * context) {
    reinterpret_cast<MyClass *>(context)->Print();
  }
  void Print() {
    Serial.println(a_);
  }
 
 private:
  int a_;
};

void Callback(void * context, void (*ptr)(void *)) {
  (*ptr)(context); 
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {}
  MyClass testA(1);
  MyClass testB(2);
  Serial.println("Calling Directly");
  testA.Print();
  testB.Print();
  Serial.println("Using Callbacks");
  Callback(&testA, MyClass::PrintCallback);
  Callback(&testB, MyClass::PrintCallback);
}

void loop() {}
 
I was messing around with this a little this morning. Of course one approach is that we can add a static member function to any class that we might want to call back and pass the object pointer to that static function.

I wish that it was more flexible to use any member function. Currently, you'd have to define a static function to call any member function. Maybe that's ok since in a way you're defining which parts of the interface are available for callbacks, but it does seem overly limiting at first glance.

It's at least easy to understand and readability is good. Here's a brief example:

Code:
class MyClass {
 public:
  explicit MyClass(int a) : a_(a) {}
  static void PrintCallback(void * context) {
    reinterpret_cast<MyClass *>(context)->Print();
  }
  void Print() {
    Serial.println(a_);
  }
 
 private:
  int a_;
};

void Callback(void * context, void (*ptr)(void *)) {
  (*ptr)(context); 
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {}
  MyClass testA(1);
  MyClass testB(2);
  Serial.println("Calling Directly");
  testA.Print();
  testB.Print();
  Serial.println("Using Callbacks");
  Callback(&testA, MyClass::PrintCallback);
  Callback(&testB, MyClass::PrintCallback);
}

void loop() {}

This is actually quite nice. Easy to understand and only 1 extra pointer needed.
 
Rats! I thought I could just skim this thread think about it for the future. Then I remembered that I've been rewriting my library for the OV7670 camera and I run up against what I think is the same issue when handling the CSI interrupt that is part of the class library. My solution has been to add a non-class CSI_proxy() function which then calls the class IRQ handler function. This works for the camera/CSI library since there is only one CSI and, thus, one cl_OV7670 instance.

A more general interface which could handle interrupts from multiple devices calling the same or different classes and multiple instances might come in handy. In the end it would be nice to just write:

attachInterruptHandler(IRQ_CSI, OV7670.IRQHandler);
 
In the end it would be nice to just write:
attachInterruptHandler(IRQ_CSI, OV7670.IRQHandler);

Maybe I misunderstood, but wouldn't that be attachInterruptVector?

If so, this should work out of the box:
Code:
attachInterruptVector(IRQ_CSI,[]{OV7670.IRQHandler();});

Addendum
Or, if IRQHandler is static, a
Code:
attachInterruptVector(IRQ_CSI, cl_OV7670::IRQHandler);
should do?
 
Last edited:
Luni is correct that I should have specified "attachInterruptVector".

I'll try out the code in the first example he gives. I think that one problem with static handlers is that they make it impossible to create derivative classes---but I'll need to do some research on that.
 
Luni's first example worked for me:

Code:
attachInterruptVector(IRQ_CSI,[]{OV7670.CSIService();});
\

Normally, in a member function, you don't need to specify the object name ('OV7670.' in this case) as there is an implied 'this.' when calling a member function from within another member function of the same object. However the compiler complains about missing the 'this' specification without an explicit object reference.

It seems I need a little continuing education on the subject of C++ syntax, lambda functions and the other things that allowed Luni to write that single line of code. I doubt I could have come up with the proper sequence of brackets, braces, semicolons and parentheses without many hours of research. Since I'm now at the life expectancy of the average American male, I'm always appreciative of shortcuts--even though the research might have driven me crazy--it probable postpones dementia!:rolleyes:
 
I doubt I could have come up with the proper sequence of brackets, braces, semicolons and parentheses without many hours of research.

Actually it is quite simple. The sequence []{/*someCode;*/} instructs the compiler to generate a function for you, which takes no paramter, returns void, using the function body given in the braces and return a pointer to this function.

Thus,
Code:
[]{OV7670.CSIService();}

is more or less the same as

Code:
void CSI_proxy()
{
   OV7670.CSIService();
}

attachInteruptVector requires a pointer to a void(void) function. Thus you can either do it the traditional way, i.e. define CSI_proxy as you did and write

Code:
attachInterruptVector(IRQ_CSI, CSI_proxy) ;

or, you do a shortcut using a the lambda function syntax and write

Code:
attachInterruptVector(IRQ_CSI,[]{OV7670.CSIService();});

So, nothing magical about it. Hope that helps postpone dementia :)
 
I spent some time working out a proof of concept for a callback system fulfilling the requirements given in #1.

  1. avoiding heap allocation
  2. ability to call C functions
  3. static C++ functions
  4. class member functions
  5. Avoiding C++ template syntax in Arduino sketch code is highly desirable
  6. template syntax is ok inside libraries and their headers, and maybe we can adopt a practice of typedefs or #defines for the function def).
  7. Support for lambda functions would be nice, maybe even mutable lambda functions.
To have a real example to play with, I implemented a very simple PIT timer class (Teensy 4.x) using the proposed callback system. The complete code can be found on GitHub (https://github.com/luni64/cb).

The repo currently contains 2 example folders.
Example1 demonstrates how 2) 3) 4) 5) 6) and 7) (only capture less lambdas) is fulfilled.
Example2 demonstrates that the callback system can be used to embed callback providers (here the PIT class) into user classes, which IMHO is the most important use case.

Generally, it is not as elegant as using a std::function based API but it is much cheaper and avoids dynamic memory allocation.

Here the user code from example 1 (see also (https://github.com/luni64/cb/blob/master/example1/main.cpp))
Code:
#include "PIT.h"

// test class to demonstrate member function callbacks
class Test
{
 public:
    void myFunc1()
    {
        Serial.printf("called Test::myFunc1 i = %d\n", i);
    }
    void myFunc2()
    {
        Serial.printf("called Test::myFunc2 2*i = %d\n", i * 2);
    }

    int i = 0;
};
Test test{42}; // some instance of Test

// free function callback
void onTimer()
{
    Serial.println("called free function");
}

PIT timer;

void setup()
{
    while (!Serial) {}

    constexpr unsigned PIT_channel = 0; // for the sake of simplicity the PIT class doesn't automatically choose a free channel. (0..3)

    // use free function callback
    timer.begin(PIT_channel, onTimer, 250'000);

    // attach lambda function as callback
    //timer.begin(PIT_channel, [] { Serial.println("called lambda"); }, 250'000);

    // use lambda to attach member function as callback
    // timer.begin(PIT_channel, [] { test.myFunc2(); }, 250'000);

    // use member function pointer and instance to attach callback
    //timer.begin(PIT_channel, &Test::myFunc1, &test, 250'000);
}

void loop() {}

Here the main code from example2 showing how to embedd the PIT into a user class (https://github.com/luni64/cb/blob/master/example2/main.cpp).
Please note that one can use the member function pointer version (4) to do this withot the need for the usual void* state pointer.

Code:
#include "Arduino.h"
#include "PIT.h"

// demonstrate how to embedd a PIT into a class
class Blinker
{
 public:
    void begin()
    {
        t1.begin(0, &Blinker::onTimer, this, 500'000); // attach the non static member function "onTimer" and invoke using "this"
    }

 protected:
    void onTimer()  // non static callback
    {
        digitalToggle(LED_BUILTIN);
    }

    PIT t1;
};

Blinker blinker; // some instance of Blinker


void setup()
{
    while (!Serial) {}
    pinMode(LED_BUILTIN, OUTPUT);

    blinker.begin();
}

void loop() {}


Probably the most interesting part is the implementation of the PIT class: https://github.com/luni64/cb/blob/master/example2/PIT.h

Please note: This is proof of concept code only, no error checking, no destructos etc. Probably full of subtle bugs and so on.
 
Last edited:
Actually it is quite simple. The sequence []{/*someCode;*/} instructs the compiler to generate a function for you, which takes no paramter, returns void, using the function body given in the braces and return a pointer to this function.

Thus,
Code:
[]{OV7670.CSIService();}

is more or less the same as

Code:
void CSI_proxy()
{
   OV7670.CSIService();
}

attachInteruptVector requires a pointer to a void(void) function. Thus you can either do it the traditional way, i.e. define CSI_proxy as you did and write

Code:
attachInterruptVector(IRQ_CSI, CSI_proxy) ;

or, you do a shortcut using a the lambda function syntax and write

Code:
attachInterruptVector(IRQ_CSI,[]{OV7670.CSIService();});

So, nothing magical about it. Hope that helps postpone dementia :)

Thanks, it confirms my guess.

Does it work with a variable inside it, for example a position in an array (vector)

aka :

(count is a position changing in a for)
Code:
for (count=0;(count<4);++count)
  attachInterruptVector(IRQ_CSI,[]{OV7670[count].CSIService();});
 
Does it work with a variable inside it, for example a position in an array (vector)

Sadly, no, it won't work.

Assuming "count" is an ordinary local variable on the stack, you'll get "error: 'count' is not captured", and if you try adding "count" to the capture list, you'll get "error: cannot convert 'myfunction()::<lambda()>' to 'void (*)()'".

This code also configures the same interrupt 4 times. Even if it did work, only the last iteration would have any lasting effect. But that's a problem unrelated to lambdas.

It can compile if "count" is a static variable. But that pretty much defeats the (probably) intended purpose. When the lambda functions actually run, they'll access whatever is stored in the static variable at that moment, not what it was when defined.

For example:

Code:
void setup() {
  static int pin;
  for (pin = 2; pin <= 9; pin++) {
    pinMode(pin, INPUT_PULLUP);
    attachInterrupt(pin, [] { Serial.println(pin); }, FALLING);
  }
}

void loop() {
}

If you run this, touching each pin to GND with a wire will always print "10", no matter which pin you touch. All the lambda functions access the same static "pin" with is retains the number 10 after this code has set up the interrupts.
 
Back
Top