IntervalTimerEx - Callbacks with state

luni

Well-known member
I often need to pass state to callbacks attached to an IntervalTimer. Since the IntervalTimer only accepts void(*)() callbacks, it is a bit tedious to work around. I therefore did a shallow wrapper around the IntervalTimer which accepts callbacks of type std::function. I.e., you can attach more or less anything callable to it. Since IntervalTimerEx objects use normal IntervalTimers as workhorses they do not interfere with standard IntervalTimers and can be used in parallel.

IntervalTimerEx.h
IntervalTimerEx.cpp
https://github.com/luni64/IntervalTimerEx

Here a few examples:

Code:
#include "IntervalTimerEx.h"

// plain vanilla void(*)() callback
void myCallback_0(){
    Serial.print("myCallback_0 ");
    Serial.println(millis());
}

// need to manipulate the calling timer in the callback?
void myCallback_1(IntervalTimer* caller){
    Serial.print("--> myCallback_1 ");
    Serial.println(millis());

    caller->end(); // e.g. do a one shot timer
}

IntervalTimerEx t1, t2;

void setup(){
    t1.begin(myCallback_0, 150'000);
    t2.begin([] { myCallback_1(&t2); }, 1'000'000);  // attach callback using a lambda
}

void loop(){
}

// Output
Code:
...
myCallback_0 900
myCallback_0 1050
myCallback_0 1200
--> myCallback_1 1300
myCallback_0 1350
myCallback_0 1500
myCallback_0 1650
myCallback_0 1800
...


Since the IntervalTimerEx accepts std::function callbacks it is easy to embed it in a class and use non static member functions as callbacks. Here a simple blinker class showing how to achieve that:

Code:
#include "IntervalTimerEx.h"

class Blinker
{
 public:
    void begin(float seconds)
    {
        pinMode(LED_BUILTIN, OUTPUT);
        timer.begin([this] { this->blink(); }, seconds * 1E6); // attach member function
    }

 protected:
    IntervalTimerEx timer;

    void blink()
    {
        digitalToggleFast(LED_BUILTIN);
    }
};

//----------------------------

Blinker blinker;

void setup(){
    blinker.begin(0.1);  // start the blinker
}

void loop(){
}
 
Last edited:
attachInterruptEx

I also added an attachInterruptEx function to the repo. Same as with IntervalTimerEx from above it does exactly the same as the standard function but accepts anything callable (functions, lambdas, functors, member functions...) as callback. Here an example showing how to embed a pin interrupt ISR to a user class.

attachInterruptEx.h
attachInteruptEx.cpp

Code:
#include "attachInterruptEx.h"

class EdgeCounter
{
 public:
    void begin(unsigned pin)
    {
        counter = 0;
        pinMode(pin, INPUT_PULLUP);
        attachInterruptEx(pin, [this] { this->ISR(); }, CHANGE);
    }

    unsigned getCounter() { return counter; }

 protected:
    void ISR(){
        counter++;
    }

    unsigned counter;
};

//-------------------------

EdgeCounter ec1,ec2;

void setup(){
    ec1.begin(0);
    ec2.begin(1);
}

void loop(){
    Serial.printf("Detected edges: Pin1: %u Pin2:%u\n", ec1.getCounter(), ec2.getCounter());
    delay(250);
}

And here an example attaching a callback to a couple of pins:

Code:
#include "attachInterruptEx.h"

void pinCallback(unsigned pin)
{
    Serial.println(pin);
}

void setup()
{
    for (unsigned pin : {7, 3, 15, 12})  // attach interrupt to pins 7, 3, 15 and 12
    {
        pinMode(pin, INPUT_PULLUP);
        attachInterruptEx(pin, [pin] { pinCallback(pin); }, FALLING);
    }
}

void loop(){
}

Information about the used initializer list can be found here: https://forum.pjrc.com/threads/62891-pinMode-amp-initializer-lists
 
@luni - This great:) I just got my MSC working in a non-blocking way using the EventResponder, Circular_Buffer and IntervalTimer. I originally had them inside massStorageDriver.cpp but found out that the IntervalTimer callback would not work with an msc member function. Am I right in thinking IntervalTimerEx should be able to do that?

