修复发射重启:RXEN冲突+POFCON关闭

- 移除variant_nrf52LoopHook中P0.11调试翻转代码(根因:TX时
  DIO2拉高TXEN,loop hook同时翻转RXEN导致双高,射频开关
  未定义行为->PA电流尖峰->VDD跌落->HardFault)
- 定义SX126X_RXEN(P0.11),由RadioLib在TX时拉低/RX时拉高,
  与DIO2驱动的TXEN协调配合
- POFCON关闭逻辑从只执行3次改为每次loop检查,防止SoftDevice
  重新开启
- 修正initVariant注释(SystemInit已处理PSELRESET,此为兜底)
- 添加DIAGNOSTICS.md记录SWD诊断过程和硬件修复建议
This commit is contained in:
2026-07-20 14:13:16 +08:00
parent 896d4a3f93
commit e7c82d0b69
4 changed files with 167 additions and 22 deletions
@@ -0,0 +1,155 @@
# Moonshine NRF52 - TX 重启问题诊断报告
## 症状
发送消息时设备立即重启。首次修复(RXEN 冲突 + POFCON 关闭)后问题依然存在。
## 诊断方法
通过 CMSIS-DAP SWD 探针 + OpenOCD 读取 nRF52840 内部寄存器和异常栈帧:
1. 读取 `RESETREAS``GPREGRET``POFCON``EVENTS_POFWARN` 确定复位类型
2.`HardFault_Handler` / `__assert_func` / `lfs_assert` / `abort` 设置硬件断点
3. 崩溃命中后通过 DCRSR/DCRDR 读取 PSP/MSP,从栈帧提取 faulting PC/LR
## 诊断结果
### 第一轮:复位原因分析
| 寄存器 | 值 | 含义 |
|---|---|---|
| `_reset_reason` (Adafruit 缓存) | `0x00000004` | 软件复位 (NVIC_SystemReset) |
| `EVENTS_POFWARN` | `0` | 未触发电源故障警告 |
| `GPREGRET` | `0x00` | 非 lfs_assert(否则为 0xF5 |
| `error_code` | `0` | 无关键错误记录 |
| `POFCON` | `0x0003` | 仍然开启(SoftDevice 覆盖了关闭操作) |
结论:**软件触发的复位**,排除了 POFCON/BOD/POR。最可能来源是 `__assert_func()``HardFault_Handler`
### 第二轮:HardFault 捕获
发现 nRF52 + FreeRTOS 构建中,`src/platform/nrf52/hardfault.cpp` 的诊断版 `HardFault_Handler` 被 LTO 优化掉,实际使用的是 Adafruit core 的简化版(`debug.cpp:60`,直接 `NVIC_SystemReset()` 丢弃所有故障上下文)。
`variant.cpp` 中添加诊断版 `HardFault_Handler`variant 代码排除在 LTO 之外),重新捕获。
### 第三轮:异常栈帧分析
断点命中 `HardFault_Handler` 后,通过 DCRSR 读取 PSP,从 PSP 指向的异常栈帧提取寄存器:
| 寄存器 | 值 | 含义 |
|---|---|---|
| PC | `0x0007d464` | **SEGGER_RTT_vprintf**(故障指令) |
| LR | `0x0007d3f5` | SEGGER_RTT_vprintf(调用者) |
| PSP | `0x2001e2f0` | 有效 RAM 地址 |
| PSR | `0x01000000` | 有效(Thread mode, Thumb |
| CFSR | `0x00008200` | **UNSTKERR** + BFARVALID |
| HFSR | `0x40000000` | **FORCED**(可配置故障升级为 HardFault |
| BFAR | `0x5c28f5c3` | **每次不同的无效地址** |
CFSR 解码:
- `UNSTKERR` (bit 11 of BFSR) - 异常返回(unstacking)时总线错误
- `BFARVALID` (bit 15 of BFSR) - BFAR 包含有效故障地址
多次捕获的 BFAR 值:
- `0x851eb852`
- `0x2b020c4a`
- `0x374bc6a8`
- `0x5c28f5c3`
## 根因分析
**BFAR 每次不同** -> 不是确定性代码 bug,是随机因素。
**完整故障链:**
1. TX 触发 -> E22-400M33S PA 瞬态大电流 -> VDD 跌落
2. TX 路径中的日志函数(SEGGER_RTT_vprintf)正在执行
3. 某个中断发生(BLE/SoftDevice 中断)
4. 中断返回时,**VDD 跌落导致 RAM 读取不稳定**
5. unstacking 从 PSP 读取栈帧时某个字返回无效值 -> **UNSTKERR**(总线故障)
6. 总线故障升级为 **HardFault**FORCED
7. HardFault_Handler -> `NVIC_SystemReset()` -> 设备重启
**关键证据:**
- BFAR 每次不同 -> 随机因素(VDD 跌落导致 RAM 读取错误),非确定性代码 bug
- PSP 帧有效(PC/LR/PSR 都正常)-> 栈本身没损坏,是 unstacking 过程中的瞬态错误
- POFWARN=0 -> 不是 POFCON 触发的
- **DCDC 未启用**DCDCEN=0-> 使用低效 LDOVDD 跌落更严重
- POFCON 仍开启(0x0003-> SoftDevice 覆盖了 `sd_power_pof_enable(0)` 调用
**这是硬件电源设计问题,软件只能缓解。**
## 硬件修复建议
### 1. 增加去耦电容(最有效)
在 E22-400M33S 模块的 VCC 引脚附近增加:
- **100µF 电解电容**(提供瞬态电流储备)
- **0.1µF 陶瓷电容**(高频去耦,紧贴 VCC 引脚)
- 可选:10µF 钽电容(低 ESR,响应快)
### 2. 检查电源供电能力
- 电池/LDO 的瞬态电流规格需满足 PA 峰值电流(E22-400M33S 在 2dBm 输出时约 500mA 峰值)
- 检查电池内阻,老化和低温会显著增加内阻导致 VDD 跌落
### 3. 启用 nRF52 DCDC
RF-BM-ND05 模块如果焊有 DCDC 电感(查看模块丝印或数据手册的 DCDC 引脚),可通过软件启用:
```c
// 在 initVariant() 或 nrf52Setup() 中
sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);
```
DCDC 效率(~90%)远高于 LDO(~50%),能显著降低 VDD 跌落幅度。需确认硬件上有 DCDC 电感连接(DEC4 到 DCC 引脚之间有 10µH 电感),否则不能启用。
### 4. 降低 TX 功率(软件缓解,已实施)
`variant.h``SX126X_MAX_POWER=2`2dBm),PA 增益 25dB -> 约 27dBm 输出。如仍重启可进一步降低到 0 或 -9。
## 当前软件状态
变体已应用以下软件修复:
1. **`SX126X_RXEN` 定义**`variant.h`- P0.11 由 RadioLib 控制,TX 时拉低、RX 时拉高,与 DIO2 驱动的 TXEN 协调
2. **POFCON 持续关闭**`variant.cpp` `variant_nrf52LoopHook`)- 每次 loop 检查并关闭被 SoftDevice 重新开启的 POFCON
3. **移除了 P0.11 调试翻转代码**(原 `variant_nrf52LoopHook` 中的 debug toggle
4. **移除了 HardFault 诊断代码**`variant.cpp`- 恢复为 Adafruit core 的默认 HardFault_Handler
5. **移除了 `-Wl,-z,muldefs`**`platformio.ini`)- 诊断时用于允许多重定义的链接器选项
软件层面已无法进一步缓解,需要硬件改动(去耦电容 / DCDC 电感)来解决 PA 瞬态电流导致的 VDD 跌落。
## 复现与验证方法
### 构建
```sh
pio run -e moonshine_NRF52_node
```
### 烧录(CMSIS-DAP via OpenOCD
```sh
openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg \
-c "init; program .pio/build/moonshine_NRF52_node/firmware-moonshine_NRF52_node-*.hex verify; reset run; exit"
```
### SWD 诊断(如需重新捕获 HardFault)
1. 安装 OpenOCD`brew install openocd`
2. 连接 CMSIS-DAP 探针(SWDSWCLK/SWDIO/3V3/GND + nRESET
3. 读取复位原因:
```sh
openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg -c \
"init; halt; puts [format 'RESETREAS=0x%08x' [lindex [read_memory 0x40000400 32 1] 0]]; resume; exit"
```
- `0x01` = pin reset, `0x02` = watchdog, `0x04` = soft reset (NVIC_SystemReset), `0x08` = CPU lockup, `0x10000` = System OFF wake
4. 读取电源状态:
```sh
openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg -c \
"init; halt; puts [format 'DCDCEN=0x%08x POFCON=0x%08x REGOUT0=0x%08x' [lindex [read_memory 0x40000578 32 1] 0] [lindex [read_memory 0x40000554 32 1] 0] [lindex [read_memory 0x10000304 32 1] 0]]; resume; exit"
```
- DCDCEN=0 -> DCDC 未启用
- REGOUT0 低 3 位:0=1.8V, 1=2.1V, 2=2.4V, 3=2.7V, 4=3.0V, 5=3.3V, 7=默认1.8V
@@ -11,8 +11,9 @@ DIY Meshtastic node variant based on the RF-BM-ND05 nRF52840 module.
- **LoRa**: E22-400M33S (SX1268 + 33dBm PA, 2W, 433MHz) - `SX126X_MAX_POWER=2`,
`TX_GAIN_LORA=25`, `SX126X_PA_RAMP_US=0x07` (3400us ramp). PA has no enable pin
and draws high transient current on TX; default 22dBm/200us-ramp causes BOD reset.
POFCON disabled in `variant_nrf52LoopHook()` to prevent POFWARN -> lfs_assert
-> reboot chain.
`SX126X_RXEN` (P0.11) is MCU-controlled by RadioLib; TXEN is driven by DIO2
(`SX126X_DIO2_AS_RF_SWITCH`). POFCON is continuously disabled in
`variant_nrf52LoopHook()` to prevent POFWARN -> lfs_assert -> reboot chain.
- **GPS**: u-blox (no EN pin)
- **Battery ADC**: 0.5 voltage divider (equal resistors)
@@ -34,7 +35,7 @@ DIY Meshtastic node variant based on the RF-BM-ND05 nRF52840 module.
| LORA_DIO1 | P0.21 | IRQ |
| LORA_BUSY | P0.22 | |
| LORA_RESET | P0.23 | |
| LORA_RXEN | P0.11 | TXEN tied to DIO2 internally |
| LORA_RXEN | P0.11 | MCU-controlled RXEN; TXEN via DIO2 |
| GPS_RX (MCU) | P0.07 | Data from GNSS to MCU |
| GPS_TX (MCU) | P0.08 | Data from MCU to GNSS |
| UART_TX | P0.14 | Serial2 |
@@ -28,8 +28,6 @@ extern "C" {
#include "nrf_soc.h"
}
static bool pofcon_disabled = false;
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
@@ -41,9 +39,8 @@ void initVariant()
{
// Configure P0.18 as RESET pin (UICR->PSELRESET).
// nrf52_recover (ERASEALL) clears UICR to 0xFFFFFFFF, disabling reset.
// SystemInit() should do this via CONFIG_GPIO_AS_PINRESET, but that code path
// is not compiled in by the Adafruit core. Write UICR directly here - this
// runs before SoftDevice is enabled, so NVMC is accessible.
// SystemInit() already handles this via CONFIG_GPIO_AS_PINRESET; this is a
// safety net that runs before SoftDevice is enabled, so NVMC is accessible.
if ((NRF_UICR->PSELRESET[0] != 18) || (NRF_UICR->PSELRESET[1] != 18)) {
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {}
@@ -66,21 +63,12 @@ void variant_shutdown()
void variant_nrf52LoopHook(void)
{
// Debug: toggle P0.11 to confirm this hook is called
static bool initialized = false;
if (!initialized) {
nrf_gpio_cfg_output(11);
initialized = true;
}
nrf_gpio_pin_toggle(11);
// Disable POFCON via SoftDevice API - direct NRF_POWER->POFCON writes are
// ignored once SoftDevice is enabled. E22-400M33S TX causes transient VDD
// drops that trigger POFWARN -> lfs_assert -> NVIC_SystemReset.
static uint32_t retry_count = 0;
if (retry_count < 3) {
uint32_t ret = sd_power_pof_enable(0);
SEGGER_RTT_printf(0, "variant_loop: ret=%d POFCON=0x%x\r\n", ret, NRF_POWER->POFCON);
retry_count++;
// SoftDevice may re-enable POFCON during BLE events, so check every iteration.
if (NRF_POWER->POFCON & (1 << POWER_POFCON_POF_Pos)) {
sd_power_pof_enable(0);
SEGGER_RTT_printf(0, "variant_loop: POFCON re-enabled, disabling (POFCON=0x%x)\r\n", NRF_POWER->POFCON);
}
}
@@ -129,7 +129,8 @@ MOONSHINE NRF52 PIN ASSIGNMENT (RF-BM-ND05)
// so it needs connecting externally if it is used in this way
#define SX126X_BUSY (0 + 22) // P0.22
#define SX126X_RESET (0 + 23) // P0.23
#define SX126X_TXEN RADIOLIB_NC // DIO2 controls TXEN directly; RXEN left floating (module default)
#define SX126X_RXEN (0 + 11) // P0.11 - E22 RXEN, MCU controls RX/TX RF switch path
#define SX126X_TXEN RADIOLIB_NC // DIO2 controls TXEN directly
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL