Compare commits

...
Author SHA1 Message Date
J. Nick Koston 2a5cb7d957 Merge branch 'dev' into libretiny-gpio-write-before-output 2026-10-24 19:18:57 -10:00
J. Nick Koston 058d9db4dc [libretiny] Note the ISR write contract and the LibreTiny source 2026-10-24 19:17:49 -10:00
J. Nick Koston 0077231478 [libretiny] Keep the Realtek SDK headers out of clang-tidy 2026-10-24 18:19:19 -10:00
J. Nick Koston 2faaaa4c3c [libretiny] Simplify the GPIO level workaround
Pass the pin itself as the ISR argument, enter output on BK72xx with one register
store while keeping LibreTiny's pin state, leave inputs, reads and writes on
LibreTiny, switch on flags_to_mode() instead of repeating the mask, and shorten
the comments.
2026-10-24 17:52:44 -10:00
J. Nick Koston 371884d97e [libretiny] Mark the GPIO level workaround for removal once LibreTiny handles it 2026-10-24 17:41:19 -10:00
J. Nick Koston 55182cda22 [libretiny] Set the level before a Realtek pin switches to output
The switch to output on Realtek only changes the direction and drove the data
register's old level for about 5 us. Write the level through LibreTiny's gpio_t
first; the HAL keeps it while the pin is still an input.
2026-10-24 17:37:34 -10:00
J. Nick Koston 46fde171b6 [libretiny] Switch on the flag value and mark the register access for clang-tidy 2026-10-24 17:26:24 -10:00
J. Nick Koston 8132569898 [libretiny] Drive simple BK72xx pin modes through the BDK so outputs start at their level
LibreTiny's switch to output on Beken writes 0 to the pin's config register, so a
pin switched to output HIGH first drove LOW for about 1.3 us. Set the level while
the pin is still an input and enable the output in one register write that keeps
it, and read and write those pins through the BDK too.
2026-10-24 17:03:29 -10:00
J. Nick Koston b41e8ff924 [libretiny] Apply a level written before the pin becomes an output
On LibreTiny a write to a pin that is not an output only changes its pull,
so writing a level and then switching to output drove a stale level. Keep
the requested level and apply it when the pin becomes an output.
2026-10-24 16:24:00 -10:00
2 changed files with 74 additions and 21 deletions

No files matched your search

