From a51098725db1764c4800943de7df1704fc2c704c Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 15 Jun 2026 13:52:16 -0400 Subject: [PATCH] Support multi-I2C and fix ESP32-S3 USB logging Add dynamic I2C bus selection for battery gauge to support boards with multiple I2C controllers (e.g., Sticky uses Wire1 to avoid conflicts with GT911 touch). Fix logging on ESP32-S3 USB-Serial-JTAG by using esp_rom_printf instead of Serial, which incorrectly reports disconnected state. --- lib/Logging/Logging.cpp | 11 +++++++++++ lib/hal/HalPowerManager.cpp | 32 +++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/lib/Logging/Logging.cpp b/lib/Logging/Logging.cpp index 7b483657..1547a1b6 100644 --- a/lib/Logging/Logging.cpp +++ b/lib/Logging/Logging.cpp @@ -1,5 +1,7 @@ #include "Logging.h" +#include + #include #define MAX_ENTRY_LEN 256 @@ -59,9 +61,18 @@ void logPrintf(const char* level, const char* origin, const char* format, ...) { } } va_end(args); +#if defined(CONFIG_IDF_TARGET_ESP32S3) + // ESP32-S3 over USB-Serial-JTAG (HWCDC): Serial's `operator bool` reads false under a + // host monitor (and HWCDC.write itself drops when it thinks it's disconnected), so the + // `if (logSerial)` path below silently swallows every line. esp_rom_printf writes to the + // always-on ROM/IDF console — the same channel that carries the boot banner and ARDUHAL + // logs — so output is actually visible. `buf` is already fully formatted; pass via %s. + esp_rom_printf("%s", buf); +#else if (logSerial) { logSerial.print(buf); } +#endif addToLogRingBuffer(buf); } diff --git a/lib/hal/HalPowerManager.cpp b/lib/hal/HalPowerManager.cpp index 0b950d17..b9637ca2 100644 --- a/lib/hal/HalPowerManager.cpp +++ b/lib/hal/HalPowerManager.cpp @@ -10,14 +10,27 @@ HalPowerManager powerManager; // Singleton instance +namespace { +// The fuel gauge's I2C controller (Wire or Wire1) per the active board profile. +// On single-bus SoCs (ESP32-C3, SOC_I2C_NUM == 1) Wire1 doesn't exist, so always +// use Wire. On Sticky the gauge is on Wire1 so it doesn't fight the GT911 touch, +// which owns Wire. +TwoWire& gaugeWire() { +#if SOC_I2C_NUM > 1 + if (BoardConfig::ACTIVE.batteryGauge.i2cBus == 1) return Wire1; +#endif + return Wire; +} +} // namespace + void HalPowerManager::begin() { const auto& gauge = BoardConfig::ACTIVE.batteryGauge; if (gauge.gaugeAddr != 0) { // Board has an I2C fuel gauge (X3, LilyGo, Sticky, ...). Pins/freq come from // the active board profile, not hardcoded X3 values. I2C init must come AFTER // gpio.begin() so early hardware detection/probes are finished. - Wire.begin(gauge.i2cSda, gauge.i2cScl, gauge.i2cHz); - Wire.setTimeOut(4); + gaugeWire().begin(gauge.i2cSda, gauge.i2cScl, gauge.i2cHz); + gaugeWire().setTimeOut(4); _batteryUseI2C = true; } else if (BoardConfig::ACTIVE.batteryAdc >= 0) { // ADC-sensed board (X4: GPIO0, M5Paper: GPIO35). Skip when the profile leaves @@ -116,19 +129,20 @@ uint16_t HalPowerManager::getBatteryPercentage() const { // BQ27220-class, SOC at 0x2C). On I2C error, keep last known value to avoid // UI jitter/slowdowns. const uint8_t gaugeAddr = BoardConfig::ACTIVE.batteryGauge.gaugeAddr; - Wire.beginTransmission(gaugeAddr); - Wire.write(BQ27220_SOC_REG); - if (Wire.endTransmission(false) != 0) { + TwoWire& w = gaugeWire(); + w.beginTransmission(gaugeAddr); + w.write(BQ27220_SOC_REG); + if (w.endTransmission(false) != 0) { _batteryLastPollMs = now; return _batteryCachedPercent; } - Wire.requestFrom(gaugeAddr, (uint8_t)2); - if (Wire.available() < 2) { + w.requestFrom(gaugeAddr, (uint8_t)2); + if (w.available() < 2) { _batteryLastPollMs = now; return _batteryCachedPercent; } - const uint8_t lo = Wire.read(); - const uint8_t hi = Wire.read(); + const uint8_t lo = w.read(); + const uint8_t hi = w.read(); const uint16_t soc = (hi << 8) | lo; _batteryCachedPercent = soc > 100 ? 100 : soc; _batteryLastPollMs = now;