Using a class member for ISR is it possible?

Trying to create a class and pass a member function into attachInterrupt() to act as the ISR. Like so:

class Thing
{ public:
Thing( int pin = 2 ) { attachInterrupt( digitalPinToInterrupt( pin ), ISR, CHANGE );
m_timer = millis();
}

bool isSignalled() { return m_eventFlag; }
void resetSignal() { m_eventFlag = false; }

protected:
void ISR();

private:
volatile bool m_eventFlag;
volatile int m_pin;
volatile unsigned long m_timer;
};

void Thing::ISR()
{ if( (millis() -m_timer) < 500 ) return;
m_timer = millis();

if( digitalRead( m_pin )) m_eventFlag = true;
}

The problem is I can’t get the syntax to pass the handler function into attachInterrupt without error. I can do it if I change the handler to a static member, but then I can’t access any members since there is no valid ‘this’ pointer. I tried making a global variable like this

volatile Thing *_thing;

then when I create my thing:

volatile Thing myThing;

_thing = &myThing;

thinking I could follow the _thing pointer from inside the ISR, but it always shows up NULL over there, and I ended up having to get rid of the volatile attribute and changing it to static or it would not compile.

Is it possible to do this?

Ideally I’d like to have one ISR per instance which messes with only that instance’s properties.

From my messing around with this it is evidently the case that ISR’s must be global functions that can only access volatile global variables which are not custom classes. True?

You can't, unless the function is static. Member function code is shared among all instances of that class, and there is no mechanism by which an interrupt handler can know WHICH instance is fielding the interrupt. If you will only ever have a single instance of the class, then you can save a pointer to that one instance, and a statis member function can then use that pointer to access the member data and functions. But if there can be more than one instance of the class, then you're kinda screwed. There are ways around this, but none of them are pretty, and mostl carry some risks.

Regards, Ray L.

Yeah. That's what I was afraid of. I want more than one instance, in case I want to use multiple interrupts, but with the restriction that each instance is tied to a unique interrupt only it can grab. The ISR ought to be able to figure out which instance is relevant by noting which instance is connected to the pin that the interrupt came in on. There will ever be only one. I can enforce that but apparently the compiler doesn't trust me to do it right. It would be nice if there was a way to manually tell the ISR which instance to always use.

Guess I have to rethink this one.

Thanks for the reply.

Then you need 10 static methods, and 10 instance pointers, which will have to be initialized before the first interrupt occurs.

Regards, Ray L.

In the class, keep a static array for what instance is on what pin.

When an instance is attached to a pin, put a pointer to the instance into the array at that index (sadly, C++ does not do arrays of references).

Having said that, if you hook an ISR to two different pins, there seems to be no way to know which pin triggered the interrupt. So yeah - you are going to need a separate function for each pin. On a different platform, you could play games with putting some machine code into a byte array and passing that. But PROGMEM makes that not do-able.

Most efficient way I can thing of would be to write some assembly that puts a parameter on the stack (or in a register) and then does a JMP to a ISR routine that uses that parameter to work out which instance you want.

Something like:

  load '0' into register
  JMP to ISR
  load '1' into register
  JMP to ISR
  load '2' into register
  JMP to ISR
  load '3' into register
  JMP to ISR

you would then set the ISR on pin 2 (for instance) to the memory address of that third load instruction - assuming that that’s where the compiler expects one-byte arguments to be.

It would only be a few bytes. The ISR itself would be normal, except that it would have a parameter.

Something like:

class Thing {
  static Thing *handlers[16];
  
  static void ISR() {
// assembler goes here
    load ax, 0
    jmp Thing::ISR_delegate
    load ax, 1
    jmp Thing::ISR_delegate
    load ax, 2
    jmp Thing::ISR_delegate
    load ax, 3
    jmp Thing::ISR_delegate
  }

  static void ISR_delegate(byte pin) {
    Thing::handlers[pin]->isSignalled();
  }

  void attach(byte pin) {
    handlers[pin] = this;
    attachInterrupt(pin, (void (*)())(((_PROGMEM byte *)ISR) + (pin * 4))); // each block is 4 bytes of machine code
  }
}

Thanks. That is essentially what I've done, although I just did it in global scope. Works fine.