+68 -20
View File
@@ -4,6 +4,11 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
// clang-tidy cannot parse the Realtek SDK headers behind wiring_data.h
#if defined(USE_BK72XX) || (defined(USE_RTL87XX) && !defined(CLANG_TIDY))
#include <wiring_data.h>
#endif
namespace esphome::libretiny {
ESPHOME_LOG_TAG(TAG, "libretiny.gpio");
@@ -26,16 +31,10 @@ static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
}
}
struct ISRPinArg {
uint8_t pin;
bool inverted;
};
// The ISR copy is the pin itself, so both share the stored mode and level. Pins are never freed.
ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
return ISRInternalGPIOPin(
const_cast<ArduinoInternalGPIOPin *>(this)); // NOLINT(cppcoreguidelines-pro-type-const-cast)
}
void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const {
@@ -61,8 +60,59 @@ void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg,
attachInterruptParam(pin_, func, arduino_mode, arg);
}
#ifdef USE_BK72XX
// BDK GPIO config register (REG_GPIO_0_CONFIG, GCFG_OUTPUT_BIT): 0 enables the output, bit 1 is the output level
static constexpr uintptr_t BK_GPIO_CFG_BASE = 0x00802800;
static constexpr uint32_t BK_GPIO_OUTPUT_BIT = 1 << 1;
// LibreTiny's switch to output always starts the pin LOW. Enable the output at its level in one store, then update
// LibreTiny's pin state the way its own switch does (cores/beken-72xx/arduino/src/wiring_digital.c).
static bool bk_enter_output(uint8_t pin, bool level) {
PinInfo *info = pinInfo(pin);
if (info == nullptr || info->gpio > 31)
return false;
PinData *data = pinData(info);
if (pinEnabled(info, PIN_GPIO) && data->gpioMode == OUTPUT)
return false;
pinRemoveMode(info, PIN_PWM);
auto *cfg =
reinterpret_cast<volatile uint32_t *>(BK_GPIO_CFG_BASE + info->gpio * 4); // NOLINT(performance-no-int-to-ptr)
*cfg = level ? BK_GPIO_OUTPUT_BIT : 0;
pinEnable(info, PIN_GPIO);
data->gpioMode = OUTPUT;
return true;
}
#endif
#if defined(USE_RTL87XX) && !defined(CLANG_TIDY)
// The switch to output keeps the HAL data register, so write the level to it first
static void rtl_preset_level(uint8_t pin, bool level) {
PinInfo *info = pinInfo(pin);
if (info == nullptr)
return;
if (info->data == nullptr || info->data->gpio == nullptr)
pinMode(pin, INPUT); // NOLINT - LibreTiny creates the gpio_t on first use
if (info->data != nullptr && info->data->gpio != nullptr)
gpio_write(info->data->gpio, level);
}
#endif
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags)); // NOLINT
const int mode = flags_to_mode(flags);
this->output_ = flags & gpio::FLAG_OUTPUT;
#ifdef USE_BK72XX
if (mode == OUTPUT && bk_enter_output(this->pin_, this->level_))
return;
#endif
#if defined(USE_RTL87XX) && !defined(CLANG_TIDY)
// The preset avoids a LOW blip when going HIGH, but a LOW only takes after the switch (measured on a BW15),
// so the write below is still needed
if (mode == OUTPUT)
rtl_preset_level(this->pin_, this->level_);
#endif
pinMode(this->pin_, mode); // NOLINT
if (this->output_)
digitalWrite(this->pin_, this->level_); // NOLINT
}
size_t ArduinoInternalGPIOPin::dump_summary(char *buffer, size_t len) const {
@@ -70,10 +120,12 @@ size_t ArduinoInternalGPIOPin::dump_summary(char *buffer, size_t len) const {
}
bool ArduinoInternalGPIOPin::digital_read() {
return bool(digitalRead(pin_)) ^ inverted_; // NOLINT
return bool(digitalRead(this->pin_)) ^ this->inverted_; // NOLINT
}
void ArduinoInternalGPIOPin::digital_write(bool value) {
digitalWrite(pin_, value ^ inverted_); // NOLINT
this->level_ = value ^ this->inverted_;
if (this->output_)
digitalWrite(this->pin_, this->level_); // NOLINT
}
void ArduinoInternalGPIOPin::detach_interrupt() const {
detachInterrupt(pin_); // NOLINT
@@ -86,20 +138,16 @@ namespace esphome {
using namespace libretiny;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
return bool(digitalRead(arg->pin)) ^ arg->inverted; // NOLINT
return reinterpret_cast<ArduinoInternalGPIOPin *>(this->arg_)->digital_read();
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
digitalWrite(arg->pin, value ^ arg->inverted); // NOLINT
reinterpret_cast<ArduinoInternalGPIOPin *>(this->arg_)->digital_write(value);
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
detachInterrupt(arg->pin);
detachInterrupt(reinterpret_cast<ArduinoInternalGPIOPin *>(this->arg_)->get_pin());
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags)); // NOLINT
reinterpret_cast<ArduinoInternalGPIOPin *>(this->arg_)->pin_mode(flags);
}
} // namespace esphome
+6 -1
View File
@@ -30,7 +30,12 @@ class ArduinoInternalGPIOPin final : public GPIOPin {
void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const;
uint8_t pin_;
bool inverted_{};
bool inverted_ : 1 {};
// LibreTiny drops writes to a pin that is not an output; keep the level until it becomes one.
// Remove with bk_enter_output() and rtl_preset_level() once LibreTiny handles this.
// These bits share a byte with the ISR copy, so a pin must not be written from an ISR and the loop at once.
bool output_ : 1 {};
bool level_ : 1 {};
gpio::Flags flags_{};
};