feat(vd960DBN): fault_diag — .noinit RAM 现场 + 执行轨迹 marker 定位神秘复位
现象: LUP 帧后复位, RST=0x00000000 (无标志) 且 HardFault 打印缺失
(上次 0x10000000=SFT 确认 HardFault 路径, 这次连打印都没有)
方案: .noinit 段复位不清零, 跨复位留痕
- Link.ld: 新增 .noinit 段 (startup 只清 .bss 不碰)
- fault_diag.h: FaultDiag{magic,mcause,mepc,mtval,marker,boot_cnt} + FAULT_MARKER 轨迹宏
- HardFault_Handler: 纯 RAM 写 mcause/mepc/mtval (官方 __get_MCAUSE/MEPC/MTVAL,
不依赖 UART/printf, 栈坏也能留痕) + 尽力打印
- main 开头 fault_diag_init(): 打印上次现场 + 递增 boot_cnt
- 轨迹 marker: 主循环 LOOP_TOP/UART_SRV/SIM_SPI/POLL_BLE + LUP 链
LUP_FRAME/INGEST/EVT_FEED 入口出口, 复位后看 marker 停在哪一步
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
#include "eth_driver.h"
|
#include "eth_driver.h"
|
||||||
#include "CONFIG.h"
|
#include "CONFIG.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "fault_diag.h"
|
||||||
|
|
||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* LOCAL FUNCTIONS
|
* LOCAL FUNCTIONS
|
||||||
@@ -39,6 +40,35 @@ void NMI_Handler(void)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 故障诊断现场: .noinit 段 (复位不清零) */
|
||||||
|
FaultDiag g_fault_diag __attribute__((section(".noinit")));
|
||||||
|
|
||||||
|
/*********************************************************************
|
||||||
|
* @fn fault_diag_init
|
||||||
|
*
|
||||||
|
* @brief main 开头调用: 打印上次复位现场 + 重置 + 递增 boot_cnt
|
||||||
|
*
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
void fault_diag_init(void)
|
||||||
|
{
|
||||||
|
if (g_fault_diag.magic == 0xFA57FA57UL) {
|
||||||
|
PRINT("FAULT_DIAG: boot_cnt=%lu mcause=0x%08lx mepc=0x%08lx mtval=0x%08lx marker=0x%08lx\n",
|
||||||
|
(unsigned long)g_fault_diag.boot_cnt,
|
||||||
|
(unsigned long)g_fault_diag.mcause,
|
||||||
|
(unsigned long)g_fault_diag.mepc,
|
||||||
|
(unsigned long)g_fault_diag.mtval,
|
||||||
|
(unsigned long)g_fault_diag.marker);
|
||||||
|
}
|
||||||
|
/* 重置现场, boot_cnt 保留递增 */
|
||||||
|
g_fault_diag.magic = 0;
|
||||||
|
g_fault_diag.mcause = 0;
|
||||||
|
g_fault_diag.mepc = 0;
|
||||||
|
g_fault_diag.mtval = 0;
|
||||||
|
g_fault_diag.marker = 0;
|
||||||
|
g_fault_diag.boot_cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* @fn HardFault_Handler
|
* @fn HardFault_Handler
|
||||||
*
|
*
|
||||||
@@ -48,16 +78,16 @@ void NMI_Handler(void)
|
|||||||
*/
|
*/
|
||||||
void HardFault_Handler(void)
|
void HardFault_Handler(void)
|
||||||
{
|
{
|
||||||
/* 诊断 (2026-08-13): 抓 RISC-V 异常现场, 定位 HardFault 源头
|
/* 纯 RAM 写现场 (不依赖 UART/printf): 栈坏/中断禁用也能留痕 */
|
||||||
mcause: 5=Load访问错误 7=Store访问错误 2=非法指令 4/6=未对齐
|
g_fault_diag.magic = 0xFA57FA57UL;
|
||||||
mepc : 异常指令 PC (用 .map 文件反查函数)
|
g_fault_diag.mcause = __get_MCAUSE();
|
||||||
mtval : 错误访问地址 (Load/Store fault 时=野指针地址) */
|
g_fault_diag.mepc = __get_MEPC();
|
||||||
uint32_t _mcause = 0, _mepc = 0, _mtval = 0;
|
g_fault_diag.mtval = __get_MTVAL();
|
||||||
__asm__ volatile("csrr %0, 0x342" : "=r"(_mcause));
|
/* 尽力打印 (UART 可用时), 失败不影响 RAM 现场 */
|
||||||
__asm__ volatile("csrr %0, 0x341" : "=r"(_mepc));
|
|
||||||
__asm__ volatile("csrr %0, 0x343" : "=r"(_mtval));
|
|
||||||
PRINT("HARDFAULT: mcause=0x%08lx mepc=0x%08lx mtval=0x%08lx\n",
|
PRINT("HARDFAULT: mcause=0x%08lx mepc=0x%08lx mtval=0x%08lx\n",
|
||||||
(unsigned long)_mcause, (unsigned long)_mepc, (unsigned long)_mtval);
|
(unsigned long)g_fault_diag.mcause,
|
||||||
|
(unsigned long)g_fault_diag.mepc,
|
||||||
|
(unsigned long)g_fault_diag.mtval);
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/********************************** (C) COPYRIGHT *******************************
|
||||||
|
* File Name : fault_diag.h
|
||||||
|
* Author : wfq
|
||||||
|
* Version : V1.0
|
||||||
|
* Date : 2026-08-13
|
||||||
|
* Description : 故障诊断 — .noinit RAM 现场 + 执行轨迹 marker
|
||||||
|
*
|
||||||
|
* 背景: HardFault 时 UART/printf 可能不可用 (栈坏/中断禁用), 打印丢现场;
|
||||||
|
* RST=0x00000000 的复位无任何标志, 无法从 RSTSCKR 判断复位源。
|
||||||
|
* 方案: 全局记录放 .noinit 段 (复位不清零, 链接脚本已加):
|
||||||
|
* - HardFault_Handler 纯 RAM 写 mcause/mepc/mtval (不依赖 UART)
|
||||||
|
* - 关键路径 FAULT_MARKER() 记录执行轨迹
|
||||||
|
* - main 开头 fault_diag_init() 打印上次现场 + 递增 boot_cnt
|
||||||
|
* 用法: FAULT_MARKER(MK_XXX) 插在嫌疑路径各阶段; 复位后看 marker 停在哪。
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __FAULT_DIAG_H
|
||||||
|
#define __FAULT_DIAG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t magic; /* 0xFA57FA57 = 现场有效 */
|
||||||
|
uint32_t mcause; /* RISC-V 异常原因 (5/7=野指针 2=非法指令 4/6=未对齐) */
|
||||||
|
uint32_t mepc; /* 异常指令 PC (对照 .map 反查函数) */
|
||||||
|
uint32_t mtval; /* 错误访问地址 */
|
||||||
|
uint32_t marker; /* 最后执行位置标记 */
|
||||||
|
uint32_t boot_cnt; /* 复位计数 */
|
||||||
|
} FaultDiag;
|
||||||
|
|
||||||
|
/* 定义在 ch32v20x_it.c (.noinit 段) */
|
||||||
|
extern FaultDiag g_fault_diag;
|
||||||
|
|
||||||
|
/* main 开头调用: 打印上次现场 + 重置 + boot_cnt++ */
|
||||||
|
void fault_diag_init(void);
|
||||||
|
|
||||||
|
/* 执行轨迹标记 */
|
||||||
|
#define FAULT_MARKER(s) (g_fault_diag.marker = (s))
|
||||||
|
|
||||||
|
#define MK_MAIN_ENTRY 0xA0000001UL /* main 入口 */
|
||||||
|
#define MK_INIT_DONE 0xA0000002UL /* 初始化完成, 进主循环前 */
|
||||||
|
#define MK_LOOP_TOP 0xA0000010UL /* 主循环 while 开头 */
|
||||||
|
#define MK_UART_SRV_IN 0xA0000011UL /* uart_srv 调用前 */
|
||||||
|
#define MK_UART_SRV_OUT 0xA0000012UL /* uart_srv 返回后 */
|
||||||
|
#define MK_SIM_SPI_IN 0xA0000013UL /* sim_snap_spi 调用前 */
|
||||||
|
#define MK_POLL_BLE_IN 0xA0000014UL /* poll_dbn_ble 调用前 */
|
||||||
|
#define MK_LUP_FRAME_IN 0xA0000020UL /* lup_process_frame 入口 */
|
||||||
|
#define MK_LUP_FRAME_OUT 0xA0000021UL /* lup_process_frame 出口 */
|
||||||
|
#define MK_INGEST_IN 0xA0000030UL /* iot_sensor_ingest 入口 */
|
||||||
|
#define MK_INGEST_OUT 0xA0000031UL /* iot_sensor_ingest 出口 */
|
||||||
|
#define MK_EVT_FEED_IN 0xA0000032UL /* iot_evt_feed 入口 */
|
||||||
|
#define MK_EVT_FEED_OUT 0xA0000033UL /* iot_evt_feed 出口 */
|
||||||
|
#define MK_EVT_CB_IN 0xA0000034UL /* iot_evt_sensor_cb 入口 */
|
||||||
|
#define MK_EVT_CB_OUT 0xA0000035UL /* iot_evt_sensor_cb 出口 */
|
||||||
|
|
||||||
|
#endif /* __FAULT_DIAG_H */
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
#include "offlog.h"
|
#include "offlog.h"
|
||||||
#include "snapshot.h"
|
#include "snapshot.h"
|
||||||
|
#include "fault_diag.h"
|
||||||
#include "ch32v20x_iwdg.h"
|
#include "ch32v20x_iwdg.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -121,12 +122,14 @@ static void iot_evt_enqueue(uint8_t type, uint8_t ch, uint32_t value) {
|
|||||||
/* 沿检测: 每收到一帧有效 0xC0 调用一次 (两条消费路径都要喂) */
|
/* 沿检测: 每收到一帧有效 0xC0 调用一次 (两条消费路径都要喂) */
|
||||||
static void iot_evt_feed(const LUP_SensorReport *sr) {
|
static void iot_evt_feed(const LUP_SensorReport *sr) {
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
FAULT_MARKER(MK_EVT_FEED_IN);
|
||||||
if (!_evt_prev_valid) { // 首帧: 只建快照 (上电已有车不算进入沿)
|
if (!_evt_prev_valid) { // 首帧: 只建快照 (上电已有车不算进入沿)
|
||||||
for (i = 0; i < LUP_COIL_COUNT; i++) {
|
for (i = 0; i < LUP_COIL_COUNT; i++) {
|
||||||
_evt_prev_car[i] = (i < sr->coil_count) ? sr->coils[i].car_state : 0;
|
_evt_prev_car[i] = (i < sr->coil_count) ? sr->coils[i].car_state : 0;
|
||||||
_evt_prev_loop[i] = (i < sr->coil_count) ? sr->coils[i].loop_state : 0;
|
_evt_prev_loop[i] = (i < sr->coil_count) ? sr->coils[i].loop_state : 0;
|
||||||
}
|
}
|
||||||
_evt_prev_valid = 1;
|
_evt_prev_valid = 1;
|
||||||
|
FAULT_MARKER(MK_EVT_FEED_OUT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (i = 0; i < sr->coil_count && i < LUP_COIL_COUNT; i++) {
|
for (i = 0; i < sr->coil_count && i < LUP_COIL_COUNT; i++) {
|
||||||
@@ -154,6 +157,7 @@ static void iot_evt_feed(const LUP_SensorReport *sr) {
|
|||||||
_evt_prev_car[i] = cs->car_state;
|
_evt_prev_car[i] = cs->car_state;
|
||||||
_evt_prev_loop[i] = cs->loop_state;
|
_evt_prev_loop[i] = cs->loop_state;
|
||||||
}
|
}
|
||||||
|
FAULT_MARKER(MK_EVT_FEED_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 统一帧摄取: 事件沿检测 + 刷新 loop_data 缓存 (单一数据源)
|
/* 统一帧摄取: 事件沿检测 + 刷新 loop_data 缓存 (单一数据源)
|
||||||
@@ -162,10 +166,12 @@ static void iot_evt_feed(const LUP_SensorReport *sr) {
|
|||||||
帧竞争的概率实测 <2% → loop_data 发布 34s 前的陈旧快照(车走后 iscar
|
帧竞争的概率实测 <2% → loop_data 发布 34s 前的陈旧快照(车走后 iscar
|
||||||
仍 true), 且陈旧 diff 锁死 fast_mode 300ms 刷屏。事件与快照必须同源! */
|
仍 true), 且陈旧 diff 锁死 fast_mode 300ms 刷屏。事件与快照必须同源! */
|
||||||
static void iot_sensor_ingest(const LUP_SensorReport *sr) {
|
static void iot_sensor_ingest(const LUP_SensorReport *sr) {
|
||||||
|
FAULT_MARKER(MK_INGEST_IN);
|
||||||
iot_evt_feed(sr); // 事件沿检测
|
iot_evt_feed(sr); // 事件沿检测
|
||||||
memcpy(&_cached_sr, sr, sizeof(LUP_SensorReport)); // 刷新 loop_data 快照
|
memcpy(&_cached_sr, sr, sizeof(LUP_SensorReport)); // 刷新 loop_data 快照
|
||||||
_cached_sr_valid = 1;
|
_cached_sr_valid = 1;
|
||||||
snap_enqueue(sr); /* 快照入队: 中断安全, 主循环 snap_flush 落盘 */
|
snap_enqueue(sr); /* 快照入队: 中断安全, 主循环 snap_flush 落盘 */
|
||||||
|
FAULT_MARKER(MK_INGEST_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* lup 传感回调: uart_srv 消费路径的帧从这里喂入 (lup_process_frame 已过校验) */
|
/* lup 传感回调: uart_srv 消费路径的帧从这里喂入 (lup_process_frame 已过校验) */
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "CONFIG.h"
|
#include "CONFIG.h"
|
||||||
#include "loop_uart_proto.h"
|
#include "loop_uart_proto.h"
|
||||||
#include "cmcng.h"
|
#include "cmcng.h"
|
||||||
|
#include "fault_diag.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/*===========================================================================
|
/*===========================================================================
|
||||||
@@ -681,7 +682,9 @@ void lup_process_frame(const uint8_t *pkg, uint16_t len)
|
|||||||
int csr;
|
int csr;
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
if (len < 6) return;
|
FAULT_MARKER(MK_LUP_FRAME_IN);
|
||||||
|
|
||||||
|
if (len < 6) { FAULT_MARKER(MK_LUP_FRAME_OUT); return; }
|
||||||
|
|
||||||
// Debug: print raw
|
// Debug: print raw
|
||||||
PRINT("LUP Rx:");
|
PRINT("LUP Rx:");
|
||||||
@@ -712,4 +715,5 @@ void lup_process_frame(const uint8_t *pkg, uint16_t len)
|
|||||||
|
|
||||||
// Try matching with pending command
|
// Try matching with pending command
|
||||||
lup_cmd_on_response(pkg, len);
|
lup_cmd_on_response(pkg, len);
|
||||||
|
FAULT_MARKER(MK_LUP_FRAME_OUT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "tcp_json_srv.h"
|
#include "tcp_json_srv.h"
|
||||||
#include "loop_uart_proto.h"
|
#include "loop_uart_proto.h"
|
||||||
#include "iot_mqtt_srv.h"
|
#include "iot_mqtt_srv.h"
|
||||||
|
#include "fault_diag.h"
|
||||||
|
|
||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* GLOBAL TYPEDEFS
|
* GLOBAL TYPEDEFS
|
||||||
@@ -300,6 +301,7 @@ void Main_Circulation(void)
|
|||||||
uint32_t _counter = 0;
|
uint32_t _counter = 0;
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
|
FAULT_MARKER(MK_LOOP_TOP);
|
||||||
TMOS_SystemProcess();
|
TMOS_SystemProcess();
|
||||||
|
|
||||||
if(g_net_state.flag < 2)
|
if(g_net_state.flag < 2)
|
||||||
@@ -336,10 +338,13 @@ void Main_Circulation(void)
|
|||||||
WCHNET_HandleGlobalInt();
|
WCHNET_HandleGlobalInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FAULT_MARKER(MK_UART_SRV_IN);
|
||||||
uart_srv();
|
uart_srv();
|
||||||
|
FAULT_MARKER(MK_UART_SRV_OUT);
|
||||||
snap_flush(); /* 快照落盘: 无条件挂主循环 (原在 iot_enable 分支, TCP 模式不落盘) */
|
snap_flush(); /* 快照落盘: 无条件挂主循环 (原在 iot_enable 分支, TCP 模式不落盘) */
|
||||||
|
FAULT_MARKER(MK_SIM_SPI_IN);
|
||||||
sim_snap_spi(); /* 实验第一步(2026-08-13): 模拟快照区 SPI 写密度, 定位复位触发源 */
|
sim_snap_spi(); /* 实验第一步(2026-08-13): 模拟快照区 SPI 写密度, 定位复位触发源 */
|
||||||
|
FAULT_MARKER(MK_POLL_BLE_IN);
|
||||||
poll_dbn_ble();
|
poll_dbn_ble();
|
||||||
|
|
||||||
if (g_sub_code_enable.iot_enable) {
|
if (g_sub_code_enable.iot_enable) {
|
||||||
@@ -374,6 +379,7 @@ int main(void)
|
|||||||
/* 诊断: 复位原因寄存器 (bit31=IWDG bit30=WWDG bit29=LPWR bit26=NRST bit25=POR bit24=软复位)
|
/* 诊断: 复位原因寄存器 (bit31=IWDG bit30=WWDG bit29=LPWR bit26=NRST bit25=POR bit24=软复位)
|
||||||
每次上电必打, 区分看门狗死锁 vs 掉电 vs 外部复位 */
|
每次上电必打, 区分看门狗死锁 vs 掉电 vs 外部复位 */
|
||||||
PRINT("RST_REASON: 0x%08lx\n", (unsigned long)OFFLOG_RCC_RSTSCKR);
|
PRINT("RST_REASON: 0x%08lx\n", (unsigned long)OFFLOG_RCC_RSTSCKR);
|
||||||
|
fault_diag_init(); /* 打印上次复位现场 (HardFault mcause/mepc + 执行轨迹 marker) */
|
||||||
PRINT("%s\n", VER_LIB);
|
PRINT("%s\n", VER_LIB);
|
||||||
PRINT("SystemCoreClock:%d\n", SystemCoreClock);
|
PRINT("SystemCoreClock:%d\n", SystemCoreClock);
|
||||||
/* ===== 实验: 禁用全部 Flash/SPI 操作 (2026-08-12) =====
|
/* ===== 实验: 禁用全部 Flash/SPI 操作 (2026-08-12) =====
|
||||||
|
|||||||
@@ -180,6 +180,14 @@ SECTIONS
|
|||||||
PROVIDE( _ebss = .);
|
PROVIDE( _ebss = .);
|
||||||
} >RAM AT>FLASH
|
} >RAM AT>FLASH
|
||||||
|
|
||||||
|
/* 故障诊断现场: 复位不清零 (startup 只清 .bss, 不碰 .noinit) */
|
||||||
|
.noinit (NOLOAD) :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.noinit*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
} >RAM
|
||||||
|
|
||||||
PROVIDE( _end = _ebss);
|
PROVIDE( _end = _ebss);
|
||||||
PROVIDE( end = . );
|
PROVIDE( end = . );
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user