#pragma once #include <stdint.h> #ifndef DEBOUNCER_DEFAULT_DEBOUNCE_PUSH_MS #define DEBOUNCER_DEFAULT_DEBOUNCE_PUSH_MS 50 // debounce delay for push button #endif #ifndef DEBOUNCER_DEFAULT_DEBOUNCE_ROTARY_MS #define DEBOUNCER_DEFAULT_DEBOUNCE_ROTARY_MS 1 // debounce delay for rotary button #endif #ifndef DEBOUNCER_DEFAULT_SHORT_MS #define DEBOUNCER_DEFAULT_SHORT_MS 200 // max interval between multiple short clicks #endif #ifndef DEBOUNCER_DEFAULT_LONG_MS #define DEBOUNCER_DEFAULT_LONG_MS 400 // min interval for long click #endif #if defined(ESP8266) && !DEBOUNCER_WITH_MILLIS #pragma message "Debouncer: using esp_get_cycle_count" #define DEBOUNCER_DEFAULT_CYCLE_FUNC esp_get_cycle_count #define DEBOUNCER_DEFAULT_CYCLE_PER_MS (F_CPU / 1000) #else #if !defined(__AVR__) #pragma message "Debouncer: using millis" #endif #define DEBOUNCER_DEFAULT_CYCLE_FUNC millis #define DEBOUNCER_DEFAULT_CYCLE_PER_MS (1) #endif #if 0 #define DEBUGBUTTON(x...) do { Serial.print(cycle_f()); Serial.print(":"); x; } while (0) #else #define DEBUGBUTTON(x...) do { } while (0) #endif #ifndef IRAM_ATTR #define IRAM_ATTR // nothing #endif /////////////////////////////////////////////////////////////// /* ButtonFast:: !enable_multi && !enable_long: short click: response on click no long click ButtonLong:: !enable_multi && enable_long: short click: response on release + debounce time long click: response on long click delay Debouncer<long_cycle=0>:: enable_multi && !enable_long: short clicks number: response on final release + debounce time no long click Debouncer<true,≠0>:: (default) enable_multi && enable_long: short clicks number: reponse on final release + debounce time long click: response on long click delay */ #if __GNUC__ <= 7 #define __cycle_template_decl #define __cycle_template_args #define __cycle_template_impl #define cycle_f DEBOUNCER_DEFAULT_CYCLE_FUNC #define cyclePerMs DEBOUNCER_DEFAULT_CYCLE_PER_MS #else #define __cycle_template_decl \ auto cycle_f = DEBOUNCER_DEFAULT_CYCLE_FUNC, \ auto cyclePerMs = DEBOUNCER_DEFAULT_CYCLE_PER_MS, \ #define __cycle_template_impl \ auto cycle_f, \ auto cyclePerMs, \ #define __cycle_template_args \ cycle_f, \ cyclePerMs, \ #endif template <const bool enable_multi = true, __cycle_template_decl const decltype(cycle_f()) long_cycle = DEBOUNCER_DEFAULT_LONG_MS * cyclePerMs, /* 0 to disable long press */ const decltype(cycle_f()) debounce_cycle = DEBOUNCER_DEFAULT_DEBOUNCE_PUSH_MS * cyclePerMs, const decltype(cycle_f()) short_cycle = DEBOUNCER_DEFAULT_SHORT_MS * cyclePerMs> class Debouncer { protected: using T = decltype(cycle_f()); enum state_e { off, on, on_long, on_debounce, off_debounce, }; state_e state; uint_fast8_t short_count; bool long_state; T state_cycle; public: Debouncer (): state(off), short_count(0), long_state(false), state_cycle(0) { DEBUGBUTTON(Serial.println(enable_multi)); DEBUGBUTTON(Serial.println(long_cycle)); DEBUGBUTTON(Serial.println(debounce_cycle)); DEBUGBUTTON(Serial.println(short_cycle)); } // - must be called in the main loop or from interrupt // - return true if a change has occured bool update (bool pressed); // simulate a long press void simLongClick () { long_state = true; } // simulate n short presses void simShortClick (uint_fast8_t n) { short_count += n; } // returns long state, does not clear shorts bool longState () { bool state = long_state; long_state = false; return state; } // returns short count, does not clear long uint_fast8_t shortCount () { auto count = short_count; short_count = 0; return count; } // returns long state and clears/ignore shorts bool longClick () { return longState(); } // returns short count and clears/ignore long uint_fast8_t shortClick () { return shortCount(); } bool isOff () const { return state == off; } bool isLong () { long_state = false; return state == on_long; } }; /////////////////////////////////////////////////////////////// template <__cycle_template_decl const decltype(cycle_f()) debounce_cycle = DEBOUNCER_DEFAULT_DEBOUNCE_PUSH_MS * cyclePerMs, const decltype(cycle_f()) short_cycle = DEBOUNCER_DEFAULT_SHORT_MS * cyclePerMs> class ButtonFast: public Debouncer</*no multi*/false, __cycle_template_args /*no long*/0, debounce_cycle, short_cycle> { public: // multiclick disabled, longclick disabled ButtonFast (): Debouncer<false, __cycle_template_args 0, debounce_cycle, short_cycle>() { } bool pressed () { using d = Debouncer<false, __cycle_template_args 0, debounce_cycle, short_cycle>; return d::state == d::on_debounce || d::state == d::on; } bool longState () = delete; uint_fast8_t shortCount () = delete; bool longClick () = delete; uint_fast8_t shortClick () = delete; }; /////////////////////////////////////////////////////////////// template <__cycle_template_decl const decltype(cycle_f()) long_cycle = DEBOUNCER_DEFAULT_LONG_MS * cyclePerMs> class ButtonLong: public Debouncer</* no multi */false, __cycle_template_args long_cycle> { public: ButtonLong (): Debouncer<false, __cycle_template_args long_cycle>() { } bool pressed () { return Debouncer<false, __cycle_template_args long_cycle>::isLong(); } bool longState () = delete; uint_fast8_t shortCount () = delete; bool longClick () = delete; uint_fast8_t shortClick () = delete; }; /////////////////////////////////////////////////////////////// template <__cycle_template_decl const decltype(cycle_f()) debounce_cycle = DEBOUNCER_DEFAULT_DEBOUNCE_ROTARY_MS * cyclePerMs> class Rotary { protected: ButtonFast<__cycle_template_args debounce_cycle> b1, b2; int_fast8_t rot; public: Rotary (): b1(), b2(), rot(0) { } bool update (bool in1, bool in2); // steady state with pullup: true, true bool right () { bool ret; if ((ret = (rot > 0))) --rot; return ret; } bool left () { bool ret; if ((ret = (rot < 0))) ++rot; return ret; } }; /////////////////////////////////////////////////////////////// template <const bool enable_multi, __cycle_template_impl const decltype(cycle_f()) long_cycle, const decltype(cycle_f()) debounce_cycle, const decltype(cycle_f()) short_cycle> IRAM_ATTR // can be called from ISR bool Debouncer<enable_multi, __cycle_template_args long_cycle, debounce_cycle, short_cycle>::update (bool pressed) { // get current time auto cycle = cycle_f(); switch (state) { case off: if (pressed) { // button is off, and now pressed DEBUGBUTTON(Serial.println("off->on_debounce")); state_cycle = cycle; if (!enable_multi && /*long press disabled*/(long_cycle == 0)) { bool ret = state != on_debounce; state = on_debounce; return ret; // true on first change } state = on_debounce; } break; // off, no change, return unreaded state case on_long: if (pressed) return false; // no change DEBUGBUTTON(Serial.println("on_long->off_debounce")); state = off_debounce; state_cycle = cycle; return !enable_multi; case on_debounce: if ((cycle - state_cycle) < debounce_cycle) { // don't care before debounce delay DEBUGBUTTON(Serial.println("debounce-on")); return false; } DEBUGBUTTON(Serial.println("on_debounce->on")); state = on; // fall through case on: if (/*long press enabled*/(long_cycle > 0) && (cycle - state_cycle) > long_cycle) { // long press in progress DEBUGBUTTON(Serial.println("on->on_long")); state = on_long; long_state = true; return true; } if (!pressed) { // off after debounce delay DEBUGBUTTON(Serial.println("on->off_debounce")); state = off_debounce; state_cycle = cycle; if (!enable_multi) // single press is released return true; // it was a short click short_count++; return !enable_multi && /*long press enabled*/(long_cycle > 0); } return false; // no change case off_debounce: if ((cycle - state_cycle) < debounce_cycle) { // don't care before debounce delay DEBUGBUTTON(Serial.println("debounce-off")); return false; } if (pressed) { // multiclick in progress DEBUGBUTTON(Serial.println("off_debounce->on_debounce")); state = on_debounce; state_cycle = cycle; return false; } if (enable_multi && (cycle - state_cycle) < short_cycle) { // wait for multiclick DEBUGBUTTON(Serial.println("(multiclick?)")); return false; } // released for good DEBUGBUTTON(Serial.println("off_debounce->off")); state = off; break; } return short_count || long_state; } /////////////////////////////////////////////////////////////// template <__cycle_template_impl const decltype(cycle_f()) debounce_cycle> bool Rotary<__cycle_template_args debounce_cycle>::update (bool pressed1, bool pressed2) { bool b1u = b1.update(pressed1); bool b2u = b2.update(pressed2); if (b1u && b2.isOff() && b1.pressed()) { rot++; return true; } if (b2u && b1.isOff() && b2.pressed()) { rot--; return true; } return false; } ///////////////////////////////////////////////////////////////