Initialize DC/DC on nine circuit-verified nRF52 boards

This commit is contained in:
mikecarper committed 2026-10-17 11:42:21 -07:00
1 parent 364e3cabd5
commit dcabcb8cb2
11 files changed
+97 -18

No files matched your search

+28
View File
@@ -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.
+59 -8
View File
@@ -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 <cassert>
#include <cstdint>
#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')
@@ -4,7 +4,7 @@
#include "MeshSolarBoard.h"
void MeshSolarBoard::begin() {
NRF52Board::begin();
NRF52BoardDCDC::begin();
meshSolarStart();
+1 -1
View File
@@ -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);
@@ -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);
@@ -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
@@ -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.
@@ -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.
+1 -1
View File
@@ -6,7 +6,7 @@
#ifdef LILYGO_TECHO
void TechoBoard::begin() {
NRF52Board::begin();
NRF52BoardDCDC::begin();
Wire.begin();
+1 -1
View File
@@ -4,7 +4,7 @@
#include "MeshPocket.h"
void HeltecMeshPocket::begin() {
NRF52Board::begin();
NRF52BoardDCDC::begin();
Serial.begin(115200);
pinMode(PIN_VBAT_READ, INPUT);
@@ -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
}
}