Passing a function as argument to be used in an interrupt

Hello all,

I am currently writing a custom library for the LoRa SX1278 but am stuck on a receive subroutine.

I first pass a function that i want to run when the module receives a packet to the ContinuousRXSetup function and set this function to a class wide variable _onReceive.

I then attach my onDIO0Rise function to the corresponding pin. In this function i then call OnCall, to minimise interruption times, finally OnCall resets some registers and calls the _onReceive function. Does anyone have any ideas how to better implement this as clearly I have made a mistake.

Here are the (simplified) class definitions:

class LoRaClass{
Public:
	uint8_t ContinousRXSetup(void(*callback)(void), uint8_t sizeOfPacket);
Private:
        void(*_onReceive)(void);
	void OnCall();
	void onDio0Rise();
        static uint8_t _dio0;


uint8_t LoRaClass::ContinousRXSetup(void(*callback)(void), uint8_t sizeOfPacket) {
	_onReceive = callback;

	
	if (callback) {
		attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);
	}
	else {
		detachInterrupt(digitalPinToInterrupt(_dio0));
		Serial.println("Failed to attach interrupt");
	}
}

void LoRaClass::OnCall() {
	writeRegister(REG_FIFO_ADDR_PTR, 0x00);
	_onReceive();
	writeRegister(REG_FIFO_ADDR_PTR, 0x00);
}

void LoRaClass::onDio0Rise() {
	LoRaClass::OnCall();
}

_onReceive is a pointer:
       void (*_onReceive) (void);

You have to dereference it before you call it:
      (*_onReceive) ();

johnwasser:
_onReceive is a pointer:
      void (*_onReceive) (void);

You have to dereference it before you call it:
      (*_onReceive) ();

?? Function names are pointers...

_onReceive();
should be fine if you are calling it in a member function.

attachInterrupt takes a function pointer with the signature void(*)(void), no this pointer...

BulldogLowell:
?? Function names are pointers...

Well you learn something new every day! I had not realized before that the value of a pointer to a function would be the same as the value of the function name.

johnwasser:
Well you learn something new every day! I had not realized before that the value of a pointer to a function would be the same as the value of the function name.

I'm glad, after learning so much from you...

example here:

class CallBackCLass {
  public:
    CallBackCLass(void(*action)(void)) : callback(action){}
    void action() {
      if (callback) {  // can inspect callback, here, if not null pointer
        callback();    // can operate callback
        Serial.println(F("Action Completed"));
      } else {
        Serial.println(F("Nothing to do here"));
      }
    }
  private:
    void(*callback)(void);
};

void someAction(void);

void setup() {
  Serial.begin(9600);
  CallBackCLass myInstance = someAction;  // equivalent to: CallBackCLass myInstance(someAction);
  myInstance.action();
  CallBackCLass myOtherInstance = nullptr;
  myOtherInstance.action();  // nothing happens here...
}

void loop() {
}

void someAction(void) {
  Serial.println(F("Some Action"));
}

I have implemented those changes as to fit my library but since I don't want to initialise the class in the library but do still want to maintain the global scope of onReceive within that library the code returns the following errors:

Arduino: 1.8.5 (Windows 10), Board: "Arduino Nano, ATmega328P"

...\Arduino\libraries\CustomLoRa\CustomLoRa.cpp: In member function 'uint8_t LoRaClass::ContinousRXSetup(void (*)(), uint8_t)':
...\Arduino\libraries\CustomLoRa\CustomLoRa.cpp:317:85: error: only constructors take member initializers

 uint8_t LoRaClass::ContinousRXSetup(void(*onReceive)(void), uint8_t sizeOfPacket) : callback(onReceive) {

                                                                                     ^

...\Arduino\libraries\CustomLoRa\CustomLoRa.cpp:322:78: error: invalid use of non-static member function

   attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);

                                                                              ^

exit status 1
Error compiling for board Arduino Nano.

