Compiler raises error when trying to use IntervalTimer with non static Method

SomeoneFromGermany

Well-known member
I have this written this class to simplify controlling 4 pumps (using an H-bridge motor driver which requires only a HIGH signal to work).
I need those 4 instances along with the IntervalTimers and callbacks.

But for some reason it raises the "invalid use of non-static member function" exception.

I also tried referencing the internal methods and external functions.
The external referencing worked to a certain degree, but due to the lack of arguments in the IntervalTimer.begin() function i cannot use the needed variables.

Anyone got a solution?

Code:
class PumpClass {
  private:
    IntervalTimer PumpTimeout;
    void pumpOutWithTimer()
    {
      _secs = 0;
      starttime = 0;
      isActive = false;
      digitalWriteFast(_Pin, LOW);
      PumpTimeout.end();
    }
  public:
    unsigned short _ID;
    unsigned short _Pin;
    volatile bool isActive = false;
    volatile uint8_t _secs = 0;
    volatile unsigned long starttime = 0;
    PumpClass(unsigned short ID, unsigned short Pin/*, void *callback*/) {
      _ID = ID;
      _Pin = Pin;
      //pumpOutWithTimer = &callback;
    }
    unsigned long millisLeft()
    {
      return isActive ? _secs - (millis() - starttime) : 0;
    }
    void run(uint8_t seconds) {
      _secs = seconds;
      starttime = millis();
      isActive = true;
      digitalWriteFast(_Pin, LOW);
      PumpTimeout.begin(pumpOutWithTimer, seconds * 1000000);
    }
};
//Pinout is a static class for used pins
PumpClass Pump0(0, Pinout.P0);
PumpClass Pump1(1, Pinout.P1);
PumpClass Pump2(2, Pinout.P2);
PumpClass Pump3(3, Pinout.P3);
 
The way the interval timer is implemented you can only attach a parameter-less function returning void as callback. Your callback function pumpOutWithTimer looks like such a function but in fact it isn't. Since it is a member function the compiler needs to add a pointer to the actual object in the call. Thus, it actually has the signature: void pumpOutWithTimer(PumpClass*) which is not compatible.

Due to the nature of hardware interrupts this is a very common problem in embedded programming. Of course there are workarounds:

1) Simplest: Declare your function static. Disadvantage: declaring it static is kind of infectious. A static function can only access static member variables. Thus most of your variables have to be static as well. As static member variables are shared between all objects of a class this only works if you only have one object. But then the whole class concept doesn't make much sense and you could as well use normal functions....

2) Define relay functions for each channel of the timer which call the actual static callback with a pointer to the original object. The method is described here in full detail: https://github.com/TeensyUser/doc/wiki/callbacks

3) Use the TeensyTimerTool instead. This uses a std::function interface so that you don't need any workaround and can use your code without changes. See the TeensyTimerTool WIKI to explain this in more detail: https://github.com/luni64/TeensyTimerTool/wiki/Callbacks.

4) If you prefer the IntervalTimer, you can have a look here: https://github.com/luni64/TeensyHelpers#intervaltimerex This subclasses the Intervaltimer so that it also accepts std::function arguments and you can use your code without changes. It also explains how to embed a timer intto a class, i.e. exactly what you want to do: https://github.com/luni64/TeensyHelpers#intervaltimerex

Hope that helps
 
Last edited:
That's because, in the simplest way of looking at it, you can't pass an instance function as a regular function pointer. @luni wrote an adapter that can address this by allowing you to use a `std::function`: see `IntervalTimerEx` in https://github.com/luni64/TeensyHelpers.

Some useful references:
* https://stackoverflow.com/questions/8865766/get-a-pointer-to-objects-member-function
* http://www.parashift.com/c++-faq-lite/pointers-to-members.html
* https://stackoverflow.com/questions/130322/how-do-you-pass-a-member-function-pointer
(The last two links are from the first.)

Edit: Ah, @luni just beat me to it. :)
 
First of all thanks for your help.

Second using the TeensyTimerTool is probably the best solution.
But using PumpTimeout.begin in the constructor and just calling start in the run method causes my teensy 4.1 to lock up on Upload with 7 Blinks from the Bootloader.
So for some reason just the upload causes the Teensy to freeze?:confused:
Meaning of the blinks found here: https://www.pjrc.com/store/ic_mkl02_t4.html
Code:
PumpClass(unsigned short ID, unsigned short Pin) {
      _ID = ID;
      _Pin = Pin;
      PumpTimeout.begin([this] { this->pumpOffWithTimer(); }, 1000, false);
    }

Edit:
And is it on purpose that if I use 1 * 1000000 period the pump runs for 2400 ms?
i got 1000ms by using 1 * (250000 / 6)

