T-Echo Card: apply hardware findings from Meshtastic PR #10267.

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.
This commit is contained in:
pelgraine
2026-04-25 19:35:44 +10:00
parent e74aa3b214
commit b5b11a2036
6 changed files with 291 additions and 67 deletions
+112 -20
View File
@@ -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 03.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 03.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
// -----------------------------------------------------------------------------
@@ -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 <helpers/NRF52Board.h>
#include "variant.h"
// WS2812 NeoPixel support — 3 independent LEDs on separate GPIOs
#if defined(HAS_RGB_LED)
#include <Adafruit_NeoPixel.h>
#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);
+15 -7
View File
@@ -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
// (047 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
@@ -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
+56 -17
View File
@@ -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 2899, pages 37 (rows 2463).
//
// After the driver's standard init, we send SETDISPLAYOFFSET = 24 to
// shift the COM output mapping so that data written to pages 04
// 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 37 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
}
+84 -20
View File
@@ -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 2899, pages 37 (rows 2463).
// 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 04 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 37 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 → 03.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