Fix rak_wismeshtag low‑voltage reboot hang after App configuration (#9897)

* Fix TAG low‑voltage reboot hang after App configuration

* nRF52: Move low-VDD System OFF logic to variant hook

* Addressed review

* serialize SAADC access with shared mutex for VDD and battery reads

* raise LPCOMP wake threshold to ensure rising-edge wake

* Trunk fmt

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
Ethac.chen
2026-03-27 19:56:19 +08:00
committed by GitHub
parent 33e7f16c05
commit c36ae159ed
6 changed files with 129 additions and 6 deletions

View File

@@ -35,6 +35,11 @@
#include "nrfx_power.h"
#endif
#if defined(ARCH_NRF52)
#include "Nrf52SaadcLock.h"
#include "concurrency/LockGuard.h"
#endif
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
@@ -328,6 +333,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
scaled *= operativeAdcMultiplier;
#else // block for all other platforms
#ifdef ARCH_NRF52
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
#endif
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
raw += analogRead(BATTERY_PIN);
}

View File

@@ -0,0 +1,13 @@
#include "Nrf52SaadcLock.h"
#include "concurrency/Lock.h"
#include "configuration.h"
#ifdef ARCH_NRF52
namespace concurrency
{
static Lock nrf52SaadcLockInstance;
Lock *nrf52SaadcLock = &nrf52SaadcLockInstance;
} // namespace concurrency
#endif

View File

@@ -0,0 +1,12 @@
#pragma once
#ifdef ARCH_NRF52
namespace concurrency
{
class Lock;
/** Shared mutex for SAADC configuration and reads (VDD + battery analog path). */
extern Lock *nrf52SaadcLock;
} // namespace concurrency
#endif

View File

