Use lambda function as microcontroller interrupts handler

Viewed 42

For my microcontroller project I need custom timer with possibility to change interruption handler function. I created a Timer class for this.

I need to initialize ESP32 timer with function. That is how I am trying to do this:

class Timer
{
  private:
    hw_timer_t* timer = nullptr;
    std::function<void(void)>& onTimer;
  public:
    Timer(uint16_t intervalMs, std::function<void(void)>& newOnTimer): onTimer(newOnTimer)
    {
      timer = timerBegin(0, 40, true);
      timerAttachInterrupt(timer, &onTimer, true);
      timerAlarmWrite(timer, intervalMs * 1000, true);
    }
    void start()
    {
      timerAlarmEnable(timer);
    }
};

And timer initialization:

Timer t = Timer(250, []IRAM_ATTR(){
  Serial.print("Tick ");
  Serial.println(millis());
  if(point)
  {
    point = false;
    d.clearPixel(4, 4);
    return;
  }
  point = true;
  d.drawPixel(4,4);
});

But when I am launching it, I get:

sketch.ino: In constructor 'Timer::Timer(uint16_t, std::function<void()>&)':
sketch.ino:1161:35: error: cannot convert 'std::function<void()>*' to 'void (*)()'
       timerAttachInterrupt(timer, &onTimer, true);
                                   ^~~~~~~~
In file included from /esp32/hardware/esp32/2.0.4/cores/esp32/esp32-hal.h:88,
                 from /esp32/hardware/esp32/2.0.4/cores/esp32/Arduino.h:36,
                 from sketch.ino.cpp:1:
/esp32/hardware/esp32/2.0.4/cores/esp32/esp32-hal-timer.h:39:53: note:   initializing argument 2 of 'void timerAttachInterrupt(hw_timer_t*, void (*)(), bool)'
 void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge);
                                              ~~~~~~~^~~~~~~~~
sketch.ino: At global scope:
sketch.ino:1341:16: error: expected primary-expression before '(' token
 Timer t = Timer(250, []IRAM_ATTR(){
                ^
In file included from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/hal/esp32/include/hal/cpu_ll.h:18,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/hal/include/hal/cpu_hal.h:16,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/esp_hw_support/include/esp_cpu.h:14,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/esp_hw_support/include/soc/cpu.h:14,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/esp_hw_support/include/soc/spinlock.h:11,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/freertos/port/xtensa/include/freertos/portmacro.h:42,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/freertos/include/freertos/portable.h:51,
                 from /esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/freertos/include/freertos/FreeRTOS.h:63,
                 from /esp32/hardware/esp32/2.0.4/cores/esp32/Arduino.h:33,
                 from sketch.ino.cpp:1:
sketch.ino: In lambda function:
/esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/esp_common/include/esp_attr.h:150:46: error: expected '{' before '__attribute__'
 #define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
                                              ^~~~~~~~~~~~~
/esp32/hardware/esp32/2.0.4/tools/sdk/esp32/include/esp_common/include/esp_attr.h:23:19: note: in expansion of macro '_SECTION_ATTR_IMPL'
 #define IRAM_ATTR _SECTION_ATTR_IMPL(".iram1", __COUNTER__)
                   ^~~~~~~~~~~~~~~~~~
sketch.ino:1341:24: note: in expansion of macro 'IRAM_ATTR'
 Timer t = Timer(250, []IRAM_ATTR(){
                        ^~~~~~~~~
sketch.ino: At global scope:
sketch.ino:1341:24: error: expected ')' before '__attribute__'
 Timer t = Timer(250, []IRAM_ATTR(){
                ~       ^
                        )

Error during build: exit status 1

I am not expert in C++, so I totally don't understand what I need to do there to fix it.

Could anyone who knows what to do tell me about this, please? Thank you in advance.

1 Answers

The signature of timerAttachInterrupt is

void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge);

Like the error says, you cannot convert a std::function<void()>* to a void(*)(). While std::function<...> is a class that can wrap pretty much any callable object and store the data needed (eg. the variables captured in a lambda), function pointers are just simple addresses in memory.

If your lambdas are simple and don't capture anything, like the one in your example, std::function is overkill. You can just use void(*)() instead and the issue is solved.

Otherwise, if your lambdas do capture stuff, then you need std::function and perhaps some static member variables.

Since you seem to be using only one timer, you could do something like this:

class Timer
{
  private:
    hw_timer_t* timer = nullptr;
    static std::function<void(void)> onTimer;
    static void onTimerCaller()
    {
      onTimer();
    }

  public:
    Timer(uint16_t intervalMs, std::function<void(void)>&& newOnTimer)
    {
      if (onTimer)
      {
        // another Timer already exists. Show an error, somehow
      }
      onTimer = std::move(newOnTimer);
      timer = timerBegin(0, 40, true);
      timerAttachInterrupt(timer, &onTimerCaller, true);
      timerAlarmWrite(timer, intervalMs * 1000, true);
    }
    void start()
    {
      timerAlarmEnable(timer);
    }
};

The most important changes are1:

  • Add a onTimerCaller static function (that can be converted to void(*)() and therefore used with timerAttachInterrupt) which calls onTimer;
  • onTimer is now static, so it can be accessed from onTimerCaller.

Also note that I tried to keep the class as simple as possible. It should also have copy/move constructors/operators and a destructor. Additionally, there's not really much reason to even use a class in this case. A namespace with some functions would be simpler and cleaner.

It is also possible to support multiple timers, but I believe this may be enough for your use-case. Let me know if you'd like to see a version for multiple timers.


1 I also did some minor changes, like moving the std::function into the class to avoid a dangling reference.

Related