IntervalTimerEx works the same for me as the regular IntervalTimer in my application.

Thanks

Warren
 
I just got my MSC working in a non-blocking way using the EventResponder, Circular_Buffer and IntervalTimer.
Oh, that sounds complicated :)

...but found out that the IntervalTimer callback would not work with an msc member function. Am I right in thinking IntervalTimerEx should be able to do that?

Yes, definitely. The second example in #1 shows how to attach a non static member function to the IntervalTimerEx.

(BTW, all hard- and software timers of the TeensyTimerTool (FTM, QUAD, GPT, PIT4, TCK and TCK64) support std::function callbacks and can handle member function callbacks per default.)
 
I added an option to the IntervalTimerEx.h header for those who prefer good old void(*)(void* state) callbacks to pass state to callbacks.

Instead of the std::function<> way :
Code:
class DeepThougth
{
 public:
    void begin()
    {
        answer = 42;
        t1.begin([this] { ISR(); }, 7.5E6);  // 7.5s
    }

 protected:
    IntervalTimerEx t1;
    int answer;

    void ISR()
    {
        Serial.printf("The answer is %d\n", answer);
    }
};

//----------------------------

DeepThougth deepThougth;

void setup()
{
    deepThougth.begin();
}

void loop()
{
}

...you can now also use the traditional void* way to encapsulate the timer and its callback in a class:

Code:
class DeepThougth
{
 public:
    void begin()
    {
        answer = 42;
        t1.begin(ISR, this, 7.5E6); // 7.5s
    }

 protected:
    IntervalTimerEx t1;
    int answer;

    static void ISR(void* state)
    {
        DeepThougth* THIS = (DeepThougth*)state;
        Serial.printf("The answer is %d\n", THIS->answer);
    }
};

DeepThougth deepThougth;

void setup()
{
    deepThougth.begin();
}

void loop()
{
}

Please note: I also renamed the repo to TeensyHelpers, (https://github.com/luni64/TeensyHelpers)
so, the links in the previous posts probably won't work anymore.
 
attachYieldFunc

Here yet another additon to my TeensyHelpers repository:

Sometimes your have code that needs to be called by the user as often as possible. Prominent examples are AccelStepper where you are required to call run() at high speed. Using the debounce library, you need to call update(). The PID library requires a frequent call to Compute() and so on. Usually, users of these libraries just call these functions in loop which works fine for simple code. As soon as there is longer running code or some delays in loop the call rate of these functions can get unacceptably low and the libraries don't work as expected.

Teensyduino provides a yield() function which is called before each call to loop(), during delay() and probably a lot of other long running core functions while they spin.

If you want to add your own functions to be called from yield without completely overriding yield, you can use `attachYieldFunc(callback)` which is defined in the `src/attachYieldFunc` folder of the repository. Here an example which will toggle pin 0 at high frequency in the background. The `delay(250)` in loop doesn't disturb the high frequency toggling since delay it calls yield while it spins.

Code:
#include "attachYieldFunc.h"

void myCallback()      //will be called from yield
{
  digitalToggleFast(0);
}

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

  attachYieldFunc(myCallback); // attach our callback to the yield stack
}

void loop()
{
  digitalToggleFast(LED_BUILTIN);
  delay(250);
}
 
@luni - More to play with:) So far everything works with what I am doing with MSC. Still learning about these things though:(
 
@luni - In TeensyHelpers I could not compile attachYieldFunc.cpp. The compiler said it could not find
'Eventresponder.h'. That was because the EventResponder include filename is 'EventResponder.h'.
The 'r' in the filename was in lower case instead of upper case. I fixed that and it compiled ok.

Thanks for creating these great libraries. I am having a lot of fun testing them with MSC. I have made some good progress with using them.

Warren
 
Back
Top