Skip to main content
Added shorter example.
Source Link
nPn
  • 174
  • 4

Below is a full example but if that is too long to read, here is a minimal example.

class ISRClass {
public:
  ISRClass(int pin) : pin_(pin) {}
  
  void setup() {
    attachInterrupt(digitalPinToInterrupt(pin_),
                    std::bind(&ISRClass::sensePinIsr, this), CHANGE);
  }

private:
  int pin_;
  ulong change_count_;

  void IRAM_ATTR sensePinIsr() { 
    // do something fast and simple
    change_count_++;
  }
};

The function returned by std::bind takes no arguments, not even this.

Full example follows.

Here is an example. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

Here is an example. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

Below is a full example but if that is too long to read, here is a minimal example.

class ISRClass {
public:
  ISRClass(int pin) : pin_(pin) {}
  
  void setup() {
    attachInterrupt(digitalPinToInterrupt(pin_),
                    std::bind(&ISRClass::sensePinIsr, this), CHANGE);
  }

private:
  int pin_;
  ulong change_count_;

  void IRAM_ATTR sensePinIsr() { 
    // do something fast and simple
    change_count_++;
  }
};

The function returned by std::bind takes no arguments, not even this.

Full example follows.

Here is an example. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

added needed include statements
Source Link
nPn
  • 174
  • 4

Here is an example using. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

#include "FunctionalInterrupt.h"
#include <Arduino.h>
#include <functional>
#include <optional>
#include <map>

using CallBack = std::function<void(int)>;

class PinMonitor {
public:
  PinMonitor(int pin) : pin_(pin) {}
  void setup() {
    pin_state_ = digitalRead(pin_);
    attachInterrupt(digitalPinToInterrupt(pin_),
                    std::bind(&PinMonitor::sensePinIsr, this), CHANGE);
  }

  std::optional<int> CheckForNewPinState(int debounce) {
    if (!change_count_) {
      return std::nullopt;
    }
    if (!is_debouncing_) {
      is_debouncing_ = true;
      debouce_started_at_ = millis();
      return std::nullopt;
    }
    auto time = millis();
    if (time < debouce_started_at_) {
      debouce_started_at_ = time; // handle timer rollover
    } 
    if (time < debouce_started_at_ + debounce) {
      return std::nullopt;
    }
    is_debouncing_ = false;
    change_count_ = 0;
    auto new_pin_state = digitalRead(pin_);
    if (new_pin_state != pin_state_) {
      pin_state_ = new_pin_state;
      for ( auto [_, callback] : callbacks_) {
        callback(pin_state_);
      }
      return pin_state_;
    }
    return std::nullopt;
  }

  int DoIfPinStateChanges(const CallBack callback) {
    next_callback_key++;
    callbacks_[next_callback_key] = callback;
    return next_callback_key;
  }

  void RemoveCallback(int key) {
    callbacks_.erase(key);
  }

private:
  int pin_;
  int pin_state_;
  ulong change_count_;
  CallBack callback_ = [](auto a) {};
  std::map<int, CallBack> callbacks_;
  bool is_debouncing_{};
  ulong debouce_started_at_{};
  int next_callback_key{};

  void IRAM_ATTR sensePinIsr() { change_count_++; }
};

Here is an example using. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

using CallBack = std::function<void(int)>;

class PinMonitor {
public:
  PinMonitor(int pin) : pin_(pin) {}
  void setup() {
    pin_state_ = digitalRead(pin_);
    attachInterrupt(digitalPinToInterrupt(pin_),
                    std::bind(&PinMonitor::sensePinIsr, this), CHANGE);
  }

  std::optional<int> CheckForNewPinState(int debounce) {
    if (!change_count_) {
      return std::nullopt;
    }
    if (!is_debouncing_) {
      is_debouncing_ = true;
      debouce_started_at_ = millis();
      return std::nullopt;
    }
    auto time = millis();
    if (time < debouce_started_at_) {
      debouce_started_at_ = time; // handle timer rollover
    } 
    if (time < debouce_started_at_ + debounce) {
      return std::nullopt;
    }
    is_debouncing_ = false;
    change_count_ = 0;
    auto new_pin_state = digitalRead(pin_);
    if (new_pin_state != pin_state_) {
      pin_state_ = new_pin_state;
      for ( auto [_, callback] : callbacks_) {
        callback(pin_state_);
      }
      return pin_state_;
    }
    return std::nullopt;
  }

  int DoIfPinStateChanges(const CallBack callback) {
    next_callback_key++;
    callbacks_[next_callback_key] = callback;
    return next_callback_key;
  }

  void RemoveCallback(int key) {
    callbacks_.erase(key);
  }

private:
  int pin_;
  int pin_state_;
  ulong change_count_;
  CallBack callback_ = [](auto a) {};
  std::map<int, CallBack> callbacks_;
  bool is_debouncing_{};
  ulong debouce_started_at_{};
  int next_callback_key{};

  void IRAM_ATTR sensePinIsr() { change_count_++; }
};

Here is an example. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

#include "FunctionalInterrupt.h"
#include <Arduino.h>
#include <functional>
#include <optional>
#include <map>

using CallBack = std::function<void(int)>;

class PinMonitor {
public:
  PinMonitor(int pin) : pin_(pin) {}
  void setup() {
    pin_state_ = digitalRead(pin_);
    attachInterrupt(digitalPinToInterrupt(pin_),
                    std::bind(&PinMonitor::sensePinIsr, this), CHANGE);
  }

