From da3d734e4bc5c71dce4d53f6071eaa116ae5b79d Mon Sep 17 00:00:00 2001 From: Christos Themelis Date: Wed, 18 Mar 2026 09:50:03 +0200 Subject: [PATCH] wip --- examples/simple_secure_chat_ui/main.cpp | 32 +++++++- src/helpers/esp32/SenseCapHAL.h | 76 ++++++++++++++++--- variants/sensecap_indicator-espnow/target.cpp | 10 +-- 3 files changed, 100 insertions(+), 18 deletions(-) diff --git a/examples/simple_secure_chat_ui/main.cpp b/examples/simple_secure_chat_ui/main.cpp index c14dc9e27..e3f0bd893 100644 --- a/examples/simple_secure_chat_ui/main.cpp +++ b/examples/simple_secure_chat_ui/main.cpp @@ -848,9 +848,11 @@ void initializeMesh() { Serial.println("[mesh] the_mesh.begin() done"); float freq = the_mesh.getFreqPref(); - uint8_t txpwr = the_mesh.getTxPowerPref(); - Serial.printf("[mesh] Radio params: freq=%.3f MHz BW=%d SF=%d CR=%d TX=%d dBm\n", - freq, LORA_BW, LORA_SF, LORA_CR, txpwr); + // Always use the build-defined TX power; saved prefs may contain a stale + // value from a previous firmware version with a different default. + uint8_t txpwr = LORA_TX_POWER; + Serial.printf("[mesh] Radio params: freq=%.3f MHz BW=%.1f SF=%d CR=%d TX=%d dBm\n", + freq, (float)LORA_BW, (int)LORA_SF, (int)LORA_CR, (int)txpwr); radio_set_params(freq, LORA_BW, LORA_SF, LORA_CR); radio_set_tx_power(txpwr); @@ -884,10 +886,32 @@ void setup() { initializeUI(); createTasks(); - lv_timer_handler(); + + // ── Let LVGL render the initial UI before suspending ─────────────────────── + // configureDisplay() fills the RGB framebuffer with black. LVGL needs at + // least one lv_timer_handler() cycle to paint the initial screen into that + // framebuffer. Since lvgl_task (Core 0) runs in parallel with setup() + // (Core 1), a short delay here gives it time to complete the first render, + // so the display shows the UI rather than a blank screen during radio init. + delay(100); + + // ── Suspend LVGL task before radio init ──────────────────────────────────── + // radio_init() calls Wire.end()/Wire.begin() while the LVGL touch callback + // (my_touchpad_read) also accesses Wire. Suspending lvgl_task eliminates + // the race; the I2C mutex (g_i2c_mutex) guards all Wire access once it is + // created inside radio_init(). + Serial.println("[setup] Suspending LVGL task for radio init..."); + vTaskSuspend(t_core0_lvgl); initializeMesh(); + // Mark entire LVGL screen dirty so the first lv_timer_handler() tick + // after resume flushes the full UI. + lv_obj_invalidate(lv_scr_act()); + + Serial.println("[setup] Resuming LVGL task..."); + vTaskResume(t_core0_lvgl); + vTaskResume(t_core1_core); Serial.println("Setup completed"); diff --git a/src/helpers/esp32/SenseCapHAL.h b/src/helpers/esp32/SenseCapHAL.h index 2875b8be5..04bd48aac 100644 --- a/src/helpers/esp32/SenseCapHAL.h +++ b/src/helpers/esp32/SenseCapHAL.h @@ -28,6 +28,7 @@ #include #include #include +#include class SenseCapHAL : public ArduinoHal { TwoWire* _wire; @@ -111,19 +112,76 @@ public: } // ── Overrides ────────────────────────────────────────────────────────── + // + // SOFTWARE SPI — we bit-bang GPIO 41/47/48 directly instead of using + // the hardware FSPI peripheral. Calling spi.begin(41,47,48) routes + // those pins through the ESP32-S3 GPIO matrix to FSPI, which silently + // disables LovyanGFX's LCD_CAM framebuffer output (blank display). + // Software SPI avoids that conflict entirely; the SX1262 works fine + // at the ~500 kHz rate the bit-bang produces. + + // ArduinoHal::init() only calls spiBegin() when initInterface==true, + // which is false when an explicit SPIClass& is supplied. Override to + // always call our spiBegin(). + void init() override { spiBegin(); } - // Initialize SPI with the correct pin numbers for SenseCAP Indicator. void spiBegin() override { - this->spi->begin(_sclk, _miso, _mosi); - Serial.printf("[SenseCapHAL] SPI begin: SCLK=%d MISO=%d MOSI=%d\n", - _sclk, _miso, _mosi); + // ── IMPORTANT ────────────────────────────────────────────────────────── + // We must NOT call ::pinMode() here. ::pinMode() calls gpio_reset_pin() + // which disrupts the GPIO matrix signal routing used by LovyanGFX's + // Bus_RGB LCD_CAM driver — blanking the display permanently. + // + // We also must NOT use gpio_set_direction() alone: it does not call + // PIN_FUNC_SELECT, so the IO_MUX for GPIO 41/48 may remain pointed at + // the JTAG MTDI/MTCK function (restored by LovyanGFX's pin_backup_t + // after lcd.begin()). With IO_MUX in JTAG function the physical pins + // bypass the GPIO matrix entirely, making soft-SPI write nothing. + // + // Fix: gpio_config() sets IO_MUX → PIN_FUNC_GPIO (via PIN_FUNC_SELECT) + // AND enables GPIO output via the GPIO matrix (SIG_GPIO_OUT_IDX), all + // without ever calling gpio_reset_pin(). + // ─────────────────────────────────────────────────────────────────────── + gpio_config_t out_conf = {}; + out_conf.pin_bit_mask = (1ULL << _sclk) | (1ULL << _mosi); + out_conf.mode = GPIO_MODE_OUTPUT; + out_conf.pull_up_en = GPIO_PULLUP_DISABLE; + out_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + out_conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&out_conf); + + gpio_config_t in_conf = {}; + in_conf.pin_bit_mask = (1ULL << _miso); + in_conf.mode = GPIO_MODE_INPUT; + in_conf.pull_up_en = GPIO_PULLUP_DISABLE; + in_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + in_conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&in_conf); + + gpio_set_level((gpio_num_t)_sclk, 0); + gpio_set_level((gpio_num_t)_mosi, 0); + Serial.printf("[SenseCapHAL] SPI (soft) SCLK=%d MOSI=%d MISO=%d\n", + _sclk, _mosi, _miso); } - // ArduinoHal::init() only calls spiBegin() when initInterface==true, - // which is NOT set when an explicit SPIClass& is passed to the constructor. - // Override init() to always initialise SPI with our custom pins. - void init() override { - spiBegin(); + void spiBeginTransaction() override { /* no-op for soft SPI */ } + void spiEndTransaction() override { /* no-op for soft SPI */ } + void spiEnd() override { /* no-op for soft SPI */ } + + // SPI Mode 0: CPOL=0 (CLK idles LOW), CPHA=0 (sample on rising edge) + void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override { + for (size_t i = 0; i < len; i++) { + uint8_t txByte = out ? out[i] : 0xFF; + uint8_t rxByte = 0; + for (int b = 7; b >= 0; b--) { + ::digitalWrite(_mosi, (txByte >> b) & 1); + ::delayMicroseconds(1); // MOSI setup time + ::digitalWrite(_sclk, HIGH); + rxByte |= (uint8_t)(::digitalRead(_miso) << b); // sample on rising edge + ::delayMicroseconds(1); // hold time + ::digitalWrite(_sclk, LOW); + } + if (in) in[i] = rxByte; + } } void pinMode(uint32_t pin, uint32_t mode) override { diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index b34d56aea..c78f43f59 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -61,11 +61,11 @@ ESP32Board board; #define LORA_DIO1 (3 | IO_EXPANDER) // TCA9535 port-0 pin 3 (0x43) // → interrupt fires on GPIO 42 -static SPIClass spi(FSPI); - -// Custom RadioLib HAL: routes expander pins through TCA9535 (I2C 0x20) -// and inits SPI with the correct pins on first use. -static SenseCapHAL radio_hal(spi, LORA_SCLK, LORA_MISO, LORA_MOSI, 0x20, &Wire); +// Custom RadioLib HAL: routes expander pins through TCA9535 (I2C 0x20). +// Uses software (bit-bang) SPI on SCLK/MOSI/MISO to avoid taking the +// hardware FSPI peripheral, which would disrupt LovyanGFX's LCD_CAM output. +// SPI is passed as a placeholder only — all SPI methods are overridden. +static SenseCapHAL radio_hal(SPI, LORA_SCLK, LORA_MISO, LORA_MOSI, 0x20, &Wire); // SX1262 module using the custom HAL RADIO_CLASS radio = new Module(&radio_hal, LORA_NSS, LORA_DIO1, LORA_RESET, LORA_BUSY);