From a1d41376084b88cc6797ca15acf3bb40f51d4f81 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Mon, 2 Mar 2026 15:10:43 +0100 Subject: [PATCH 1/3] Fix RAK4631 boot issue Wait 50ms after boot before actually shutting down on low voltage Also fix deadlock issue where only USB can wake the device --- src/helpers/NRF52Board.cpp | 25 +++++++++++++++++++++---- src/helpers/NRF52Board.h | 2 +- variants/heltec_t114/T114Board.cpp | 4 +++- variants/rak4631/RAK4631Board.cpp | 4 +++- variants/xiao_nrf52/XiaoNrf52Board.cpp | 4 +++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 2c8753d464..16227d5fc0 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -130,10 +130,17 @@ bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) { // Only trigger shutdown if reading is valid (>1000mV) AND below threshold // This prevents spurious shutdowns on ADC glitches or uninitialized reads if (boot_voltage_mv > 1000 && boot_voltage_mv < config->voltage_bootlock) { - MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown"); + // First reading below threshold - wait for DCDC to stabilize and re-read + delay(50); + boot_voltage_mv = getBattMilliVolts(); + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage (confirmed) = %u mV (threshold = %u mV)", + boot_voltage_mv, config->voltage_bootlock); - initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT); - return false; // Should never reach this + if (boot_voltage_mv > 1000 && boot_voltage_mv < config->voltage_bootlock) { + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown"); + initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT); + return false; // Should never reach this + } } return true; @@ -177,7 +184,7 @@ void NRF52Board::enterSystemOff(uint8_t reason) { NVIC_SystemReset(); } -void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { +bool NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { // LPCOMP is not managed by SoftDevice - direct register access required // Halt and disable before reconfiguration NRF_LPCOMP->TASKS_STOP = 1; @@ -213,6 +220,14 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { delayMicroseconds(50); } + // Safety: if voltage is already above threshold, UP detection will never fire + if (NRF_LPCOMP->RESULT & LPCOMP_RESULT_RESULT_Msk) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP shows voltage above threshold - unsafe for SYSTEMOFF"); + NRF_LPCOMP->TASKS_STOP = 1; + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled; + return false; + } + if (refsel == 7) { MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel); } else if (refsel <= 6) { @@ -235,6 +250,8 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { } MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured"); + + return true; } #endif diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c9f1e071b8..3482fd3e19 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -40,7 +40,7 @@ class NRF52Board : public mesh::MainBoard { bool checkBootVoltage(const PowerMgtConfig* config); void enterSystemOff(uint8_t reason); - void configureVoltageWake(uint8_t ain_channel, uint8_t refsel); + bool configureVoltageWake(uint8_t ain_channel, uint8_t refsel); virtual void initiateShutdown(uint8_t reason); #endif diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index c03d39afb6..10fb332907 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -25,7 +25,9 @@ void T114Board::initiateShutdown(uint8_t reason) { digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW); if (enable_lpcomp) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + if (!configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel)) { + NVIC_SystemReset(); + } } enterSystemOff(reason); diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 9fb47b432e..83c450297f 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -18,7 +18,9 @@ void RAK4631Board::initiateShutdown(uint8_t reason) { if (reason == SHUTDOWN_REASON_LOW_VOLTAGE || reason == SHUTDOWN_REASON_BOOT_PROTECT) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + if (!configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel)) { + NVIC_SystemReset(); + } } enterSystemOff(reason); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 42ee6a87fe..85e02627cc 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -22,7 +22,9 @@ void XiaoNrf52Board::initiateShutdown(uint8_t reason) { digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH); if (enable_lpcomp) { - configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + if (!configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel)) { + NVIC_SystemReset(); + } } enterSystemOff(reason); From 3e32e97799cf569a8c84a7b10c8c3422aec38368 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Tue, 3 Mar 2026 00:01:26 +0100 Subject: [PATCH 2/3] diag build --- variants/rak4631/RAK4631Board.cpp | 34 ++++++++++++++++++++++++++++++- variants/rak4631/platformio.ini | 17 ++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 83c450297f..c30b189bb9 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -3,6 +3,22 @@ #include "RAK4631Board.h" +#ifdef BOOT_DIAG +// Diagnostic LED blink: N short blinks on green LED to indicate boot stage +static void diag_blink(uint8_t count) { + pinMode(PIN_LED1, OUTPUT); + for (uint8_t i = 0; i < count; i++) { + digitalWrite(PIN_LED1, HIGH); + delay(100); + digitalWrite(PIN_LED1, LOW); + delay(150); + } + delay(300); +} +#else +#define diag_blink(n) ((void)0) +#endif + #ifdef NRF52_POWER_MANAGEMENT // Static configuration for power management // Values set in variant.h defines @@ -28,7 +44,16 @@ void RAK4631Board::initiateShutdown(uint8_t reason) { #endif // NRF52_POWER_MANAGEMENT void RAK4631Board::begin() { + diag_blink(1); // Stage 1: entering begin() + +#ifdef DISABLE_DCDC + NRF52Board::begin(); +#else NRF52BoardDCDC::begin(); +#endif + + diag_blink(2); // Stage 2: board base init done + pinMode(PIN_VBAT_READ, INPUT); #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); @@ -44,12 +69,19 @@ void RAK4631Board::begin() { Wire.begin(); + diag_blink(3); // Stage 3: I2C done, about to configure LoRa power + pinMode(SX126X_POWER_EN, OUTPUT); -#ifdef NRF52_POWER_MANAGEMENT +#if defined(NRF52_POWER_MANAGEMENT) && !defined(DISABLE_BOOT_PROTECTION) // 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 + + diag_blink(4); // Stage 4: power check passed + digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up + + diag_blink(5); // Stage 5: board.begin() complete } \ No newline at end of file diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index ea7e49c355..cb8e74efed 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -48,6 +48,23 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> +; Diagnostic build: LED blinks at boot stages + boot protection disabled +[env:RAK_4631_repeater_diag] +extends = env:RAK_4631_repeater +build_flags = + ${env:RAK_4631_repeater.build_flags} + -D BOOT_DIAG + -D DISABLE_BOOT_PROTECTION + +; Diagnostic build: LED blinks + boot protection disabled + DC/DC disabled +[env:RAK_4631_repeater_diag_no_dcdc] +extends = env:RAK_4631_repeater +build_flags = + ${env:RAK_4631_repeater.build_flags} + -D BOOT_DIAG + -D DISABLE_BOOT_PROTECTION + -D DISABLE_DCDC + [env:RAK_4631_repeater_bridge_rs232_serial1] extends = rak4631 build_flags = From 75605434cb05a5141d9b7e171bc294c5b9be7837 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Tue, 3 Mar 2026 01:09:39 +0100 Subject: [PATCH 3/3] Only enable DC/DC converter when SoftDevice is managing power --- src/helpers/NRF52Board.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 16227d5fc0..ab25cf0fd8 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -258,13 +258,15 @@ bool NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { void NRF52BoardDCDC::begin() { NRF52Board::begin(); - // Enable DC/DC converter for improved power efficiency + // Enable DC/DC converter only when SoftDevice is managing the POWER peripheral. + // Without SoftDevice, NRF_POWER->DCDCEN = 1 leaves the DC/DC permanently on, + // which causes boot failures on some boards when running on battery alone. + // The SoftDevice manages DC/DC dynamically (on during radio, off during idle), + // which is the safe way to use it. uint8_t sd_enabled = 0; sd_softdevice_is_enabled(&sd_enabled); if (sd_enabled) { sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; } }