Nonstatic Class function as Callback within Same Class

I'm trying to take the BLDC FOC motor controller code found in one of my previous posts and wrapping it in a class. For timing purposes, I use a library called TeensyTimerTool.h and all of it's "timers" use callbacks. Within my FOC class, I have several of these timers. The desired callback functions for each timer is a member function of the FOC class, but this gives me an "invalid use of non-static member function" error.

I'm not sure how to fix this - does anyone have any ideas apart from ripping the callbacks into the global scope?

Sketch:

#include "foc.h"




foc_ctrl motorCtrlr;




void setup()
{
  Serial.begin(115200);

  motorCtrlr.begin(0, 1, 2);
  
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  
  digitalWriteFast(3, HIGH);
  digitalWriteFast(4, HIGH);
  digitalWriteFast(5, HIGH);
}




void loop()
{
  if (motorCtrlr.getAlpha() < 359.9)
    motorCtrlr.setAlpha(motorCtrlr.getAlpha() + 0.1);
  else
    motorCtrlr.setAlpha(0);

  delayMicroseconds(40);
}

foc.h:

#pragma once
#include "Arduino.h"
#include "TeensyTimerTool.h"




using namespace TeensyTimerTool;




class foc_ctrl
{
public:
  void begin(const byte&         a_pin,
             const byte&         b_pin,
             const byte&         c_pin,
             const float&        alpha        = 0,
             const unsigned int& fullPeriod   = 50,
             const float&        maxDutyCycle = 1.0)
  {
    setPins(a_pin, b_pin, c_pin);
    setAlpha(alpha);
    setFullPeriod_us(fullPeriod);
    setMaxDutyCycle(maxDutyCycle);

    foc_timer.begin(foc_timer_callback, _full_period);
    
    a_rise_timer.begin(a_rise_timer_callback);
    b_rise_timer.begin(b_rise_timer_callback);
    c_rise_timer.begin(c_rise_timer_callback);
  
    a_fall_timer.begin(a_fall_timer_callback);
    b_fall_timer.begin(b_fall_timer_callback);
    c_fall_timer.begin(c_fall_timer_callback);
  };
  
  void setPins(const byte& a_pin,
               const byte& b_pin,
               const byte& c_pin)
  {
    _a_pin = a_pin;
    _b_pin = b_pin;
    _c_pin = c_pin;

    pinMode(_a_pin, OUTPUT);
    pinMode(_b_pin, OUTPUT);
    pinMode(_c_pin, OUTPUT);
  };
  
  void  setAlpha(const float& alpha)                     { _alpha          = alpha;        };
  void  setFullPeriod_us(const unsigned int& fullPeriod) { _full_period    = fullPeriod;
                                                           foc_timer.begin(foc_timer_callback, _full_period); };
  void  setMaxDutyCycle(const float& maxDutyCycle)       { _max_duty_cycle = maxDutyCycle; };
  float getAlpha()                                       { return _alpha;                  };
  int   getFullPeriod_us()                               { return _full_period;            };
  float getMaxDutyCycle()                                { return _max_duty_cycle;         };




private:
  PeriodicTimer foc_timer;
  
  OneShotTimer a_rise_timer;
  OneShotTimer b_rise_timer;
  OneShotTimer c_rise_timer;
  
  OneShotTimer a_fall_timer;
  OneShotTimer b_fall_timer;
  OneShotTimer c_fall_timer;




  int   _full_period    = 50; // 50us -> 20kHz
  float _max_duty_cycle = 1.0;

  int _a_pin = 0;
  int _b_pin = 1;
  int _c_pin = 2;
  
  float _alpha = 0;



  
  FASTRUN void a_rise_timer_callback() { digitalWriteFast(_a_pin, HIGH); }
  FASTRUN void b_rise_timer_callback() { digitalWriteFast(_b_pin, HIGH); }
  FASTRUN void c_rise_timer_callback() { digitalWriteFast(_c_pin, HIGH); }
  FASTRUN void a_fall_timer_callback() { digitalWriteFast(_a_pin, LOW);  }
  FASTRUN void b_fall_timer_callback() { digitalWriteFast(_b_pin, LOW);  }
  FASTRUN void c_fall_timer_callback() { digitalWriteFast(_c_pin, LOW);  }

