Compare commits

...

18 Commits

Author SHA1 Message Date
Matthias Wientapper
e22e63c9f8 Disable flood adverts 2026-02-28 15:55:34 +01:00
Matthias Wientapper
81406cdb25 Set RAK boot lock voltage to 0V 2026-02-28 15:55:34 +01:00
Matthias Wientapper
baa17d204b Integration of upstrem PR #1810 2026-02-28 15:55:34 +01:00
Matthias Wientapper
cf5c268cfd Integration of upstrem PR #1743 2026-02-28 15:55:34 +01:00
Matthias Wientapper
1ae9b18e10 Integration of upstrem PR #1338 2026-02-28 15:55:34 +01:00
Matthias Wientapper
291eb6d305 Integration of upstrem PR #1297 2026-02-28 15:55:34 +01:00
Wessel Nieboer
87717610f5 fix comment, we know the band now after checking LR1110 user manual 2026-02-26 23:16:02 +01:00
Wessel Nieboer
dcfc83fb3d Calibrate configured frequency for AGC reset 2026-02-26 23:16:02 +01:00
Wessel Nieboer
b90da8e1c0 Also implement LR11x10 AGC reset
Similar to SX126x but simpler.
2026-02-26 23:16:01 +01:00
Wessel Nieboer
cb2ebbf2f8 make it more dry 2026-02-26 23:16:01 +01:00
Wessel Nieboer
34c1f13d55 reset noise_floor sampling after agc reset 2026-02-26 23:16:01 +01:00
Wessel Nieboer
bb5bdcf9e5 when doing AGC reset, call Calibrate(0x7F)
1. warm sleep
2. wake to stdby
3. Calibrate(0x7F) to reset all internal blocks
4. re-apply DIO2 RF / boosted gain & register patch to make sure
everything is as it was
2026-02-26 23:16:00 +01:00
Wessel Nieboer
de9100785e fix agc reset 2026-02-26 23:16:00 +01:00
Stefan Berthold
f864c5f547 allow direct message paths when denyf * is set 2026-02-23 23:33:41 +01:00
Matthias Wientapper
b039af0b52 Rak bootlock voltage set to 0x 2026-02-21 19:21:38 +01:00
ViezeVingertjes
519b97a90a Updated the Dispatcher logic to replace hardcoded values with defined constants for minimum TX budget reserve and airtime division. 2026-02-07 19:07:33 +01:00
ViezeVingertjes
30d6588792 Update logic in Dispatcher to ensure refill is only applied when greater than zero. 2026-02-07 18:26:39 +01:00
ViezeVingertjes
eb4fa032ff Implement token bucket duty cycle enforcement 2026-01-04 21:33:46 +01:00
18 changed files with 165 additions and 19 deletions

View File

@@ -807,7 +807,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
strcpy(_prefs.node_name, "NONAME"); strcpy(_prefs.node_name, "NONAME");
_prefs.freq = LORA_FREQ; _prefs.freq = LORA_FREQ;
_prefs.sf = LORA_SF; _prefs.sf = LORA_SF;

View File

