From aacf8c777f08b20d39df8f95062413d514bf66a9 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:24:37 +1000 Subject: [PATCH] prelim techo card WIP files while I wait for hardware to arrive. claude putting excessive comments in platformio descriptors --- boards/lilygo_techo_card.json | 48 +++++ variants/lilygo_techo_card/TechoCardBoard.cpp | 169 +++++++++++++++ variants/lilygo_techo_card/TechoCardBoard.h | 68 ++++++ variants/lilygo_techo_card/pins_arduino.h | 60 ++++++ variants/lilygo_techo_card/platformio.ini | 106 ++++++++++ variants/lilygo_techo_card/target.cpp | 95 +++++++++ variants/lilygo_techo_card/target.h | 39 ++++ variants/lilygo_techo_card/variant.h | 193 ++++++++++++++++++ 8 files changed, 778 insertions(+) create mode 100644 boards/lilygo_techo_card.json create mode 100644 variants/lilygo_techo_card/TechoCardBoard.cpp create mode 100644 variants/lilygo_techo_card/TechoCardBoard.h create mode 100644 variants/lilygo_techo_card/pins_arduino.h create mode 100644 variants/lilygo_techo_card/platformio.ini create mode 100644 variants/lilygo_techo_card/target.cpp create mode 100644 variants/lilygo_techo_card/target.h create mode 100644 variants/lilygo_techo_card/variant.h diff --git a/boards/lilygo_techo_card.json b/boards/lilygo_techo_card.json new file mode 100644 index 00000000..5fb180c7 --- /dev/null +++ b/boards/lilygo_techo_card.json @@ -0,0 +1,48 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": [ + "-DARDUINO_NRF52840_TECHO_CARD", + "-DNRF52840_XXAA", + "-DNRF52_SERIES" + ], + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x8029"]], + "mcu": "nrf52840", + "variant": "lilygo_techo_card", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "usb_product": "T-Echo Card" + }, + "connectivity": ["bluetooth", "lora"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "openocd_target": "nrf52840" + }, + "frameworks": ["arduino"], + "name": "LilyGo T-Echo Card (nRF52840, SX1262, 4MB Flash)", + "upload": { + "flash_size": "796KB", + "maximum_ram_size": 248832, + "maximum_size": 815104, + "native_usb": true, + "protocol": "nrfutil", + "protocols": ["nrfutil", "jlink", "cmsis-dap"], + "require_upload_port": true, + "speed": 115200, + "use_1200bps_touch": true, + "wait_for_upload_port": true + }, + "url": "https://github.com/Xinyuan-LilyGO/T-Echo-Card", + "vendor": "LILYGO" +} diff --git a/variants/lilygo_techo_card/TechoCardBoard.cpp b/variants/lilygo_techo_card/TechoCardBoard.cpp new file mode 100644 index 00000000..84fe314d --- /dev/null +++ b/variants/lilygo_techo_card/TechoCardBoard.cpp @@ -0,0 +1,169 @@ +// ============================================================================= +// TechoCardBoard — Implementation for LilyGo T-Echo Card +// ============================================================================= + +#include "TechoCardBoard.h" +#include "variant.h" + +// nRF52840 SAADC includes +#include "nrf.h" + +void TechoCardBoard::begin() { + NRF52BoardDCDC::begin(); + + // Configure battery ADC pin as analog input + pinMode(PIN_VBAT_READ, INPUT); + + // Configure button(s) + 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 + #if defined(HAS_RGB_LED) + ledOff(); + #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). +// +// 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. +// ----------------------------------------------------------------------------- +float TechoCardBoard::getBatteryVoltage() { + uint32_t now = millis(); + + // Cache battery reading — only read every 10 seconds + if (_cached_battery_mv > 0 && (now - _last_battery_read) < 10000) { + return _cached_battery_mv; + } + + // Configure SAADC for single-shot reading + NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit; + + // Channel 0: AIN0 (P0.02), 1/6 gain, internal 0.6V reference + // Effective range: 0 – 3.6V + NRF_SAADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput0; // AIN0 + NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC; // Single-ended + NRF_SAADC->CH[0].CONFIG = + (SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos) | + (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) | + (SAADC_CH_CONFIG_TACQ_40us << SAADC_CH_CONFIG_TACQ_Pos) | + (SAADC_CH_CONFIG_MODE_SE << SAADC_CH_CONFIG_MODE_Pos) | + (SAADC_CH_CONFIG_BURST_Disabled << SAADC_CH_CONFIG_BURST_Pos) | + (SAADC_CH_CONFIG_RESP_Bypass << SAADC_CH_CONFIG_RESP_Pos) | + (SAADC_CH_CONFIG_RESN_Bypass << SAADC_CH_CONFIG_RESN_Pos); + + // Set up result buffer + volatile int16_t result = 0; + NRF_SAADC->RESULT.PTR = (uint32_t)&result; + NRF_SAADC->RESULT.MAXCNT = 1; + + // Enable, calibrate on first use + NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled; + + // Start and wait for sample + NRF_SAADC->EVENTS_END = 0; + NRF_SAADC->TASKS_START = 1; + while (!NRF_SAADC->EVENTS_STARTED); + NRF_SAADC->EVENTS_STARTED = 0; + + NRF_SAADC->TASKS_SAMPLE = 1; + while (!NRF_SAADC->EVENTS_END); + NRF_SAADC->EVENTS_END = 0; + + NRF_SAADC->TASKS_STOP = 1; + while (!NRF_SAADC->EVENTS_STOPPED); + NRF_SAADC->EVENTS_STOPPED = 0; + + // Disable SAADC to save power + NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Disabled; + + // 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; + + _cached_battery_mv = voltage_mv; + _last_battery_read = now; + + return voltage_mv; +} + +uint8_t TechoCardBoard::getBatteryPercent() { + float mv = getBatteryVoltage(); + if (mv <= 0) return 0; + + // Simple linear approximation for single-cell LiPo + // 3200 mV = 0%, 4200 mV = 100% + if (mv >= 4200.0f) return 100; + if (mv <= 3200.0f) return 0; + return (uint8_t)(((mv - 3200.0f) / 1000.0f) * 100.0f); +} + +// ----------------------------------------------------------------------------- +// GPS power control +// ----------------------------------------------------------------------------- +void TechoCardBoard::enableGPS(bool enable) { + #if defined(HAS_GPS) && PIN_GPS_EN >= 0 + digitalWrite(PIN_GPS_EN, enable ? HIGH : LOW); + #endif + #if defined(HAS_GPS) && PIN_GPS_RF_EN >= 0 + digitalWrite(PIN_GPS_RF_EN, enable ? HIGH : LOW); + #endif +} + +// ----------------------------------------------------------------------------- +// Speaker power control +// ----------------------------------------------------------------------------- +void TechoCardBoard::enableSpeaker(bool enable) { + #if defined(HAS_SPEAKER) + digitalWrite(PIN_SPK_EN, enable ? HIGH : LOW); + #if PIN_SPK_EN2 >= 0 + digitalWrite(PIN_SPK_EN2, enable ? HIGH : LOW); + #endif + #endif +} + +// ----------------------------------------------------------------------------- +// RGB LED — WS2812 via NeoPixel protocol +// Simple bit-bang implementation for nRF52840 at 64MHz +// ----------------------------------------------------------------------------- +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 + (void)r; (void)g; (void)b; + #endif +} + +void TechoCardBoard::ledOff() { + setLED(0, 0, 0); +} + +// ----------------------------------------------------------------------------- +// Buzzer — PWM tone generation +// ----------------------------------------------------------------------------- +void TechoCardBoard::buzz(uint16_t freq_hz, uint16_t duration_ms) { + #if defined(HAS_BUZZER) && PIN_BUZZER >= 0 + if (freq_hz == 0 || duration_ms == 0) { + noTone(PIN_BUZZER); + return; + } + tone(PIN_BUZZER, freq_hz, duration_ms); + #else + (void)freq_hz; (void)duration_ms; + #endif +} diff --git a/variants/lilygo_techo_card/TechoCardBoard.h b/variants/lilygo_techo_card/TechoCardBoard.h new file mode 100644 index 00000000..dc0a5595 --- /dev/null +++ b/variants/lilygo_techo_card/TechoCardBoard.h @@ -0,0 +1,68 @@ +#pragma once + +// ============================================================================= +// TechoCardBoard — Board class for LilyGo T-Echo Card +// +// Extends NRF52BoardDCDC with: +// - Battery ADC (AIN0, P0.02) with solar charging via BQ25896 +// - GPS power control (L76K) +// - Speaker/mic enable +// - RGB LED control +// - Buzzer +// - NFC NDEF contact sharing +// ============================================================================= + +#include +#include +#include "variant.h" + +#ifdef NRF52_POWER_MANAGEMENT +// Power management config for T-Echo Card +// AIN0 (P0.02) for battery voltage sensing +// REFSEL=4 → 5/8 VDD ≈ 2.0625V threshold (with 2:1 divider → ~4.125V cell) +static const PowerMgtConfig TECHO_CARD_POWER_CONFIG = { + .lpcomp_ain_channel = BATTERY_ADC_AIN, // AIN0 + .lpcomp_refsel = 4, // 5/8 VDD + .voltage_bootlock = 3100, // Don't boot below 3.1V +}; +#endif + +class TechoCardBoard : public NRF52BoardDCDC { +private: + float _adc_multiplier; + uint32_t _last_battery_read; + float _cached_battery_mv; + +public: + TechoCardBoard() + : _adc_multiplier(ADC_MULTIPLIER), + _last_battery_read(0), + _cached_battery_mv(0) {} + + void begin() override; + + // Battery + float getBatteryVoltage() override; + uint8_t getBatteryPercent() override; + float getAdcMultiplier() override { return _adc_multiplier; } + void setAdcMultiplier(float mult) { _adc_multiplier = mult; } + + // GPS power control + void enableGPS(bool enable); + + // Speaker power control + void enableSpeaker(bool enable); + + // RGB LED + void setLED(uint8_t r, uint8_t g, uint8_t b); + void ledOff(); + + // Buzzer + void buzz(uint16_t freq_hz, uint16_t duration_ms); + +#ifdef NRF52_POWER_MANAGEMENT + const PowerMgtConfig* getPowerConfig() const override { + return &TECHO_CARD_POWER_CONFIG; + } +#endif +}; diff --git a/variants/lilygo_techo_card/pins_arduino.h b/variants/lilygo_techo_card/pins_arduino.h new file mode 100644 index 00000000..aa339a52 --- /dev/null +++ b/variants/lilygo_techo_card/pins_arduino.h @@ -0,0 +1,60 @@ +#pragma once + +// ============================================================================= +// Arduino pin compatibility header for LilyGo T-Echo Card +// +// This file provides Arduino-standard pin name aliases for the nRF52840 GPIOs. +// 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. +// ============================================================================= + +// On nRF52840, Arduino digital pin numbers map 1:1 to nRF GPIO numbers +// (0–47 for port 0 and port 1). + +// LED +#define LED_BUILTIN PIN_LED1 +#define PIN_LED1 39 // WS2812 RGB LED data (1, 7) +#define LED_STATE_ON 1 + +// Buttons +#define PIN_BUTTON1 42 // Button A — orange front button (1, 10) +#define PIN_BUTTON2 24 // Boot button (0, 24) + +// Serial (USB CDC) +// nRF52840 native USB — no UART pin assignment needed for Serial +// Serial1 is used for GPS +#define PIN_SERIAL1_RX 21 // GPS TX → nRF RX (0, 21) +#define PIN_SERIAL1_TX 19 // nRF TX → GPS RX (0, 19) + +// I2C +#define PIN_WIRE_SDA 36 // (1, 4) +#define PIN_WIRE_SCL 34 // (1, 2) + +// SPI (LoRa — directly mapped, RadioLib handles pin control) +#define PIN_SPI_MISO 17 // (0, 17) +#define PIN_SPI_MOSI 15 // (0, 15) +#define PIN_SPI_SCK 13 // (0, 13) + +// 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. + +// NFC (dedicated nRF52840 NFC pins — not GPIO-assignable) +// NFC1 = P0.09, NFC2 = P0.10 +// These are only usable as NFC when NFC is enabled in UICR. +// If NFC is disabled, they become GPIO9 and GPIO10. + +// PDM Microphone +#define PIN_PDM_CLK 35 // (1, 3) +#define PIN_PDM_DIN 37 // (1, 5) + +// I2S Speaker (MAX98357) +#define PIN_I2S_SCK 16 // BCLK (0, 16) +#define PIN_I2S_LRCK 22 // LRCK / WS (0, 22) +#define PIN_I2S_SDOUT 20 // DATA (0, 20) diff --git a/variants/lilygo_techo_card/platformio.ini b/variants/lilygo_techo_card/platformio.ini new file mode 100644 index 00000000..735b037e --- /dev/null +++ b/variants/lilygo_techo_card/platformio.ini @@ -0,0 +1,106 @@ +; ============================================================================= +; LilyGo T-Echo Card — Meck variant configuration +; +; nRF52840 + SX1262 (HPB16B3) + SSD1315 OLED (72×40) + L76K GPS +; + MAX98357 Speaker + MP34DT05 PDM Mic + ICM20948 IMU + Solar + NFC +; +; Platform: nRF52 (Adafruit nRF52 Arduino) +; ============================================================================= + +; --- Base configuration for all T-Echo Card builds --- +[lilygo_techo_card] +extends = nrf52_base +platform = https://github.com/maxgerhardt/platform-nordicnrf52.git +board = lilygo_techo_card +board_check = false +extra_scripts = + create-uf2.py +build_flags = ${nrf52_base.build_flags} + -I variants/lilygo_techo_card + -D LILYGO_TECHO_CARD + -D NRF52_POWER_MANAGEMENT + ; I2C + -D PIN_BOARD_SDA=36 + -D PIN_BOARD_SCL=34 + ; LoRa SX1262 (HPB16B3 module) + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_NSS=11 + -D P_LORA_DIO_1=40 + -D P_LORA_RESET=7 + -D P_LORA_BUSY=14 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=17 + -D P_LORA_MOSI=15 + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_DIO2_AS_RF_SWITCH=1 + ; Display — SSD1315 OLED (72×40, I2C, SSD1306-compatible) + -D DISPLAY_CLASS=SSD1306Display + -D PIN_OLED_RESET=-1 + ; GPS — L76K + -D HAS_GPS=1 + -D PIN_GPS_TX=19 + -D PIN_GPS_RX=21 + -D PIN_GPS_EN=47 + -D GPS_BAUDRATE=9600 + -D ENV_INCLUDE_GPS=1 + ; Battery ADC + -D PIN_VBAT_READ=2 + ; User button + -D PIN_USER_BTN=42 + ; Board class + -D BOARD_CLASS=TechoCardBoard +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/lilygo_techo_card/*.cpp> +lib_deps = ${nrf52_base.lib_deps} + olikraus/U8g2 @ ^2.35.19 + stevemarple/MicroNMEA @ ^2.0.6 + +; ============================================================================= +; Build Environments +; ============================================================================= + +; --- BLE Companion Radio --- +; Pairs with MeshCore companion app (Android/iOS/Web) over Bluetooth. +; Includes GPS for location and time sync. +[env:meck_techo_card_companion_radio_ble] +extends = lilygo_techo_card +build_flags = ${lilygo_techo_card.build_flags} + -D FIRMWARE_NAME='"Meck T-Echo Card BLE"' + ; Debug (disable for release) + ; -D MESH_DEBUG + ; -D BLE_DEBUG_LOGGING +build_src_filter = ${lilygo_techo_card.build_src_filter} + +<../examples/companion_radio/*.cpp> + +; --- Repeater --- +; Standalone LoRa repeater node. GPS for position adverts. +; OLED shows status. Solar + 800mAh battery for outdoor deployment. +[env:meck_techo_card_repeater] +extends = lilygo_techo_card +build_flags = ${lilygo_techo_card.build_flags} + -D FIRMWARE_NAME='"Meck T-Echo Card Repeater"' + ; -D MESH_DEBUG +build_src_filter = ${lilygo_techo_card.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +; --- Room Server --- +; BBS-style message board node. +[env:meck_techo_card_room_server] +extends = lilygo_techo_card +build_flags = ${lilygo_techo_card.build_flags} + -D FIRMWARE_NAME='"Meck T-Echo Card Room"' +build_src_filter = ${lilygo_techo_card.build_src_filter} + +<../examples/simple_room_server/*.cpp> + +; --- Sensor Node --- +; Telemetry node with GPS position reporting. +; IMU data (ICM20948) can be added as a custom sensor source. +[env:meck_techo_card_sensor] +extends = lilygo_techo_card +build_flags = ${lilygo_techo_card.build_flags} + -D FIRMWARE_NAME='"Meck T-Echo Card Sensor"' +build_src_filter = ${lilygo_techo_card.build_src_filter} + +<../examples/simple_sensor/*.cpp> diff --git a/variants/lilygo_techo_card/target.cpp b/variants/lilygo_techo_card/target.cpp new file mode 100644 index 00000000..dd0a30f2 --- /dev/null +++ b/variants/lilygo_techo_card/target.cpp @@ -0,0 +1,95 @@ +// ============================================================================= +// MeshCore target implementation for LilyGo T-Echo Card +// +// nRF52840 + SX1262 (HPB16B3 module) + SSD1315 OLED + L76K GPS +// ============================================================================= + +#include "target.h" +#include "variant.h" + +// --- SPI for LoRa radio (software SPI on nRF52) --- +// The HPB16B3 SX1262 module uses dedicated SPI pins, not shared with anything else. +// RadioLib Module handles the SPI internally when given pin numbers. +static Module radio_module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); + +// --- Radio driver --- +RADIO_CLASS radio_driver(&radio_module); +WRAPPER_CLASS radio_wrapper(radio_driver); + +// --- Board --- +TechoCardBoard board; + +// --- Display (SSD1306-compatible, SSD1315 at 0x3C, 72x40) --- +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display(OLED_WIDTH, OLED_HEIGHT); +#endif + +// --- MeshCore stores --- +mesh::IdentityStore identity_store; +mesh::NodePrefs node_prefs; +mesh::DataStore data_store; + +// --- Sensor manager --- +#if defined(ENV_INCLUDE_GPS) && ENV_INCLUDE_GPS + #include + static MicroNMEALocationProvider gps_provider(Serial1); + EnvironmentSensorManager sensor_manager(gps_provider); +#else + EnvironmentSensorManager sensor_manager; +#endif + +// --- 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 + + // Enable GPS power + #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 + #if defined(HAS_GPS) + Serial1.setPins(PIN_GPS_RX, PIN_GPS_TX); + Serial1.begin(GPS_BAUDRATE); + #endif + + // Speaker off by default (save power) + #if defined(HAS_SPEAKER) + pinMode(PIN_SPK_EN, OUTPUT); + digitalWrite(PIN_SPK_EN, LOW); + #if PIN_SPK_EN2 >= 0 + pinMode(PIN_SPK_EN2, OUTPUT); + digitalWrite(PIN_SPK_EN2, LOW); + #endif + #endif + + // Initialize I2C + Wire.setPins(I2C_SDA, I2C_SCL); + Wire.begin(); + Wire.setClock(400000); + + // Initialize display + #ifdef DISPLAY_CLASS + display.begin(OLED_I2C_ADDR); + #endif + + // Board-level init + board.begin(); + + // Initialize LoRa radio + // SX1262 DIO2 as RF switch control (common for HPB16B3 modules) + radio_driver.setDio2AsRfSwitch(true); +} diff --git a/variants/lilygo_techo_card/target.h b/variants/lilygo_techo_card/target.h new file mode 100644 index 00000000..f72264cc --- /dev/null +++ b/variants/lilygo_techo_card/target.h @@ -0,0 +1,39 @@ +#pragma once + +// ============================================================================= +// MeshCore target declarations for LilyGo T-Echo Card +// ============================================================================= + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS +#include +#endif + +#include "TechoCardBoard.h" + +// Hardware object declarations (instantiated in target.cpp) +extern RADIO_CLASS radio_driver; +extern WRAPPER_CLASS radio_wrapper; +extern TechoCardBoard board; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +#endif + +extern mesh::IdentityStore identity_store; +extern mesh::NodePrefs node_prefs; +extern mesh::DataStore data_store; +extern EnvironmentSensorManager sensor_manager; + +// Target initialization +void target_setup(); diff --git a/variants/lilygo_techo_card/variant.h b/variants/lilygo_techo_card/variant.h new file mode 100644 index 00000000..5c9fc118 --- /dev/null +++ b/variants/lilygo_techo_card/variant.h @@ -0,0 +1,193 @@ +#pragma once + +// ============================================================================= +// LilyGo T-Echo Card — Pin Definitions for Meck Firmware +// +// nRF52840 + SX1262 + SSD1315 (72×40 OLED) + L76K GPS + MAX98357 Speaker +// + MP34DT05 PDM Microphone + ICM20948 IMU + BQ25896 Charger + Solar +// +// Pin notation from LilyGo pinmap: (port, pin) → nRF GPIO = port*32 + pin +// ============================================================================= + +#define LILYGO_TECHO_CARD + +// ----------------------------------------------------------------------------- +// I2C Bus (shared: OLED, IMU ICM20948, fuel gauge BQ27220 if present) +// ----------------------------------------------------------------------------- +#define I2C_SDA 36 // (1, 4) +#define I2C_SCL 34 // (1, 2) +#define PIN_BOARD_SDA I2C_SDA +#define PIN_BOARD_SCL I2C_SCL + +// ----------------------------------------------------------------------------- +// SX1262 LoRa Radio (SPI bit-bang on nRF52) +// ----------------------------------------------------------------------------- +#define P_LORA_NSS 11 // (0, 11) — CS +#define P_LORA_RESET 7 // (0, 7) — RST +#define P_LORA_SCLK 13 // (0, 13) — SCK +#define P_LORA_MOSI 15 // (0, 15) — MOSI +#define P_LORA_MISO 17 // (0, 17) — MISO +#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 + +// 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 +#define PIN_LORA_EN -1 + +// Default radio settings (Australia) +#ifndef LORA_TX_POWER +#define LORA_TX_POWER 22 +#endif + +// ----------------------------------------------------------------------------- +// 0.42" OLED Display — SSD1315 (SSD1306-compatible), 72×40, I2C +// ----------------------------------------------------------------------------- +#define HAS_OLED 1 +#define OLED_I2C_ADDR 0x3C +#define OLED_WIDTH 72 +#define OLED_HEIGHT 40 +#define OLED_SDA I2C_SDA +#define OLED_SCL I2C_SCL + +// OLED power control via RT9080 enable pin +#define PIN_OLED_EN 30 // (0, 30) — RT9080_EN + +// No hardware reset pin for OLED on T-Echo Card +#define PIN_OLED_RESET -1 + +// ----------------------------------------------------------------------------- +// GPS — L76K Multi-GNSS (GPS, GLONASS, BeiDou, QZSS) +// ----------------------------------------------------------------------------- +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 +#define PIN_GPS_TX 19 // (0, 19) — nRF TX → GPS RX +#define PIN_GPS_RX 21 // (0, 21) — nRF RX ← GPS TX +#define PIN_GPS_EN 47 // (1, 15) — GPS power enable +#define PIN_GPS_WAKEUP 25 // (0, 25) — GPS wakeup +#define PIN_GPS_1PPS 23 // (0, 23) — 1PPS time pulse +#define PIN_GPS_RF_EN 29 // (0, 29) — GPS RF / LNA enable + +// ----------------------------------------------------------------------------- +// Battery & Power +// ----------------------------------------------------------------------------- +#define PIN_VBAT_READ 2 // (0, 2) = AIN0 — battery voltage ADC +#define BATTERY_ADC_AIN 0 // nRF SAADC AIN channel number +#define BATTERY_CAPACITY_MAH 800 + +// 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. +// Adjust ADC_MULTIPLIER after measuring real voltage vs ADC reading. +#ifndef ADC_MULTIPLIER +#define ADC_MULTIPLIER 2.0f +#endif + +// BQ25896 charger is on I2C but managed by hardware — no software control needed +// Solar panel input: 0.25W 5V via VBUS + +// Auto-shutdown threshold (millivolts) +#define AUTO_SHUTDOWN_MILLIVOLTS 3200 + +// ----------------------------------------------------------------------------- +// Speaker — MAX98357 I2S Class-D Mono Amp (8Ω, 1W) +// ----------------------------------------------------------------------------- +#define HAS_SPEAKER 1 +#define PIN_SPK_EN 43 // (1, 11) — speaker amplifier enable +#define PIN_SPK_EN2 3 // (0, 3) — secondary enable +#define PIN_SPK_BCLK 16 // (0, 16) — I2S bit clock +#define PIN_SPK_DATA 20 // (0, 20) — I2S data out +#define PIN_SPK_LRCK 22 // (0, 22) — I2S word select / LRCK + +// ----------------------------------------------------------------------------- +// Microphone — MP34DT05 Digital MEMS PDM +// ----------------------------------------------------------------------------- +#define HAS_MICROPHONE 1 +#define PIN_MIC_CLK 35 // (1, 3) — PDM clock +#define PIN_MIC_DATA 37 // (1, 5) — PDM data + +// ----------------------------------------------------------------------------- +// Buttons +// ----------------------------------------------------------------------------- +#define PIN_BUTTON_A 42 // (1, 10) — orange button (front, main user button) +#define PIN_BUTTON_BOOT 24 // (0, 24) — boot button (nRF52840 BOOT) +#define PIN_USER_BTN PIN_BUTTON_A + +// Button_B is RESET — hardware only, no GPIO + +// Active LOW for nRF52 buttons (internal pull-up, press = LOW) +#define BUTTON_ACTIVE_LOW 1 + +// ----------------------------------------------------------------------------- +// Buzzer +// ----------------------------------------------------------------------------- +#define HAS_BUZZER 1 +#define PIN_BUZZER 38 // (1, 6) — piezo buzzer data / PWM + +// ----------------------------------------------------------------------------- +// WS2812 RGB LED (3 LEDs in series) +// ----------------------------------------------------------------------------- +#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_NEOPIXEL PIN_RGB_LED_1 +#define NUM_NEOPIXELS 3 + +// ----------------------------------------------------------------------------- +// IMU — ICM20948 9-axis MotionTracking (accelerometer + gyro + compass) +// ----------------------------------------------------------------------------- +#define HAS_IMU 1 +#define IMU_I2C_ADDR 0x68 +#define IMU_SDA I2C_SDA +#define IMU_SCL I2C_SCL + +// ----------------------------------------------------------------------------- +// NFC — nRF52840 NFC-A (Type 2 Tag) +// NFC uses dedicated NFC1/NFC2 pins on nRF52840 (P0.09 / P0.10) +// These are fixed by silicon — no GPIO config needed. +// NFC is handled by the nRF52 SDK nfc_t2t_lib. +// ----------------------------------------------------------------------------- +#define HAS_NFC 1 + +// ----------------------------------------------------------------------------- +// External Flash — ZD25WQ32CEIGR (4MB SPI Flash) +// Uses QSPI interface on nRF52840 +// Pin mapping from nRF52840 QSPI peripheral (typically fixed) +// ----------------------------------------------------------------------------- +#define HAS_EXT_FLASH 1 + +// ----------------------------------------------------------------------------- +// No SD Card on T-Echo Card (unlike T-Echo which has e-paper + no SD either) +// Settings stored in LittleFS on internal/external flash +// ----------------------------------------------------------------------------- +// #define HAS_SDCARD + +// ----------------------------------------------------------------------------- +// No dedicated RTC chip — time from GPS or BLE companion sync +// nRF52840 has a 32.768 kHz RTC peripheral for timekeeping while running +// ----------------------------------------------------------------------------- +// #define HAS_PCF85063_RTC + +// ----------------------------------------------------------------------------- +// Misc / Compatibility +// ----------------------------------------------------------------------------- + +// No e-ink display +// #define HAS_EINK + +// This board has no physical keyboard +// #define HAS_PHYSICAL_KEYBOARD + +// Fallback for code that references GPS_BAUDRATE without HAS_GPS guard +#ifndef GPS_BAUDRATE +#define GPS_BAUDRATE 9600 +#endif