From b5b11a2036ce36453ba0df9be60227445f90f304 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:35:44 +1000 Subject: [PATCH] T-Echo Card: apply hardware findings from Meshtastic PR #10267. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Incorporates hardware-specific learnings from caveman99's Meshtastic T-Echo Card support PR (meshtastic/firmware#10267), cross-referenced against LilyGo's official t_echo_card_config.h pinmap. Battery (critical): - Add BATTERY_MEASUREMENT_CONTROL pin P0.31 — gates the resistive voltage divider feeding AIN0. Without toggling this pin, battery ADC reads are invalid (floating divider input). - TechoCardBoard::getBatteryVoltage() now drives P0.31 HIGH before SAADC read and LOW after to avoid parasitic drain. Display (critical): - Add OLED_DISPLAY_OFFSET = 24 for the SSD1315 72×40 panel. The physical display is mapped at GDDRAM pages 3–7 (rows 24–63); SETDISPLAYOFFSET and SETMULTIPLEX commands are sent after display.begin() in target.cpp to shift the visible window. Power rail (high priority): - RT9080 3V3 LDO now gets a clean HIGH→LOW→HIGH reset cycle with 100ms dwell in board.begin(), preventing brown-out when LoRa TX fires at +22 dBm after a soft reset. --- .../lilygo_techo_card_WIP/TechoCardBoard.cpp | 132 +++++++++++++++--- .../lilygo_techo_card_WIP/TechoCardBoard.h | 23 ++- variants/lilygo_techo_card_WIP/pins_arduino.h | 22 ++- variants/lilygo_techo_card_WIP/platformio.ini | 4 + variants/lilygo_techo_card_WIP/target.cpp | 73 +++++++--- variants/lilygo_techo_card_WIP/variant.h | 104 +++++++++++--- 6 files changed, 291 insertions(+), 67 deletions(-) diff --git a/variants/lilygo_techo_card_WIP/TechoCardBoard.cpp b/variants/lilygo_techo_card_WIP/TechoCardBoard.cpp index 84fe314d..6beba993 100644 --- a/variants/lilygo_techo_card_WIP/TechoCardBoard.cpp +++ b/variants/lilygo_techo_card_WIP/TechoCardBoard.cpp @@ -1,5 +1,10 @@ // ============================================================================= // TechoCardBoard — Implementation for LilyGo T-Echo Card +// +// Patches applied from Meshtastic PR #10267 (caveman99): +// 1. RT9080 power rail reset cycle in begin() — prevents LoRa TX brown-out +// 2. Battery measurement control pin (P0.31) — enables voltage divider +// 3. WS2812 NeoPixel implementation via Adafruit_NeoPixel // ============================================================================= #include "TechoCardBoard.h" @@ -11,6 +16,49 @@ void TechoCardBoard::begin() { NRF52BoardDCDC::begin(); + // ------------------------------------------------------------------------- + // RT9080 3V3 rail: clean reset cycle + // + // From Meshtastic PR #10267 (earlyInitVariant): if the nRF52840 was in a + // half-enabled state from a previous soft reset, the RT9080 LDO can be in + // an indeterminate state. Toggling EN HIGH→LOW→HIGH with 100ms dwell + // forces a clean power-on. Without this, the 3V3 rail can brown-out when + // LoRa TX fires at full power (+22 dBm). + // ------------------------------------------------------------------------- + #if PIN_OLED_EN >= 0 + pinMode(PIN_OLED_EN, OUTPUT); + digitalWrite(PIN_OLED_EN, HIGH); + delay(100); + digitalWrite(PIN_OLED_EN, LOW); + delay(100); + digitalWrite(PIN_OLED_EN, HIGH); + delay(100); + #endif + + // ------------------------------------------------------------------------- + // Park peripheral enable pins LOW before the rest of setup runs. + // Prevents peripherals from sinking current while the 3V3 rail is ramping. + // (Adapted from Meshtastic PR #10267 earlyInitVariant) + // ------------------------------------------------------------------------- + #if defined(HAS_GPS) && PIN_GPS_EN >= 0 + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, LOW); + #endif + #if defined(HAS_GPS) && PIN_GPS_RF_EN >= 0 + pinMode(PIN_GPS_RF_EN, OUTPUT); + digitalWrite(PIN_GPS_RF_EN, LOW); + #endif + #if defined(HAS_BUZZER) && PIN_BUZZER >= 0 + pinMode(PIN_BUZZER, OUTPUT); + digitalWrite(PIN_BUZZER, LOW); + #endif + + // Configure battery measurement control pin + #if defined(BATTERY_MEASUREMENT_CONTROL) + pinMode(BATTERY_MEASUREMENT_CONTROL, OUTPUT); + digitalWrite(BATTERY_MEASUREMENT_CONTROL, !BATTERY_MEASUREMENT_ACTIVE); + #endif + // Configure battery ADC pin as analog input pinMode(PIN_VBAT_READ, INPUT); @@ -18,29 +66,31 @@ void TechoCardBoard::begin() { pinMode(PIN_BUTTON_A, INPUT_PULLUP); pinMode(PIN_BUTTON_BOOT, INPUT_PULLUP); - // Buzzer off - #if defined(HAS_BUZZER) && PIN_BUZZER >= 0 - pinMode(PIN_BUZZER, OUTPUT); - digitalWrite(PIN_BUZZER, LOW); - #endif - - // RGB LED off at boot + // Initialise WS2812 NeoPixels (3 independent LEDs, all off at boot) #if defined(HAS_RGB_LED) - ledOff(); + _pixel_power.begin(); + _pixel_power.clear(); + _pixel_power.show(); + + _pixel_notify.begin(); + _pixel_notify.clear(); + _pixel_notify.show(); + + _pixel_pairing.begin(); + _pixel_pairing.clear(); + _pixel_pairing.show(); #endif } // ----------------------------------------------------------------------------- // Battery voltage reading via nRF52840 SAADC // -// The T-Echo Card has a voltage divider on AIN0 (P0.02). -// nRF52840 SAADC: 12-bit, internal 0.6V reference, configurable gain. -// With 1/6 gain: input range 0–3.6V. Multiply by divider ratio (ADC_MULTIPLIER). +// The T-Echo Card has a gated voltage divider on AIN0 (P0.02). +// BATTERY_MEASUREMENT_CONTROL (P0.31) must be driven HIGH to enable the +// divider before reading, and LOW after to avoid parasitic drain. // -// NOTE: The T-Echo Lite has a known issue reading 100% / 6.00V constantly. -// This is likely caused by incorrect SAADC configuration (wrong gain, wrong -// reference, or the pin being pulled high by the charging circuit). -// We use explicit SAADC register programming to avoid that issue. +// nRF52840 SAADC: 12-bit, internal 0.6V reference, 1/6 gain. +// With 1/6 gain: input range 0–3.6V. Multiply by divider ratio (ADC_MULTIPLIER). // ----------------------------------------------------------------------------- float TechoCardBoard::getBatteryVoltage() { uint32_t now = millis(); @@ -50,6 +100,12 @@ float TechoCardBoard::getBatteryVoltage() { return _cached_battery_mv; } + // Enable battery voltage divider + #if defined(BATTERY_MEASUREMENT_CONTROL) + digitalWrite(BATTERY_MEASUREMENT_CONTROL, BATTERY_MEASUREMENT_ACTIVE); + delay(5); // Allow divider to settle + #endif + // Configure SAADC for single-shot reading NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit; @@ -91,6 +147,11 @@ float TechoCardBoard::getBatteryVoltage() { // Disable SAADC to save power NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Disabled; + // Disable battery voltage divider to save power + #if defined(BATTERY_MEASUREMENT_CONTROL) + digitalWrite(BATTERY_MEASUREMENT_CONTROL, !BATTERY_MEASUREMENT_ACTIVE); + #endif + // Convert: voltage = (result / 4096) * 3.6V * ADC_MULTIPLIER * 1000 (mV) if (result < 0) result = 0; float voltage_mv = ((float)result / 4096.0f) * 3600.0f * _adc_multiplier; @@ -137,14 +198,24 @@ void TechoCardBoard::enableSpeaker(bool enable) { } // ----------------------------------------------------------------------------- -// RGB LED — WS2812 via NeoPixel protocol -// Simple bit-bang implementation for nRF52840 at 64MHz +// RGB LEDs — WS2812 via Adafruit_NeoPixel +// +// Three independent WS2812s on separate data pins (not a chain). +// Each is a 1-pixel NeoPixel strand. +// +// setLED() drives all three to the same colour (legacy interface). +// For per-LED control, use setStatusLED() with a role index. // ----------------------------------------------------------------------------- void TechoCardBoard::setLED(uint8_t r, uint8_t g, uint8_t b) { #if defined(HAS_RGB_LED) - // TODO: Implement WS2812 bit-bang or use Adafruit_NeoPixel library - // For initial bringup, just use the Adafruit_NeoPixel library - // which is available in the Adafruit nRF52 Arduino core + uint32_t color = Adafruit_NeoPixel::Color(r, g, b); + _pixel_power.setPixelColor(0, color); + _pixel_power.show(); + _pixel_notify.setPixelColor(0, color); + _pixel_notify.show(); + _pixel_pairing.setPixelColor(0, color); + _pixel_pairing.show(); + #else (void)r; (void)g; (void)b; #endif } @@ -153,6 +224,27 @@ void TechoCardBoard::ledOff() { setLED(0, 0, 0); } +void TechoCardBoard::setStatusLED(uint8_t led_index, uint32_t color) { + #if defined(HAS_RGB_LED) + switch (led_index) { + case 0: // Power / charge + _pixel_power.setPixelColor(0, color); + _pixel_power.show(); + break; + case 1: // Notification / mesh activity + _pixel_notify.setPixelColor(0, color); + _pixel_notify.show(); + break; + case 2: // BLE pairing + _pixel_pairing.setPixelColor(0, color); + _pixel_pairing.show(); + break; + } + #else + (void)led_index; (void)color; + #endif +} + // ----------------------------------------------------------------------------- // Buzzer — PWM tone generation // ----------------------------------------------------------------------------- diff --git a/variants/lilygo_techo_card_WIP/TechoCardBoard.h b/variants/lilygo_techo_card_WIP/TechoCardBoard.h index dc0a5595..cfceca63 100644 --- a/variants/lilygo_techo_card_WIP/TechoCardBoard.h +++ b/variants/lilygo_techo_card_WIP/TechoCardBoard.h @@ -4,10 +4,11 @@ // TechoCardBoard — Board class for LilyGo T-Echo Card // // Extends NRF52BoardDCDC with: -// - Battery ADC (AIN0, P0.02) with solar charging via BQ25896 +// - Battery ADC (AIN0, P0.02) with gated voltage divider (P0.31) +// - Solar charging via BQ25896 // - GPS power control (L76K) // - Speaker/mic enable -// - RGB LED control +// - WS2812 RGB LEDs (3 independent, via Adafruit_NeoPixel) // - Buzzer // - NFC NDEF contact sharing // ============================================================================= @@ -16,6 +17,11 @@ #include #include "variant.h" +// WS2812 NeoPixel support — 3 independent LEDs on separate GPIOs +#if defined(HAS_RGB_LED) + #include +#endif + #ifdef NRF52_POWER_MANAGEMENT // Power management config for T-Echo Card // AIN0 (P0.02) for battery voltage sensing @@ -33,6 +39,13 @@ private: uint32_t _last_battery_read; float _cached_battery_mv; + // Three independent WS2812 NeoPixels (1 LED per strand) + #if defined(HAS_RGB_LED) + Adafruit_NeoPixel _pixel_power = Adafruit_NeoPixel(1, PIN_RGB_LED_1, NEO_GRB + NEO_KHZ800); + Adafruit_NeoPixel _pixel_notify = Adafruit_NeoPixel(1, PIN_RGB_LED_2, NEO_GRB + NEO_KHZ800); + Adafruit_NeoPixel _pixel_pairing = Adafruit_NeoPixel(1, PIN_RGB_LED_3, NEO_GRB + NEO_KHZ800); + #endif + public: TechoCardBoard() : _adc_multiplier(ADC_MULTIPLIER), @@ -53,10 +66,14 @@ public: // Speaker power control void enableSpeaker(bool enable); - // RGB LED + // RGB LEDs — all three to same colour (legacy interface) void setLED(uint8_t r, uint8_t g, uint8_t b); void ledOff(); + // Per-LED status control (0=power, 1=notify, 2=pairing) + // colour is packed 0xRRGGBB — use NEOPIXEL_COLOR_* defines from variant.h + void setStatusLED(uint8_t led_index, uint32_t color); + // Buzzer void buzz(uint16_t freq_hz, uint16_t duration_ms); diff --git a/variants/lilygo_techo_card_WIP/pins_arduino.h b/variants/lilygo_techo_card_WIP/pins_arduino.h index aa339a52..f89e92f0 100644 --- a/variants/lilygo_techo_card_WIP/pins_arduino.h +++ b/variants/lilygo_techo_card_WIP/pins_arduino.h @@ -7,14 +7,18 @@ // Only needed if creating a custom board variant inside the Adafruit nRF52 // Arduino framework package. If using build flag overrides in platformio.ini, // this file is optional. +// +// Pin mapping cross-referenced against: +// - LilyGo official: T-Echo-Card/libraries/private_library/t_echo_card_config.h +// - Meshtastic PR #10267 (caveman99 T-Echo-Card support) // ============================================================================= // On nRF52840, Arduino digital pin numbers map 1:1 to nRF GPIO numbers // (0–47 for port 0 and port 1). -// LED +// LED — WS2812 addressable (no plain GPIO LED on this board) #define LED_BUILTIN PIN_LED1 -#define PIN_LED1 39 // WS2812 RGB LED data (1, 7) +#define PIN_LED1 39 // WS2812 RGB LED data 1 (1, 7) #define LED_STATE_ON 1 // Buttons @@ -39,11 +43,15 @@ // Analog #define PIN_A0 2 // (0, 2) — Battery ADC / AIN0 -// QSPI Flash (ZD25WQ32CEIGR 4MB) -// nRF52840 QSPI uses fixed pins — framework handles these -// #define PIN_QSPI_SCK 19 // Conflict with GPS TX — check if QSPI is on different pins -// NOTE: QSPI pin mapping needs verification on actual hardware. -// The T-Echo Card may use SPI (not QSPI) for external flash. +// QSPI Flash — ZD25WQ32CEIGR 4MB +// Confirmed from LilyGo t_echo_card_config.h and Meshtastic PR #10267. +// These are on a dedicated SPI bus, separate from LoRa SPI. +#define PIN_QSPI_SCK 4 // (0, 4) +#define PIN_QSPI_CS 12 // (0, 12) +#define PIN_QSPI_IO0 6 // (0, 6) — MOSI / D0 +#define PIN_QSPI_IO1 8 // (0, 8) — MISO / D1 +#define PIN_QSPI_IO2 41 // (1, 9) — WP / D2 +#define PIN_QSPI_IO3 26 // (0, 26) — HOLD / D3 // NFC (dedicated nRF52840 NFC pins — not GPIO-assignable) // NFC1 = P0.09, NFC2 = P0.10 diff --git a/variants/lilygo_techo_card_WIP/platformio.ini b/variants/lilygo_techo_card_WIP/platformio.ini index 735b037e..fc00db66 100644 --- a/variants/lilygo_techo_card_WIP/platformio.ini +++ b/variants/lilygo_techo_card_WIP/platformio.ini @@ -5,6 +5,9 @@ ; + MAX98357 Speaker + MP34DT05 PDM Mic + ICM20948 IMU + Solar + NFC ; ; Platform: nRF52 (Adafruit nRF52 Arduino) +; +; Patches applied from Meshtastic PR #10267 (caveman99): +; - Adafruit_NeoPixel added for WS2812 RGB LED support ; ============================================================================= ; --- Base configuration for all T-Echo Card builds --- @@ -57,6 +60,7 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${nrf52_base.lib_deps} olikraus/U8g2 @ ^2.35.19 stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit NeoPixel @ ^1.12.3 ; ============================================================================= ; Build Environments diff --git a/variants/lilygo_techo_card_WIP/target.cpp b/variants/lilygo_techo_card_WIP/target.cpp index dd0a30f2..af8860e0 100644 --- a/variants/lilygo_techo_card_WIP/target.cpp +++ b/variants/lilygo_techo_card_WIP/target.cpp @@ -1,7 +1,12 @@ // ============================================================================= // MeshCore target implementation for LilyGo T-Echo Card // -// nRF52840 + SX1262 (HPB16B3 module) + SSD1315 OLED + L76K GPS +// nRF52840 + SX1262 (HPB16B3 / S62F module) + SSD1315 OLED + L76K GPS +// +// Patches applied from Meshtastic PR #10267 (caveman99): +// - RT9080 power rail reset cycle (handled in board.begin()) +// - SSD1315 OLED display offset for 72×40 panel +// - SX1262 TCXO voltage via DIO3 // ============================================================================= #include "target.h" @@ -40,27 +45,27 @@ mesh::DataStore data_store; // --- Target initialization --- void target_setup() { - // Enable OLED power - #if PIN_OLED_EN >= 0 - pinMode(PIN_OLED_EN, OUTPUT); - digitalWrite(PIN_OLED_EN, HIGH); - delay(10); - #endif + // ------------------------------------------------------------------------- + // Board-level init FIRST — this cycles the RT9080 3V3 rail and parks + // peripheral enable pins LOW. Must happen before any other peripheral + // access to prevent brown-out on LoRa TX. + // ------------------------------------------------------------------------- + board.begin(); - // Enable GPS power + // ------------------------------------------------------------------------- + // Enable GPS power (was parked LOW in board.begin()) + // ------------------------------------------------------------------------- #if defined(HAS_GPS) && PIN_GPS_EN >= 0 - pinMode(PIN_GPS_EN, OUTPUT); digitalWrite(PIN_GPS_EN, HIGH); delay(10); #endif // GPS RF/LNA enable #if defined(HAS_GPS) && PIN_GPS_RF_EN >= 0 - pinMode(PIN_GPS_RF_EN, OUTPUT); digitalWrite(PIN_GPS_RF_EN, HIGH); #endif - // Initialize GPS UART + // Initialise GPS UART #if defined(HAS_GPS) Serial1.setPins(PIN_GPS_RX, PIN_GPS_TX); Serial1.begin(GPS_BAUDRATE); @@ -76,20 +81,54 @@ void target_setup() { #endif #endif - // Initialize I2C + // Initialise I2C Wire.setPins(I2C_SDA, I2C_SCL); Wire.begin(); Wire.setClock(400000); - // Initialize display + // ------------------------------------------------------------------------- + // Initialise display + // + // The SSD1315 has a 128×64 GDDRAM but the physical panel is 72×40, + // hardwired at columns 28–99, pages 3–7 (rows 24–63). + // + // After the driver's standard init, we send SETDISPLAYOFFSET = 24 to + // shift the COM output mapping so that data written to pages 0–4 + // appears on the physical display. + // + // If content is still blank or shifted when hardware arrives, the + // alternative is to modify the PAGEADDR writes in the display() method + // to target pages 3–7 directly (as Meshtastic does with setYOffset(3)). + // ------------------------------------------------------------------------- #ifdef DISPLAY_CLASS display.begin(OLED_I2C_ADDR); + + #if defined(OLED_DISPLAY_OFFSET) && OLED_DISPLAY_OFFSET > 0 + // SSD1306/SSD1315 command 0xD3: Set Display Offset + // Shifts COM output by OLED_DISPLAY_OFFSET rows (24 = 3 pages) + display.sendCommand(0xD3); // SETDISPLAYOFFSET + display.sendCommand(OLED_DISPLAY_OFFSET); + + // SSD1306/SSD1315 command 0xA8: Set Multiplex Ratio + // Must match actual panel height (40 - 1 = 39) + display.sendCommand(0xA8); // SETMULTIPLEX + display.sendCommand(OLED_HEIGHT - 1); // 39 + #endif #endif - // Board-level init - board.begin(); - - // Initialize LoRa radio + // ------------------------------------------------------------------------- + // Initialise LoRa radio + // ------------------------------------------------------------------------- // SX1262 DIO2 as RF switch control (common for HPB16B3 modules) radio_driver.setDio2AsRfSwitch(true); + + // SX1262 TCXO via DIO3 + // Meshtastic PR #10267 sets this to 1.8V. If the module has a TCXO + // powered by DIO3, this must be configured before any frequency setting + // or the oscillator won't start, causing frequency drift or init failure. + // TODO: Verify on hardware. If the module uses a crystal (not TCXO), + // remove this call — it would waste current on DIO3. + #if defined(SX126X_DIO3_TCXO_VOLTAGE) + radio_driver.setTCXO(SX126X_DIO3_TCXO_VOLTAGE); + #endif } diff --git a/variants/lilygo_techo_card_WIP/variant.h b/variants/lilygo_techo_card_WIP/variant.h index 5c9fc118..6ccc8370 100644 --- a/variants/lilygo_techo_card_WIP/variant.h +++ b/variants/lilygo_techo_card_WIP/variant.h @@ -7,6 +7,10 @@ // + MP34DT05 PDM Microphone + ICM20948 IMU + BQ25896 Charger + Solar // // Pin notation from LilyGo pinmap: (port, pin) → nRF GPIO = port*32 + pin +// +// Cross-referenced against: +// - LilyGo official: T-Echo-Card/libraries/private_library/t_echo_card_config.h +// - Meshtastic PR #10267 (caveman99 T-Echo-Card support) // ============================================================================= #define LILYGO_TECHO_CARD @@ -30,14 +34,24 @@ #define P_LORA_BUSY 14 // (0, 14) — BUSY #define P_LORA_DIO_1 40 // (1, 8) — DIO1 / interrupt -// RF switch control (HPB16B3 module) -#define LORA_DIO2 5 // (0, 5) — DIO2 (TXCO / RF switch) -#define LORA_RF_VC1 27 // (0, 27) — RF_VC1 -#define LORA_RF_VC2 33 // (1, 1) — RF_VC2 +// RF switch control (HPB16B3 / S62F module) +// DIO2 is used internally by the SX1262 for RF switch control. +// RF_VC1 / RF_VC2 are external PA/LNA select lines. +// Meshtastic PR #10267 maps these as TXEN/RXEN — verify on hardware +// whether DIO2-as-RF-switch alone is sufficient, or if VC1/VC2 are also +// needed for full TX/RX performance. +#define LORA_DIO2 5 // (0, 5) — DIO2 (RF switch / TXCO) +#define LORA_RF_VC1 27 // (0, 27) — RF_VC1 (potential TXEN) +#define LORA_RF_VC2 33 // (1, 1) — RF_VC2 (potential RXEN) -// LoRa radio power enable (from schematic: LORA_EN drives RT9080 for LORA_VDD) -// NOTE: Confirm actual GPIO — pinmap shows dedicated enable pin -// For now use -1 if always powered +// SX1262 TCXO voltage via DIO3 +// Meshtastic PR #10267 sets this to 1.8V. Without it, the TCXO may not +// start and the radio will have frequency drift or fail to init. +// Confirm on hardware — if the module has a TCXO fed by DIO3, this is needed. +#define SX126X_DIO3_TCXO_VOLTAGE 1.8f + +// LoRa radio power: RT9080 controls the 3V3 rail for all peripherals +// including LoRa. No dedicated LoRa power enable pin. #define PIN_LORA_EN -1 // Default radio settings (Australia) @@ -47,6 +61,21 @@ // ----------------------------------------------------------------------------- // 0.42" OLED Display — SSD1315 (SSD1306-compatible), 72×40, I2C +// +// The SSD1315 has a 128×64 GDDRAM, but the physical panel is only 72×40. +// The visible window is mapped at columns 28–99, pages 3–7 (rows 24–63). +// This means: +// - Horizontal: auto-centred by driver ((128 - 72) / 2 = 28) +// - Vertical: need SETDISPLAYOFFSET = 24 (3 pages × 8 rows) so that +// data written to pages 0–4 appears on the physical display. +// +// If the MeshCore OLEDDisplay driver's display() method sends PAGEADDR +// starting at page 0, the SETDISPLAYOFFSET command should handle the +// mapping. If content appears shifted or blank, the alternative is to +// modify the PAGEADDR commands to write to pages 3–7 directly. +// +// Ref: Meshtastic PR #10267 uses setYOffset(3) to shift every PAGEADDR +// write, plus GEOMETRY_72_40 which sets SETMULTIPLEX to 39. // ----------------------------------------------------------------------------- #define HAS_OLED 1 #define OLED_I2C_ADDR 0x3C @@ -55,7 +84,12 @@ #define OLED_SDA I2C_SDA #define OLED_SCL I2C_SCL -// OLED power control via RT9080 enable pin +// SSD1315 display offset: 3 pages = 24 rows +// Applied via SETDISPLAYOFFSET (0xD3) after display.begin() in target.cpp +#define OLED_DISPLAY_OFFSET 24 + +// OLED / peripheral power control via RT9080 enable pin +// This controls the 3V3 rail for OLED, GPS, LoRa, and sensors. #define PIN_OLED_EN 30 // (0, 30) — RT9080_EN // No hardware reset pin for OLED on T-Echo Card @@ -80,11 +114,18 @@ #define BATTERY_ADC_AIN 0 // nRF SAADC AIN channel number #define BATTERY_CAPACITY_MAH 800 +// Battery voltage divider enable gate +// P0.31 controls a FET/switch that enables the resistive divider feeding +// AIN0. Must be driven HIGH before ADC read and LOW after to avoid +// parasitic drain through the divider. +// Source: LilyGo t_echo_card_config.h → BATTERY_MEASUREMENT_CONTROL +// Confirmed: Meshtastic PR #10267 → ADC_CTRL (0 + 31), ADC_CTRL_ENABLED HIGH +#define BATTERY_MEASUREMENT_CONTROL 31 // (0, 31) +#define BATTERY_MEASUREMENT_ACTIVE HIGH + // Battery voltage divider calibration -// The T-Echo Lite has issues reading battery correctly — we'll need to -// calibrate this with actual hardware. Starting with a reasonable default. // nRF52840 SAADC: 0.6V internal ref, 1/6 gain → 0–3.6V range -// If there's a voltage divider (e.g. 2:1), multiply by 2. +// With 2:1 resistive divider, multiply by 2 to get actual cell voltage. // Adjust ADC_MULTIPLIER after measuring real voltage vs ADC reading. #ifndef ADC_MULTIPLIER #define ADC_MULTIPLIER 2.0f @@ -132,13 +173,28 @@ #define PIN_BUZZER 38 // (1, 6) — piezo buzzer data / PWM // ----------------------------------------------------------------------------- -// WS2812 RGB LED (3 LEDs in series) +// WS2812 RGB LEDs — 3 independent LEDs on separate data lines (NOT a chain) +// +// Confirmed by Meshtastic PR #10267: each WS2812 is on its own GPIO, +// driven as a 1-pixel NeoPixel strand. The bare-die WS2812s are very +// bright at full intensity — scale to ~25% (0x40 max per channel). +// +// Role assignments (matching Meshtastic PR #10267): +// DATA_1 (P1.7) → power/charge status (red) +// DATA_2 (P1.12) → notification / mesh activity (green) +// DATA_3 (P0.28) → BLE pairing status (blue) // ----------------------------------------------------------------------------- #define HAS_RGB_LED 1 -#define PIN_RGB_LED_1 39 // (1, 7) — WS2812 data 1 -#define PIN_RGB_LED_2 44 // (1, 12) — WS2812 data 2 -#define PIN_RGB_LED_3 28 // (0, 28) — WS2812 data 3 -// Typically only one data pin drives the chain; confirm wiring on hardware +#define PIN_RGB_LED_1 39 // (1, 7) — WS2812 data 1 (power/charge) +#define PIN_RGB_LED_2 44 // (1, 12) — WS2812 data 2 (notification) +#define PIN_RGB_LED_3 28 // (0, 28) — WS2812 data 3 (BLE pairing) + +// Default NeoPixel colours at 25% brightness (0x40 max per channel) +#define NEOPIXEL_COLOR_POWER 0x400000 // red +#define NEOPIXEL_COLOR_NOTIFY 0x004000 // green +#define NEOPIXEL_COLOR_PAIRING 0x000040 // blue + +// Legacy aliases (kept for any code referencing these) #define PIN_NEOPIXEL PIN_RGB_LED_1 #define NUM_NEOPIXELS 3 @@ -159,14 +215,22 @@ #define HAS_NFC 1 // ----------------------------------------------------------------------------- -// External Flash — ZD25WQ32CEIGR (4MB SPI Flash) -// Uses QSPI interface on nRF52840 -// Pin mapping from nRF52840 QSPI peripheral (typically fixed) +// External Flash — ZD25WQ32CEIGR (4MB QSPI Flash) +// +// Pin mapping confirmed from LilyGo t_echo_card_config.h and Meshtastic +// PR #10267. These are on a separate SPI bus from LoRa. +// The Adafruit nRF52 core supports QSPI via Adafruit_SPIFlash + LittleFS. // ----------------------------------------------------------------------------- #define HAS_EXT_FLASH 1 +#define PIN_QSPI_SCK 4 // (0, 4) +#define PIN_QSPI_CS 12 // (0, 12) +#define PIN_QSPI_IO0 6 // (0, 6) — MOSI / D0 +#define PIN_QSPI_IO1 8 // (0, 8) — MISO / D1 +#define PIN_QSPI_IO2 41 // (1, 9) — WP / D2 +#define PIN_QSPI_IO3 26 // (0, 26) — HOLD / D3 // ----------------------------------------------------------------------------- -// No SD Card on T-Echo Card (unlike T-Echo which has e-paper + no SD either) +// No SD Card on T-Echo Card // Settings stored in LittleFS on internal/external flash // ----------------------------------------------------------------------------- // #define HAS_SDCARD