diff --git a/src/esphome/api/basic_messages.cpp b/src/esphome/api/basic_messages.cpp index 5bf6275a..9a1fda6a 100644 --- a/src/esphome/api/basic_messages.cpp +++ b/src/esphome/api/basic_messages.cpp @@ -9,8 +9,6 @@ ESPHOME_NAMESPACE_BEGIN namespace api { -static const char *TAG = "api.message"; - // Hello bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { switch (field_id) { diff --git a/src/esphome/api/user_services.cpp b/src/esphome/api/user_services.cpp index ce52775c..925f75da 100644 --- a/src/esphome/api/user_services.cpp +++ b/src/esphome/api/user_services.cpp @@ -9,8 +9,6 @@ ESPHOME_NAMESPACE_BEGIN namespace api { -static const char *TAG = "api.UserServices"; - template<> bool ExecuteServiceArgument::get_value() { return this->value_bool_; } template<> int ExecuteServiceArgument::get_value() { return this->value_int_; } template<> float ExecuteServiceArgument::get_value() { return this->value_float_; } diff --git a/src/esphome/display/display.cpp b/src/esphome/display/display.cpp index dc957f4b..7eab301c 100644 --- a/src/esphome/display/display.cpp +++ b/src/esphome/display/display.cpp @@ -293,7 +293,7 @@ void DisplayBuffer::set_pages(std::vector pages) { for (auto *page : pages) page->set_parent(this); - for (int i = 0; i < pages.size() - 1; i++) { + for (uint32_t i = 0; i < pages.size() - 1; i++) { pages[i]->set_next(pages[i + 1]); pages[i + 1]->set_prev(pages[i]); } diff --git a/src/esphome/display/nextion.cpp b/src/esphome/display/nextion.cpp index 01aa0fe7..9126cef3 100644 --- a/src/esphome/display/nextion.cpp +++ b/src/esphome/display/nextion.cpp @@ -13,9 +13,8 @@ static const char *TAG = "display.nextion"; void Nextion::setup() { this->send_command_no_ack(""); - this->ack_(); - this->goto_page("0"); this->send_command_printf("bkcmd=3"); + this->goto_page("0"); } float Nextion::get_setup_priority() const { return setup_priority::POST_HARDWARE; } void Nextion::update() { diff --git a/src/esphome/esphal.cpp b/src/esphome/esphal.cpp index 5fcc67b8..78e2b0bc 100644 --- a/src/esphome/esphal.cpp +++ b/src/esphome/esphal.cpp @@ -3,14 +3,20 @@ #include "esphome/log.h" #ifdef ARDUINO_ARCH_ESP8266 -#include "FunctionalInterrupt.h" -extern "C" void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, void (*)(void*), void* fp, // NOLINT - int mode); +extern "C" { +typedef struct { // NOLINT + void *interruptInfo; // NOLINT + void *functionInfo; // NOLINT +} ArgStructure; + +void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, void (*)(void *), void *fp, // NOLINT + int mode); +}; #endif ESPHOME_NAMESPACE_BEGIN -static const char* TAG = "esphal"; +static const char *TAG = "esphal"; GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) : pin_(pin), @@ -29,8 +35,8 @@ GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) { } -const char* GPIOPin::get_pin_mode_name() const { - const char* mode_s; +const char *GPIOPin::get_pin_mode_name() const { + const char *mode_s; switch (this->mode_) { case INPUT: mode_s = "INPUT"; @@ -114,6 +120,9 @@ void GPIOPin::setup() { this->pin_mode(this->mode_); } bool ICACHE_RAM_ATTR HOT GPIOPin::digital_read() { return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_; } +bool ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_read() { + return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_; +} void ICACHE_RAM_ATTR HOT GPIOPin::digital_write(bool value) { #ifdef ARDUINO_ARCH_ESP8266 if (this->pin_ != 16) { @@ -138,7 +147,59 @@ void ICACHE_RAM_ATTR HOT GPIOPin::digital_write(bool value) { } #endif } -GPIOPin* GPIOPin::copy() const { return new GPIOPin(*this); } +void ISRInternalGPIOPin::digital_write(bool value) { +#ifdef ARDUINO_ARCH_ESP8266 + if (this->pin_ != 16) { + if (value != this->inverted_) { + GPOS = this->gpio_mask_; + } else { + GPOC = this->gpio_mask_; + } + } else { + if (value != this->inverted_) { + GP16O |= 1; + } else { + GP16O &= ~1; + } + } +#endif +#ifdef ARDUINO_ARCH_ESP32 + if (value != this->inverted_) { + (*this->gpio_set_) = this->gpio_mask_; + } else { + (*this->gpio_clear_) = this->gpio_mask_; + } +#endif +} +ISRInternalGPIOPin::ISRInternalGPIOPin(uint8_t pin, +#ifdef ARDUINO_ARCH_ESP32 + volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set, +#endif + volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted) + : pin_(pin), + gpio_read_(gpio_read), + gpio_mask_(gpio_mask), + inverted_(inverted) +#ifdef ARDUINO_ARCH_ESP32 + , + gpio_clear_(gpio_clear), + gpio_set_(gpio_set) +#endif +{ +} +void ICACHE_RAM_ATTR ISRInternalGPIOPin::clear_interrupt() { +#ifdef ARDUINO_ARCH_ESP8266 + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, this->gpio_mask_); +#endif +#ifdef ARDUINO_ARCH_ESP32 + if (this->pin_ < 32) { + GPIO.status_w1tc = this->gpio_mask_; + } else { + GPIO.status1_w1tc.intr_st = this->gpio_mask_; + } +#endif +} +GPIOPin *GPIOPin::copy() const { return new GPIOPin(*this); } void ICACHE_RAM_ATTR HOT GPIOPin::pin_mode(uint8_t mode) { pinMode(this->pin_, mode); } @@ -147,23 +208,51 @@ GPIOOutputPin::GPIOOutputPin(uint8_t pin, uint8_t mode, bool inverted) : GPIOPin GPIOInputPin::GPIOInputPin(uint8_t pin, uint8_t mode, bool inverted) : GPIOPin(pin, mode, inverted) {} #ifdef ARDUINO_ARCH_ESP8266 -void ICACHE_RAM_ATTR custom_interrupt_functional(void* arg) { - ArgStructure* local_arg = (ArgStructure*) arg; - if (local_arg->functionInfo->reqFunction) { - local_arg->functionInfo->reqFunction(); - } -} +struct ESPHomeInterruptFuncInfo { + void (*func)(void *); + void *arg; +}; -void ICACHE_RAM_ATTR attach_functional_interrupt(uint8_t pin, std::function func, int mode) { - FunctionInfo* fi = new FunctionInfo; - fi->reqFunction = func; +void ICACHE_RAM_ATTR interrupt_handler(void *arg) { + ArgStructure *as = static_cast(arg); + auto *info = static_cast(as->functionInfo); + info->func(info->arg); +} +#endif - ArgStructure* as = new ArgStructure; +void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const { + if (this->inverted_) { + if (mode == RISING) { + mode = FALLING; + } else if (mode == FALLING) { + mode = RISING; + } + } +#ifdef ARDUINO_ARCH_ESP8266 + ArgStructure *as = new ArgStructure; as->interruptInfo = nullptr; - as->functionInfo = fi; - __attachInterruptArg(pin, custom_interrupt_functional, as, mode); + as->functionInfo = new ESPHomeInterruptFuncInfo{ + .func = func, + .arg = arg, + }; + + __attachInterruptArg(this->pin_, interrupt_handler, as, mode); +#endif +#ifdef ARDUINO_ARCH_ESP32 + // work around issue https://github.com/espressif/arduino-esp32/pull/1776 in arduino core + // yet again proves how horrible code is there :( - how could that have been accepted... + auto *attach = reinterpret_cast(attachInterruptArg); + attach(this->pin_, func, arg, mode); +#endif } + +ISRInternalGPIOPin *GPIOPin::to_isr() const { + return new ISRInternalGPIOPin(this->pin_, +#ifdef ARDUINO_ARCH_ESP32 + this->gpio_clear_, this->gpio_set_, #endif + this->gpio_read_, this->gpio_mask_, this->inverted_); +} ESPHOME_NAMESPACE_END diff --git a/src/esphome/esphal.h b/src/esphome/esphal.h index e95e6f7c..3076501f 100644 --- a/src/esphome/esphal.h +++ b/src/esphome/esphal.h @@ -29,6 +29,29 @@ ESPHOME_NAMESPACE_BEGIN #define LOG_PIN_PATTERN "GPIO%u (Mode: %s%s)" #define LOG_PIN_ARGS(pin) (pin)->get_pin(), (pin)->get_pin_mode_name(), ((pin)->is_inverted() ? ", INVERTED" : "") +/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) +class ISRInternalGPIOPin { + public: + ISRInternalGPIOPin(uint8_t pin, +#ifdef ARDUINO_ARCH_ESP32 + volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set, +#endif + volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted); + bool digital_read(); + void digital_write(bool value); + void clear_interrupt(); + + protected: + const uint8_t pin_; +#ifdef ARDUINO_ARCH_ESP32 + volatile uint32_t *const gpio_clear_; + volatile uint32_t *const gpio_set_; +#endif + volatile uint32_t *const gpio_read_; + const uint32_t gpio_mask_; + const bool inverted_; +}; + /** A high-level abstraction class that can expose a pin together with useful options like pinMode. * * Set the parameters for this at construction time and use setup() to apply them. The inverted parameter will @@ -65,7 +88,13 @@ class GPIOPin { /// Return whether this pin shall be treated as inverted. (for example active-low) bool is_inverted() const; + template void attach_interrupt(void (*func)(T *), T *arg, int mode) const; + + ISRInternalGPIOPin *to_isr() const; + protected: + void attach_interrupt_(void (*func)(void *), void *arg, int mode) const; + const uint8_t pin_; const uint8_t mode_; #ifdef ARDUINO_ARCH_ESP32 @@ -77,10 +106,6 @@ class GPIOPin { const bool inverted_; }; -#ifdef ARDUINO_ARCH_ESP8266 -void attach_functional_interrupt(uint8_t pin, std::function func, int mode); -#endif - /** Basically just a GPIOPin, but defaults to OUTPUT pinMode. * * Note that theoretically you can still assign an INPUT pinMode to this - we intentionally don't check this. @@ -103,6 +128,10 @@ class GPIOInputPin : public GPIOPin { GPIOInputPin(uint8_t pin, uint8_t mode = INPUT, bool inverted = false); // NOLINT }; +template void GPIOPin::attach_interrupt(void (*func)(T *), T *arg, int mode) const { + this->attach_interrupt_(reinterpret_cast(func), arg, mode); +} + ESPHOME_NAMESPACE_END #endif // ESPHOME_ESPHAL_H diff --git a/src/esphome/light/mqtt_json_light_component.cpp b/src/esphome/light/mqtt_json_light_component.cpp index 9283ac2c..e657cce7 100644 --- a/src/esphome/light/mqtt_json_light_component.cpp +++ b/src/esphome/light/mqtt_json_light_component.cpp @@ -46,7 +46,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover effect_list.add(effect->get_name()); effect_list.add("None"); } - config.platform = "mqtt_json"; } bool MQTTJSONLightComponent::send_initial_state() { return this->publish_state_(); } bool MQTTJSONLightComponent::is_internal() { return this->state_->is_internal(); } diff --git a/src/esphome/mqtt/mqtt_component.cpp b/src/esphome/mqtt/mqtt_component.cpp index d2dd6dac..93eb37b0 100644 --- a/src/esphome/mqtt/mqtt_component.cpp +++ b/src/esphome/mqtt/mqtt_component.cpp @@ -73,14 +73,11 @@ bool MQTTComponent::send_discovery_() { SendDiscoveryConfig config; config.state_topic = true; config.command_topic = true; - config.platform = "mqtt"; this->send_discovery(root, config); std::string name = this->friendly_name(); root["name"] = name; - if (strcmp(config.platform, "mqtt") != 0) - root["platform"] = config.platform; if (config.state_topic) root["state_topic"] = this->get_state_topic_(); if (config.command_topic) diff --git a/src/esphome/mqtt/mqtt_component.h b/src/esphome/mqtt/mqtt_component.h index 0eb3a802..bd99be29 100644 --- a/src/esphome/mqtt/mqtt_component.h +++ b/src/esphome/mqtt/mqtt_component.h @@ -14,9 +14,8 @@ namespace mqtt { /// Simple Helper struct used for Home Assistant MQTT send_discovery(). struct SendDiscoveryConfig { - bool state_topic{true}; ///< If the state topic should be included. Defaults to true. - bool command_topic{true}; ///< If the command topic should be included. Default to true. - const char *platform{"mqtt"}; ///< The platform of this component. Defaults to "mqtt". + bool state_topic{true}; ///< If the state topic should be included. Defaults to true. + bool command_topic{true}; ///< If the command topic should be included. Default to true. }; #define LOG_MQTT_COMPONENT(state_topic, command_topic) \ diff --git a/src/esphome/remote/remote_receiver.cpp b/src/esphome/remote/remote_receiver.cpp index 5d16d5be..b76a8a80 100644 --- a/src/esphome/remote/remote_receiver.cpp +++ b/src/esphome/remote/remote_receiver.cpp @@ -246,42 +246,47 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { #ifdef ARDUINO_ARCH_ESP8266 -void ICACHE_RAM_ATTR HOT RemoteReceiverComponent::gpio_intr_() { +void ICACHE_RAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { const uint32_t now = micros(); // If the lhs is 1 (rising edge) we should write to an uneven index and vice versa - const uint32_t next = (this->buffer_write_at_ + 1) % this->buffer_size_; - if (uint32_t(this->pin_->digital_read()) != next % 2) + const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size; + if (uint32_t(arg->pin->digital_read()) != next % 2) return; - const uint32_t last_change = this->buffer_[this->buffer_write_at_]; - if (now - last_change <= this->filter_us_) + const uint32_t last_change = arg->buffer[arg->buffer_write_at]; + if (now - last_change <= arg->filter_us) return; - this->buffer_[this->buffer_write_at_ = next] = now; + arg->buffer[arg->buffer_write_at = next] = now; - if (next == this->buffer_read_at_) { - this->overflow_ = true; + if (next == arg->buffer_read_at) { + arg->overflow = true; } } void RemoteReceiverComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); this->pin_->setup(); + auto &s = this->store_; + s.filter_us = this->filter_us_; + s.pin = this->pin_->to_isr(); + s.buffer_size = this->buffer_size_; + this->high_freq_.start(); - if (this->buffer_size_ % 2 != 0) { + if (s.buffer_size % 2 != 0) { // Make sure divisible by two. This way, we know that every 0bxxx0 index is a space and every 0bxxx1 index is a mark - this->buffer_size_++; + s.buffer_size++; } - this->buffer_ = new uint32_t[this->buffer_size_]; + s.buffer = new uint32_t[s.buffer_size]; // First index is a space. if (this->pin_->digital_read()) { - this->buffer_write_at_ = this->buffer_read_at_ = 1; - this->buffer_[1] = 0; - this->buffer_[0] = 0; + s.buffer_write_at = s.buffer_read_at = 1; + s.buffer[1] = 0; + s.buffer[0] = 0; } else { - this->buffer_write_at_ = this->buffer_read_at_ = 0; - this->buffer_[0] = 0; + s.buffer_write_at = s.buffer_read_at = 0; + s.buffer[0] = 0; } - attach_functional_interrupt(this->pin_->get_pin(), [this]() ICACHE_RAM_ATTR { this->gpio_intr_(); }, CHANGE); + this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, CHANGE); } void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); @@ -301,21 +306,22 @@ void RemoteReceiverComponent::dump_config() { } void RemoteReceiverComponent::loop() { - if (this->overflow_) { - this->buffer_read_at_ = this->buffer_write_at_; - this->overflow_ = false; + auto &s = this->store_; + if (s.overflow) { + s.buffer_read_at = s.buffer_write_at; + s.overflow = false; ESP_LOGW(TAG, "Data is coming in too fast! Try increasing the buffer size."); return; } // copy write at to local variables, as it's volatile - const uint32_t write_at = this->buffer_write_at_; - const uint32_t dist = (this->buffer_size_ + write_at - this->buffer_read_at_) % this->buffer_size_; + const uint32_t write_at = s.buffer_write_at; + const uint32_t dist = (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; // signals must at least one rising and one leading edge if (dist <= 1) return; const uint32_t now = micros(); - if (now - this->buffer_[write_at] < this->idle_us_) + if (now - s.buffer[write_at] < this->idle_us_) // The last change was fewer than the configured idle time ago. // TODO: Handle case when loop() is not called quickly enough to catch idle return; @@ -324,16 +330,16 @@ void RemoteReceiverComponent::loop() { this->buffer_[write_at]); // Skip first value, it's from the previous idle level - this->buffer_read_at_ = (this->buffer_read_at_ + 1) % this->buffer_size_; - uint32_t prev = this->buffer_read_at_; - this->buffer_read_at_ = (this->buffer_read_at_ + 1) % this->buffer_size_; - const uint32_t reserve_size = 1 + (this->buffer_size_ + write_at - this->buffer_read_at_) % this->buffer_size_; + s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; + uint32_t prev = s.buffer_read_at; + s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; + const uint32_t reserve_size = 1 + (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; this->temp_.clear(); this->temp_.reserve(reserve_size); - int32_t multiplier = this->buffer_read_at_ % 2 == 0 ? 1 : -1; + int32_t multiplier = s.buffer_read_at % 2 == 0 ? 1 : -1; for (uint32_t i = 0; prev != write_at; i++) { - int32_t delta = this->buffer_[this->buffer_read_at_] - this->buffer_[prev]; + int32_t delta = s.buffer[s.buffer_read_at] - s.buffer[prev]; if (uint32_t(delta) >= this->idle_us_) { // already found a space longer than idle. There must have been two pulses break; @@ -342,11 +348,11 @@ void RemoteReceiverComponent::loop() { ESP_LOGVV(TAG, " i=%u buffer[%u]=%u - buffer[%u]=%u -> %d", i, this->buffer_read_at_, this->buffer_[this->buffer_read_at_], prev, this->buffer_[prev], multiplier * delta); this->temp_.push_back(multiplier * delta); - prev = this->buffer_read_at_; - this->buffer_read_at_ = (this->buffer_read_at_ + 1) % this->buffer_size_; + prev = s.buffer_read_at; + s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; multiplier *= -1; } - this->buffer_read_at_ = (this->buffer_size_ + this->buffer_read_at_ - 1) % this->buffer_size_; + s.buffer_read_at = (s.buffer_size + s.buffer_read_at - 1) % s.buffer_size; this->temp_.push_back(this->idle_us_ * multiplier); RemoteReceiveData data(this, &this->temp_); diff --git a/src/esphome/remote/remote_receiver.h b/src/esphome/remote/remote_receiver.h index 598c7a53..dbb307cd 100644 --- a/src/esphome/remote/remote_receiver.h +++ b/src/esphome/remote/remote_receiver.h @@ -121,6 +121,23 @@ class RemoteReceiveDumper { virtual bool is_secondary(); }; +struct RemoteReceiverComponentStore { + static void gpio_intr(RemoteReceiverComponentStore *arg); + + /// Stores the time (in micros) that the leading/falling edge happened at + /// * An even index means a falling edge appeared at the time stored at the index + /// * An uneven index means a rising edge appeared at the time stored at the index + volatile uint32_t *buffer{nullptr}; + /// The position last written to + volatile uint32_t buffer_write_at; + /// The position last read from + uint32_t buffer_read_at{0}; + bool overflow{false}; + uint32_t buffer_size{1000}; + uint8_t filter_us{10}; + ISRInternalGPIOPin *pin; +}; + class RemoteReceiverComponent : public RemoteControlComponentBase, public Component { public: explicit RemoteReceiverComponent(GPIOPin *pin); @@ -151,16 +168,7 @@ class RemoteReceiverComponent : public RemoteControlComponentBase, public Compon RingbufHandle_t ringbuf_; #endif #ifdef ARDUINO_ARCH_ESP8266 - /// Stores the time (in micros) that the leading/falling edge happened at - /// * An even index means a falling edge appeared at the time stored at the index - /// * An uneven index means a rising edge appeared at the time stored at the index - volatile uint32_t *buffer_{nullptr}; - /// The position last written to - volatile uint32_t buffer_write_at_; - /// The position last read from - uint32_t buffer_read_at_{0}; - bool overflow_{false}; - void gpio_intr_(); + RemoteReceiverComponentStore store_; #endif #ifdef ARDUINO_ARCH_ESP32 diff --git a/src/esphome/sensor/duty_cycle_sensor.cpp b/src/esphome/sensor/duty_cycle_sensor.cpp index 7049ce07..dbd7f3cc 100644 --- a/src/esphome/sensor/duty_cycle_sensor.cpp +++ b/src/esphome/sensor/duty_cycle_sensor.cpp @@ -14,68 +14,38 @@ static const char *TAG = "sensor.duty_cycle"; DutyCycleSensor::DutyCycleSensor(const std::string &name, GPIOPin *pin, uint32_t update_interval) : PollingSensorComponent(name, update_interval), pin_(pin) {} -DutyCycleSensor *duty_cycle_sensors = nullptr; - -void ICACHE_RAM_ATTR HOT DutyCycleSensor::gpio_intr() { - DutyCycleSensor *sensor = duty_cycle_sensors; - while (sensor != nullptr) { - sensor->on_interrupt(); - sensor = sensor->next_; - } -} void DutyCycleSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up Duty Cycle Sensor '%s'...", this->get_name().c_str()); this->pin_->setup(); - this->last_level_ = this->pin_->digital_read(); - - disable_interrupts(); - if (duty_cycle_sensors == nullptr) { - duty_cycle_sensors = this; - } else { - DutyCycleSensor *last_sensor = duty_cycle_sensors; - while (last_sensor->next_ != nullptr) { - last_sensor = last_sensor->next_; - } - last_sensor->next_ = this; - } - enable_interrupts(); - - attachInterrupt(this->pin_->get_pin(), gpio_intr, CHANGE); + this->store_.pin = this->pin_->to_isr(); + this->store_.last_level = this->pin_->digital_read(); + this->last_update_ = micros(); + + this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); } void DutyCycleSensor::dump_config() { LOG_SENSOR("", "Duty Cycle Sensor", this); LOG_PIN(" Pin: ", this->pin_); LOG_UPDATE_INTERVAL(this); } -void ICACHE_RAM_ATTR HOT DutyCycleSensor::on_interrupt() { - const bool new_level = this->pin_->digital_read(); - if (new_level == this->last_level_) - return; - this->last_level_ = new_level; - const uint32_t now = micros(); - - if (!new_level) - this->on_time_ += now - this->last_interrupt_; - - this->last_interrupt_ = now; -} void DutyCycleSensor::update() { const uint32_t now = micros(); - const bool level = this->last_level_; - const uint32_t last_interrupt = this->last_interrupt_; - uint32_t on_time = this->on_time_; + const bool level = this->store_.last_level; + const uint32_t last_interrupt = this->store_.last_interrupt; + uint32_t on_time = this->store_.on_time; if (level) on_time += now - last_interrupt; - const float total_time = this->get_update_interval() * 1000.0f; + const float total_time = float(now - this->last_update_); const float value = (on_time / total_time) * 100.0f; ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); this->publish_state(value); - this->on_time_ = 0; - this->last_interrupt_ = now; + this->store_.on_time = 0; + this->store_.last_interrupt = now; + this->last_update_ = now; } std::string DutyCycleSensor::unit_of_measurement() { return "%"; } @@ -83,6 +53,19 @@ std::string DutyCycleSensor::icon() { return "mdi:percent"; } int8_t DutyCycleSensor::accuracy_decimals() { return 1; } float DutyCycleSensor::get_setup_priority() const { return setup_priority::HARDWARE_LATE; } +void ICACHE_RAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) { + const bool new_level = arg->pin->digital_read(); + if (new_level == arg->last_level) + return; + arg->last_level = new_level; + const uint32_t now = micros(); + + if (!new_level) + arg->on_time += now - arg->last_interrupt; + + arg->last_interrupt = now; +} + } // namespace sensor ESPHOME_NAMESPACE_END diff --git a/src/esphome/sensor/duty_cycle_sensor.h b/src/esphome/sensor/duty_cycle_sensor.h index 9b132ef1..78644730 100644 --- a/src/esphome/sensor/duty_cycle_sensor.h +++ b/src/esphome/sensor/duty_cycle_sensor.h @@ -11,6 +11,16 @@ ESPHOME_NAMESPACE_BEGIN namespace sensor { +/// Store data in a class that doesn't use multiple-inheritance (vtables in flash) +struct DutyCycleSensorStore { + volatile uint32_t last_interrupt{0}; + volatile uint32_t on_time{0}; + volatile bool last_level{false}; + ISRInternalGPIOPin *pin; + + static void gpio_intr(DutyCycleSensorStore *arg); +}; + class DutyCycleSensor : public PollingSensorComponent { public: DutyCycleSensor(const std::string &name, GPIOPin *pin, uint32_t update_interval = 60000); @@ -23,19 +33,12 @@ class DutyCycleSensor : public PollingSensorComponent { std::string icon() override; int8_t accuracy_decimals() override; - void on_interrupt(); - - static void gpio_intr(); - protected: GPIOPin *pin_; - volatile uint32_t last_interrupt_{0}; - volatile uint32_t on_time_{0}; - volatile bool last_level_{false}; - DutyCycleSensor *next_; -}; -extern DutyCycleSensor *duty_cycle_sensors; + DutyCycleSensorStore store_; + uint32_t last_update_; +}; } // namespace sensor diff --git a/src/esphome/sensor/pulse_counter.cpp b/src/esphome/sensor/pulse_counter.cpp index 21d8bf94..7d8f0d36 100644 --- a/src/esphome/sensor/pulse_counter.cpp +++ b/src/esphome/sensor/pulse_counter.cpp @@ -35,28 +35,29 @@ const char *EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; GPIOPin *PulseCounterBase::get_pin() { return this->pin_; } #ifdef ARDUINO_ARCH_ESP8266 -void ICACHE_RAM_ATTR HOT PulseCounterBase::gpio_intr_() { +void ICACHE_RAM_ATTR PulseCounterBase::gpio_intr(PulseCounterBase *arg) { const uint32_t now = micros(); - const bool discard = now - this->last_pulse_ < this->filter_us_; - this->last_pulse_ = now; + const bool discard = now - arg->last_pulse_ < arg->filter_us_; + arg->last_pulse_ = now; if (discard) return; - PulseCounterCountMode mode = this->pin_->digital_read() ? this->rising_edge_mode_ : this->falling_edge_mode_; + PulseCounterCountMode mode = arg->isr_pin_->digital_read() ? arg->rising_edge_mode_ : arg->falling_edge_mode_; switch (mode) { case PULSE_COUNTER_DISABLE: break; case PULSE_COUNTER_INCREMENT: - this->counter_++; + arg->counter_++; break; case PULSE_COUNTER_DECREMENT: - this->counter_--; + arg->counter_--; break; } } bool PulseCounterBase::pulse_counter_setup() { this->pin_->setup(); - attach_functional_interrupt(this->pin_->get_pin(), [this]() ICACHE_RAM_ATTR { this->gpio_intr_(); }, CHANGE); + this->isr_pin_ = this->pin_->to_isr(); + this->pin_->attach_interrupt(PulseCounterBase::gpio_intr, this, CHANGE); return true; } pulse_counter_t PulseCounterBase::read_raw_value() { diff --git a/src/esphome/sensor/pulse_counter.h b/src/esphome/sensor/pulse_counter.h index 11200110..d8cd1510 100644 --- a/src/esphome/sensor/pulse_counter.h +++ b/src/esphome/sensor/pulse_counter.h @@ -39,7 +39,7 @@ class PulseCounterBase { protected: #ifdef ARDUINO_ARCH_ESP8266 - void gpio_intr_(); + static void gpio_intr(PulseCounterBase *arg); volatile pulse_counter_t counter_{0}; volatile uint32_t last_pulse_{0}; #endif @@ -47,6 +47,9 @@ class PulseCounterBase { GPIOPin *pin_; #ifdef ARDUINO_ARCH_ESP32 pcnt_unit_t pcnt_unit_; +#endif +#ifdef ARDUINO_ARCH_ESP8266 + ISRInternalGPIOPin *isr_pin_; #endif PulseCounterCountMode rising_edge_mode_{PULSE_COUNTER_INCREMENT}; PulseCounterCountMode falling_edge_mode_{PULSE_COUNTER_DISABLE}; diff --git a/src/esphome/sensor/rotary_encoder.cpp b/src/esphome/sensor/rotary_encoder.cpp index d0f42093..94b9c2c5 100644 --- a/src/esphome/sensor/rotary_encoder.cpp +++ b/src/esphome/sensor/rotary_encoder.cpp @@ -11,130 +11,137 @@ namespace sensor { static const char *TAG = "sensor.rotary_encoder"; -// copied from https://github.com/jkDesignDE/MechInputs/blob/master/QEIx4.cpp -static const uint16_t QE_IX4_MASK = 0x1C; -static const uint16_t QE_IX4_1X_INC = 0x0100; -static const uint16_t QE_IX4_2X_INC = 0x0200; -static const uint16_t QE_IX4_4X_INC = 0x0400; -static const uint16_t QE_IX4_1X_DEC = 0x1000; -static const uint16_t QE_IX4_2X_DEC = 0x2000; -static const uint16_t QE_IX4_4X_DEC = 0x4000; -static const uint16_t QE_IX4_DIR = 0x20; -static const uint16_t QE_IX4_A = 1; -static const uint16_t QE_IX4_B = 2; -static const uint16_t QE_IX4_AB = 3; -static const uint16_t QE_IX4_S0 = 0x0; -static const uint16_t QE_IX4_S1 = 0x4; -static const uint16_t QE_IX4_S2 = 0x8; -static const uint16_t QE_IX4_S3 = 0xC; -static const uint16_t QE_IX4_CCW = 0; -static const uint16_t QE_IX4_CW = 0x10; -static const uint16_t QE_IX4_IS_CHG = 0x7700; -static const uint16_t QE_IX4_IS_INC = 0x0700; -static const uint16_t QE_IX4_IS_DEC = 0x7000; - -static uint16_t state_lookup_table[32] = { +// based on https://github.com/jkDesignDE/MechInputs/blob/master/QEIx4.cpp +static const uint8_t STATE_LUT_MASK = 0x1C; // clears upper counter increment/decrement bits and pin states +static const uint16_t STATE_PIN_A_HIGH = 0x01; +static const uint16_t STATE_PIN_B_HIGH = 0x02; +static const uint16_t STATE_S0 = 0x00; +static const uint16_t STATE_S1 = 0x04; +static const uint16_t STATE_S2 = 0x08; +static const uint16_t STATE_S3 = 0x0C; +static const uint16_t STATE_CCW = 0x00; +static const uint16_t STATE_CW = 0x10; +static const uint16_t STATE_HAS_INCREMENTED = 0x0700; +static const uint16_t STATE_INCREMENT_COUNTER_4_2_1 = 0x0700; +static const uint16_t STATE_INCREMENT_COUNTER_4_2 = 0x0300; +static const uint16_t STATE_INCREMENT_COUNTER_4 = 0x0100; +static const uint16_t STATE_HAS_DECREMENTED = 0x7000; +static const uint16_t STATE_DECREMENT_COUNTER_4_2_1 = 0x7000; +static const uint16_t STATE_DECREMENT_COUNTER_4_2 = 0x3000; +static const uint16_t STATE_DECREMENT_COUNTER_4 = 0x1000; + +// State explanation: 8-bit uint +// Bit 0 (0x01) encodes Pin A HIGH/LOW (reset before each read) +// Bit 1 (0x02) encodes Pin B HIGH/LOW (reset before each read) +// Bit 2&3 (0x0C) encodes state S0-S3 +// Bit 4 (0x10) encodes clockwise/counter-clockwise rotation + +static const uint16_t STATE_LOOKUP_TABLE[32] = { // act state S0 in CCW direction - QE_IX4_CCW | QE_IX4_S0, QE_IX4_CW | QE_IX4_S1 | QE_IX4_A | QE_IX4_4X_INC | QE_IX4_DIR, - QE_IX4_CCW | QE_IX4_S0 | QE_IX4_B, QE_IX4_CCW | QE_IX4_S3 | QE_IX4_AB | QE_IX4_1X_DEC, + STATE_CCW | STATE_S0, // 0x00: stay here + STATE_CW | STATE_S1 | STATE_INCREMENT_COUNTER_4, // 0x01: goto CW+S1 and increment counter (dir change) + STATE_CCW | STATE_S0, // 0x02: stay here + STATE_CCW | STATE_S3 | STATE_DECREMENT_COUNTER_4_2_1, // 0x03: goto CCW+S3 and decrement counter // act state S1 in CCW direction - QE_IX4_CCW | QE_IX4_S1, QE_IX4_CCW | QE_IX4_S1 | QE_IX4_A, QE_IX4_CCW | QE_IX4_S0 | QE_IX4_B | QE_IX4_4X_DEC, - QE_IX4_CW | QE_IX4_S2 | QE_IX4_AB | QE_IX4_1X_INC | QE_IX4_DIR, + STATE_CCW | STATE_S1, // 0x04: stay here + STATE_CCW | STATE_S1, // 0x05: stay here + STATE_CCW | STATE_S0 | STATE_DECREMENT_COUNTER_4, // 0x06: goto CCW+S0 and decrement counter + STATE_CW | STATE_S2 | STATE_INCREMENT_COUNTER_4_2_1, // 0x07: goto CW+S2 and increment counter (dir change) // act state S2 in CCW direction - QE_IX4_CCW | QE_IX4_S1 | QE_IX4_2X_DEC, QE_IX4_CCW | QE_IX4_S2 | QE_IX4_A, - QE_IX4_CW | QE_IX4_S3 | QE_IX4_B | QE_IX4_4X_INC | QE_IX4_DIR, QE_IX4_CCW | QE_IX4_S2 | QE_IX4_AB, + STATE_CCW | STATE_S1 | STATE_DECREMENT_COUNTER_4_2, // 0x08: goto CCW+S1 and decrement counter + STATE_CCW | STATE_S2, // 0x09: stay here + STATE_CW | STATE_S3 | STATE_INCREMENT_COUNTER_4, // 0x0A: goto CW+S3 and increment counter (dir change) + STATE_CCW | STATE_S2, // 0x0B: stay here // act state S3 in CCW direction - QE_IX4_CW | QE_IX4_S0 | QE_IX4_2X_INC | QE_IX4_DIR, QE_IX4_CCW | QE_IX4_S2 | QE_IX4_A | QE_IX4_4X_DEC, - QE_IX4_CCW | QE_IX4_S3 | QE_IX4_B, QE_IX4_CCW | QE_IX4_S3 | QE_IX4_AB, + STATE_CW | STATE_S0 | STATE_INCREMENT_COUNTER_4_2, // 0x0C: goto CW+S0 and increment counter (dir change) + STATE_CCW | STATE_S2 | STATE_DECREMENT_COUNTER_4, // 0x0D: goto CCW+S2 and decrement counter + STATE_CCW | STATE_S3, // 0x0E: stay here + STATE_CCW | STATE_S3, // 0x0F: stay here // act state S0 in CW direction - QE_IX4_CW | QE_IX4_S0, QE_IX4_CW | QE_IX4_S1 | QE_IX4_A | QE_IX4_4X_INC, QE_IX4_CW | QE_IX4_S0 | QE_IX4_B, - QE_IX4_CCW | QE_IX4_S3 | QE_IX4_AB | QE_IX4_1X_DEC | QE_IX4_DIR, + STATE_CW | STATE_S0, // 0x10: stay here + STATE_CW | STATE_S1 | STATE_INCREMENT_COUNTER_4, // 0x11: goto CW+S1 and increment counter + STATE_CW | STATE_S0, // 0x12: stay here + STATE_CCW | STATE_S3 | STATE_DECREMENT_COUNTER_4_2_1, // 0x13: goto CCW+S3 and decrement counter (dir change) // act state S1 in CW direction - QE_IX4_CW | QE_IX4_S1, QE_IX4_CW | QE_IX4_S1 | QE_IX4_A, - QE_IX4_CCW | QE_IX4_S0 | QE_IX4_B | QE_IX4_4X_DEC | QE_IX4_DIR, QE_IX4_CW | QE_IX4_S2 | QE_IX4_AB | QE_IX4_1X_INC, + STATE_CW | STATE_S1, // 0x14: stay here + STATE_CW | STATE_S1, // 0x15: stay here + STATE_CCW | STATE_S0 | STATE_DECREMENT_COUNTER_4, // 0x16: goto CCW+S0 and decrement counter (dir change) + STATE_CW | STATE_S2 | STATE_INCREMENT_COUNTER_4_2_1, // 0x17: goto CW+S2 and increment counter // act state S2 in CW direction - QE_IX4_CCW | QE_IX4_S1 | QE_IX4_2X_DEC | QE_IX4_DIR, QE_IX4_CW | QE_IX4_S2 | QE_IX4_A, - QE_IX4_CW | QE_IX4_S3 | QE_IX4_B | QE_IX4_4X_INC, QE_IX4_CW | QE_IX4_S2 | QE_IX4_AB, + STATE_CCW | STATE_S1 | STATE_DECREMENT_COUNTER_4_2, // 0x18: goto CCW+S1 and decrement counter (dir change) + STATE_CW | STATE_S2, // 0x19: stay here + STATE_CW | STATE_S3 | STATE_INCREMENT_COUNTER_4, // 0x1A: goto CW+S3 and increment counter + STATE_CW | STATE_S2, // act state S3 in CW direction - QE_IX4_CW | QE_IX4_S0 | QE_IX4_2X_INC, QE_IX4_CCW | QE_IX4_S2 | QE_IX4_A | QE_IX4_4X_DEC | QE_IX4_DIR, - QE_IX4_CW | QE_IX4_S3 | QE_IX4_B, QE_IX4_CW | QE_IX4_S3 | QE_IX4_AB}; + STATE_CW | STATE_S0 | STATE_INCREMENT_COUNTER_4_2, // 0x1C: goto CW+S0 and increment counter + STATE_CCW | STATE_S2 | STATE_DECREMENT_COUNTER_4, // 0x1D: goto CCW+S2 and decrement counter (dir change) + STATE_CW | STATE_S3, // 0x1E: stay here + STATE_CW | STATE_S3 // 0x1F: stay here +}; + +void ICACHE_RAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore *arg) { + // Forget upper bits and add pin states + uint8_t input_state = arg->state & STATE_LUT_MASK; + if (arg->pin_a->digital_read()) + input_state |= STATE_PIN_A_HIGH; + if (arg->pin_b->digital_read()) + input_state |= STATE_PIN_B_HIGH; + + uint8_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++; + } + if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) { + if (arg->counter > arg->min_value) + arg->counter--; + } + arg->state = new_state; +} RotaryEncoderSensor::RotaryEncoderSensor(const std::string &name, GPIOPin *pin_a, GPIOPin *pin_b) : Sensor(name), Component(), pin_a_(pin_a), pin_b_(pin_b) {} + void RotaryEncoderSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up Rotary Encoder '%s'...", this->name_.c_str()); - int interrupt_a = digitalPinToInterrupt(this->pin_a_->get_pin()); this->pin_a_->setup(); - attachInterrupt(interrupt_a, RotaryEncoderSensor::encoder_isr, CHANGE); + this->store_.pin_a = this->pin_a_->to_isr(); + this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); - int interrupt_b = digitalPinToInterrupt(this->pin_b_->get_pin()); this->pin_b_->setup(); - attachInterrupt(interrupt_b, RotaryEncoderSensor::encoder_isr, CHANGE); + this->store_.pin_b = this->pin_b_->to_isr(); + this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); if (this->pin_i_ != nullptr) { this->pin_i_->setup(); } - global_rotary_encoders_.push_back(this); } - void RotaryEncoderSensor::dump_config() { LOG_SENSOR("", "Rotary Encoder", this); LOG_PIN(" Pin A: ", this->pin_a_); LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin I: ", this->pin_i_); } -void ICACHE_RAM_ATTR RotaryEncoderSensor::encoder_isr() { - for (auto *encoder : global_rotary_encoders_) { - encoder->process_state_machine_(); - } -} -void ICACHE_RAM_ATTR RotaryEncoderSensor::process_state_machine_() { - this->state_ &= QE_IX4_MASK; - if (this->pin_a_->digital_read()) - this->state_ |= QE_IX4_A; - if (this->pin_b_->digital_read()) - this->state_ |= QE_IX4_B; - - this->state_ = state_lookup_table[this->state_]; - this->state_ &= this->resolution_; - - if ((this->state_ & QE_IX4_IS_CHG) != 0) { - bool counter_change = false; - - if ((this->state_ & QE_IX4_IS_INC) != 0) { - this->counter_ = std::min(this->counter_ + 1, this->max_value_); - counter_change = true; - } - if ((this->state_ & QE_IX4_IS_DEC) != 0) { - this->counter_ = std::max(this->counter_ - 1, this->min_value_); - counter_change = true; - } - - if (counter_change && this->pin_i_ != nullptr && this->pin_i_->digital_read()) { - this->counter_ = 0; - } - - this->has_changed_ = this->has_changed_ || counter_change; - } -} void RotaryEncoderSensor::loop() { - if (this->has_changed_) { - this->has_changed_ = false; - this->publish_state(this->counter_); + if (this->pin_i_ != nullptr && this->pin_i_->digital_read()) { + this->store_.counter = 0; + } + int counter = this->store_.counter; + if (this->store_.last_read != counter) { + this->store_.last_read = counter; + this->publish_state(counter); } } std::string RotaryEncoderSensor::unit_of_measurement() { return "steps"; } std::string RotaryEncoderSensor::icon() { return "mdi:rotate-right"; } int8_t RotaryEncoderSensor::accuracy_decimals() { return 0; } -void RotaryEncoderSensor::set_resolution(RotaryEncoderResolution mode) { this->resolution_ = mode; } void RotaryEncoderSensor::set_reset_pin(const GPIOInputPin &pin_i) { this->pin_i_ = pin_i.copy(); } -std::vector global_rotary_encoders_; - float RotaryEncoderSensor::get_setup_priority() const { return setup_priority::HARDWARE_LATE; } -void RotaryEncoderSensor::set_min_value(int32_t min_value) { this->min_value_ = min_value; } -void RotaryEncoderSensor::set_max_value(int32_t max_value) { this->max_value_ = max_value; } +void RotaryEncoderSensor::set_resolution(RotaryEncoderResolution mode) { this->store_.resolution = mode; } +void RotaryEncoderSensor::set_min_value(int32_t min_value) { this->store_.min_value = min_value; } +void RotaryEncoderSensor::set_max_value(int32_t max_value) { this->store_.max_value = max_value; } } // namespace sensor diff --git a/src/esphome/sensor/rotary_encoder.h b/src/esphome/sensor/rotary_encoder.h index a81c1baf..b38b5511 100644 --- a/src/esphome/sensor/rotary_encoder.h +++ b/src/esphome/sensor/rotary_encoder.h @@ -17,9 +17,23 @@ namespace sensor { /// All possible resolutions for the rotary encoder enum RotaryEncoderResolution { ROTARY_ENCODER_1_PULSE_PER_CYCLE = - 0x11FF, /// increment counter by 1 with every A-B cycle, slow response but accurate - ROTARY_ENCODER_2_PULSES_PER_CYCLE = 0x33FF, /// increment counter by 2 with every A-B cycle - ROTARY_ENCODER_4_PULSES_PER_CYCLE = 0x77FF, /// increment counter by 4 with every A-B cycle, most inaccurate + 0x1100, /// increment counter by 1 with every A-B cycle, slow response but accurate + ROTARY_ENCODER_2_PULSES_PER_CYCLE = 0x2200, /// increment counter by 2 with every A-B cycle + ROTARY_ENCODER_4_PULSES_PER_CYCLE = 0x4400, /// increment counter by 4 with every A-B cycle, most inaccurate +}; + +struct RotaryEncoderSensorStore { + ISRInternalGPIOPin *pin_a; + ISRInternalGPIOPin *pin_b; + + volatile int32_t counter{0}; + RotaryEncoderResolution resolution{ROTARY_ENCODER_1_PULSE_PER_CYCLE}; + int32_t min_value{INT32_MIN}; + int32_t max_value{INT32_MAX}; + int32_t last_read{0}; + uint8_t state{0}; + + static void gpio_intr(RotaryEncoderSensorStore *arg); }; class RotaryEncoderSensor : public Sensor, public Component { @@ -51,27 +65,13 @@ class RotaryEncoderSensor : public Sensor, public Component { float get_setup_priority() const override; protected: - /// The ISR that handles pushing all interrupts to process_state_machine_ of all rotary encoders. - static void encoder_isr(); - - /// Process the state machine state of this rotary encoder. Called from encoder_isr_ - void process_state_machine_(); - GPIOPin *pin_a_; GPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. - volatile int32_t counter_{0}; /// The internal counter for steps - volatile bool has_changed_{true}; - uint16_t state_{0}; - RotaryEncoderResolution resolution_{ROTARY_ENCODER_1_PULSE_PER_CYCLE}; - int32_t min_value_{INT32_MIN}; - int32_t max_value_{INT32_MAX}; + RotaryEncoderSensorStore store_; }; -/// Global storage for having multiple rotary encoders with a single ISR -extern std::vector global_rotary_encoders_; - } // namespace sensor ESPHOME_NAMESPACE_END diff --git a/src/esphome/sensor/ultrasonic_sensor.cpp b/src/esphome/sensor/ultrasonic_sensor.cpp index 392cfcc3..5e1e76e4 100644 --- a/src/esphome/sensor/ultrasonic_sensor.cpp +++ b/src/esphome/sensor/ultrasonic_sensor.cpp @@ -22,52 +22,60 @@ void UltrasonicSensorComponent::setup() { this->trigger_pin_->setup(); this->trigger_pin_->digital_write(false); } -void UltrasonicSensorComponent::dump_config() { - LOG_SENSOR("", "Ultrasonic Sensor", this); - LOG_PIN(" Echo Pin: ", this->echo_pin_); - LOG_PIN(" Trigger Pin: ", this->trigger_pin_); - ESP_LOGCONFIG(TAG, " Pulse time: %u µs", this->pulse_time_us_); - ESP_LOGCONFIG(TAG, " Timeout: %u µs", this->timeout_us_); - LOG_UPDATE_INTERVAL(this); +void ICACHE_RAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) { + if (arg->has_echo || !arg->has_triggered) + // already have echo or no trigger + return; + + arg->has_echo = true; + arg->echo_at = micros(); } void UltrasonicSensorComponent::update() { this->trigger_pin_->digital_write(true); delayMicroseconds(this->pulse_time_us_); this->trigger_pin_->digital_write(false); - disable_interrupts(); - uint32_t time = pulseIn(this->echo_pin_->get_pin(), uint8_t(!this->echo_pin_->is_inverted()), this->timeout_us_); - enable_interrupts(); - - float result = 0; - if (time == 0) - result = NAN; - else - result = us_to_m(time); - ESP_LOGV(TAG, "Echo took %uµs (%fm)", time, result); + this->store_.has_triggered = true; + this->store_.has_echo = false; + this->store_.triggered_at = micros(); +} +void UltrasonicSensorComponent::loop() { + if (this->store_.has_triggered && this->store_.has_echo) { + this->store_.has_triggered = false; + uint32_t d_us = this->store_.echo_at - this->store_.triggered_at; - this->publish_state(result); + float result = us_to_m(d_us); + ESP_LOGD(TAG, "Got distance: %.1f m", result); + this->publish_state(result); + } + uint32_t now = micros(); + if (this->store_.has_triggered && !this->store_.has_echo && now - this->store_.triggered_at > this->timeout_us_) { + // timeout + this->store_.has_triggered = false; + ESP_LOGD(TAG, "Distance measurement timed out!"); + this->publish_state(NAN); + } +} +void UltrasonicSensorComponent::dump_config() { + LOG_SENSOR("", "Ultrasonic Sensor", this); + LOG_PIN(" Echo Pin: ", this->echo_pin_); + LOG_PIN(" Trigger Pin: ", this->trigger_pin_); + ESP_LOGCONFIG(TAG, " Pulse time: %u µs", this->pulse_time_us_); + ESP_LOGCONFIG(TAG, " Timeout: %u µs", this->timeout_us_); + LOG_UPDATE_INTERVAL(this); } -uint32_t UltrasonicSensorComponent::get_timeout_us() const { return this->timeout_us_; } -void UltrasonicSensorComponent::set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } -void UltrasonicSensorComponent::set_timeout_m(float timeout_m) { this->set_timeout_us(m_to_us(timeout_m)); } float UltrasonicSensorComponent::us_to_m(uint32_t us) { // The ultrasonic sound wave needs to travel both ways. return (SPEED_OF_SOUND_M_PER_US / 2.0f) * us; } -uint32_t UltrasonicSensorComponent::m_to_us(float m) { - // The ultrasonic sound wave needs to travel both ways. - return static_cast(m / (SPEED_OF_SOUND_M_PER_US / 2.0f)); -} -float UltrasonicSensorComponent::get_timeout_m() const { return us_to_m(this->get_timeout_us()); } float UltrasonicSensorComponent::get_setup_priority() const { return setup_priority::HARDWARE_LATE; } -uint32_t UltrasonicSensorComponent::get_pulse_time_us() const { return this->pulse_time_us_; } void UltrasonicSensorComponent::set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; } std::string UltrasonicSensorComponent::unit_of_measurement() { return "m"; } std::string UltrasonicSensorComponent::icon() { return "mdi:arrow-expand-vertical"; } int8_t UltrasonicSensorComponent::accuracy_decimals() { return 2; // cm precision } +void UltrasonicSensorComponent::set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } } // namespace sensor diff --git a/src/esphome/sensor/ultrasonic_sensor.h b/src/esphome/sensor/ultrasonic_sensor.h index 7e986d86..97361264 100644 --- a/src/esphome/sensor/ultrasonic_sensor.h +++ b/src/esphome/sensor/ultrasonic_sensor.h @@ -15,23 +15,15 @@ namespace sensor { /// The speed of sound in air in meters per µs. const float SPEED_OF_SOUND_M_PER_US = 0.000343f; -/** This sensor component allows you to use your ultrasonic distance sensors. - * - * Instances of this component periodically send out a short pulse to an ultrasonic sensor, thus creating an ultrasonic - * sound, and check how long it takes until an "echo" comes back. With this we can measure the distance to an object. - * - * Sometimes it can happen that we send a short ultrasonic pulse, but never hear the echo. In order to not wait for that - * signal indefinitely, this component has an additional parameter that allows setting a timeout is microseconds (or - * meters). If this timeout is reached, the sensor reports a "nan" (not a number) float value. The timeout defaults to - * 11662µs or 2m. - * - * Usually these would be like HC-SR04 ultrasonic sensors: one trigger pin for sending the signal and another one echo - * pin for receiving the signal. Be very careful with that sensor though: it's made for 5v VCC and doesn't work - * very well with the ESP's 3.3V, so you need to create a voltage divider in order to not damage your ESP. - * - * Note: The MQTTSenorComponent will create a moving average over these values by default, to disable this behavior, - * call ultrasonic->mqtt->clear_filters() or similar like above. - */ +struct UltrasonicSensorStore { + uint32_t triggered_at{0}; + bool has_triggered{false}; + uint32_t echo_at{0}; + bool has_echo{false}; + + static void gpio_intr(UltrasonicSensorStore *arg); +}; + class UltrasonicSensorComponent : public PollingSensorComponent { public: /** Construct the ultrasonic sensor with the specified trigger pin and echo pin. @@ -41,48 +33,39 @@ class UltrasonicSensorComponent : public PollingSensorComponent { * @param update_interval The interval in ms the sensor should check for new values. */ UltrasonicSensorComponent(const std::string &name, GPIOPin *trigger_pin, GPIOPin *echo_pin, - uint32_t update_interval = 5000); + uint32_t update_interval = 60000); /// Set the timeout for waiting for the echo in µs. void set_timeout_us(uint32_t timeout_us); - /// Set the timeout for waiting for the echo in meter. - void set_timeout_m(float timeout_m); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - /// Get the timeout in meters for waiting for the echo. - float get_timeout_m() const; - /// Get the timeout in µs for waiting for the echo. - uint32_t get_timeout_us() const; - /// Set up pins and register interval. void setup() override; void dump_config() override; void update() override; + void loop() override; std::string unit_of_measurement() override; std::string icon() override; int8_t accuracy_decimals() override; - /// Hardware setup priority, before MQTT and WiFi. float get_setup_priority() const override; /// Set the time in µs the trigger pin should be enabled for in µs, defaults to 10µs (for HC-SR04) void set_pulse_time_us(uint32_t pulse_time_us); - /// Return the time in µs the trigger pin should be enabled for. - uint32_t get_pulse_time_us() const; protected: /// Helper function to convert the specified echo duration in µs to meters. static float us_to_m(uint32_t us); /// Helper function to convert the specified distance in meters to the echo duration in µs. - static uint32_t m_to_us(float m); GPIOPin *trigger_pin_; GPIOPin *echo_pin_; - uint32_t timeout_us_{11662}; /// 2 meters. + uint32_t timeout_us_{58309}; /// 10 meters. uint32_t pulse_time_us_{10}; + UltrasonicSensorStore store_; }; } // namespace sensor diff --git a/src/esphome/uart_component.cpp b/src/esphome/uart_component.cpp index 4090b60b..542de5cd 100644 --- a/src/esphome/uart_component.cpp +++ b/src/esphome/uart_component.cpp @@ -244,41 +244,41 @@ void UARTComponent::flush() { void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate) { this->bit_time_ = F_CPU / baud_rate; if (tx_pin != -1) { - this->tx_mask_ = (1U << tx_pin); - pinMode(tx_pin, OUTPUT); - this->tx_high_(); + this->tx_pin_ = GPIOOutputPin(tx_pin).copy(); + this->tx_pin_->setup(); + this->tx_pin_->digital_write(true); } if (rx_pin != -1) { - this->rx_mask_ = (1U << rx_pin); - pinMode(rx_pin, INPUT); + auto pin = GPIOInputPin(rx_pin); + pin.setup(); + this->rx_pin_ = pin.to_isr(); this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; - attach_functional_interrupt(rx_pin, [this]() ICACHE_RAM_ATTR { this->gpio_intr_(); }, FALLING); + pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); } } - -void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::gpio_intr_() { - uint32_t wait = this->bit_time_ + this->bit_time_ / 3 - 500; +void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { + uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; const uint32_t start = ESP.getCycleCount(); uint8_t rec = 0; // Manually unroll the loop - rec |= this->read_bit_(wait, start) << 0; - rec |= this->read_bit_(wait, start) << 1; - rec |= this->read_bit_(wait, start) << 2; - rec |= this->read_bit_(wait, start) << 3; - rec |= this->read_bit_(wait, start) << 4; - rec |= this->read_bit_(wait, start) << 5; - rec |= this->read_bit_(wait, start) << 6; - rec |= this->read_bit_(wait, start) << 7; + rec |= arg->read_bit_(&wait, start) << 0; + rec |= arg->read_bit_(&wait, start) << 1; + rec |= arg->read_bit_(&wait, start) << 2; + rec |= arg->read_bit_(&wait, start) << 3; + rec |= arg->read_bit_(&wait, start) << 4; + rec |= arg->read_bit_(&wait, start) << 5; + rec |= arg->read_bit_(&wait, start) << 6; + rec |= arg->read_bit_(&wait, start) << 7; // Stop bit - this->wait_(wait, start); + arg->wait_(&wait, start); - this->rx_buffer_[this->rx_in_pos_] = rec; - this->rx_in_pos_ = (this->rx_in_pos_ + 1) % this->rx_buffer_size_; + arg->rx_buffer_[arg->rx_in_pos_] = rec; + arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; // Clear RX pin so that the interrupt doesn't re-trigger right away again. - GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, this->rx_mask_); + arg->rx_pin_->clear_interrupt(); } void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { - if (this->tx_mask_ == 0) { + if (this->tx_pin_ == nullptr) { ESP_LOGE(TAG, "UART doesn't have TX pins set!"); return; } @@ -287,38 +287,32 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { uint32_t wait = this->bit_time_; const uint32_t start = ESP.getCycleCount(); // Start bit - this->write_bit_(false, wait, start); - this->write_bit_(data & (1 << 0), wait, start); - this->write_bit_(data & (1 << 1), wait, start); - this->write_bit_(data & (1 << 2), wait, start); - this->write_bit_(data & (1 << 3), wait, start); - this->write_bit_(data & (1 << 4), wait, start); - this->write_bit_(data & (1 << 5), wait, start); - this->write_bit_(data & (1 << 6), wait, start); - this->write_bit_(data & (1 << 7), wait, start); + this->write_bit_(false, &wait, start); + this->write_bit_(data & (1 << 0), &wait, start); + this->write_bit_(data & (1 << 1), &wait, start); + this->write_bit_(data & (1 << 2), &wait, start); + this->write_bit_(data & (1 << 3), &wait, start); + this->write_bit_(data & (1 << 4), &wait, start); + this->write_bit_(data & (1 << 5), &wait, start); + this->write_bit_(data & (1 << 6), &wait, start); + this->write_bit_(data & (1 << 7), &wait, start); // Stop bit - this->write_bit_(true, wait, start); + this->write_bit_(true, &wait, start); enable_interrupts(); } -void ESP8266SoftwareSerial::wait_(uint32_t &wait, const uint32_t &start) { - while (ESP.getCycleCount() - start < wait) +void ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { + while (ESP.getCycleCount() - start < *wait) ; - wait += this->bit_time_; + *wait += this->bit_time_; } -uint8_t ESP8266SoftwareSerial::read_bit_(uint32_t &wait, const uint32_t &start) { +bool ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { this->wait_(wait, start); - return uint8_t((GPI & this->rx_mask_) != 0); + return this->rx_pin_->digital_read(); } -void ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t &wait, const uint32_t &start) { - if (bit) { - this->tx_high_(); - } else { - this->tx_low_(); - } +void ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { + this->tx_pin_->digital_write(bit); this->wait_(wait, start); } -void ESP8266SoftwareSerial::tx_high_() { GPOS = this->tx_mask_; } -void ESP8266SoftwareSerial::tx_low_() { GPOC = this->tx_mask_; } uint8_t ESP8266SoftwareSerial::read_byte() { if (this->rx_in_pos_ == this->rx_out_pos_) return 0; diff --git a/src/esphome/uart_component.h b/src/esphome/uart_component.h index ec7f5135..71d9b842 100644 --- a/src/esphome/uart_component.h +++ b/src/esphome/uart_component.h @@ -25,21 +25,19 @@ class ESP8266SoftwareSerial { int available(); protected: - void gpio_intr_(); + static void gpio_intr(ESP8266SoftwareSerial *arg); - inline void wait_(uint32_t &wait, const uint32_t &start); - inline uint8_t read_bit_(uint32_t &wait, const uint32_t &start); - inline void write_bit_(bool bit, uint32_t &wait, const uint32_t &start); - inline void tx_high_(); - inline void tx_low_(); + inline void wait_(uint32_t *wait, const uint32_t &start); + inline bool read_bit_(uint32_t *wait, const uint32_t &start); + inline void write_bit_(bool bit, uint32_t *wait, const uint32_t &start); - uint32_t rx_mask_{0}; - uint32_t tx_mask_{0}; uint32_t bit_time_{0}; uint8_t *rx_buffer_{nullptr}; size_t rx_buffer_size_{64}; volatile size_t rx_in_pos_{0}; size_t rx_out_pos_{0}; + GPIOPin *tx_pin_{nullptr}; + ISRInternalGPIOPin *rx_pin_{nullptr}; }; #endif diff --git a/travis/run-clang-format.py b/travis/run-clang-format.py old mode 100644 new mode 100755 diff --git a/travis/run-clang-tidy.py b/travis/run-clang-tidy.py old mode 100644 new mode 100755