embedding Interrupts and ISR in a class

Hi,

I want to create a class "Odometer" which outputs distance measured by a rotary encoder.

I'm re-using one of the sketches code from here : Arduino Playground - RotaryEncoders.
And I've embedded the interrupts and ISRs into the class constructor.

Now, the code "attachInterrup(0, encodePinA, CHANGE)" works just fine when it's in the void setup(),
but when I put it in the class constructor, it's not compiling !

Details
the compile error is :

* *In constructor 'Odometer::Odometer(int, int)': .../libraries/Odometer/Odometer.cpp:20:40: error: cannot convert 'Odometer::encodePinA' from type 'void (Odometer::)()' to type 'void (*)()'* *

I've tried changing ISR call to "this.encodePinA" or "this.encodePinA()" or "this->encodePinA" or "this->*encodePinA" and am out of ideas.

Find below, the sketch, the class librabary .h and .cpp

SKETCH

#include <Odometer.h>

Odometer MyOdometer(2, 3);

long prev_distance = 0;

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:
  if (prev_distance != MyOdometer.distance()) {
    Serial.println(MyOdometer.distance());
    prev_distance = MyOdometer.distance();
  }

}

HEADER

#ifndef Odometer_h
#define Odometer_h

#include "Arduino.h"

class Odometer
{
  public:
    Odometer(int pinA, int pinB);
    long distance();
    void debug(boolean debug);
    
  private:
    void encodePinA();
    void encodePinB();
    
    int _pinA;
    int _pinB;
    boolean _A_set = false;
	boolean _B_set = false;
    boolean _debug;
    long _distance; 
};

#endif

CONSTRUCTOR

#include "Arduino.h"
#include "Odometer.h"

Odometer::Odometer(int pinA, int pinB)
{
  pinMode(pinA, INPUT);
  digitalWrite(pinA, HIGH);  // turn on pullup resistor
  _pinA = pinA;
  
  pinMode(pinB, INPUT);
  digitalWrite(pinB, HIGH);  // turn on pullup resistor
  _pinB = pinB;
  
  attachInterrupt(0, encodePinA, CHANGE);
  attachInterrupt(1, encodePinB, CHANGE);
  
  _distance = 0;
  _debug = false;
}

// Return Distance
long Odometer::distance(){
	return _distance;
}

// Interrupt on A changing state
void Odometer::encodePinA(){
  // Test transition
  _A_set = digitalRead(_pinA) == HIGH;
  // and adjust counter + if A leads B
  _distance += (_A_set != _B_set) ? +1 : -1;
  
  if (_debug) {Serial.println(_distance);}
}

// Interrupt on B changing state
void Odometer::encodePinB(){
  // Test transition
  _B_set = digitalRead(_pinB) == HIGH;
  // and adjust counter + if B follows A
  _distance += (_A_set == _B_set) ? +1 : -1;

  if (_debug) {Serial.println(_distance);}
}

Am I tackling a pure syntax problem or is this more core to using attachInterrupt in classes ?

Thanks for your help !
Baptiste

You can't make a class method an ISR because there is no way for the instance pointer (ie. this) to be passed to the ISR.

One approach is to make the ISR a static method (then there is no instance - it belongs to the entire class).

    boolean _A_set = false;

Variables starting with an underscore followed by an upper-case letter are reserved by the C++ standard. You should not be using them. Switch to a trailing underscore.

Thanks Nick,
Thanks Paul,