  // https://www.youtube.com/watch?v=nh9TD2M2r-o
  FASTRUN void foc_timer_callback()
  {
    // Find raw phase values using alpha input (global variable)
    float V_a = sin(radians(_alpha));
    float V_b = sin(radians(_alpha) - radians(120));
    float V_c = sin(radians(_alpha) - radians(240));
  
    float V_max;
    float V_min;
  
    // Determine third harmonic triangle wave using max/min phase values
    if ((V_a >= V_b) && (V_a >= V_c))
      V_max = V_a;
    else if ((V_b >= V_a) && (V_b >= V_c))
      V_max = V_b;
    else
      V_max = V_c;
  
    if ((V_a <= V_b) && (V_a <= V_c))
      V_min = V_a;
    else if ((V_b <= V_a) && (V_b <= V_c))
      V_min = V_b;
    else
      V_min = V_c;
    
    float V_cm = -(V_max + V_min) / 2;
  
    // Inject the third harmonic
    V_a += V_cm;
    V_b += V_cm;
    V_c += V_cm;
  
    // Scale to +-0.5
    V_a *= (_max_duty_cycle / sqrt(3));
    V_b *= (_max_duty_cycle / sqrt(3));
    V_c *= (_max_duty_cycle / sqrt(3));
    
    // Bias by 0.5 so that outputs go 0-1: results in duty cycle ratio for each phase
    V_a += 0.5;
    V_b += 0.5;
    V_c += 0.5;
  
    // Determine on/off period (in us) based on full switching period and phase duty cycle ratio
    int T_a = round(_full_period * V_a);
    int T_b = round(_full_period * V_b);
    int T_c = round(_full_period * V_c);
  
    // Set timers to turn phase high using center-aligned PWM waveform
    a_rise_timer.trigger((_full_period - T_a) / 2);
    b_rise_timer.trigger((_full_period - T_b) / 2);
    c_rise_timer.trigger((_full_period - T_c) / 2);
  
    // Set timers to turn phase low using center-aligned PWM waveform
    a_fall_timer.trigger(((_full_period - T_a) / 2) + T_a);
    b_fall_timer.trigger(((_full_period - T_b) / 2) + T_b);
    c_fall_timer.trigger(((_full_period - T_c) / 2) + T_c);
  }
};

Just like the message says. It cannot be a member function, it must be static.

I don't know what kind of callbacks it accepts, but u probably could use a lambda though, try this:

a_rise_timer.begin([&] () { a_rise_timer_callback(); });

Works like a charm, thank you!

For the sake of learning, what exactly is going on in your "fix"? Is it somehow creating a static function pointer?

I'm not really "C++ under the hood guy", so I'm not sure if my explanation will be correct.

My understanding is that a member function (object method) is not a normal function that you can "point to". You always have to point to the method AND the object it operates on.

So, if you pass a method, you lose the information about the object it belongs to.

Now the lambda: [] () {} – it is just a "shortcut" for a nameless function. A special function, to which you can pass the context around. the & in [&] means that the local context will be held in this function. So that's why u have access to the methods of the object with the lambda.

But, as I said, a lambda is "just a function", not a "member of an object function". That's why you can pass it into your callback.

1 Like

If your environment supports std library (e.g. ESP32) you could do this:

void foo(std::function<void()> callback) {
  callback();
}

class MyClass {
public:
  void handler() {
    Serial.print("handler");
  }

  void callfoo() {
    foo([this]() { this->handler(); });
  }
};

void setup() {
  Serial.begin(115200);
}

MyClass myClass;
void loop() {
  myClass.callfoo();
}

Or if not, then you could use an instance of the class directly

class MyClass {
public:
  void handler() {
    Serial.print("handler");
  }
};

void foo(void (*callback)()) {
  callback();
}

void setup() {
  Serial.begin(115200);
}

MyClass myClass;
void loop() {
  foo([&myClass]() { myClass.handler(); });
}

Also if you control both the source of the event and the receiver you can try using Eventfun library it supports nonstatic methods as event delegates and can be used on platforms that don't support std, like Uno or Mega

That's lambda again, but with one mistake: if u do [myClass], u capture the object by value -> meaning -> it will be copied.

it would be better to do for example [&myClass] -> which would capture by reference, thus avoiding the copy.

1 Like

Absolutely. Thanks!

[this] is usually more suited / readable if you just want to capture the current instance but [&] is fine. Indeed you don't want a copy (most of the time)

I just noticed that the question says "within the Same Class", if the function that takes the callback lives in the same class then it's easier, you could just do this:

class MyClass {
public:
  void handler() {
    Serial.print("handler");
  }

  void foo(void (MyClass::*callback)()) {
    (this->*callback)();
  }

  void callfoo() {
    foo(&MyClass::handler);
  }
};

void setup() {
  Serial.begin(115200);
}

MyClass myClass;
void loop() {
  myClass.callfoo();
}

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