I have attached my library with the amendments according to your syntax.

CustomLoRa.cpp (8.92 KB)

CustomLoRa.h (1.24 KB)

bakerw71:
I have attached my library with the amendments according to your syntax.

as I pointed out, since:

attachInterrupt takes a function pointer with the signature void(*)(void), no this pointer...

you cannot use a pointer that includes an instance.

You can make the function static or you can try this workaround, as done in this example:

// CLASS DEFINITION

class FanSpeed {
  
  public:
    void 
      setup(uint8_t irq_pin, void (*ISR_callback)(void), int value),
      handleInterrupt(void);
    double
      getSpeed(),
      getHertz();

  private:
    double
      _timeConstant = 60000000.0;
    uint32_t
      _lastMicros = 0UL,
      _interval = 60000000UL;
    void(*ISR_callback)();
};

void FanSpeed::setup(uint8_t irq_pin, void (*ISR_callback)(void), int value)
{
  attachInterrupt(digitalPinToInterrupt(irq_pin), ISR_callback, value);
}

inline void FanSpeed::handleInterrupt(void)
{
  uint32_t nowMicros = micros();
  _interval = nowMicros - _lastMicros;
  _lastMicros = nowMicros;
}

double FanSpeed::getSpeed()
{
  if (micros() - _lastMicros < 1000000UL) // has rotated in the last second
  {
    return _timeConstant / _interval;
  }
  else
  {
    return 0;
  }   
}

double FanSpeed::getHertz()
{
  if (micros() - _lastMicros < 1000000UL) // has rotated in the last second
  {
    return getSpeed() * 60.0;
  }
  else
  {
    return 0;
  }   
}

// PROGRAM START

void handlePin2(void);

FanSpeed* fan1;
uint8_t fan1pin = 2;

FanSpeed* fan2;
uint8_t fan2pin = 3;

void setup()
{
  Serial.begin(9600);
  pinMode(fan1pin, INPUT_PULLUP);
  fan1 = new FanSpeed();
  fan1->setup(fan1pin, [](){fan1->handleInterrupt();}, FALLING);  // done with a lambda
  fan2 = new FanSpeed();
  fan2->setup(fan2pin, handlePin2, FALLING);  // done with a function
}

void loop()
{
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 1000UL)
  {
    char message[64] = "";
    sprintf(message, "Fan1 Speed:%4i RPM\t%4i Hz", uint32_t(floor(fan1->getSpeed() + 0.5)), uint32_t(floor(fan1->getHertz() + 0.5)));
    Serial.println(message);
    sprintf(message, "Fan2 Speed:%4i RPM\t%4i Hz", uint32_t(floor(fan2->getSpeed() + 0.5)), uint32_t(floor(fan2->getHertz() + 0.5)));
    Serial.println(message);
    lastMillis = millis();
  }
}

void handlePin2(void) {
  fan2->handleInterrupt();
}

Ok i see how that works but is there not a more simple method (according to the perspective of the user of the library) as having to include the line: fan1->setup(fan1pin, {fan1->handleInterrupt();}, FALLING); is not very appealing to novices that wish to use the library.

The reason why I ask is because the library that I ported from (available via the library manager) LoRa by Sandeep Mistery has a identical method for an interrupt but has a much simpler setup method without any "work around". But does not verify in my library. However Sandeep's library compiles perfectly fine, here is an extract of what Sandeep has done (I have removed all the registry activities):

LoRaClass::LoRaClass() :
  _spiSettings(8E6, MSBFIRST, SPI_MODE0),
  _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN),
  _frequency(0),
  _packetIndex(0),
  _implicitHeaderMode(0),
  _onReceive(NULL)    //not sure what this exactly does in the initialisation of the class
{
  // overide Stream timeout value
  setTimeout(0);
}

