Passing callback function in class as argument

Hello, I want to use the TeensyTimerTool.h library in my class Stepper. Its just a Interrupt Timer like in Arduino with passing a callback function.

I’m getting this error:

no suitable constructor exists to convert from "void ()" to "std::function<void ()>"

src/Stepper.cpp: In member function 'void Stepper::startMotorInterrupt(int)':
src/Stepper.cpp:147:42: error: invalid use of non-static member function
     T1.beginPeriodic(stepperInterrupt, ms);

The usage is what I had done in the main.cpp when I would not use this in a Class. There it would work this way.
I did a bit research now and it has something to do the pointing to this function.

Here are the codes simplified for faster understanding:

main.cpp uses class stepper.h with stepper.cpp and stepper.cpp uses TeensyTimerTool.h:

main.cpp:

#include "Stepper.h"
Stepper myStepper(MOTOR_X_EN,MOTOR_X_DIR,MOTOR_X_STEP,MOTOR_Y1_EN,MOTOR_Y1_DIR,MOTOR_Y1_STEP,MOTOR_Y2_EN,MOTOR_Y2_DIR,MOTOR_Y2_STEP,ENDSTOP_X,ENDSTOP_Y1,ENDSTOP_Y2);
void setup()
{
	myStepper.startMotorInterrupt(10);
}

Stepper.h:

#ifndef STEPPER_H
#define STEPPER_H

#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

class Stepper
{
public:
    Stepper(unsigned int MX_Enable_Pin, .......);
    void startMotorInterrupt(int ms);
private:
    void stepperInterrupt();
    Timer T1;
};
#endif

Stepper.cpp:

#include "Stepper.h"

// Interrupt
Timer T1;


Stepper::Stepper(unsigned int MX_Enable_Pin,...)
{
....
}
void stepperInterrupt()
{
    // if need to move
    // make steps
    // check movement done
    // check endstops
}
void Stepper::startMotorInterrupt(int ms){
    T1.beginPeriodic(stepperInterrupt, ms);
}

I could make stepperInterrupt() static but this throws:

src/Stepper.cpp.o: In function `Stepper::startMotorInterrupt(int)':
Stepper.cpp:(.text._ZN7Stepper19startMotorInterruptEi+0xfc): undefined reference to `Stepper::stepperInterrupt()'

And I rather want not to use static!
What would be here the correct solution for this?

Kind regards,
Philipp

This comes up in the forum a couple times a month. Pointers to instance (member) functions are different animals than pointers to regular or class (static) functions. They are not interchangeable. You can read about them here: Standard C++

gfvalvo:
This comes up in the forum a couple times a month. Pointers to instance (member) functions are different animals than pointers to regular or class (static) functions. They are not interchangeable. You can read about them here: Standard C++

Hello Thank you. This explains how to do it outside of a class which might work when I use the Timer T1 in the main.cpp. However I'm using the Timer T1 in the Stepper class. I can't think of a similiar approach when I'm in the Steppe Class. The Stepper is instantiated in the main.cpp. And the Stepper class has Timer T1 where I want to pass the callback function which is a non static member of stepper.cpp.

Or did I just miss something in the FAQ you provided?

MindCode:
Or did I just miss something in the FAQ you provided?

No you didn’t. I’ve never come across a satisfactory way to do what you’re asking.

EDIT:
The best technique that I’ve found only works if the library in question provides for Callbacks with Parameters as demonstrated in Reply #5 and #9 in this thread.

First question:
But then: How the official Stepper libraries are solving this? Do they use then static methods?

Second question:
What I can do is something like this, which works:

#include "Stepper.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

Stepper myStepper(MOTOR_X_EN,MOTOR_X_DIR,MOTOR_X_STEP,MOTOR_Y1_EN,MOTOR_Y1_DIR,MOTOR_Y1_STEP,MOTOR_Y2_EN,MOTOR_Y2_DIR,MOTOR_Y2_STEP,ENDSTOP_X,ENDSTOP_Y1,ENDSTOP_Y2);
Stepper *stp;
Timer T1;

void wrapper(){
  stp->stepperInterrupt();
}
void setup()
 T1.beginPeriodic(wrapper,10);
{

}

It works, allthough Im not sure why because I never assign Stepper * stp to myStepper. How deos it know which function it has to call, especially when I make more instances of Stepper?

MindCode:
Second question:
What I can do is something like this, which works:

