How to "attachInterrupt" inside a library?

Status
Not open for further replies.

Revalogics

Well-known member
Hi, I'm currently working on creating a library to read an encoder.
Basically, it reads encoder pins and automatically debounces it.
Also, I made it to set flags if it was rotated, and set direction flags.
I'm not into counting encoder steps kind of thing.

One problem arises: I can't succesfully include attachInterrupt functions inside the library to create interrupts when those pins changed.

This awkward code works with my hardware:
Code:
// contents of the library "R_Encoder.h"
#ifndef R_Encoder_h
#define R_Encoder_h

#include "Arduino.h"
class R_Encoder {
  public:
    R_Encoder(uint8_t pin1, uint8_t pin2, uint32_t debounce) {
      pinMode(pin1, INPUT_PULLUP);
      pinMode(pin2, INPUT_PULLUP);
      pins[0] = pin1;
      pins[1] = pin2;
      debounceTime = debounce;
    }
    bool wasRotated() {
      bool temp = rotated;
      rotated = 0;
      return temp;
    }
    bool goesRight() {return direction;}
    void interrupt() {
      static byte pos;
      bool pinState[2];
      pinState[0] = digitalReadFast(pins[0]);
      pinState[1] = digitalReadFast(pins[1]);
      if(t >= debounceTime) {
        t = 0;
        if(pinState[0] && pinState[1]) pos = 0;
        else if(pinState[0] && !pinState[1]) pos = 1;
        else if(!pinState[0] && !pinState[1]) {
          if(pos == 3) direction = 0;
          else if(pos == 1) direction = 1;
          pos = 2;
          rotated = 1;
        }
        else if(!pinState[0] && pinState[1]) pos = 3;
      }
    }
    uint8_t pins[2];
  private:
    elapsedMillis t;
    volatile bool rotated, direction;
    uint32_t debounceTime;
};

#endif
Code:
// contents of "R_Encoder.cpp"
#include "R_Encoder.h"
Code:
// contents of test sketch "R_Encoder_test.ino"
#define LD7       2
#define LD6       4
#define LD5       5
#define LD4       6
#define LEN       7
#define LRW       8
#define LRS       9

#define ENC1A     33
#define ENC1B     34
#define ENC2A     37
#define ENC2B     38

#include <LiquidCrystalFast.h>
LiquidCrystalFast MC2004(LRS, LRW, LEN, LD4, LD5, LD6, LD7);

#include <R_Encoder.h>
R_Encoder encoder1(ENC1A, ENC1B, 5);
R_Encoder encoder2(ENC2A, ENC2B, 5);

//#include <Encoder.h>
//Encoder encoder1(ENC1A, ENC1B);
//Encoder encoder2(ENC2A, ENC2B);


elapsedMillis t;
long count[2];

void setup() {
  Serial.begin(115200);
  MC2004.begin(20, 4);
  MC2004.clear();
  attachInterrupt(encoder1.pins[0], wut1, CHANGE);
  attachInterrupt(encoder1.pins[1], wut1, CHANGE);
  attachInterrupt(encoder2.pins[0], wut2, CHANGE);
  attachInterrupt(encoder2.pins[1], wut2, CHANGE);
}
void loop() {
  if(t >= 50) {
    t = 0;
    /**/
    if(encoder1.wasRotated()) {
      if(encoder1.goesRight()) count[0]++;
      else count[0]--;
    }
    if(encoder2.wasRotated()) {
      if(encoder2.goesRight()) count[1]++;
      else count[1]--;
    }
    /*
    if(encoder1.read() > 0) count[0]++;
    else if(encoder1.read() < 0) count[0]--;
    if(encoder2.read() > 0) count[1]++;
    else if(encoder2.read() < 0) count[1]--;
    encoder1.write(0);
    encoder2.write(0);
    /**/
    MC2004.setCursor(0, 0);
    MC2004.print(count[0]);
    MC2004.print("        ");
    MC2004.setCursor(0, 1);
    MC2004.print(count[1]);
    MC2004.print("        ");
    MC2004.setCursor(0, 2);
    MC2004.setCursor(0, 3);
  }
}
void wut1() {encoder1.interrupt();}
void wut2() {encoder2.interrupt();}
These three, when together, works as expected.

