Add saved regulator modes and battery-gated crash recovery
This commit is contained in:
1 parent
515bb57cdf
commit
c4a1d485ff
70 files changed
+5335
-2472
No files matched your search
@@ -10,6 +10,7 @@
|
||||
#include <helpers/MorseRx.h>
|
||||
#include <helpers/AGCResetScheduler.h>
|
||||
#include <helpers/BeaconConfig.h>
|
||||
#include <helpers/RegulatorMode.h>
|
||||
#ifndef RADIO_LIVENESS_SOFT_ONLY
|
||||
#include <helpers/RadioLivenessTracker.h>
|
||||
#endif
|
||||
@@ -36,6 +37,12 @@ public:
|
||||
*/
|
||||
class Radio {
|
||||
public:
|
||||
virtual uint8_t regulatorModes() const { return 0; }
|
||||
virtual power::RegulatorMode defaultRegulatorMode() const { return power::RegulatorMode::Default; }
|
||||
// Cache a selection before the first hardware initialization. Save later
|
||||
// choices for the next reboot; never change a live RX/TX regulator here.
|
||||
virtual bool configureBootRegulator(power::RegulatorMode) { return false; }
|
||||
virtual power::RegulatorMode activeRegulatorMode() const { return power::RegulatorMode::Default; }
|
||||
static constexpr uint32_t CarrierWaveDefaultMillis = 10000;
|
||||
static constexpr uint32_t CarrierWaveMaxMillis = 60000;
|
||||
virtual bool supportsCarrierWave() const { return false; }
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <math.h>
|
||||
#include "helpers/UsbLogging.h"
|
||||
#include "helpers/BatteryChargeSupport.h"
|
||||
#include "helpers/RegulatorMode.h"
|
||||
#include "helpers/PowerSavingDiagnostics.h"
|
||||
#if defined(ARDUINO) && (defined(RP2040_PLATFORM) || defined(STM32_PLATFORM))
|
||||
#include <Arduino.h>
|
||||
@@ -145,6 +146,14 @@ public:
|
||||
virtual void onBeforeTransmit() { }
|
||||
virtual void onAfterTransmit() { }
|
||||
virtual void reboot() = 0;
|
||||
virtual uint8_t mcuRegulatorModes() const { return 0; }
|
||||
virtual power::RegulatorMode defaultMcuRegulatorMode() const {
|
||||
return power::RegulatorMode::Default;
|
||||
}
|
||||
virtual bool applyMcuRegulator(power::RegulatorMode) { return false; }
|
||||
virtual power::RegulatorMode activeMcuRegulatorMode() const {
|
||||
return power::RegulatorMode::Default;
|
||||
}
|
||||
// Reboot into a UF2-capable bootloader when the platform supports the
|
||||
// retained reset request. Returns false only when unsupported or rejected;
|
||||
// a successful implementation resets and does not return.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <Arduino.h>
|
||||
#include "CarrierTools.h"
|
||||
#include "PacketFileLoggingConfig.h"
|
||||
@@ -2978,6 +2979,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
#if MESH_BATTERY_CHARGE_CONTROL
|
||||
if (mesh::power::handleBatteryChargeCommand(*_board, command, reply, 160)) return;
|
||||
#endif
|
||||
if (mesh::power::handleRegulatorCommand(command, reply, 157)) return;
|
||||
if (mesh::handleUsbLoggingWatchdogCommand(command, reply, 160)) return;
|
||||
if (mesh::powerdiag::handleCommand(command, reply, 157, millis())) return;
|
||||
if (handleManagementCommand(command, reply)) return;
|
||||
|
||||
+127
-10
@@ -9,6 +9,7 @@
|
||||
#include <target.h>
|
||||
#include "UserGpioPinPolicy.h"
|
||||
#include "PowerSavingDiagnostics.h"
|
||||
#include "Nrf52RegulatorPlatform.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include "ble_gap.h"
|
||||
@@ -29,6 +30,87 @@ static uint16_t ota_conn_handle = BLE_CONN_HANDLE_INVALID;
|
||||
static bool ota_active = false;
|
||||
static bool ota_ble_started = false;
|
||||
|
||||
// Intentional reboot paths share the recovery policy's clean-reset marker.
|
||||
// Keep this optional for standalone board/library builds without the policy.
|
||||
extern "C" void meshRegulatorCleanReset() __attribute__((weak));
|
||||
|
||||
namespace {
|
||||
volatile mesh::power::RecoveryRecord regulator_recovery_record
|
||||
__attribute__((section(".regulator_recovery"), used, aligned(4)));
|
||||
static_assert(sizeof(mesh::power::RecoveryRecord) <= 64,
|
||||
"nRF52 regulator recovery exceeds retained allocation");
|
||||
uint32_t regulator_reset_reason = 0;
|
||||
uint8_t regulator_shutdown_reason = 0;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
extern uint8_t __regulator_recovery_start__[] __attribute__((weak));
|
||||
extern uint8_t __regulator_recovery_end__[] __attribute__((weak));
|
||||
}
|
||||
|
||||
// Capture before the core's SystemInit constructor applies reset errata and
|
||||
// before power management clears GPREGRET2. This also runs on boards without
|
||||
// NRF52_POWER_MANAGEMENT; the recovery policy never writes either GPREGRET.
|
||||
static void __attribute__((constructor(101))) nrf52_regulator_reset_capture() {
|
||||
regulator_reset_reason = NRF_POWER->RESETREAS;
|
||||
regulator_shutdown_reason = NRF_POWER->GPREGRET2;
|
||||
}
|
||||
|
||||
namespace mesh { namespace power {
|
||||
volatile RecoveryRecord& platformRecoveryRecord() {
|
||||
return regulator_recovery_record;
|
||||
}
|
||||
|
||||
bool platformRecoverySupported() {
|
||||
return reinterpret_cast<uintptr_t>(__regulator_recovery_start__) == 0x20006050UL
|
||||
&& reinterpret_cast<uintptr_t>(__regulator_recovery_end__) == 0x20006090UL
|
||||
&& reinterpret_cast<uintptr_t>(®ulator_recovery_record) == 0x20006050UL;
|
||||
}
|
||||
|
||||
ResetKind platformResetKind() {
|
||||
// RESETREAS is sticky. Boards without power management otherwise retain
|
||||
// wake bits forever and could hide a later real watchdog failure.
|
||||
static bool acknowledged = false;
|
||||
if (!acknowledged) {
|
||||
uint8_t sd_enabled = 0;
|
||||
if (mesh_nrf52::softdeviceIsEnabled(sd_enabled) != NRF_SUCCESS)
|
||||
return ResetKind::Unknown;
|
||||
if (sd_enabled) {
|
||||
if (sd_power_reset_reason_clr(0xFFFFFFFFUL) != NRF_SUCCESS)
|
||||
return ResetKind::Unknown;
|
||||
} else {
|
||||
NRF_POWER->RESETREAS = 0xFFFFFFFFUL;
|
||||
}
|
||||
acknowledged = true;
|
||||
}
|
||||
// A deliberate SYSTEMOFF, reset-pin press, or debugger event never counts
|
||||
// against DCDC, even if another latched reason bit is also present.
|
||||
if (regulator_shutdown_reason == 0x4C // low voltage
|
||||
|| regulator_shutdown_reason == 0x55 // user shutdown
|
||||
|| regulator_shutdown_reason == 0x42) // boot protection
|
||||
return ResetKind::Intentional;
|
||||
const uint32_t reason = regulator_reset_reason;
|
||||
if (reason & POWER_RESETREAS_RESETPIN_Msk) return ResetKind::Intentional;
|
||||
#ifdef POWER_RESETREAS_DIF_Msk
|
||||
if (reason & POWER_RESETREAS_DIF_Msk) return ResetKind::Intentional;
|
||||
#endif
|
||||
uint32_t wakes = 0;
|
||||
#ifdef POWER_RESETREAS_OFF_Msk
|
||||
wakes |= POWER_RESETREAS_OFF_Msk;
|
||||
#endif
|
||||
#ifdef POWER_RESETREAS_LPCOMP_Msk
|
||||
wakes |= POWER_RESETREAS_LPCOMP_Msk;
|
||||
#endif
|
||||
#ifdef POWER_RESETREAS_VBUS_Msk
|
||||
wakes |= POWER_RESETREAS_VBUS_Msk;
|
||||
#endif
|
||||
if (reason & wakes) return ResetKind::Wake;
|
||||
if (reason & (POWER_RESETREAS_DOG_Msk | POWER_RESETREAS_LOCKUP_Msk
|
||||
| POWER_RESETREAS_SREQ_Msk)) return ResetKind::Crash;
|
||||
return reason == 0 ? ResetKind::Cold : ResetKind::Unknown;
|
||||
}
|
||||
}} // namespace mesh::power
|
||||
|
||||
// A normal internal-flash operation completes in milliseconds. One minute is
|
||||
// deliberately generous for other legitimate application work while still
|
||||
// recovering an indefinitely blocked SoftDevice flash wait without requiring
|
||||
@@ -113,7 +195,7 @@ bool NRF52Board::rebootToUf2Bootloader() {
|
||||
NRF_POWER->GPREGRET = DFU_MAGIC_UF2_RESET;
|
||||
}
|
||||
|
||||
NVIC_SystemReset();
|
||||
reboot();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,6 +222,46 @@ void NRF52Board::begin() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void NRF52Board::reboot() {
|
||||
if (meshRegulatorCleanReset) meshRegulatorCleanReset();
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
uint8_t NRF52Board::mcuRegulatorModes() const {
|
||||
return cpu_dcdc_supported
|
||||
? mesh::power::REGULATOR_DCDC | mesh::power::REGULATOR_LDO : 0;
|
||||
}
|
||||
|
||||
mesh::power::RegulatorMode NRF52Board::defaultMcuRegulatorMode() const {
|
||||
return cpu_dcdc_supported ? mesh::power::RegulatorMode::Dcdc
|
||||
: mesh::power::RegulatorMode::Default;
|
||||
}
|
||||
|
||||
mesh::power::RegulatorMode NRF52Board::activeMcuRegulatorMode() const {
|
||||
return cpu_dcdc_supported ? cpu_regulator_mode
|
||||
: mesh::power::RegulatorMode::Default;
|
||||
}
|
||||
|
||||
bool NRF52Board::applyMcuRegulator(mesh::power::RegulatorMode mode) {
|
||||
using mesh::power::RegulatorMode;
|
||||
if (!cpu_dcdc_supported) return false;
|
||||
if (mode == RegulatorMode::Default) mode = defaultMcuRegulatorMode();
|
||||
if (mode != RegulatorMode::Dcdc && mode != RegulatorMode::Ldo) return false;
|
||||
|
||||
uint8_t sd_enabled = 0;
|
||||
if (mesh_nrf52::softdeviceIsEnabled(sd_enabled) != NRF_SUCCESS) return false;
|
||||
const bool dcdc = mode == RegulatorMode::Dcdc;
|
||||
if (sd_enabled) {
|
||||
if (sd_power_dcdc_mode_set(dcdc ? NRF_POWER_DCDC_ENABLE
|
||||
: NRF_POWER_DCDC_DISABLE) != NRF_SUCCESS)
|
||||
return false;
|
||||
} else {
|
||||
NRF_POWER->DCDCEN = dcdc ? 1 : 0;
|
||||
}
|
||||
cpu_regulator_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NRF52Board::loop() {
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
if (!mesh::power::voltagePolicySupported()) return;
|
||||
@@ -454,15 +576,10 @@ void NRF52Board::armVbusWake() {
|
||||
|
||||
void NRF52BoardDCDC::begin() {
|
||||
NRF52Board::begin();
|
||||
|
||||
// Enable DC/DC converter for improved power efficiency
|
||||
uint8_t sd_enabled = 0;
|
||||
mesh_nrf52::softdeviceIsEnabled(sd_enabled);
|
||||
if (sd_enabled) {
|
||||
sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);
|
||||
} else {
|
||||
NRF_POWER->DCDCEN = 1;
|
||||
}
|
||||
// Only boards which explicitly call this method approve the external LC
|
||||
// circuitry. The common startup hook applies saved choices after the
|
||||
// board's battery ADC and filesystem are ready; do not enable DCDC here.
|
||||
cpu_dcdc_supported = true;
|
||||
}
|
||||
|
||||
bool NRF52Board::isExternalPowered() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/KeyValueStore.h>
|
||||
#include <helpers/RegulatorMode.h>
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
|
||||
@@ -35,6 +36,8 @@ class NRF52Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
char *ota_name;
|
||||
bool cpu_dcdc_supported = false;
|
||||
mesh::power::RegulatorMode cpu_regulator_mode = mesh::power::RegulatorMode::Ldo;
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
void pwrmgtInit() { initPowerMgr(); }
|
||||
@@ -65,7 +68,11 @@ public:
|
||||
void serviceWatchdog() override;
|
||||
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
||||
virtual float getMCUTemperature() override;
|
||||
virtual void reboot() override { NVIC_SystemReset(); }
|
||||
void reboot() override;
|
||||
uint8_t mcuRegulatorModes() const override;
|
||||
mesh::power::RegulatorMode defaultMcuRegulatorMode() const override;
|
||||
bool applyMcuRegulator(mesh::power::RegulatorMode mode) override;
|
||||
mesh::power::RegulatorMode activeMcuRegulatorMode() const override;
|
||||
bool rebootToUf2Bootloader() override;
|
||||
virtual void shutdownPeripherals();
|
||||
virtual void powerOff() override;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include "RegulatorRecovery.h"
|
||||
|
||||
namespace mesh { namespace power {
|
||||
|
||||
volatile RecoveryRecord& platformRecoveryRecord();
|
||||
ResetKind platformResetKind();
|
||||
// False for an unsupported linker layout, so an unreserved orphan section
|
||||
// cannot accidentally enable automatic recovery.
|
||||
bool platformRecoverySupported();
|
||||
|
||||
}} // namespace mesh::power
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh { namespace power {
|
||||
|
||||
enum class RegulatorMode : uint8_t { Default = 0, Dcdc = 1, Ldo = 2 };
|
||||
constexpr uint8_t REGULATOR_DCDC = 1;
|
||||
constexpr uint8_t REGULATOR_LDO = 2;
|
||||
|
||||
inline bool regulatorModeSupported(uint8_t modes, RegulatorMode mode) {
|
||||
return modes != 0 && (mode == RegulatorMode::Default
|
||||
|| (mode == RegulatorMode::Dcdc && (modes & REGULATOR_DCDC))
|
||||
|| (mode == RegulatorMode::Ldo && (modes & REGULATOR_LDO)));
|
||||
}
|
||||
|
||||
}} // namespace mesh::power
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "RegulatorPlatform.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_system.h>
|
||||
#include <esp_attr.h>
|
||||
#include <soc/soc_caps.h>
|
||||
#if SOC_RTC_SLOW_MEM_SUPPORTED || SOC_RTC_FAST_MEM_SUPPORTED
|
||||
#define MESH_REGULATOR_RTC 1
|
||||
static RTC_NOINIT_ATTR volatile mesh::power::RecoveryRecord regulator_record;
|
||||
#else
|
||||
static volatile mesh::power::RecoveryRecord regulator_record;
|
||||
#endif
|
||||
namespace mesh { namespace power {
|
||||
volatile RecoveryRecord& platformRecoveryRecord() { return regulator_record; }
|
||||
bool platformRecoverySupported() {
|
||||
#ifdef MESH_REGULATOR_RTC
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
ResetKind platformResetKind() {
|
||||
switch (esp_reset_reason()) {
|
||||
case ESP_RST_PANIC:
|
||||
case ESP_RST_INT_WDT:
|
||||
case ESP_RST_TASK_WDT:
|
||||
case ESP_RST_WDT: return ResetKind::Crash;
|
||||
case ESP_RST_DEEPSLEEP: return ResetKind::Wake;
|
||||
case ESP_RST_SW: return ResetKind::Intentional;
|
||||
case ESP_RST_POWERON:
|
||||
case ESP_RST_EXT: return ResetKind::Cold;
|
||||
// A supply failure is not evidence that the DC/DC regulator is faulty.
|
||||
default: return ResetKind::Unknown;
|
||||
}
|
||||
}
|
||||
}}
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <hardware/watchdog.h>
|
||||
// Arduino-Pico's linker explicitly keeps this outside BSS and above the
|
||||
// boot-ROM's scratch area. No watchdog scratch registers are commandeered.
|
||||
static volatile mesh::power::RecoveryRecord regulator_record
|
||||
__attribute__((section(".uninitialized_data.regulator_recovery"), used));
|
||||
namespace mesh { namespace power {
|
||||
volatile RecoveryRecord& platformRecoveryRecord() { return regulator_record; }
|
||||
bool platformRecoverySupported() { return true; }
|
||||
ResetKind platformResetKind() {
|
||||
if (watchdog_enable_caused_reboot()) return ResetKind::Crash;
|
||||
return watchdog_caused_reboot() ? ResetKind::Intentional : ResetKind::Cold;
|
||||
}
|
||||
}}
|
||||
#elif !defined(NRF52_PLATFORM)
|
||||
namespace mesh { namespace power {
|
||||
static volatile RecoveryRecord regulator_record;
|
||||
volatile RecoveryRecord& platformRecoveryRecord() { return regulator_record; }
|
||||
bool platformRecoverySupported() { return false; }
|
||||
ResetKind platformResetKind() { return ResetKind::Unknown; }
|
||||
}}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "RegulatorRecovery.h"
|
||||
|
||||
namespace mesh { namespace power {
|
||||
volatile RecoveryRecord& platformRecoveryRecord();
|
||||
ResetKind platformResetKind();
|
||||
bool platformRecoverySupported();
|
||||
}}
|
||||
@@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh { namespace power {
|
||||
|
||||
constexpr uint8_t RECOVERY_MCU = 1;
|
||||
constexpr uint8_t RECOVERY_RADIO = 2;
|
||||
constexpr uint8_t RECOVERY_DOMAINS = RECOVERY_MCU | RECOVERY_RADIO;
|
||||
constexpr uint8_t REGULATOR_CRASH_THRESHOLD = 3;
|
||||
constexpr uint32_t REGULATOR_HEALTHY_MS = 300000;
|
||||
constexpr uint32_t REGULATOR_BATTERY_FRESH_MS = 60000;
|
||||
|
||||
enum class ResetKind : uint8_t { Cold, Intentional, Wake, Crash, Unknown };
|
||||
|
||||
// Keep this POD in reset-retained RAM, never in flash. The checksum is committed
|
||||
// last; an interrupted write or an uninitialised retained region cannot add a
|
||||
// crash to the next boot. Only the sticky fallback mask needs flash storage.
|
||||
struct RecoveryRecord {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t bytes;
|
||||
uint32_t startedAt;
|
||||
uint32_t heartbeatAt;
|
||||
uint32_t batteryAt;
|
||||
uint8_t activeDcdcMask;
|
||||
uint8_t fallbackMask;
|
||||
uint8_t mcuCrashes;
|
||||
uint8_t radioCrashes;
|
||||
uint8_t batteryPercent;
|
||||
uint8_t flags;
|
||||
uint16_t reserved;
|
||||
uint32_t crc;
|
||||
};
|
||||
static_assert(sizeof(RecoveryRecord) <= 64, "regulator recovery retention budget");
|
||||
static_assert(sizeof(RecoveryRecord) == 32, "regulator recovery record layout");
|
||||
static_assert(offsetof(RecoveryRecord, crc) == 28, "regulator recovery CRC layout");
|
||||
|
||||
class RecoveryPolicy {
|
||||
public:
|
||||
explicit RecoveryPolicy(volatile RecoveryRecord& retained) : retained_(retained), state_{} {}
|
||||
|
||||
// Read the previous boot exactly once, before arm(). A cold/intentional/wake
|
||||
// or unclassified reset breaks a streak, but never undoes a saved fallback.
|
||||
void begin(ResetKind reset, uint8_t savedFallbackMask) {
|
||||
RecoveryRecord previous{};
|
||||
read(previous);
|
||||
const bool valid = isValid(previous);
|
||||
state_ = RecoveryRecord{};
|
||||
state_.magic = kMagic;
|
||||
state_.version = kVersion;
|
||||
state_.bytes = sizeof(RecoveryRecord);
|
||||
state_.fallbackMask = savedFallbackMask & RECOVERY_DOMAINS;
|
||||
if (valid) {
|
||||
state_.fallbackMask |= previous.fallbackMask;
|
||||
if (reset == ResetKind::Crash && eligible(previous)) {
|
||||
state_.mcuCrashes = nextCount(previous, RECOVERY_MCU, previous.mcuCrashes);
|
||||
state_.radioCrashes = nextCount(previous, RECOVERY_RADIO, previous.radioCrashes);
|
||||
if (state_.mcuCrashes >= REGULATOR_CRASH_THRESHOLD)
|
||||
state_.fallbackMask |= RECOVERY_MCU;
|
||||
if (state_.radioCrashes >= REGULATOR_CRASH_THRESHOLD)
|
||||
state_.fallbackMask |= RECOVERY_RADIO;
|
||||
}
|
||||
}
|
||||
// Leave the new boot disarmed until its actual selected modes and measured
|
||||
// battery are known. A crash before that point cannot extend the streak.
|
||||
commit();
|
||||
}
|
||||
|
||||
// Call before enabling either DC-DC regulator / starting the radio. Each bit
|
||||
// describes an actually selected DC-DC domain, not mere board capability.
|
||||
void arm(uint8_t activeDcdcMask, uint32_t now, uint8_t percent, bool valid) {
|
||||
state_.activeDcdcMask = activeDcdcMask & RECOVERY_DOMAINS & ~state_.fallbackMask;
|
||||
state_.startedAt = state_.heartbeatAt = now;
|
||||
state_.flags = kArmed;
|
||||
setBattery(now, percent, valid);
|
||||
commit();
|
||||
}
|
||||
|
||||
// Heartbeats must not refresh the battery timestamp: a stale/unknown reading
|
||||
// must never turn a USB-only device or a discharged battery into an event.
|
||||
void heartbeat(uint32_t now) {
|
||||
state_.heartbeatAt = now;
|
||||
if ((state_.flags & kArmed) && !(state_.flags & kHealthy) &&
|
||||
elapsed(now, state_.startedAt) >= REGULATOR_HEALTHY_MS) {
|
||||
state_.mcuCrashes = state_.radioCrashes = 0;
|
||||
state_.flags |= kHealthy;
|
||||
}
|
||||
commit();
|
||||
}
|
||||
|
||||
void updateBattery(uint32_t now, uint8_t percent, bool valid) {
|
||||
setBattery(now, percent, valid);
|
||||
commit();
|
||||
}
|
||||
|
||||
void updateActiveDomains(uint8_t activeDcdcMask) {
|
||||
state_.activeDcdcMask = activeDcdcMask & RECOVERY_DOMAINS & ~state_.fallbackMask;
|
||||
commit();
|
||||
}
|
||||
|
||||
// Convenience for callers that really take a fresh battery measurement each
|
||||
// tick. Use heartbeat() separately when measurements are less frequent.
|
||||
void tick(uint32_t now, uint8_t percent, bool valid) {
|
||||
setBattery(now, percent, valid);
|
||||
heartbeat(now);
|
||||
}
|
||||
|
||||
void markCleanReset() {
|
||||
state_.flags = (state_.flags & ~kArmed) | kClean;
|
||||
state_.mcuCrashes = state_.radioCrashes = 0;
|
||||
commit();
|
||||
}
|
||||
|
||||
// A deliberate user mode change resets only that domain's learned fallback.
|
||||
void clearDomain(uint8_t domains) {
|
||||
domains &= RECOVERY_DOMAINS;
|
||||
state_.fallbackMask &= ~domains;
|
||||
if (domains & RECOVERY_MCU) state_.mcuCrashes = 0;
|
||||
if (domains & RECOVERY_RADIO) state_.radioCrashes = 0;
|
||||
commit();
|
||||
}
|
||||
|
||||
uint8_t fallbackMask() const { return state_.fallbackMask; }
|
||||
uint8_t crashCount(uint8_t domain) const {
|
||||
return domain == RECOVERY_MCU ? state_.mcuCrashes :
|
||||
domain == RECOVERY_RADIO ? state_.radioCrashes : 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kMagic = 0x52504752; // RGPR
|
||||
static constexpr uint16_t kVersion = 1;
|
||||
static constexpr uint8_t kArmed = 1;
|
||||
static constexpr uint8_t kClean = 2;
|
||||
static constexpr uint8_t kBatteryValid = 4;
|
||||
static constexpr uint8_t kHealthy = 8;
|
||||
static constexpr uint8_t kFlags = kArmed | kClean | kBatteryValid | kHealthy;
|
||||
|
||||
volatile RecoveryRecord& retained_;
|
||||
RecoveryRecord state_;
|
||||
|
||||
static uint32_t elapsed(uint32_t now, uint32_t then) { return now - then; }
|
||||
|
||||
static uint32_t checksum(const RecoveryRecord& record) {
|
||||
uint32_t crc = 0xffffffff;
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&record);
|
||||
for (size_t i = 0; i < offsetof(RecoveryRecord, crc); ++i) {
|
||||
crc ^= bytes[i];
|
||||
for (uint8_t bit = 0; bit < 8; ++bit)
|
||||
crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u)));
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
static bool isValid(const RecoveryRecord& record) {
|
||||
return record.magic == kMagic && record.version == kVersion &&
|
||||
record.bytes == sizeof(RecoveryRecord) && record.reserved == 0 &&
|
||||
!(record.activeDcdcMask & ~RECOVERY_DOMAINS) &&
|
||||
!(record.fallbackMask & ~RECOVERY_DOMAINS) &&
|
||||
!(record.flags & ~kFlags) && record.batteryPercent <= 100 &&
|
||||
record.mcuCrashes <= REGULATOR_CRASH_THRESHOLD &&
|
||||
record.radioCrashes <= REGULATOR_CRASH_THRESHOLD &&
|
||||
record.crc == checksum(record);
|
||||
}
|
||||
|
||||
static bool eligible(const RecoveryRecord& record) {
|
||||
return (record.flags & (kArmed | kClean | kBatteryValid)) == (kArmed | kBatteryValid) &&
|
||||
record.batteryPercent >= 50 &&
|
||||
elapsed(record.heartbeatAt, record.batteryAt) <= REGULATOR_BATTERY_FRESH_MS;
|
||||
}
|
||||
|
||||
static uint8_t nextCount(const RecoveryRecord& previous, uint8_t domain, uint8_t count) {
|
||||
if (!(previous.activeDcdcMask & domain) || (previous.fallbackMask & domain)) return 0;
|
||||
return count < REGULATOR_CRASH_THRESHOLD ? count + 1 : REGULATOR_CRASH_THRESHOLD;
|
||||
}
|
||||
|
||||
void setBattery(uint32_t now, uint8_t percent, bool valid) {
|
||||
state_.batteryAt = now;
|
||||
state_.batteryPercent = percent <= 100 ? percent : 0;
|
||||
if (valid && percent <= 100) state_.flags |= kBatteryValid;
|
||||
else state_.flags &= ~kBatteryValid;
|
||||
}
|
||||
|
||||
void read(RecoveryRecord& result) const {
|
||||
const volatile uint8_t* source = reinterpret_cast<const volatile uint8_t*>(&retained_);
|
||||
uint8_t* target = reinterpret_cast<uint8_t*>(&result);
|
||||
for (size_t i = 0; i < sizeof(result); ++i) target[i] = source[i];
|
||||
}
|
||||
|
||||
void commit() {
|
||||
state_.crc = checksum(state_);
|
||||
// Invalidate before changing any payload, then publish its CRC last. No
|
||||
// flash writes, heap allocation, interrupt dependencies, or platform APIs.
|
||||
retained_.magic = 0;
|
||||
retained_.crc = 0;
|
||||
volatile uint8_t* target = reinterpret_cast<volatile uint8_t*>(&retained_);
|
||||
const uint8_t* source = reinterpret_cast<const uint8_t*>(&state_);
|
||||
for (size_t i = sizeof(state_.magic); i < offsetof(RecoveryRecord, crc); ++i)
|
||||
target[i] = source[i];
|
||||
retained_.magic = state_.magic;
|
||||
retained_.crc = state_.crc;
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace mesh::power
|
||||
@@ -0,0 +1,264 @@
|
||||
#include "RegulatorSettings.h"
|
||||
#include "RegulatorPlatform.h"
|
||||
#include "NRF52VoltageRules.h"
|
||||
#include "NRF52VoltagePolicy.h"
|
||||
#include "PersistentStoreFormat.h"
|
||||
#include <Arduino.h>
|
||||
#include <Dispatcher.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh { namespace power {
|
||||
namespace {
|
||||
MainBoard* main_board = nullptr;
|
||||
Radio* main_radio = nullptr;
|
||||
FILESYSTEM* settings_fs = nullptr;
|
||||
RegulatorMode requested[2] = {RegulatorMode::Default, RegulatorMode::Default};
|
||||
uint8_t saved_fallback = 0;
|
||||
bool initialized = false;
|
||||
bool fallback_saved = true;
|
||||
uint32_t last_service = 0, last_battery = 0, last_save = 0;
|
||||
RecoveryPolicy& recovery() {
|
||||
static RecoveryPolicy policy(platformRecoveryRecord());
|
||||
return policy;
|
||||
}
|
||||
const char* const path = "/regulator_v1";
|
||||
const char* const temporary = "/regulator_v1.tmp";
|
||||
const char* const backup = "/regulator_v1.bak";
|
||||
|
||||
void encode(RegulatorMode mcu, RegulatorMode radio, uint8_t fallback, uint8_t (&data)[12]) {
|
||||
memcpy(data, "REG1", 4);
|
||||
data[4] = uint8_t(mcu); data[5] = uint8_t(radio);
|
||||
data[6] = fallback; data[7] = 0;
|
||||
storage::writeLE32(data + 8, storage::updateCRC32(0xffffffffUL, data, 8));
|
||||
}
|
||||
bool readFile(const char* name, uint8_t (&data)[12]) {
|
||||
if (!settings_fs) return false;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
File file(*settings_fs);
|
||||
if (!file.open(name, FILE_O_READ)) return false;
|
||||
#else
|
||||
File file = settings_fs->open(name, "r");
|
||||
if (!file) return false;
|
||||
#endif
|
||||
const bool complete = file.size() == sizeof(data)
|
||||
&& file.read(data, sizeof(data)) == sizeof(data);
|
||||
file.close();
|
||||
return complete && memcmp(data, "REG1", 4) == 0
|
||||
&& data[4] <= 2 && data[5] <= 2 && data[6] <= RECOVERY_DOMAINS
|
||||
&& data[7] == 0 && storage::readLE32(data + 8)
|
||||
== storage::updateCRC32(0xffffffffUL, data, 8);
|
||||
}
|
||||
bool save(RegulatorMode mcu, RegulatorMode radio, uint8_t fallback) {
|
||||
if (!settings_fs) return false;
|
||||
settings_fs->remove(temporary);
|
||||
if (settings_fs->exists(temporary)) return false;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
File file(*settings_fs);
|
||||
if (!file.open(temporary, FILE_O_WRITE)) return false;
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = settings_fs->open(temporary, "w");
|
||||
if (!file) return false;
|
||||
#else
|
||||
File file = settings_fs->open(temporary, "w", true);
|
||||
if (!file) return false;
|
||||
#endif
|
||||
uint8_t data[12], checked[12];
|
||||
encode(mcu, radio, fallback, data);
|
||||
const bool written = file.write(data, sizeof(data)) == sizeof(data);
|
||||
file.close();
|
||||
if (!written || !readFile(temporary, checked)
|
||||
|| memcmp(data, checked, sizeof(data)) != 0) return false;
|
||||
if (settings_fs->exists(backup) && !settings_fs->remove(backup)) return false;
|
||||
if (settings_fs->exists(path) && !settings_fs->rename(path, backup)) return false;
|
||||
if (!settings_fs->rename(temporary, path)) {
|
||||
if (settings_fs->exists(backup)) settings_fs->rename(backup, path);
|
||||
return false;
|
||||
}
|
||||
settings_fs->remove(backup);
|
||||
saved_fallback = fallback;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t readBatteryPercent(bool& valid) {
|
||||
uint16_t samples[3];
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
samples[i] = main_board->getBattMilliVolts();
|
||||
if (i != 2) delay(5);
|
||||
}
|
||||
if (samples[0] > samples[1]) { auto v = samples[0]; samples[0] = samples[1]; samples[1] = v; }
|
||||
if (samples[1] > samples[2]) { auto v = samples[1]; samples[1] = samples[2]; samples[2] = v; }
|
||||
if (samples[0] > samples[1]) samples[1] = samples[0];
|
||||
const uint16_t mv = samples[1];
|
||||
valid = mv >= 2000 && mv <= 5500;
|
||||
if (!valid) return 0;
|
||||
#if defined(NRF52_PLATFORM) && defined(NRF52_POWER_MANAGEMENT)
|
||||
// Board ADC methods already return calibrated millivolts.
|
||||
return configuredBatteryPercent(mv);
|
||||
#else
|
||||
return batteryPercent(mv, 3000, 4200);
|
||||
#endif
|
||||
}
|
||||
RegulatorMode effective(bool mcu) {
|
||||
RegulatorMode mode = requested[mcu ? 0 : 1];
|
||||
if (mode == RegulatorMode::Default) mode = mcu
|
||||
? main_board->defaultMcuRegulatorMode() : main_radio->defaultRegulatorMode();
|
||||
const uint8_t bit = mcu ? RECOVERY_MCU : RECOVERY_RADIO;
|
||||
if (mode == RegulatorMode::Dcdc && (recovery().fallbackMask() & bit)) mode = RegulatorMode::Ldo;
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
const char* regulatorModeName(RegulatorMode mode) {
|
||||
return mode == RegulatorMode::Dcdc ? "dcdc" : mode == RegulatorMode::Ldo ? "ldo" : "auto";
|
||||
}
|
||||
uint8_t configuredRegulatorModes(bool mcu) {
|
||||
if (!main_board || !main_radio) return 0;
|
||||
return mcu ? main_board->mcuRegulatorModes() : main_radio->regulatorModes();
|
||||
}
|
||||
RegulatorMode requestedRegulatorMode(bool mcu) { return requested[mcu ? 0 : 1]; }
|
||||
RegulatorMode activeRegulatorMode(bool mcu) {
|
||||
if (!main_board || !main_radio) return RegulatorMode::Default;
|
||||
return mcu ? main_board->activeMcuRegulatorMode() : main_radio->activeRegulatorMode();
|
||||
}
|
||||
const char* activeRegulatorModeName(bool mcu) {
|
||||
const auto mode = activeRegulatorMode(mcu);
|
||||
return mode == RegulatorMode::Default ? "unknown" : regulatorModeName(mode);
|
||||
}
|
||||
bool regulatorFallbackActive(bool mcu) {
|
||||
return initialized && (recovery().fallbackMask() & (mcu ? RECOVERY_MCU : RECOVERY_RADIO));
|
||||
}
|
||||
bool regulatorRecoveryAvailable() { return initialized && platformRecoverySupported(); }
|
||||
|
||||
bool beginRegulatorSettings(MainBoard& board, Radio& radio, FILESYSTEM* fs) {
|
||||
if (initialized) return true;
|
||||
main_board = &board; main_radio = &radio; settings_fs = fs;
|
||||
if (!board.mcuRegulatorModes() && !radio.regulatorModes()) return true;
|
||||
uint8_t data[12];
|
||||
bool loaded = readFile(path, data);
|
||||
if (!loaded && readFile(backup, data)) {
|
||||
loaded = true;
|
||||
settings_fs->remove(path);
|
||||
settings_fs->rename(backup, path);
|
||||
}
|
||||
if (loaded) {
|
||||
requested[0] = RegulatorMode(data[4]); requested[1] = RegulatorMode(data[5]);
|
||||
saved_fallback = data[6];
|
||||
}
|
||||
recovery().begin(platformRecoverySupported() ? platformResetKind() : ResetKind::Unknown,
|
||||
saved_fallback);
|
||||
initialized = true;
|
||||
uint8_t active_dcdc = 0;
|
||||
bool valid = false;
|
||||
const uint8_t percent = readBatteryPercent(valid);
|
||||
const uint32_t now = millis();
|
||||
const auto cpu_mode = effective(true), radio_mode = effective(false);
|
||||
if (configuredRegulatorModes(true) && cpu_mode == RegulatorMode::Dcdc) active_dcdc |= RECOVERY_MCU;
|
||||
if (configuredRegulatorModes(false) && radio_mode == RegulatorMode::Dcdc) active_dcdc |= RECOVERY_RADIO;
|
||||
recovery().arm(active_dcdc, now, percent, valid);
|
||||
fallback_saved = recovery().fallbackMask() == saved_fallback
|
||||
|| save(requested[0], requested[1], recovery().fallbackMask());
|
||||
bool okay = true;
|
||||
if (configuredRegulatorModes(true)) {
|
||||
if (!board.applyMcuRegulator(cpu_mode)) {
|
||||
active_dcdc &= ~RECOVERY_MCU;
|
||||
recovery().arm(active_dcdc, now, percent, valid);
|
||||
okay = false;
|
||||
}
|
||||
}
|
||||
if (configuredRegulatorModes(false) && !radio.configureBootRegulator(radio_mode)) {
|
||||
active_dcdc &= ~RECOVERY_RADIO;
|
||||
recovery().updateActiveDomains(active_dcdc);
|
||||
okay = false;
|
||||
}
|
||||
last_service = last_battery = last_save = now;
|
||||
return okay;
|
||||
}
|
||||
|
||||
void serviceRegulatorSettings() {
|
||||
if (!initialized) return;
|
||||
const uint32_t now = millis();
|
||||
if (uint32_t(now - last_service) < 1000) return;
|
||||
last_service = now;
|
||||
const uint8_t active_dcdc =
|
||||
(activeRegulatorMode(true) == RegulatorMode::Dcdc ? RECOVERY_MCU : 0)
|
||||
| (activeRegulatorMode(false) == RegulatorMode::Dcdc ? RECOVERY_RADIO : 0);
|
||||
recovery().updateActiveDomains(active_dcdc);
|
||||
if (uint32_t(now - last_battery) >= 30000) {
|
||||
bool valid = false;
|
||||
const uint8_t percent = readBatteryPercent(valid);
|
||||
last_battery = millis();
|
||||
recovery().updateBattery(last_battery, percent, valid);
|
||||
}
|
||||
recovery().heartbeat(millis());
|
||||
if (!fallback_saved && uint32_t(now - last_save) >= 30000) {
|
||||
last_save = now;
|
||||
fallback_saved = save(requested[0], requested[1], recovery().fallbackMask());
|
||||
}
|
||||
}
|
||||
|
||||
bool handleRegulatorCommand(const char* command, char* reply, size_t capacity) {
|
||||
const bool get = strncmp(command, "get ", 4) == 0;
|
||||
const bool set = strncmp(command, "set ", 4) == 0;
|
||||
if (!get && !set) return false;
|
||||
const char* key = command + 4;
|
||||
bool mcu;
|
||||
size_t length;
|
||||
if (strncmp(key, "mcu.regulator", 13) == 0) { mcu = true; length = 13; }
|
||||
else if (strncmp(key, "radio.regulator", 15) == 0) { mcu = false; length = 15; }
|
||||
else return false;
|
||||
if (key[length] != 0 && key[length] != ' ') return false;
|
||||
if (!initialized || !configuredRegulatorModes(mcu)) {
|
||||
snprintf(reply, capacity, "Error: %s regulator selection unsupported", mcu ? "MCU" : "radio");
|
||||
return true;
|
||||
}
|
||||
const char* value = key + length;
|
||||
if (get) {
|
||||
if (*value) snprintf(reply, capacity, "Error: unexpected argument");
|
||||
else snprintf(reply, capacity, "> %s; active %s; fallback %s; crashes %u; recovery %s%s",
|
||||
regulatorModeName(requestedRegulatorMode(mcu)), activeRegulatorModeName(mcu),
|
||||
regulatorFallbackActive(mcu) ? "ldo" : "off",
|
||||
unsigned(recovery().crashCount(mcu ? RECOVERY_MCU : RECOVERY_RADIO)),
|
||||
regulatorRecoveryAvailable() ? "on" : "unsupported",
|
||||
fallback_saved ? "" : "; fallback not saved");
|
||||
return true;
|
||||
}
|
||||
while (*value == ' ') ++value;
|
||||
RegulatorMode mode;
|
||||
if (strcmp(value, "auto") == 0) mode = RegulatorMode::Default;
|
||||
else if (strcmp(value, "dcdc") == 0) mode = RegulatorMode::Dcdc;
|
||||
else if (strcmp(value, "ldo") == 0) mode = RegulatorMode::Ldo;
|
||||
else { snprintf(reply, capacity, "Error: use set %s.regulator auto|dcdc|ldo", mcu ? "mcu" : "radio"); return true; }
|
||||
if (!regulatorModeSupported(configuredRegulatorModes(mcu), mode)) {
|
||||
snprintf(reply, capacity, "Error: regulator mode unsupported by this hardware");
|
||||
return true;
|
||||
}
|
||||
const uint8_t domain = mcu ? RECOVERY_MCU : RECOVERY_RADIO;
|
||||
const uint8_t fallback = recovery().fallbackMask() & ~domain;
|
||||
const auto cpu = mcu ? mode : requested[0], rf = mcu ? requested[1] : mode;
|
||||
if (!save(cpu, rf, fallback)) {
|
||||
snprintf(reply, capacity, "Error: regulator setting save failed");
|
||||
return true;
|
||||
}
|
||||
requested[mcu ? 0 : 1] = mode;
|
||||
recovery().clearDomain(domain);
|
||||
fallback_saved = true;
|
||||
snprintf(reply, capacity, "OK - %s regulator %s saved; reboot required", mcu ? "MCU" : "radio", regulatorModeName(mode));
|
||||
return true;
|
||||
}
|
||||
|
||||
void markRegulatorCleanReset() {
|
||||
if (initialized) recovery().markCleanReset();
|
||||
else {
|
||||
RecoveryPolicy policy(platformRecoveryRecord());
|
||||
policy.begin(ResetKind::Intentional, 0);
|
||||
policy.markCleanReset();
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace mesh::power
|
||||
|
||||
extern "C" void meshRegulatorCleanReset() {
|
||||
// Intentional resets can happen before the settings/filesystem is ready.
|
||||
mesh::power::markRegulatorCleanReset();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include "RegulatorMode.h"
|
||||
|
||||
namespace mesh {
|
||||
class MainBoard;
|
||||
class Radio;
|
||||
namespace power {
|
||||
// Called once after the primary filesystem and board ADC are ready, before
|
||||
// display/radio/wireless initialization. Choices never alter an active radio.
|
||||
bool beginRegulatorSettings(MainBoard& board, Radio& radio, FILESYSTEM* fs);
|
||||
void serviceRegulatorSettings();
|
||||
bool handleRegulatorCommand(const char* command, char* reply, size_t capacity);
|
||||
const char* regulatorModeName(RegulatorMode mode);
|
||||
uint8_t configuredRegulatorModes(bool mcu);
|
||||
RegulatorMode requestedRegulatorMode(bool mcu);
|
||||
RegulatorMode activeRegulatorMode(bool mcu);
|
||||
const char* activeRegulatorModeName(bool mcu);
|
||||
bool regulatorFallbackActive(bool mcu);
|
||||
bool regulatorRecoveryAvailable();
|
||||
}}
|
||||
|
||||
extern "C" void meshRegulatorCleanReset();
|
||||
@@ -35,6 +35,9 @@
|
||||
#if defined(NRF52_PLATFORM) && defined(USE_TINYUSB)
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM)
|
||||
extern "C" void meshRegulatorCleanReset() __attribute__((weak));
|
||||
#endif
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
#include "UsbAsciiBinarySwitch.h"
|
||||
#endif
|
||||
@@ -1907,6 +1910,7 @@ extern "C" void meshTinyUsbCdcLineStateChanged(uint8_t instance, bool dtr,
|
||||
cdc_line_coding_t coding;
|
||||
tud_cdc_get_line_coding(&coding);
|
||||
if (coding.bit_rate == 1200) {
|
||||
if (meshRegulatorCleanReset) meshRegulatorCleanReset();
|
||||
TinyUSB_Port_EnterDFU();
|
||||
}
|
||||
} else if (instance == 0) {
|
||||
|
||||
@@ -21,6 +21,7 @@ static const char* const WC_ALLOWED_SET_KEYS[] = {
|
||||
"name", "bluetooth.name", "bluetooth.mac", "bluetooth.stealth", "lost.reply",
|
||||
"lat", "lon", "radio", "tx", "af", "rxdelay", "txdelay",
|
||||
"cad", "radio.rxgain", "radio.fem.rxgain", "radio.fem.txgain", "radio.rxps", "powersaving",
|
||||
"mcu.regulator", "radio.regulator",
|
||||
"repeat",
|
||||
"display.mode", "display.timeout", "display.usb.mode", "display.usb.timeout",
|
||||
"display.inbox", "display.rotation",
|
||||
@@ -70,7 +71,9 @@ static inline bool wcSetKeyRequiresReboot(const char* key) {
|
||||
return key != NULL
|
||||
&& (wcIsEspNowChannelKey(key)
|
||||
|| strcmp(key, "bluetooth.mac") == 0
|
||||
|| strcmp(key, "bluetooth.stealth") == 0);
|
||||
|| strcmp(key, "bluetooth.stealth") == 0
|
||||
|| strcmp(key, "mcu.regulator") == 0
|
||||
|| strcmp(key, "radio.regulator") == 0);
|
||||
}
|
||||
|
||||
// The admin password maps to the top-level `password` command, not a setter, so
|
||||
|
||||
+2370
-2319
File diff suppressed because it is too large.
Load diff
@@ -13,6 +13,7 @@ static_assert(sizeof(WEBCONFIG_AP_PREFIX) <= 28,
|
||||
#include <ArduinoJson.h>
|
||||
#include <helpers/ui/DisplayPowerSettings.h>
|
||||
#include <helpers/ui/DisplayBuildFlags.h>
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <Preferences.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_arduino_version.h>
|
||||
@@ -2331,11 +2332,23 @@ void WebConfigServer::handleLogout(AsyncWebServerRequest* req) {
|
||||
req->send(res);
|
||||
}
|
||||
|
||||
static void webConfigRegulatorsSnapshot(JsonObject regulators) {
|
||||
regulators["recovery"] = mesh::power::regulatorRecoveryAvailable();
|
||||
for (uint8_t domain = 0; domain < 2; ++domain) {
|
||||
const bool mcu = domain == 0;
|
||||
JsonObject entry = regulators.createNestedObject(mcu ? "mcu" : "radio");
|
||||
entry["modes"] = mesh::power::configuredRegulatorModes(mcu);
|
||||
entry["requested"] = mesh::power::regulatorModeName(mesh::power::requestedRegulatorMode(mcu));
|
||||
entry["active"] = mesh::power::activeRegulatorModeName(mcu);
|
||||
entry["fallback"] = mesh::power::regulatorFallbackActive(mcu);
|
||||
}
|
||||
}
|
||||
|
||||
void WebConfigServer::handleConfigGet(AsyncWebServerRequest* req) {
|
||||
if (_mode == MODE_OFF) { req->send(503); return; }
|
||||
if (!checkAuth(req)) { req->send(401, "application/json", "{\"error\":\"auth\"}"); return; }
|
||||
|
||||
DynamicJsonDocument doc(6144);
|
||||
DynamicJsonDocument doc(6656);
|
||||
{
|
||||
WCLock lock(_mux);
|
||||
NodeSnapshot node = {};
|
||||
@@ -2400,6 +2413,7 @@ void WebConfigServer::handleConfigGet(AsyncWebServerRequest* req) {
|
||||
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
|
||||
wifi["espnow_channel"] = mesh::wifi::loadConfiguredEspNowChannel();
|
||||
#endif
|
||||
webConfigRegulatorsSnapshot(doc.createNestedObject("regulators"));
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
MQTTPrefs* obs = static_cast<MQTTPrefs*>(_mqtt_prefs);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "OtaSelf.h" // SelfFwInfo / ota_self_firmware (running-image base_hash gate)
|
||||
#endif
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
extern "C" void meshRegulatorCleanReset() __attribute__((weak));
|
||||
#include "OtaVerify.h"
|
||||
#include "OtaSelf.h"
|
||||
#include "OtaFlashLayout_nrf52.h"
|
||||
@@ -498,6 +499,7 @@ static bool ota_nrf52_clear_reset_reasons() {
|
||||
}
|
||||
|
||||
void ota_reboot_to_apply() { // public: set the apply magic + reset (does not return)
|
||||
if (meshRegulatorCleanReset) meshRegulatorCleanReset();
|
||||
uint8_t stage_handoff = GPREGRET2_OTA_STAGE_LEGACY;
|
||||
#if defined(OTA_RAK_AUTO_STORE) || defined(OTA_TOWER_AUTO_STORE)
|
||||
if (g_nrf52_apply_store) {
|
||||
@@ -546,6 +548,7 @@ void ota_reboot_to_apply() { // public: set the apply magic +
|
||||
}
|
||||
|
||||
void ota_reboot_to_bootloader_update() {
|
||||
if (meshRegulatorCleanReset) meshRegulatorCleanReset();
|
||||
uint8_t source = GPREGRET2_OTA_STAGE_QSPI;
|
||||
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
|
||||
source = GPREGRET2_OTA_STAGE_EXPANDED;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "RadioRegulatorState.h"
|
||||
#include "MeshCore.h"
|
||||
#include "RXPowerSaving.h"
|
||||
#include "LR1110RxRecovery.h"
|
||||
@@ -19,6 +20,9 @@
|
||||
#endif
|
||||
|
||||
class CustomLR1110 : public LR1110 {
|
||||
// Existing LR11xx recipes already require the fitted DC/DC network.
|
||||
mesh::power::RadioRegulatorState _regulator{mesh::power::REGULATOR_DCDC | mesh::power::REGULATOR_LDO,
|
||||
mesh::power::RegulatorMode::Dcdc};
|
||||
uint32_t _preambleMillis = 66;
|
||||
uint32_t _maxPayloadMillis = 3934;
|
||||
uint32_t _activityAt = 0;
|
||||
@@ -33,6 +37,11 @@ class CustomLR1110 : public LR1110 {
|
||||
public:
|
||||
CustomLR1110(Module *mod) : LR1110(mod) { }
|
||||
|
||||
uint8_t regulatorModes() const { return _regulator.modes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const { return _regulator.defaultMode(); }
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) { return _regulator.configureBoot(mode); }
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const { return _regulator.activeMode(); }
|
||||
|
||||
void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS],
|
||||
const Module::RfSwitchMode_t table[]) {
|
||||
_morse_rf_switch.capture(this->mod, pins, table);
|
||||
@@ -40,12 +49,14 @@ class CustomLR1110 : public LR1110 {
|
||||
}
|
||||
|
||||
int16_t beginMorseReceive(float frequency_mhz) {
|
||||
const auto regulator = _regulator.beginInitialization();
|
||||
_profileSwitch.invalidate();
|
||||
_profilePacketDirty = false;
|
||||
int16_t status = LR1110::beginGFSK(frequency_mhz,
|
||||
mesh::MorseRx::ReceiverBitRateKbps, 0.6f, 4.8f, 2, 16, tcxoVoltage);
|
||||
if (status == RADIOLIB_ERR_NONE) status = applyMeshCoreTcxoDelay();
|
||||
if (status == RADIOLIB_ERR_NONE) status = setRegulatorDCDC();
|
||||
if (status == RADIOLIB_ERR_NONE) status = regulator == mesh::power::RegulatorMode::Ldo
|
||||
? setRegulatorLDO() : setRegulatorDCDC();
|
||||
if (status == RADIOLIB_ERR_NONE) status = _morse_rf_switch.restore(
|
||||
[this](uint8_t enable, const uint8_t* modes) {
|
||||
return setDioAsRfSwitch(enable, modes[0], modes[1], modes[2],
|
||||
@@ -60,7 +71,7 @@ class CustomLR1110 : public LR1110 {
|
||||
}
|
||||
// Infinite continuous RX samples envelope power independently of the
|
||||
// FSK packet detector; no direct-data pin or TX/PA activation is used.
|
||||
return status;
|
||||
return _regulator.finishInitialization(status);
|
||||
}
|
||||
|
||||
void setProfileSwitchOptimization(bool enabled) {
|
||||
@@ -233,14 +244,16 @@ class CustomLR1110 : public LR1110 {
|
||||
int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7,
|
||||
uint8_t syncWord = RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, int8_t power = 10,
|
||||
uint16_t preambleLength = 8, float tcxoVoltage = 1.6) {
|
||||
const auto regulator = _regulator.beginInitialization();
|
||||
_profileSwitch.invalidate();
|
||||
_profilePacketDirty = false;
|
||||
int16_t state = LR1110::begin(freq, bw, sf, cr, syncWord, power, preambleLength,
|
||||
tcxoVoltage);
|
||||
if (state == RADIOLIB_ERR_NONE) state = applyMeshCoreTcxoDelay();
|
||||
// RadioLib begin() defaults to LDO; use the LR1110 DC/DC regulator.
|
||||
if (state == RADIOLIB_ERR_NONE) state = setRegulatorDCDC();
|
||||
return state;
|
||||
// Native begin() resets to LDO; restore the boot choice on every reset.
|
||||
if (state == RADIOLIB_ERR_NONE) state = regulator == mesh::power::RegulatorMode::Ldo
|
||||
? setRegulatorLDO() : setRegulatorDCDC();
|
||||
return _regulator.finishInitialization(state);
|
||||
}
|
||||
|
||||
int16_t applyMeshCoreTcxoDelay() {
|
||||
|
||||
@@ -13,6 +13,16 @@ class CustomLR1110Wrapper : public RadioLibWrapper {
|
||||
DeepInitCallback _deep_init;
|
||||
|
||||
public:
|
||||
uint8_t regulatorModes() const override { return static_cast<CustomLR1110*>(_radio)->regulatorModes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const override {
|
||||
return static_cast<CustomLR1110*>(_radio)->defaultRegulatorMode();
|
||||
}
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) override {
|
||||
return static_cast<CustomLR1110*>(_radio)->configureBootRegulator(mode);
|
||||
}
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const override {
|
||||
return static_cast<CustomLR1110*>(_radio)->activeRegulatorMode();
|
||||
}
|
||||
~CustomLR1110Wrapper() { unregisterInterruptAction(); }
|
||||
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board)
|
||||
: RadioLibWrapper(radio, board), _deep_init(NULL) { }
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "RadioRegulatorState.h"
|
||||
#include "MeshCore.h"
|
||||
#include "LR11x0MorseRfSwitch.h"
|
||||
#include "../MorseRx.h"
|
||||
|
||||
class CustomLR1121 : public LR1121 {
|
||||
// Existing LR11xx recipes already require the fitted DC/DC network.
|
||||
mesh::power::RadioRegulatorState _regulator{mesh::power::REGULATOR_DCDC | mesh::power::REGULATOR_LDO,
|
||||
mesh::power::RegulatorMode::Dcdc};
|
||||
uint32_t _preambleMillis = 66;
|
||||
uint32_t _maxPayloadMillis = 3934;
|
||||
uint32_t _activityAt = 0;
|
||||
@@ -18,6 +22,11 @@ class CustomLR1121 : public LR1121 {
|
||||
public:
|
||||
CustomLR1121(Module *mod) : LR1121(mod) { }
|
||||
|
||||
uint8_t regulatorModes() const { return _regulator.modes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const { return _regulator.defaultMode(); }
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) { return _regulator.configureBoot(mode); }
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const { return _regulator.activeMode(); }
|
||||
|
||||
int16_t setProfileProtocol(uint8_t syncWord) {
|
||||
int16_t status = LR1121::setSyncWord(syncWord);
|
||||
if (status == RADIOLIB_ERR_NONE) status = LR1121::setCRC(2);
|
||||
@@ -33,9 +42,11 @@ class CustomLR1121 : public LR1121 {
|
||||
}
|
||||
|
||||
int16_t beginMorseReceive(float frequency_mhz) {
|
||||
const auto regulator = _regulator.beginInitialization();
|
||||
int16_t status = LR1121::beginGFSK(frequency_mhz,
|
||||
mesh::MorseRx::ReceiverBitRateKbps, 0.6f, 4.8f, 2, 16, tcxoVoltage);
|
||||
if (status == RADIOLIB_ERR_NONE) status = setRegulatorDCDC();
|
||||
if (status == RADIOLIB_ERR_NONE) status = regulator == mesh::power::RegulatorMode::Ldo
|
||||
? setRegulatorLDO() : setRegulatorDCDC();
|
||||
if (status == RADIOLIB_ERR_NONE) status = _morse_rf_switch.restore(
|
||||
[this](uint8_t enable, const uint8_t* modes) {
|
||||
return setDioAsRfSwitch(enable, modes[0], modes[1], modes[2],
|
||||
@@ -46,17 +57,19 @@ class CustomLR1121 : public LR1121 {
|
||||
syncWordLength = preambleDetLength = 0;
|
||||
status = LR1121::setCRC(0);
|
||||
}
|
||||
return status;
|
||||
return _regulator.finishInitialization(status);
|
||||
}
|
||||
|
||||
int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7,
|
||||
uint8_t syncWord = RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, int8_t power = 10,
|
||||
uint16_t preambleLength = 8, float tcxoVoltage = 1.6) {
|
||||
const auto regulator = _regulator.beginInitialization();
|
||||
int16_t state = LR1121::begin(freq, bw, sf, cr, syncWord, power, preambleLength,
|
||||
tcxoVoltage);
|
||||
// RadioLib begin() defaults to LDO; use the LR1121 DC/DC regulator.
|
||||
if (state == RADIOLIB_ERR_NONE) state = setRegulatorDCDC();
|
||||
return state;
|
||||
// Native begin() resets to LDO; restore the boot choice on every reset.
|
||||
if (state == RADIOLIB_ERR_NONE) state = regulator == mesh::power::RegulatorMode::Ldo
|
||||
? setRegulatorLDO() : setRegulatorDCDC();
|
||||
return _regulator.finishInitialization(state);
|
||||
}
|
||||
|
||||
size_t getPacketLength(bool update) override {
|
||||
|
||||
@@ -8,6 +8,16 @@ class CustomLR1121Wrapper : public RadioLibWrapper {
|
||||
using DeepInitCallback = bool (*)();
|
||||
DeepInitCallback _deep_init = nullptr;
|
||||
public:
|
||||
uint8_t regulatorModes() const override { return static_cast<CustomLR1121*>(_radio)->regulatorModes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const override {
|
||||
return static_cast<CustomLR1121*>(_radio)->defaultRegulatorMode();
|
||||
}
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) override {
|
||||
return static_cast<CustomLR1121*>(_radio)->configureBootRegulator(mode);
|
||||
}
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const override {
|
||||
return static_cast<CustomLR1121*>(_radio)->activeRegulatorMode();
|
||||
}
|
||||
~CustomLR1121Wrapper() { unregisterInterruptAction(); }
|
||||
CustomLR1121Wrapper(CustomLR1121& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool supportsMorseTx() const override { return true; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "RadioRegulatorState.h"
|
||||
#include "MeshCore.h"
|
||||
#include "LR2021Band.h"
|
||||
#include "../MorseRx.h"
|
||||
@@ -21,6 +22,13 @@
|
||||
#endif
|
||||
|
||||
class CustomLR2021 : public LR2021 {
|
||||
#if LR2021_USE_DCDC
|
||||
mesh::power::RadioRegulatorState _regulator{mesh::power::REGULATOR_DCDC | mesh::power::REGULATOR_LDO,
|
||||
mesh::power::RegulatorMode::Dcdc};
|
||||
#else
|
||||
// An LDO-only layout must not enable SIMO without its external inductor.
|
||||
mesh::power::RadioRegulatorState _regulator{mesh::power::REGULATOR_LDO, mesh::power::RegulatorMode::Ldo};
|
||||
#endif
|
||||
uint32_t _preambleMillis = 66;
|
||||
uint32_t _maxPayloadMillis = 3934;
|
||||
uint32_t _activityAt = 0;
|
||||
@@ -130,6 +138,11 @@ class CustomLR2021 : public LR2021 {
|
||||
public:
|
||||
CustomLR2021(Module *mod) : LR2021(mod) { irqDioNum = LR2021_IRQ_DIO; }
|
||||
|
||||
uint8_t regulatorModes() const { return _regulator.modes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const { return _regulator.defaultMode(); }
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) { return _regulator.configureBoot(mode); }
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const { return _regulator.activeMode(); }
|
||||
|
||||
#if LR2021_TX_PREARM_MS > 0
|
||||
using LR2021::standby;
|
||||
int16_t standby() override {
|
||||
@@ -164,13 +177,16 @@ class CustomLR2021 : public LR2021 {
|
||||
}
|
||||
|
||||
int16_t beginMorseReceive(float frequency_mhz) {
|
||||
const auto regulator = _regulator.beginInitialization();
|
||||
const uint8_t gain = mesh::lr2021::isHighBand(frequency_mhz) ? gainModeHf : gainModeLf;
|
||||
int16_t status = LR2021::beginGFSK(frequency_mhz,
|
||||
mesh::MorseRx::ReceiverBitRateKbps, 0.6f, 4.8f, 2, 16, tcxoVoltage);
|
||||
// Native modem initialization resets the regulator to LDO. Verified
|
||||
// SIMO boards must restore DC/DC before admitting RX after any reset.
|
||||
// Native initialization resets to LDO. SIMO is allowed only on verified
|
||||
// layouts; an explicit LDO choice on those boards is checked as well.
|
||||
if (status == RADIOLIB_ERR_NONE && regulator == mesh::power::RegulatorMode::Dcdc)
|
||||
status = setRegulatorDCDC();
|
||||
#if LR2021_USE_DCDC
|
||||
if (status == RADIOLIB_ERR_NONE) status = setRegulatorDCDC();
|
||||
else if (status == RADIOLIB_ERR_NONE) status = setRegulatorLDO();
|
||||
#endif
|
||||
if (status == RADIOLIB_ERR_NONE) status = restoreRfSwitchTable();
|
||||
if (status == RADIOLIB_ERR_NONE) status = LR2021::setRxBoostedGainMode(gain);
|
||||
@@ -183,10 +199,11 @@ class CustomLR2021 : public LR2021 {
|
||||
status = LR2021::setCRC(0);
|
||||
}
|
||||
// launchMode() retains this chip's band-aware RX RF switch selection.
|
||||
return status;
|
||||
return _regulator.finishInitialization(status);
|
||||
}
|
||||
|
||||
bool std_init(SPIClass* spi = NULL) {
|
||||
const auto regulator = _regulator.beginInitialization();
|
||||
#ifdef LR2021_TCXO_VOLTAGE
|
||||
const float tcxo = LR2021_TCXO_VOLTAGE;
|
||||
#else
|
||||
@@ -220,15 +237,16 @@ class CustomLR2021 : public LR2021 {
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
mesh::usbLoggingPort().print("ERROR: radio init failed: ");
|
||||
mesh::usbLoggingPort().println(status);
|
||||
_regulator.finishInitialization(status);
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
// Also reached when restoring LoRa after Morse RX or hard recovery.
|
||||
if (regulator == mesh::power::RegulatorMode::Dcdc) status = setRegulatorDCDC();
|
||||
#if LR2021_USE_DCDC
|
||||
// Also reached by radioDeepInit() when restoring LoRa after Morse RX.
|
||||
// Check this before later setters can overwrite a regulator error.
|
||||
status = setRegulatorDCDC();
|
||||
if (status != RADIOLIB_ERR_NONE) return false;
|
||||
else status = setRegulatorLDO();
|
||||
#endif
|
||||
if (_regulator.finishInitialization(status) != RADIOLIB_ERR_NONE) return false;
|
||||
|
||||
status = setCRC(2);
|
||||
if (status != RADIOLIB_ERR_NONE) return false;
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
|
||||
class CustomLR2021Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
uint8_t regulatorModes() const override { return static_cast<CustomLR2021*>(_radio)->regulatorModes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const override {
|
||||
return static_cast<CustomLR2021*>(_radio)->defaultRegulatorMode();
|
||||
}
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) override {
|
||||
return static_cast<CustomLR2021*>(_radio)->configureBootRegulator(mode);
|
||||
}
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const override {
|
||||
return static_cast<CustomLR2021*>(_radio)->activeRegulatorMode();
|
||||
}
|
||||
~CustomLR2021Wrapper() { unregisterInterruptAction(); }
|
||||
CustomLR2021Wrapper(CustomLR2021& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool supportsMorseTx() const override { return true; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "RadioRegulatorState.h"
|
||||
#include <math.h>
|
||||
#include "MeshCore.h"
|
||||
#include "RXPowerSaving.h"
|
||||
@@ -39,6 +40,12 @@
|
||||
#endif
|
||||
|
||||
class CustomSX1262 : public SX1262 {
|
||||
#if defined(SX126X_USE_REGULATOR_LDO) && SX126X_USE_REGULATOR_LDO
|
||||
mesh::power::RadioRegulatorState _regulator{mesh::power::REGULATOR_LDO, mesh::power::RegulatorMode::Ldo};
|
||||
#else
|
||||
mesh::power::RadioRegulatorState _regulator{mesh::power::REGULATOR_DCDC | mesh::power::REGULATOR_LDO,
|
||||
mesh::power::RegulatorMode::Dcdc};
|
||||
#endif
|
||||
uint32_t _preambleMillis = 66;
|
||||
uint32_t _maxPayloadMillis = 3934;
|
||||
uint32_t _activityAt = 0;
|
||||
@@ -52,6 +59,11 @@ class CustomSX1262 : public SX1262 {
|
||||
|
||||
public:
|
||||
CustomSX1262(Module *mod) : SX1262(mod) { }
|
||||
|
||||
uint8_t regulatorModes() const { return _regulator.modes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const { return _regulator.defaultMode(); }
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) { return _regulator.configureBoot(mode); }
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const { return _regulator.activeMode(); }
|
||||
void invalidateMorseModemCache() {
|
||||
_profileSwitch.invalidate();
|
||||
_coldStandby = true;
|
||||
@@ -296,6 +308,10 @@ class CustomSX1262 : public SX1262 {
|
||||
uint8_t syncWord = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, int8_t power = 10,
|
||||
uint16_t preambleLength = 8, float tcxoVoltage = 1.6,
|
||||
bool useRegulatorLDO = false) {
|
||||
useRegulatorLDO = _regulator.beginInitialization(useRegulatorLDO
|
||||
? mesh::power::RegulatorMode::Ldo : mesh::power::RegulatorMode::Dcdc)
|
||||
== mesh::power::RegulatorMode::Ldo;
|
||||
MESH_DEBUG_PRINTLN("SX1262 regulator requested: %s", useRegulatorLDO ? "LDO" : "DC-DC");
|
||||
_profileSwitch.invalidate();
|
||||
// A held dual-profile oscillator must not be selected during reset or
|
||||
// setup, before DIO3/TCXO configuration has been restored. Restore the
|
||||
@@ -309,7 +325,20 @@ class CustomSX1262 : public SX1262 {
|
||||
tcxoVoltage, useRegulatorLDO);
|
||||
if (state == RADIOLIB_ERR_NONE) state = applyMeshCoreTcxoDelay();
|
||||
standbyXOSC = savedStandbyXosc;
|
||||
return state;
|
||||
return _regulator.finishInitialization(state);
|
||||
}
|
||||
|
||||
// The GFSK reset used by Morse must retain the same boot choice and must
|
||||
// invalidate reported active state when native initialization fails.
|
||||
int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0,
|
||||
float rxBw = 156.2, int8_t power = 10, uint16_t preambleLength = 16,
|
||||
float tcxoVoltage = 1.6, bool useRegulatorLDO = false) override {
|
||||
useRegulatorLDO = _regulator.beginInitialization(useRegulatorLDO
|
||||
? mesh::power::RegulatorMode::Ldo : mesh::power::RegulatorMode::Dcdc)
|
||||
== mesh::power::RegulatorMode::Ldo;
|
||||
const int16_t status = SX1262::beginFSK(freq, br, freqDev, rxBw, power,
|
||||
preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||
return _regulator.finishInitialization(status);
|
||||
}
|
||||
|
||||
// Read tcxoVoltage back from the chip object rather than from the argument:
|
||||
@@ -379,8 +408,6 @@ class CustomSX1262 : public SX1262 {
|
||||
constexpr bool useRegulatorLDO = false;
|
||||
#endif
|
||||
|
||||
MESH_DEBUG_PRINTLN("SX1262 regulator requested: %s", useRegulatorLDO ? "LDO" : "DC-DC");
|
||||
|
||||
#if defined(STATION_G3_ESP32)
|
||||
// Reinitialization/watchdog recovery must keep the boot-selected slot;
|
||||
// the build's historical slot macros are not the live SPI routing.
|
||||
|
||||
@@ -37,6 +37,16 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
uint8_t regulatorModes() const override { return static_cast<CustomSX1262*>(_radio)->regulatorModes(); }
|
||||
mesh::power::RegulatorMode defaultRegulatorMode() const override {
|
||||
return static_cast<CustomSX1262*>(_radio)->defaultRegulatorMode();
|
||||
}
|
||||
bool configureBootRegulator(mesh::power::RegulatorMode mode) override {
|
||||
return static_cast<CustomSX1262*>(_radio)->configureBootRegulator(mode);
|
||||
}
|
||||
mesh::power::RegulatorMode activeRegulatorMode() const override {
|
||||
return static_cast<CustomSX1262*>(_radio)->activeRegulatorMode();
|
||||
}
|
||||
~CustomSX1262Wrapper() { unregisterInterruptAction(); }
|
||||
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool supportsCarrierWave() const override { return true; }
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/RegulatorMode.h>
|
||||
|
||||
namespace mesh { namespace power {
|
||||
|
||||
// A boot selection never writes the radio. Once initialization starts, freeze
|
||||
// it so recovery and temporary modem changes cannot apply a pending CLI edit.
|
||||
class RadioRegulatorState {
|
||||
const uint8_t _modes;
|
||||
const RegulatorMode _default;
|
||||
RegulatorMode _requested = RegulatorMode::Default;
|
||||
RegulatorMode _effective = RegulatorMode::Default;
|
||||
RegulatorMode _active = RegulatorMode::Default;
|
||||
bool _started = false;
|
||||
|
||||
public:
|
||||
RadioRegulatorState(uint8_t modes, RegulatorMode default_mode)
|
||||
: _modes(modes), _default(default_mode) { }
|
||||
|
||||
uint8_t modes() const { return _modes; }
|
||||
RegulatorMode defaultMode() const { return _default; }
|
||||
RegulatorMode activeMode() const { return _active; }
|
||||
|
||||
bool configureBoot(RegulatorMode mode) {
|
||||
if (_started || !regulatorModeSupported(_modes, mode)) return false;
|
||||
_requested = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
RegulatorMode beginInitialization(RegulatorMode legacy_default = RegulatorMode::Default) {
|
||||
_started = true;
|
||||
_active = RegulatorMode::Default; // no successful application in this init yet
|
||||
const RegulatorMode fallback = regulatorModeSupported(_modes, legacy_default)
|
||||
&& legacy_default != RegulatorMode::Default ? legacy_default : _default;
|
||||
_effective = _requested == RegulatorMode::Default ? fallback : _requested;
|
||||
return _effective;
|
||||
}
|
||||
|
||||
int16_t finishInitialization(int16_t status) {
|
||||
_active = status == 0 ? _effective : RegulatorMode::Default;
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace mesh::power
|
||||
Reference in new issue
Block a user