I don't know. You didn't post complete code for Stepper.h or Stepper.cpp.

gfvalvo:
I don’t know. You didn’t post complete code for Stepper.h or Stepper.cpp.

#ifndef STEPPER_H
#define STEPPER_H

#include <core_pins.h>
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

class Stepper
{

public:
    // Moving Modes
    const int FAST_POSITION_MODE = 0;
    const int LINEAR_POSITION_MODE = 1;
    const int ARC_CW_POSITION_MODE = 2;
    const int ARC_CCW_POSITION_MODE = 3;
    int Current_Position_Mode = 0;

    // Position
    float Motor_X_Position = 0.0;
    float Motor_Y_Position = 0.0;

    // Directions
    int Motor_X_Direction = 0;
    int Motor_Y_Direction = 0;

    // Pin Out
    unsigned int Motor_X_Enable_Pin;
    unsigned int Motor_X_Direction_Pin;
    unsigned int Motor_X_Stepout_Pin;

    unsigned int Motor_Y1_Enable_Pin;
    unsigned int Motor_Y1_Direction_Pin;
    unsigned int Motor_Y1_Stepout_Pin;

    unsigned int Motor_Y2_Enable_Pin;
    unsigned int Motor_Y2_Direction_Pin;
    unsigned int Motor_Y2_Stepout_Pin;

    unsigned int Endstop_X_Pin;
    unsigned int Endstop_Y1_Pin;
    unsigned int Endstop_Y2_Pin;

    Stepper(unsigned int MX_Enable_Pin, unsigned int MX_Direction_Pin, unsigned int MX_Stepout_Pin,
            unsigned int MY1_Enable_Pin, unsigned int MY1_Direction_Pin, unsigned int MY1_Stepout_Pin,
            unsigned int MY2_Enable_Pin, unsigned int MY2_Direction_Pin, unsigned int MY2_Stepout_Pin,
            unsigned int E_X_Pin, unsigned int E_Y1_Pin, unsigned int E_Y2_Pin);

    void startMotorInterrupt(int ms);
    void wrapper();
    bool getMotorMoving();
    bool getMotorMovingDone();
    void RapidMove(int x, int y, int f);
    void LinearMove(int x, int y, int f);
    void ClockwiseArcMove(int x, int y, int i, int j);
    void CounterClockwiseArcMove(int x, int y, int i, int j);
    void setMovingMode(int mode);
    void makeStepsX(float steps);
    void makeStepsY(float steps);
    void stepperInterrupt();

private:
    // Stepper Scaling:
    float motorXStepsPerMM = 0.0;
    float motorYStepsPerMM = 0.0;

    // Timings
    unsigned long Motor_X_Task_Time = 500;
    unsigned long Motor_Y_Task_Time = 100;
    unsigned long Motor_XY_Coupling_Factor = 5;
    unsigned long Motor_X_Timecounter = 0;
    unsigned long Motor_Y1_Timecounter = 0;
    unsigned long Motor_Y2_Timecounter = 0;

    // Statemachine
    bool Motors_Moving = false;
    bool Motors_Moving_Done = false;
    bool Endstop_X_Triggered = false;
    bool Endstop_Y1_Triggered = false;
    bool Endstop_Y2_Triggered = false;
    
    
    
    Timer T1;
};

#endif
#include "Stepper.h"
using namespace TeensyTimerTool;

// Interrupt
//Timer T1;

// Moving Modes
const int FAST_POSITION_MODE = 0;
const int LINEAR_POSITION_MODE = 1;
const int ARC_CW_POSITION_MODE = 2;
const int ARC_CCW_POSITION_MODE = 3;
int Current_Position_Mode = 0;

// Position
float Motor_X_Position = 0.0;
float Motor_Y_Position = 0.0;

// Directions
int Motor_X_Direction = 0;
int Motor_Y_Direction = 0;

// Pin Out
unsigned int Motor_X_Enable_Pin;
unsigned int Motor_X_Direction_Pin;
unsigned int Motor_X_Stepout_Pin;

unsigned int Motor_Y1_Enable_Pin;
unsigned int Motor_Y1_Direction_Pin;
unsigned int Motor_Y1_Stepout_Pin;

unsigned int Motor_Y2_Enable_Pin;
unsigned int Motor_Y2_Direction_Pin;
unsigned int Motor_Y2_Stepout_Pin;

