0

It seems a difficult thing to do in C++. A callback to a member function in a class in C style, thus making this a void(*)() to be able to use this in a C-style callback function. In this case I want to use this on an ESP32/Arduino to attachInterrupt to a member function.

(I've researched the internet and spend a couple of days to search for a solution, but to no avail).

Good news, I found a solution myself, it's tricky I think, but I haven't seen such a solution before. It works locally running on my Mac, but the bad news, it doesn't work on an ESP32.

On PlatformIO the line:

attachInterrupt(pin,_lambdas.back(),RISING) ; 

gives a compile error:

"no suitable conversion function from "std::function<void ()>" to "void (*)()" exists"

whilst the definition of of the attachInterrupt function is exactly the same.

The code is pretty straightforward and simple to understand. The tricky thing I do is storing a Lambda function in a vector. I'm actually surprised that it works, but it does on my Mac. I'm a bit of a noob, but any help to get this to work on an EPS32 is appreciated!!!

See code below:

Main.cpp:

#include "button.hpp"

void pushedA() { printf("Pushed A\n") ; }
void pushedB() { printf("Pushed B\n") ; }
void pushedC() { printf("Pushed C\n") ; }

int main() {
    printf("Start\n") ;
    Button a(27,pushedA) ;
    Button b(25,pushedB) ;
    Button c(23,pushedC) ;

    printf("Execute\n") ;
    // So in the interruptStore we have functionpointers to class members. Now execute!
    for (auto interruptRoutine : interruptStore) {
        interruptRoutine() ;
    }
    return 0 ;
}

Button.hpp:

#ifndef BUTTON_H
#define BUTTON_H
#include <vector>
#include <functional>
/* Helper routines to simulate Arduino "attachInterrupt" */
extern std::vector<std::function<void(void)>>interruptStore ;
#define RISING 1
// Same function definition as the Arduino definition, but on the Arduino this doesn't seem to work :-(
inline void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode) { interruptStore.push_back(intRoutine) ; }
inline void detachInterrupt(uint8_t pin) { ; }
/**************************************************/

class Button {
public:
    Button(uint8_t pin, void (*callbackFunction)()) ;
  ~Button() ;
private:
  inline void pressed() ;
  uint32_t _pin ;
  void    (*_func)() ;
  uint32_t _debounce ;
  static std::vector<std::function<void(void)>>_lambdas ;
} ;
#endif

Button.cpp:

#include "button.hpp"

std::vector<std::function<void(void)>>interruptStore ;

Button::Button(uint8_t pin, void (*callbackFunction)()) {
    _func = callbackFunction ;
    auto pressedFunction =  [this]() {this->pressed() ;} ;
    _lambdas.push_back(pressedFunction) ;
    attachInterrupt(pin,_lambdas.back(),RISING) ;
    _pin = pin ;
}

Button::~Button() {
    detachInterrupt(_pin) ;
}

inline void Button::pressed() {
//  if (millis()-_debounce>60) { // Here we would normally handle the debounce
        printf("Button on pin %d pressed\n",(int)_pin) ; 
        _func() ;
//      _debounce = millis() ;
//  }
}

std::vector<std::function<void(void)>>Button::_lambdas ; // We need to define a static here, declaration is done in button.hpp

or on Github: https://github.com/MatersM/Arduino-Button

ps. I couldn't believe it either but it really works on a Mac: enter image description here

Output on terminal, to show it works:

Launching: '/Users/mmaters/Documents/code/BUTTON-TEST/output/main'
Working directory: '/Users/mmaters/Documents/code/BUTTON-TEST'
1 arguments:
argv[0] = '/Users/mmaters/Documents/code/BUTTON-TEST/output/main'
Start
Execute
Button on pin 27 pressed
Pushed A
Button on pin 25 pressed
Pushed B
Button on pin 23 pressed
Pushed C
Process exited with status 0
logout

Saving session...
...copying shared history...
...saving history...truncating history files...
...completed.

[Proces voltooid]

Link to working code: code on godbolt

15
  • Where in your posted code is the issue that you face? Also, please mention any errors when compiling for esp32. Commented Mar 8, 2024 at 10:22
  • On PlatformIO the line attachInterrupt(pin,_lambdas.back(),RISING) ; give a compile error "no suitable conversion function from "std::function<void ()>" to "void (*)()" exists", whilst the definition of of the attachInterrupt function is exactly the same. Commented Mar 8, 2024 at 10:38
  • 1
    Please edit the question and add the details - which line the error was and the complete error message. Commented Mar 8, 2024 at 10:40
  • This is the definition for the Arduino/ESP32: void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode), and that's exactly the same definition I've used above. Commented Mar 8, 2024 at 10:43
  • 1
    I've edited the post to include compile error message and the code line. Commented Mar 8, 2024 at 10:50

1 Answer 1

4

The ability to use an interrupt callback of this form is an extension present on the ESP32 Arduino/PlatformIO library that isn't available in the original AVR Arduino. It's defined in header you must include to get this extra overload definition of attachInterrupt().

#include <FunctionalInterrupt.h>

It works on the other code you wrote to test because you wrote the attachInterrupt() definition yourself and you used the one from FunctionalInterrupt.h.

The reason this can work is because on ESP32 the ESP-IDF core runtime includes interrupt handlers that have a parameter passed to them that is supplied when the handler was registered. This parameter could be anything, but for C++ a common use would be as the this pointer for class.

This fixes the root problem with non-static class methods as interrupt handlers: There is no way to pass the this pointer to the method when they're called.

std::function<> is internally a class called a functor. It has a method named operator() that is executed when the class object is called with the syntax of a function. Since we can now call class methods, it can be used as an interrupt handler.

Sign up to request clarification or add additional context in comments.

7 Comments

Woooowwww. That worked!!!! Thank you so much. So are you also saying there is a simpler method to call a classmember then what I did above? Since I never was able to find another solution that worked, see also comments from stackoverflow.com/users/775806/n-m-could-be-an-ai above.
ps. tried to give you a +1 for your answer, but here again, since I'm a noob, stackoverflow didn't allow me.
What comment, they just said "show where you found this definition?" Is all I see. You can accept the answer. It's the grey check mark below the up/down arrows for voting.
Check, thanks! Somebody said: "Here we go again. YOU CAN'T DO THAT. The story ends here." Although I do understand that remark, since my solution actually shouldn't work. Anyway, thanks for the answer above.
Here's a simplified version of your code, godbolt.org/z/sKaYsnqTY There's nothing wrong with your solution. It wouldn't combine for AVR because that Arduino runtime doesn't have FunctionalInterrupt. I see that comment now, maybe it was hidden and hadn't expanded all, IDK what they mean by it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.