修复 P2 全部 10 项:错误传播/竞态/门控/文档失真
待办池 docs/待处理内容.md §3.2 的 10 个 P2 全部闭环;逐项落代码 + 注释说明。 P2-1 cfg_file: 受管键行超 CFG_LINE_TEST_MAX 拒绝保存(防重复键行) P2-2 ch395f: DMA 失败传播状态 + 锁存通道故障 + 读不拷不可信数据 P2-7 net_select: LISTENING 不再恒判可读(消除 select 忙轮询地雷) P2-8 time_sync: 核实 L3 pending 改写已闭环(net_recv_to 分片 + 总预算) P2-9 sys_log: 挂载改幂等 + 卷锁,defaultTask 2s 重试建 /log P2-10 sys_log: s_read_raw 纳入卷锁 try(读者/写者 unlink 互斥) P2-11 net_task: RECONFIG 改 osEventFlagsWait 原子取走 + 清除 P2-12 app_cfg: APP_*_ENABLE 如实标注为未接线占位,删“开关”说法 P2-13 freertos: 产品构建 vTaskDelete(testTask) 回收 8KB CCM 栈 P2-15 download.bat: 先删旧日志 + 增“未产出日志”判定 P2-13 用 USER CODE 区回收而非生成区 #if 门控:生成区改动会被 CubeMX 重生成覆盖,本方案重生成不丢。P2-12 因产品定义 B0 未定、APP_NET_ENABLE 无单一接线点,按修复建议备选(删“开关”说法)处理。 验证:@build 0 错 0 警;主机单测 watchdog 39/39 + sys_clock 100/100 + cfg_file 149/149(新增 TC-CFG-044);pre-commit --all-files 17/17。
This commit is contained in:
+16
-5
@@ -1,11 +1,18 @@
|
||||
/*
|
||||
* 模块名称:Application Configuration
|
||||
* 模块功能:应用层全局配置宏,控制功能模块的编译开关
|
||||
* 模块功能:应用层全局配置宏(**预留占位,当前均未接线**)
|
||||
* 适用平台:STM32F407ZGTx
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-19
|
||||
* 修改记录:
|
||||
* v1.0 2026-07-19 王建锋 创建初始版本
|
||||
* v1.1 2026-10-04 王建锋 P2-12:下列宏此前被表述为"功能模块使能开关",但全库
|
||||
* 零代码引用(grep 仅本文件有定义)——"开关名存实亡"会
|
||||
* 误导评审者以为模块可裁剪。现如实标注为**预留占位**:
|
||||
* 当前**不门控任何代码**,各模块一律编译进产品。是否/
|
||||
* 如何接线取决于产品定义(待处理内容.md §2 阶段 B0),
|
||||
* 确认后再在真实初始化点(如 app_main.c 的 rs485_init /
|
||||
* tpafe5160_init)加门控;在此之前**不要**把它们当开关用。
|
||||
*/
|
||||
|
||||
#ifndef APP_CFG_H
|
||||
@@ -16,14 +23,18 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 系统时钟源选择
|
||||
* 0 - DWT CYCCNT(默认,0 外设开销)
|
||||
* 1 - TIM2(32-bit 定时器,独立于 DWT)
|
||||
* 系统时钟源选择(**预留占位,未接线**)
|
||||
* 0 - DWT CYCCNT(当前实现:console_log.c 直接使用 DWT,未读本宏)
|
||||
* 1 - TIM2(未实现)
|
||||
* 说明:本宏当前无任何消费点;实际的"与时间源无关硬上界"由 console_log.c 内联
|
||||
* 使能 DWT 实现,不经过本宏。
|
||||
*/
|
||||
#define APP_SYS_TIME_SRC 0
|
||||
|
||||
/*
|
||||
* 功能模块使能开关
|
||||
* 功能模块"预留开关"(**当前均未接线,不门控任何代码**)
|
||||
* 三个宏一律保持定义(= 模块编译进产品);它们**不产生任何编译期效果**,
|
||||
* 仅作为产品定义(B0)落定后接入真实门控的占位锚点。
|
||||
*/
|
||||
#define APP_NET_ENABLE
|
||||
#define APP_ADC_ENABLE
|
||||
|
||||
+78
-3
@@ -18,6 +18,9 @@
|
||||
* 回工厂默认 → 基线粘住 → 用户配置销毁链);save 输出上限
|
||||
* 由 CFG_OUT_MAX 收紧至 CFG_FILE_MAX_LEN;s_write_doc 写盘
|
||||
* 唯一闸口拒写超长文档;load/poll 超长处置补自检留痕
|
||||
* v1.3 2026-10-04 王建锋 P2-1:受管键行超 CFG_LINE_TEST_MAX 时拒绝保存——
|
||||
* 原实现 Pass1 静默跳过超长行,Pass2 遂在段头补写新 KV 且
|
||||
* 原文超长行原样保留 → 同一键出现两行(旧值残留、静默截断)
|
||||
* 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save,见 P1-3;
|
||||
* 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证
|
||||
* (见《配置文件设计说明》D15 A+ 方案)
|
||||
@@ -822,13 +825,67 @@ static int s_surgical(char *p_in, size_t in_len, char *p_out, size_t out_cap, si
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:判断一行(非修改式)的键名是否为指定段的受管键
|
||||
* 入口参数:p_line - 行文本(只读) const char *
|
||||
* i_sec - 段索引 int
|
||||
* 返回值:1 - 是受管键;0 - 不是(无 '=' 或键名不匹配) int
|
||||
* 限定条件:仅比较 '=' 前的键名(大小写无关、剥尾部空白),不解析值
|
||||
* 函数说明:P2-1 用于"超长行是否落在受管键上"的判定(值可能很长,无法拷入定长缓冲);
|
||||
* 非修改式,故可安全用于仍供 Pass2 原样保留的行
|
||||
*/
|
||||
static int s_line_key_managed(const char *p_line, int i_sec) {
|
||||
const char *p_eq = strchr(p_line, '=');
|
||||
size_t kl;
|
||||
int k;
|
||||
|
||||
if (p_eq == NULL) {
|
||||
return 0;
|
||||
}
|
||||
kl = (size_t)(p_eq - p_line);
|
||||
while (kl > 0u && (p_line[kl - 1u] == ' ' || p_line[kl - 1u] == '\t')) {
|
||||
kl--;
|
||||
}
|
||||
for (k = 0; k < (int)s_sections[i_sec].key_count; k++) {
|
||||
const char *p_name = s_sections[i_sec].p_keys[k].name;
|
||||
size_t i = 0u;
|
||||
int eq = 1;
|
||||
|
||||
while (i < kl) {
|
||||
char ca = p_line[i];
|
||||
char cb = p_name[i];
|
||||
|
||||
if (ca >= 'A' && ca <= 'Z') {
|
||||
ca = (char)(ca + ('a' - 'A'));
|
||||
}
|
||||
if (cb >= 'A' && cb <= 'Z') {
|
||||
cb = (char)(cb + ('a' - 'A'));
|
||||
}
|
||||
if (cb == '\0' || ca != cb) {
|
||||
eq = 0;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (eq != 0 && p_name[i] == '\0') {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:保存 Pass 1——定位段头行号与各受管键行号
|
||||
* 入口参数:p_in - 原文本(可修改) char *
|
||||
* 返回值:无
|
||||
* 返回值:0 - 成功;-1 - 受管键行超 CFG_LINE_TEST_MAX(拒绝保存,见 P2-1) int
|
||||
* 限定条件:行末被 NUL 化且不复原——调用方须传一次性副本
|
||||
* 函数说明:P2-1(2026-10-04):受管键行超过 CFG_LINE_TEST_MAX 时**不能**静默跳过
|
||||
* ——Pass2 会因该行未登记而在段头补写新 KV,同时原文超长行原样保留,导致
|
||||
* 同一键出现两行(旧值残留 + 新值,解析取谁取决于行序)。故此处显式拒绝:
|
||||
* 返回 -1,调用方中止保存、原文件不动、自检留痕。非受管键的超长行照旧跳过
|
||||
* (属未知内容,Pass2 原样保留,无冲突)。
|
||||
*/
|
||||
static void s_surgical_pass1(char *p_in) {
|
||||
static int s_surgical_pass1(char *p_in) {
|
||||
char *p = p_in;
|
||||
int line_no = 0;
|
||||
int i;
|
||||
@@ -883,8 +940,16 @@ static void s_surgical_pass1(char *p_in) {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i_cur >= 0) {
|
||||
/* 超长行(> CFG_LINE_TEST_MAX):若其键名为本段受管键 → 拒绝保存(P2-1);
|
||||
* 否则按未知内容放过(Pass2 原样保留) */
|
||||
if (s_line_key_managed(p_line, i_cur) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
line_no++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1091,7 +1156,17 @@ int cfg_file_save(void) {
|
||||
}
|
||||
(void)memcpy(p_in1, p_in, in_len + 1u);
|
||||
|
||||
s_surgical_pass1((char *)p_in1);
|
||||
if (s_surgical_pass1((char *)p_in1) != 0) {
|
||||
/* P2-1:受管键行超 CFG_LINE_TEST_MAX → 中止保存(原文件不动)。此类超长行
|
||||
* 只可能来自手工编辑(代码生成的受管 KV 行恒 <127B);若强行外科保存会
|
||||
* 产生重复键行,故宁可拒存并留痕,也不静默写坏。 */
|
||||
cfg_store_free(p_in1);
|
||||
cfg_store_free(p_in);
|
||||
DBG_ERROR("save: managed key line > %uB, aborted (file untouched)",
|
||||
(unsigned)CFG_LINE_TEST_MAX);
|
||||
syslog_post(SYSLOG_SELFTEST, "cfg save line too long");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cfg_store_free(p_in1);
|
||||
|
||||
|
||||
+45
-4
@@ -16,6 +16,12 @@
|
||||
* v1.3 2026-10-02 王建锋 新增 sys_log_evidence():把看门狗停喂现场
|
||||
* 证据落 /log/wdev.log(掉电不丢)+ flush 诱导
|
||||
* (round 7 纯取证,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)
|
||||
* v1.4 2026-10-04 王建锋 P2-9:syslog_mcu_mounted() 改为幂等 + 卷锁保护;
|
||||
* defaultTask 每 2s 复查,建 /log 曾失败时自动重试
|
||||
* (原"仅调用一次"→ 失败后 drain 永久 no-op)。
|
||||
* P2-10:s_read_raw() 纳入卷锁 try 语义,与写者
|
||||
* s_append 的"容量满 unlink 重建"互斥,消除
|
||||
* "UI 读记录(FA_READ) 时文件被 unlink"竞态窗口。
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -49,6 +55,7 @@ static volatile uint8_t s_preboot_cnt; /* 数组条目数 */
|
||||
static osMessageQueueId_t s_queue; /* 运行期消息队列(syslog_drain 首次调用时惰性创建) */
|
||||
static const osMessageQueueAttr_t s_queue_attr = { .name = "syslog" };
|
||||
static volatile uint8_t s_fs_ready; /* 卷挂载 + /log 目录就绪后置位 */
|
||||
static volatile uint8_t s_mount_fail_reported; /* P2-9:建目录失败日志去重(成功复位) */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
@@ -153,18 +160,28 @@ static int s_append(syslog_id_t id, const char *desc) {
|
||||
* 入口参数:id - 日志 ID syslog_id_t
|
||||
* idx - 记录序号(0 起) uint32_t
|
||||
* p_rec- 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t *
|
||||
* 返回值:0 - 成功;-1 - 越界/读失败 int
|
||||
* 返回值:0 - 成功;-1 - 越界/读失败/卷忙 int
|
||||
* 函数说明:P2-10(2026-10-04):读者纳入卷锁(try),与写者 s_append 的
|
||||
* "容量满 unlink 重建"互斥——原实现读时无锁,写者 unlink 与读者
|
||||
* open/read 可交错,读到的可能是半删除态文件。try 语义(timeout=0):
|
||||
* 本路径可由 uiTask 调用,uiTask 在 required_mask 内、须每 100ms 喂
|
||||
* 心跳,绝不能阻塞等锁;busy = 本次读失败(UI 下轮刷新重读,非故障)。
|
||||
*/
|
||||
static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
|
||||
FIL f;
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
int rc;
|
||||
|
||||
if (p_rec == NULL || idx >= syslog_count(id)) {
|
||||
return -1;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -1; /* 卷被占用:本次读失败(非故障,下轮重读) */
|
||||
}
|
||||
fr = f_open(&f, s_paths[id], FA_READ);
|
||||
if (fr != FR_OK) {
|
||||
vol_lock_release();
|
||||
return -1;
|
||||
}
|
||||
fr = f_lseek(&f, (FSIZE_t)idx * SYSLOG_REC_SIZE);
|
||||
@@ -172,7 +189,9 @@ static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
|
||||
fr = f_read(&f, p_rec, SYSLOG_REC_SIZE, &br);
|
||||
}
|
||||
(void)f_close(&f);
|
||||
return (fr == FR_OK && br == SYSLOG_REC_SIZE) ? 0 : -1;
|
||||
rc = (fr == FR_OK && br == SYSLOG_REC_SIZE) ? 0 : -1;
|
||||
vol_lock_release();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -194,20 +213,42 @@ int syslog_mcu_init(void) {
|
||||
* 函数功能:卷挂载成功后的一次性处理(建 /log 目录 + 写"上电/重启"分类记录)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:defaultTask f_mount/f_mkfs 成功且置位 SYS_EVT_STORAGE_READY 前调用一次
|
||||
* 限定条件:defaultTask f_mount/f_mkfs 成功后调用;**可重复调用(幂等)**——
|
||||
* 产品运行期 defaultTask 每 2s 复查一次(见下 P2-9 说明)
|
||||
* 函数说明:P2-9(2026-10-04):原实现"仅调用一次",若此处建 /log 失败则
|
||||
* s_fs_ready 恒 0 → syslog_drain 永久 no-op(运行期日志与自检全部
|
||||
* 滞留/丢弃),且无任何重试。现改为幂等:s_fs_ready 已置位则立即
|
||||
* 返回;否则重试建目录。defaultTask 在 STORAGE_READY 置位后每 2s
|
||||
* 调用本函数——卷已就绪但日志后端未就绪时自动补建目录。
|
||||
* FS 操作纳入卷锁 try:运行期重试时 ftpTask 可能正操作卷,try 失败
|
||||
* = 本轮顺延(下轮再试),绝不阻塞(本任务 = 看门狗喂狗点)。
|
||||
*/
|
||||
void syslog_mcu_mounted(void) {
|
||||
FILINFO finfo;
|
||||
uint32_t last = 0u;
|
||||
const char *desc = "power on";
|
||||
|
||||
if (s_fs_ready != 0u) {
|
||||
return; /* 幂等:已就绪 */
|
||||
}
|
||||
/* 卷锁 try:运行期重试时与 ftpTask 卷访问串行;busy = 本轮顺延 */
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return;
|
||||
}
|
||||
if (f_stat("/log", &finfo) != FR_OK) {
|
||||
FRESULT fr = f_mkdir("/log");
|
||||
if (fr != FR_OK && fr != FR_EXIST) {
|
||||
DBG_ERROR("mkdir /log fail (%d)", (int)fr);
|
||||
/* 失败日志去重:持续失败只报第一次(防 2s 周期刷屏),成功后复位 */
|
||||
if (s_mount_fail_reported == 0u) {
|
||||
s_mount_fail_reported = 1u;
|
||||
DBG_ERROR("mkdir /log fail (%d), will retry", (int)fr);
|
||||
}
|
||||
vol_lock_release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
vol_lock_release();
|
||||
s_mount_fail_reported = 0u;
|
||||
s_fs_ready = 1u;
|
||||
|
||||
/* 上电/重启分类:末条 epoch 距 now < SYSLOG_REBOOT_WINDOW_S → 重启,否则上电 */
|
||||
|
||||
@@ -217,6 +217,13 @@ void default_task_func(void *argument) {
|
||||
syslog_post(SYSLOG_SELFTEST, "filesystem fault");
|
||||
}
|
||||
}
|
||||
/* P2-9:卷已就绪(STORAGE_READY)但日志后端未就绪(建 /log 曾失败,
|
||||
* s_fs_ready 恒 0 → syslog_drain 永久 no-op)→ 2s 周期重试(幂等;
|
||||
* 就绪后 syslog_mcu_mounted 立即返回,仅一次 volatile 读)。 */
|
||||
if (sys_evtHandle != NULL
|
||||
&& (osEventFlagsGet(sys_evtHandle) & SYS_EVT_STORAGE_READY) != 0u) {
|
||||
syslog_mcu_mounted();
|
||||
}
|
||||
cfg_file_poll();
|
||||
#endif
|
||||
}
|
||||
|
||||
+23
-18
@@ -240,26 +240,31 @@ void net_task_func(void const *arg) {
|
||||
task_wd_beat(TASK_WD_NET);
|
||||
|
||||
/* UI 保存网口配置后请求立即应用:在此重建网络(新 IP/掩码/网关/MAC)。
|
||||
* 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。 */
|
||||
if (sys_evtHandle != NULL
|
||||
&& (osEventFlagsGet(sys_evtHandle) & SYS_EVT_NET_RECONFIG) != 0u) {
|
||||
(void)osEventFlagsClear(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
DBG_INFO("netTask: apply new net config (re-init)");
|
||||
if (net_stack_bring_up() == 0) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
|
||||
* 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。
|
||||
* P2-11(2026-10-04):用 osEventFlagsWait(timeout=0) 原子"取走并清除"RECONFIG
|
||||
* 位,替换原 Get+Clear 两步;原写法在 Get 与 Clear 之间若有生产者在同一位上
|
||||
* Set,该次请求会被 Clear 一并吞掉(丢事件窗口)。 */
|
||||
if (sys_evtHandle != NULL) {
|
||||
uint32_t evt = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_RECONFIG,
|
||||
osFlagsWaitAny, 0U);
|
||||
if ((evt & osFlagsError) == 0u) {
|
||||
DBG_INFO("netTask: apply new net config (re-init)");
|
||||
if (net_stack_bring_up() == 0) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
|
||||
s_reinit_retry_at = 0U;
|
||||
} else {
|
||||
/* P1-8(2026-10-02):重建失败 → 自检留痕 + 30s 延迟重试(重事件位),
|
||||
* 新配置不再静默丢失(原实现仅一条 DBG_ERROR 后丢弃) */
|
||||
DBG_ERROR("netTask: re-init FAIL, retry in 30s");
|
||||
(void)syslog_post(SYSLOG_SELFTEST, "net reinit fail");
|
||||
s_reinit_retry_at = (uint32_t)HAL_GetTick() + 30000U;
|
||||
}
|
||||
} else if ((s_reinit_retry_at != 0U)
|
||||
&& ((int32_t)((uint32_t)HAL_GetTick() - s_reinit_retry_at) >= 0)) {
|
||||
/* P1-8:30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */
|
||||
s_reinit_retry_at = 0U;
|
||||
} else {
|
||||
/* P1-8(2026-10-02):重建失败 → 自检留痕 + 30s 延迟重试(重事件位),
|
||||
* 新配置不再静默丢失(原实现仅一条 DBG_ERROR 后丢弃) */
|
||||
DBG_ERROR("netTask: re-init FAIL, retry in 30s");
|
||||
(void)syslog_post(SYSLOG_SELFTEST, "net reinit fail");
|
||||
s_reinit_retry_at = (uint32_t)HAL_GetTick() + 30000U;
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
}
|
||||
} else if ((sys_evtHandle != NULL) && (s_reinit_retry_at != 0U)
|
||||
&& ((int32_t)((uint32_t)HAL_GetTick() - s_reinit_retry_at) >= 0)) {
|
||||
/* P1-8:30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */
|
||||
s_reinit_retry_at = 0U;
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
}
|
||||
|
||||
net_poll();
|
||||
|
||||
+42
-22
@@ -10,6 +10,9 @@
|
||||
* 2026-07-18 王建锋 改进write_send_buf/read_recv_buf使用SPI2 DMA批量传输
|
||||
* 2026-10-02 王建锋 E-34:3 处字节级 SPI 调用由 HAL_MAX_DELAY 改有限超时(100ms)
|
||||
* + 返回值检查 + 故障锁存(沿程只记一条日志);原 SPI 异常时调用方永久挂死
|
||||
* 2026-10-04 王建锋 P2-2:ch395f_spi_dma_transfer 由 void 改为返回状态(0 成功/-1 失败),
|
||||
* DMA 初始化失败/超时中止均锁存通道故障;read_recv_buf 在 DMA 失败时不再把
|
||||
* rx_buf 内容拷入调用方缓冲(改零填充),消除"失败当成功"的静默数据损坏
|
||||
*/
|
||||
|
||||
/* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */
|
||||
@@ -40,7 +43,7 @@
|
||||
* 静态变量定义区 - DMA传输相关
|
||||
*/
|
||||
static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */
|
||||
static uint8_t s_spi_byte_fault = 0U; /* E-34:字节级SPI故障锁存(超时/出错置位,MCU复位清除) */
|
||||
static uint8_t s_spi_fault = 0U; /* E-34/P2-2:SPI 通道故障锁存(字节级或 DMA 级超时/出错置位,MCU复位清除) */
|
||||
|
||||
__attribute__((aligned(4)))
|
||||
static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */
|
||||
@@ -52,8 +55,8 @@ static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA接收缓冲区 */
|
||||
*/
|
||||
static void ch395f_spi_begin(void);
|
||||
static void ch395f_spi_end(void);
|
||||
static void ch395f_spi_dma_transfer(uint16_t len);
|
||||
static void ch395f_spi_byte_fault_set(void);
|
||||
static int ch395f_spi_dma_transfer(uint16_t len);
|
||||
static void ch395f_spi_fault_set(const char *kind);
|
||||
|
||||
/*
|
||||
* 私有函数定义区
|
||||
@@ -82,31 +85,34 @@ static void ch395f_spi_end(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:锁存一次字节级SPI故障(E-34:仅首条故障记日志)
|
||||
* 入口参数:无
|
||||
* 函数功能:锁存一次 SPI 通道故障(E-34/P2-2:仅首条故障记日志)
|
||||
* 入口参数:kind - 故障类别标签("byte"/"dma-hal"/"dma-timeout") const char*
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:任一字节级SPI调用超时/失败时调用;置位 s_spi_byte_fault 并记一条日志,
|
||||
* 函数说明:任一字节级或 DMA 级 SPI 调用超时/失败时调用;置位 s_spi_fault 并记一条日志,
|
||||
* 后续故障静默(防串口刷屏);上层经 ch395f_spi_byte_fault() 查询
|
||||
*/
|
||||
static void ch395f_spi_byte_fault_set(void) {
|
||||
if (s_spi_byte_fault == 0U) {
|
||||
s_spi_byte_fault = 1U;
|
||||
DBG_ERROR("SPI byte op fail, channel latched");
|
||||
static void ch395f_spi_fault_set(const char *kind) {
|
||||
if (s_spi_fault == 0U) {
|
||||
s_spi_fault = 1U;
|
||||
DBG_ERROR("SPI channel fault latched: %s", kind);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:DMA全双工传输并等待完成
|
||||
* 入口参数:len - 传输长度(字节) uint16_t 1 - CH395F_DMA_BUF_SIZE
|
||||
* 返回值:0 - 传输完成且无错误 -1 - 传输失败(HAL 启动失败 / 超时中止)
|
||||
* 限定条件:1. s_spi2_dma_tx_buf中已填充待发送数据
|
||||
* 2. 需确保DMA传输缓冲区已对齐到4字节边界
|
||||
* 函数说明:1. 发送s_spi2_dma_tx_buf内容,同时接收数据存入s_spi2_dma_rx_buf
|
||||
* 2. 阻塞等待DMA完成回调置标志位
|
||||
* 3. 超时(1秒)时强制中止SPI DMA并打印错误信息
|
||||
* 4. 传输完成后检查SPI错误码并清除溢出标志
|
||||
* 5. P2-2(2026-10-04):失败路径锁存通道故障并返回 -1,调用方据此
|
||||
* 判定 rx_buf 内容不可信,不再"失败当成功"继续消费
|
||||
*/
|
||||
static void ch395f_spi_dma_transfer(uint16_t len) {
|
||||
static int ch395f_spi_dma_transfer(uint16_t len) {
|
||||
HAL_StatusTypeDef status;
|
||||
uint32_t tickstart;
|
||||
volatile uint32_t loop_cnt = 0U;
|
||||
@@ -119,7 +125,8 @@ static void ch395f_spi_dma_transfer(uint16_t len) {
|
||||
(int)hspi2.State,
|
||||
hspi2.ErrorCode,
|
||||
(int)hspi2.hdmarx->State);
|
||||
return;
|
||||
ch395f_spi_fault_set("dma-hal");
|
||||
return -1;
|
||||
}
|
||||
|
||||
tickstart = HAL_GetTick();
|
||||
@@ -142,7 +149,8 @@ static void ch395f_spi_dma_transfer(uint16_t len) {
|
||||
SPI2->CR1, SPI2->CR2, SPI2->SR);
|
||||
/* 强制中止SPI2 DMA状态,避免死锁 */
|
||||
HAL_SPI_Abort(&hspi2);
|
||||
return;
|
||||
ch395f_spi_fault_set("dma-timeout");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +160,8 @@ static void ch395f_spi_dma_transfer(uint16_t len) {
|
||||
hspi2.ErrorCode, (int)hspi2.State);
|
||||
__HAL_SPI_CLEAR_OVRFLAG(&hspi2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -200,7 +210,7 @@ uint8_t ch395f_write_cmd(uint8_t cmd) {
|
||||
/* 通过SPI发送命令并接收回显(E-34:有限超时+返回值检查;失败返回0xFF占位并锁存故障) */
|
||||
if (HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1,
|
||||
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
|
||||
ch395f_spi_byte_fault_set();
|
||||
ch395f_spi_fault_set("byte");
|
||||
}
|
||||
return rx_data;
|
||||
}
|
||||
@@ -217,7 +227,7 @@ uint8_t ch395f_write_data(uint8_t data) {
|
||||
/* 通过SPI发送数据并接收回显(E-34:有限超时+返回值检查) */
|
||||
if (HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1,
|
||||
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
|
||||
ch395f_spi_byte_fault_set();
|
||||
ch395f_spi_fault_set("byte");
|
||||
}
|
||||
return rx_data;
|
||||
}
|
||||
@@ -235,20 +245,20 @@ uint8_t ch395f_read_data(void) {
|
||||
/* 发送0xFF作为时钟源,读取芯片返回数据(E-34:有限超时+返回值检查) */
|
||||
if (HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1,
|
||||
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
|
||||
ch395f_spi_byte_fault_set();
|
||||
ch395f_spi_fault_set("byte");
|
||||
}
|
||||
return rx_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询字节级SPI故障锁存(E-34)
|
||||
* 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误
|
||||
* 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2)
|
||||
* 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级)
|
||||
* 限定条件:无
|
||||
* 函数说明:锁存式(不自动清除,MCU 复位前一直有效);上层据此判定
|
||||
* "SPI 通道异常"(区别于"无芯片"/"PHY 未链接");仅首条故障记日志
|
||||
*/
|
||||
uint8_t ch395f_spi_byte_fault(void) {
|
||||
return s_spi_byte_fault;
|
||||
return s_spi_fault;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1173,9 +1183,10 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
|
||||
/* 数据同步屏障:确保memcpy写入对DMA可见 */
|
||||
__DSB();
|
||||
|
||||
/* CS低 → DMA批量传输 → CS高 */
|
||||
/* CS低 → DMA批量传输 → CS高(P2-2:失败已由 transfer 锁存通道故障并记日志,
|
||||
* 调用方经 ch395f_spi_byte_fault() 查询;此处无后续处理,失败即放弃本次发送) */
|
||||
ch395f_spi_begin();
|
||||
ch395f_spi_dma_transfer(total);
|
||||
(void)ch395f_spi_dma_transfer(total);
|
||||
ch395f_spi_end();
|
||||
}
|
||||
|
||||
@@ -1193,6 +1204,7 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
|
||||
*/
|
||||
void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
|
||||
uint16_t total;
|
||||
int rc;
|
||||
|
||||
/* 检查输入参数合法性 */
|
||||
if (p_data == NULL) {
|
||||
@@ -1221,9 +1233,17 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
|
||||
|
||||
/* CS低 → DMA全双工传输(发送命令头 + 接收数据) → CS高 */
|
||||
ch395f_spi_begin();
|
||||
ch395f_spi_dma_transfer(total);
|
||||
rc = ch395f_spi_dma_transfer(total);
|
||||
ch395f_spi_end();
|
||||
|
||||
/* P2-2(2026-10-04):DMA 失败时 rx_buf 内容不可信,禁止拷入调用方缓冲
|
||||
* (否则把总线残留/半截数据当有效载荷 → 静默数据损坏);改零填充并返回,
|
||||
* 调用方据 ch395f_spi_byte_fault() 判定通道异常。 */
|
||||
if (rc != 0) {
|
||||
memset(p_data, 0, len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 从DMA接收缓冲区偏移4处拷贝有效数据到用户缓冲区 */
|
||||
memcpy(p_data, &s_spi2_dma_rx_buf[4], len);
|
||||
}
|
||||
|
||||
@@ -302,8 +302,8 @@ uint8_t ch395f_write_data(uint8_t data);
|
||||
uint8_t ch395f_read_data(void);
|
||||
|
||||
/*
|
||||
* 函数功能:查询字节级SPI故障锁存(E-34)
|
||||
* 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误
|
||||
* 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2)
|
||||
* 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级)
|
||||
* 限定条件:无
|
||||
* 函数说明:锁存式(MCU 复位前一直有效);上层据此判定"SPI 通道异常"
|
||||
* (区别于"无芯片"/"PHY 未链接");仅首条故障记日志
|
||||
|
||||
@@ -241,10 +241,16 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
|
||||
* 函数功能:检查指定 Socket 当前是否可读
|
||||
* 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 可读(监听态有新连接,或已连接态接收缓冲非空);0 - 不可读或描述符无效 int
|
||||
* 返回值:1 - 可读(接收缓冲非空);0 - 不可读或描述符无效 int
|
||||
* 限定条件:net_init() 已调用
|
||||
* 函数说明:1. 监听态 Socket 视为可读(存在可 accept 的新连接)
|
||||
* 2. 已建立连接且接收缓冲区长度大于 0 时视为可读
|
||||
* 函数说明:1. P2-7(2026-10-04):LISTENING 态**不再**恒判可读。本网络层无
|
||||
* "待 accept 的挂起连接队列"——新连接到达时 CONNECT 事件把该 socket
|
||||
* 自身直接转为 ESTABLISHED(handle_connect_event:"监听自身转数据通道"),
|
||||
* 故监听态恒无数据可读。原实现对 LISTENING 恒返回 1,会使
|
||||
* select()/poll() 在监听 socket 上永久立即就绪(忙轮询地雷;当前无
|
||||
* 调用方,属潜在地雷)。连接到达经 NET_EVENT_CONNECTED 回调通知。
|
||||
* 2. 其余态:readable 由 net_poll 在 RECV_OK 时置位、net_recv_locked
|
||||
* 读空后清零。避免应用任务直接访问 CH395F SPI(见 Trap 16)
|
||||
*/
|
||||
static int check_socket_readable(int sockfd) {
|
||||
const net_sock_t *p_sock;
|
||||
@@ -254,13 +260,12 @@ static int check_socket_readable(int sockfd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 监听 Socket 有新连接时可读 */
|
||||
/* 监听态恒不可读(见上方 P2-7 说明) */
|
||||
if (NET_SOCK_STATE_LISTENING == p_sock->state) {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 已建立连接或 UDP 等有数据可读:readable 由 net_poll 在 RECV_OK 时置位、
|
||||
* net_recv_locked 读空后清零。避免应用任务直接访问 CH395F SPI(见 Trap 16) */
|
||||
/* 有数据可读 */
|
||||
if (p_sock->readable) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,18 @@ exit /b 2
|
||||
:uv4_ok
|
||||
set UV4=%DTU_KEIL_UV4%
|
||||
|
||||
:: Remove stale log first: UV4 -o writes the log only when a download actually
|
||||
:: runs. A leftover log would otherwise be misreported as this download's
|
||||
:: result (same guard as build.bat).
|
||||
if exist "%LOG%" del "%LOG%"
|
||||
|
||||
"%UV4%" -f "%PROJ%" -t%TARGET% -j0 -o "%LOG%"
|
||||
|
||||
if not exist "%LOG%" (
|
||||
echo DOWNLOAD_FAIL: no download log produced - UV4 did not run or hung
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
findstr /C:"Application running" "%LOG%" >nul
|
||||
if %ERRORLEVEL% equ 0 (
|
||||
echo DOWNLOAD_OK: flashed and running
|
||||
|
||||
@@ -184,6 +184,20 @@ void MX_FREERTOS_Init(void) {
|
||||
|
||||
/* 本地时间同步任务(PC 推送 TCP):依赖 netTask 处理消息队列 */
|
||||
timeSyncTaskHandle = osThreadNew(time_sync_task, NULL, &timeSyncTask_attributes);
|
||||
|
||||
/* P2-13(2026-10-04):产品构建(未选任何 TEST_SUITE_*)不需要 testTask。
|
||||
* 上方生成区按 .ioc FREERTOS.Tasks01 无条件创建它,其 2048 words(8KB) 栈 + TCB
|
||||
* 取自 CCM 堆;而产品构建下 start_test_task() 仅打印一行后 osDelay(1000) 永久空转,
|
||||
* 白白占用 8KB CCM。此处于内核启动前(MX_FREERTOS_Init 先于 osKernelStart 调用)
|
||||
* 立即回收:调度器未运行时 vTaskDelete 对非当前任务同步 prvDeleteTCB,栈/TCB 归还
|
||||
* 堆(INCLUDE_vTaskDelete=1);置句柄 NULL 后栈水位探针(default_task.c 既有 NULL
|
||||
* 分支)自动跳过 test 项。不改生成区 / .ioc —— CubeMX 重生成不丢本逻辑。 */
|
||||
#if !defined(TEST_SUITE_SELECTED)
|
||||
if (testTaskHandle != NULL) {
|
||||
vTaskDelete(testTaskHandle);
|
||||
testTaskHandle = NULL;
|
||||
}
|
||||
#endif
|
||||
/* USER CODE END RTOS_THREADS */
|
||||
|
||||
/* Create the event(s) */
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 265,940 B (259.71 KB) | 1,048,576 B (1024 KB) | 25.4% |
|
||||
| ER_IROM1 | 266,548 B (260.30 KB) | 1,048,576 B (1024 KB) | 25.4% |
|
||||
| RW_IRAM1 | 84,960 B (82.97 KB) | 131,072 B (128 KB) | 64.8% |
|
||||
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
|
||||
| **Flash (ROM 合计)** | **266,676 B (260.4 KB)** | 1,048,576 B (1024 KB) | **25.4%** |
|
||||
| **Flash (ROM 合计)** | **267,288 B (261.0 KB)** | 1,048,576 B (1024 KB) | **25.5%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
+20
-17
@@ -39,7 +39,7 @@
|
||||
|---|---|---|
|
||||
| **P0** | 0 | **P0-1 已闭环**:2026-09-18 修复(cfg_file 临时缓冲改走 FreeRTOS 堆)+ 2026-10-03 真机 `@test` PASS + 2026-10-04 独立复跑 PASS(见 §4 说明与 `子系统设计/配置文件设计说明.md` v1.13) |
|
||||
| **P1** | 2 (+1 收尾) | **P1-1 ADC 采样路径产品构建中死亡**;**P1-7 MAC 写入顺序**(待 CH395F 手册 §9.2.1);P1-10 测试固件构建实测闭环(主体已闭环,见 §3.1) |
|
||||
| **P2** | 10 | 健壮性/错误传播/文档失真:P2-1/2/7/8/9/10/11/12/13/15(详见 §3.2) |
|
||||
| **P2** | 0 | **全部销项(2026-10-04)**:P2-1/2/7/8/9/10/11/12/13/15 共 10 项已全部闭环,明细与证据见 §3.2 |
|
||||
| **P3** | 6 | 卫生项:P3-1/2/4/6/8/11(其余已销项),可随其他修改顺手处理 |
|
||||
| 登记簿 | 10 | 网络:N-2(49s 日志静默,**低风险观察项**)、N-3(L5 基线重验)、N-4(pending 二期)、N-16(A2/A4 专项负向验证)、N-17(探针窗口心跳缺口);存储/日志:**S-1(defaultTask 栈余量 10%,需尽快处理)**、S-2(boot_log 存档)、S-3(USART1 TX-only 根治)、S-4(dhara 重试自旋)、S-5(可选全序列卷锁) |
|
||||
| 测试/基础设施 | 5 | T-7(CI runner 未就绪)+ 测试架构专项 **S12**(断言宏)、**S1-c**(可选单测)、**A-4**(采集链路测试约束)、**A-5 残余**(CI 体积门禁盲区) |
|
||||
@@ -92,7 +92,7 @@
|
||||
| **C1** | **pending 二期(ACCEPT + SEND)** | 沿一期机制扩展;同期消化 E-25(net_select 语义)、E-24/E-27/E-28(跨上下文收口);基线重锁 + 探针族扩充 + 看门狗接 selftest。**设计基线见 §5.1** | 第二档 #2 / N-4 |
|
||||
| **C3** | **存储侧日志管线**(消化 N-2) | 丢弃可观测性**代码已具备**(`console_log.c` `s_con_drop`+`[ERR] CONSOLE dropped`、`sys_log_mcu.c` 队列满 `DBG_ERROR`、TX 僵尸通道自愈);V5 30 min 日志**未复现 49s 静默**(运行期 >10s 静默全为 ~28s、均在 `e02_probe` 窗口内=N-17)。剩余:与 S8(非阻塞日志)合并评估 + **S-3(USART1 TX-only 结构根治)** | N-2 / S-3 |
|
||||
| **C4** ⚠️ | **defaultTask 栈余量**(S-1,**需尽快处理**) | **2026-10-04 实测 3664/4096(余量 432 B ≈ 10%,远低于 30% 判据)**。处置:栈 4096→4608(CCM 堆余量可覆盖 +512B)或 GC 链局部削减;**建议随阶段 B 之前先做** | S-1 |
|
||||
| **C5** | 一档剩余 + P2 收口 | E-49(RS485 DE 收尾)、E-15(ringbuf 定去留——**已确认零调用者,倾向删除**)、E-61(SD2506 注释订正)、E-16(cfg_file 段名长度)、E-26(UDP 定案,倾向移除)、E-23、E-30 + P2-1/2/7/8/9/10/11/12/13/15 | 一档/二档 |
|
||||
| **C5** | 一档剩余 + P2 已收口 | E-49(RS485 DE 收尾)、E-15(ringbuf 定去留——**已确认零调用者,倾向删除**)、E-61(SD2506 注释订正)、E-16(cfg_file 段名长度)、E-26(UDP 定案,倾向移除)、E-23、E-30(P2 十项已于 2026-10-04 全部销项,见 §3.2) | 一档/二档 |
|
||||
| **C6** | **USART1 TX-only 结构根治**(S-3) | 灭 ORE 僵尸机制根源:改 `.ioc` 令 USART1 不使能 RX / PA10 不复用为 `USART1_RX` → CubeMX 重生成 → 跑 `check_cubemx_survival.py`。属结构性改动,需单开一轮 + 复跑台架闭环 | S-3 |
|
||||
|
||||
### 2.4 阶段 D:验证与卫生(常态化)
|
||||
@@ -128,7 +128,7 @@
|
||||
#### P1-1 ADC 采样数据路径产品构建中死亡
|
||||
- **位置**:`App/app/app_main.c`(`tpafe5160_start_conv_irq()` 仅启动时调用一次)+ `Drivers/BSP/TPAFE5160/tpafe5160.c`
|
||||
- **证据**:启动序列调用一次 `tpafe5160_start_conv_irq()`;全仓库无周期性 CONVST 重触发;`data_ready` / `get_buf` 无任何消费者(`adc_task.c` 是空桩)。
|
||||
- **影响**:上电后仅采集 1 个样本,此后数据永久陈旧;`APP_ADC_ENABLE` 开关又无人引用(见 P2-12),形成"看似启用实则失效"的假象。
|
||||
- **影响**:上电后仅采集 1 个样本,此后数据永久陈旧;且 `APP_ADC_ENABLE` 为未接线占位(P2-12 已销项:已如实标注,不再冒充开关),形成"看似启用实则失效"的假象。
|
||||
- **修复建议**:周期 CONVST 重触发(定时器或任务)+ 数据消费任务;若基线版本有意不实现采集,应删除启动调用并以 `APP_ADC_ENABLE` 真实门控。
|
||||
- **置信度**:high(代码级确证);P0/P1 定级取决于产品定义(见 §4 待确认)。
|
||||
|
||||
@@ -149,20 +149,23 @@
|
||||
- **剩余**:① CH395F 套件(`TEST_SUITE_CH395F`,HW 全阶段 / NET 层 7 配置)实际构建确认链接通过——PHASE5 三静态缓冲 3×4093B 仍在,理论余量足够但未实测;② `@test` 闭环;③ **不建议**扩 scatter 到 192KB(破坏既有克隆芯片兼容决策)。
|
||||
- **置信度**:high(基线数据来自 2026-09-21 map;测试固件构建未复验)。
|
||||
|
||||
### 3.2 P2(未闭环 10 项)
|
||||
### 3.2 P2 —— ✅ 全部销项(2026-10-04)
|
||||
|
||||
| # | 位置 | 问题 | 修复建议 | 置信度 |
|
||||
|---|---|---|---|---|
|
||||
| P2-1 | `App/cfg/cfg_file.c` | 受管键行 >127 字符时外科保存产生重复键行(值静默截断,旧行残留;Pass1 匹配仍受 `CFG_LINE_TEST_MAX=127` 限制) | 行长超限时拒绝保存并报错,或整卷重写 | high |
|
||||
| P2-2 | `Drivers/BSP/CH395F/ch395f.c` | `ch395f_spi_dma_transfer` 为 void:DMA 超时/abort 后不向调用方传播错误(调用方当成功继续 → 静默数据损坏) | 超时路径置错误标志并经返回值/查询接口上报 | high |
|
||||
| P2-7 | `Drivers/BSP/NET/net_select.c` `check_socket_readable` | LISTENING 视为恒可读 → 单连接模式下对监听 socket select(readfds) 永久立即返回(当前无调用方,潜在地雷) | LISTENING 仅在写集合/异常集合计数可读,或明确文档化 | high |
|
||||
| P2-8 | `App/sync/time_sync.c` + `Drivers/BSP/NET/net_config.h` | `NET_RECV_TIMEOUT_MS=30000`;且 `time_sync.c` 的 `net_recv` 现传 timeout=0(**无限等待**)——静默对端使同步任务永久卡死 | 同步收包单独用短超时(如 3s),与通用收发超时分离 | high |
|
||||
| P2-9 | `App/log/sys_log_mcu.c` | 挂载时 mkdir 失败 → `s_fs_ready` 不置位,drain 永久 no-op 无重试(`syslog_mcu_mounted` 仅调用一次) | 周期重试挂载/建目录 | medium |
|
||||
| P2-10 | `App/log/sys_log_mcu.c` | 容量满 unlink 与 UI 读记录(FA_READ)存在竞态窗口 | unlink 前等待读者释放,或双文件轮转 | medium |
|
||||
| P2-11 | `App/task/net_task.c` | RECONFIG 事件用 `osEventFlagsGet`+Clear,非原子(检查与清除间可丢事件) | 改 `osEventFlagsWait` 原子获取 | high |
|
||||
| P2-12 | `App/app/app_cfg.h` | `APP_NET_ENABLE`/`APP_ADC_ENABLE`/`APP_RS485_ENABLE` 全仓库无 .c 引用,纯装饰 | 真实接入模块门控,或从文档/注释中删除"开关"说法 | high |
|
||||
| P2-13 | `Src/freertos.c` | testTask 无条件创建,产品构建空转占 2048 words(8KB,CCM 堆) | 按 `test_config.h` 门控创建 | high |
|
||||
| P2-15 | `MDK-ARM/download.bat` | 调用 UV4 前不删旧 `download_log.txt`:陈旧 "Application running" 掩盖失败烧录 | 调用前先删旧日志 | high |
|
||||
P2-1/2/7/8/9/10/11/12/13/15 共 10 项已全部闭环(代码 + 主机单测 + `@build` 0/0)。
|
||||
**按"只保留待办"约定,明细条目已删除**;结论与证据沉淀进各模块文档,此处只留索引:
|
||||
|
||||
| # | 位置 | 闭环方式(摘要) | 证据落点 |
|
||||
|---|---|---|---|
|
||||
| P2-1 | `App/cfg/cfg_file.c` | 受管键行 > `CFG_LINE_TEST_MAX`(127) 时 `s_surgical_pass1` 返回 -1 → 拒绝保存、原文件不动、自检留痕(消除"超长行静默跳过 → 重复键行") | 本文件 §3.2;主机单测 TC-CFG-044(`test/host/cfg_file/cfg_file_test.c`) |
|
||||
| P2-2 | `Drivers/BSP/CH395F/ch395f.c` | `ch395f_spi_dma_transfer` 改返回状态;失败锁存通道故障(`ch395f_spi_byte_fault()` 现覆盖字节级 + DMA 级);`read_recv_buf` 失败时不再拷入不可信 rx 数据(改零填充) | 本文件 §3.2;`ch395f.c` v 变更记录 |
|
||||
| P2-7 | `Drivers/BSP/NET/net_select.c` | `check_socket_readable` 对 LISTENING 不再恒返回 1(本层无挂起 accept 队列,CONNECT 事件直接把该 socket 转 ESTABLISHED)→ 消除 select/poll 忙轮询地雷 | 本文件 §3.2;`net_select.c` 函数注释 |
|
||||
| P2-8 | `App/sync/time_sync.c` | **核实已由 L3 pending 改写闭环**:收包用 `net_recv_to`(≤50ms 分片 + 30s 总预算 + 每片喂心跳),无 `net_recv` 无限等待 | 本文件 §3.2;`time_sync.c` 头注释 |
|
||||
| P2-9 | `App/log/sys_log_mcu.c` | `syslog_mcu_mounted()` 改幂等 + 卷锁保护;`defaultTask` 每 2s 复查(`STORAGE_READY` 已置位但日志后端未就绪时重试建 `/log`) | 本文件 §3.2;`sys_log_mcu.c` v1.4 |
|
||||
| P2-10 | `App/log/sys_log_mcu.c` | `s_read_raw()` 纳入卷锁 try(读者 vs 写者 unlink 互斥),消除"UI 读记录时文件被 unlink"竞态 | 本文件 §3.2;`sys_log_mcu.c` v1.4 |
|
||||
| P2-11 | `App/task/net_task.c` | RECONFIG 位改用 `osEventFlagsWait(timeout=0)` 原子取走+清除,替换 `Get`+`Clear` 两步(消除丢事件窗口) | 本文件 §3.2;`net_task.c` 主循环注释 |
|
||||
| P2-12 | `App/app/app_cfg.h` | 三个 `APP_*_ENABLE`(及 `APP_SYS_TIME_SRC`)全库零引用 → 如实标注为"未接线占位",删除"开关"说法(同步 `项目部署环境要求.md`) | 本文件 §3.2;`app_cfg.h` v1.1 |
|
||||
| P2-13 | `Src/freertos.c` | 产品构建(`!TEST_SUITE_SELECTED`)下 `MX_FREERTOS_Init()` 于内核启动前 `vTaskDelete(testTask)` 回收 8KB CCM 栈(不改生成区/.ioc,重生成不丢) | 本文件 §3.2;`项目架构总览.md` §2.1 |
|
||||
| P2-15 | `MDK-ARM/download.bat` | 调 UV4 前先删旧 `download_log.txt` + 增"未产出日志"判定(对齐 `build.bat`),消除陈旧 "Application running" 掩盖烧录失败 | 本文件 §3.2;`download.bat` 注释 |
|
||||
|
||||
> **已销项 P2(勿重复报)**:P2-3(`gd5f_bbt_save` 返回值检查)、P2-4(`dhara_nand_read` 检查 `gd5f_read_from_cache`)、P2-5(`sd2506.c` 负温 `-(int8_t)(val & 0x7F)`)、P2-6(SD2506 CTR1 写禁止)、P2-14(`build.bat` 删旧日志)、P2-16(AGENTS.md 栈表)。
|
||||
|
||||
@@ -304,7 +307,7 @@ S14 已在 workflow 显式提示"体积门禁未执行"(`::notice::`)并在
|
||||
|
||||
1. TPAFE5160 是否有自动扫描模式(决定 P1-1 定 P0 还是 P1)。
|
||||
2. CH395F 手册 §9.2.1:`INIT_CH395` 是否锁存 MAC 寄存器(决定 P1-7 修复紧迫度)。
|
||||
3. `APP_ADC_ENABLE` / `APP_RS485_ENABLE` 是否应在产品构建真实门控模块(关联 P1-1、P2-12、P3-11)。
|
||||
3. `APP_ADC_ENABLE` / `APP_RS485_ENABLE` 是否应在产品构建真实门控模块(关联 P1-1、P3-11;P2-12 已销项——宏已如实标注为"未接线占位",此处确认的是**是否接入真实门控**)。
|
||||
4. RS-485 PD0 DE 的半双工方向语义需硬件实测确认(本次仅静态审查)。
|
||||
|
||||
---
|
||||
|
||||
+2
-2
@@ -55,13 +55,13 @@
|
||||
| `defaultTask` | 4096 | Normal | FatFS 挂载(无卷 `f_mkfs`)→ 卷健康探针/自愈 → `net_cfg_load`(配置引擎属主)→ 置 `STORAGE_READY`(= 存储+配置就绪)→ 日志落盘(唯一写者,`syslog_drain`)+ 配置保存执行/2s poll + LED 闪烁 + **栈水位观测**(`stack_probe_poll`:首次 5s 后报告,此后仅在出现更深峰值时打印) | `StartDefaultTask` |
|
||||
| `netTask` | 4096 | Normal | 等存储(配置已由 defaultTask 就绪,本任务只读 RAM 副本)→ `net_stack_bring_up()`(不触碰配置文件引擎)→ 置 `NET_READY` → `net_poll` 轮询(CH395F 硬件层测试模式下入口自挂起) | `StartNetTask` |
|
||||
| `ftpTask` | 4096 | Normal | 等网络**且**存储(`WaitAll`)→ `lftpd_start("/", 21)` → 单客户端会话循环(断开后重开监听) | `StartFtpTask` |
|
||||
| `testTask` | 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择;产品构建空转) | `start_test_task` |
|
||||
| `testTask` | 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**:CubeMX 生成区无条件建后,`MX_FREERTOS_Init()` 在未选套件时(`!TEST_SUITE_SELECTED`)立即 `vTaskDelete` 回收其 8KB CCM 栈(P2-13,2026-10-04) | `start_test_task` |
|
||||
| `uiTask` | 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60s 切待机屏)/`ui_net_sink` → 10ms 刷新 | `ui_task_func` |
|
||||
| `timeSyncTask` | 2048 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC | `time_sync_task` |
|
||||
|
||||
> `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 48KB heap_4 堆分配。
|
||||
> **栈实测与预算**(2026-09-21,工具 `scripts/stack_report.py` = Keil 静态调用图 + 串口 `[STACK]` 水位;详见 `docs/内存管理/RAM使用分析与优化方案.md` §0.6):
|
||||
> defaultTask 静态最坏 2776B/4096B(余量 32%)、ftpTask 实测 **1184B/4096B(余量 71%)**(lftpd 大局部缓冲静态化前为 3480B / 15%,见内存方案 §1.4)、uiTask 实测 3824B/16384B(缩栈空间大,内存方案 §1.3 目标 6KB)、netTask 588B、timeSyncTask 420B、testTask(产品构建)348B。
|
||||
> defaultTask 静态最坏 2776B/4096B(余量 32%)、ftpTask 实测 **1184B/4096B(余量 71%)**(lftpd 大局部缓冲静态化前为 3480B / 15%,见内存方案 §1.4)、uiTask 实测 3824B/16384B(缩栈空间大,内存方案 §1.3 目标 6KB)、netTask 588B、timeSyncTask 420B、testTask 348B(2026-09-21 实测;P2-13 后产品构建已不再创建该任务,仅测试构建保留)。
|
||||
> ⚠ 改栈或加大栈内局部变量前后**必须**重跑该脚本——静态分析低估含递归/间接调用的链(优化前 ftpTask:静态 2528B vs 实测 3480B)。
|
||||
|
||||
### 2.2 就绪屏障(Event Flags,取代魔法延时)
|
||||
|
||||
@@ -488,9 +488,9 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
|
||||
|---|---|---|---|
|
||||
| CH395F 以太网 | SPI2 | PB12 CS / PB13 SCK / PB14 SDO / PB15 SDI | PHY 强制 100M 全双工;SPI 分频系数必须为 8(PCB 设计约束,速度受限) |
|
||||
| GD5F2GQ5UE NAND(2 Gbit) | SPI1 | PE0 CS / PB3 SCK / PB4 MISO / PB5 MOSI / PB8 WP / PE1 HOLD | FatFS(FAT32)+ dhara FTL;上电无卷自动 `f_mkfs` |
|
||||
| TPAFE5160 ADC | 并行 16 位 | PG0~PG15 数据 / PD3 RD / PD4 CONVST / PD7 BUSY | `APP_ADC_ENABLE` 开关 |
|
||||
| TPAFE5160 ADC | 并行 16 位 | PG0~PG15 数据 / PD3 RD / PD4 CONVST / PD7 BUSY | 编译期常驻(`app_cfg.h` 的 `APP_ADC_ENABLE` 为**未接线占位**,不门控本模块) |
|
||||
| USART1(调试串口) | UART | PA9 TX / PA10 RX | 115200 8N1;printf 日志 + `@test` 采集通道 |
|
||||
| UART5(RS-485) | UART | PC12 TX / PD2 RX / PD0 DE | 半双工;`APP_RS485_ENABLE` 开关 |
|
||||
| UART5(RS-485) | UART | PC12 TX / PD2 RX / PD0 DE | 半双工;编译期常驻(`APP_RS485_ENABLE` 为**未接线占位**) |
|
||||
| 状态 LED ×6 | GPIO | PC4 / PC5 / PB1 / PB2 / PF11 / PF12 | **低电平点亮** |
|
||||
|
||||
### 3.3 资源预算(部署红线)
|
||||
@@ -529,9 +529,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
|
||||
|
||||
| 开关 | 位置 | 当前值 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `APP_NET_ENABLE` | `App/app/app_cfg.h` | 开 | 网络(CH395F + 全部 TCP 服务) |
|
||||
| `APP_ADC_ENABLE` | 同上 | 开 | TPAFE5160 采样 |
|
||||
| `APP_RS485_ENABLE` | 同上 | 开 | RS-485 通信 |
|
||||
| `APP_NET_ENABLE` / `APP_ADC_ENABLE` / `APP_RS485_ENABLE` | `App/app/app_cfg.h` | 全定义 | **未接线占位,不门控任何代码**(P2-12,2026-10-04 核实):三个宏全库零消费点,各模块一律编译进产品;"能否裁剪"取决于产品定义(待处理内容.md §2 阶段 B0),落定后再接入真实门控。**不要把它们当开关** |
|
||||
| `TEST_SUITE_*` | `test/board/test_config.h` | 全注释(产品构建) | 测试套件**互斥**:一次构建只开一个(CH395F / 存储 / GD5F / RTC);全注释 = 正常产品固件 |
|
||||
|
||||
---
|
||||
|
||||
@@ -46,12 +46,14 @@ extern "C" {
|
||||
#error "测试套件互斥:一次构建只能启用一个 TEST_SUITE_*"
|
||||
#endif
|
||||
|
||||
/* 派生标记:当前**仅作语义标记,全仓无任何代码引用**(2026-10-03 实机核实:
|
||||
* grep 全仓仅本文件有定义,无使用点)。testTask 的创建与调度见 Src/freertos.c
|
||||
* (无条件创建 testTask)+ test/board/test_config.c 的 start_test_task(),后者**直接
|
||||
* 按 TEST_SUITE_RTC 等宏分支**,与本标记无关——故"RTC 构建下 testTask 不被创建"
|
||||
* 的旧疑点不成立(实测 RTC 套件正常执行)。
|
||||
* 4 个套件(含 RTC)在此补齐:若将来真引入"测试构建下 defaultTask 空闲"等分支而复用
|
||||
/* 派生标记:任一套件被选中时置位。**2026-10-04 起有唯一消费点**——`Src/freertos.c`
|
||||
* 的 MX_FREERTOS_Init() 用它门控 testTask 的回收(P2-13):产品构建下 CubeMX 生成区
|
||||
* 无条件创建 testTask,随后本标记未定义 → 立即 vTaskDelete 回收其 8KB CCM 栈 + TCB,
|
||||
* 避免空转占堆。testTask 的调度见 test/board/test_config.c 的 start_test_task(),
|
||||
* 后者**直接按 TEST_SUITE_RTC 等宏分支**,与本标记无关——故"RTC 构建下 testTask 不被
|
||||
* 创建"的旧疑点不成立(实测 RTC 套件正常执行;RTC 已计入下方析取,故 RTC 构建下本
|
||||
* 标记亦置位、testTask 正常保留)。
|
||||
* 4 个套件(含 RTC)在此补齐:若将来再引入"测试构建下 defaultTask 空闲"等分支而复用
|
||||
* 本标记,可避免重演"漏 RTC"的隐患。详见 docs/测试说明/实机测试实施指南.md §8.3。 */
|
||||
#if defined(TEST_SUITE_CH395F) || defined(TEST_SUITE_STORAGE) || defined(TEST_SUITE_GD5F) || defined(TEST_SUITE_RTC)
|
||||
#define TEST_SUITE_SELECTED
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
* TC-CFG-041 外科手术:保留注释/未知段/未知键,受管键原位改写
|
||||
* TC-CFG-042 缺失受管段/键补写
|
||||
* TC-CFG-043 输出超长 -> 拒绝保存(-1) 且原文件不动
|
||||
* TC-CFG-044 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动(P2-1);
|
||||
* 非受管键的超长行照旧放过(原样保留)
|
||||
* —— poll 契约(cfg_file_poll)——
|
||||
* TC-CFG-050 首轮建立基线(不重载)
|
||||
* TC-CFG-051 沉降去抖:连续 2 次稳定才重载
|
||||
@@ -698,6 +700,52 @@ static void tc_save(void) {
|
||||
CHECK(s_syslog_count >= 1, "overflow self-test logged");
|
||||
}
|
||||
}
|
||||
|
||||
printf("[TC-CFG-044] over-long managed key line -> reject (P2-1)\n");
|
||||
{
|
||||
char buf[512];
|
||||
size_t n;
|
||||
size_t i;
|
||||
size_t before;
|
||||
int r;
|
||||
|
||||
/* 构造受管键 u8 的超长行(行总长 > CFG_LINE_TEST_MAX):值为 200 位数字。
|
||||
* 修复前:Pass1 静默跳过该行 → Pass2 在段头补写新 KV + 原文超长行原样保留
|
||||
* → 同一键两行。修复后:显式拒绝保存,原文件不动。 */
|
||||
vfs_reset();
|
||||
register_std();
|
||||
n = (size_t)snprintf(buf, sizeof(buf), "[net]\r\nu8 = ");
|
||||
for (i = 0u; i < 200u && n < sizeof(buf) - 3u; i++) {
|
||||
buf[n++] = '9';
|
||||
}
|
||||
buf[n++] = '\r';
|
||||
buf[n++] = '\n';
|
||||
buf[n] = '\0';
|
||||
vfs_put(buf);
|
||||
s_net_data.u8 = 7u;
|
||||
s_syslog_count = 0;
|
||||
before = s_vfs_len;
|
||||
r = cfg_file_save();
|
||||
CHECK(r == -1, "over-long managed key line rejected (-1)");
|
||||
CHECK(s_vfs_len == before, "original file untouched on over-long line");
|
||||
CHECK(s_syslog_count >= 1, "line-too-long self-test logged");
|
||||
|
||||
/* 反向:**非受管键**的超长行不属本守卫范围,应照旧原样保留、保存成功 */
|
||||
vfs_reset();
|
||||
register_std();
|
||||
n = (size_t)snprintf(buf, sizeof(buf), "[net]\r\nu8=1\r\nvendor = ");
|
||||
for (i = 0u; i < 200u && n < sizeof(buf) - 3u; i++) {
|
||||
buf[n++] = 'k';
|
||||
}
|
||||
buf[n++] = '\r';
|
||||
buf[n++] = '\n';
|
||||
buf[n] = '\0';
|
||||
vfs_put(buf);
|
||||
s_net_data.u8 = 9u;
|
||||
CHECK(cfg_file_save() == 0, "over-long unknown key line tolerated");
|
||||
CHECK(strstr(vfs_get(), "u8=9") != NULL, "managed key still rewritten");
|
||||
CHECK(strstr(vfs_get(), "vendor = kkk") != NULL, "unknown long line preserved");
|
||||
}
|
||||
}
|
||||
|
||||
/* TC-CFG-050~054:poll 契约 */
|
||||
|
||||
Reference in New Issue
Block a user