Static method : did you mean declaring it a static method within the class ? (I'm getting hosts of errors on the variables :
error: invalid use of member 'Odometer::_pinA_set' in static member function
boolean _pinA_set = false;

goig to post the updated .h and .cpp

did you mean declaring it a static method within the class ?

Yes.

(I'm getting hosts of
Noted for the "_Axx" variable.

Say what?

Declaring the method as static has raised errors on pretty much every private variable in the class.

Error look like :

.../libraries/Odometer/Odometer.h: In static member function 'static void Odometer::encodePinA()':
.../libraries/Odometer/Odometer.h:24:25: error: invalid use of member 'Odometer::_pinA_set' in static member function
     boolean _pinA_set = false;
                         ^
.../libraries/Odometer/Odometer.cpp:35:3: error: from this location
   _pinA_set = digitalRead(_pinA) == HIGH;
   ^

.h now is

#ifndef Odometer_h
#define Odometer_h

#include "Arduino.h"

class Odometer
{
  public:
    Odometer(int pinA, int pinB);
    long distance();
    void debug(boolean debug);
    
  private:
    static void encodePinA();
    static void encodePinB();
    
    int _pinA;
    int _pinB;
    boolean _pinA_set = false;
	boolean _pinB_set = false;
    boolean _debug;
    long _distance; 
};

#endif

.cpp now is

#include "Arduino.h"
#include "Odometer.h"

Odometer::Odometer(int pinA, int pinB)
{
  pinMode(pinA, INPUT);
  digitalWrite(pinA, HIGH);  // turn on pullup resistor
  _pinA = pinA;
  
  pinMode(pinB, INPUT);
  digitalWrite(pinB, HIGH);  // turn on pullup resistor
  _pinB = pinB;
  
  attachInterrupt(0, encodePinA, CHANGE);
  attachInterrupt(1, encodePinB, CHANGE);
  
  _distance = 0;
  _debug = false;
}

// Return Distance
long Odometer::distance(){
	return _distance;
}

// Interrupt on A changing state
void Odometer::encodePinA(){
  // Test transition
  _pinA_set = digitalRead(_pinA) == HIGH;
  // and adjust counter + if A leads B
  _distance += (_pinA_set != _pinB_set) ? +1 : -1;
  
  if (_debug) {Serial.println(_distance);}
}

// Interrupt on B changing state
void Odometer::encodePinB(){
  // Test transition
  _pinB_set = digitalRead(_pinB) == HIGH;
  // and adjust counter + if B follows A
  _distance += (_pinA_set == _pinB_set) ? +1 : -1;

  if (_debug) {Serial.println(_distance);}
}

// Debug activation/de-activation
void Odometer::debug(boolean debug)
{
  _debug = debug;
  if (_debug) {
  	Serial.begin(9600);
  }	
}

Baptiste
(pretty impressed with the response delay, people are debug slingers here)

Static methods can not access non-static data.

Suppose that you have a high-end hotel. You have a bunch of doormen (that could service an interrupt). A car drives up (an interrupt happens). Which doorman is supposed to respond? What is he/she supposed to know?

The static method is like the doorman-on-duty. It is up to the class (the static method) to determine which specific instance is to deal with THIS interrupt.

okay, so that worked, thanks Paul.
One karma point for the info, and a second karma point for the clever metaphore.

and now I get a truckload of "undefined reference to "Odometer::
.h and .cpp haven't changed since last post.

Errors are :

dometer/Odometer.cpp.o: In function `Odometer::encodePinA()':
.../libraries/Odometer/Odometer.cpp:38: undefined reference to `Odometer::_pinA'
.../libraries/Odometer/Odometer.cpp:38: undefined reference to `Odometer::_pinA_set'
.../libraries/Odometer/Odometer.cpp:40: undefined reference to `Odometer::_distance'
...

(you know, reading the forum, I dislike the newbies sort of apologizing for being newbies... and thinking they should know better... I'm not there yet. yet... :roll_eyes: )

and googling it, it says: it's a problem linking the .o, so I'm looking into that now.
I'm using 1.6.0. and looking around for information.

Any easy hint, I'm happy to take.

Baptiste

Please post your current code, including the .h file.

Checked that the library was in the right folder. seems to be.
Tried deleting the Odometer.cpp.o file and recompiling. not much of an improvement.
Tried changing the "" to <> on the include in the .cpp file, and switched back.

Here's the sketch, .h and .cpp

#include <Odometer.h>

Odometer MyOdometer(2, 3);

long prev_distance = 0;

void setup() {
  // put your setup code here, to run once:
  //Serial.begin(9600);
  MyOdometer.debug(true);

}

void loop() {
  // put your main code here, to run repeatedly:
  if (prev_distance != MyOdometer.distance()) {
    Serial.println(MyOdometer.distance());
    prev_distance = MyOdometer.distance();
  }

}

.h is :

#ifndef Odometer_h
#define Odometer_h

#include "Arduino.h"

class Odometer
{
  public:
    Odometer(int pinA, int pinB);
    long distance();
    void debug(boolean debug);
    
  private:
    static void encodePinA();
    static void encodePinB();
    
    static int _pinA;
    static int _pinB;
    static boolean _pinA_set;
	static boolean _pinB_set;
    static boolean _debug;
    static long _distance; 
};

#endif

and .cpp is

#include "Arduino.h"
#include "Odometer.h"

Odometer::Odometer(int pinA, int pinB)
{
  pinMode(pinA, INPUT);
  digitalWrite(pinA, HIGH);  // turn on pullup resistor
  _pinA = pinA;
  
  pinMode(pinB, INPUT);
  digitalWrite(pinB, HIGH);  // turn on pullup resistor
  _pinB = pinB;
  
  attachInterrupt(0, encodePinA, CHANGE);
  attachInterrupt(1, encodePinB, CHANGE);
  
  _distance = 0;
  _debug = false;
  _pinA_set = false;
  _pinB_set = false;

}

// Return Distance
long Odometer::distance(){
	return _distance;
}

// Interrupt on A changing state
void Odometer::encodePinA(){
  // Test transition
  _pinA_set = digitalRead(_pinA) == HIGH;
  // and adjust counter + if A leads B
  _distance += (_pinA_set != _pinB_set) ? +1 : -1;
  
  if (_debug) {Serial.println(_distance);}
}

// Interrupt on B changing state
void Odometer::encodePinB(){
  // Test transition
  _pinB_set = digitalRead(_pinB) == HIGH;
  // and adjust counter + if B follows A
  _distance += (_pinA_set == _pinB_set) ? +1 : -1;

  if (_debug) {Serial.println(_distance);}
}

// Debug activation/de-activation
void Odometer::debug(boolean debug)
{
  _debug = debug;
  if (_debug) {

dometer/Odometer.cpp.o: In function Odometer::encodePinA()': .../libraries/Odometer/Odometer.cpp:38: undefined reference to Odometer::_pinA'
.../libraries/Odometer/Odometer.cpp:38: undefined reference to Odometer::_pinA_set' .../libraries/Odometer/Odometer.cpp:40: undefined reference to Odometer::_distance'

Are those static members of the class? Where are the declarations? You really need to post all of your code.

Your class consists of nothing but static data. There is no point to having such a class. A library does NOT have to include a class definition.

I agree with PaulS. If you want the neatness of Odometer::pin etc. use a namespace. Example:

namespace Odometer
  {
  
  unsigned long _distance;
  
  // Return Distance
  long distance() 
    {
    return _distance;
    } // end of distance
    
  };  // end of namespace Odometer
  
void setup ()
  {
  }  // end of setup

void loop ()
  {
  long foo = Odometer::distance ();
  }  // end of loop

Now since it isn't a class, you don't have problems with ISRs.