From dcabcb8cb29d9e00ae6766c1258683226d2c62be Mon Sep 17 00:00:00 2001 From: mikecarper Date: Sat, 17 Oct 2026 11:42:21 -0700 Subject: [PATCH] Initialize DC/DC on nine circuit-verified nRF52 boards --- docs/power_saving_diagnostics.md | 28 ++++++++ test/test_nrf52_board_regulators.py | 67 ++++++++++++++++--- variants/heltec_mesh_solar/MeshSolarBoard.cpp | 2 +- variants/heltec_t1/T1Board.cpp | 2 +- .../heltec_tower_v2/HeltecTowerV2Board.cpp | 2 +- .../ikoka_handheld_nrf/IkokaNrf52Board.cpp | 2 +- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 2 +- .../ikoka_stick_nrf/IkokaStickNRFBoard.cpp | 2 +- variants/lilygo_techo/TechoBoard.cpp | 2 +- variants/mesh_pocket/MeshPocket.cpp | 2 +- .../MinewsemiME25LS01Board.cpp | 4 +- 11 files changed, 97 insertions(+), 18 deletions(-) diff --git a/docs/power_saving_diagnostics.md b/docs/power_saving_diagnostics.md index 7777c4bbe..7469a67dd 100644 --- a/docs/power_saving_diagnostics.md +++ b/docs/power_saving_diagnostics.md @@ -120,3 +120,31 @@ compare battery-only cold starts, low-voltage recovery, BLE operation and settings writes/reboots on the actual board/baseboard revisions before a fleet rollout. USB power can mask the reported startup symptoms. A current meter is still needed to quantify the power difference. + +The following boards also now call their declared `NRF52BoardDCDC` initializer +instead of bypassing it with `NRF52Board::begin()`. This applies to every role +that uses the board class, not just Companion, and does not require enabling +the runtime sleep policy. The nRF52840 hardware manages the enabled regulator +according to load. This is REG1 only, not a blanket change to REG0 or an +external LDO, PMIC output, or radio regulator. + +| Board | Circuit evidence used for REG1 support | +| --- | --- | +| Heltec T1 | [T1 V1.0 schematic](https://resource.heltec.cn/download/Mesh_Node_T1/schematic/Mesh_Node_T1_V1.0.pdf), DCC-to-DEC4 inductors and capacitors. | +| Heltec MeshPocket | [MeshPocket schematic](https://resource.heltec.cn/download/MeshPocket/schematic/MeshPocket_nRF52840%2BSX1262.pdf), DCC-to-DEC4 L1/L2 and C5/C6. | +| Heltec MeshSolar and MeshTower V2 | [HT-N5262M module circuit](https://resource.heltec.cn/download/MeshTower-V2/schematic/HT-N5262M_Schematic_Diagram.pdf), L1/L2 and C1/C2. Heltec links this circuit from its [MeshTower V2 resources](https://heltec.org/project/meshtower/); [MeshSolar's datasheet](https://resource.heltec.cn/download/MeshSolar/datasheet/MeshSolar_V1.0.0.pdf) identifies the module. | +| LilyGO T-Echo | [T-Echo schematic](https://raw.githubusercontent.com/Xinyuan-LilyGO/T-Echo/main/schematic/T-Echo_Schematic.pdf), REG1 DC/DC network and explicit DC/DC-enabled design note. | +| Ikoka Handheld, Nano and Stick nRF52 | [XIAO nRF52840 circuit](https://files.seeedstudio.com/wiki/XIAO-BLE/Seeed_Studio_XIAO_nRF52840_PDF.pdf), DCC L1 and DEC4 capacitors. The designers identify XIAO as the MCU for [Handheld](https://github.com/ndoo/ikoka-handheld-meshtastic-device), [Nano](https://ndoo.sg/projects:amateur_radio:meshtastic:diy_devices:ikoka_nano) and [Stick](https://github.com/ndoo/ikoka-stick-meshtastic-device). | +| Minew ME25LS01 | [Manufacturer datasheet](https://store.minewsemi.com/wp-content/uploads/2024/03/ME25LS01_LR1110_Datasheet_K_EN.pdf), electrical schematic page 9: MCU DCC through ML2/ML3 to DEC4_6 and MC15/MC16. | + +The startup tests execute each real `begin()` with SoftDevice off/on and power +management compiled out/in. They require DC/DC before peripheral initialization +and before any enabled boot-voltage check, while retaining one common board +initialization. The previous bypass fails these tests. + +ThinkNode M3/M4/M6 and generic ProMicro remain unchanged pending exact circuit +or physical-board confirmation; their DC/DC inheritance alone is not evidence. +Nano G2 Ultra, ThinkNode M1/M8 and Keepteen LT1 retain their plain `NRF52Board` +initializers for the same reason. The MeshTracker X1's LR2021 regulator likewise +remains unchanged. Do not turn on a switching regulator without its required +inductor/capacitor network. The MCU and LoRa regulator choices are independent. diff --git a/test/test_nrf52_board_regulators.py b/test/test_nrf52_board_regulators.py index b5e461ca7..5b0366339 100644 --- a/test/test_nrf52_board_regulators.py +++ b/test/test_nrf52_board_regulators.py @@ -11,16 +11,41 @@ from cpp_source import body ROOT = Path(__file__).resolve().parents[1] +# Each target has manufacturer/board-designer circuit evidence recorded in +# docs/power_saving_diagnostics.md. Do not infer eligibility from inheritance. +VERIFIED_BOARDS = ( + ('heltec_t1/T1Board.cpp', 'T1Board', True), + ('heltec_mesh_solar/MeshSolarBoard.cpp', 'MeshSolarBoard', False), + ('heltec_tower_v2/HeltecTowerV2Board.cpp', 'HeltecTowerV2Board', True), + ('mesh_pocket/MeshPocket.cpp', 'HeltecMeshPocket', False), + ('lilygo_techo/TechoBoard.cpp', 'TechoBoard', False), + ('ikoka_handheld_nrf/IkokaNrf52Board.cpp', 'IkokaNrf52Board', False), + ('ikoka_nano_nrf/IkokaNanoNRFBoard.cpp', 'IkokaNanoNRFBoard', False), + ('ikoka_stick_nrf/IkokaStickNRFBoard.cpp', 'IkokaStickNRFBoard', False), + ('minewsemi_me25ls01/MinewsemiME25LS01Board.cpp', 'MinewsemiME25LS01Board', False), +) + HARNESS = r''' #include #include -#define NRF52_POWER_MANAGEMENT 1 #define PIN_BOARD_SDA 1 #define PIN_BOARD_SCL 2 #define PIN_USER_BTN 9 #define PIN_USER_BTN_ANA 31 +#define PIN_WIRE_SDA 1 +#define PIN_WIRE_SCL 2 +#define P_LORA_TX_LED 28 +#define BUTTON_PIN 9 +#define DISPLAY_CLASS TestDisplay +#define DISPLAY_ROTATION 0 constexpr int INPUT = 0, OUTPUT = 1, INPUT_PULLUP = 2, LOW = 0, HIGH = 1; constexpr int PIN_VBAT_READ = 5, WB_IO2 = 34, SX126X_POWER_EN = 37; +constexpr int PIN_VBAT = 5, VBAT_ENABLE = 14, PIN_BAT_CTL = 15, LED_PIN = 28; +constexpr int PIN_GPS_EN = 10, PIN_GPS_RESET = 11, PIN_GPS_STANDBY = 12; +constexpr int PIN_SENSOR_EN = 13, PIN_BUZZER = 16; +constexpr int PIN_BUZZER_VOLTAGE_MULTIPLIER_1 = 17, PIN_BUZZER_VOLTAGE_MULTIPLIER_2 = 18; +constexpr int LED_STATE_ON = 1, PIN_GPS_EN_ACTIVE = 1, PIN_SENSOR_EN_ACTIVE = 1; +constexpr int GPS_RESET_MODE = 0; constexpr int NRF_POWER_DCDC_ENABLE = 1, NRF_POWER_DCDC_DISABLE = 0; static unsigned base_calls, sdk_calls, voltage_checks; static uint8_t softdevice; @@ -33,19 +58,33 @@ void sd_power_dcdc_mode_set(int mode) { assert(base_calls == 1 && softdevice && mode == NRF_POWER_DCDC_ENABLE); ++sdk_calls; } -void pinMode(int, int) {} -void digitalWrite(int, int) {} +void requireDcdc() { + assert(base_calls == 1); + assert(softdevice ? sdk_calls == 1 : power.DCDCEN == 1); +} +void pinMode(int, int) { requireDcdc(); } +void digitalWrite(int, int) { requireDcdc(); } void delay(unsigned) {} -struct Bus { void setPins(int, int) {} void begin() {} } Wire; +void meshSolarStart() { requireDcdc(); } +struct Bus { + void setPins(int, int) { requireDcdc(); } + void begin() { requireDcdc(); } +} Wire; +struct Uart { void begin(unsigned) { requireDcdc(); } } Serial; +struct Peripheral { + void begin() { requireDcdc(); } + void init() { requireDcdc(); } +}; struct Config {} power_config; struct NRF52Board { void begin() { ++base_calls; } }; struct NRF52BoardDCDC : NRF52Board { void begin(); }; struct @BOARD@ : NRF52BoardDCDC { + Peripheral periph_power, loRaFEMControl; + unsigned btn_prev_state = 0; void begin(); void checkBootVoltage(const Config*) { ++voltage_checks; - assert(base_calls == 1); - assert(softdevice ? sdk_calls == 1 : power.DCDCEN == 1); + requireDcdc(); } }; @METHODS@ @@ -54,7 +93,7 @@ int main() { power.DCDCEN = base_calls = sdk_calls = voltage_checks = 0; @BOARD@ board; board.begin(); - assert(base_calls == 1 && voltage_checks == 1); + assert(base_calls == 1 && voltage_checks == @VOLTAGE_CHECKS@); assert(sdk_calls == softdevice); assert(power.DCDCEN == (softdevice ? 0U : 1U)); } @@ -63,11 +102,15 @@ int main() { class Nrf52BoardRegulatorTests(unittest.TestCase): - def run_startup(self, filename, board, flags=()): + def run_startup(self, filename, board, flags=(), power_management=True, + has_voltage_check=True): methods = body((ROOT / 'src/helpers/NRF52Board.cpp').read_text(), 'void NRF52BoardDCDC::begin()') methods += body((ROOT / filename).read_text(), f'void {board}::begin()') source = HARNESS.replace('@BOARD@', board).replace('@METHODS@', methods) + source = source.replace('@VOLTAGE_CHECKS@', str(int(power_management and has_voltage_check))) + if power_management: + flags = (*flags, '-DNRF52_POWER_MANAGEMENT=1') with tempfile.TemporaryDirectory(prefix='meshcore-board-regulator-') as tmp: cpp, binary = Path(tmp) / 'test.cpp', Path(tmp) / 'test' cpp.write_text(source, encoding='ascii') @@ -86,6 +129,14 @@ class Nrf52BoardRegulatorTests(unittest.TestCase): with self.subTest(flags=flags): self.run_startup('variants/rak4631/RAK4631Board.cpp', 'RAK4631Board', flags) + def test_verified_boards_initialize_dcdc_before_peripherals(self): + for filename, board, has_voltage_check in VERIFIED_BOARDS: + for power_management in (False, True): + with self.subTest(board=board, power_management=power_management): + self.run_startup('variants/' + filename, board, + power_management=power_management, + has_voltage_check=has_voltage_check) + def test_rak_companion_profiles_do_not_force_ldo(self): config = configparser.ConfigParser(interpolation=None) config.read(ROOT / 'variants/rak4631/platformio.ini') diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp index bc955fb53..7cc637681 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.cpp +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -4,7 +4,7 @@ #include "MeshSolarBoard.h" void MeshSolarBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); meshSolarStart(); diff --git a/variants/heltec_t1/T1Board.cpp b/variants/heltec_t1/T1Board.cpp index 7cfd21249..8804d579b 100644 --- a/variants/heltec_t1/T1Board.cpp +++ b/variants/heltec_t1/T1Board.cpp @@ -27,7 +27,7 @@ void T1Board::initiateShutdown(uint8_t reason) { #endif void T1Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); #ifdef NRF52_POWER_MANAGEMENT checkBootVoltage(&power_config); diff --git a/variants/heltec_tower_v2/HeltecTowerV2Board.cpp b/variants/heltec_tower_v2/HeltecTowerV2Board.cpp index 61de14c48..801ba95bd 100644 --- a/variants/heltec_tower_v2/HeltecTowerV2Board.cpp +++ b/variants/heltec_tower_v2/HeltecTowerV2Board.cpp @@ -36,7 +36,7 @@ void HeltecTowerV2Board::initiateShutdown(uint8_t reason) { #endif void HeltecTowerV2Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); pinMode(P_LORA_TX_LED, OUTPUT); digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp index 91decb1eb..92d39407e 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -6,7 +6,7 @@ #include "IkokaNrf52Board.h" void IkokaNrf52Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); // ensure we have pull ups on the screen i2c, this isn't always available // in hardware and it should only be 20k ohms. Disable the pullups if we diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp index 7065ab256..d45bdd5e3 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -6,7 +6,7 @@ #include "IkokaNanoNRFBoard.h" void IkokaNanoNRFBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT, INPUT); // Seeed's XIAO charging guidance requires READ_BAT_ENABLE to stay LOW. diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 09f4c6308..5f9f7e529 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -6,7 +6,7 @@ #include "IkokaStickNRFBoard.h" void IkokaStickNRFBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT, INPUT); // Seeed's XIAO charging guidance requires READ_BAT_ENABLE to stay LOW. diff --git a/variants/lilygo_techo/TechoBoard.cpp b/variants/lilygo_techo/TechoBoard.cpp index 81d3d0c9a..2edbb6a3e 100644 --- a/variants/lilygo_techo/TechoBoard.cpp +++ b/variants/lilygo_techo/TechoBoard.cpp @@ -6,7 +6,7 @@ #ifdef LILYGO_TECHO void TechoBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); Wire.begin(); diff --git a/variants/mesh_pocket/MeshPocket.cpp b/variants/mesh_pocket/MeshPocket.cpp index 0c53121f2..f01e2168d 100644 --- a/variants/mesh_pocket/MeshPocket.cpp +++ b/variants/mesh_pocket/MeshPocket.cpp @@ -4,7 +4,7 @@ #include "MeshPocket.h" void HeltecMeshPocket::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); Serial.begin(115200); pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index 0267185c9..72493a2a6 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -4,7 +4,7 @@ #include "MinewsemiME25LS01Board.h" void MinewsemiME25LS01Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); @@ -26,4 +26,4 @@ void MinewsemiME25LS01Board::begin() { #endif delay(10); // give sx1262 some time to power up -} \ No newline at end of file +}