unsigned int Endstop_X_Pin;
unsigned int Endstop_Y1_Pin;
unsigned int Endstop_Y2_Pin;

// Statemachine
bool Motors_Moving = false;
bool Motors_Moving_Done = false;
bool Endstop_X_Triggered = false;
bool Endstop_Y1_Triggered = false;
bool Endstop_Y2_Triggered = false;

// Stepper Scaling:
float motorXStepsPerMM = 0.0;
float motorYStepsPerMM = 0.0;

// Timings
unsigned long Motor_X_Task_Time = 500;
unsigned long Motor_Y_Task_Time = 100;
unsigned long Motor_XY_Coupling_Factor = 5;
unsigned long Motor_X_Timecounter = 0;
unsigned long Motor_Y1_Timecounter = 0;
unsigned long Motor_Y2_Timecounter = 0;

Stepper::Stepper(unsigned int MX_Enable_Pin, unsigned int MX_Direction_Pin, unsigned int MX_Stepout_Pin,
                 unsigned int MY1_Enable_Pin, unsigned int MY1_Direction_Pin, unsigned int MY1_Stepout_Pin,
                 unsigned int MY2_Enable_Pin, unsigned int MY2_Direction_Pin, unsigned int MY2_Stepout_Pin,
                 unsigned int E_X_Pin, unsigned int E_Y1_Pin, unsigned int E_Y2_Pin)
{
    Motor_X_Enable_Pin = MX_Enable_Pin;
    Motor_X_Direction_Pin = MX_Direction_Pin;
    Motor_X_Stepout_Pin = MX_Stepout_Pin;

    Motor_Y1_Enable_Pin = MY1_Enable_Pin;
    Motor_Y1_Direction_Pin = MY1_Direction_Pin;
    Motor_Y1_Stepout_Pin = MY1_Stepout_Pin;

    Motor_Y2_Enable_Pin = MY2_Enable_Pin;
    Motor_Y2_Direction_Pin = MY2_Direction_Pin;
    Motor_Y2_Stepout_Pin = MY2_Stepout_Pin;

    Endstop_X_Pin = E_X_Pin;
    Endstop_Y1_Pin = E_Y1_Pin;
    Endstop_Y2_Pin = E_Y2_Pin;
    
    // Pinouts
    pinMode(Motor_X_Enable_Pin, OUTPUT);
    pinMode(Motor_X_Direction_Pin, OUTPUT);
    pinMode(Motor_X_Stepout_Pin, OUTPUT);

    pinMode(Motor_Y1_Enable_Pin, OUTPUT);
    pinMode(Motor_Y1_Direction_Pin, OUTPUT);
    pinMode(Motor_Y1_Stepout_Pin, OUTPUT);

    pinMode(Motor_Y2_Enable_Pin, OUTPUT);
    pinMode(Motor_Y2_Direction_Pin, OUTPUT);
    pinMode(Motor_Y2_Stepout_Pin, OUTPUT);

    pinMode(Endstop_X_Pin, INPUT);
    pinMode(Endstop_Y1_Pin, INPUT);
    pinMode(Endstop_Y2_Pin, INPUT);

    pinMode(13, OUTPUT);
}
/************************
   Stepper stepping
 ***********************/
int test = false;
void Stepper::stepperInterrupt()
{
    // if need to move
    // make steps
    // check movement done
    // check endstops
    if(Motor_X_Enable_Pin == 1337){
    test = !test;
    digitalWrite(13,test);
    }
}

/************************
   Checking
 ***********************/
void readInputs()
{
    int ex = digitalRead(Endstop_X_Pin);
    int ey1 = digitalRead(Endstop_Y1_Pin);
    int ey2 = digitalRead(Endstop_Y2_Pin);

    if (ex >= 1)
    {
        Endstop_X_Triggered = true;
    }
    else
    {
        Endstop_X_Triggered = false;
    }

    if (ey1 >= 1)
    {
        Motor_Y1_Timecounter = true;
    }
    else
    {
        Motor_Y1_Timecounter = false;
    }

    if (ey2 >= 1)
    {
        Endstop_Y2_Triggered = true;
    }
    else
    {
        Endstop_Y2_Triggered = false;
    }
}

/************************
   Controlling
 ***********************/


void Stepper::startMotorInterrupt(int ms){
    //T1.beginPeriodic(stepperInterrupt, 10);
}

