attachInterrupt's callback method from a C++ Class

I have a C++ / Class problem I believe. I knew C once, but never learned C++. I am trying to build a ‘private’ library for my Aquaponics system. Some classes I have managed with google’s help. But this Class, ‘Flowmeter’ is for ‘Hall Effect water flow meters.’ I am having trouble referencing a class function for the interrupt handler from within the class using ‘attachInterrupt.’ The second parameter of the attachInterrupt is a function callback as I understand it. My problem is how to pass this method to attachInterrupt.

Here, well is pseudo code, for it doesnot work that I would like to accomplish:
** attachInterrupt( this->interrupt, this->interruptHandler, RISING );**

CODE for eaSystem.h

/*
  eaSystem.h

  Last updated:  17/05/29
*/

#ifndef eaSystem_h
#define eaSystem_h

#include "Arduino.h"


#define EASLEY_AQUAPONICS_SYSTEM_LIBRARY_VERSION 2

#define SECOND 1000 // One Second for Pause as Delay
#define LITERS2GALLON 3.78541 // ...liters per gallon

// Digiten flow meter calibration factors
#define DigitenHalfFactor 7.5 // Digiten G1-1/2"
#define DigitenOneFactor  4.8 // Digiten G1-1"

// "proof of life" blibcking LED pin number
#define LEDPIN 13

// Siphon EC reading threshold:
// on iff raw < SiphonThreshold, else off
#define SiphonThreshold 900

class Flowmeter
{
  public:
    Flowmeter( int pin );
    Flowmeter( uint8_t pin );
    int flowRate;
    int getFlowRate();

  private:
    void interruptHandler();
    
    unsigned int counter;
    uint8_t pin;
    uint8_t interrupt;
    unsigned long last;
};


class Siphon
{
  public:
    Siphon( int pin );
    Siphon( uint8_t pin );
    int getState();
    int check();
    
    int state;
    uint8_t data[5];

  private:
    int raw;
    uint8_t pin;
    uint8_t ndx;
};

#endif

CODE for eaSystem.cpp

#include "eaSystem.h"


Flowmeter::Flowmeter( int pin )
{
	this->pin = (uint8_t) pin;
  pinMode(this->pin, INPUT);
	this->interrupt = (uint8_t) digitalPinToInterrupt( this->pin );

  attachInterrupt( this->interrupt, this->interruptHandler, RISING);

  this->last = millis();
  this->flowRate = 0;
}

Flowmeter::Flowmeter( uint8_t pin )
{
	this->pin = pin;
  pinMode(this->pin, INPUT);
	this->interrupt = (uint8_t) digitalPinToInterrupt( this->pin );
  
  attachInterrupt( this->interrupt, this->interruptHandler, RISING);
  
  this->last = millis();
  this->flowRate = 0;
}

void Flowmeter::interruptHandler() { this->counter += 1; }

int Flowmeter::getFlowRate( ) {

  float flow; // ...local calculation variable.

  // Re-calculate deltaMs and convert to float.
  float deltaMS = millis() - this->last;

  // Disable interrupts, to save and reset the counter data.
  detachInterrupt( this->interrupt );

  // Save current tick counters while more no-interrupts allowed.
  float count = this->counter;

  // ...and reset counter for the next interval check.
  this->counter = 0;

  // re-Enable the interrupts having saved the current count.
  attachInterrupt( this->interrupt, this->interruptHandler, RISING);
  
  // Calculation: Start with normalized clicks per 5-seconds,
  flow = ((5000.0/deltaMS) * count);

  // Convert clicks/sec to liters/min 
  flow = (flow/5.0) / DigitenHalfFactor;

  // Convert to (gallons/min)*10 and round.
  this->flowRate = round( (flow / LITERS2GALLON) * 10.0 );
  
  return this->flowRate;
}



Siphon::Siphon( int pin )
{
	this->pin = (uint8_t) pin;
  pinMode(this->pin, INPUT);
}

Siphon::Siphon( uint8_t pin )
{
	this->pin = pin;
  pinMode(this->pin, INPUT);
}

int Siphon::check()
{
  this->raw = (int) analogRead(this->pin);
  return this->raw;
}

int Siphon::getState() 
{
  int k, sum = 0;
  this->raw = (int) analogRead(this->pin);
  this->data[this->ndx] = this->raw < SiphonThreshold ? 1 : 0 ;
  this->ndx = this->ndx == 4 ? 0 : this->ndx + 1;
  for( k=0 ; k < 5 ; k++ ) { sum += this->data[k]; }
  this->state = sum > 3;
  return this->state;
}

CODE for testing.ino

/*
 * Arduino Mega
 * Collection of buckets data.
 * 
 * Updated: 170529
 */
#define NAME "megaTesting_4.ino (Testing 05/29)"

#include <eaSystem.h>


// EC type water level sensor pins for siphons
int bucketsSiphonPin = A0;

// Water flow meter sensor at sub-system inlets
int bucketsFlowPin  = 19;