My goal here is to abstract all "attachInterrupt" calls inside the sketch and have it included inside the library, like this:
Code:
#ifndef R_Encoder_h
#define R_Encoder_h

#include "Arduino.h"
class R_Encoder {
  public:
    R_Encoder(uint8_t pin1, uint8_t pin2, uint32_t debounce) {
      pinMode(pin1, INPUT_PULLUP);
      pinMode(pin2, INPUT_PULLUP);
      pins[0] = pin1;
      pins[1] = pin2;
      debounceTime = debounce;
    }
    void begin() {
      attachInterrupt(pins[0], interrupt, CHANGE);
      attachInterrupt(pins[1], interrupt, CHANGE);
    }
    bool wasRotated() {
      bool temp = rotated;
      rotated = 0;
      return temp;
    }
    bool goesRight() {return direction;}
  private:
    void interrupt() {
      static byte pos;
      bool pinState[2];
      pinState[0] = digitalReadFast(pins[0]);
      pinState[1] = digitalReadFast(pins[1]);
      if(t >= debounceTime) {
        t = 0;
        if(pinState[0] && pinState[1]) pos = 0;
        else if(pinState[0] && !pinState[1]) pos = 1;
        else if(!pinState[0] && !pinState[1]) {
          if(pos == 3) direction = 0;
          else if(pos == 1) direction = 1;
          pos = 2;
          rotated = 1;
        }
        else if(!pinState[0] && pinState[1]) pos = 3;
      }
    }
    elapsedMillis t;
    volatile bool rotated, direction;
    uint8_t pins[2];
    uint32_t debounceTime;
};

#endif
with this test sketch:
Code:
#define LD7       2
#define LD6       4
#define LD5       5
#define LD4       6
#define LEN       7
#define LRW       8
#define LRS       9

#define ENC1A     33
#define ENC1B     34
#define ENC2A     37
#define ENC2B     38

#include <LiquidCrystalFast.h>
LiquidCrystalFast MC2004(LRS, LRW, LEN, LD4, LD5, LD6, LD7);

#include <R_Encoder.h>
R_Encoder encoder1(ENC1A, ENC1B, 5);
R_Encoder encoder2(ENC2A, ENC2B, 5);

//#include <Encoder.h>
//Encoder encoder1(ENC1A, ENC1B);
//Encoder encoder2(ENC2A, ENC2B);

elapsedMillis t;
long count[2];

void setup() {
  Serial.begin(115200);
  MC2004.begin(20, 4);
  MC2004.clear();
  encoder1.begin();
  encoder2.begin();
}
void loop() {
  if(t >= 50) {
    t = 0;
    /**/
    if(encoder1.wasRotated()) {
      if(encoder1.goesRight()) count[0]++;
      else count[0]--;
    }
    if(encoder2.wasRotated()) {
      if(encoder2.goesRight()) count[1]++;
      else count[1]--;
    }
    /*
    if(encoder1.read() > 0) count[0]++;
    else if(encoder1.read() < 0) count[0]--;
    if(encoder2.read() > 0) count[1]++;
    else if(encoder2.read() < 0) count[1]--;
    encoder1.write(0);
    encoder2.write(0);
    /**/
    MC2004.setCursor(0, 0);
    MC2004.print(count[0]);
    MC2004.print("        ");
    MC2004.setCursor(0, 1);
    MC2004.print(count[1]);
    MC2004.print("        ");
    MC2004.setCursor(0, 2);
    MC2004.setCursor(0, 3);
  }
}
 
Thanks Paul for the reply, I already noted that, I even tried changing "void interrupt()" to "static void interrupt()" also making all variables (outside the function) associated to be static, but the library goes haywire.