  std::optional<int> CheckForNewPinState(int debounce) {
    if (!change_count_) {
      return std::nullopt;
    }
    if (!is_debouncing_) {
      is_debouncing_ = true;
      debouce_started_at_ = millis();
      return std::nullopt;
    }
    auto time = millis();
    if (time < debouce_started_at_) {
      debouce_started_at_ = time; // handle timer rollover
    } 
    if (time < debouce_started_at_ + debounce) {
      return std::nullopt;
    }
    is_debouncing_ = false;
    change_count_ = 0;
    auto new_pin_state = digitalRead(pin_);
    if (new_pin_state != pin_state_) {
      pin_state_ = new_pin_state;
      for ( auto [_, callback] : callbacks_) {
        callback(pin_state_);
      }
      return pin_state_;
    }
    return std::nullopt;
  }

  int DoIfPinStateChanges(const CallBack callback) {
    next_callback_key++;
    callbacks_[next_callback_key] = callback;
    return next_callback_key;
  }

  void RemoveCallback(int key) {
    callbacks_.erase(key);
  }

private:
  int pin_;
  int pin_state_;
  ulong change_count_;
  CallBack callback_ = [](auto a) {};
  std::map<int, CallBack> callbacks_;
  bool is_debouncing_{};
  ulong debouce_started_at_{};
  int next_callback_key{};

  void IRAM_ATTR sensePinIsr() { change_count_++; }
};
Source Link
nPn
  • 174
  • 4

I know this is a late answer but I think it is possible to get past the problem with this if you use std::bind

Here is an example using. This class uses interrupts to simply set the state of a member variable when the pin has changed state. It then only reads the pin when needed. You still have to query the member variable in the loop, but that is about 10x faster than reading the pin.

using CallBack = std::function<void(int)>;

class PinMonitor {
public:
  PinMonitor(int pin) : pin_(pin) {}
  void setup() {
    pin_state_ = digitalRead(pin_);
    attachInterrupt(digitalPinToInterrupt(pin_),
                    std::bind(&PinMonitor::sensePinIsr, this), CHANGE);
  }

  std::optional<int> CheckForNewPinState(int debounce) {
    if (!change_count_) {
      return std::nullopt;
    }
    if (!is_debouncing_) {
      is_debouncing_ = true;
      debouce_started_at_ = millis();
      return std::nullopt;
    }
    auto time = millis();
    if (time < debouce_started_at_) {
      debouce_started_at_ = time; // handle timer rollover
    } 
    if (time < debouce_started_at_ + debounce) {
      return std::nullopt;
    }
    is_debouncing_ = false;
    change_count_ = 0;
    auto new_pin_state = digitalRead(pin_);
    if (new_pin_state != pin_state_) {
      pin_state_ = new_pin_state;
      for ( auto [_, callback] : callbacks_) {
        callback(pin_state_);
      }
      return pin_state_;
    }
    return std::nullopt;
  }

  int DoIfPinStateChanges(const CallBack callback) {
    next_callback_key++;
    callbacks_[next_callback_key] = callback;
    return next_callback_key;
  }

  void RemoveCallback(int key) {
    callbacks_.erase(key);
  }

private:
  int pin_;
  int pin_state_;
  ulong change_count_;
  CallBack callback_ = [](auto a) {};
  std::map<int, CallBack> callbacks_;
  bool is_debouncing_{};
  ulong debouce_started_at_{};
  int next_callback_key{};

  void IRAM_ATTR sensePinIsr() { change_count_++; }
};

Then in main you could use this like below.

NetworkConnection networkConnection;
OTAHandler otaHandler;
Logger &logger = Logger::getInstance();

PinMonitor pinMonitor1(16);
PinMonitor pinMonitor2(17);

int call_count{};
int removable_callback{};

void setup() {
  networkConnection.connect();
  logger.startWebSocket();
  otaHandler.setup();
  pinMode(16, INPUT_PULLDOWN);
  pinMode(17, INPUT_PULLDOWN);
  auto chip_id = ESP.getEfuseMac();

  pinMonitor1.setup();
  pinMonitor1.DoIfPinStateChanges([chip_id](auto a) {
    logger.infoln("chip %12llx, reports pin1 new state of %d detected", chip_id, a);
  });
  removable_callback = pinMonitor1.DoIfPinStateChanges([chip_id](auto a) {
    logger.infoln("second call back was called on pin1, new state of %d detected", a);
    call_count++;
  });
  pinMonitor2.setup();
  pinMonitor2.DoIfPinStateChanges([chip_id](auto a) {
    logger.infoln("chip %12llx, reports pin2 new state of %d detected", chip_id, a);
  });

}

void loop() {
  otaHandler.loop();
  logger.checkWebSocket();
 
  std::optional<int> new_pin_state1;
  std::optional<int> new_pin_state2;
  
  new_pin_state1 = pinMonitor1.CheckForNewPinState(100);
  if (new_pin_state1) {logger.infoln("new pin1 state is %d", *new_pin_state1);}

  new_pin_state2 = pinMonitor2.CheckForNewPinState(100);
  if (new_pin_state2) {logger.infoln("new pin2 state is %d", *new_pin_state2);}
  

  if (call_count > 5) {pinMonitor1.RemoveCallback(removable_callback);}

}