Use attachInterrupt with a class method / function?

I suspect this can't be done but I may as well ask

If I have a class e.g. MyClass and it has a method called callback() which I'd like to be able to get called by as an ISR

but when I try to write the code to in by MyClass::setup()

e.g.

MyClass::setup()
{

attachInterrupt(2,&this->callback,FALLING)

}

I get compile errors

error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function.

As far as I can tell there is no solution to ISR's calling methods in a class

It seems to be possible to call a static function in the class, but this is an issue if I have more than one instance of the class, each being triggered off a different interrupt.

I've read this stackoverflow question and answers but they all seem to suggest its not possible.

I'd be happy to declare a function pointer in my class as a property / variable and initialise it in the constructor, but it just doesn't seem to be possible to access the function pointer to a method in C++ at all.

:frowning:

PS.

What I'm really trying to do is to use SPI DMA with multiple SPI devices and hence the possibilities of multiple DMA callbacks into different instances of the SPI class e.g. on the Due.

I suspect this can't be done but I may as well ask

It can, but it probably doesn't make sense to.

If I have a class e.g. MyClass and it has a method called callback() which I'd like to be able to get called by as an ISR

but when I try to write the code to in by MyClass::setup()

When the interrupt happens, a function is to be called. If that function is a class method, which instance of the class' method should be called?

The method can be a static method, which makes it belong to the class, not an instance of the class. That makes it the class' responsibility to determine which instance should respond.

As far as I can tell there is no solution to ISR's calling methods in a class

But, there is. Static methods.

but this is an issue if I have more than one instance of the class, each being triggered off a different interrupt.

That is something that the class will have to deal with. Think about a hotel. It employs several doormen. Only one is on duty at a time. A guest drives up. Which doorman gets called? The instance of the doorman class that is registered (with the class) as the doorman-on-duty.

Your class needs static methods that the instances can call to say "Hey, I care about interrupt xxx". The class needs a static method to be called when interrupt xxx gets called. Then, the class needs to call the appropriate method on the instance of the class that says that it cares.

Thanks Paul,

Your solution is similar to one posted on StackOverflow, ie have a static method to handle the ISR, but it needs to get called with a pointer to the class instance

However the as its the mircoprocessor ISR that is calling back, I can't get it to pass any arguments

I think the only solution to this is to have different static methods in the class for each DMA channel, and use a switch statement to setup the attach interrupt, passing a different static method depending

e.g.

switch(dmaChannel)
{
case DMA_CH1:
dma_attach_interrupt(&SPIClass::DMA_CH1_Callback);
break;
case DMA_CH2:
dma_attach_interrupt(&SPIClass::DMA_CH2_Callback);
break;

etc
}

This isnt a big overhead when the DMA is setup

It causes a small amount of code bloat, but I don't think it can be avoided, and the uP is not short of Flash memory (normally the STM32's have at least 64k)

Thanks again

Roger

There is one other way to do this that I use a lot. It isn't pretty but it does work. In the class, add a public instance function for the "interrupt handler". Do the attach interrupt in the sketch, attaching to a sketch-level function which does nothing but call the handler in the class instance. For timer-based interrupts, I generally add a "setHandler()" function to the class, to which I pass the address of the sketch-level handler, so the class can do the attachInterrupt itself. That way the sketch doesn't need to know what interrupt the handler is attached to.

Regards,
Ray L.

Hi Ray

Thanks for the suggestion.

The handler is setup in a library and the ISR is in the library.

So I have added a static method, to the library and I am passing that to the ISR.

It works ok, but is not ideal.

What am am doing is not on a traditional Arduino AVR or a Due etc.
I'm using a STM32F103 board, and they have DMA which can be used for SPI and many other purposes.
The DMA can call a function when a transfer to SPI etc is complete, but when is passed to the microprocessor is a pointer to a function which then gets put into an ISR vector table, which is then read by the microprocessor when the DMA finishes.

The STM32F103 devices have multiple SPI channels. It has 2 or 3 sets of sck,miso,most etc, all of which can run simultaneously.
So I wanted to code the SPI library, so that multiple instances could be created of SPIClass, all of which call their own instance of DMA-Complete()

But unfortunately c++ doesn't allow you cast a method to a function pointer.

The static class member work around, works, but requires one static function per DMA device, so if the STM32 has 3 possible SPI devices, I need 3 static methods.

So far I have only added one, in order to get things working and do basic testing.

I think there may be a partial work around, where I may be able to store the instance to the class in an array, and lookup the DMA number associated when the DMA channel when I get the interrupt

However I suspect that the wasted ram and other additional code required for the array solution, is worse than the static method solution.

