Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 4 of 4

Thread: Using an Object for Interrupt (and other) Call Backs

  1. #1
    Senior Member
    Join Date
    Feb 2017
    Posts
    217

    Using an Object for Interrupt (and other) Call Backs

    Hi. This really isn’t a question / support issue but a request for comments on an idea. First posted it at the Arduino forum without much response. Thought maybe I’d do better here with the generally higher level of coding experience.

    Many folks (myself included) find when they’re first starting with C++ / Arduino that you can’t use attachInterrupt() to attach an object’s instance function. The reason you can’t get a compliant pointer to an instance function has been discussed on many forums. This also applies to other code that uses callback functions.

    It seems a really slick way to handle this would be to define an abstract “callback” class:
    Code:
    #ifndef CALLBACK_H_
    #define CALLBACK_H_
    
    class CallBack {
      public:
        virtual void *callback(void *ptr = nullptr) = 0;
        virtual ~CallBack() {
        }
    };
    
    #endif /* CALLBACK_H_ */
    You’d then create your own classes that inherit from this and pass a base class pointer to the attach function. One example that comes to mind would be a class that handles rotary encoders using interrupts.

    In theory, it should be fairly straight-forward adding the capability to use callback objects to the code that attaches, detaches, and services interrupts. You’d create an array of CallBack class pointers similar to the array of callback function pointers. The attachInterrupt() would be overloaded take a pointer to a CallBack object. A flag would be set for each interrupt to tell the ISR vector function whether to use the CallBack object or the standard callback function.

    The use (if any) of the passed and returned void * pointers would be up to the specific application employing callbacks. For instance, with interrupts you could pass a pointer to the interrupt number that fired to the callback.

    Well, that’s the theory. The first problem I see is files that contain the interrupt-related code, such as:

    Teensy -- pins_teensy.c
    ESP8266 -- core_esp8266_wiring_digital.c
    Arduino M0 -- WInterrupts.c (in SAMD Core)
    AVR -- WInterrupts.c (in AVR Core)


    These are obviously all .c files. Thus, it doesn’t seem the above idea would work in them as it requires C++ techniques. So, the first question is do they have to be .c files? I don’t know enough about the nuances involved with how the tool chain compiles and links C code verses C++ code to know.

    After that, maybe the bigger question is if a change like this is even possible given inertia of the Arduino ecosystem and large number of board packages that would need to be modified. It doesn’t seem to me that the change would affect existing application code as the old attach method would still be available. But, not I don’t know how to tell for sure.

    Anyway, I’m just throwing it out there because of the vast breadth and depth of experience people on this forum have. Maybe it has been considered already and deemed impractical / impossible.
    Last edited by gfvalvo; 01-11-2019 at 10:44 PM.

  2. #2
    Senior Member
    Join Date
    Jul 2014
    Posts
    2,006
    Could you give an example code how to use this?

  3. #3
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    7,382
    Not sure it would relate to general usage but in the Talkie library - .cpp class code - it starts a timer function to push out PWM data for the voice. It was a blocking call as the original AVR code sat and waited for the sound bits to be pushed out in small blocks from the whole of the sound bite.

    The code sets up some future data block the _isr pushes out - then as needed sets up the next block of data. Frank_B provided a scheme to save the 'Talkie *this' to be statically stored. When the code gets the first block of data sets up starts the _isr and returns. The timer _isrs eats the data at a fixed rate or number if calls to the _isr. On the Xth call when new data is needed it uses the stored pointer to call a class func to pull the next block of data to feed the _isr and leaves. Using Frank's idea this worked and I extended Talkie to queue up a number of sound data arrays in addition to the current one. So it is now not only non blocking on one sound but allows calling code to cue up sounds in a batch and continue to have them play.

    So if that represents the general idea Teensy can and does do it on a localized basis. As always the _isr is just entered with no params - it is up to the Talkie code in this case to keep context know what to do next. Perhaps that can be generalized for some set of other examples - give it a look.

  4. #4
    Senior Member
    Join Date
    Feb 2017
    Posts
    217
    Quote Originally Posted by WMXZ View Post
    Could you give an example code how to use this?
    Sure. Sorry if what follows is a little verbose. But, I'm trying to be very clear.

    As an example, if you've looked at the source code for the (rotary) Encoder class, you've seen that it has to jump through a lot of hoops to get the proper interrupts associated with each instance of the class. Imagine if the system allowed you to attached a pointer to a CallBack object to the interrupt vector as an alternative to the traditional callback function pointer. Let's also imagine that it's able to pass the interrupt number to the CallBack object.

    The following code is just notional and not specific to any processor. But, it's based (very) loosely on how 'pins_teensy.c' handles attachInterrupt() and the ISR. The overloaded attachInterrupt() and interrupt vector function would look something like:
    Code:
    struct CallBackPair {
      CallBack *callbackPtr;
      uint8_t intNumber;
    };
    
    static CallBackPair callbackTable[MAX_INTERRUPTS];
    static void (*functionTable[MAX_INTERRUPTS])();
    
    void attachInterrupt(uint8_t intNumber, CallBack *ptr, int mode) {  // Attach interrupt using CallBack class
      uint8_t interruptIndex;
      //
      // Processor-specific interrupt configuration here
      // Determine index into callback table based on interrupt #
      //
      callbackTable[interruptIndex].callbackPtr = ptr;
      callbackTable[interruptIndex].intNumber = intNumber;
      functionTable[interruptIndex] = nullptr;
    }
    
    void attachInterrupt(uint8_t intNumber, void (*ptr)(), int mode) {  // Attach interrupt using traditional callback function
      uint8_t interruptIndex;
      //
      // Processor-specific interrupt configuration here
      // Determine index into callback table based on interrupt #
      //
      functionTable[interruptIndex] = ptr;
    }
    
    
    // ACTUAL HARDWARE INTERRUPT VECTOR FUNCTION
    static void port_A_isr() {
      uint8_t interruptIndex;
      CallBack *ptr;
    
      // Interrupt vector function
      // Handle processor-specific registers and flags
      // Determine index into callback table based on what interrupt(s) this vector handles
      //
    
      // Now call the callback:
      if (functionTable[interruptIndex]) {
        functionTable[interruptIndex]();
      } else {
        ptr = callbackTable[interruptIndex].callbackPtr;
        ptr->callback(((void *)(&callbackTable[interruptIndex].intNumber)));
      }
    }
    So, now the new Encoder class would inherit from the CallBack class and look something like this skeletal outline:
    Code:
    #include "CallBack.h"
    class Encoder : public CallBack {
      public:
    
        Encoder(uint8_t PinA, uint8_t PinB) : pinAinterrupt(digitalPinToInterrupt(PinA)), pinBinterrupt(digitalPinToInterrupt(PinB)) {
          attachInterrupt(pinAinterrupt, this, FALLING);
          attachInterrupt(pinBinterrupt, this, FALLING);
        }
    
        virtual ~Encoder() {
        }
    
        // Handle the two pin interrupts
        virtual void *callback(void *ptr) {
          uint8_t intNumber = *((uint8_t *)ptr);
    
          if (intNumber == pinAinterrupt) {
            // Handle interrupt on Pin A, determine encoder position
          }
    
          if (intNumber == pinBinterrupt) {
            // Handle interrupt on Pin B, determine encoder position
          }
          return nullptr;
        }
    
        int16_t getPosition() {
          return position;
        }
    
      private:
        uint8_t pinAinterrupt;
        uint8_t pinBinterrupt;
        volatile int16_t position;
    };
    Finally, in application code:
    Code:
    #include "NewEncoder.h"
    
    Encoder encoder1(4, 5);
    Encoder encoder2(6, 7);
    
    void setup() {
    }
    
    void loop() {
      int16_t pos1, pos2;
    
      pos1 = encoder1.getPosition();
      pos2 = encoder2.getPosition();
    }
    Doing it this way allows you to associate an interrupt with an instance of a class. That class overloads the callback() method in the base class via polymorphism. Thus the interrupt vector function is able to call the appropriate method, avoiding the problems of getting a pointer to an instance function.

    Also, note how passing in the interrupt number allows one callback object ISR to gracefully handle the interrupts for multiple pins.

    Hope I've been somewhat clear with this example.

    Thanks.
    Last edited by gfvalvo; 01-12-2019 at 03:54 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •