Files
Meck/variants/lilygo_techo_card_WIP/TechoCardBoard.cpp
T

261 lines
8.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// =============================================================================
// 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"
#include "variant.h"
// nRF52840 SAADC includes
#include "nrf.h"
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);
// Configure button(s)
pinMode(PIN_BUTTON_A, INPUT_PULLUP);
pinMode(PIN_BUTTON_BOOT, INPUT_PULLUP);
// Initialise WS2812 NeoPixels (3 independent LEDs, all off at boot)
#if defined(HAS_RGB_LED)
_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 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.
//
// 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).
// -----------------------------------------------------------------------------
uint16_t TechoCardBoard::getBattMilliVolts() {
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;
}
// 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;
// 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;
// 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;
_cached_battery_mv = voltage_mv;
_last_battery_read = now;
return (uint16_t)voltage_mv;
}
uint8_t TechoCardBoard::getBatteryPercent() {
uint16_t mv = getBattMilliVolts();
if (mv == 0) return 0;
// Simple linear approximation for single-cell LiPo
// 3200 mV = 0%, 4200 mV = 100%
if (mv >= 4200) return 100;
if (mv <= 3200) return 0;
return (uint8_t)(((uint32_t)(mv - 3200) * 100) / 1000);
}
// -----------------------------------------------------------------------------
// 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 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)
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
}
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
// -----------------------------------------------------------------------------
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
}