Merge pull request #1413 from entr0p1/powermgt-nrf52840-v2

nRF52840 Power Management v2 Phase 1 - LPCOMP Wake and startup lockout
This commit is contained in:
ripplebiz 2026-01-24 21:51:06 +11:00 committed by GitHub
commit e744adfa39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 667 additions and 24 deletions

View file

@ -3,6 +3,35 @@
#include <Arduino.h>
#include <Wire.h>
#ifdef NRF52_POWER_MANAGEMENT
// 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
};
void T114Board::initiateShutdown(uint8_t reason) {
#if ENV_INCLUDE_GPS == 1
pinMode(GPS_EN, OUTPUT);
digitalWrite(GPS_EN, LOW);
#endif
digitalWrite(SX126X_POWER_EN, LOW);
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
reason == SHUTDOWN_REASON_BOOT_PROTECT);
pinMode(PIN_BAT_CTL, OUTPUT);
digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW);
if (enable_lpcomp) {
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
}
enterSystemOff(reason);
}
#endif // NRF52_POWER_MANAGEMENT
void T114Board::begin() {
NRF52Board::begin();
NRF_POWER->DCDCEN = 1;
@ -21,6 +50,11 @@ void T114Board::begin() {
#endif
pinMode(SX126X_POWER_EN, OUTPUT);
#ifdef NRF52_POWER_MANAGEMENT
// Boot voltage protection check (may not return if voltage too low)
// We need to call this after we configure SX126X_POWER_EN as output but before we pull high
checkBootVoltage(&power_config);
#endif
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}

View file

@ -10,6 +10,11 @@
#define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range
class T114Board : public NRF52BoardOTA {
protected:
#ifdef NRF52_POWER_MANAGEMENT
void initiateShutdown(uint8_t reason) override;
#endif
public:
T114Board() : NRF52BoardOTA("T114_OTA") {}
void begin();
@ -42,13 +47,13 @@ public:
}
void powerOff() override {
#ifdef LED_PIN
#ifdef LED_PIN
digitalWrite(LED_PIN, HIGH);
#endif
#if ENV_INCLUDE_GPS == 1
#endif
#if ENV_INCLUDE_GPS == 1
pinMode(GPS_EN, OUTPUT);
digitalWrite(GPS_EN, LOW);
#endif
#endif
sd_power_system_off();
}
};

View file

@ -11,6 +11,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/heltec_t114
-I src/helpers/ui
-D HELTEC_T114
-D NRF52_POWER_MANAGEMENT
-D P_LORA_DIO_1=20
-D P_LORA_NSS=24
-D P_LORA_RESET=25

View file

@ -30,6 +30,14 @@
#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)
// 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)
////////////////////////////////////////////////////////////////////////////////
// Number of pins

View file

@ -3,6 +3,28 @@
#include "RAK4631Board.h"
#ifdef NRF52_POWER_MANAGEMENT
// 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
};
void RAK4631Board::initiateShutdown(uint8_t reason) {
// Disable LoRa module power before shutdown
digitalWrite(SX126X_POWER_EN, LOW);
if (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
reason == SHUTDOWN_REASON_BOOT_PROTECT) {
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
}
enterSystemOff(reason);
}
#endif // NRF52_POWER_MANAGEMENT
void RAK4631Board::begin() {
NRF52BoardDCDC::begin();
pinMode(PIN_VBAT_READ, INPUT);
@ -21,6 +43,11 @@ void RAK4631Board::begin() {
Wire.begin();
pinMode(SX126X_POWER_EN, OUTPUT);
#ifdef NRF52_POWER_MANAGEMENT
// Boot voltage protection check (may not return if voltage too low)
// We need to call this after we configure SX126X_POWER_EN as output but before we pull high
checkBootVoltage(&power_config);
#endif
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}

View file

@ -30,6 +30,11 @@
#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000)
class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA {
protected:
#ifdef NRF52_POWER_MANAGEMENT
void initiateShutdown(uint8_t reason) override;
#endif
public:
RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {}
void begin();

View file

@ -7,6 +7,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/rak4631
-D RAK_4631
-D RAK_BOARD
-D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_GPS_TX=PIN_SERIAL1_RX

View file

@ -104,6 +104,14 @@ 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)
// 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)
// Other pins
#define PIN_AREF (2)
#define PIN_NFC1 (9)

View file

@ -5,12 +5,40 @@
#include "XiaoNrf52Board.h"
#ifdef NRF52_POWER_MANAGEMENT
// 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
};
void XiaoNrf52Board::initiateShutdown(uint8_t reason) {
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
reason == SHUTDOWN_REASON_BOOT_PROTECT);
pinMode(VBAT_ENABLE, OUTPUT);
digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH);
if (enable_lpcomp) {
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
}
enterSystemOff(reason);
}
#endif // NRF52_POWER_MANAGEMENT
void XiaoNrf52Board::begin() {
NRF52BoardDCDC::begin();
// Configure battery voltage ADC
pinMode(PIN_VBAT, INPUT);
pinMode(VBAT_ENABLE, OUTPUT);
digitalWrite(VBAT_ENABLE, HIGH);
digitalWrite(VBAT_ENABLE, LOW); // Enable VBAT divider for reading
analogReadResolution(12);
analogReference(AR_INTERNAL_3_0);
delay(50); // Allow ADC to settle
#ifdef PIN_USER_BTN
pinMode(PIN_USER_BTN, INPUT_PULLUP);
@ -27,9 +55,20 @@ void XiaoNrf52Board::begin() {
digitalWrite(P_LORA_TX_LED, HIGH);
#endif
// pinMode(SX126X_POWER_EN, OUTPUT);
// digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
#ifdef NRF52_POWER_MANAGEMENT
// Boot voltage protection check (may not return if voltage too low)
checkBootVoltage(&power_config);
#endif
delay(10); // Give sx1262 some time to power up
}
uint16_t XiaoNrf52Board::getBattMilliVolts() {
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
// VBAT_ENABLE must be LOW to read battery voltage
digitalWrite(VBAT_ENABLE, LOW);
int adcvalue = analogRead(PIN_VBAT);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
}
#endif

View file

@ -6,7 +6,12 @@
#ifdef XIAO_NRF52
class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA {
class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA {
protected:
#if NRF52_POWER_MANAGEMENT
void initiateShutdown(uint8_t reason) override;
#endif
public:
XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {}
void begin();
@ -20,21 +25,7 @@ public:
}
#endif
uint16_t getBattMilliVolts() override {
// Please read befor going further ;)
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
// We can't drive VBAT_ENABLE to HIGH as long
// as we don't know wether we are charging or not ...
// this is a 3mA loss (4/1500)
digitalWrite(VBAT_ENABLE, LOW);
int adcvalue = 0;
analogReadResolution(12);
analogReference(AR_INTERNAL_3_0);
delay(10);
adcvalue = analogRead(PIN_VBAT);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
}
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override {
return "Seeed Xiao-nrf52";

View file

@ -9,6 +9,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/xiao_nrf52
-UENV_INCLUDE_GPS
-D NRF52_PLATFORM
-D NRF52_POWER_MANAGEMENT
-D XIAO_NRF52
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper

View file

@ -75,6 +75,21 @@ 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
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
#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)
static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;