Can't use attachInterrupt in a library

2019-02-17 19:09发布

问题:

I'm writing a simple library for an ultrasonic distance sensor and thought i'd try using interrupts.

However i can't set my functions in the attachCallback method properly.

I want HCSR04Interrupt::echoHigh() and HCSR04Interrupt::echoLow() called when the pin goes high and low respectively.

I've Googled this to no avail. The Ardiuno IDE says the following:

./Arduino/libraries/HCSR04/HCSR04Interrupt.cpp: In member function 'void HCSR04Interrupt::getDistance()':
./Arduino/libraries/HCSR04/HCSR04Interrupt.cpp:31: error: argument of type 'void (HCSR04Interrupt::)()' does not match 'void (*)()'
./Arduino/libraries/HCSR04/HCSR04Interrupt.cpp: In member function 'void HCSR04Interrupt::echoHigh()':
./Arduino/libraries/HCSR04/HCSR04Interrupt.cpp:47: error: argument of type 'void (HCSR04Interrupt::)()' does not match 'void (*)()'

Here is my header:

#ifndef _HCSR04Interrupt_
#define _HCSR04Interrupt_

#include "Arduino.h"

#define HCSR04_CM_FACTOR 58.0
#define HCSR04_IN_FACTOR 148.0
#define HCSR04_CM_MODE 0
#define HCSR04_IN_MODE 1

class HCSR04Interrupt {
  public:
    double distance;

    HCSR04Interrupt(int trigger_pin, int echo_pin, void (*callback)());

    void setUnits(int units);

    void getDistance();
  private:
    int _trigger_pin;
    int _echo_pin;
    int _units;
    unsigned long _micros_start;
    void (*_callback)();

    void initialize();
    void echoHigh();
    void echoLow();
};

#endif

And my implementation (not complete since i cant get past the attachInterrupt step):

#include "Arduino.h"
#include "HCSR04Interrupt.h"

HCSR04Interrupt::HCSR04Interrupt(int trigger_pin, int echo_pin, void (*callback)()) {
  _trigger_pin = trigger_pin;
  _echo_pin = echo_pin;
  _callback = callback;

  initialize();
}

void HCSR04Interrupt::setUnits(int units) {
  _units = units;
}

void HCSR04Interrupt::initialize() {
  pinMode(_trigger_pin, OUTPUT);
  pinMode(_echo_pin, INPUT);

  digitalWrite(_trigger_pin, LOW);
}

void HCSR04Interrupt::getDistance() {
  //Listen for the RISING interrupt
  attachInterrupt(_echo_pin - 2, echoHigh, RISING);

  //The trigger pin should be pulled high,
  digitalWrite(_trigger_pin, HIGH);

  //for 10 us.
  delayMicroseconds(20);

  //Then reset it.
  digitalWrite(_trigger_pin, LOW);
}

void HCSR04Interrupt::echoHigh() {
  _micros_start = micros();

  detachInterrupt(_echo_pin - 2);
  attachInterrupt(_echo_pin - 2, echoLow, FALLING);
}

void HCSR04Interrupt::echoLow() {
  detachInterrupt(_echo_pin - 2);

  unsigned long us = micros() - _micros_start;

  distance = us;

  (*_callback)();
}

回答1:

So the compiler (not the IDE) tells you exactly what's wrong:

argument of type 'void (HCSR04Interrupt::)()' does not match 'void (*)()

So, while attachInterrupt() takes a function pointer of type void (*)(), you're trying to pass it a non-static member function, which you can't. You can try making the member function static and casting:

static void echoHigh();

// ...

attachInterrupt(_echo_pin - 2, reinterpret_cast<void (*)()>(&echoHigh), RISING);


回答2:

Arduino interrupt handlers can only be functions. You are trying make method of an object an interrupt handler. Hence the compiler complains.

To be more precise about it, object methods are like functions, but it is as if they take a "hidden" parameter, which specifies the object instance. Therefore, they actually have different type signatures from plain functions. This disallows one to pass a method pointer when what a function is looking for is a plain function pointer.

The solution is to move your echoHigh() and echoLow() out of the HCSR04Interrupt class, and make them plain functions.



回答3:

As I stumbled upon this question and it hasn't had an accepted answer, I write what I found, which worked for me:

The interrupt has to be called by a global wrapper. This wrapper needs to call a handleInterupt function of the class. Therefore it has to know the class. This can be done by storing it in a global variable. If multiple instances of the class should be used, multiple such global variables have to be used. But as the interrupt pins are just a few you can write a global variable and function for every pin:

MyClass theInstance_pin3 = NULL;
MyClass theInstance_pin7 = NULL;

// Somewhere, fill in an initialized copy of MyClass,
// and set theInstance_pin3 or theInstance_pin7 to it

void ISR_3()
{
   if (theInstance_pin3)
       theInstance_pin3->handleInterrupt();
}
void ISR_7()
{
   if (theInstance_pin7)
       theInstance_pin7->handleInterrupt();
}

as a reference see: http://forum.arduino.cc/index.php?topic=41713.0 or http://forum.arduino.cc/index.php?topic=160101.0



回答4:

I got around this by making a singleton base class which represents the hardware as a whole (which kinda makes sense in this situation anyway).

Any function pointers can then be passed to the sub-component class, and handled by the singleton, whose member variables and methods are all static.

Example headers (untested):

// Sub-component
class LampButton {
public:
    LampButton(int pin, void(*pushHandler)());
}

// Sub-component
class LampLed {
public:
    LampLed(int pin);
    void toggle();
}

// Singleton represents the hardware in it's entirety
class Lamp {
public:
    // Call this instead of a constructor
    static void initialize(int buttonPin, int ledPin);

    // Function implemented inline for clarity - don't do this
    static void handleButtonPush() {
        led.toggle();
    }

private:
    static LampButton button;
    static LampLed led;
}