From 92ffc5c9805c0af32fd21fa66be52e2a6aff7a73 Mon Sep 17 00:00:00 2001 From: Christos Themelis Date: Wed, 18 Mar 2026 00:25:20 +0200 Subject: [PATCH] wip --- .claude/settings.local.json | 3 +- examples/simple_secure_chat_ui/main.cpp | 9 +- examples/simple_secure_chat_ui/uiManager.cpp | 24 ++++++ src/helpers/esp32/SenseCapHAL.h | 30 +++++-- variants/sensecap_indicator-espnow/target.cpp | 82 ++++++++++++++++++- variants/sensecap_indicator-espnow/target.h | 11 +++ 6 files changed, 148 insertions(+), 11 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8de9c1140..0cf78b77e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,8 @@ "WebFetch(domain:raw.githubusercontent.com)", "WebFetch(domain:api.github.com)", "Bash(grep -r \"IO_EXPANDER\\\\|i2c_addr\\\\|PCA9554\\\\|expander\" D:GitMeshCore.piolibdepsSenseCapIndicator-ESPNow_comp_radio_usbLovyanGFXsrc --include=*.hpp --include=*.cpp -l)", - "Bash(dir \"D:\\\\Git\\\\MeshCore\\\\.pio\\\\libdeps\\\\SenseCapIndicator-ESPNow_comp_radio_usb\\\\LovyanGFX\\\\src\\\\lgfx\\\\v1\\\\platforms\\\\esp32s3\" /b)" + "Bash(dir \"D:\\\\Git\\\\MeshCore\\\\.pio\\\\libdeps\\\\SenseCapIndicator-ESPNow_comp_radio_usb\\\\LovyanGFX\\\\src\\\\lgfx\\\\v1\\\\platforms\\\\esp32s3\" /b)", + "Bash(findstr /S /M \"ui_Screen1_screen_init\\\\|ui_TabPageHome\\\\|lv_tabview_add_tab\" D:GitMeshCore*.cpp D:GitMeshCore*.h)" ] } } diff --git a/examples/simple_secure_chat_ui/main.cpp b/examples/simple_secure_chat_ui/main.cpp index e74eea2a6..c14dc9e27 100644 --- a/examples/simple_secure_chat_ui/main.cpp +++ b/examples/simple_secure_chat_ui/main.cpp @@ -141,9 +141,16 @@ void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) { + // Serialize Wire access with SenseCapHAL (TCA9535 radio expander). + // g_i2c_mutex is nullptr until radio_init() creates it — safe to skip then. + if (g_i2c_mutex) xSemaphoreTake(g_i2c_mutex, pdMS_TO_TICKS(20)); + uint16_t x, y; + bool touched = lcd.getTouch(&x, &y); + + if (g_i2c_mutex) xSemaphoreGive(g_i2c_mutex); - if (lcd.getTouch(&x, &y)) + if (touched) { data->state = LV_INDEV_STATE_PR; data->point.x = x; diff --git a/examples/simple_secure_chat_ui/uiManager.cpp b/examples/simple_secure_chat_ui/uiManager.cpp index 88d9e05c9..a3361860a 100644 --- a/examples/simple_secure_chat_ui/uiManager.cpp +++ b/examples/simple_secure_chat_ui/uiManager.cpp @@ -482,6 +482,11 @@ void UIManager::onHideKeyboard() LvObj(ui_SendBtn, true).positionY(channelInputBaseY); } +static void s_onRestartClick(lv_event_t *e) +{ + ESP.restart(); +} + static void s_onChannelInputFocus(lv_event_t *e) { UIManager *self = (UIManager*) lv_event_get_user_data(e); @@ -639,6 +644,25 @@ void UIManager::ui_Screen1_screen_init(void) .align(LV_ALIGN_CENTER) .position(0, -100); + lv_obj_t* ui_RestartBtn = LvButton(ui_TabPageHome) + .size(200, 56) + .align(LV_ALIGN_CENTER) + .position(0, 60) + .bgColor(0xC62828) + .onClick(s_onRestartClick, nullptr) + .raw(); + + lv_obj_t* ui_RestartLabel = LvLabel(ui_RestartBtn) + #if defined(LANG_EN) + .text(LV_SYMBOL_REFRESH " Restart") + #elif defined(LANG_GR) + .text(LV_SYMBOL_REFRESH " Επανεκκίνηση") + #endif + .font(&lv_font_arial_22) + .textColor(0xFFFFFF) + .raw(); + lv_obj_center(ui_RestartLabel); + ui_Contacts = LvList(ui_TabPageContacts) .width(140) .height(400) diff --git a/src/helpers/esp32/SenseCapHAL.h b/src/helpers/esp32/SenseCapHAL.h index 4a62121d0..f55fe1d5d 100644 --- a/src/helpers/esp32/SenseCapHAL.h +++ b/src/helpers/esp32/SenseCapHAL.h @@ -26,13 +26,16 @@ #include #include #include +#include +#include class SenseCapHAL : public ArduinoHal { - TwoWire* _wire; - uint8_t _addr; // 7-bit I2C address of TCA9535 (0x20) - uint8_t _out0; // cached Output Port 0 latch (all HIGH = de-asserted) - uint8_t _cfg0; // cached Config Port 0 (all 1 = input initially) - int _sclk, _miso, _mosi; // SPI data pins + TwoWire* _wire; + uint8_t _addr; // 7-bit I2C address of TCA9535 (0x20) + uint8_t _out0; // cached Output Port 0 latch (all HIGH = de-asserted) + uint8_t _cfg0; // cached Config Port 0 (all 1 = input initially) + int _sclk, _miso, _mosi; // SPI data pins + SemaphoreHandle_t _mutex; // shared Wire mutex (set via setMutex() after creation) // A pin is on the expander when its upper nibble equals IO_EXPANDER static bool isExp(uint32_t pin) { @@ -48,17 +51,22 @@ class SenseCapHAL : public ArduinoHal { } void writeReg(uint8_t reg, uint8_t val) { + if (_mutex) xSemaphoreTake(_mutex, portMAX_DELAY); _wire->beginTransmission(_addr); _wire->write(reg); _wire->write(val); _wire->endTransmission(); + if (_mutex) xSemaphoreGive(_mutex); } uint8_t readReg(uint8_t reg) { + if (_mutex) xSemaphoreTake(_mutex, portMAX_DELAY); _wire->beginTransmission(_addr); _wire->write(reg); _wire->endTransmission(false); _wire->requestFrom(_addr, (uint8_t)1); - return _wire->available() ? _wire->read() : 0xFF; + uint8_t val = _wire->available() ? _wire->read() : 0xFF; + if (_mutex) xSemaphoreGive(_mutex); + return val; } public: @@ -69,8 +77,13 @@ public: , _out0(0xFF) // all HIGH (NSS, RESET de-asserted) , _cfg0(0xFF) // all inputs initially , _sclk(sclk), _miso(miso), _mosi(mosi) + , _mutex(nullptr) {} + // Set the shared I2C mutex. Must be called before any Wire operations. + // Both this HAL and the touch-read callback must use the same handle. + void setMutex(SemaphoreHandle_t m) { _mutex = m; } + // Call once Wire is running — sets TCA9535 Port 0: all outputs HIGH void initExpander() { writeReg(0x02, _out0); // Output Port 0: all HIGH @@ -137,7 +150,10 @@ public: // Scan for the TCA9535 on I2C and log the result bool scanExpander() { + if (_mutex) xSemaphoreTake(_mutex, portMAX_DELAY); _wire->beginTransmission(_addr); - return (_wire->endTransmission() == 0); + bool found = (_wire->endTransmission() == 0); + if (_mutex) xSemaphoreGive(_mutex); + return found; } }; diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index fa248e484..b34d56aea 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -1,5 +1,47 @@ #include #include "target.h" +#include + +// Recover a stuck I2C bus by manually clocking SCL up to 9 times until +// SDA is released, then issuing a STOP condition. +static void i2c_bus_recovery() { + Serial.println("[i2c_recover] Attempting I2C bus recovery..."); + pinMode(PIN_BOARD_SCL, OUTPUT_OPEN_DRAIN); + pinMode(PIN_BOARD_SDA, INPUT_PULLUP); + digitalWrite(PIN_BOARD_SCL, HIGH); + delayMicroseconds(10); + + bool released = false; + for (int i = 0; i < 9; i++) { + digitalWrite(PIN_BOARD_SCL, LOW); + delayMicroseconds(10); + digitalWrite(PIN_BOARD_SCL, HIGH); + delayMicroseconds(10); + if (digitalRead(PIN_BOARD_SDA) == HIGH) { + released = true; + Serial.printf("[i2c_recover] SDA released after %d clocks\n", i + 1); + break; + } + } + if (!released) { + Serial.println("[i2c_recover] SDA still LOW after 9 clocks!"); + } + + // Generate STOP condition: SDA LOW→HIGH while SCL HIGH + pinMode(PIN_BOARD_SDA, OUTPUT_OPEN_DRAIN); + digitalWrite(PIN_BOARD_SDA, LOW); + delayMicroseconds(10); + digitalWrite(PIN_BOARD_SCL, HIGH); + delayMicroseconds(10); + digitalWrite(PIN_BOARD_SDA, HIGH); + delayMicroseconds(10); + + // Restore pins to input so Wire can take over + pinMode(PIN_BOARD_SCL, INPUT_PULLUP); + pinMode(PIN_BOARD_SDA, INPUT_PULLUP); + delay(5); + Serial.println("[i2c_recover] Done."); +} ESP32Board board; @@ -35,9 +77,17 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); EnvironmentSensorManager sensors; +// Shared Wire mutex — created in radio_init(), exported via target.h +SemaphoreHandle_t g_i2c_mutex = nullptr; + // ── radio_init ───────────────────────────────────────────────────────── bool radio_init() { + // Create the shared Wire mutex FIRST so both the radio HAL and the + // LVGL touch callback (my_touchpad_read) serialise their Wire access. + g_i2c_mutex = xSemaphoreCreateMutex(); + radio_hal.setMutex(g_i2c_mutex); + Serial.println("[radio_init] Starting..."); Serial.printf("[radio_init] SPI : SCLK=%d MISO=%d MOSI=%d\n", LORA_SCLK, LORA_MISO, LORA_MOSI); @@ -45,9 +95,37 @@ bool radio_init() { LORA_NSS & 0x0F, LORA_RESET & 0x0F, LORA_BUSY & 0x0F, LORA_DIO1 & 0x0F); Serial.printf("[radio_init] DIO1 interrupt → GPIO %d\n", IO_EXPANDER_IRQ); - // Wire must be up before TCA9535 access. - // (lcd.begin() may have already called Wire.begin, but re-init is safe.) + // Take mutex for bus recovery + Wire re-init + scan + // (prevents LVGL touch task from accessing Wire concurrently) + xSemaphoreTake(g_i2c_mutex, portMAX_DELAY); + + // Recover stuck I2C bus (may be left in bad state after display init) + i2c_bus_recovery(); + + // Wire must be up before TCA9535 access — force full re-init after recovery + Wire.end(); + delay(5); Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL); + Wire.setTimeOut(15); // 15 ms per address for fast scan + delay(10); + + // ── Full I2C bus scan ─────────────────────────────────────────────────── + Serial.println("[radio_init] I2C scan:"); + uint8_t found_addr = 0; + for (uint8_t addr = 0x08; addr <= 0x77; addr++) { + Wire.beginTransmission(addr); + uint8_t err = Wire.endTransmission(); + if (err == 0) { + Serial.printf("[radio_init] Device found at 0x%02X\n", addr); + found_addr = addr; + } + } + if (found_addr == 0) { + Serial.println("[radio_init] No I2C devices found!"); + } + Serial.println("[radio_init] I2C scan done."); + + xSemaphoreGive(g_i2c_mutex); // Verify TCA9535 is present on the bus if (radio_hal.scanExpander()) { diff --git a/variants/sensecap_indicator-espnow/target.h b/variants/sensecap_indicator-espnow/target.h index 805c63456..e18ca3fcb 100644 --- a/variants/sensecap_indicator-espnow/target.h +++ b/variants/sensecap_indicator-espnow/target.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -56,6 +58,15 @@ extern EnvironmentSensorManager sensors; #endif +// ------------------------------------------------- +// Shared I2C mutex +// Protects Wire access shared between SenseCapHAL (TCA9535 @ 0x20) +// and the LVGL touch callback (FT5x06 @ 0x48). +// Created in radio_init() before std_init(); use via g_i2c_mutex. +// ------------------------------------------------- +extern SemaphoreHandle_t g_i2c_mutex; + + // ------------------------------------------------- // Functions // -------------------------------------------------