void LoRaClass::onReceive(void(*callback)(int))   //in setup the user can simply type onReceive(a_function); where a_function function is called every time a packet is received
{
	_onReceive = callback;

	if (callback) {
		attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);
	}
	else {
		detachInterrupt(digitalPinToInterrupt(_dio0));
		Serial.println("Failed to attach interrupt");
	}
}

void LoRaClass::handleDio0Rise()
{
    if (_onReceive) {
      _onReceive(packetLength);
    }
}

void LoRaClass::onDio0Rise()
{
  LoRa.handleDio0Rise();
}


//corresponding stuff from header file
class LoRaClass : public Stream {
public:
  LoRaClass();
  void onReceive(void(*callback)(int));
private:
  static void onDio0Rise();
  void handleDio0Rise();
private:
  void (*_onReceive)(int);

Is the above a valid method of doing what I am i trying to achieve, if so why does it not work for me, or is it incorrect but simply not caught by the compiler.
I hope you can see the source of my confusion.

bakerw71:
Ok i see how that works but is there not a more simple method (according to the perspective of the user of the library) as having to include the line: fan1->setup(fan1pin, {fan1->handleInterrupt();}, FALLING); is not very appealing to novices that wish to use the library.

The reason why I ask is because the library that I ported from (available via the library manager) LoRa by Sandeep Mistery has a identical method for an interrupt but has a much simpler setup method without any "work around". But does not verify in my library. However Sandeep's library compiles perfectly fine, here is an extract of what Sandeep has done (I have removed all the registry activities):

I hope you can see the source of my confusion.

yes, as I mentioned now twice:

You can make the function static

like the author of the code did...

private:
  static void onDio0Rise();

because , as I mentioned in my first reply, a static member function does not use the this pointer, so passing a function pointer with a void(*)(void) signature is possible.

If you are only ever creating exactly one instance of this class, that may work for you. Example:

class Test {
  public:
    Test(int irqPin) : pin(irqPin) {}
    void setupIRQ() {
      attachInterrupt(pin, Test::onInterrupt, FALLING);
      Serial.println(F("IRQ Setup Complete"));
    }
    static void onInterrupt() {
      eventCount++;
    }
    private:
      uint8_t pin;
      static uint32_t eventCount;
};

uint32_t Test::eventCount = 0;
Test test(3);

void setup() {
  Serial.begin(9600);
  test.setupIRQ();
}

void loop() {
  
}

Out of curiosity, is it possible to define a function that expects as its argument a pointer to a class instance method? Let’s say that instance method doesn’t expect any arguments. What would such a definition look like?

gfvalvo:
Out of curiosity, is it possible to define a function that expects as its argument a pointer to a class instance method? Let’s say that instance method doesn’t expect any arguments. What would such a definition look like?

a static member? sure:

class MyClass {
  public:
    static void myMethod(void) {
      Serial.println(F("this method"));
    }
    static void myOtherMethod(void) {
      Serial.println(F("Other Method"));
    }
};

void someFunction(void(*func)(void)) {
  func();
}

MyClass myClass;
void setup() {
  Serial.begin(9600);
  someFunction(MyClass::myMethod);
  myClass.myOtherMethod();
}

void loop() {

}

But an instance, you'd need a reference to the instance you want to call. But then that is just a simple myClass.myOtherMethod(); in this example.

I'm not sure what you mean... maybe try to write what you want to try in pseudo code.

BulldogLowell:
I'm not sure what you mean... maybe try to write what you want to try in pseudo code.

I guess what I’m asking is as follows. Here’s one way of providing a class instance with a call back function. It only works with “regular” functions (i.e. no implicit ‘this’ pointer in the signature).

class Class1 {
  public:
    Class1(void (*fPtr)()): _callBack(fPtr) {}
    void call() {
      _callBack();
    }
  private:
    void (*_callBack)();
};

void myCallBack();
Class1 instance1(myCallBack);

void setup() {
  instance1.call();
}

void loop() {}

void myCallBack() {}

So, my question, is it possible to define a pointer to an instance function (i.e. expecting the implicit ‘this’ pointer in the signature)? So, you could do something like this:

class Class2 {
  public:
  void instanceFunction() {}
};

class Class1 {
  public:
    Class1(pointerToInstanceFunction (*fPtr)()): _callBack(fPtr) {}
    void call() {
      _callBack();
    }
  private:
    void (*_callBack)();
};

Class2 instance2;
Class1 instance1(instance2.instanceFunction);

void setup() {
  instance1.call();
}

void loop() {}

Suppose that you have a class, Dog. You have two instances of the class:

Dog *pSpot = new Dog("Barks like crazy");
Dog *pFido = new Dog("Craps everywhere and chews the furniture");

The door bell rings (the UPS guy interrupted their naps).

Which dog should attack the UPS driver?

I specified precisely which dog:

Class1 instance1(instance2.instanceFunction);

Doesn't that uniquely identify which function to call?

When I have a need to do something like this, I use static methods, and the class has a mechanism to register the instance who's method is to be called. The interrupt handler calls the static function which calls the appropriate member function for the correct instance.

Understood. But, the question was whether or not it's possible to do it the way I outlined.

If the answer is: "No". Then that's fine.

If the answer is: "No, and here's the reason". So much the better.

gfvalvo:
I guess what I’m asking is as follows. Here’s one way of providing a class instance with a call back function. It only works with “regular” functions (i.e. no implicit ‘this’ pointer in the signature).

My impulse would be so solve the issue using a functor, overloading the paren-paren operator ():

class Example {
  public:
    Example(void(*action)(void)) : callback(action){}
    int operator() (Example& A) {
        callback();
        A.value = 4;  // can modify by reference
        return A.value + value;  // state of passed object "A" depends on instance state fo the caller
    }
  private:
    int value = 2; 
    void(*callback)(void);
};

Example first([](){Serial.println("FIRST");});
Example second([](){Serial.println("SECOND");});

void setup() {
  Serial.begin(9600);
  Serial.println(first(second));
}

void loop() {

}

Note: this isn't a developed solution for the attachInterrupt() issue, just an attempt at answering @gfvalvo's question!

Bulldog,

Yes, I see how the lambda creates a function pointer with proper signature to make the Example class’s constructor happy. So, between this and PaulS’s suggestion (static class function) there at least two ways of doing it, neither being the technique I asked about.

So, I’m coming to the conclusion that the way I wanted to do it isn’t possible. But, still haven’t yet heard why it isn’t

gfvalvo:
Bulldog,

Yes, I see how the lambda creates a function pointer with proper signature to make the Example class’s constructor happy. So, between this and PaulS’s suggestion (static class function) there at least two ways of doing it, neither being the technique I asked about.

So, I’m coming to the conclusion that the way I wanted to do it isn’t possible. But, still haven’t yet heard why it isn’t

to be honest, I'm not sure what you are asking... your pseudo example didn't contain any call to attachInterrupt().

:confused:

BulldogLowell:
to be honest, I'm not sure what you are asking... your pseudo example didn't contain any call to attachInterrupt().

My question isn’t about attachInterrupt(). I simply want to provide a way to specify a class that has a call back feature for each instance. The call back function provided to each instance (either in the constructor or with an attach() method) happens to be an instance method of a different class rather than a "regular" function. Look at my pseudo code again:

class Class2 {
  public:
  void instanceFunction() {}
};

class Class1 {
  public:
    Class1(pointerToInstanceFunction (*fPtr)()): _callBack(fPtr) {}
    void call() {
      _callBack();
    }
  private:
    void (*_callBack)();
};

Class2 instance2;
Class1 instance1(instance2.instanceFunction);

void setup() {
  instance1.call();
}

void loop() {}

Each instance of Class1 needs to be provided with a call back function. I want that call back function for instance1 (of Class1) to be the instanceFunction() of instance2 (of Class2).