Struggling to have ISR(PCINT2_vect) trigger a class function

I need to find a way to trigger a class function when an interrupt occurs and have all code within the .cpp and .h files.

ISR(PCINT2_vect)
triggers
RC::Timers(PIND, 0, 0, 7);

getting error: cannot call member function 'void RC::Timers(uint8_t, uint8_t, uint8_t, uint8_t)' without object

I want to package a working chunk of code into a class to simplify adding it to projects.

RC::RC() {

}
// port change Interrupt
ISR(PCINT2_vect) { //this ISR pins 0-7
  sei();                    // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
  RC::Timers(PIND, 0, 0, 7);
}

void RC::Timers(uint8_t pin, uint8_t group, uint8_t StartPin, uint8_t EndPin) {
 //process the interrupt
}

So Here is the Full Code I would like to insert into a Class. Maybe this will spark some posts :slight_smile:
Any and all help would be great!
I personally created the code from scratch and suggested licencing for others to use would be appreciated also I've never went this far to provide open source code.

Sketch compiles and uses only 3,448 bytes as shown below.
Code functions perfectly!
No delays to slow down your other code!!!
produces values from about 1000~2000 representing width of the pulse coming from each receiver source
And it should be able to use ALL pins on the Arduino UNO at once! 19 inputs! If you could find something that you could use that for.

Take any RC remote and Connect it to any Arduino Uno Pin including Analog!!!

Would like to make this into a nice packaged class and share it!
Any Help

Thanks :slight_smile:

