From a0f30f28b58c6b38e2c4739f74f9e18754d73d72 Mon Sep 17 00:00:00 2001 From: ainyan03 <205502311+ainyan03@users.noreply.github.com> Date: Tue, 20 Oct 2026 00:02:52 +0000 Subject: [PATCH 1/2] ESP32 I2C: decide TwoWire::end() in one place, also without core_version.h Whether lgfx may call TwoWire::end() (released ports, the temporary pin switcher) was decided in three places from ARDUINO_ESP32_GIT_VER, which comes from core_version.h. Board packages that do not ship that header left it undefined, so the port was handed back with its pins as GPIO while the Arduino driver stayed installed; the next init found the driver open and the bus stayed unusable. The decision is now one macro, LGFX_TWOWIRE_HAS_END. With core_version.h it is the previous rule (any version but 2.0.0, which has no end()). Without it, esp_arduino_version.h decides (>= 2.0.1). The git version stays first because 2.0.1 and 2.0.2 still report 2.0.0 there. This also covers 2.0.1 to 2.0.4 without core_version.h, where WIRE_HAS_END is not yet defined. For every official release the result is unchanged. The unused i2c_context_t::setPins(), which carried the third copy of the rule, is removed. --- src/lgfx/v1/platforms/esp32/common.inl | 66 +++++++++----------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/src/lgfx/v1/platforms/esp32/common.inl b/src/lgfx/v1/platforms/esp32/common.inl index 595e2328..a98cfb37 100644 --- a/src/lgfx/v1/platforms/esp32/common.inl +++ b/src/lgfx/v1/platforms/esp32/common.inl @@ -1240,6 +1240,23 @@ namespace lgfx #include #endif +// TwoWire::end() exists from core 2.0.1 (0x44c11981 is 2.0.0). Some board packages lack +// core_version.h; they are judged by esp_arduino_version.h, which 2.0.1 and 2.0.2 still +// report as 2.0.0, hence the git version first. +#if defined ( ARDUINO ) && __has_include () && defined ( ESP_IDF_VERSION_VAL ) + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) + #if defined ( ARDUINO_ESP32_GIT_VER ) + #if ARDUINO_ESP32_GIT_VER != 0x44c11981 + #define LGFX_TWOWIRE_HAS_END + #endif + #elif defined ( ESP_ARDUINO_VERSION_VAL ) + #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 1) + #define LGFX_TWOWIRE_HAS_END + #endif + #endif + #endif +#endif + // Register layout of the I2C block. Only the older chips are named; anything not // listed is taken to carry the current layout, so a new target lands there rather // than on the ESP32 one. @@ -1591,35 +1608,6 @@ namespace lgfx updateDev(dev); } - void setPins(i2c_dev_t* dev, gpio_num_t scl, gpio_num_t sda) - { - pin_sda = sda; - pin_scl = scl; -#if defined ( ARDUINO ) && __has_include () - #if defined ( ESP_IDF_VERSION_VAL ) - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) - #if defined ARDUINO_ESP32_GIT_VER - #if ARDUINO_ESP32_GIT_VER != 0x44c11981 - #define USE_TWOWIRE_SETPINS - #endif - #endif - #endif - #endif - -#if LGFX_HP_I2C_NUM == 1 - auto twowire = &Wire; -#else - auto twowire = ((dev == &I2C0) ? &Wire : &Wire1); -#endif - - #if defined ( USE_TWOWIRE_SETPINS ) - twowire->setPins(sda, scl); - #else - twowire->begin((int)sda, (int)scl); - #endif -#endif - } - // A low power port is opened through the ESP-IDF driver even in an Arduino build, where // the normal ports go through TwoWire, so the handle is needed in both cases. #if __has_include() \ @@ -1979,17 +1967,13 @@ namespace lgfx #endif { #if defined ( ARDUINO ) && __has_include () && defined ( ESP_IDF_VERSION_VAL ) - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) - #if defined ARDUINO_ESP32_GIT_VER - #if ARDUINO_ESP32_GIT_VER != 0x44c11981 - #if LGFX_HP_I2C_NUM == 1 + #if defined ( LGFX_TWOWIRE_HAS_END ) + #if LGFX_HP_I2C_NUM == 1 auto twowire = &Wire; - #else + #else auto twowire = ((i2c_port == 0) ? &Wire : &Wire1); - #endif - twowire->end(); - #endif #endif + twowire->end(); #endif #elif __has_include() auto bus_handle = i2c_context[i2c_port].i2c_bus_handle; @@ -2746,18 +2730,12 @@ namespace lgfx #if defined ( ARDUINO ) && __has_include () _twowire = nullptr; if (search_sda >=0 && search_scl >= 0) { -#if defined ( ARDUINO ) && __has_include () && defined ( ESP_IDF_VERSION_VAL ) - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) - #if defined ARDUINO_ESP32_GIT_VER - #if ARDUINO_ESP32_GIT_VER != 0x44c11981 +#if defined ( LGFX_TWOWIRE_HAS_END ) _twowire = &Wire; #if defined (I2CEXT1_SDA_IN_IDX) if (i2c_port != 0) { _twowire = &Wire1; } #endif _twowire->end(); - #endif - #endif - #endif #endif } #endif From cacd7f5e1a312409af7e8ba877a9cabc0b189671 Mon Sep 17 00:00:00 2001 From: ainyan03 <205502311+ainyan03@users.noreply.github.com> Date: Tue, 20 Oct 2026 00:02:53 +0000 Subject: [PATCH 2/2] ESP32 I2C: recover a controller left bus busy before a transfer A device can leave the controller waiting for a STOP that never comes: bus_busy stays set and TIME_OUT is raised while nothing is transferred. beginTransaction() only waited 128 us for busy to clear and then started the address phase, which failed at the first wait, and every following transfer failed the same way. The ESP32 has no FSM reset bit, so nothing else cleared it. If the port is still busy after that wait, i2c_stop() now recovers the bus and the controller first, as the ESP-IDF driver does before it starts a command. It runs after save_reg(), so load_reg() at the end of the transfer still restores the registers shared with the platform driver. The C2 and C3 skip the peripheral reset in i2c_stop() and rely on the fsm_rst that follows. --- src/lgfx/v1/platforms/esp32/common.inl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lgfx/v1/platforms/esp32/common.inl b/src/lgfx/v1/platforms/esp32/common.inl index a98cfb37..d1ed1dcf 100644 --- a/src/lgfx/v1/platforms/esp32/common.inl +++ b/src/lgfx/v1/platforms/esp32/common.inl @@ -2388,6 +2388,16 @@ namespace lgfx } i2c_context[i2c_port].save_reg(dev); + // Still busy although no transfer of ours is running: a slave glitch can leave the + // controller waiting for a STOP forever (ESP32 has no FSM reset bit). Recover the bus + // and the controller as the ESP-IDF driver does before it starts a command (the C2 and + // C3 skip the peripheral reset there and rely on fsm_rst below). + if (getBusBusy(dev)) + { + ESP_LOGV("LGFX", "i2c::beginTransaction : bus busy, recover"); + i2c_stop(i2c_port); + } + // A low power port reaches its pads through the low power IO domain, which set_pin() // knows nothing about: routing them through the normal GPIO matrix here would // disconnect the port from its own pins on every transaction. A shared port keeps