1

I am trying to write a library for encoders.

main.ino:

#include <./Encoders.h>

Encoders encoders();

void setup(){ Serial.begin(9600); }

void loop(){
Serial.print(""); }

Encoders.h:

#ifndef Encoders_h
#define Encoders_h

#include "Arduino.h"

class Encoders{ public: Encoders(); void EncodersSetup(); long RightCount; long LeftCount; void R_A_RISE(); void R_A_FALL(); void R_B_RISE(); void R_B_FALL(); void L_A_RISE(); void L_A_FALL(); void L_B_RISE(); void L_B_FALL(); private: short R_A_SIG, R_B_SIG, L_A_SIG, L_B_SIG; };

#endif

Encoders.cpp

#include "Arduino.h"
#include "Encoders.h"

long RightCount; long LeftCount; long R_A_SIG, R_B_SIG, L_A_SIG, L_B_SIG;

Encoders::Encoders(){ attachInterrupt(0, R_A_RISE, RISING); attachInterrupt(1, R_B_RISE, RISING); attachInterrupt(2, L_A_RISE, RISING); attachInterrupt(3, L_B_RISE, RISING); R_A_RISE(); }

void Encoders::R_A_RISE(){ detachInterrupt(0); R_A_SIG = 1; if(R_B_SIG == 0){ RightCount++; } if(R_B_SIG == 1){ RightCount--; } attachInterrupt(0, R_A_FALL, FALLING); } void Encoders::R_A_FALL(){ detachInterrupt(0); R_A_SIG = 0; if(R_B_SIG == 1){ RightCount++; } if(R_B_SIG == 0){ RightCount--; } attachInterrupt(0, R_A_RISE, RISING); } void Encoders::R_B_RISE(){ detachInterrupt(1); R_B_SIG = 1; if(R_A_SIG == 1){ RightCount++; } if(R_A_SIG == 0){ RightCount--; } attachInterrupt(1, R_B_FALL, FALLING); } void Encoders::R_B_FALL(){ detachInterrupt(1); R_B_SIG = 0; if(R_A_SIG == 0){ RightCount++; } if(R_A_SIG == 1){ RightCount--; }
attachInterrupt(1, R_B_RISE, RISING); } void Encoders::L_A_RISE(){ detachInterrupt(2); L_A_SIG = 1; if(L_B_SIG == 0){ LeftCount++; } if(L_B_SIG == 1){ LeftCount--; } attachInterrupt(2, L_A_FALL, FALLING); } void Encoders::L_A_FALL(){ detachInterrupt(2); L_A_SIG = 0; if(L_B_SIG == 1){ LeftCount++; } if(L_B_SIG == 0){ LeftCount--; } attachInterrupt(2, L_A_RISE, RISING);
} void Encoders::L_B_RISE(){ detachInterrupt(3); L_B_SIG = 1; if(L_A_SIG == 1){ LeftCount++; } if(L_A_SIG == 0){ LeftCount--; } attachInterrupt(3, L_B_FALL, FALLING); } void Encoders::L_B_FALL(){ detachInterrupt(3); L_B_SIG = 0; if(L_A_SIG == 0){ LeftCount++; } if(L_A_SIG == 1){ LeftCount--; }
attachInterrupt(3, L_B_RISE, RISING); }

When I try to compile this, I get errors like this:

C:/Users/marcel/Documents/dev/Arduino/encodeurs/Encoders.cpp:10:38: error: cannot convert 'Encoders::R_B_RISE' from type 'void (Encoders::)()' to type 'void (*)()'

I tried answers on other threads. Most said to make the functions passed to attachInterrupt() static. That threw different errors:

C:/Users/marcel/Documents/dev/Arduino/encodeurs/Encoders.cpp:16:33: error: cannot declare member function 'void Encoders::R_A_RISE()' to have static linkage [-fpermissive]

I also tried this:

attachInterrupt(0, &Encoders::R_A_RISE, this, RISING);

with no success.

The Arduino in question is a Mega but I doubt that matters.

Edit: Per Nick Gammon's answer, I now have this.