// Instantiate Sensors
Siphon bucketsSiphon = Siphon( bucketsSiphonPin );
Flowmeter bucketsFlowmeter = Flowmeter( bucketsFlowPin );


void setup( ) {

  pinMode( LEDPIN, OUTPUT );
  
  Serial.begin( 9600 );

  delay( SECOND );

  Serial.print( "Device: " );
  Serial.print( NAME );
  Serial.print( " online.\n" );

  return;
}


unsigned long last1SecondTime = 0,
              last5SecondTime = 0;


void loop( ) {

  // Only process once every second and reset last1SecondTime
  if ( (millis() - last1SecondTime) > 1000 ) {
    
    last1SecondTime = millis();

    // HeartBeat - proof of life using the one second timer.
    digitalWrite( LEDPIN, ( digitalRead( LEDPIN ) != HIGH ? HIGH : LOW ) );

    // Get siphon state every second.
    bucketsSiphon.getState();
  }

  // Only process once every five seconds and reset last5SecondTime
  if ( (millis() - last5SecondTime) > 5000 ) {

    last5SecondTime = millis();

    // Print siphon data every 5th time checked
    printSiphon( &bucketsSiphon );

    // Get flowmeter rate every five seconds.
    bucketsFlowmeter.getFlowRate();
    
    // ...and print flowmeter data each time checked
    printFlowmeter( &bucketsFlowmeter );

    Serial.println();
  }  

  return;
}


void printSiphon( Siphon* siphon ) {

  Serial.print( "siphon->state:  " );
  Serial.println( siphon->state ); 

  return;
}

void printFlowmeter( Flowmeter* flowmeter ) {

  Serial.print( "flowmeter->flowRate:  " );
  Serial.println( flowmeter->flowRate );

  return;
}

ERROR Message

Arduino: 1.8.1 (Mac OS X), Board: "Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)"

/Users/gregbaker/Dropbox/Arduino/libraries/eaSystem/eaSystem.cpp: In constructor 'Flowmeter::Flowmeter(int)':
/Users/gregbaker/Dropbox/Arduino/libraries/eaSystem/eaSystem.cpp:10:67: error: invalid use of non-static member function
   attachInterrupt( this->interrupt, this->interruptHandler, RISING);
                                                                   ^
/Users/gregbaker/Dropbox/Arduino/libraries/eaSystem/eaSystem.cpp: In constructor 'Flowmeter::Flowmeter(uint8_t)':
/Users/gregbaker/Dropbox/Arduino/libraries/eaSystem/eaSystem.cpp:22:67: error: invalid use of non-static member function
   attachInterrupt( this->interrupt, this->interruptHandler, RISING);
                                                                   ^
/Users/gregbaker/Dropbox/Arduino/libraries/eaSystem/eaSystem.cpp: In member function 'int Flowmeter::getFlowRate()':
/Users/gregbaker/Dropbox/Arduino/libraries/eaSystem/eaSystem.cpp:47:67: error: invalid use of non-static member function
   attachInterrupt( this->interrupt, this->interruptHandler, RISING);
                                                                   ^
exit status 1
Error compiling for board Arduino/Genuino Mega or Mega 2560.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.



My question is “how to pass this Class method to ‘attachInterrupt’, if it is at all possible.

Any help would be appreciated.

So, my question is how to pass this Class method “this->interruptHandler” to the ‘attachInterrupt’ method, if it is at all possible.

Any help would be appreciated. TY

Simple answer is you can't.

You can pass a static member function as the handler, but not a member that belongs to a particular instance. Can you make those functions static?

I do not know how that would work.

I'm trying to increment a Class var 'count' with each interrupt. There are six flow meters, each attached to an interrupt. I intended each meter as a class instance and therein the counter to increment.

Could you lead me a little more perhaps?

You could write a normal function in the sketch that decides which member function to call and calls it and attach that function as the handler.

Thank you Delta_G for your quick and useful responses.

Yes. This I’ve tried this and it works - just seemed awkward.

But maybe is best to do and simply move on for I have much yet to learn/do.

Again, I thank you for your time and advice.

Greg

Delta_G:
You could write a normal function in the sketch that decides which member function to call and calls it and attach that function as the handler.

like this example…

// CLASS DEFINITION

class FanSpeed {
  
  public:
    void 
      setup(uint8_t irq_pin, void (*ISR_callback)(void), int value),
      handleInterrupt(void);
    double
      getSpeed();

  private:
    double
      _timeConstant = 60000000.0;
    uint32_t
      _lastMicros = 0UL,
      _interval = 60000000UL;
    void(*ISR_callback)();
};

void FanSpeed::setup(uint8_t irq_pin, void (*ISR_callback)(void), int value)
{
  attachInterrupt(digitalPinToInterrupt(irq_pin), ISR_callback, value);
}

inline void FanSpeed::handleInterrupt(void)
{
  uint32_t nowMicros = micros();
  _interval = nowMicros - _lastMicros;
  _lastMicros = nowMicros;
}