@@ -514,7 +514,10 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) { if ((pkt->getPayloadType() == PAYLOAD_TYPE_GRP_TXT ||
pkt->getPayloadType() == PAYLOAD_TYPE_GRP_DATA ||
pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) &&
region_map.getWildcard().flags & REGION_DENY_FLOOD) {
recv_pkt_region = NULL; recv_pkt_region = NULL;
} else { } else {
recv_pkt_region = &region_map.getWildcard(); recv_pkt_region = &region_map.getWildcard();
@@ -832,7 +835,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
_prefs.tx_delay_factor = 0.5f; // was 0.25f _prefs.tx_delay_factor = 0.5f; // was 0.25f
_prefs.direct_tx_delay_factor = 0.3f; // was 0.2 _prefs.direct_tx_delay_factor = 0.3f; // was 0.2

View File

@@ -608,7 +608,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0 _prefs.rx_delay_base = 0.0f; // off by default, was 10.0
_prefs.tx_delay_factor = 0.5f; // was 0.25f; _prefs.tx_delay_factor = 0.5f; // was 0.25f;
_prefs.direct_tx_delay_factor = 0.2f; // was zero _prefs.direct_tx_delay_factor = 0.2f; // was zero

View File

@@ -281,7 +281,7 @@ public:
{ {
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 2.0; // one third _prefs.airtime_factor = 1.0;
strcpy(_prefs.node_name, "NONAME"); strcpy(_prefs.node_name, "NONAME");
_prefs.freq = LORA_FREQ; _prefs.freq = LORA_FREQ;
_prefs.tx_power_dbm = LORA_TX_POWER; _prefs.tx_power_dbm = LORA_TX_POWER;

View File

@@ -706,7 +706,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half _prefs.airtime_factor = 1.0;
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
_prefs.tx_delay_factor = 0.5f; // was 0.25f _prefs.tx_delay_factor = 0.5f; // was 0.25f
_prefs.direct_tx_delay_factor = 0.2f; // was zero _prefs.direct_tx_delay_factor = 0.2f; // was zero

View File

@@ -8,7 +8,9 @@
namespace mesh { namespace mesh {
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds #define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
#define MIN_TX_BUDGET_RESERVE_MS 100 // min budget (ms) required before allowing next TX
#define MIN_TX_BUDGET_AIRTIME_DIV 2 // require at least 1/N of estimated airtime as budget before TX
#ifndef NOISE_FLOOR_CALIB_INTERVAL #ifndef NOISE_FLOOR_CALIB_INTERVAL
#define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds #define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds
@@ -20,12 +22,34 @@ void Dispatcher::begin() {
_err_flags = 0; _err_flags = 0;
radio_nonrx_start = _ms->getMillis(); radio_nonrx_start = _ms->getMillis();
duty_cycle_window_ms = getDutyCycleWindowMs();
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
tx_budget_ms = (unsigned long)(duty_cycle_window_ms * duty_cycle);
last_budget_update = _ms->getMillis();
_radio->begin(); _radio->begin();
prev_isrecv_mode = _radio->isInRecvMode(); prev_isrecv_mode = _radio->isInRecvMode();
} }
float Dispatcher::getAirtimeBudgetFactor() const { float Dispatcher::getAirtimeBudgetFactor() const {
return 2.0; // default, 33.3% (1/3rd) return 1.0;
}
void Dispatcher::updateTxBudget() {
unsigned long now = _ms->getMillis();
unsigned long elapsed = now - last_budget_update;
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
unsigned long max_budget = (unsigned long)(getDutyCycleWindowMs() * duty_cycle);
unsigned long refill = (unsigned long)(elapsed * duty_cycle);
if (refill > 0) {
tx_budget_ms += refill;
if (tx_budget_ms > max_budget) {
tx_budget_ms = max_budget;
}
last_budget_update = now;
}
} }
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const { int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
@@ -61,11 +85,24 @@ void Dispatcher::loop() {
if (outbound) { // waiting for outbound send to be completed if (outbound) { // waiting for outbound send to be completed
if (_radio->isSendComplete()) { if (_radio->isSendComplete()) {
long t = _ms->getMillis() - outbound_start; long t = _ms->getMillis() - outbound_start;
total_air_time += t; // keep track of how much air time we are using total_air_time += t;
//Serial.print(" airtime="); Serial.println(t); //Serial.print(" airtime="); Serial.println(t);
// will need radio silence up to next_tx_time updateTxBudget();
next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
if (t > tx_budget_ms) {
tx_budget_ms = 0;
} else {
tx_budget_ms -= t;
}
if (tx_budget_ms < MIN_TX_BUDGET_RESERVE_MS) {
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
unsigned long needed = MIN_TX_BUDGET_RESERVE_MS - tx_budget_ms;
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
} else {
next_tx_time = _ms->getMillis();
}
_radio->onSendFinished(); _radio->onSendFinished();
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
@@ -235,9 +272,20 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
} }
void Dispatcher::checkSend() { void Dispatcher::checkSend() {
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return;
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
if (_radio->isReceiving()) { // LBT - check if radio is currently mid-receive, or if channel activity updateTxBudget();
uint32_t est_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
if (tx_budget_ms < est_airtime / MIN_TX_BUDGET_AIRTIME_DIV) {
float duty_cycle = 1.0f / (1.0f + getAirtimeBudgetFactor());
unsigned long needed = est_airtime / MIN_TX_BUDGET_AIRTIME_DIV - tx_budget_ms;
next_tx_time = futureMillis((unsigned long)(needed / duty_cycle));
return;
}
if (!millisHasNowPassed(next_tx_time)) return;
if (_radio->isReceiving()) {
if (cad_busy_start == 0) { if (cad_busy_start == 0) {
cad_busy_start = _ms->getMillis(); // record when CAD busy state started cad_busy_start = _ms->getMillis(); // record when CAD busy state started
} }

View File

@@ -122,8 +122,12 @@ class Dispatcher {
bool prev_isrecv_mode; bool prev_isrecv_mode;
uint32_t n_sent_flood, n_sent_direct; uint32_t n_sent_flood, n_sent_direct;
uint32_t n_recv_flood, n_recv_direct; uint32_t n_recv_flood, n_recv_direct;
unsigned long tx_budget_ms;
unsigned long last_budget_update;
unsigned long duty_cycle_window_ms;
void processRecvPacket(Packet* pkt); void processRecvPacket(Packet* pkt);
void updateTxBudget();
protected: protected:
PacketManager* _mgr; PacketManager* _mgr;
@@ -142,6 +146,9 @@ protected:
_err_flags = 0; _err_flags = 0;
radio_nonrx_start = 0; radio_nonrx_start = 0;
prev_isrecv_mode = true; prev_isrecv_mode = true;
tx_budget_ms = 0;
last_budget_update = 0;
duty_cycle_window_ms = 3600000;
} }
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0; virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
@@ -159,6 +166,7 @@ protected:
virtual uint32_t getCADFailMaxDuration() const; virtual uint32_t getCADFailMaxDuration() const;
virtual int getInterferenceThreshold() const { return 0; } // disabled by default virtual int getInterferenceThreshold() const { return 0; } // disabled by default
virtual int getAGCResetInterval() const { return 0; } // disabled by default virtual int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
public: public:
void begin(); void begin();
@@ -168,8 +176,9 @@ public:
void releasePacket(Packet* packet); void releasePacket(Packet* packet);
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0); void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds unsigned long getTotalAirTime() const { return total_air_time; }
unsigned long getReceiveAirTime() const {return rx_air_time; } unsigned long getReceiveAirTime() const {return rx_air_time; }
unsigned long getRemainingTxBudget() const { return tx_budget_ms; }
uint32_t getNumSentFlood() const { return n_sent_flood; } uint32_t getNumSentFlood() const { return n_sent_flood; }
uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumSentDirect() const { return n_sent_direct; }
uint32_t getNumRecvFlood() const { return n_recv_flood; } uint32_t getNumRecvFlood() const { return n_recv_flood; }

View File

@@ -2,6 +2,7 @@
#include "CustomLLCC68.h" #include "CustomLLCC68.h"
#include "RadioLibWrappers.h" #include "RadioLibWrappers.h"
#include "SX126xReset.h"
class CustomLLCC68Wrapper : public RadioLibWrapper { class CustomLLCC68Wrapper : public RadioLibWrapper {
public: public:
@@ -19,4 +20,6 @@ public:
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
}; };

View File

@@ -20,6 +20,8 @@ class CustomLR1110 : public LR1110 {
return len; return len;
} }
float getFreqMHz() const { return freqMHz; }
bool isReceiving() { bool isReceiving() {
uint16_t irq = getIrqStatus(); uint16_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));

View File

@@ -2,11 +2,13 @@
#include "CustomLR1110.h" #include "CustomLR1110.h"
#include "RadioLibWrappers.h" #include "RadioLibWrappers.h"
#include "LR11x0Reset.h"
class CustomLR1110Wrapper : public RadioLibWrapper { class CustomLR1110Wrapper : public RadioLibWrapper {
public: public:
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
bool isReceivingPacket() override { void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
bool isReceivingPacket() override {
return ((CustomLR1110 *)_radio)->isReceiving(); return ((CustomLR1110 *)_radio)->isReceiving();
} }
float getCurrentRSSI() override { float getCurrentRSSI() override {

View File

@@ -2,6 +2,7 @@
#include "CustomSTM32WLx.h" #include "CustomSTM32WLx.h"
#include "RadioLibWrappers.h" #include "RadioLibWrappers.h"
#include "SX126xReset.h"
#include <math.h> #include <math.h>
class CustomSTM32WLxWrapper : public RadioLibWrapper { class CustomSTM32WLxWrapper : public RadioLibWrapper {
@@ -20,4 +21,6 @@ public:
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
}; };

View File

@@ -2,6 +2,7 @@
#include "CustomSX1262.h" #include "CustomSX1262.h"
#include "RadioLibWrappers.h" #include "RadioLibWrappers.h"
#include "SX126xReset.h"
class CustomSX1262Wrapper : public RadioLibWrapper { class CustomSX1262Wrapper : public RadioLibWrapper {
public: public:
@@ -22,4 +23,6 @@ public:
virtual void powerOff() override { virtual void powerOff() override {
((CustomSX1262 *)_radio)->sleep(false); ((CustomSX1262 *)_radio)->sleep(false);
} }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
}; };

View File

@@ -2,6 +2,7 @@
#include "CustomSX1268.h" #include "CustomSX1268.h"
#include "RadioLibWrappers.h" #include "RadioLibWrappers.h"
#include "SX126xReset.h"
class CustomSX1268Wrapper : public RadioLibWrapper { class CustomSX1268Wrapper : public RadioLibWrapper {
public: public:
@@ -19,4 +20,6 @@ public:
int sf = ((CustomSX1268 *)_radio)->spreadingFactor; int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
}; };

View File

@@ -0,0 +1,21 @@
#pragma once
#include <RadioLib.h>
// Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121).
// Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks,
// then re-applies RX settings that calibration may reset.
inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) {
radio->sleep(true, 0);
radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true);
radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL);
// calibrate(0x3F) defaults image calibration to 902-928MHz band.
// Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default).
radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f);
#ifdef RX_BOOSTED_GAIN
radio->setRxBoostedGainMode(RX_BOOSTED_GAIN);
#endif
}

View File

@@ -53,13 +53,24 @@ void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) {
} }
} }
void RadioLibWrapper::doResetAGC() {
_radio->sleep(); // warm sleep to reset analog frontend
}
void RadioLibWrapper::resetAGC() { void RadioLibWrapper::resetAGC() {
// make sure we're not mid-receive of packet! // make sure we're not mid-receive of packet!
if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return; if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return;
// NOTE: according to higher powers, just issuing RadioLib's startReceive() will reset the AGC. doResetAGC();
// revisit this if a better impl is discovered.
state = STATE_IDLE; // trigger a startReceive() state = STATE_IDLE; // trigger a startReceive()
// Reset noise floor sampling so it reconverges from scratch.
// Without this, a stuck _noise_floor of -120 makes the sampling threshold
// too low (-106) to accept normal samples (~-105), self-reinforcing the
// stuck value even after the receiver has recovered.
_noise_floor = 0;
_num_floor_samples = 0;
_floor_sample_sum = 0;
} }
void RadioLibWrapper::loop() { void RadioLibWrapper::loop() {

View File

@@ -16,6 +16,7 @@ protected:
void startRecv(); void startRecv();
float packetScoreInt(float snr, int sf, int packet_len); float packetScoreInt(float snr, int sf, int packet_len);
virtual bool isReceivingPacket() =0; virtual bool isReceivingPacket() =0;
virtual void doResetAGC();
public: public:
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }

View File

@@ -0,0 +1,37 @@
#pragma once
#include <RadioLib.h>
// Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx).
// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration,
// then re-applies RX settings that calibration may reset.
inline void sx126xResetAGC(SX126x* radio) {
radio->sleep(true);
radio->standby(RADIOLIB_SX126X_STANDBY_RC, true);
uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL;
radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false);
radio->mod->hal->delay(5);
uint32_t start = millis();
while (radio->mod->hal->digitalRead(radio->mod->getGpio())) {
if (millis() - start > 50) break;
radio->mod->hal->yield();
}
// Calibrate(0x7F) defaults image calibration to 902-928MHz band.
// Re-calibrate for the actual operating frequency.
radio->calibrateImage(radio->freqMHz);
#ifdef SX126X_DIO2_AS_RF_SWITCH
radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
#endif
#ifdef SX126X_RX_BOOSTED_GAIN
radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
#ifdef SX126X_REGISTER_PATCH
uint8_t r_data = 0;
radio->readRegister(0x8B5, &r_data, 1);
r_data |= 0x01;
radio->writeRegister(0x8B5, &r_data, 1);
#endif
}

View File

@@ -106,7 +106,7 @@ extern "C"
// Power management boot protection threshold (millivolts) // Power management boot protection threshold (millivolts)
// Set to 0 to disable boot protection // Set to 0 to disable boot protection
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) #define PWRMGT_VOLTAGE_BOOTLOCK 0 // Won't boot below this voltage (mV)
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) // LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ // AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
#define PWRMGT_LPCOMP_AIN 3 #define PWRMGT_LPCOMP_AIN 3