diff --git a/docs/nrf52_power_management.md b/docs/nrf52_power_management.md index 9c7416b3d6..70d8182b45 100644 --- a/docs/nrf52_power_management.md +++ b/docs/nrf52_power_management.md @@ -2,34 +2,41 @@ ## Overview -The nRF52 Power Management module provides battery protection features to prevent over-discharge, minimise likelihood of brownout and flash corruption conditions existing, and enable safe voltage-based recovery. +The nRF52 power management module protects batteries from over-discharge, prevents brownout-related flash corruption, and enables automatic voltage-based recovery. When enabled and configured, it checks battery voltage at boot and enters protective shutdown (SYSTEMOFF) if voltage is too low, then automatically wakes when the battery recovers or external power is connected. -## Features +## CLI Commands -### Boot Voltage Protection -- Checks battery voltage immediately after boot and before mesh operations commence -- If voltage is below a configurable threshold (e.g., 3300mV), the device configures voltage wake (LPCOMP + VBUS) and enters protective shutdown (SYSTEMOFF) -- Prevents boot loops when battery is critically low -- Skipped when external power (USB VBUS) is detected - -### Voltage Wake (LPCOMP + VBUS) -- Configures the nRF52's Low Power Comparator (LPCOMP) before entering SYSTEMOFF -- Enables USB VBUS detection so external power can wake the device -- Device automatically wakes when battery voltage rises above recovery threshold or when VBUS is detected - -### Early Boot Register Capture -- Captures RESETREAS (reset reason) and GPREGRET2 (shutdown reason) before SystemInit() clears them -- Allows firmware to determine why it booted (cold boot, watchdog, LPCOMP wake, etc.) -- Allows firmware to determine why it last shut down (user request, low voltage, boot protection) - -### Shutdown Reason Tracking -Shutdown reason codes (stored in GPREGRET2): -| Code | Name | Description | -|------|------|-------------| -| 0x00 | NONE | Normal boot / no previous shutdown | -| 0x4C | LOW_VOLTAGE | Runtime low voltage threshold reached | -| 0x55 | USER | User requested powerOff() | -| 0x42 | BOOT_PROTECT | Boot voltage protection triggered | +| Command | Description | +|---------|-------------| +| `get pwrmgt.support` | Returns "supported" or "unsupported" | +| `get pwrmgt.source` | Returns current power source: "battery" or "external" | +| `get pwrmgt.bootreason` | Returns reset reason and shutdown reason strings | +| `get pwrmgt.bootmv` | Returns battery voltage at boot in millivolts | +| `get pwrmgt.batt` | Returns configured battery chemistry: liion, lfp, or lto | +| `set pwrmgt.batt liion\|lfp\|lto` | Set battery chemistry (persisted, takes effect on next boot) | +| `get pwrmgt.bootlock` | Returns boot protection state: "enabled" or "disabled" | +| `set pwrmgt.bootlock on\|off` | Enable or disable boot protection (persisted, reboot required) | +| `poweroff` / `shutdown` | Immediate power off. Does not configure voltage wake — recovery requires USB power or a hardware wake source (e.g. button press). | + +On boards without power management support, all commands except `get pwrmgt.support` return `ERROR: Power management not supported`. + +## Configuration + +### Set battery chemistry + +The correct chemistry must be set so the firmware uses the right voltage thresholds. Default is Li-ion. + +| Chemistry | Boot lockout threshold | +|-----------|----------------------| +| Li-ion/LiPo (`liion`) | 3000 mV | +| Lithium Iron Phosphate (`lfp`) | 2500 mV | +| Lithium Titanate Oxide (`lto`) | 1800 mV | + +### Enable boot protection + +Boot protection is disabled by default. Enable with `set pwrmgt.bootlock on`, then reboot. + +Verify with `get pwrmgt.batt` and `get pwrmgt.bootlock`. ## Supported Boards @@ -38,11 +45,11 @@ Shutdown reason codes (stored in GPREGRET2): | Seeed Studio XIAO nRF52840 (`xiao_nrf52`) | Yes | Yes | Yes | | RAK4631 (`rak4631`) | Yes | Yes | Yes | | Heltec T114 (`heltec_t114`) | Yes | Yes | Yes | +| SenseCAP Solar (`sensecap_solar`) | Yes | Yes | Yes | | Promicro nRF52840 | No | No | No | | RAK WisMesh Tag | No | No | No | | Heltec Mesh Solar | No | No | No | | LilyGo T-Echo / T-Echo Lite | No | No | No | -| SenseCAP Solar | Yes | Yes | Yes | | WIO Tracker L1 / L1 E-Ink | No | No | No | | WIO WM1110 | No | No | No | | Mesh Pocket | No | No | No | @@ -53,159 +60,80 @@ Shutdown reason codes (stored in GPREGRET2): | Keepteen LT1 | No | No | No | | Minewsemi ME25LS01 | No | No | No | -Notes: -- "Implemented" reflects Phase 1 (boot lockout + shutdown reason capture). -- User power-off on Heltec T114 does not enable LPCOMP wake. -- VBUS detection is used to skip boot lockout on external power, and VBUS wake is configured alongside LPCOMP when supported hardware exposes VBUS to the nRF52. - -## Technical Details - -### Architecture - -The power management functionality is integrated into the `NRF52Board` base class in `src/helpers/NRF52Board.cpp`. Board variants provide hardware-specific configuration via a `PowerMgtConfig` struct and override `initiateShutdown(uint8_t reason)` to perform board-specific power-down work and conditionally enable voltage wake (LPCOMP + VBUS). - -### Early Boot Capture - -A static constructor with priority 101 in `NRF52Board.cpp` captures the RESETREAS and GPREGRET2 registers before: -- SystemInit() (priority 102) - which clears RESETREAS -- Static C++ constructors (default priority 65535) - -This ensures we capture the true reset reason before any initialisation code runs. - -### Board Implementation - -To enable power management on a board variant: - -1. **Enable in platformio.ini**: - ```ini - -D NRF52_POWER_MANAGEMENT - ``` - -2. **Define configuration in variant.h**: - ```c - #define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) - #define PWRMGT_LPCOMP_AIN 7 // AIN channel for voltage sensing - #define PWRMGT_LPCOMP_REFSEL 2 // REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) - ``` - -3. **Implement in board .cpp file**: - ```cpp - #ifdef NRF52_POWER_MANAGEMENT - const PowerMgtConfig power_config = { - .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, - .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, - .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK - }; - - void MyBoard::initiateShutdown(uint8_t reason) { - // Board-specific shutdown preparation (e.g., disable peripherals) - bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || - reason == SHUTDOWN_REASON_BOOT_PROTECT); - - if (enable_lpcomp) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); - } - - enterSystemOff(reason); - } - #endif - - void MyBoard::begin() { - NRF52Board::begin(); // or NRF52BoardDCDC::begin() - // ... board setup ... - - #ifdef NRF52_POWER_MANAGEMENT - checkBootVoltage(&power_config); - #endif - } - ``` - - For user-initiated shutdowns, `powerOff()` remains board-specific. Power management only arms LPCOMP for automated shutdown reasons (boot protection/low voltage). - -4. **Declare override in board .h file**: - ```cpp - #ifdef NRF52_POWER_MANAGEMENT - void initiateShutdown(uint8_t reason) override; - #endif - ``` - -### Voltage Wake Configuration - -The LPCOMP (Low Power Comparator) is configured to: -- Monitor the specified AIN channel (0-7 corresponding to P0.02-P0.05, P0.28-P0.31) -- Compare against VDD fraction reference (REFSEL: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) -- Detect UP events (voltage rising above threshold) -- Use 50mV hysteresis for noise immunity -- Wake the device from SYSTEMOFF when triggered - -VBUS wake is enabled via the POWER peripheral USBDETECTED event whenever `configureVoltageWake()` is used. This requires USB VBUS to be routed to the nRF52 (typical on nRF52840 boards with native USB). - -**LPCOMP Reference Selection (PWRMGT_LPCOMP_REFSEL)**: -| REFSEL | Fraction | VBAT @ 1M/1M divider (VDD=3.0-3.3) | VBAT @ 1.5M/1M divider (VDD=3.0-3.3) | -|--------|----------|------------------------------------|--------------------------------------| -| 0 | 1/8 | 0.75-0.82 V | 0.94-1.03 V | -| 1 | 2/8 | 1.50-1.65 V | 1.88-2.06 V | -| 2 | 3/8 | 2.25-2.47 V | 2.81-3.09 V | -| 3 | 4/8 | 3.00-3.30 V | 3.75-4.12 V | -| 4 | 5/8 | 3.75-4.12 V | 4.69-5.16 V | -| 5 | 6/8 | 4.50-4.95 V | 5.62-6.19 V | -| 6 | 7/8 | 5.25-5.77 V | 6.56-7.22 V | -| 7 | ARef | - | - | -| 8 | 1/16 | 0.38-0.41 V | 0.47-0.52 V | -| 9 | 3/16 | 1.12-1.24 V | 1.41-1.55 V | -| 10 | 5/16 | 1.88-2.06 V | 2.34-2.58 V | -| 11 | 7/16 | 2.62-2.89 V | 3.28-3.61 V | -| 12 | 9/16 | 3.38-3.71 V | 4.22-4.64 V | -| 13 | 11/16 | 4.12-4.54 V | 5.16-5.67 V | -| 14 | 13/16 | 4.88-5.36 V | 6.09-6.70 V | -| 15 | 15/16 | 5.62-6.19 V | 7.03-7.73 V | - -**Important**: For boards with a voltage divider on the battery sense pin, LPCOMP measures the divided voltage. Use: -`VBAT_threshold ≈ (VDD * fraction) * divider_scale`, where `divider_scale = (Rtop + Rbottom) / Rbottom` (e.g., 2.0 for 1M/1M, 2.5 for 1.5M/1M, 3.0 for XIAO). - -### SoftDevice Compatibility - -The power management code checks whether SoftDevice is enabled and uses the appropriate API: -- When SD enabled: `sd_power_*` functions -- When SD disabled: Direct register access (NRF_POWER->*) - -This ensures compatibility regardless of BLE stack state. +## How It Works -## CLI Commands +### Boot Voltage Protection -Power management status can be queried via the CLI: +On every boot (when enabled), the firmware reads the battery voltage before starting mesh operations. If USB/external power is detected, the check is skipped. If the voltage is below the lockout threshold for the configured chemistry, the device configures LPCOMP and VBUS wake sources and enters SYSTEMOFF. -| Command | Description | -|---------|-------------| -| `get pwrmgt.support` | Returns "supported" or "unsupported" | -| `get pwrmgt.source` | Returns current power source - "battery" or "external" (5V/USB power) | -| `get pwrmgt.bootreason` | Returns reset and shutdown reason strings | -| `get pwrmgt.bootmv` | Returns boot voltage in millivolts | +### Wake Sources -On boards without power management enabled, all commands except `get pwrmgt.support` return: +A device in protective shutdown can be woken by two sources: + +- **LPCOMP (Low Power Comparator)**: The nRF52's hardware comparator monitors battery voltage on an analog input pin during SYSTEMOFF. When voltage rises above the configured recovery threshold, the comparator triggers a wake event. The recovery threshold is set above the boot lockout threshold so the device does not immediately shut down again. LPCOMP draws negligible current (~1 uA) during SYSTEMOFF. +- **VBUS (USB power detection)**: The nRF52's POWER peripheral detects USB voltage on the VBUS pin and triggers a wake event. This is configured alongside LPCOMP whenever the board routes VBUS to the nRF52 (standard on nRF52840 boards with native USB). Connecting any USB power source will wake the device immediately regardless of battery state. + +### Shutdown Reasons + +The firmware records why the device entered SYSTEMOFF. This value is stored in the GPREGRET2 register (persists across SYSTEMOFF) and can be queried after boot with `get pwrmgt.bootreason`. + +| Code | Name | Description | +|------|------|-------------| +| 0x00 | NONE | Normal boot / no previous shutdown | +| 0x4C | LOW_VOLTAGE | Runtime low voltage threshold reached | +| 0x55 | USER | Manual shutdown via `poweroff` or `shutdown` CLI command | +| 0x42 | BOOT_PROTECT | Boot voltage protection triggered | + +### Boot Reason Tracking + +The firmware captures the nRF52 RESETREAS and GPREGRET2 registers at early boot before system initialisation clears them. This allows `get pwrmgt.bootreason` to report both the wake source (e.g. LPCOMP, VBUS, reset pin, watchdog) and the prior shutdown reason. + +## LPCOMP Wake Voltage Reference + +The LPCOMP wake voltage depends on the board's voltage divider ratio and the REFSEL value programmed before entering SYSTEMOFF. + +**Wake voltage formula**: ``` -ERROR: Power management not supported +VBAT_wake = REFSEL_fraction x VDD_sys x ADC_MULTIPLIER ``` +Where VDD_sys is approximately 3.0-3.3V (regulator output during SYSTEMOFF) and ADC_MULTIPLIER is the board's voltage divider scale factor. + +**REFSEL fraction reference**: + +| REFSEL | Fraction | REFSEL | Fraction | +|--------|----------|--------|----------| +| 0 | 1/8 | 8 | 1/16 | +| 1 | 2/8 | 9 | 3/16 | +| 2 | 3/8 | 10 | 5/16 | +| 3 | 4/8 | 11 | 7/16 | +| 4 | 5/8 | 12 | 9/16 | +| 5 | 6/8 | 13 | 11/16 | +| 6 | 7/8 | 14 | 13/16 | +| 7 | ARef | 15 | 15/16 | + +**Per-board per-chemistry wake voltages**: + +| Board | ADC_MUL | Li-ion REFSEL | Wake range | LFP REFSEL | Wake range | LTO REFSEL | Wake range | +|-------|---------|---------------|------------|------------|------------|------------|------------| +| RAK4631 | ~1.73 | 4 (5/8) | 3.24-3.57V | 4 (5/8) | 3.24-3.57V | 11 (7/16) | 2.27-2.50V | +| T114 | 4.90 | 1 (2/8) | 3.68-4.04V | 9 (3/16) | 2.76-3.03V | 0 (1/8) | 1.84-2.02V* | +| XIAO | 3.0 | 2 (3/8) | 3.38-3.71V | 10 (5/16) | 2.81-3.09V | 1 (2/8) | 2.25-2.47V | + +*T114 LTO uses a narrower margin (~50 mV plus LPCOMP 50 mV hysteresis) and assumes VDD_sys >= 3.0V in SYSTEMOFF. + ## Debug Output -When `MESH_DEBUG=1` is enabled, the power management module outputs: +When the firmware is built with `MESH_DEBUG=1`, the power management module logs at boot: + ``` -DEBUG: PWRMGT: Reset = Wake from LPCOMP (0x20000); Shutdown = Low Voltage (0x4C) -DEBUG: PWRMGT: Boot voltage = 3450 mV (threshold = 3300 mV) -DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD) +PWRMGT: Reset = Wake from LPCOMP (0x20000); Shutdown = Low Voltage (0x4C) +PWRMGT: Boot protection enabled (Li-ion), threshold=3000 mV +PWRMGT: Boot voltage=3450 mV +PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD) +PWRMGT: VBUS wake configured ``` -## Phase 2 (Planned) - -- Runtime voltage monitoring -- Voltage state machine (Normal -> Warning -> Critical -> Shutdown) -- Configurable thresholds -- Load shedding callbacks for power reduction -- Deep sleep integration -- Scheduled wake-up -- Extended sleep with periodic monitoring - ## References - [nRF52840 Product Specification - POWER](https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index eff9efca47..e859b80aa4 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -128,7 +128,9 @@ void setup() { fast_rng.begin(radio_get_rng_seed()); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - InternalFS.begin(); + #if defined(STM32_PLATFORM) + InternalFS.begin(); + #endif #if defined(QSPIFLASH) if (!QSPIFlash.begin()) { // debug output might not be available at this point, might be too early. maybe should fall back to InternalFS here? diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 3507959297..5d838a2de0 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -30,7 +30,6 @@ void halt() { void loadOrCreateIdentity() { #if defined(NRF52_PLATFORM) - InternalFS.begin(); IdentityStore store(InternalFS, ""); #elif defined(ESP32) SPIFFS.begin(true); diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index e37078ce5f..7ee8afd101 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -61,7 +61,9 @@ void setup() { FILESYSTEM* fs; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - InternalFS.begin(); + #if defined(STM32_PLATFORM) + InternalFS.begin(); + #endif fs = &InternalFS; IdentityStore store(InternalFS, ""); #elif defined(ESP32) diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 825fb007d5..82986f5c58 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -39,7 +39,6 @@ void setup() { FILESYSTEM* fs; #if defined(NRF52_PLATFORM) - InternalFS.begin(); fs = &InternalFS; IdentityStore store(InternalFS, ""); #elif defined(RP2040_PLATFORM) diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index ab14d3933f..138f0bf779 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -565,7 +565,6 @@ void setup() { fast_rng.begin(radio_get_rng_seed()); #if defined(NRF52_PLATFORM) - InternalFS.begin(); the_mesh.begin(InternalFS); #elif defined(RP2040_PLATFORM) LittleFS.begin(); diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index 330adcc2e4..81c43c4d18 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -72,7 +72,9 @@ void setup() { FILESYSTEM* fs; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - InternalFS.begin(); + #if defined(STM32_PLATFORM) + InternalFS.begin(); + #endif fs = &InternalFS; IdentityStore store(InternalFS, ""); #elif defined(ESP32) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2f7a0fffcb..3b42142fa6 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -80,7 +80,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 - file.read(pad, 3); // 153 + file.read((uint8_t *)&_prefs->battery_chemistry, sizeof(_prefs->battery_chemistry)); // 153 + file.read((uint8_t *)&_prefs->bootlock_enabled, sizeof(_prefs->bootlock_enabled)); // 154 + file.read(pad, 1); // 155 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 @@ -111,6 +113,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1); + if (_prefs->battery_chemistry > 2) _prefs->battery_chemistry = 0; // invalid value, reset to liion default + _prefs->bootlock_enabled = constrain(_prefs->bootlock_enabled, 0, 1); _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); @@ -170,7 +174,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 - file.write(pad, 3); // 153 + file.write((uint8_t *)&_prefs->battery_chemistry, sizeof(_prefs->battery_chemistry)); // 153 + file.write((uint8_t *)&_prefs->bootlock_enabled, sizeof(_prefs->bootlock_enabled)); // 154 + file.write(pad, 1); // 155 file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 @@ -440,6 +446,21 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %u mV", _board->getBootVoltage()); #else strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.batt", 11) == 0 && config[11] == '\0') { +#ifdef NRF52_POWER_MANAGEMENT + const char* chem_str[] = {"liion", "lfp", "lto"}; + uint8_t chem = _prefs->battery_chemistry; + if (chem > 2) chem = 0; + sprintf(reply, "> %s", chem_str[chem]); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.bootlock", 15) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + sprintf(reply, "> %s", _prefs->bootlock_enabled ? "enabled" : "disabled"); +#else + strcpy(reply, "ERROR: Power management not supported"); #endif } else { sprintf(reply, "??: %s", config); @@ -698,6 +719,44 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->adc_multiplier = 0.0f; strcpy(reply, "Error: unsupported by this board"); }; + } else if (memcmp(config, "pwrmgt.batt ", 12) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + const char* value = &config[12]; + if (memcmp(value, "liion", 5) == 0) { + _prefs->battery_chemistry = 0; + savePrefs(); + strcpy(reply, "OK - Li-ion"); + } else if (memcmp(value, "lfp", 3) == 0) { + _prefs->battery_chemistry = 1; + savePrefs(); + strcpy(reply, "OK - LFP"); + } else if (memcmp(value, "lto", 3) == 0) { + _prefs->battery_chemistry = 2; + savePrefs(); + strcpy(reply, "OK - LTO"); + } else { + strcpy(reply, "ERROR: enter liion/lfp/lto"); + } +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.bootlock ", 16) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + const char* value = &config[16]; + if (memcmp(value, "on", 2) == 0) { + _prefs->bootlock_enabled = 1; + savePrefs(); + strcpy(reply, "OK - enabled (reboot required)"); + } else if (memcmp(value, "off", 3) == 0) { + _prefs->bootlock_enabled = 0; + savePrefs(); + strcpy(reply, "OK - disabled (reboot required)"); + } else { + strcpy(reply, "ERROR: Use on/off"); + } +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 3a4332d1f2..92dac942cc 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -48,8 +48,10 @@ struct NodePrefs { // persisted to file uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) uint8_t bridge_channel; // 1-14 (ESP-NOW only) char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) - // Power setting + // Power settings uint8_t powersaving_enabled; // boolean + uint8_t battery_chemistry; // 0=Li-ion, 1=LFP, 2=LTO + uint8_t bootlock_enabled; // 0=disabled, 1=enabled // Gps settings uint8_t gps_enabled; uint32_t gps_interval; // in seconds diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 2c8753d464..9a9bb1a6a7 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,8 +1,9 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" - #include #include +#include +using Adafruit_LittleFS_Namespace::File; static BLEDfu bledfu; @@ -20,6 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void NRF52Board::begin() { startup_reason = BD_STARTUP_NORMAL; + InternalFS.begin(); } #ifdef NRF52_POWER_MANAGEMENT @@ -102,6 +104,7 @@ const char* NRF52Board::getResetReasonString(uint32_t reason) { const char* NRF52Board::getShutdownReasonString(uint8_t reason) { switch (reason) { + case SHUTDOWN_REASON_NONE: return "None"; case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage"; case SHUTDOWN_REASON_USER: return "User Request"; case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection"; @@ -109,29 +112,77 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) { return "Unknown"; } +uint16_t NRF52Board::getThresholdForChemistry(BatteryChemistry chem, const PowerMgtConfig* config) { + switch (chem) { + case CHEM_LIION: return config->voltage_bootlock_liion; + case CHEM_LFP: return config->voltage_bootlock_lfp; + case CHEM_LTO: return config->voltage_bootlock_lto; + default: return config->voltage_bootlock_liion; // Safe default + } +} + +uint8_t NRF52Board::getRefselForChemistry(BatteryChemistry chem, const PowerMgtConfig* config) { + switch (chem) { + case CHEM_LIION: return config->lpcomp_refsel_liion; + case CHEM_LFP: return config->lpcomp_refsel_lfp; + case CHEM_LTO: return config->lpcomp_refsel_lto; + default: return config->lpcomp_refsel_liion; // Safe default + } +} + +const char* NRF52Board::getChemistryString(BatteryChemistry chem) { + switch (chem) { + case CHEM_LIION: return "Li-ion"; + case CHEM_LFP: return "LFP"; + case CHEM_LTO: return "LTO"; + default: return "Unknown"; + } +} + bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) { initPowerMgr(); + // Read bootlock config + uint8_t raw_chem = 0, raw_bootlock = 0; // default: Li-ion, bootlock disabled + File file = InternalFS.open("/com_prefs"); + if (file) { + if (file.seek(153)) { + uint8_t buf[2]; + if (file.read(buf, sizeof(buf)) == sizeof(buf)) { + raw_chem = (buf[0] <= 2) ? buf[0] : 0; + raw_bootlock = (buf[1] <= 1) ? buf[1] : 0; + } + } + file.close(); + } + bool enabled = raw_bootlock != 0; + battery_chem = static_cast(raw_chem); + // Read boot voltage boot_voltage_mv = getBattMilliVolts(); - - if (config->voltage_bootlock == 0) return true; // Protection disabled - // Skip check if externally powered + // If disabled skip check + if (!enabled) { + MESH_DEBUG_PRINTLN("PWRMGT: Boot protection disabled"); + return true; + } + + // Skip check if externally powered (USB/5V) if (isExternalPowered()) { MESH_DEBUG_PRINTLN("PWRMGT: Boot check skipped (external power)"); - boot_voltage_mv = getBattMilliVolts(); return true; } - MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage = %u mV (threshold = %u mV)", - boot_voltage_mv, config->voltage_bootlock); + // Get threshold for configured chemistry + uint16_t threshold = getThresholdForChemistry(battery_chem, config); + + MESH_DEBUG_PRINTLN("PWRMGT: Boot protection enabled (%s), threshold=%u mV", + getChemistryString(battery_chem), threshold); + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage=%u mV", boot_voltage_mv); // Only trigger shutdown if reading is valid (>1000mV) AND below threshold - // This prevents spurious shutdowns on ADC glitches or uninitialized reads - if (boot_voltage_mv > 1000 && boot_voltage_mv < config->voltage_bootlock) { + if (boot_voltage_mv > 1000 && boot_voltage_mv < threshold) { MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown"); - initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT); return false; // Should never reach this } @@ -178,50 +229,54 @@ void NRF52Board::enterSystemOff(uint8_t reason) { } void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { - // LPCOMP is not managed by SoftDevice - direct register access required - // Halt and disable before reconfiguration - NRF_LPCOMP->TASKS_STOP = 1; - NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled; + if (refsel != 0xFF) { + // LPCOMP is not managed by SoftDevice - direct register access required + // Halt and disable before reconfiguration + NRF_LPCOMP->TASKS_STOP = 1; + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled; - // Select analog input (AIN0-7 maps to PSEL 0-7) - NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk; + // Select analog input (AIN0-7 maps to PSEL 0-7) + NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk; - // Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) - NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk; + // Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) + NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk; - // Detect UP events (voltage rises above threshold for battery recovery) - NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up; + // Detect UP events (voltage rises above threshold for battery recovery) + NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up; - // Enable 50mV hysteresis for noise immunity - NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV; + // Enable 50mV hysteresis for noise immunity + NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV; - // Clear stale events/interrupts before enabling wake - NRF_LPCOMP->EVENTS_READY = 0; - NRF_LPCOMP->EVENTS_DOWN = 0; - NRF_LPCOMP->EVENTS_UP = 0; - NRF_LPCOMP->EVENTS_CROSS = 0; + // Clear stale events/interrupts before enabling wake + NRF_LPCOMP->EVENTS_READY = 0; + NRF_LPCOMP->EVENTS_DOWN = 0; + NRF_LPCOMP->EVENTS_UP = 0; + NRF_LPCOMP->EVENTS_CROSS = 0; - NRF_LPCOMP->INTENCLR = 0xFFFFFFFF; - NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk; + NRF_LPCOMP->INTENCLR = 0xFFFFFFFF; + NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk; - // Enable LPCOMP - NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled; - NRF_LPCOMP->TASKS_START = 1; + // Enable LPCOMP + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled; + NRF_LPCOMP->TASKS_START = 1; - // Wait for comparator to settle before entering SYSTEMOFF - for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) { - delayMicroseconds(50); - } + // Wait for comparator to settle before entering SYSTEMOFF + for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) { + delayMicroseconds(50); + } - if (refsel == 7) { - MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel); - } else if (refsel <= 6) { - MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)", - ain_channel, refsel + 1); + if (refsel == 7) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel); + } else if (refsel <= 6) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)", + ain_channel, refsel + 1); + } else { + uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1); + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)", + ain_channel, ref_num); + } } else { - uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1); - MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)", - ain_channel, ref_num); + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake skipped for this chemistry"); } // Configure VBUS (USB power) wake alongside LPCOMP diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 96f67dc950..4c792008c5 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -12,15 +12,25 @@ #define SHUTDOWN_REASON_USER 0x55 // 'U' - User requested powerOff() #define SHUTDOWN_REASON_BOOT_PROTECT 0x42 // 'B' - Boot voltage protection +// Battery chemistry types +enum BatteryChemistry { + CHEM_LIION = 0, // Li-ion/LiPo: 3.0V - 4.2V + CHEM_LFP = 1, // Lithium Iron Phosphate: 2.5V - 3.65V + CHEM_LTO = 2 // Lithium Titanate Oxide: 1.8V - 2.85V +}; + // Boards provide this struct with their hardware-specific settings and callbacks. struct PowerMgtConfig { // LPCOMP wake configuration (for voltage recovery from SYSTEMOFF) uint8_t lpcomp_ain_channel; // AIN0-7 for voltage sensing pin - uint8_t lpcomp_refsel; // REFSEL value: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16 + uint8_t lpcomp_refsel_liion; // Li-ion wake REFSEL (0-6=N/8, 8-15=N/16); 0xFF = VBUS-only + uint8_t lpcomp_refsel_lfp; // LFP wake REFSEL; 0xFF = VBUS-only + uint8_t lpcomp_refsel_lto; // LTO wake REFSEL; 0xFF = VBUS-only - // Boot protection voltage threshold (millivolts) - // Set to 0 to disable boot protection - uint16_t voltage_bootlock; + // Boot protection voltage thresholds per chemistry (millivolts) + uint16_t voltage_bootlock_liion; // Li-ion minimum safe voltage + uint16_t voltage_bootlock_lfp; // LFP minimum safe voltage + uint16_t voltage_bootlock_lto; // LTO minimum safe voltage }; #endif @@ -37,11 +47,15 @@ class NRF52Board : public mesh::MainBoard { uint32_t reset_reason; // RESETREAS register value uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF) uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts) + BatteryChemistry battery_chem; // Configured battery chemistry (read from com_prefs) bool checkBootVoltage(const PowerMgtConfig* config); void enterSystemOff(uint8_t reason); void configureVoltageWake(uint8_t ain_channel, uint8_t refsel); virtual void initiateShutdown(uint8_t reason); + uint16_t getThresholdForChemistry(BatteryChemistry chem, const PowerMgtConfig* config); + uint8_t getRefselForChemistry(BatteryChemistry chem, const PowerMgtConfig* config); + const char* getChemistryString(BatteryChemistry chem); #endif public: diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index c03d39afb6..a1843ba21e 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -7,9 +7,13 @@ // Static configuration for power management // Values come from variant.h defines const PowerMgtConfig power_config = { - .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, - .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, - .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel_liion = PWRMGT_LPCOMP_REFSEL_LIION, + .lpcomp_refsel_lfp = PWRMGT_LPCOMP_REFSEL_LFP, + .lpcomp_refsel_lto = PWRMGT_LPCOMP_REFSEL_LTO, + .voltage_bootlock_liion = PWRMGT_VOLTAGE_BOOTLOCK_LIION, + .voltage_bootlock_lfp = PWRMGT_VOLTAGE_BOOTLOCK_LFP, + .voltage_bootlock_lto = PWRMGT_VOLTAGE_BOOTLOCK_LTO }; void T114Board::initiateShutdown(uint8_t reason) { @@ -25,7 +29,8 @@ void T114Board::initiateShutdown(uint8_t reason) { digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW); if (enable_lpcomp) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + configureVoltageWake(power_config.lpcomp_ain_channel, + getRefselForChemistry(battery_chem, &power_config)); } enterSystemOff(reason); diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index bfb4484d13..a308a68eae 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -30,13 +30,17 @@ #define AREF_VOLTAGE (3.0) -// Power management boot protection threshold (millivolts) -// Set to 0 to disable boot protection -#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) +// Power management boot protection thresholds (millivolts) +// Safe minimum voltages per battery chemistry +#define PWRMGT_VOLTAGE_BOOTLOCK_LIION 3000 // Li-ion/LiPo minimum +#define PWRMGT_VOLTAGE_BOOTLOCK_LFP 2500 // LFP minimum +#define PWRMGT_VOLTAGE_BOOTLOCK_LTO 1800 // LTO minimum // LPCOMP wake configuration (voltage recovery from SYSTEMOFF) // AIN2 = P0.04 = BATTERY_PIN / PIN_VBAT_READ -#define PWRMGT_LPCOMP_AIN 2 -#define PWRMGT_LPCOMP_REFSEL 1 // 2/8 VDD (~3.68-4.04V) +#define PWRMGT_LPCOMP_AIN 2 +#define PWRMGT_LPCOMP_REFSEL_LIION 1 // 2/8 VDD (~3.7–4.0V) +#define PWRMGT_LPCOMP_REFSEL_LFP 9 // 3/16 VDD (~2.8–3.0V) +#define PWRMGT_LPCOMP_REFSEL_LTO 0 // 1/8 VDD (~1.84–2.02V); requires VDD_sys >= 3.0V //////////////////////////////////////////////////////////////////////////////// // Number of pins diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 9fb47b432e..27a12dab4a 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -7,9 +7,13 @@ // Static configuration for power management // Values set in variant.h defines const PowerMgtConfig power_config = { - .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, - .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, - .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel_liion = PWRMGT_LPCOMP_REFSEL_LIION, + .lpcomp_refsel_lfp = PWRMGT_LPCOMP_REFSEL_LFP, + .lpcomp_refsel_lto = PWRMGT_LPCOMP_REFSEL_LTO, + .voltage_bootlock_liion = PWRMGT_VOLTAGE_BOOTLOCK_LIION, + .voltage_bootlock_lfp = PWRMGT_VOLTAGE_BOOTLOCK_LFP, + .voltage_bootlock_lto = PWRMGT_VOLTAGE_BOOTLOCK_LTO }; void RAK4631Board::initiateShutdown(uint8_t reason) { @@ -18,7 +22,8 @@ void RAK4631Board::initiateShutdown(uint8_t reason) { if (reason == SHUTDOWN_REASON_LOW_VOLTAGE || reason == SHUTDOWN_REASON_BOOT_PROTECT) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + configureVoltageWake(power_config.lpcomp_ain_channel, + getRefselForChemistry(battery_chem, &power_config)); } enterSystemOff(reason); diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 142d93e91d..74bfef94ab 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -104,13 +104,17 @@ extern "C" static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 -// Power management boot protection threshold (millivolts) -// Set to 0 to disable boot protection -#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) +// Power management boot protection thresholds (millivolts) +// Safe minimum voltages per battery chemistry +#define PWRMGT_VOLTAGE_BOOTLOCK_LIION 3000 // Li-ion/LiPo minimum +#define PWRMGT_VOLTAGE_BOOTLOCK_LFP 2500 // LFP minimum +#define PWRMGT_VOLTAGE_BOOTLOCK_LTO 1800 // LTO minimum // LPCOMP wake configuration (voltage recovery from SYSTEMOFF) // AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ -#define PWRMGT_LPCOMP_AIN 3 -#define PWRMGT_LPCOMP_REFSEL 4 // 5/8 VDD (~3.13-3.44V) +#define PWRMGT_LPCOMP_AIN 3 +#define PWRMGT_LPCOMP_REFSEL_LIION 4 // 5/8 VDD (~3.1–3.4V) +#define PWRMGT_LPCOMP_REFSEL_LFP 4 // 5/8 VDD (~3.1–3.4V) +#define PWRMGT_LPCOMP_REFSEL_LTO 11 // 7/16 VDD (~2.27–2.50V) // Other pins #define PIN_AREF (2) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 42ee6a87fe..6231135c22 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -9,9 +9,13 @@ // Static configuration for power management // Values set in variant.h defines const PowerMgtConfig power_config = { - .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, - .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, - .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel_liion = PWRMGT_LPCOMP_REFSEL_LIION, + .lpcomp_refsel_lfp = PWRMGT_LPCOMP_REFSEL_LFP, + .lpcomp_refsel_lto = PWRMGT_LPCOMP_REFSEL_LTO, + .voltage_bootlock_liion = PWRMGT_VOLTAGE_BOOTLOCK_LIION, + .voltage_bootlock_lfp = PWRMGT_VOLTAGE_BOOTLOCK_LFP, + .voltage_bootlock_lto = PWRMGT_VOLTAGE_BOOTLOCK_LTO }; void XiaoNrf52Board::initiateShutdown(uint8_t reason) { @@ -22,7 +26,8 @@ void XiaoNrf52Board::initiateShutdown(uint8_t reason) { digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH); if (enable_lpcomp) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + configureVoltageWake(power_config.lpcomp_ain_channel, + getRefselForChemistry(battery_chem, &power_config)); } enterSystemOff(reason); diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index 25619b9e59..8da264e656 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -75,20 +75,20 @@ static const uint8_t D10 = 10; #define AREF_VOLTAGE (3.0) #define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge -// Power management boot protection threshold (millivolts) -// Set to 0 to disable boot protection -#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage +// Power management boot protection thresholds (millivolts) +// Safe minimum voltages per battery chemistry +#define PWRMGT_VOLTAGE_BOOTLOCK_LIION 3000 // Li-ion/LiPo minimum +#define PWRMGT_VOLTAGE_BOOTLOCK_LFP 2500 // LFP minimum +#define PWRMGT_VOLTAGE_BOOTLOCK_LTO 1800 // LTO minimum // LPCOMP wake configuration (voltage recovery from SYSTEMOFF) -#define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT +#define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT // IMPORTANT: The XIAO exposes battery via a resistor divider (ADC_MULTIPLIER = 3.0). // LPCOMP measures the divided voltage, not the battery voltage directly. -// Vpin = VDD * (REFSEL fraction), and VBAT ≈ Vpin * ADC_MULTIPLIER. -// -// Using 3/8 VDD gives a wake threshold above the boot protection point: -// - If VDD ≈ 3.0V: VBAT ≈ (3.0 * 3/8) * 3 ≈ 3375mV -// - If VDD ≈ 3.3V: VBAT ≈ (3.3 * 3/8) * 3 ≈ 3712mV -#define PWRMGT_LPCOMP_REFSEL 2 // 3/8 VDD (~3.38-3.71V) +// VBAT_wake = REFSEL_fraction × VDD_sys × ADC_MULTIPLIER +#define PWRMGT_LPCOMP_REFSEL_LIION 2 // 3/8 VDD (~3.4–3.7V) +#define PWRMGT_LPCOMP_REFSEL_LFP 10 // 5/16 VDD (~2.8–3.1V) +#define PWRMGT_LPCOMP_REFSEL_LTO 1 // 2/8 VDD (~2.25–2.47V) static const uint8_t A0 = PIN_A0; static const uint8_t A1 = PIN_A1;