byte RCinCtr = 0;
volatile unsigned long edgeTime[20];
volatile uint16_t rcValue[20]  = { 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502, 1502}; // interval [1000;2000] RC Timing window 
volatile uint16_t rcValueX[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // interval [0;65535]  // Allows for alternate usages shorter or longer times
volatile uint8_t  PinArray[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // List of pins that could be used as ping inputs:
volatile uint8_t  mask[3]      = {0, 0, 0};
volatile uint8_t  PinMask[3]   = {0, 0, 0}; 
volatile uint8_t  PCintLast[3] = {0, 0, 0}; 

// port change Interrupt
ISR(PCINT2_vect) { //this ISR pins 0~7
  sei();                    // re enable other interrupts at this point. 
  Timers(PIND, 0,0,7);
}

ISR(PCINT0_vect) { //this ISR pins 8~13
  sei();                    // re enable other interrupts at this point
  Timers(PINB, 1,8,13);
}

ISR(PCINT1_vect) { //this ISR s A0~A5
  sei();                    // re enable other interrupts at this point
  Timers(PINC, 2,14,19);
}

void Timers(uint8_t pin, byte group,uint8_t StartPin, uint8_t EndPin) {
  uint16_t cTime, dTime;
  cTime = micros();         // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
  mask[group] = pin ^ PCintLast[group];   // doing a ^ between the current interruption and the last one indicates wich pin changed      
  PCintLast[group] = pin;          // we memorize the current state of all PINs in group
  for (byte P = 0; P < (EndPin - StartPin); P++) { // Cycle throug all the pins
    if (PinMask[group] >> P & 1) { // See if we activated the pin
      if (mask[group] >> P & 1) { // See if the pin has changed
        if ((pin >> P & 1)) edgeTime[P + StartPin] = cTime; //Pulse went HIGH store the start time
        else { // Pulse Went low calculate the duratoin
          dTime = cTime - edgeTime[P + StartPin]; // Calculate the change in time
          if (900 < dTime && dTime < 2200) rcValue[PinArray[P + StartPin]] = dTime; // only save proper PWM Pulse
          rcValueX[PinArray[P + StartPin]] = dTime; // Lets Store any duration up to 65535 micro seconds
        }
      }
    }
  }
}


// Install Pin change interrupt for a pin, can be called multiple times
void pciSetupRC(byte pin)
{
  
  if (pin <= 7)PinMask[0] =  bitWrite(PinMask[0], pin, 1); // PIND for pins 0~7
  if ((pin > 7) && (pin <= 13)) PinMask[1] =  bitWrite(PinMask[1] , pin - 8, 1); // PINB for pins 8~13
  if (pin > 13) PinMask[2] =  bitWrite(PinMask[2] , pin - 14, 1); // PINC for A0~A5 Starts on Pin 14
  pinMode(pin, INPUT);// enable interrupt for pin...
  *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
  PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
  PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
  PinArray[pin] = RCinCtr;
  RCinCtr++;
}




void setup() {
  Serial.begin(115200); 
  while (!Serial) {
  }
  // enable interrupt for pin... un-comment line to use that pin for input
  // Select the pins you want to use for input (Notes for alternate uses)
  // pciSetupRC(0); // Serial Communication
  // pciSetupRC(1); // Serial Communication
  //pciSetupRC(2); // Interrupt 2
  //pciSetupRC(3); // Interrupt 1
  //pciSetupRC(4);
  //pciSetupRC(5);
  //pciSetupRC(6);
  //pciSetupRC(7);
  //pciSetupRC(8);
  pciSetupRC(9);
  pciSetupRC(10); // SPI SS Slave Select
  pciSetupRC(11); // SPI MOSI
  //pciSetupRC(12); // SPI MISO
  //pciSetupRC(13); // SPI SCK 
  //pciSetupRC(14); // A0
  //pciSetupRC(15); // A1
  //pciSetupRC(16); // A2
  //pciSetupRC(17); // A3
  // pciSetupRC(18); // A4 // i2c
  // pciSetupRC(19); // A5 // i2c
}

void loop() {
  for (static long QTimer = millis(); (long)( millis() - QTimer ) >= 100; QTimer = millis() ) { // Quick one line no delay spam timer 10 times a second
    for (int c = 0; c < RCinCtr; c++) {
      Serial.print(rcValueX[c]);
      Serial.print("\t");
    }
    Serial.println();
  }
}

Suppose you have a class called Dog, with a method called Bark().. Suppose you have 12 instances of the class, called Spot, Fido, CrapsLots, EatsFurniture, ShedsEverywhere, etc.

Now suppose you have an event which triggers an interrupt, and you want, when the interrupt happens, for a dog to Bark().

Which dog should bark?

Thank you PaulS I've seen this as a difficulty and like the way you explained it. And so I need a shock collar that recognizes the dogs bark lol :slight_smile:

So if i were to package the somewhat short chunk of code I submitted below in a class I would need to have some type of pointer to my class function to hand to the ISR(PCINT2_vect){} or break out the interrupts altogether.
I am not sure what the best practices are and where I should go with Plan B. Can someone provide me a Best way scenario I tried to figure out how pinchangeint library or the servo Library do it but I'm not there yet. The code became overwhelming and my attempts failed.

But which dog should bark? If it's Fifi then just call fifi.bark() inside the ISR. If it's Bella just call bella.bark(). If you want all to bark just call .bark() on all. But this may take time and a ISR should be short, you might want to set a flag and check that flag in loop() and call .bark() of all there.

The way that you get it to work is to declare the method to be called, Bark(), as static. That makes is a class method, rather than an instance method.

Then, the class method determines which dog is to bark.

Of course, this means that each dog must tell the class that it is ready, willing, and able to bark, and the class needs to keep track of all the dogs, so that it can decide which one should bark() this time (or maybe all of them should be told to bark()) (and notice that bark() and Bark() are two different methods - one belongs to the class, the other belongs to the instances).

What this means is that Spot->bark() would make one dog bark, while Dog::Bark() might make a real racket, with all the mutts bark()ing their fool heads off.

So a portion of My code:

// port change Interrupt
ISR(PCINT2_vect) { //this ISR pins 0~7
  sei();                    // re enable other interrupts at this point. 
  Timers(PIND, 0,0,7);
}

ISR(PCINT0_vect) { //this ISR pins 8~13
  sei();                    // re enable other interrupts at this point
  Timers(PINB, 1,8,13);
}

ISR(PCINT1_vect) { //this ISR s A0~A5
  sei();                    // re enable other interrupts at this point
  Timers(PINC, 2,14,19);
}

void Timers(uint8_t pin, byte group,uint8_t StartPin, uint8_t EndPin) {
  uint16_t cTime, dTime;
  cTime = micros();         // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
  mask[group] = pin ^ PCintLast[group];   // doing a ^ between the current interruption and the last one indicates wich pin changed      
  PCintLast[group] = pin;          // we memorize the current state of all PINs in group
  for (byte P = 0; P < (EndPin - StartPin); P++) { // Cycle throug all the pins
    if (PinMask[group] >> P & 1) { // See if we activated the pin <<< Check to see if this dog should bark
        DogBark();
    }
  }
}

How would I convert that to a Class and trigger the interrupt... Mabe a Class isn't the correct description of how to do it. A simpler request is I would like to have it contained in a file RC_V1.cpp and RC_V1.h and use included: #include<RC_V1.h> to attach it to my code to make it simple for everyone to use. I can already paste the chunk of code into my robot to control it. I'm hoping to take it to the next level.

Dog::Bark() might make a real racket, with all the mutts bark()ing their fool heads off.

I only need one instance of my class but if there were more than on my code checks by default which pin tripped it and discards the rest. I also free up for other interrupts instantly because I'm not that worried about being a couple microseconds off.
If you have a RC remote connect it up and try my code out it works! This is a discussion on how to share it correctly to the world(making my life easier in the process), Please provide a short example that I can try I an struggling on the conversion of the above section. Everything else is easy.

I only need one instance of my class

Then you probably don't need a class. Just because code is declared in a header file and implemented in a source file does not mean that a class is needed. And, if you don't have a class, you don't have a problem.

So is there a way to properly contain it like a class does to have private variables and prevent some other code from creating havoc if a variable is named the same?

Jeez, we are only talking about a few thousand lines at most. Just keep track of things. If necessary use prefixes like: mutt_numDogs or poodle_numDogs.

ETA:
Mainly, just minimize the use of global variables. Functions can use pre-fixes.

But it's not forbidden. Just create 1 instance of that class in your header and use that in the ISR

class Dog{
  public:
    bark(){
      //blaaaa
    }
};

Dog myDog;

ISR(PCINT2_vect){
  myDog.bark();
}

The usual way to ensure that there is only one instance of the class is to have a private constructor. That prevents anyone from trying to create another instance.

You provide a public method to get THE instance, and have that method return the instance, if it exists, and create one and return it, if it does not already exist.

Google "singleton".

But it's not forbidden. Just create 1 instance of that class in your header and use that in the ISR

So in short :
This has to be part of the end users code so cryptic for them...

ISR(PCINT2_vect){
  myDog.bark();
}

And can't be hidden away... Disappointing I would love to have a cleaner solution.

JUST IN:

PaulS Thanks this is probably the best post of the bunch!!!

The usual way to ensure that there is only one instance of the class is to have a private constructor. That prevents anyone from trying to create another instance.

You provide a public method to get THE instance, and have that method return the instance, if it exists, and create one and return it, if it does not already exist.

Google "singleton".

I will google it when I get back from work!
Thanks everyone
Please post any examples or other suggestions and I'll look at them. when I get the code done I WILL post it for everyone :slight_smile:

zhomeslice:
And can't be hidden away... Disappointing I would love to have a cleaner solution.

It can, why not? But you HAVE to make a instance of Dog (called myDog) in the header. The servo library also uses a ISR inside the library :slight_smile:

It can, why not? But you HAVE to make a instance of Dog (called myDog) in the header. The servo library also uses a ISR inside the library

I need a brief example on how to put the ISR inside the library.
Thanks

I need a brief example on how to put the ISR inside the library.

The ISR is a function, like any other function. Put the function in the sketch, if that is appropriate. Put the function in the source for the library, if that is a appropriate.

Just as I showed you...

And you might also have a look at the Servo library.

And you might also have a look at the Servo library.

I'll look at it. Lots of code to review but I am certainly up for the challenge and they have decent comments. I'll post what I come up with either way. Thanks, wish me luck

I Did It!!!! and here is the code promised RC Remote to ANY Arduino UNO pin

RC.ino:

#include <RC_V1.h>
RC RCRadio(); 
void setup() {
	Serial.begin(115200); 
	while (!Serial) {
	}
	Serial.println("Alive");
	RCRadio.pciSetupRC(3); //  add a pin 
	RCRadio.pciSetupRC(10); //  add a pin 
	RCRadio.pciSetupRC(11); //  add a pin 
	RCRadio.pciSetupRC(A3); //add a pin
}
void loop() {
	int SpamDelay = 100;
	RCRadio.Print(SpamDelay);
}

RC_V1.cpp:

#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include "RC_V1.h"
volatile uint8_t  mask[4]      = {0, 0, 0};
volatile uint8_t  PCintLast[3] = {0, 0, 0}; //looking fo pin changes using bianary
volatile uint8_t  PinMask[3]   = {0, 0, 0}; // Bionary pin list 3 groups 
volatile uint32_t edgeTime[20]; //Pulse HIGH start time
volatile uint16_t rcValue[20]  = { 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500}; // interval [1000;2000]  Channels 9,10,11,A0,A1
volatile uint16_t rcValueX[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // interval [0;65535]  Channels 9,10,11,A0,A1
volatile uint8_t  PinArray[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // Key = Pin Val = Position List of pins that could be used as ping inputs:
volatile uint16_t rcValueCenter[20]  = { 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500}; // interval [1000;2000]  Channels 9,10,11,A0,A1

volatile uint32_t  AliveBits = 0;  // a reading Since Last Test,
volatile int8_t IsAlive = 0;
volatile bool FailSafe = true;
volatile uint8_t RCPinCtr = 0; // Number of Pins Activated


/*** static functions common to all instances ***/

static inline void Timers(uint16_t cTime, uint8_t pin, uint8_t group,uint8_t StartPin, uint8_t EndPin) {
	uint16_t  dTime;

	//	cTime = micros();         // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
	mask[group] = pin ^ PCintLast[group];   // doing a ^ between the current interruption and the last one indicates wich pin changed      Serial.print(mask,BIN);
	PCintLast[group] = pin;          // we memorize the current state of all PINs in group
	for (byte P = 0; P < (EndPin - StartPin); P++) { // Cycle throug all the pins in Group
		if (PinMask[group] >> P & 1) { // See if we activated the pin
			if (mask[group] >> P & 1) { // See if the pin has changed
				uint8_t PinNumber = P + StartPin;
				uint8_t Position = PinArray[PinNumber];
				if ((pin >> P & 1)) edgeTime[PinNumber] = cTime; //Pulse went HIGH store the start time
				else { // Pulse Went low calculate the duratoin
					dTime = cTime - edgeTime[PinNumber]; // Calculate the change in time
					if (900 < dTime && dTime < 2200)rcValue[Position] = dTime; // only save proper PWM Pulse
					rcValueX[Position] = dTime; // Lets Store any duration up to 65535 micro seconds
					AliveBits = AliveBits | 1 << Position;
					//Serial.print(PinNumber);Serial.print(" - ");Serial.print(Position);Serial.print(" = ");Serial.println(dTime);
				}
			}
		}
	}
}

// port change Interrupt
ISR(PCINT2_vect) { //this ISR pins 0-7
	sei();                    // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
	Timers(micros(),PIND, 0,0,7);
}

ISR(PCINT0_vect) { //this ISR is common to every receiver channel, it is call everytime a change state occurs on a pins 8-13 only need 9-11
	sei();                    // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
	Timers(micros(),PINB, 1,8,13);
}

ISR(PCINT1_vect) { //this ISR s A0~A5
	sei();                    // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
	Timers(micros(),PINC, 2,14,19);
}

SIGNAL(TIMER0_COMPA_vect){ // Timer to check for signal loss
	sei();
	static int MSec=0;
	if(MSec++ < 10)return; // Check every 10 Miliseconds for change
	MSec = 0;
	IsAlive = (AliveBits > 0) ?   IsAlive+1 : IsAlive-1  ;
	IsAlive = constrain(IsAlive,-20,+20);// Must have 20 tests good or 20 tests bad for IsAlive to change states
	if ((IsAlive <= 0) && FailSafe){
		//		for (uint8_t c = 0;c < RCPinCtr ;c++) rcValue[c] = rcValueCenter[c];
	}
	AliveBits = 0;
}

/*** end of static functions ***/

RC::RC(){
	OCR0A = 0xAF;
	TIMSK0 |= _BV(OCIE0A);
}

// Install Pin change interrupt for a pin, can be called multiple times
void RC::pciSetupRC(uint8_t pin){
	if (pin <= 7)PinMask[0] =  bitWrite(PinMask[0], pin, 1); // PIND for pins 0~7
	if ((pin > 7) && (pin <= 13)) PinMask[1] =  bitWrite(PinMask[1] , pin - 8, 1); // PINB for pins 8~13
	if (pin > 13) PinMask[2] =  bitWrite(PinMask[2] , pin - 14, 1); // PINC for A0~A5 Starts on Pin 14
	pinMode(pin, INPUT);// enable interrupt for pin...
	*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
	PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
	PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
	PinArray[pin] = RCPinCtr;
	PinNumber[RCPinCtr] = pin;
	RCPinCtr++;
}
void RC::Print(uint16_t SpamDelay) {
	runAfter(SpamDelay){ // print after SpamDelay miliseconds (No Windup)
		if(!Alive()) {
			Serial.print("No Signal ");
		} else {
			Serial.print("#");
			for (int Ctr = 0; Ctr <= RCPinCtr; Ctr++) {
				Serial.print(Ctr);
				Serial.print(" Pin ");
				Serial.print(PinNumber[Ctr]);
				Serial.print(" Value ");
				Serial.print(rcValue[Ctr]);
				Serial.print("\t");
			}
		}
		Serial.println();
	}
}
uint8_t RC::Alive() {
	return(IsAlive > 0);
}
int16_t RC::PinTime(uint8_t pin, uint16_t offset) {
	return(PosTime(PinArray[pin], offset ));
}
int16_t RC::PinTimeX(uint8_t pin) {
	return(PosTimeX(PinArray[pin]));
}
int16_t RC::PosTime(uint8_t PinCount, uint16_t offset ) {
	return(rcValue[PinCount] - offset);
}
int16_t RC::PosTimeX(uint8_t PinCount) {
	return(rcValueX[PinCount]);
}

RC_V1.h

#ifndef RV_V1_h
#define RC_V1_h
#define LIBRARY_VERSION	1.1.1
#define runAfter(t) for (static unsigned long _ATimer; (unsigned long)(millis() - _ATimer) >= (t); _ATimer = millis())
#include <avr/pgmspace.h>
class RC
{
public:
	RC();
	void pciSetupRC(uint8_t pin);
	void Print(uint16_t SpamDelay = 100);
	void Timers(uint8_t pin, uint8_t group, uint8_t StartPin, uint8_t EndPin);
	uint8_t Alive();
	int16_t PinTime(uint8_t pin, uint16_t offset = 1000);
	int16_t PinTimeX(uint8_t pin);
	int16_t PosTime(uint8_t PinCount, uint16_t offset = 1000);
	int16_t PosTimeX(uint8_t PinCount);
private:
	uint8_t  PinNumber[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // List of pins that could be used as ping inputs:
};
#endif