MotorEncoderMaster.ino

#include </home/marcel/dev/Arduino/encoders/MotorEncoderMaster/Encoders.h>
Encoders * Encoders::instances [2] = { NULL, NULL };

// instances of our class Encoders right; Encoders left;

void setup (){ right.begin (2); // pin D2 left.begin (3); // pin D3 } // end of setup

void loop (){ // whatever } // end of loop

Encoders.h:

class Encoders{
    volatile bool switchChanged;
static Encoders * instances [2];

static void switchPressedExt0(){
    if(Encoders::instances [0] != NULL){
        Encoders::instances [0]-&gt;switchPressed();
    }
}

static void switchPressedExt1 (){
    if (Encoders::instances [1] != NULL){
        Encoders::instances [1]-&gt;switchPressed();
    }
}


public:
    void begin (const int whichPin){
        pinMode (whichPin, INPUT_PULLUP);
        switch (whichPin){
            case 2:
                attachInterrupt(0, switchPressedExt0, CHANGE);
                instances [0] = this;
                break;

            case 3:
                attachInterrupt(1, switchPressedExt1, CHANGE);
                instances [1] = this;
                break;

        }
    }

    void switchPressed(){
        switchChanged = true;
    }

};

When I try to compile this, I get :

In file included from MotorEncoderMaster.ino:1:0:
/home/marcel/dev/Arduino/encoders/Encoders.h: In static member function ‘static void Encoders::switchPressedExt0()’:
/home/marcel/dev/Arduino/encoders/Encoders.h:7:34: error: ‘NULL’ was not declared in this scope
    if(Encoders::instances [0] != NULL){
                                  ^
/home/marcel/dev/Arduino/encoders/Encoders.h: In static member function ‘static void Encoders::switchPressedExt1()’:
/home/marcel/dev/Arduino/encoders/Encoders.h:13:35: error: ‘NULL’ was not declared in this scope
    if (Encoders::instances [1] != NULL){
                                   ^
/home/marcel/dev/Arduino/encoders/Encoders.h: In member function ‘void Encoders::begin(int)’:
/home/marcel/dev/Arduino/encoders/Encoders.h:21:24: error: ‘INPUT_PULLUP’ was not declared in this scope
     pinMode (whichPin, INPUT_PULLUP);
                        ^
/home/marcel/dev/Arduino/encoders/Encoders.h:21:36: error: ‘pinMode’ was not declared in this scope
     pinMode (whichPin, INPUT_PULLUP);
                                    ^
/home/marcel/dev/Arduino/encoders/Encoders.h:24:45: error: ‘CHANGE’ was not declared in this scope
       attachInterrupt(0, switchPressedExt0, CHANGE);
                                             ^
/home/marcel/dev/Arduino/encoders/Encoders.h:24:51: error: ‘attachInterrupt’ was not declared in this scope
       attachInterrupt(0, switchPressedExt0, CHANGE);
                                                   ^
Marcel
  • 171
  • 4
  • 11

1 Answers1

5

Interrupt Service Routine (ISR) outside a class

Let's consider a simple use of interrupts:

volatile bool switchChanged;

void switchPressed ()
  {
  switchChanged = true;
  }  // end of switchPressed

void setup ()
  {
  pinMode (2, INPUT_PULLUP);
  attachInterrupt (0, switchPressed, CHANGE);
  }  // end of setup

void loop ()
  {
  // whatever    
  }  // end of loop

That compiles fine.


ISR inside a class as a class function (method)

Now let's imagine we want to put the interrupt handling into a class for our convenience.

class myClass
  {
  volatile bool switchChanged;

  public:

  void begin ()
    {
    pinMode (2, INPUT_PULLUP);
    attachInterrupt (0, switchPressed, CHANGE);   // <--- line 10
    }  // end of myClass::begin

  void switchPressed ()
    {
    switchChanged = true;
    }  // end of myClass::switchPressed

  };  // end of class myClass

myClass foo;  // make an instance of myClass

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

void loop ()
  {
  // whatever    
  }  // end of loop

That does not compile:

ISR_in_class_test.ino: In member function ‘void myClass::begin()’:
ISR_in_class_test:10: error: argument of type ‘void (myClass::)()’ does not match ‘void (*)()’

What is going on here?

ISRs have to be static functions, taking no arguments. However (non-static) class functions have an implied this-> pointer which points to the particular instance of the class.

For example, if we have two instances:

myClass foo;  
myClass bar; 

If we call foo.begin() then this-> points to "foo", and if we call bar.begin then this-> points to "bar".

However an ISR, when fired by the processor, cannot know whether this-> is "foo" or "bar" or something else. Thus the compiler cannot compile that line.


ISR inside a class as a static class function

We can try to work around this by making the class function static. Doing that means that the function is not tied to any particular instance, and thus the attachInterrupt line will compile.

class myClass
  {
  volatile bool switchChanged;   // <--- line 3

  public:

  void begin ()
    {
    pinMode (2, INPUT_PULLUP);
    attachInterrupt (0, switchPressed, CHANGE);
    }  // end of myClass::begin

  static void switchPressed ()
    {
    switchChanged = true;   // <--- line 15
    }  // end of myClass::switchPressed

  };  // end of class myClass

myClass foo;  // make an instance of myClass

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

void loop ()
  {
  // whatever    
  }  // end of loop

However now we have a different problem:

ISR_in_class_test.ino: In static member function ‘static void myClass::switchPressed()’:
ISR_in_class_test:3: error: invalid use of member ‘myClass::switchChanged’ in static member function
ISR_in_class_test:15: error: from this location

A non-static class variable cannot be called from a static class function. Why? Because the compiler doesn't know which variable you want. Is it foo.switchChanged or bar.switchChanged?


ISR inside a class as a static class function with static variables

To make the static function work, it can only access static variables. So we can make switchChanged static. Plus we need to define an instance of this static variable.

class myClass
  {
  static volatile bool switchChanged;  // declare

  public:

  void begin ()
    {
    pinMode (2, INPUT_PULLUP);
    attachInterrupt (0, switchPressed, CHANGE);
    }  // end of myClass::begin

  static void switchPressed ()
    {
    switchChanged = true;
    }  // end of myClass::switchPressed

  };  // end of class myClass

volatile bool myClass::switchChanged;  // define

myClass foo;  // make an instance of myClass

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

void loop ()
  {
  // whatever    
  }  // end of loop

Now the class compiles. However we have thrown away most of the advantages of having a class in the first place, as we are forced to use a static function, and that static function can only access static variables.


Glue routines

To work around this problem we can write short "glue" routines. These are functions that interface between an ISR and an instance of a class.

class myClass
  {
  volatile bool switchChanged;

  static myClass * instances [2];

  static void switchPressedExt0 ()
    {
    if (myClass::instances [0] != NULL)
      myClass::instances [0]->switchPressed ();
    }  // end of myClass::switchPressedExt0

  static void switchPressedExt1 ()
    {
    if (myClass::instances [1] != NULL)
      myClass::instances [1]->switchPressed ();
    }  // end of myClass::switchPressedExt1


  public:

  void begin (const byte whichPin)
    {
    pinMode (whichPin, INPUT_PULLUP);
    switch (whichPin)
      {
      case 2: 
        attachInterrupt (0, switchPressedExt0, CHANGE);
        instances [0] = this;
        break;

      case 3: 
        attachInterrupt (1, switchPressedExt1, CHANGE);
        instances [1] = this;
        break;

      } // end of switch
    }  // end of myClass::begin

  void switchPressed ()
    {
    switchChanged = true; 
    }

  };  // end of class myClass

myClass * myClass::instances [2] = { NULL, NULL };

// instances of our class  
myClass foo; 
myClass bar;

void setup ()
  {
  foo.begin (2);   // pin D2
  bar.begin (3);   // pin D3
  }  // end of setup

void loop ()
  {
  // whatever    
  }  // end of loop

This is a bit fiddly, however what it is doing is remembering in an array which instance of the class is associated with which interrupt. The "glue" routines switchPressedExt0 and switchPressedExt1 call the appropriate instance of the switchPressed function by using the remembered class pointer.

Now the non-static function switchPressed can access non-static class variables.


Adapted from my web page Calling an ISR from a class

Nick Gammon
  • 38,184
  • 13
  • 65
  • 124
  • The whole point of this was to break my code into multiple files. When I put the class declaration in a different file and #include it, I get this error: http://pastebin.com/TkBCG74G. Is there a way to declare the class in another file and not break everything? – Marcel Feb 13 '16 at 18:43
  • Of course. However not seeing your code it is hard to advise. – Nick Gammon Feb 13 '16 at 21:22
  • Code: https://bitbucket.org/MarcelRobitaille/encoders/ – Marcel Feb 14 '16 at 03:55
  • See my reply here Classes and objects: how many and which file types do I actually need to use them? - you don't need to include .ino files - the IDE does that for you (if they are in the same folder). Also don't call your files "main.xxx". – Nick Gammon Feb 14 '16 at 04:10
  • Rename your file Encoders.ino to be Encoders.h and include that into your main file. Rename your main file to be something other than main.ino. Then it compiles. – Nick Gammon Feb 14 '16 at 04:15
  • Still not compiling: http://pastebin.com/g2c5NQ6S – Marcel Feb 14 '16 at 04:45
  • What is still not compiling? We seem to be jumping from error messages, to code. The fact is, that I got it to compile. Therefore you are doing something different from me. – Nick Gammon Feb 14 '16 at 05:57
  • Could you send me your files so I know if it's my code or my compiler? Thanks. – Marcel Feb 18 '16 at 19:44
  • Rather than having me send you files (which other readers won't see) please amend your original question to show what your code is now, exactly, including file names. Then post the error message underneath, rather than having it in Pastebin somewhere. – Nick Gammon Feb 18 '16 at 19:49
  • Is that what you meant or should I remove what I used to have? – Marcel Feb 18 '16 at 20:01
  • That's fine - I wasn't sure whether to suggest editing the original or not. It would have been in the edit history. Anyway, give me a minute to look at it. – Nick Gammon Feb 18 '16 at 20:13
  • Why don't you have #include "Encoders.h" - instead of that huge include statement? - Is that not in the same folder? – Nick Gammon Feb 18 '16 at 20:15
  • I changed that include line to what I suggested: #include "Encoders.h" and it compiled fine under IDE 1.6.7. Just those two files. – Nick Gammon Feb 18 '16 at 20:18
  • what do you know. It worked in 1.6.7. I had to use my windows laptop. Unfortunately I can't easily get 1.6.7 on my main pc because the linux package repo is behind. Anyway I am rambling. Thanks for all your help! – Marcel Feb 18 '16 at 20:24
  • I just download the Linux package onto my Ubuntu and run from that (my test was on Ubuntu). You don't need to wait for the package to be updated. – Nick Gammon Feb 18 '16 at 21:13
  • @NickGammon: why don't you use as ISR a friend function, paired to a reference to the class instance? The reference can be a static field in the class itself. I would find it much more expressive. – Igor Stoppa Jun 15 '16 at 08:58
  • @IgorStoppa - can you give an example somewhere? There are probably other ways of achieving this, but the method I showed was integrated into the class. I don't see how using a friend gets around needing to know which instance of the class the interrupt is for. – Nick Gammon Jun 15 '16 at 21:35
  • @NickGammon: I meant that the class does have a static pointer to an instance of itself. This cannot be avoided. But the friend function could be the ISR itself. It would get away with one level of indirection (you called it "the glue"). If it's not clear what I mean, where could i put a snippet of code? – Igor Stoppa Jun 15 '16 at 21:44
  • Er, yes but the ISR won't know which class instance to call? You can make your own answer with your alternative example. :) – Nick Gammon Jun 15 '16 at 22:03
  • Isn't this solution the equivalent of using named static functions for every possible pin? – Denis G. Labrecque Feb 28 '21 at 01:38
  • Yes, effectively. There is no easy workaround, except to make static functions for each pin, one way or the other. – Nick Gammon Mar 01 '21 at 04:23