I suspect there may be a very hacky way to look at the object structure to get the method pointer to a non static function, but its probably not a road I should go down :wink:

Have you tried?

void MyClass::setup()
{

attachInterrupt(2,callback,FALLING)

}

void MyClass::callback() {

}

Mistergreen

I thought I'd tried that and it doesn't compile

Because the arguement type to attach interrupt isn't a method its a function pointer and you can't cast a method to function pointer in C++

As PaulS pointed out, of course you can't.

A method has an implied "this" which is the instance of the class. Trying to make it into a static function discards which instance you are talking about.

Look, imagine you were told that if you hear bell, go and feed "the dog" when there are 20 dogs outside. Which one would you feed? But if you were told to "feed Fido" (an instance of the dogs - that is, one dog) then you can do it.

Now you can do this in C++ by using "glue" routines, providing you identify in advance which instance you want. For example, someone might tie a ribbon around Fido and say "when you hear the bell, feed the dog with the ribbon".

So your glue routine might know which instance to call.

Here’s an example I wrote a while ago:

class Foo
{
  static void isr0 ();
  static void isr1 ();
  
  const byte whichISR_;
  static Foo * instance0_;
  static Foo * instance1_;
  
  void handleInterrupt ();
  
  volatile int counter_;
  
  public:
    Foo (const byte which);
    void begin ();
    
};  // end of class Foo

void Foo::begin ()
  {
  switch (whichISR_)
    {
    case 0: 
      attachInterrupt (0, isr0, FALLING); 
      instance0_ = this;
      break;
    
    case 1: 
      attachInterrupt (1, isr1, FALLING); 
      instance1_ = this;
    break;
    }  
  }  // end of Foo::begin 
  
// constructor
Foo::Foo (const byte whichISR) : whichISR_ (whichISR) 
    {
    counter_ = 0;
    }

// ISR glue routines
void Foo::isr0 ()
  {
  instance0_->handleInterrupt ();  
  }  // end of Foo::isr0

void Foo::isr1 ()
  {
  instance1_->handleInterrupt ();  
  }  // end of Foo::isr1
  
// for use by ISR glue routines
Foo * Foo::instance0_;
Foo * Foo::instance1_;

// class instance to handle an interrupt
void Foo::handleInterrupt ()
  {
  counter_++;
    
  }  // end of Foo::handleInterrupt

// instances of class
Foo firstFoo (0);
Foo secondFoo (1);

void setup()
  {
  firstFoo.begin (); 
  secondFoo.begin (); 
  }  // end of setup

void loop() { }
2 Likes

Thanks Nick

Your solution effectively replaces the static methods with static vars, and you also have separate (non static) ISR's

I'm not sure its going to be beneficial to me to implement it in that way.

Each of my static ISR functions does need to be different e.g. here is the first one I've done

	static inline void DMA1_CH3_Event() {
		dma1_ch3_Active = 0;
		dma_disable(DMA1, DMA_CH3);
		dma_disable(DMA1, DMA_CH2);
		
		// To Do. Need to wait for 
	}

i.e

The callback from DMA1_CH3 needs to disable DMA1,CH3 (both of which are just numbers), and set a static volatile flag to indicate to code that is waiting, that the DMA has completed

(in case you are wondering I can disable CH2 at the same time, because CH3 is the transmit DMA channel and CH2 is the receiving DMA channel from the SPI's data register, so as TX and RX occur on the same sclock, if TX has finished, then RX must also have finished, and there is no point in using a separate ISR for both the TX and RX side of SPI DMA)

If I could have just used function pointers to the method, I'd have put the DMA device number and the DMA Channel number into private (non static) properties,

But because of the need to use static functions or multiple static variables, the trade off between all the subsequent number of static variables in RAM vs the additional code size of some Static functions, looks like in my case, that using static functions for the ISR's with hard coded DMA and Channel numbers will work out better, because the basic STM32F103CB device has 64k Flash and the one most people use (Mini maple clone) has 128kb of Flash.

But ram is much smaller (16k on both of those variants), hence ram conservation probably has to take priority over code size

DMA1 etc are just addresses in memory. Sounds like you need to use a few pointers to simplify your ISR's.

Mark

As it is a multi-channel device, it should be pretty easy to make a single static ISR handle all instances. When the interrupt happens, just poll the status registers of the DMA channels, to see which one(s) need servicing. The added time, code and data should be insignificant.

The reason you can't get a pointer to a non-static function should be clear, once you understand that a single copy of the code is shared across ALL instances of a class, so there is no way for one instance to "see" any function that the others do not.

Regards,
Ray L.

Ray

Yes. It should be possible to get the DMA and Channel number

I will attempt to update my single static function to do that