double FanSpeed::getSpeed()
{
  if (micros() - _lastMicros < 1000000UL) // has rotated in the last second
  {
    return _timeConstant / _interval;
  }
  else
  {
    return 0;
  }   
}

// PROGRAM START

FanSpeed* fan1;
uint8_t fan1pin = 2;
FanSpeed* fan2;
uint8_t fan2pin = 3;

void setup()
{
  Serial.begin(115200);
  pinMode(fan1pin, INPUT_PULLUP);
  pinMode(fan2pin, INPUT_PULLUP);
  fan1 = new FanSpeed();
  fan1->setup(fan1pin, []{fan1->handleInterrupt();}, FALLING);
  fan2 = new FanSpeed();
  fan2->setup(fan2pin, []{fan2->handleInterrupt();}, FALLING);
}

void loop()
{
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 1000UL)
  {
    Serial.print(F("Fan one speed = ")); Serial.print(floor(fan1->getSpeed() + 0.5), 0);Serial.println(F(" RPM"));
    Serial.print(F("Fan two speed = ")); Serial.print(floor(fan2->getSpeed() + 0.5), 0);Serial.println(F(" RPM"));
    Serial.print('\r');
    lastMillis = millis();
  }
}

Thank you BulldogLowell.
This looks quite useful and I will try it out.

Appreciated!

Greg

BulldogLowell:
like this example…

// CLASS DEFINITION

class FanSpeed {
 
  public:
    void
      setup(uint8_t irq_pin, void (*ISR_callback)(void), int value),
      handleInterrupt(void);
    double
      getSpeed();

private:
    double
      _timeConstant = 60000000.0;
    uint32_t
      _lastMicros = 0UL,
      _interval = 60000000UL;
    void(*ISR_callback)();
};

void FanSpeed::setup(uint8_t irq_pin, void (*ISR_callback)(void), int value)
{
  attachInterrupt(digitalPinToInterrupt(irq_pin), ISR_callback, value);
}

inline void FanSpeed::handleInterrupt(void)
{
  uint32_t nowMicros = micros();
  _interval = nowMicros - _lastMicros;
  _lastMicros = nowMicros;
}

double FanSpeed::getSpeed()
{
  if (micros() - _lastMicros < 1000000UL) // has rotated in the last second
  {
    return _timeConstant / _interval;
  }
  else
  {
    return 0;
  } 
}

// PROGRAM START

FanSpeed* fan1;
uint8_t fan1pin = 2;
FanSpeed* fan2;
uint8_t fan2pin = 3;

void setup()
{
  Serial.begin(115200);
  pinMode(fan1pin, INPUT_PULLUP);
  pinMode(fan2pin, INPUT_PULLUP);
  fan1 = new FanSpeed();
  fan1->setup(fan1pin, {fan1->handleInterrupt();}, FALLING);
  fan2 = new FanSpeed();
  fan2->setup(fan2pin, {fan2->handleInterrupt();}, FALLING);
}

void loop()
{
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 1000UL)
  {
    Serial.print(F(“Fan one speed = “)); Serial.print(floor(fan1->getSpeed() + 0.5), 0);Serial.println(F(” RPM”));
    Serial.print(F(“Fan two speed = “)); Serial.print(floor(fan2->getSpeed() + 0.5), 0);Serial.println(F(” RPM”));
    Serial.print(’\r’);
    lastMillis = millis();
  }
}

Aren’t lambda functions a c++11 feature that won’t work on Arduino without changing compiler flags?

Regards,
Ray L.

RayLivingston:
Aren't lambda functions a c++11 feature

yes

RayLivingston:
that won't work on Arduino without changing compiler flags?

no... no mods to the compiler required.

BulldogLowell:
yes

no... no mods to the compiler required.

For what versions? I know for a fact c++11 support was NOT enabled a while back...

Regards,
Ray L.

like this example...

I, too, have to ask what version of the IDE you compiled that under. It fails to compile using 1.0.5, although it does compile in 1.8.2.

I use version 1.8. but as far as I can see, Lambda has been available at least since 1.6.6

Lambda has been available at least since 1.6.6

ARDUINO 1.6.6 - 2015.11.03

So, a relatively recent introduction.

PaulS:
So, a relatively recent introduction.

Or, in the life of IoT, a very long time ago…

Something to keep in mind about using Lambdas like this (in the place of a function pointer) is that the lambda must have the same function signature as the function pointer. That is to say, in the example above with attachInterrupt(), the lambda may have no capture or return… just as ISRs take no argument and return void.

WoW!

Thanks again. USA - just sitting down to work on it. LOL

Delta_G:
Simple answer is you can't.

The reason why you can't is because a member function pointer is a fundamentally different type than a more typical function pointer. Declaring and using them is a much more ugly affair than function pointers usually are, if you can believe such a thing. The syntax is hideous.

A good explanation link, Jiggy-Ninja. Useful. TY