From 72002ce70ecd3018d834abe6c9ed088dc295693b Mon Sep 17 00:00:00 2001 From: mknjc Date: Sun, 10 Jan 2021 20:05:53 +0100 Subject: [PATCH] Rotary Encoder: Don't call callbacks in the isr (#1456) --- .../rotary_encoder/rotary_encoder.cpp | 51 ++++++++++++++++++- .../rotary_encoder/rotary_encoder.h | 13 +++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 873aaf1971..0398489dca 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -90,16 +90,34 @@ void ICACHE_RAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensor if (arg->pin_b->digital_read()) input_state |= STATE_PIN_B_HIGH; + int8_t rotation_dir = 0; uint16_t new_state = STATE_LOOKUP_TABLE[input_state]; if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) { if (arg->counter < arg->max_value) arg->counter++; - arg->on_clockwise_callback_.call(); + rotation_dir = 1; } if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) { if (arg->counter > arg->min_value) arg->counter--; - arg->on_anticlockwise_callback_.call(); + rotation_dir = -1; + } + + if (rotation_dir != 0) { + auto first_zero = std::find(arg->rotation_events.begin(), arg->rotation_events.end(), 0); // find first zero + if (first_zero == arg->rotation_events.begin() // are we at the start (first event this loop iteration) + || std::signbit(*std::prev(first_zero)) != + std::signbit(rotation_dir) // or is the last stored event the wrong direction + || *std::prev(first_zero) == std::numeric_limits::lowest() // or the last event slot is full (negative) + || *std::prev(first_zero) == std::numeric_limits::max()) { // or the last event slot is full (positive) + if (first_zero != arg->rotation_events.end()) { // we have a free rotation slot + *first_zero += rotation_dir; // store the rotation into a new slot + } else { + arg->rotation_events_overflow = true; + } + } else { + *std::prev(first_zero) += rotation_dir; // store the rotation into the previous slot + } } arg->state = new_state; @@ -137,6 +155,35 @@ void RotaryEncoderSensor::dump_config() { } } void RotaryEncoderSensor::loop() { + std::array rotation_events; + bool rotation_events_overflow; + ets_intr_lock(); + rotation_events = this->store_.rotation_events; + rotation_events_overflow = this->store_.rotation_events_overflow; + + this->store_.rotation_events.fill(0); + this->store_.rotation_events_overflow = false; + ets_intr_unlock(); + + if (rotation_events_overflow) { + ESP_LOGW(TAG, "Captured more rotation events than expected"); + } + + for (auto events : rotation_events) { + if (events == 0) // we are at the end of the recorded events + break; + + if (events > 0) { + while (events--) { + this->on_clockwise_callback_.call(); + } + } else { + while (events++) { + this->on_anticlockwise_callback_.call(); + } + } + } + if (this->pin_i_ != nullptr && this->pin_i_->digital_read()) { this->store_.counter = 0; } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index e066188f22..000350d66c 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include "esphome/core/automation.h" @@ -27,8 +29,8 @@ struct RotaryEncoderSensorStore { int32_t last_read{0}; uint8_t state{0}; - CallbackManager on_clockwise_callback_; - CallbackManager on_anticlockwise_callback_; + std::array rotation_events{}; + bool rotation_events_overflow{false}; static void gpio_intr(RotaryEncoderSensorStore *arg); }; @@ -66,11 +68,11 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { float get_setup_priority() const override; void add_on_clockwise_callback(std::function callback) { - this->store_.on_clockwise_callback_.add(std::move(callback)); + this->on_clockwise_callback_.add(std::move(callback)); } void add_on_anticlockwise_callback(std::function callback) { - this->store_.on_anticlockwise_callback_.add(std::move(callback)); + this->on_anticlockwise_callback_.add(std::move(callback)); } protected: @@ -79,6 +81,9 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. RotaryEncoderSensorStore store_{}; + + CallbackManager on_clockwise_callback_; + CallbackManager on_anticlockwise_callback_; }; template class RotaryEncoderSetValueAction : public Action {