Edit 1:
Probably not the upload but the bootprocess
 
Last edited:
But using PumpTimeout.begin in the constructor and just calling start in the run method causes my teensy 4.1 to lock up on Upload with 7 Blinks from the Bootloader.
So for some reason just the upload causes the Teensy to freeze?

Difficult to help if you don't post complete code showing the issue. I tried to deduce from the fragments what you want to achieve. Looks like the run function is supposed to be non blocking and should switch the pump on for n seconds. Here an example showing how to implement this with the TimerTool.

Remarks and assumptions:
  • If I deduced the required functionality correctly you need a OneShotTimer rather than a PeriodicTimer. I changed that accordingly.
  • I assume the required timings might be quite long so I used the TCK64 timers. You have 20 of them and they run up to several hundred years with the full 1.66ns resolution.
  • Since the TCK timers are software based you don't need to make the variables volatile.
  • You didn't include your Pinout class -> I hardcoded the pins.

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

using namespace TeensyTimerTool;

class PumpClass
{
 public:
    PumpClass(unsigned short ID, unsigned short Pin)
        : PumpTimeout(TCK64)
    {
        _ID  = ID;
        _Pin = Pin;
        pinMode(_Pin, OUTPUT);
    }

    unsigned long millisLeft()
    {
        return isActive ? _secs - (millis() - starttime) : 0;
    }

    void run(uint8_t seconds)
    {
        _secs     = seconds;
        starttime = millis();
        isActive  = true;
        digitalWriteFast(_Pin, HIGH);
        PumpTimeout.begin([this] { this->pumpOutWithTimer(); });
        PumpTimeout.trigger(1E6f * seconds);
    }

 private:
    OneShotTimer PumpTimeout;

    void pumpOutWithTimer()
    {
        _secs     = 0;
        starttime = 0;
        isActive  = false;
        digitalWriteFast(_Pin, LOW);
    }

    unsigned short _ID;
    unsigned short _Pin;
    bool isActive           = false;
    uint8_t _secs           = 0;
    unsigned long starttime = 0;
};

PumpClass Pump0(0, 0), Pump1(1, 1), Pump2(2, 2), Pump3(3, 3);

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);

    Pump0.run(1);
    delay(500);
    Pump1.run(5);
    Pump2.run(2);
    Pump3.run(3);
}

void loop()
{
    digitalToggleFast(LED_BUILTIN);
    delay(150);
}

The code produces this on the 4 pump pins
Screenshot 2022-01-02 234813.png
 
@luni yeah you're right, thought it would be enought code sorry:D
Here is the Code that works for me now.
Thank you for your help.

Code:
#include <Arduino.h>

struct PINOUT
{
  private:
    bool _wasSet = false;
  public:
    bool wasSet() {
      return _wasSet;
    }
    const uint8_t
    //Pump
    P0  = 10,
    P1  = 11,
    P2  =  9,
    P3  =  2,
    //FAN
    FAN = 12,
    //Button
    T0  = 24,
    T1  = 25,
    T2  = 26,
    T3  = 27,
    //Buttonlight
    TL0 = 28,
    TL1 = 29,
    TL2 = 30,
    TL3 = 31,
    //Keyboard Pins
    K0  = 33,
    K1  = 34,
    K2  = 35,
    K3  = 36,
    K4  = 37,
    K5  = 13,
    K6  = 22,
    K7  = 32,
    //Flow control Pins
    D0  = 41,
    D1  = 40,
    D2  = 39,
    D3  = 38,
    //Reset ESP32
    ESPRESET =  6,
    //Not useable
    NC0     = 15,
    NC1     = 16,
    //WS2812 // SK6812
    LEDRGBW = 17,
    LEDRGB  = 14;

    void setDirection()
    {
      if (_wasSet) return;
      //PWM FAN
      pinMode(FAN, OUTPUT);
      //Pumps
      pinMode(P0 , OUTPUT);
      pinMode(P1 , OUTPUT);
      pinMode(P2 , OUTPUT);
      pinMode(P3 , OUTPUT);
      digitalWrite(P0, LOW);
      digitalWrite(P1, LOW);
      digitalWrite(P2, LOW);
      digitalWrite(P3, LOW);
      //Buttons
      pinMode(T0 , INPUT_PULLUP);
      pinMode(T1 , INPUT_PULLUP);
      pinMode(T2 , INPUT_PULLUP);
      pinMode(T3 , INPUT_PULLUP);
      //ButtonLight
      pinMode(TL0, OUTPUT);
      pinMode(TL1, OUTPUT);
      pinMode(TL2, OUTPUT);
      pinMode(TL3, OUTPUT);
      //Reset ESP32
      pinMode(ESPRESET, OUTPUT);
      digitalWriteFast(ESPRESET, LOW);
      delay(10);
      digitalWriteFast(ESPRESET, HIGH);
      //Used and handled by library: K0 K1 K2 K3 K4 K5 K6 K7
      //Used with analogRead(); D0 D1 D2 D3
      //NC  NC0 NC1
      //Used by library LEDRGBW LEDRGB
      _wasSet = true;
    }
};
PINOUT Pinout;


