From 6c00698d7bfd7d2aba4bceb56f524e098466ce88 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:16:09 +0000 Subject: [PATCH 1/2] Fix backlight on CrowPanel Advance 4.3/5.0/7.0 V1.2+ hardware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The V1.2 hardware revision of the CrowPanel Advance 7-inch (and the 4.3/5.0 siblings sharing this LGFX driver) replaced the TCA9534 I/O expander with an STC8H1K28 microcontroller at I2C 0x30 that now gates the backlight, buzzer, speaker and touch enable. The existing driver only programmed the TCA9534 at 0x18, so boards from V1.2 onward (incl. the V1.3 'latest' revision and V1.4 units currently in the wild) boot with the backlight off — the panel initialises correctly but is only visible under a flashlight. See meshtastic/firmware#7126. Probe I2C 0x30 at init_impl() time and, when the MCU is present, follow Elecrow's reference sequence (pulse GPIO1 low, write 250 to activate touch, write 0 for max backlight) instead of the TCA9534 path. Sleep/wakeup on V1.2+ hardware now toggle backlight via the same MCU (write 245 to disable, write 0 to re-enable). V1.0/V1.1 boards with the TCA9534 at 0x18 continue to use the original code path unchanged. Co-Authored-By: mirko sacchi --- include/graphics/LGFX/LGFX_ELECROW70.h | 93 ++++++++++++++++++++------ 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/include/graphics/LGFX/LGFX_ELECROW70.h b/include/graphics/LGFX/LGFX_ELECROW70.h index d3f03159..77ece9a8 100644 --- a/include/graphics/LGFX/LGFX_ELECROW70.h +++ b/include/graphics/LGFX/LGFX_ELECROW70.h @@ -10,6 +10,22 @@ #define FREQ_WRITE 14000000 #endif +// Backlight / buzzer / speaker / touch control on CrowPanel Advance 4.3"/5.0"/7.0": +// - HW rev V1.0/V1.1: TCA9534 I/O expander at I2C 0x18 (pin 1 gates backlight). +// - HW rev V1.2+ (incl. V1.3/V1.4): STC8H1K28 microcontroller at I2C 0x30. +// Writing a single byte selects the function, per Elecrow's reference code: +// V1.2: 0x05..0x10 (0x05 = off, 0x10 = max) +// V1.3+: 0..245 (0 = max, 244 = min, 245 = off) +// Both: 246/247 buzzer on/off, 248/249 speaker on/off, 250 activate touch +// Sending a value in the V1.3+ scale to a V1.2 board is a no-op (undefined +// commands are ignored), so we send the V1.3+ "max brightness" byte and let +// V1.2 hardware stay dark rather than risk flipping buzzer/speaker pins by +// reusing bytes with overlapping meanings. +#define CROW_MCU_I2C_ADDR 0x30 +#define CROW_MCU_BL_MAX 0 +#define CROW_MCU_BL_OFF 245 +#define CROW_MCU_TOUCH_ACTIVATE 250 + class LGFX_ELECROW70 : public lgfx::LGFX_Device { lgfx::Bus_RGB _bus_instance; @@ -24,24 +40,45 @@ class LGFX_ELECROW70 : public lgfx::LGFX_Device bool init_impl(bool use_reset, bool use_clear) override { - ioex.attach(Wire); - ioex.setDeviceAddress(0x18); - ioex.config(1, TCA9534::Config::OUT); - ioex.config(2, TCA9534::Config::OUT); - ioex.config(3, TCA9534::Config::OUT); - ioex.config(4, TCA9534::Config::OUT); - - ioex.output(1, TCA9534::Level::H); - ioex.output(3, TCA9534::Level::L); - ioex.output(4, TCA9534::Level::H); - - pinMode(1, OUTPUT); - digitalWrite(1, LOW); - ioex.output(2, TCA9534::Level::L); - delay(20); - ioex.output(2, TCA9534::Level::H); - delay(100); - pinMode(1, INPUT); + // Probe for the STC8H1K28 MCU (V1.2+) first. If present, we must not + // touch the TCA9534 path because those boards wire the backlight + // enable through the MCU instead of the I/O expander. + Wire.beginTransmission(CROW_MCU_I2C_ADDR); + hasMcu = (Wire.endTransmission() == 0); + + if (hasMcu) { + // V1.3+ some boards require pulsing GPIO1 low for the MCU to + // latch the first command after power-on (per Elecrow's + // reference code in CrowPanel-Advance-7 / lesson-03). + pinMode(1, OUTPUT); + digitalWrite(1, LOW); + delay(20); + pinMode(1, INPUT); + delay(100); + + writeMcu(CROW_MCU_TOUCH_ACTIVATE); // activate touch controller + writeMcu(CROW_MCU_BL_MAX); // backlight fully on + } else { + // V1.0/V1.1 fallback: TCA9534 I/O expander at 0x18. + ioex.attach(Wire); + ioex.setDeviceAddress(0x18); + ioex.config(1, TCA9534::Config::OUT); + ioex.config(2, TCA9534::Config::OUT); + ioex.config(3, TCA9534::Config::OUT); + ioex.config(4, TCA9534::Config::OUT); + + ioex.output(1, TCA9534::Level::H); + ioex.output(3, TCA9534::Level::L); + ioex.output(4, TCA9534::Level::H); + + pinMode(1, OUTPUT); + digitalWrite(1, LOW); + ioex.output(2, TCA9534::Level::L); + delay(20); + ioex.output(2, TCA9534::Level::H); + delay(100); + pinMode(1, INPUT); + } return LGFX_Device::init_impl(use_reset, use_clear); } @@ -137,15 +174,31 @@ class LGFX_ELECROW70 : public lgfx::LGFX_Device void sleep(void) { - ioex.output(1, TCA9534::Level::L); + if (hasMcu) { + writeMcu(CROW_MCU_BL_OFF); + } else { + ioex.output(1, TCA9534::Level::L); + } _panel->setSleep(true); } void wakeup(void) { - ioex.output(1, TCA9534::Level::H); + if (hasMcu) { + writeMcu(CROW_MCU_BL_MAX); + } else { + ioex.output(1, TCA9534::Level::H); + } _panel->setSleep(false); } private: + void writeMcu(uint8_t cmd) + { + Wire.beginTransmission(CROW_MCU_I2C_ADDR); + Wire.write(cmd); + Wire.endTransmission(); + } + TCA9534 ioex; + bool hasMcu = false; }; From 1608a459f83881142d2bdc60cddb0cd7e6c9f04e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:48:15 +0000 Subject: [PATCH 2/2] Cold-boot wake sequence for STC8H1K28 MCU on CrowPanel V1.2+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Probing I2C 0x30 directly is unreliable on cold boot — the MCU hasn't finished coming up yet and NACKs every probe attempt, which caused the driver to fall through to the TCA9534 path and leave the backlight disabled on V1.4 hardware after a power cycle (though it worked after a soft reset because the MCU retained state from the previous run). Discriminate hardware revisions via the TCA9534 at 0x18 instead — it's always passive and always ACKs on V1.0/V1.1. If the TCA9534 is absent we assume V1.2+ and execute Elecrow's reference wake sequence unconditionally (write 250, pulse GPIO1 low for 120 ms, write 0 for max brightness), repeated 5 times so a single NACK during MCU boot doesn't leave us dark. Co-Authored-By: mirko sacchi --- include/graphics/LGFX/LGFX_ELECROW70.h | 43 ++++++++++++++++---------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/include/graphics/LGFX/LGFX_ELECROW70.h b/include/graphics/LGFX/LGFX_ELECROW70.h index 77ece9a8..ba9a74f8 100644 --- a/include/graphics/LGFX/LGFX_ELECROW70.h +++ b/include/graphics/LGFX/LGFX_ELECROW70.h @@ -40,26 +40,35 @@ class LGFX_ELECROW70 : public lgfx::LGFX_Device bool init_impl(bool use_reset, bool use_clear) override { - // Probe for the STC8H1K28 MCU (V1.2+) first. If present, we must not - // touch the TCA9534 path because those boards wire the backlight - // enable through the MCU instead of the I/O expander. - Wire.beginTransmission(CROW_MCU_I2C_ADDR); - hasMcu = (Wire.endTransmission() == 0); + // Distinguish hardware revisions by probing for the TCA9534 I/O + // expander at 0x18: present on V1.0/V1.1, absent on V1.2+ where + // its role has been taken over by the STC8H1K28 microcontroller + // at 0x30. Probing 0x18 is reliable even on cold boot — the + // TCA9534 is passive and responds immediately, unlike the MCU + // which needs a wake sequence before it ACKs. + Wire.beginTransmission(0x18); + bool hasExpander = (Wire.endTransmission() == 0); + hasMcu = !hasExpander; if (hasMcu) { - // V1.3+ some boards require pulsing GPIO1 low for the MCU to - // latch the first command after power-on (per Elecrow's - // reference code in CrowPanel-Advance-7 / lesson-03). - pinMode(1, OUTPUT); - digitalWrite(1, LOW); - delay(20); - pinMode(1, INPUT); - delay(100); - - writeMcu(CROW_MCU_TOUCH_ACTIVATE); // activate touch controller - writeMcu(CROW_MCU_BL_MAX); // backlight fully on + // V1.2+ wake sequence, mirrors Elecrow's CrowPanel-Advance-7 + // reference code. The first write may NACK because the MCU + // is still booting; the GPIO1 pulse then latches/wakes it, + // and subsequent writes set the desired state. Repeat the + // command a few times so we don't rely on any single + // transmission succeeding. + for (int attempt = 0; attempt < 5; ++attempt) { + writeMcu(CROW_MCU_TOUCH_ACTIVATE); + pinMode(1, OUTPUT); + digitalWrite(1, LOW); + delay(120); + pinMode(1, INPUT); + delay(100); + writeMcu(CROW_MCU_BL_MAX); + delay(20); + } } else { - // V1.0/V1.1 fallback: TCA9534 I/O expander at 0x18. + // V1.0/V1.1: TCA9534 I/O expander at 0x18. ioex.attach(Wire); ioex.setDeviceAddress(0x18); ioex.config(1, TCA9534::Config::OUT);