Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Electronics ➜ Microprocessors ➜ Calling an ISR from a class

Calling an ISR from a class

Postings by administrators only.

Refresh page


Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Sat 05 Sep 2015 11:06 PM (UTC)
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


18,691 views.

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.