mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
[zephyr] Implement GPIO interrupts (ISRInternalGPIOPin) (#17077)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com>
This commit is contained in:
committed by
GitHub
parent
5fe36a45ed
commit
c3233739c5
@@ -39,7 +39,6 @@ CONFIG_SCHEMA = (
|
||||
# due to hardware limitations or lack of reliable interrupt support. This ensures
|
||||
# stable operation on these platforms. Future maintainers should verify platform
|
||||
# capabilities before changing this default behavior.
|
||||
# nrf52 has no gpio interrupts implemented yet
|
||||
cv.SplitDefault(
|
||||
CONF_USE_INTERRUPT,
|
||||
bk72xx=False,
|
||||
@@ -47,7 +46,7 @@ CONFIG_SCHEMA = (
|
||||
esp8266=True,
|
||||
host=True,
|
||||
ln882x=False,
|
||||
nrf52=False,
|
||||
nrf52=True,
|
||||
rp2040=True,
|
||||
rtl87xx=False,
|
||||
): cv.boolean,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
#include "gpio.h"
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -33,20 +34,80 @@ static gpio_flags_t flags_to_mode(gpio::Flags flags, bool inverted, bool value)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ESPHome's InterruptType is expressed in logical levels, but the pin is configured active-high in Zephyr (inversion is
|
||||
// applied in software by digital_read()/digital_write(), see the `!= inverted_` convention below). So when the pin is
|
||||
// inverted we must swap the physical edge/level the interrupt arms on: a logical rising edge is a physical falling
|
||||
// edge, etc. GPIO_INT_EDGE_BOTH is symmetric and needs no swap.
|
||||
static gpio_flags_t interrupt_type_to_flags(gpio::InterruptType type, bool inverted) {
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
return inverted ? GPIO_INT_EDGE_FALLING : GPIO_INT_EDGE_RISING;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
return inverted ? GPIO_INT_EDGE_RISING : GPIO_INT_EDGE_FALLING;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
return GPIO_INT_EDGE_BOTH;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
return inverted ? GPIO_INT_LEVEL_HIGH : GPIO_INT_LEVEL_LOW;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
return inverted ? GPIO_INT_LEVEL_LOW : GPIO_INT_LEVEL_HIGH;
|
||||
}
|
||||
return inverted ? GPIO_INT_EDGE_FALLING : GPIO_INT_EDGE_RISING;
|
||||
}
|
||||
|
||||
// Zephyr calls this with a pointer to the gpio_callback the interrupt fired on.
|
||||
// Recover the owning ZephyrGPIOInterrupt and dispatch to the ESPHome ISR.
|
||||
static void gpio_interrupt_handler(const device * /*dev*/, gpio_callback *cb, uint32_t /*pins*/) {
|
||||
auto *interrupt = CONTAINER_OF(cb, ZephyrGPIOInterrupt, callback);
|
||||
if (interrupt->func != nullptr) {
|
||||
interrupt->func(interrupt->arg);
|
||||
}
|
||||
}
|
||||
|
||||
struct ISRPinArg {
|
||||
const device *gpio;
|
||||
uint8_t pin;
|
||||
uint8_t gpio_size;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin ZephyrGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->gpio = this->gpio_;
|
||||
arg->pin = this->pin_;
|
||||
arg->gpio_size = this->gpio_size_;
|
||||
arg->inverted = this->inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void ZephyrGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
// TODO
|
||||
if (!device_is_ready(this->gpio_)) {
|
||||
ESP_LOGE(TAG, "Cannot attach interrupt: GPIO device not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop any interrupt previously attached to this pin before re-registering.
|
||||
this->detach_interrupt();
|
||||
|
||||
this->interrupt_.func = func;
|
||||
this->interrupt_.arg = arg;
|
||||
|
||||
uint8_t port_pin = this->pin_ % this->gpio_size_;
|
||||
gpio_init_callback(&this->interrupt_.callback, gpio_interrupt_handler, BIT(port_pin));
|
||||
|
||||
int ret = gpio_add_callback(this->gpio_, &this->interrupt_.callback);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "gpio_add_callback failed for pin %u: %d", this->pin_, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = gpio_pin_interrupt_configure(this->gpio_, port_pin, interrupt_type_to_flags(type, this->inverted_));
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "gpio_pin_interrupt_configure failed for pin %u: %d", this->pin_, ret);
|
||||
gpio_remove_callback(this->gpio_, &this->interrupt_.callback);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Interrupt attached to pin %u (type=%d)", this->pin_, (int) type);
|
||||
}
|
||||
|
||||
void ZephyrGPIOPin::setup() {
|
||||
@@ -88,15 +149,28 @@ void ZephyrGPIOPin::digital_write(bool value) {
|
||||
}
|
||||
gpio_pin_set(this->gpio_, this->pin_ % this->gpio_size_, value != this->inverted_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void ZephyrGPIOPin::detach_interrupt() const {
|
||||
// TODO
|
||||
if (this->gpio_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t port_pin = this->pin_ % this->gpio_size_;
|
||||
gpio_pin_interrupt_configure(this->gpio_, port_pin, GPIO_INT_DISABLE);
|
||||
gpio_remove_callback(this->gpio_, &this->interrupt_.callback);
|
||||
|
||||
this->interrupt_.func = nullptr;
|
||||
this->interrupt_.arg = nullptr;
|
||||
}
|
||||
|
||||
} // namespace zephyr
|
||||
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
// TODO
|
||||
return false;
|
||||
auto *arg = (zephyr::ISRPinArg *) this->arg_;
|
||||
if (arg == nullptr || arg->gpio == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return bool(gpio_pin_get(arg->gpio, arg->pin % arg->gpio_size) != arg->inverted);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -3,8 +3,19 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
#include "esphome/core/hal.h"
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
namespace esphome::zephyr {
|
||||
|
||||
// Bundles the Zephyr gpio_callback together with the ESPHome ISR function and
|
||||
// argument. Keeping them in one POD struct lets the static handler recover the
|
||||
// owning data straight from the callback pointer via CONTAINER_OF, so no global
|
||||
// pin->instance lookup table is needed.
|
||||
struct ZephyrGPIOInterrupt {
|
||||
struct gpio_callback callback;
|
||||
void (*func)(void *){nullptr};
|
||||
void *arg{nullptr};
|
||||
};
|
||||
|
||||
class ZephyrGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
ZephyrGPIOPin(const device *gpio, int gpio_size, const char *pin_name_prefix) {
|
||||
@@ -36,6 +47,10 @@ class ZephyrGPIOPin : public InternalGPIOPin {
|
||||
uint8_t gpio_size_{};
|
||||
bool inverted_{};
|
||||
bool value_{false};
|
||||
|
||||
// attach_interrupt()/detach_interrupt() are const (matching the base class), so
|
||||
// the interrupt state they manage has to be mutable.
|
||||
mutable ZephyrGPIOInterrupt interrupt_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::zephyr
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
# P0.2, P0.4 and P0.5 all live on the same Zephyr port device (gpio0) and each
|
||||
# attaches its own interrupt. This locks in shared-port behavior: every pin owns
|
||||
# a separate gpio_callback initialized with its own BIT(pin) mask, so Zephyr
|
||||
# dispatches to each pin independently even though the port device is shared.
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: 2
|
||||
id: gpio_binary_sensor
|
||||
use_interrupt: true
|
||||
interrupt_type: ANY
|
||||
|
||||
# Inverted pin with an edge-specific interrupt: exercises the inversion-aware
|
||||
# interrupt-arming path (logical RISING must arm on the physical falling edge).
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: P0.4
|
||||
inverted: true
|
||||
id: gpio_binary_sensor_inverted
|
||||
use_interrupt: true
|
||||
interrupt_type: RISING
|
||||
|
||||
# Second non-inverted interrupt on the same port (gpio0) as P0.2 above: verifies
|
||||
# multiple pins sharing one port device each get their own callback/pin_mask.
|
||||
- platform: gpio
|
||||
pin: P0.5
|
||||
id: gpio_binary_sensor_shared_port
|
||||
use_interrupt: true
|
||||
interrupt_type: FALLING
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
|
||||
Reference in New Issue
Block a user