class SCHNAPS {
  public:
    struct SchnapsStr {
      String         name;
      unsigned short capacity;
      unsigned int   frequency;
      bool           active;
      //      unsigned int   timer() {
      //        return frequency * 1000;
      //      }
    };
  public:
    SchnapsStr Schnaps0 = {"Schnaps0", 1, 0, 0};
    SchnapsStr Schnaps1 = {"Schnaps1", 1, 0, 0};
    SchnapsStr Schnaps2 = {"Schnaps2", 1, 0, 0};
    SchnapsStr Schnaps3 = {"Schnaps3", 1, 0, 0};
    unsigned short RandomSchnaps;

    unsigned int combinedFrequency() {
      return Schnaps0.frequency + Schnaps1.frequency + Schnaps2.frequency + Schnaps3.frequency;
    }

    SchnapsStr *getSchnapsByIndex(byte num)
    {
      switch (num)
      {
        case 0:
          return &Schnaps0;
        case 1:
          return &Schnaps1;
        case 2:
          return &Schnaps2;
        case 3:
          return &Schnaps3;
        default:
          return &Schnaps0;
      }
    }
};
SCHNAPS Schnaps;

namespace IOHandler
{
bool digitalReadAnalog(uint8_t pin)
{
  return analogRead(pin) > 200;
}

class PumpClass {
  private:
    OneShotTimer PumpTimeout;
    void pumpOffWithTimer()
    {
      _secs = 0;
      Serial.print("ended: ");
      Serial.println(millis() - starttime);
      starttime = 0;
      isActive = false;
      digitalWriteFast(_Pin, LOW);
    }
  public:
    unsigned short _ID;
    unsigned short _Pin;
    volatile bool isActive = false;
    volatile uint8_t _secs = 0;
    volatile unsigned long starttime = 0;
    PumpClass(unsigned short ID, unsigned short Pin)
    : PumpTimeout(TCK64) 
    {
      _ID = ID;
      _Pin = Pin;
    }
    unsigned long millisLeft()
    {
      return isActive * (_secs * 1000 - (millis() - starttime));
    }
    void run(uint8_t seconds) {
      _secs = seconds;
      starttime = millis();
      isActive = true;
      digitalWriteFast(_Pin, HIGH);
      PumpTimeout.begin([this] { this->pumpOffWithTimer(); });
      PumpTimeout.trigger(1E6f * seconds);
    }
};
PumpClass Pump0(0, Pinout.P0);
PumpClass Pump1(1, Pinout.P1);
PumpClass Pump2(2, Pinout.P2);
PumpClass Pump3(3, Pinout.P3);

PumpClass *getPumpByIndex(byte num)
{
  switch (num)
  {
    case 0:
      return &Pump0;
    case 1:
      return &Pump1;
    case 2:
      return &Pump2;
    case 3:
      return &Pump3;
    default:
      return &Pump0;
  }
}

FASTRUN void HandlerThreadV()
{
  Serial.print("IOHandler: ");
  Serial.println(threads.id());

  static bool lastState[4] = {false, false, false, false};

  while (1)
  {
    //custom functions for WDT 
    //and to let different threads check if others are still active
    threads.count();
    threads.feedTheDog();

    for (int i = 0; i < 4; i++)
    {
      //Buttonlight
      //Pinout.TL0 = 28,
      //Pinout.TL1 = 29,
      //Pinout.TL2 = 30,
      //Pinout.TL3 = 31,
      SCHNAPS::SchnapsStr *curentSelected= Schnaps.getSchnapsByIndex(i);
      if (curentSelected->active != lastState[i])
      {
        lastState[i] = curentSelected->active;
        digitalWriteFast(Pinout.TL0 + i, curentSelected->active);
      }

      //Button
      //    Pinout.T0  = 24,
      //    Pinout.T1  = 25,
      //    Pinout.T2  = 26,
      //    Pinout.T3  = 27,
      if (!digitalReadAnalog(Pinout.T0 + i) && curentSelected->active)
      {
        curentSelected->active = false;
        getPumpByIndex(i)->run(curentSelected->capacity);
      }
    }
    threads.yield();

  }
}
}
 
Back
Top