Browse Source

wip

pull/2568/head
Christos Themelis 2 months ago
parent
commit
1f40e62225
  1. 33
      examples/simple_secure_chat_ui/main.cpp
  2. 131
      src/helpers/esp32/SenseCapHAL.h
  3. 5
      src/helpers/radiolib/RadioLibWrappers.cpp
  4. 38
      variants/sensecap_indicator-espnow/SCIndicatorDisplay.h
  5. 4
      variants/sensecap_indicator-espnow/platformio.ini

33
examples/simple_secure_chat_ui/main.cpp

@ -123,20 +123,25 @@ void parse_group_message(const char *input,
// Elecrow Display callbacks
/* Display flushing */
static uint32_t s_flush_count = 0;
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
s_flush_count++;
if (s_flush_count <= 3) {
Serial.printf("[display] flush #%u x=%d y=%d w=%d h=%d\n",
s_flush_count, area->x1, area->y1, w, h);
}
//lcd.fillScreen(TFT_WHITE);
#if (LV_COLOR_16_SWAP != 0)
lcd.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);
lcd.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);
#else
lcd.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);//
lcd.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);
#endif
lv_disp_flush_ready(disp);
}
void my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
@ -803,11 +808,18 @@ void configureDisplay() {
void initializeDisplay() {
ESP_LOGI(TAG, "Initializing display...");
lcd.begin();
lcd.fillScreen(0x000000u);
bool ok = lcd.begin();
Serial.printf("[display] lcd.begin() = %s w=%d h=%d\n", ok ? "OK" : "FAIL", lcd.width(), lcd.height());
lcd.setBrightness(255);
// Fallback: force GPIO 45 HIGH in case LovyanGFX Light_PWM LEDC setup failed.
pinMode(45, OUTPUT);
digitalWrite(45, HIGH);
Serial.printf("[display] BL GPIO45 after force HIGH: %d\n", digitalRead(45));
lcd.fillScreen(TFT_RED); // DEBUG: should show solid RED for 3s before LVGL
Serial.println("[display] RED fill done — waiting 3s for visual check");
delay(3000);
lcd.setTextSize(2);
lcd.setRotation(1);
//lcd.setBrightness(127);
}
void initializeTouchScreen() {
@ -824,6 +836,7 @@ void initializeMesh() {
Serial.println("[mesh] board.begin()");
board.begin();
#ifndef DISABLE_LORA_FOR_DISPLAY_TEST
Serial.println("[mesh] radio_init()...");
if (!radio_init()) {
Serial.println("[mesh] FATAL: radio_init() failed - halting");
@ -831,6 +844,10 @@ void initializeMesh() {
}
fast_rng.begin(radio_get_rng_seed());
#else
Serial.println("[mesh] *** LORA DISABLED FOR DISPLAY TEST ***");
fast_rng.begin(42);
#endif
Serial.println("[mesh] SPIFFS.begin()");
#if defined(NRF52_PLATFORM)

131
src/helpers/esp32/SenseCapHAL.h

@ -107,18 +107,38 @@ public:
writeReg(0x02, _out0); // Output Port 0
writeReg(0x06, _cfg0); // Config Port 0
Serial.printf("[SenseCapHAL] TCA9535@0x%02X init: out=0x%02X cfg=0x%02X\n",
// Port 1 (pins 8-15) — appears unused on the SenseCAP Indicator.
// TCA9535 /INT fires on ANY input change on either port and only clears
// when the triggering port register is read. Floating Port 1 inputs
// continuously re-assert /INT, preventing DIO1 edges from ever firing.
// Fix: make all Port 1 pins OUTPUT HIGH so they can never float.
writeReg(0x03, 0xFF); // Output Port 1: all HIGH
writeReg(0x07, 0x00); // Config Port 1: all OUTPUTS
// Read both input ports now to establish a clean /INT baseline.
readReg(0x00);
readReg(0x01);
Serial.printf("[SenseCapHAL] TCA9535@0x%02X init: out0=0x%02X cfg0=0x%02X\n",
_addr, _out0, _cfg0);
}
// ── 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.
// SOFTWARE (bit-bang) SPI — intentionally avoids the hardware FSPI peripheral.
//
// Calling SPI.begin() (hardware FSPI) AFTER lcd.begin() disrupts the
// LCD_CAM peripheral that is already running the RGB parallel bus,
// causing the display to go blank. Since the LCD_CAM uses GPIOs 0–21
// and the SX1262 SPI uses GPIOs 41/47/48, there is no pin conflict —
// the problem is at the peripheral / DMA level when FSPI is initialised
// while LCD_CAM is active.
//
// Bit-bang SPI on the same GPIOs avoids hardware SPI entirely.
// The SX1262 requires at most ~16 MHz SPI; bit-bang on ESP32-S3 gives
// ~1–2 MHz which is more than adequate for LoRa configuration and DIO
// interrupt latency is unaffected.
// ArduinoHal::init() only calls spiBegin() when initInterface==true,
// which is false when an explicit SPIClass& is supplied. Override to
@ -126,61 +146,40 @@ public:
void init() override { spiBegin(); }
void spiBegin() override {
// ── 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",
// Configure GPIO pins for direct CPU control (disconnects any
// peripheral / GPIO-matrix function, including leftover FSPI routing).
::pinMode(_sclk, OUTPUT);
::pinMode(_mosi, OUTPUT);
::pinMode(_miso, INPUT);
::digitalWrite(_sclk, LOW);
Serial.printf("[SenseCapHAL] SPI (bit-bang) SCLK=%d MOSI=%d MISO=%d\n",
_sclk, _mosi, _miso);
}
void spiBeginTransaction() override { /* no-op for soft SPI */ }
void spiEndTransaction() override { /* no-op for soft SPI */ }
void spiEnd() override { /* no-op for soft SPI */ }
void spiEnd() override {
// nothing to tear down for bit-bang
}
void spiBeginTransaction() override {
// nothing needed — CS is managed via the expander in digitalWrite()
}
void spiEndTransaction() override {
// nothing needed
}
// SPI Mode 0: CPOL=0 (CLK idles LOW), CPHA=0 (sample on rising edge)
// Bit-bang SPI Mode 0 (CPOL=0, CPHA=0): data sampled 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;
uint8_t tx = out ? out[i] : 0x00;
uint8_t rx = 0;
for (int b = 7; b >= 0; b--) {
::digitalWrite(_mosi, (txByte >> b) & 1);
::delayMicroseconds(1); // MOSI setup time
::digitalWrite(_mosi, (tx >> b) & 1);
::digitalWrite(_sclk, HIGH);
rxByte |= (uint8_t)(::digitalRead(_miso) << b); // sample on rising edge
::delayMicroseconds(1); // hold time
rx = (rx << 1) | (::digitalRead(_miso) ? 1 : 0);
::digitalWrite(_sclk, LOW);
}
if (in) in[i] = rxByte;
if (in) in[i] = rx;
}
}
@ -213,18 +212,40 @@ public:
// DIO1 is expander pin 3; redirect the interrupt to IO_EXPANDER_IRQ (GPIO 42).
// TCA9535 /INT fires FALLING when any input changes (DIO1 going HIGH = packet ready).
void attachInterrupt(uint32_t pin, void (*cb)(void), uint32_t mode) override {
// RadioLib 7.x calls pinToInterrupt(irq_pin) BEFORE attachInterrupt().
// Without this override, pinToInterrupt(0x43) → digitalPinToInterrupt(67)
// which is invalid on ESP32-S3, so attachInterrupt() silently fails.
// Return IO_EXPANDER_IRQ (GPIO 42) directly for any expander pin.
uint32_t pinToInterrupt(uint32_t pin) override {
if (isExp(pin)) {
return (uint32_t)IO_EXPANDER_IRQ;
}
return ArduinoHal::pinToInterrupt(pin);
}
// Called with either:
// a) raw expander pin (e.g. 0x43) — direct call path
// b) IO_EXPANDER_IRQ (42) — via pinToInterrupt() path (RadioLib 7.x)
// In both cases redirect to GPIO 42 FALLING (TCA9535 /INT goes LOW when DIO1 rises).
void attachInterrupt(uint32_t pin, void (*cb)(void), uint32_t mode) override {
if (isExp(pin) || pin == (uint32_t)IO_EXPANDER_IRQ) {
// Clear any pending TCA9535 /INT by reading BOTH port registers.
// /INT stays LOW until the port that triggered it is read; floating
// Port 1 inputs keep /INT stuck even after reading Port 0 alone.
readReg(0x00); // Input Port 0 (radio pins)
readReg(0x01); // Input Port 1 (prevent spurious /INT from port 1)
::pinMode(IO_EXPANDER_IRQ, INPUT_PULLUP);
int gpio42 = ::digitalRead(IO_EXPANDER_IRQ);
::attachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ), cb, FALLING);
Serial.printf("[SenseCapHAL] DIO1 interrupt → GPIO %d (FALLING)\n", IO_EXPANDER_IRQ);
Serial.printf("[SenseCapHAL] DIO1 interrupt → GPIO %d state=%d (FALLING)\n",
IO_EXPANDER_IRQ, gpio42);
} else {
ArduinoHal::attachInterrupt(pin, cb, mode);
}
}
void detachInterrupt(uint32_t pin) override {
if (isExp(pin)) {
if (isExp(pin) || pin == (uint32_t)IO_EXPANDER_IRQ) {
::detachInterrupt(digitalPinToInterrupt(IO_EXPANDER_IRQ));
} else {
ArduinoHal::detachInterrupt(pin);

5
src/helpers/radiolib/RadioLibWrappers.cpp

@ -99,6 +99,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
int len = 0;
if (state & STATE_INT_READY) {
len = _radio->getPacketLength();
MESH_DEBUG_PRINTLN("RadioLibWrapper: IRQ fired, pkt_len=%d", len);
if (len > 0) {
if (len > sz) { len = sz; }
int err = _radio->readData(bytes, len);
@ -107,14 +108,14 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
len = 0;
n_recv_errors++;
} else {
// Serial.print(" readData() -> "); Serial.println(len);
MESH_DEBUG_PRINTLN("RadioLibWrapper: recv %d bytes RSSI=%.1f SNR=%.1f", len, getLastRSSI(), getLastSNR());
n_recv++;
}
}
state = STATE_IDLE; // need another startReceive()
}
if (state != STATE_RX) {
if (state != STATE_RX && !isReceivingPacket()) {
int err = _radio->startReceive();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;

38
variants/sensecap_indicator-espnow/SCIndicatorDisplay.h

@ -8,9 +8,35 @@
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
// Custom ST7701 panel subclass with SenseCap Indicator-specific init commands.
// The default Panel_ST7701 init sequence does not set MADCTL or SDIR, so the
// display outputs pixels in an orientation that doesn't match the physical
// RGB bus wiring on the SenseCap Indicator — resulting in a blank screen.
// These extra commands (ported from Meshtastic's LGFX_INDICATOR.h) apply the
// required vertical flip (MADCTL) and horizontal flip (SDIR) after the
// standard init sequence.
class Panel_SCIndicator : public lgfx::Panel_ST7701
{
public:
const uint8_t *getInitCommands(uint8_t listno) const override
{
static constexpr const uint8_t list1[] = {
0x36, 1, 0x10, // MADCTL: vertical flip
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x10, // Command2 BK0 SEL
0xC7, 1, 0x04, // SDIR: horizontal flip
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x00, // Command2 BK0 DIS
0xFF, 0xFF
};
switch (listno) {
case 1: return list1;
default: return lgfx::Panel_ST7701::getInitCommands(listno);
}
}
};
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7701 _panel_instance;
Panel_SCIndicator _panel_instance;
lgfx::Bus_RGB _bus_instance;
lgfx::Light_PWM _light_instance;
lgfx::Touch_FT5x06 _touch_instance;
@ -31,7 +57,7 @@ public:
cfg.panel_height = screenHeight;
cfg.offset_x = 0;
cfg.offset_y = 0;
cfg.offset_rotation = 1;
cfg.offset_rotation = 0;
_panel_instance.config(cfg);
}
@ -48,7 +74,7 @@ public:
auto cfg = _bus_instance.config();
cfg.panel = &_panel_instance;
cfg.freq_write = 8000000;
cfg.freq_write = 6000000; // 6 MHz — matches Meshtastic LGFX_INDICATOR
cfg.pin_henable = 18;
cfg.pin_pclk = 21;
@ -103,9 +129,9 @@ public:
cfg.x_max = 479;
cfg.y_min = 0;
cfg.y_max = 479;
cfg.pin_int = GPIO_NUM_NC;
cfg.pin_rst = GPIO_NUM_NC;
cfg.bus_shared = true;
cfg.pin_int = GPIO_NUM_NC; // do NOT use IO_EXPANDER for touch pins
cfg.pin_rst = GPIO_NUM_NC; // do NOT use IO_EXPANDER for touch pins
cfg.bus_shared = false;
cfg.offset_rotation = 0;
cfg.i2c_port = 0;

4
variants/sensecap_indicator-espnow/platformio.ini

@ -57,6 +57,8 @@ build_flags =
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=8
-D MESH_DEBUG=1
-D RADIOLIB_SPI_PARANOID=0
-D DISABLE_LORA_FOR_DISPLAY_TEST
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1
@ -68,6 +70,6 @@ lib_deps =
${SenseCapIndicator-ESPNow.lib_deps}
fbiego/ESP32Time@^2.0.6
lvgl/[email protected]
lovyan03/LovyanGFX@^1.1.16
lovyan03/LovyanGFX@^1.2.7
bitbank2/PNGdec@^1.1.6
densaugeo/base64 @ ~1.4.0

Loading…
Cancel
Save