I guess I put the attachInterrupt() calls in the main sketch? Because that works on my big Teensy project.
 
Sorry, not sure what this means?

As Paul mentioned, the function needs to be marked as static.

But hard to know what goes haywire means...

There are many examples of this in the code base, like look at SPI...

Many times we will do things like:

Have the class remember which object is the current object and keep the current object in static variable...
Then have your static ISR, do something like:
static void interrupt() {
current_object->process_interrupt();
};

And then the process interrupt function has full usage of the members variables...
 
Thanks for the reply, I'll give it a try. What's with "current_object"?
I think I have so much to learn on the coding part. Unusual code routines, structs, pointers, etc. are my weak points.
I guess I have to stick with my working version of this library because any further programming, I think, will be too much for me.
 
Last edited:
Haywire means these error codes upon compilation:
Note: I used "static void interrupt()" here
Code:
In file included from C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder\examples\R_Encoder_test\R_Encoder_test.ino:17:0:

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h: In static member function 'static void R_Encoder::interrupt()':

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:28:37: error: invalid use of member 'R_Encoder::pins' in static member function

       pinState[0] = digitalReadFast(pins[0]);

                                     ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:45:19: note: declared here

     uint8_t pins[2];

                   ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:29:37: error: invalid use of member 'R_Encoder::pins' in static member function

       pinState[1] = digitalReadFast(pins[1]);

                                     ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:45:19: note: declared here

     uint8_t pins[2];

                   ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:30:10: error: invalid use of member 'R_Encoder::t' in static member function

       if(t >= debounceTime) {

          ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:43:19: note: declared here

     elapsedMillis t;

                   ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:30:15: error: invalid use of member 'R_Encoder::debounceTime' in static member function

       if(t >= debounceTime) {

               ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:46:14: note: declared here

     uint32_t debounceTime;

              ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:31:9: error: invalid use of member 'R_Encoder::t' in static member function

         t = 0;

         ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:43:19: note: declared here

     elapsedMillis t;

                   ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:35:24: error: invalid use of member 'R_Encoder::direction' in static member function

           if(pos == 3) direction = 0;

                        ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:44:28: note: declared here

     volatile bool rotated, direction;

                            ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:36:29: error: invalid use of member 'R_Encoder::direction' in static member function

           else if(pos == 1) direction = 1;

                             ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:44:28: note: declared here

     volatile bool rotated, direction;

                            ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:38:11: error: invalid use of member 'R_Encoder::rotated' in static member function

           rotated = 1;

           ^

C:\Users\Revalogics\Documents\Arduino\libraries\R_Encoder/R_Encoder.h:44:19: note: declared here

     volatile bool rotated, direction;

                   ^

Error compiling for board Teensy 3.6.
 
That is because those member variables are not marked as static. Static implies there is only one copy of that variable used by all instances of your class. Without it, the variable is allocated as part of each instance of your class.

I don't know enough about your usage of your new class to fully understand your needs. Example do you expect to use multiple instances of your encoder class. If so then you may need to do a few things. The issue is, if both your instances use the same static interrupt, when the interrupt happens, how do you know which encoder caused this to happen? As The Attach Interrupt code does not allow you to pass any additional information...

If you wish to see how one class has done it, look at the Encoder library that ships with Teensyduino. ...hardware\teensy\avr\libraries\Encoder

So what they did was to create a potential ISR function for every possible pin number, and a static member array, that allows you to save a state variable (could be a this pointer).
So for example if your code you pass in 33 and 34 for the first instance,
it would setup: attachIntterupt(33, isr33, CHANGE);
it would also save away interruptArgs[33] = <something> ... I would use: this which would be pointer to instance.

You could then have isr33 do something like:
void isr33(void) interruptArgs[33]->interrupt();

Where interrupt would not be marked static... (in the above interruptArgs would probably be an array of pointers of your class type...

But again try looking at the encoder library to see how this works
 
Status
Not open for further replies.
Back
Top