From 0aa7911b1be979dc7c80fa855d484bd66366d0ca Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 13 May 2025 13:58:15 -0700 Subject: [PATCH] [esp32][esp8266] use low-level pin control for ISR gpio (#8743) Co-authored-by: Samuel Sieb --- esphome/components/esp32/gpio.cpp | 110 ++++++++++++++++++++++------ esphome/components/esp8266/gpio.cpp | 77 +++++++++++++++++-- 2 files changed, 155 insertions(+), 32 deletions(-) diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index 7896597d3e..b554b6d09c 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -2,42 +2,66 @@ #include "gpio.h" #include "esphome/core/log.h" +#include "driver/gpio.h" +#include "driver/rtc_io.h" +#include "hal/gpio_hal.h" +#include "soc/soc_caps.h" +#include "soc/gpio_periph.h" #include +#if (SOC_RTCIO_PIN_COUNT > 0) +#include "hal/rtc_io_hal.h" +#endif + +#ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT +#define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0 // NOLINT +#endif + namespace esphome { namespace esp32 { static const char *const TAG = "esp32"; +static const gpio_hal_context_t GPIO_HAL = {.dev = GPIO_HAL_GET_HW(GPIO_PORT_0)}; + bool ESP32InternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) { +static gpio_mode_t flags_to_mode(gpio::Flags flags) { flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); - if (flags == gpio::FLAG_INPUT) { + if (flags == gpio::FLAG_INPUT) return GPIO_MODE_INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { + if (flags == gpio::FLAG_OUTPUT) return GPIO_MODE_OUTPUT; - } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) return GPIO_MODE_OUTPUT_OD; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) return GPIO_MODE_INPUT_OUTPUT_OD; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) { + if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) return GPIO_MODE_INPUT_OUTPUT; - } else { - // unsupported or gpio::FLAG_NONE - return GPIO_MODE_DISABLE; - } + // unsupported or gpio::FLAG_NONE + return GPIO_MODE_DISABLE; } struct ISRPinArg { gpio_num_t pin; + gpio::Flags flags; bool inverted; +#if defined(USE_ESP32_VARIANT_ESP32) + bool use_rtc; + int rtc_pin; +#endif }; ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const { auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) - arg->pin = pin_; + arg->pin = this->pin_; + arg->flags = gpio::FLAG_NONE; arg->inverted = inverted_; +#if defined(USE_ESP32_VARIANT_ESP32) + arg->use_rtc = rtc_gpio_is_valid_gpio(this->pin_); + if (arg->use_rtc) + arg->rtc_pin = rtc_io_number_get(this->pin_); +#endif return ISRInternalGPIOPin((void *) arg); } @@ -90,6 +114,7 @@ void ESP32InternalGPIOPin::setup() { if (flags_ & gpio::FLAG_OUTPUT) { gpio_set_drive_capability(pin_, drive_strength_); } + ESP_LOGD(TAG, "rtc: %d", SOC_GPIO_SUPPORT_RTC_INDEPENDENT); } void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) { @@ -115,28 +140,65 @@ void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); } using namespace esp32; bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { - auto *arg = reinterpret_cast(arg_); - return bool(gpio_get_level(arg->pin)) != arg->inverted; + auto *arg = reinterpret_cast(this->arg_); + return bool(gpio_hal_get_level(&GPIO_HAL, arg->pin)) != arg->inverted; } + void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { - auto *arg = reinterpret_cast(arg_); - gpio_set_level(arg->pin, value != arg->inverted ? 1 : 0); + auto *arg = reinterpret_cast(this->arg_); + gpio_hal_set_level(&GPIO_HAL, arg->pin, value != arg->inverted); } + void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { // not supported } + void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { auto *arg = reinterpret_cast(arg_); - gpio_set_direction(arg->pin, flags_to_mode(flags)); - gpio_pull_mode_t pull_mode = GPIO_FLOATING; - if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) { - pull_mode = GPIO_PULLUP_PULLDOWN; - } else if (flags & gpio::FLAG_PULLUP) { - pull_mode = GPIO_PULLUP_ONLY; - } else if (flags & gpio::FLAG_PULLDOWN) { - pull_mode = GPIO_PULLDOWN_ONLY; + gpio::Flags diff = (gpio::Flags)(flags ^ arg->flags); + if (diff & gpio::FLAG_OUTPUT) { + if (flags & gpio::FLAG_OUTPUT) { + gpio_hal_output_enable(&GPIO_HAL, arg->pin); + if (flags & gpio::FLAG_OPEN_DRAIN) + gpio_hal_od_enable(&GPIO_HAL, arg->pin); + } else { + gpio_hal_output_disable(&GPIO_HAL, arg->pin); + } } - gpio_set_pull_mode(arg->pin, pull_mode); + if (diff & gpio::FLAG_INPUT) { + if (flags & gpio::FLAG_INPUT) { + gpio_hal_input_enable(&GPIO_HAL, arg->pin); +#if defined(USE_ESP32_VARIANT_ESP32) + if (arg->use_rtc) { + if (flags & gpio::FLAG_PULLUP) { + rtcio_hal_pullup_enable(arg->rtc_pin); + } else { + rtcio_hal_pullup_disable(arg->rtc_pin); + } + if (flags & gpio::FLAG_PULLDOWN) { + rtcio_hal_pulldown_enable(arg->rtc_pin); + } else { + rtcio_hal_pulldown_disable(arg->rtc_pin); + } + } else +#endif + { + if (flags & gpio::FLAG_PULLUP) { + gpio_hal_pullup_en(&GPIO_HAL, arg->pin); + } else { + gpio_hal_pullup_dis(&GPIO_HAL, arg->pin); + } + if (flags & gpio::FLAG_PULLDOWN) { + gpio_hal_pulldown_en(&GPIO_HAL, arg->pin); + } else { + gpio_hal_pulldown_dis(&GPIO_HAL, arg->pin); + } + } + } else { + gpio_hal_input_disable(&GPIO_HAL, arg->pin); + } + } + arg->flags = flags; } } // namespace esphome diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index a24f217756..9f23e8e67e 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -8,7 +8,7 @@ namespace esp8266 { static const char *const TAG = "esp8266"; -static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { +static int flags_to_mode(gpio::Flags flags, uint8_t pin) { if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) return INPUT; } else if (flags == gpio::FLAG_OUTPUT) { @@ -34,12 +34,36 @@ static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { struct ISRPinArg { uint8_t pin; bool inverted; + volatile uint32_t *in_reg; + volatile uint32_t *out_set_reg; + volatile uint32_t *out_clr_reg; + volatile uint32_t *mode_set_reg; + volatile uint32_t *mode_clr_reg; + volatile uint32_t *func_reg; + uint32_t mask; }; ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) - arg->pin = pin_; - arg->inverted = inverted_; + arg->pin = this->pin_; + arg->inverted = this->inverted_; + if (this->pin_ < 16) { + arg->in_reg = &GPI; + arg->out_set_reg = &GPOS; + arg->out_clr_reg = &GPOC; + arg->mode_set_reg = &GPES; + arg->mode_clr_reg = &GPEC; + arg->func_reg = &GPF(this->pin_); + arg->mask = 1 << this->pin_; + } else { + arg->in_reg = &GP16I; + arg->out_set_reg = &GP16O; + arg->out_clr_reg = nullptr; + arg->mode_set_reg = &GP16E; + arg->mode_clr_reg = nullptr; + arg->func_reg = &GPF16; + arg->mask = 1; + } return ISRInternalGPIOPin((void *) arg); } @@ -88,20 +112,57 @@ void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); } using namespace esp8266; bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { - auto *arg = reinterpret_cast(arg_); - return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT + auto *arg = reinterpret_cast(this->arg_); + return bool(*arg->in_reg & arg->mask) != arg->inverted; } + void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { auto *arg = reinterpret_cast(arg_); - digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT + if (arg->pin < 16) { + if (value != arg->inverted) { + *arg->out_set_reg = arg->mask; + } else { + *arg->out_clr_reg = arg->mask; + } + } else { + if (value != arg->inverted) { + *arg->out_set_reg |= 1; + } else { + *arg->out_set_reg &= ~1; + } + } } + void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { auto *arg = reinterpret_cast(arg_); GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); } + void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { - auto *arg = reinterpret_cast(arg_); - pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT + auto *arg = reinterpret_cast(this->arg_); + if (arg->pin < 16) { + if (flags & gpio::FLAG_OUTPUT) { + *arg->mode_set_reg = arg->mask; + } else { + *arg->mode_clr_reg = arg->mask; + } + if (flags & gpio::FLAG_PULLUP) { + *arg->func_reg |= 1 << GPFPU; + } else { + *arg->func_reg &= ~(1 << GPFPU); + } + } else { + if (flags & gpio::FLAG_OUTPUT) { + *arg->mode_set_reg |= 1; + } else { + *arg->mode_set_reg &= ~1; + } + if (flags & gpio::FLAG_PULLDOWN) { + *arg->func_reg |= 1 << GP16FPD; + } else { + *arg->func_reg &= ~(1 << GP16FPD); + } + } } } // namespace esphome