bool Stepper::getMotorMoving() { return Motors_Moving; }

bool Stepper::getMotorMovingDone() { return Motors_Moving_Done; }

void Stepper::RapidMove(int x, int y, int f)
{
}
void Stepper::LinearMove(int x, int y, int f)
{
}
void Stepper::ClockwiseArcMove(int x, int y, int i, int j)
{
}
void Stepper::CounterClockwiseArcMove(int x, int y, int i, int j)
{
}
void Stepper::setMovingMode(int mode)
{
    Current_Position_Mode = mode;
}
void Stepper::makeStepsX(float steps)
{
}
void Stepper::makeStepsY(float steps)
{
}

MindCode:
It works, allthough Im not sure why because I never assign Stepper * stp to myStepper. How deos it know which function it has to call, especially when I make more instances of Stepper?

The code may compile, but I can't see how it could possibly "work". The global stp pointer is initialized to 0 and you never change that. So, the call to: stp->stepperInterrupt(); will dereference the null pointer. I don't understand how it didn't crash your processor.

Im really not sure why but it worked. But for safety reasons I do Stepper *stp = &myStepper;

So this is so far the best solution by using a Wrapperfunction:

void stepperInterruptWrapper() { stp->stepperInterrupt(); }

It works so thank you for showing me the isocpp website as it helped me :slight_smile:

I'm a bit late to the party... But, if you are still interested how to use a non static class member as callback with the TeensyTimerTool, here an example using your code from #1. The code compiles as is and generates 1µs pulses with 10ms distance on pin 0.

The most elegant (IMHO) method is attaching the callback through a lambda expression. In this expression you can capture the 'this' pointer and use it to call the ISR. In case you are not familiar with lambda expressions you can simply use the pattern shown in the code below. If you want to dive deeper into the callback system of the TeensyTimerTool you find detailed information in the callback chapter of the TimerTool Wiki: Callbacks · luni64/TeensyTimerTool Wiki · GitHub

#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

class Stepper
{
 public:
    Stepper(unsigned int MX_Enable_Pin);
    void startMotorInterrupt(int ms);

 private:
    void stepperInterrupt();
    Timer T1;
    unsigned pin;
};

Stepper::Stepper(unsigned int MX_Enable_pin)
    : pin(MX_Enable_pin), T1(TCK) // call timer constructor to select which underlying timer module you want to use
{
    pinMode(pin, OUTPUT);
}

void Stepper::stepperInterrupt()  // to test just generate short pulses
{
    digitalWriteFast(pin, HIGH);
    delayMicroseconds(1);
    digitalWriteFast(pin, LOW);
}

void Stepper::startMotorInterrupt(int ms)
{
    T1.beginPeriodic([this] { this->stepperInterrupt(); }, ms*1000); //call the member function via a lambda expression
}

Stepper stepper(0);

void setup()
{
    stepper.startMotorInterrupt(10);
}

void loop()
{
}

Hope that helps

luni64:
The most elegant (IMHO) method is attaching the callback through a lambda expression. In this expression you can capture the ‘this’ pointer and use it to call the ISR.

Neat trick.

In playing with the technique I think I’ve convinced myself that it only works if the “attach” function (beginPeriodic in this case) takes an argument of type ‘std::function<void(void)>’. Trying to use a Lambda in this way won’t compile if the function only takes a regular function pointer (‘void (*)()’) as it’s argument.

So, the STL is required to make it work.

Sure, this only works if the library provides std::function callbacks. If you need to attach callbacks to a provider which only accepts lame void(*)() callbacks you need to do the usual stuff, some of which you discussed above.

However, lambdas can also help for void()() callbacks. E.g. (staying in the Teensy ecosystem) you can do something like this with the intervaltimer. In this case the lambda decays to a void()() function and can be used as such.

IntervalTimer t;
void setup(){
    t.begin([] { digitalToggleFast(LED_BUILTIN); }, 250'000);
}
void loop(){
}

It is also not very difficult to pimp old fashioned classes like the intervalTimer to behave a bit more user friendly. See here if you want to play with that stuff: GitHub - luni64/TeensyHelpers: A couple of utility functions and classes to improve usage of the Teensy IntervalTimer class, the attachInterrupt and the pinMode function.. You can even choose between a std::function (bit heavy for small boards) or the traditional method which passes a void pointer as parameter to the callback. There are a few examples showing how to use this.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.