@@ -25,6 +25,8 @@
#include "power.h"
#include <power/PowerHAL.h>
#include "Nrf52SaadcLock.h"
#include "concurrency/LockGuard.h"
#include <hal/nrf_lpcomp.h>
#ifdef BQ25703A_ADDR
@@ -51,6 +53,10 @@ uint16_t getVDDVoltage();
void variant_shutdown() __attribute__((weak));
void variant_shutdown() {}
// Optional variant hook called each nrf52Loop(); e.g. for low-VDD System OFF.
void variant_nrf52LoopHook(void) __attribute__((weak));
void variant_nrf52LoopHook(void) {}
static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
@@ -74,11 +80,18 @@ bool powerHAL_isVBUSConnected()
bool powerHAL_isPowerLevelSafe()
{
static bool powerLevelSafe = true;
uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV
uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000;
#ifdef SAFE_VDD_VOLTAGE_THRESHOLD_MV
uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD_MV;
#else
uint16_t threshold = (uint16_t)(SAFE_VDD_VOLTAGE_THRESHOLD * 1000.0f + 0.5f); // convert V to mV
#endif
#ifdef SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV
uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV;
#else
uint16_t hysteresis = (uint16_t)(SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000.0f + 0.5f);
#endif
if (powerLevelSafe) {
if (getVDDVoltage() < threshold) {
@@ -125,11 +138,12 @@ void powerHAL_platformInit()
// get VDD voltage (in millivolts)
uint16_t getVDDVoltage()
{
// we use the same values as regular battery read so there is no conflict on SAADC
concurrency::LockGuard guard(concurrency::nrf52SaadcLock);
// Match battery read resolution; SAADC is shared with AnalogBatteryLevel in Power.cpp.
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
// VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V
// let's hope battery reading runs in same task and we don't have race condition
analogReference(AR_INTERNAL);
uint16_t vddADCRead = analogReadVDD();
@@ -326,6 +340,8 @@ void nrf52Loop()
checkSDEvents();
reportLittleFSCorruptionOnce();
variant_nrf52LoopHook(); // Optional variant hook called each nrf52Loop();
}
#ifdef USE_SEMIHOSTING

View File

@@ -19,7 +19,11 @@
*/
#include "variant.h"
#include "Arduino.h"
#include "FreeRTOS.h"
#include "nrf.h"
#include "power/PowerHAL.h"
#include "sleep.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
@@ -40,3 +44,39 @@ void initVariant()
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
}
#ifdef LOW_VDD_SYSTEMOFF_DELAY_MS
void variant_nrf52LoopHook(void)
{
// If VDD stays unsafe for a while (brownout), force System OFF.
// Skip when VBUS present to allow recovery while USB-powered.
if (!powerHAL_isVBUSConnected()) {
// Rate-limit VDD safety checks: powerHAL_isPowerLevelSafe() calls getVDDVoltage() each time.
static constexpr uint32_t POWER_LEVEL_CHECK_INTERVAL_MS = 100;
static uint32_t last_vdd_check_ms = 0;
static bool last_power_level_safe = true;
const uint32_t now = millis();
if (last_vdd_check_ms == 0 || (uint32_t)(now - last_vdd_check_ms) >= POWER_LEVEL_CHECK_INTERVAL_MS) {
last_vdd_check_ms = now;
last_power_level_safe = powerHAL_isPowerLevelSafe();
}
// Do not use millis()==0 as a sentinel: at boot, millis() may be 0 while VDD is unsafe.
static bool low_vdd_timer_armed = false;
static uint32_t low_vdd_since_ms = 0;
if (!last_power_level_safe) {
if (!low_vdd_timer_armed) {
low_vdd_since_ms = now;
low_vdd_timer_armed = true;
}
if ((uint32_t)(now - low_vdd_since_ms) >= (uint32_t)LOW_VDD_SYSTEMOFF_DELAY_MS) {
cpuDeepSleep(portMAX_DELAY);
}
} else {
low_vdd_timer_armed = false;
}
}
}
#endif

View File

@@ -225,7 +225,41 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER 1.73
#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072
#define OCV_ARRAY 4160, 4020, 3940, 3870, 3810, 3760, 3740, 3720, 3680, 3620, 2990 // updated OCV array for rak_wismeshtag
// Wake from System OFF when battery rises again (LPCOMP).
// BAT_ADC divider: R22=1M (top), R24=1.5M (bottom) => V_BAT_ADC = VBAT * (1.5 / (1.0 + 1.5)) = 0.6 * VBAT
// RAK4630 module: AIN0 = nrf52840 AIN3 = Pin 5 (A0/BATTERY_PIN)
#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_3
// LPCOMP compares the selected input to a fraction of VDD (here 5/8 of VDD at the LPCOMP input).
// With VDD ≈ 3.3 V: threshold at input ≈ (5/8) * 3.3 V ≈ 2.06 V.
// BAT_ADC divider: V_BAT_ADC = 0.6 * VBAT → equivalent VBAT ≈ 2.06 / 0.6 ≈ 3.4 V (wake when battery recovers).
//
// Note: if VDD is drooping/tracking VBAT in the low-voltage region, using a fraction >= divider ratio helps ensure the
// input is below the threshold at shutdown; the intended wake event happens when the supply recovers enough for a rising
// crossing to occur.
#define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_5_8
// Low voltage protection:
// If VDD is below SAFE_VDD_VOLTAGE_THRESHOLD for longer than this delay (and no USB VBUS),
// the device will enter System OFF to avoid brownout loops and flash corruption.
#ifndef LOW_VDD_SYSTEMOFF_DELAY_MS
#define LOW_VDD_SYSTEMOFF_DELAY_MS 5000
#endif
// Prefer integer mV so platform code avoids float→int truncation quirks (e.g. 0.1 V → 99 vs 100 mV).
#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_MV
#define SAFE_VDD_VOLTAGE_THRESHOLD_MV 2900
#endif
#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV
#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV 100
#endif
#ifndef SAFE_VDD_VOLTAGE_THRESHOLD
#define SAFE_VDD_VOLTAGE_THRESHOLD (SAFE_VDD_VOLTAGE_THRESHOLD_MV / 1000.0f)
#endif
#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST
#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST (SAFE_VDD_VOLTAGE_THRESHOLD_HYST_MV / 1000.0f)
#endif
#define RAK_4631 1