栈整改不抬栈:删旧档迁移、FIL静态化、日志行缓冲按模块收紧、钩子脱离HAL,defaultTask链5312→2776B

This commit is contained in:
2026-10-10 10:17:00 +08:00
parent 5c978465c9
commit 0779d3a075
54 changed files with 3203 additions and 2770 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
"stack-overflow-check": "d4735e3a265e16ee",
"malloc-failed-hook": "6b86b273ff34fce1",
"assert-not-deadloop": "d4a00718ab2bd071",
"assert-impl": "908de37a05c944ce",
"assert-impl": "5d862ceb21c77a86",
"stack-overflow-hook-impl": "a006edbd30d2c4d7",
"malloc-failed-hook-impl": "fcd7250e5cd6403b",
"stack-overflow-hook-helper": "cacd9fd028be9b5b",
+4 -3
View File
@@ -55,7 +55,8 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
- **FatFs 文件名约束(2026-09-20 台架实证,`.fschk` 无限重格式化事故根因)**:本移植 `Lib/FatFs/ffconf.h` 为 `FF_USE_LFN=0`,**只支持严格 8.3 短名**——主名≤8 字符、扩展≤3 字符、**不得以点开头**(`.fschk` 会被 `ff.c create_short` 把点后当扩展域解析超限 → `f_open` 恒返 `FR_INVALID_NAME`(6),纯 RAM 名字判定、不碰盘)。ff.c `create_short` 全部拒绝点已加 DEBUG 打印:`[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符`,非法名字调用看开机日志即可定位。自愈类逻辑**严禁**把名字/参数类错误码(`FR_INVALID_NAME` 等)归入卷损坏触发集(整卷重格式化不可逆)
- **ADC 取数只用 `tpafe5160_snapshot()`(原子快照)**(2026-10-10 修 E-44/E-45/E-58):旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已从驱动**删除**——`get_buf` 只给缓冲区指针、任务再逐通道拷,两步之间被 BUSY EXTI 抢占并跨过一帧周期就拷回**两帧混合**数据(8 通道失去同一时刻性,且无错误指示);`s_ready` 电平标志有三个写者,`start_conv_irq` 触发前清 0 = **重触发本身就会抹掉一帧未消费数据**。现口径:帧序号只增不清(`tpafe5160_frame_seq()`)、取数=临界区内拷贝(`tpafe5160_snapshot(dst, count)` 返回 1/0/-1,**仅任务上下文**)、FRSTDATA 全程无跳变(芯片不驱动并行总线)由 `tpafe5160_frstdata_err()` 计数且 `tpafe5160_init()` 首帧自检直接返回 `-3` + `[ERR]`
- **接 ADC / RS-485 消费者前的强制门禁(E-59,2026-10-10 固化)**:`adc_task` / `rs485_task` 从空桩转为真实消费者**之前**必须确认——① 驱动契约已修:TPAFE **E-44 / E-45 / E-58 已修 ✅**、RS485 **E-36 / E-37 已修 ✅** 但 **E-50(单缓冲无同步 → 包撕裂)未修 ❌**、**E-49(TX 超时拉 DE)未修 ❌**;② 栈预算:两任务**尚未入 `Src/freertos.c` 栈表**,建任务前后各跑一次 `scripts/stack_report.py`。与"改栈前跑 `stack_report.py`"同级。另按 N-18:V0.1 上两片转换器之间**禁止**做相位/功率/功率因数类运算
- **`DBG_*` 每条日志约 90 B Flash + 192 B 栈**(2026-10-02 S10 实测):`dbg_log.h` 的 `DBG_INFO/ERROR/DEBUG` 在**每个调用点内联** `char __con_line[DBG_CON_LINE_MAX]`(`console_log.h` 定义 **192 B**)+ 完整 `snprintf` 调用序列 → 单条约 **90 B Flash**(字符串常量另计)。给新模块接入日志时按"条数 × 90 B"估算 Flash 代价(实测 `sd2506_init` 加 6 条 = +576 B;`app_main` 加 3 条 = +180 B);**深调用链模块另需核算栈**(每条日志在栈上多占 192 B,会进 `stack_report.py` 的最坏链)。故**日志不是免费的**——已覆盖的步骤不要再加"进入前/完成后"重复锚点,只在真正黑洞(原先静默返回)处补
- **`DBG_*` 每条日志约 90 B Flash + 216 B 栈**(2026-10-02 S10 实测;2026-10-10 修正栈量):`dbg_log.h` 的 `DBG_INFO/ERROR/DEBUG` 在**每个调用点内联**行缓冲 `char __con_line[DBG_LINE_BUF_MAX]`(默认 = `console_log.h` 的 `DBG_CON_LINE_MAX` **192 B**)+ 时间戳缓冲 **24 B**(已并入同一数组,原为独立 32 B)+ 完整 `snprintf` 调用序列 → 单条约 **90 B Flash**(字符串常量另计)。给新模块接入日志时按"条数 × 90 B"估算 Flash 代价(实测 `sd2506_init` 加 6 条 = +576 B;`app_main` 加 3 条 = +180 B);**深调用链模块另需核算栈**——每条日志在栈上多占一份行缓冲、会进 `stack_report.py` 的最坏链:实测 defaultTask 那条链上 9 个帧各含一份 → **约 1.5 KB 纯粹是日志行缓冲**(2026-10-10 把该链 5312 → 2776 B 的主要杠杆之一)。故**日志不是免费的**——已覆盖的步骤不要再加"进入前/完成后"重复锚点,只在真正黑洞(原先静默返回)处补
- **深调用链模块收紧日志行缓冲:`#define DBG_LINE_BUF_MAX 128U`,写在 `#include "dbg_log.h"` 之前**(2026-10-10 加)。已收紧:`nand_ftl.c`/`gd5f2gq5ue.c`/`cfg_file.c`/`net_cfg.c`/`net_socket.c`/`ch395f.c`/`time_sync.c`/`default_task.c`。⚠ **取值必须 ≥ 该模块最长日志行**(时间戳 20 + 标签 + 正文),否则该行被截断——各文件注释已写出实测行长。改前/改后各跑一次 `stack_report.py`
<!-- TASK_TABLE:BEGIN generated by scripts/gen_task_table.py -->
| Task | 栈 (words) | 优先级 |
@@ -77,7 +78,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
- `app_main_init`(时间子系统优先,使后续日志带时间戳):`sd2506_init` → `sys_clock_init`(墙钟;RTC 故障时降级默认时间基准并返回 -1 触发"时钟芯片异常"自检;`sys_clock_get` 每 30 天惰性再基准化防 49.7 天 tick 回绕)→ `rs485_init` → `gd5f2gq5ue_init` → `tpafe5160_init` → SD2506 诊断 → `tpafe5160_start_conv_irq`
- **就绪屏障**(`App/app/sys_events.h` Event Flags,取代魔法延时):`SYS_EVT_NET_READY`(netTask 置位)/ `STORAGE_READY`(defaultTask:卷挂载 + 网络配置加载完成)/ `FTP_READY` / `TIMESYNC_READY`;消费者用 `osEventFlagsWait` 阻塞等待
- netTask:`net_stack_bring_up`(配置已由 defaultTask 在屏障前加载,本任务只读 RAM 副本 → `net_init` + PHY 强制 100M 全双工 → 递增网络代次号)→ 置 NET_READY → 循环 `net_poll` + `net_process_messages`;消费 `SYS_EVT_NET_RECONFIG` → 立即重建网络(新 IP 免重启生效,各 TCP 服务据代次号重建监听)
- defaultTask:`f_mount` forced(无卷自动 `f_mkfs` FAT32;挂载后卷健康探针:小写两击确认,FAT 区陈旧/擦除时自动重格式化自愈,`/config.ini` 回落工厂默认——2026-09-20 /log 写失败修复)→ 网络配置加载(`net_cfg_load`,首启含旧档迁移)→ STORAGE_READY → LED 闪烁;同时是 syslog(`/log/run.log`·`selftest.log`,定长 64B 记录)的**唯一写者**与**配置引擎属主**(`cfg_file` 静态态单任务收敛于此:UI 保存请求执行 `net_cfg_save_run` + 2s `cfg_file_poll`,经 `SYS_EVT_CFG_SAVE` 事件唤醒;netTask/uiTask 不得直接触碰配置引擎,见 P1-3)
- defaultTask:`f_mount` forced(无卷自动 `f_mkfs` FAT32;挂载后卷健康探针:小写两击确认,FAT 区陈旧/擦除时自动重格式化自愈,`/config.ini` 回落工厂默认——2026-09-20 /log 写失败修复)→ 网络配置加载(`net_cfg_load`;旧档迁移已于 2026-10-10 删除,见「模块约定」网口配置条)→ STORAGE_READY → LED 闪烁;同时是 syslog(`/log/run.log`·`selftest.log`,定长 64B 记录)的**唯一写者**与**配置引擎属主**(`cfg_file` 静态态单任务收敛于此:UI 保存请求执行 `net_cfg_save_run` + 2s `cfg_file_poll`,经 `SYS_EVT_CFG_SAVE` 事件唤醒;netTask/uiTask 不得直接触碰配置引擎,见 P1-3)
- 全景 `docs/架构设计/项目架构总览.md`(六层分层 / 启动时序 / 存储·网络·UI 栈 / 目录组织)
## 硬件引脚
@@ -99,7 +100,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
- 所有 `net_*` socket 操作经 `netMsgQueue` 消息队列在 netTask 串行执行(线程安全);非 netTask 任务直接调 `net_*` API,勿直接碰 CH395F 驱动
- **`net_*` 公开 API 的错误码经"任务通知值"回传**(2026-09-21 实证):`net_msg_t` 是**按值拷入队列**的,netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 `xTaskNotify()` 通知值回来(失败=负的具体错误码如 `NET_ERR_WOULDBLOCK`、成功=字节数/描述符),公开 API 恢复 errno 后对外统一返回 -1。**勿给 `net_msg_t` 加"回传用"字段**(调用方只会读到栈垃圾;曾据此"修"错误码,导致 `ui_net_sink` 每次空轮询都误判为真错误 → 连上即 `net_close`)
- **单连接 socket 的断开语义**:监听 Socket 在 CONNECT 后自身即数据通道(`g_client == g_listen`),EOF 时 net 层已 `auto_relisten`;应用层**不得**在 EOF 时 `net_close` 该 fd(会连监听一起毁掉 → 端口"连接被拒"),仅当数据 fd ≠ 监听 fd(模拟器多连接后端)时才关
- 网口配置:`/config.ini` [network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;旧档 `/cfg/netcfg.bin` 首启自动迁移)→ `docs/子系统设计/网络配置说明.md`
- 网口配置:`/config.ini` [network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;**旧档 `/cfg/netcfg.bin` 迁移已于 2026-10-10 删除**——`net_cfg_legacy.c/h` 整体移除,旧档若存在则被忽略(不读/不删/不迁移)、配置回落工厂默认;删除理由 = 该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏链 +600 B,且本仓库无携带旧档的设备)→ `docs/子系统设计/网络配置说明.md`
- **新 .c 文件必须手动注册到两处**才能双端编译:`MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt`(用 `new-source-file` skill)
- **`MDK-ARM/*.uvprojx` 每次 `@build` 后被 Keil UV4 自动改写,改动一律不入库**(2026-09-29 实测):UV4 构建时会把工程 XML 归一化成它本机风格的旧式写法(`<Tag />` → `<Tag></Tag>`、XML 声明加 `standalone="no"`),并把 `PackID` 降到**本机装的 DFP 版本**(本机实测 `3.1.1` → `3.0.0`,属**降级**)。这些差异是环境相关的,提交后会把别人的 Pack 版本也一起改掉。**判断方法**:`git diff MDK-ARM/STM32F407-Demo.uvprojx` 里若只见 XML 格式/`PackID` 差异而**无源文件挂载增删** → `git checkout -- MDK-ARM/STM32F407-Demo.uvprojx` 丢弃。只有真的新增/删除源文件(`grep 文件名` 计数变化)才提交
- 测试构建:`test/board/test_config.h` 的 `TEST_SUITE_CH395F/STORAGE/GD5F/RTC` **互斥**(一次只能启用一个,`#error`);全部注释 = 产品构建;**该开关受 pre-commit 钩子 `test-suite-switch` 门禁**(默认要求产品构建,跑完套件回归须改回;确需提交测试构建用 `ALLOW_TEST_BUILD=1` 显式放行——2026-10-01 实测已提交 HEAD 里 `TEST_SUITE_CH395F` 长期开启,纯靠人记得改,故补此门禁);**破坏性用例默认关闭**:同文件 `ENABLE_DESTRUCTIVE_TESTS`(默认注释)控制写/擦 NAND、格式化、坏块注入与 FatFS 写类用例——关闭时套件为**只读模式**(开机横幅 `mode: READ-ONLY`,可随时在在用设备上跑、不动卷内容与容量),只在**产品首次上电/产线首启**或明确需要时打开(`mode: DESTRUCTIVE`);2026-09-21 实测一轮破坏性 GD5F 运行即让 dhara 多记一个坏块(容量 95248 → 95200;该计数持久化在 dhara journal checkpoint 头里,普通恢复模式清不掉,需全片擦除——见 `docs/测试说明/存储维护与容量恢复.md`);CH395F 套件 `test/board/ch395f_test.h`:硬件层 Phase 1~5 与 NET 层 Phase 6~11 互斥;CH395F 硬件层模式下 netTask(基础线程:无条件创建、全构建唯一实例)入口自挂起(不运行),testTask 独占 CH395F
+1 -1
View File
@@ -14,7 +14,7 @@ FreeRTOS/CMSIS-RTOS API**(见 `AGENTS.md`「平台约束」)。
| 目录 | 内容 | README |
|---|---|---|
| `app/` | **应用入口与全局**:`app_main.c/h`(初始化入口)、`app_cfg.h`(编译开关)、`sys_events.h`(就绪事件) | [`app/README.md`](app/README.md) |
| `cfg/` | **配置引擎**:`/config.ini` 解析 + 持久化 + 旧档迁移 | [`cfg/README.md`](cfg/README.md) |
| `cfg/` | **配置引擎**:`/config.ini` 解析 + 原子持久化(旧二进制档已废弃,2026-10-10) | [`cfg/README.md`](cfg/README.md) |
| `sync/` | **时间同步任务**:`time_sync.c/h` | [`sync/README.md`](sync/README.md) |
| `log/` | **日志子系统**:syslog(NAND)+ 控制台单写者日志 | [`log/README.md`](log/README.md) |
| `task/` | **FreeRTOS 任务实现**(6 个任务) | [`task/README.md`](task/README.md) |
+4 -3
View File
@@ -1,6 +1,6 @@
# App/cfg/ — 配置引擎
> **`/config.ini` 配置引擎**:分层解析、持久化(原子写)、旧档迁移。
> **`/config.ini` 配置引擎**:分层解析、持久化(原子写)。
> 属主 = **defaultTask**(单任务收敛,静态态);其它任务不得直接触碰。
## 文件一览
@@ -18,10 +18,11 @@
- **零分配共享核心**:核心逻辑不含任何分配符号,动态内存收进平台后端
(`cfg_store_alloc/free`:MCU→`pvPortMalloc`,模拟器→宿主堆)
- **原子写**:写 `/config.TMP` 后 `f_rename` 替换,避免掉电损坏
- **旧档迁移**:首启时把旧格式转换为当前格式(`net_cfg_legacy.c` 负责网络段)
- **旧档迁移已删除(2026-10-10)**:旧格式 `/cfg/netcfg.bin` 不再读取——存在则被忽略(不读、
不删、不迁移),配置回落工厂默认;`net_cfg_legacy.c/h` 与 `cfg_store_legacy_path()` 已移除
- **自研代码禁用 C 库堆分配**:MicroLIB 堆仅 512B(`malloc` 必返回 NULL)
## 相关文档
- `docs/子系统设计/配置文件设计说明.md` — 完整设计(分层/解析/持久化/迁移)
- `docs/子系统设计/配置文件设计说明.md` — 完整设计(分层/解析/持久化)
- `docs/子系统设计/网络配置说明.md` — 网络段
+4
View File
@@ -41,6 +41,10 @@
#include "sys_log.h" /* 配置读写失败上报自检记录(双端共享接口,MCU 端 preboot/队列安全) */
/* 深调用链模块(配置引擎在 defaultTask 启动链与保存链上,下接 FatFS → FTL):
* 日志行缓冲收到 128 B(默认 192)。本模块最长行 "load: file %luB > %uB, corrupt,
* factory template regenerated" 渲染后 ≈ 91 B(含时间戳 21 + 标签)< 128 → 不截断 */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[CFG]"
#include "dbg_log.h"
+2 -2
View File
@@ -103,8 +103,8 @@ const char *cfg_store_config_path(void);
/* 函数功能:取配置文件临时文件路径(write_atomic 内部用) */
const char *cfg_store_tmp_path(void);
/* 函数功能:取旧版配置遗留文件路径(net_cfg_legacy 迁移用) */
const char *cfg_store_legacy_path(void);
/* 注:旧版遗留档 getter `cfg_store_legacy_path()` 已于 2026-10-10 随旧档迁移
* net_cfg_legacy 一并删除(旧格式 /cfg/netcfg.bin 不再兼容、不再读取) */
#ifdef __cplusplus
}
+13 -11
View File
@@ -2,7 +2,7 @@
* 模块名称:cfg_file_store (MCU 后端)
* 模块功能:cfg_file 存储后端——NAND 上 FatFS 文件 /config.ini(卷根,8.3 合规);
* 原子写 = 临时文件 config.TMP(与目标同目录)+ f_rename;mtime 变更令牌 =
* (fdate << 16) | fmtime;旧版遗留文件 /cfg/netcfg.bin 供迁移读取
* (fdate << 16) | fmtime
* 适用平台:STM32F407ZGTx
* 作者:王建锋
* 创建日期:2026-09-14
@@ -37,7 +37,6 @@
static const char s_path[] = "/config.ini";
static const char s_path_tmp[] = "/config.TMP";
static const char s_path_legacy[] = "/cfg/netcfg.bin";
/* 函数定义区 */
@@ -49,9 +48,14 @@ static const char s_path_legacy[] = "/cfg/netcfg.bin";
* p_nread - 实际读字节数输出(不可为 NULL) size_t *
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:卷已挂载(未挂载时 f_open 返回 FR_NO_SYSTEM,转 -1)
* 函数说明:FIL 为**函数级 static**而非局部——本 FatFs 移植 FF_FS_TINY=0,
* `FIL` 内含 `buf[FF_MAX_SS]`=512 B(Lib/FatFs/ff.h),一个局部对象就吃掉
* 528 B 栈,是配置/syslog 调用链栈深的主要来源(2026-10-10 栈预算整改)。
* 安全性:整个 FIL 生命周期都在 `vol_lock_wait()/release()` 区间内,
* 跨任务访问被卷锁串行化,且本函数不递归、不嵌套自身
*/
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
FIL f;
static FIL f; /* static:省 528 B 栈;卷锁内串行使用 */
FRESULT fr;
UINT br = 0u;
@@ -116,9 +120,11 @@ static int s_build_tmp_path(const char *p_path, char *p_out, size_t cap) {
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
* 限定条件:卷已挂载;中途失败(含掉电)不损坏目标文件
* 函数说明:`FIL` 用函数级 static 而非局部(FF_FS_TINY=0 → 单个 FIL = 528 B 栈);
* 与 cfg_store_read 各持自己的 static 对象,互不共享,且全程在卷锁区间内
*/
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
FIL f;
static FIL f; /* static:省 528 B 栈;卷锁内串行使用 */
FRESULT fr;
UINT bw = 0u;
char tmp_path[CFG_STORE_TMP_PATH_MAX];
@@ -233,14 +239,10 @@ const char *cfg_store_tmp_path(void) {
}
/*
* 函数功能:取旧版配置遗留文件路径
* 入口参数:无
* 返回值:"/cfg/netcfg.bin"(旧 NCFG 二进制格式) const char *
* 限定条件:无
* 旧版遗留文件 /cfg/netcfg.bin 的路径 getter(cfg_store_legacy_path)已于
* 2026-10-10 随旧档迁移 net_cfg_legacy 一并删除:无调用者即无必要保留
* (留着"看起来还支持旧档"的入口会误导后续维护)
*/
const char *cfg_store_legacy_path(void) {
return s_path_legacy;
}
/*
* 函数功能:分配共享核心临时缓冲(FreeRTOS 堆)
+70 -4
View File
@@ -36,7 +36,9 @@
#include "task.h"
#include "cmsis_os.h"
#include "sys_events.h"
#include "sys_log.h"
/* 刻意不 include "sys_log.h":日志子系统不得回调本模块(调用图成环 →
* armlink 静态栈深在每个任务上虚高,见 dbg_console_drain 故障路径注释)。
* TX 故障留痕改由写者任务在 drain 之外补记 */
/* ===== 控制台单写者日志(2026-09-24 加固,HardFault 定位结论驱动)=====
* 背景:旧路径 DBG_* → printf → fputc **每字符一次** HAL_UART_Transmit。HAL 的锁只保证
@@ -66,6 +68,17 @@
* IWDG 误复位(round 8a 台架现场)。改用 DWT CYCCNT(内核硬件周期计数,不依赖任何
* 中断/uwTick)做判据;DWT 使能失败时退回 HAL_UART_Transmit(旧行为,功能不退化)。 */
#define CON_TX_TIMEOUT_CYC (SystemCoreClock / 10U) /* 发送硬上界 = 100ms 对应周期数 */
/*
* DWT 不可用时的**指令计数**上界(替代 HAL_UART_Transmit 回退,2026-10-10)。
* 为什么不再回退 HAL:① HAL_UART_Transmit 的超时判据是 HAL_GetTick——断言/停摆
* 现场 tick 未必在走,等待无上界;② 它的调用链 `HAL_UART_Transmit →
* UART_WaitOnFlagUntilTimeout` 会被 armlink 挂进**每个**碰日志的任务的最坏链
* (configASSERT 在 FreeRTOS 内部可达 → 三个内核钩子都在链尾),实测每条链多背
* 64 B 栈。计数上界同样有界、零外部依赖。
* 取值:115200 bps 下一字节 87 µs;轮询循环 ~5 周期 → 每字节约 3000 次迭代,
* 取 20000 留 6 倍余量(单字节卡死最多数毫秒级即放弃,不无限自旋)
*/
#define CON_TX_GUARD_MAX 20000u
#define CON_APPEND_WAIT_CYC (SystemCoreClock / 5U) /* 缓冲满等待硬上界 = 200ms 对应周期数 */
static uint8_t s_con_ring[DBG_CON_RING_SIZE];
@@ -73,6 +86,7 @@ static volatile uint16_t s_con_head; /* 生产者写入位置 */
static volatile uint16_t s_con_tail; /* 写者读取位置 */
static volatile uint32_t s_con_drop; /* 因缓冲满丢弃的行数 */
static volatile uint32_t s_con_tx_fault; /* TX 失败(僵尸通道)次数,均经外设软复位自愈 */
static volatile uint8_t s_con_tx_fault_log; /* TX 故障留痕待上报(由写者任务在 drain 之外补记 run.log) */
static volatile uint32_t s_con_cool_until; /* 故障冷却截止 tick(0 = 无冷却) */
static volatile uint8_t s_con_draining; /* 1 = 处于 drain 中(重入保护) */
static volatile uint8_t s_con_writer_up; /* 1 = 写者已注册(此后走环形缓冲) */
@@ -109,13 +123,45 @@ static void con_dwt_ensure(void) {
}
}
/*
* 函数功能:DWT 不可用时的计数上界发送(每字节/TC 各数 CON_TX_GUARD_MAX 次)
* 入口参数:u - USART 寄存器基址(调用方已判非 NULL) USART_TypeDef *
* p_buf - 待发数据;len - 字节数
* 返回值:HAL_OK - 已发出;HAL_TIMEOUT - 计数上界内未完成(本块放弃)
* 限定条件:USART1 已初始化
* 函数说明:刻意不用 HAL_UART_Transmit——理由见 CON_TX_GUARD_MAX 处注释
* (tick 依赖不可信 + 其调用链污染所有任务的最坏栈链)
*/
static HAL_StatusTypeDef con_tx_counted(USART_TypeDef *u, const uint8_t *p_buf, uint16_t len) {
uint32_t guard;
uint16_t i;
for (i = 0U; i < len; i++) {
guard = 0U;
while ((u->SR & USART_SR_TXE) == 0U) {
if (++guard >= CON_TX_GUARD_MAX) {
return HAL_TIMEOUT;
}
}
u->DR = (uint16_t)p_buf[i];
}
guard = 0U;
while ((u->SR & USART_SR_TC) == 0U) {
if (++guard >= CON_TX_GUARD_MAX) {
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
/*
* 函数功能:有界裸发送(不依赖 uwTick / HAL 超时)
* 入口参数:p_buf - 待发数据;len - 字节数(调用方已限 ≤ DBG_CON_CHUNK_MAX)
* 返回值:HAL_OK - 已全部写入发送寄存器;HAL_TIMEOUT - 有界上界内未完成
* 限定条件:USART1 已初始化
* 函数说明:寄存器级轮询 TXE/TC,超时判据用 DWT CYCCNT(CON_TX_TIMEOUT_CYC);
* DWT 未使能时退回 HAL_UART_Transmit(旧行为)。本函数只做 TX,
* DWT 未使能时走 con_tx_counted(指令计数上界,2026-10-10 取代
* HAL_UART_Transmit 回退)。本函数只做 TX,
* 不触碰 RX/错误标志——僵尸通道自愈由调用方按返回值处理。
* ⚠ USART1 未初始化(huart1.Instance == NULL,如 HAL_Init 早期或
* 断言/故障上报路径)时立即返回 HAL_ERROR:否则会解引用 NULL 去读写
@@ -131,7 +177,7 @@ static HAL_StatusTypeDef con_tx_bounded(const uint8_t *p_buf, uint16_t len) {
return HAL_ERROR; /* USART1 未初始化:无可用通道,安全让位 */
}
if (s_con_dwt_up == 0U) {
return HAL_UART_Transmit(&huart1, (uint8_t *)p_buf, len, DBG_CON_TX_TIMEOUT_MS);
return con_tx_counted(huart1.Instance, p_buf, len);
}
u = huart1.Instance;
@@ -254,7 +300,14 @@ void dbg_console_drain(void) {
if (s_con_tx_fault == 1U) {
static const char note[] = "\r\n[WARN] CONSOLE tx fault: UART reinit (self-healed)\r\n";
syslog_post(SYSLOG_RUN, "console tx fault, uart reinit"); /* 仅 run.log,不入自检判定 */
/* 刻意**不在这里**调 syslog_post():syslog_post → dbg_console_write_line
* → con_append → dbg_console_drain 会与本函数构成**调用图成环**。运行时
* 有 s_con_draining 重入标志挡着,不会真递归;但 armlink 静态调用图会把
* 整条日志尾巴在每个任务的最坏链上多叠一层(实测 6 个任务的最坏链末尾
* 全是这个环)→ 栈深数字虚高、真实风险反而被掩盖。
* 改法:只置标志,由写者任务(defaultTask 主循环,本就是 syslog 唯一
* 写者)在 drain **之外**补记 run.log(见 dbg_console_tx_fault_log_take) */
s_con_tx_fault_log = 1U;
(void)dbg_console_write_raw(note, (unsigned)sizeof(note) - 1U);
}
break; /* 故障即止:本轮 drain 到此为止,让位主循环 */
@@ -401,6 +454,19 @@ uint32_t dbg_console_tx_fault(void) {
return s_con_tx_fault;
}
/*
* 函数功能:取走"控制台 TX 故障待上报"标志(读后清)
* 返回值:1 - 需要补记一条 run.log 证据,0 - 无
* 限定条件:**写者任务**(defaultTask)在 dbg_console_drain() 之外调用
* 函数说明:断调用图成环用,见 dbg_console_drain 故障路径注释
*/
uint8_t dbg_console_tx_fault_log_take(void) {
uint8_t pending = s_con_tx_fault_log;
s_con_tx_fault_log = 0U;
return pending;
}
/*
* 函数功能:MicroLIB printf 底层重定向(库内部/遗留 printf 调用)
* 函数说明:同样只把字节交给单写者路径;未注册写者时直发(与旧行为一致)
+6
View File
@@ -37,6 +37,11 @@ extern "C" {
* dbg_console_drain - 写者任务排空缓冲并发送(单次有界;TX 失败自动软复位自愈)
* dbg_console_dropped - 因缓冲满丢弃的行数(诊断)
* dbg_console_tx_fault - 因僵尸通道软复位自愈的次数(诊断)
* dbg_console_tx_fault_log_take
* - 取走"TX 故障待上报"标志(读后清)。**必须**由写者任务在
* dbg_console_drain() 之外调用后自行 syslog_post——本模块
* 刻意不调日志子系统,否则形成 write_line→con_append→drain
* →syslog_post→write_line 调用图成环,静态栈深全线虚高
*/
#define DBG_CON_LINE_MAX 192U /* 单行文本上限(含结尾 '\0',超出截断) */
int dbg_console_write_line(const char *line, unsigned len);
@@ -45,6 +50,7 @@ void dbg_console_writer_attach(void);
void dbg_console_drain(void);
uint32_t dbg_console_dropped(void);
uint32_t dbg_console_tx_fault(void);
uint8_t dbg_console_tx_fault_log_take(void);
/*
* 函数功能:MicroLIB printf 底层重定向(C 库 retarget 钩子,须为全局符号)
+7 -4
View File
@@ -67,7 +67,8 @@ static volatile uint8_t s_mount_fail_reported; /* P2-9:建目录失败日
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载
*/
static int s_append(syslog_id_t id, const char *desc) {
FIL f;
static FIL f; /* static:FF_FS_TINY=0 → 局部 FIL 吃 528 B 栈;
* 本函数全程在 vol_lock 区间内,跨任务被卷锁串行化 */
FRESULT fr;
UINT bw = 0u;
uint8_t rec[SYSLOG_REC_SIZE];
@@ -168,7 +169,8 @@ static int s_append(syslog_id_t id, const char *desc) {
* 心跳,绝不能阻塞等锁;busy = 本次读失败(UI 下轮刷新重读,非故障)。
*/
static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) {
FIL f;
static FIL f; /* static:省 528 B 栈;open~close 全在卷锁区间内
* (uiTask 与写者任务由 vol_lock 串行化,见上方 P2-10 说明) */
FRESULT fr;
UINT br = 0u;
int rc;
@@ -439,11 +441,12 @@ int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch) {
* 容量护栏:> 8KB 先 unlink 重建,防长跑无界增长(8.3 合法名 wdev.log)。
*/
int sys_log_evidence(const char *p_desc) {
FIL f;
static FIL f; /* static:省 528 B 栈(FF_FS_TINY=0 → FIL=528 B) */
static char line[192]; /* static:省 192 B 栈;本函数只有 poller(defaultTask)
* 一个调用者(task_wd_poll 内),且全程在卷锁区间内 */
FRESULT fr;
UINT bw = 0u;
UINT line_len;
char line[192];
char ts[SYSLOG_TS_LEN + 1u];
int n;
int rc = 0;
+3
View File
@@ -23,6 +23,9 @@
#include "sys_events.h"
#include "task_watchdog.h"
/* 深调用链模块(timeSyncTask 栈仅 2048 B,本任务链 = 建监听 → net_bind → CH395F):
* 日志行缓冲收到 128 B(默认 192),本模块最长行渲染后 < 128 → 不截断 */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[TIME_SYNC]"
#include "dbg_log.h"
+13 -2
View File
@@ -33,6 +33,10 @@
#include "cfg_file.h"
#include "sys_log.h"
#include "sys_events.h"
/* 深调用链模块(本任务 = 存储 bring-up + 配置引擎属主 + syslog 唯一写者,
* 全项目最深链在此):日志行缓冲收到 128 B(默认 192),本模块最长行渲染后
* < 128 → 不截断 */
#define DBG_LINE_BUF_MAX 128U
#include "dbg_log.h"
#include "task_watchdog.h"
#include "vol_lock.h"
@@ -191,6 +195,12 @@ void default_task_func(void *argument) {
/* 控制台唯一发送点:把环形缓冲中的日志按块发出(无待发时立即返回) */
task_wd_seg(TASK_WD_SEG_DRAIN);
dbg_console_drain();
/* 控制台 TX 故障留痕:由本任务(syslog 唯一写者)在 drain **之外**补记 run.log。
* console_log 内部刻意不调 syslog_post —— 那会构成日志调用图成环,
* 让 armlink 静态栈深在每个任务上都多叠一条日志尾巴(见 console_log.c) */
if (dbg_console_tx_fault_log_take() != 0U) {
syslog_post(SYSLOG_RUN, "console tx fault, uart reinit");
}
/* 执行待处理保存(配置引擎属主:写盘 + 成功后立即应用);无请求 = 空操作 */
#if !defined(TEST_SUITE_STORAGE)
task_wd_seg(TASK_WD_SEG_CFGSAVE);
@@ -405,7 +415,8 @@ static FRESULT storage_bring_up(FATFS *fs) {
* 判定、不碰盘)→ 曾误入下方结构性错误集合 → 每 boot 假失败 →
* 无限重格式化。探针名必须严格 8.3:名≤8、扩展≤3、不以点开头。 */
#define FS_VOL_PROBE(h) do { \
FIL __chk; \
static FIL __chk; /* static:FF_FS_TINY=0 → 局部 FIL 吃 528 B 栈;探针只在 \
* defaultTask 启动/自愈路径跑,无并发 */ \
static const uint8_t __chkbuf[8] = { 0 }; \
UINT __bw = 0u; \
FRESULT __wr; \
@@ -465,7 +476,7 @@ static FRESULT storage_bring_up(FATFS *fs) {
DBG_INFO("[FS] FATFS mounted");
/* 日志引擎挂载后处理:建 /log 目录 + 写"上电/重启"分类记录(唯一写者) */
syslog_mcu_mounted();
/* 加载网络配置进 RAM 副本(配置引擎属主 = 本任务;首启含旧档迁移;
/* 加载网络配置进 RAM 副本(配置引擎属主 = 本任务;只读 INI,
* 文件缺失/损坏 = 工厂默认,不阻塞组网)。须在置位 STORAGE_READY 前,
* 使"存储就绪"屏障蕴含"配置已就绪",netTask 组网时配置恒有效 */
(void)net_cfg_load();
+3 -2
View File
@@ -10,8 +10,9 @@
| `task_watchdog.c/h` | **独立看门狗(IWDG)判定状态机**:任务心跳位图 + defaultTask 单一喂狗者 |
> **已删除(2026-10-02)**:`crc.c/h`(CRC-16 Modbus / CRC-8)与 `ringbuf.c/h`(SPSC 环形缓冲)
> 两模块**零调用者**且未编入任何构建——CRC 唯一使用点 `net_cfg_legacy.c` 自带静态
> `s_crc16_modbus()`,环形缓冲由 `App/log/console_log.c` 内联实现(`s_con_ring`)。
> 两模块**零调用者**且未编入任何构建——CRC 从未被链接使用:名义上的关联点 `net_cfg_legacy.c`
> (旧档 `/cfg/netcfg.bin` 迁移,**该文件亦已于 2026-10-10 删除**)自带静态 `s_crc16_modbus()`,
> 从不调用本模块;环形缓冲由 `App/log/console_log.c` 内联实现(`s_con_ring`)。
> 保留只会造成"有统一实现"的误导,故整体移除(详见 `docs/待处理内容.md` E-15 销项)。
## 设计要点
+5
View File
@@ -24,7 +24,12 @@
/*
* 调试输出配置
* 深调用链模块(ch395f_spi_fault_set 等出现在 netTask/ftpTask/timeSyncTask
* 最坏链尾部):日志行缓冲收到 128 B(默认 192)。本模块最长行
* "HAL_SPI_TransmitReceive_DMA FAIL: %d, SPI_State=%d, SPI_Error=0x%lX, DMA_State=%d"
* 渲染后 ≈ 102 B(含时间戳 21 + 标签)< 128 → 不截断
*/
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[CH395]"
#include "dbg_log.h"
+4
View File
@@ -16,7 +16,11 @@
/*
* 调试输出配置
* 深调用链模块(NAND 驱动在 FTL 之下):日志行缓冲收到 128 B(默认 192)。
* 本模块最长行 "ID mismatch: expected MID=0x.. DID=0x.., got MID=0x.. DID=0x.."
* 渲染后 ≈ 103 B(含时间戳 21 + 标签)< 128 → 不截断
*/
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[NAND]"
#include "dbg_log.h"
+5
View File
@@ -26,6 +26,11 @@
#define FTL_FATFS_OFFSET 0U
/* 调试输出配置 */
/* 深调用链模块(FatFS → 本 FTL → dhara → NAND 是 defaultTask 最坏链的主干):
* 日志行缓冲收到 128 B(默认 192)。本模块最长行 "[2026-10-10 08:00:00][ERR][NAND_FTL]
* Cache flush failed: LPN=4294967295 err=-2147483648" ≈ 84 B < 128 → 不截断。
* 核算 scripts/stack_report.py,见 docs/内存管理/RAM使用分析与优化方案.md */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[NAND_FTL]"
#include "dbg_log.h"
+4
View File
@@ -25,7 +25,11 @@
/*
* 调试输出配置
* 深调用链模块(net_bind/net_send/net_poll 是 netTask/ftpTask/timeSyncTask
* 最坏链的主干):日志行缓冲收到 128 B(默认 192)。本模块最长行渲染后
* ≈ 100 B(含时间戳 21 + 标签)< 128 → 不截断
*/
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[NET]"
#include "dbg_log.h"
+8 -3
View File
@@ -1,6 +1,6 @@
# Drivers/BSP/NETCFG/ — 网络配置
> 网络参数的**加载 / 保存 / 旧档迁移**(IP、掩码、网关等)。
> 网络参数的**加载 / 保存**(IP、掩码、网关等)。
> 本模块是**网络配置的持久化层**,与 `App/cfg/cfg_file.c`(配置引擎)配合工作。
## 文件一览
@@ -8,12 +8,17 @@
| 文件 | 作用 |
|---|---|
| `net_cfg.c/h` | 网络配置加载(`net_cfg_load`)/ 保存(`net_cfg_save_run`)+ RAM 副本管理 |
| `net_cfg_legacy.c/h` | **旧版配置档迁移**(首启时把旧格式转换为当前格式) |
> **已删除(2026-10-10)**:`net_cfg_legacy.c/h`——旧二进制档 `/cfg/netcfg.bin` 的"首启自动迁移"。
> 现口径:旧档若存在则被**忽略**(不读、不删、不迁移),配置直接回落工厂默认(`/config.ini`
> `[network]` 段)。删除理由:该一次性路径把整条配置写链(`net_cfg_save → cfg_file_save →
> cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)挂进启动期调用图,使 defaultTask 的
> armlink 静态最坏栈链 +600 B,而本仓库不存在携带旧档的设备。
## 工作流
```
defaultTask:net_cfg_load(含旧档迁移)→ 置 STORAGE_READY
defaultTask:net_cfg_load → 置 STORAGE_READY
↓ (网络配置在屏障前已加载)
netTask:只读 RAM 副本 → net_stack_bring_up(不在 netTask 里读盘)
↓
+12 -7
View File
@@ -2,8 +2,7 @@
* 模块名称:Network Configuration Core (配置核心)
* 模块功能:RAM 配置副本 + 工厂默认 + 加载/保存编排 + 实际 MAC 影子 +
* IPv4 字符串转换;存储经 cfg_file 通用配置文件引擎(INI [network]
* 段:ip/mask/gw/mac);旧版 NCFG 二进制格式归 net_cfg_legacy
* (首启一次性迁移)
* 段:ip/mask/gw/mac)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
@@ -12,6 +11,12 @@
* v1.1 2026-09-14 王建锋 存储迁移至 cfg_file 引擎(INI);旧 NCFG 代码移至 net_cfg_legacy
* v1.2 2026-09-15 王建锋 保存改跨任务交接(request/state/run + 通知回调);
* 引擎属主 = defaultTask(P1-3 竞态修复)
* v1.3 2026-10-10 王建锋 **删除旧档 NCFG 迁移**(net_cfg_legacy 整体移除):该兼容
* 路径把整条配置写链(net_cfg_save → cfg_file_save →
* cfg_store_write_atomic → f_rename → FTL)挂进启动期调用图,
* 使 defaultTask 静态最坏链 +600 B;本仓库无携带
* /cfg/netcfg.bin 的设备,故按"不保留旧档兼容"处置。
* 旧档若存在 → 被忽略(不读、不删、不迁移),配置回落工厂默认
*/
/* 头文件包含区 */
@@ -23,10 +28,11 @@
#include "net_cfg.h"
#include "net_cfg_legacy.h"
#include "cfg_file.h"
/* 深调用链模块(net_cfg_load/save 在 defaultTask 启动链与保存链上):
* 日志行缓冲收到 128 B(默认 192),本模块最长行渲染后 < 128 → 不截断 */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[NET_CFG]"
#include "dbg_log.h"
@@ -140,9 +146,8 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
int net_cfg_load(void) {
s_net_register();
/* 首启先迁旧档 NCFG(有效 → 旧值物化进新文件);新文件已存在则跳过 */
(void)net_cfg_legacy_migrate();
/* 旧档 NCFG 迁移已于 2026-10-10 删除(net_cfg_legacy 移除;理由见文件头 v1.3)。
* 若卷上仍残留 /cfg/netcfg.bin:不读、不删、不迁移 → 配置回落工厂默认 */
if (cfg_file_load() != 0) {
/* 严重错误:引擎已将 RAM 副本复位工厂默认,行为仍安全 */
DBG_ERROR("load: cfg_file_load FAIL");
+3 -2
View File
@@ -7,8 +7,9 @@
* = 不覆盖芯片内置 MAC。cfg_file 引擎为静态单任务设计,调用收敛于
* 单一属主任务(MCU = defaultTask:就绪屏障前加载 / 保存执行 / 2s
* poll;模拟器 = 主循环);UI 保存只投递请求,netTask 只读 RAM 副本
* 函数说明:旧版 NCFG 二进制格式(magic+ver+CRC-16+18B 字段)归 net_cfg_legacy,
* 首启一次性迁移进通用配置文件
* 函数说明:旧版 NCFG 二进制格式(magic+ver+CRC-16+18B 字段)的迁移路径
* (net_cfg_legacy)**已于 2026-10-10 删除** → 旧档 /cfg/netcfg.bin
* 不再读取/迁移,携带旧档的设备升级后回落工厂默认(理由见 net_cfg.c v1.3)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-10
-125
View File
@@ -1,125 +0,0 @@
/*
* 模块名称:net_cfg_legacy
* 模块功能:旧版 NCFG 二进制配置一次性迁移——读遗留文件 → 校验 magic/ver/CRC →
* 填网络配置 RAM 副本 → 经 cfg_file 引擎生成新文件(带旧值)→ 删旧文件
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用;平台路径经 cfg_file_store 后端)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(P3:net_cfg 引擎化,旧格式代码迁入)
*/
/* 头文件包含区 */
#include <string.h>
#include "net_cfg_legacy.h"
#include "net_cfg.h"
#include "cfg_file.h"
#include "cfg_file_store.h"
#define DBG_TAG "[NET_CFG]"
#include "dbg_log.h"
/* 常量定义区 */
#define NET_CFG_MAGIC 0x4647434Eu /* 'NCFG'(文件字节序 N,C,G,F;小端装载) */
#define NET_CFG_VER 1u /* 磁盘格式版本 */
/* 旧磁盘格式(两端布局一致:u32 4 对齐;字段区 18B 连续,CRC 覆盖之) */
typedef struct {
uint32_t magic; /* NET_CFG_MAGIC */
uint16_t ver; /* 格式版本 */
uint16_t crc; /* CRC-16/MODBUS(覆盖 ip..mac 共 18B) */
uint8_t ip[4];
uint8_t mask[4];
uint8_t gw[4];
uint8_t mac[6];
} net_cfg_blob_t;
/* 静态函数声明区 */
static uint16_t s_crc16_modbus(const uint8_t *p_data, size_t len);
/* 函数定义区 */
/*
* 函数功能:CRC-16/MODBUS(无查表逐位算法)
* 入口参数:p_data - 数据指针 const uint8_t *
* len - 数据长度 size_t
* 返回值:CRC-16 值 uint16_t
* 限定条件:p_data 非空(len 可为 0,返回初始值 0xFFFF)
* 函数说明:文件私有(数据量 ≤32B,性能无关)
*/
static uint16_t s_crc16_modbus(const uint8_t *p_data, size_t len) {
uint16_t crc = 0xFFFFu;
for (size_t i = 0u; i < len; i++) {
crc = (uint16_t)(crc ^ (uint16_t)p_data[i]);
for (int b = 0; b < 8; b++) {
if (crc & 0x0001u) {
crc = (uint16_t)((crc >> 1) ^ 0xA001u);
}
else {
crc = (uint16_t)(crc >> 1);
}
}
}
return crc;
}
/*
* 函数功能:旧 NCFG 二进制配置一次性迁移到通用配置文件
* 入口参数:无
* 返回值:0 - 迁移成功,或无旧文件/新文件已存在(正常);-1 - 迁移失败 int
* 限定条件:调用方须已注册 [network] 段(net_cfg_load 内部完成)
* 函数说明:见 net_cfg_legacy.h 同名函数说明
*/
int net_cfg_legacy_migrate(void) {
net_cfg_blob_t blob;
uint8_t buf[32];
size_t nread = 0u;
cfg_file_stat_t st;
net_cfg_t tmp;
const char *p_legacy;
/* 新文件已存在 = 已迁移过(或已有配置):跳过 */
if (cfg_store_stat(cfg_store_config_path(), &st) == 0) {
return 0;
}
p_legacy = cfg_store_legacy_path();
if (cfg_store_read(p_legacy, buf, sizeof(buf), &nread) != 0
|| nread != sizeof(net_cfg_blob_t)) {
DBG_INFO("legacy: no legacy file, skip");
return 0;
}
(void)memcpy(&blob, buf, sizeof(blob));
if (blob.magic != NET_CFG_MAGIC || blob.ver != NET_CFG_VER
|| s_crc16_modbus(blob.ip, 18u) != blob.crc) {
DBG_INFO("legacy: blob invalid (magic/ver/crc), file kept for inspection");
return 0;
}
/* 填 RAM 副本(net_cfg_get 只读,经 net_cfg_set 可写接口整体回灌)
* → 经引擎物化旧值到新文件(此时新文件必缺失 → 生成模板带旧值) */
(void)memcpy(&tmp, net_cfg_get(), sizeof(tmp));
(void)memcpy(tmp.ip, blob.ip, 4u);
(void)memcpy(tmp.mask, blob.mask, 4u);
(void)memcpy(tmp.gw, blob.gw, 4u);
(void)memcpy(tmp.mac, blob.mac, 6u);
net_cfg_set(&tmp);
if (net_cfg_save() != 0) {
DBG_ERROR("legacy: migrate save FAIL");
return -1;
}
(void)cfg_store_remove(p_legacy);
DBG_INFO("legacy: migrated to config file (ip=%u.%u.%u.%u)",
(unsigned)blob.ip[0], (unsigned)blob.ip[1],
(unsigned)blob.ip[2], (unsigned)blob.ip[3]);
return 0;
}
-35
View File
@@ -1,35 +0,0 @@
/*
* 模块名称:net_cfg_legacy
* 模块功能:旧版 NCFG 二进制配置一次性迁移到通用配置文件(net_cfg 旧磁盘格式
* 的拥有者:magic 'NCFG' + ver + CRC-16/MODBUS + 18B 字段)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用;平台路径经 cfg_file_store 后端)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(P3:net_cfg 引擎化,旧格式代码迁入)
*/
#ifndef NET_CFG_LEGACY_H
#define NET_CFG_LEGACY_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* 函数功能:旧 NCFG 二进制配置(28B)一次性迁移到通用配置文件
* 入口参数:无
* 返回值:0 - 迁移成功,或无旧文件/新文件已存在(正常);
* -1 - 迁移失败(新文件未生成) int
* 限定条件:调用方须已注册 [network] 段(net_cfg_load 内部完成)
* 函数说明:新文件已存在 → 跳过(已迁移过);旧文件有效 → 填网络配置 RAM
* 副本 → 经引擎生成新文件(带旧值)→ 删旧文件;旧文件缺失/无效 →
* 不动旧文件(留人工检查),新文件按工厂默认生成
*/
int net_cfg_legacy_migrate(void);
#ifdef __cplusplus
}
#endif
#endif /* NET_CFG_LEGACY_H */
+1 -1
View File
@@ -13,7 +13,7 @@
| `SD2506/` | SD2506 RTC | I2C | 实时时钟驱动 | `docs/问题记录/SD2506_Trap_Records.md` |
| `RS485/` | RS-485 半双工 | UART5 | 收发方向控制(DE 引脚)+ 时序 | — |
| `NET/` | BSD Socket 网络抽象层 | — | Socket API + select + 轻量 FTP 服务器 | `docs/网络/`(含 `Net层阻塞模型架构设计.md`) |
| `NETCFG/` | 网络配置 | — | `/config.ini` 网络段的加载/保存 + 旧档迁移 | `docs/子系统设计/网络配置说明.md` |
| `NETCFG/` | 网络配置 | — | `/config.ini` 网络段的加载/保存(旧 NCFG 档已删除,2026-10-10) | `docs/子系统设计/网络配置说明.md` |
| `UI/` | LVGL UI 组件 | — | 菜单/内容页/组件/中文字体 | `docs/UI设计/` |
## 根级文件
+60 -23
View File
@@ -42,12 +42,49 @@ extern "C" {
*/
#ifdef DBG_ENABLE
/*
* 行缓冲上限(栈预算旋钮,2026-10-10 加)
* 默认 = 控制台单行上限 DBG_CON_LINE_MAX(192)。
* ⚠ 本数组是**在每个日志调用点的栈帧里内联**的,会进 armlink 最坏调用链:
* 实测存储/配置那条链上 9 个帧各含 192 B 行缓冲 + 32 B 时间戳缓冲
* → defaultTask 静态最坏链 5312 B 里约 1.5 KB 纯粹是日志行缓冲。
* 故**深调用链模块**(存储 FTL/NAND、配置引擎、网络驱动、时间同步)应在
* 包含本文件前显式收紧,例如 `#define DBG_LINE_BUF_MAX 128U`;
* 取值须 ≥ 该模块最长日志行(含时间戳 20 + 标签 + 正文),否则该行被截断。
* 核算:scripts/stack_report.py(改前/改后各跑一次)
*/
#ifndef DBG_LINE_BUF_MAX
#define DBG_LINE_BUF_MAX DBG_CON_LINE_MAX
#endif
/*
* 时间戳文本缓冲上限:sys_clock_get_str() 恒写 "----/--/-- --:--:--" 或
* "%04d-%02d-%02d %02d:%02d:%02d" = 19 字符 + '\0' → 24 足够(原 32 是随手取整,
* 每个日志调用点白占 8 B 栈)
*/
#define DBG_TS_BUF_MAX 24U
/*
* 有界格式化 + 交单写者控制台(行缓冲由调用方提供,便于与时间戳缓冲共用数组)
*/
#define DBG_EMIT(p_line, ...) do { \
int __con_n = snprintf((p_line), DBG_LINE_BUF_MAX, __VA_ARGS__); \
if (__con_n > 0) { \
unsigned __con_len = (__con_n >= (int)DBG_LINE_BUF_MAX) \
? (unsigned)(DBG_LINE_BUF_MAX - 1u) : (unsigned)__con_n; \
(void)dbg_console_write_line((p_line), __con_len); \
} \
} while (0)
/*
* 可重定向的打印函数,用户可在编译选项或 net_config.h 中预定义
* #define DBG_PRINT(...) my_printf(__VA_ARGS__)
* ⚠ 2026-10-10 起:带时间戳的 DBG_ERROR/DBG_INFO/DBG_DEBUG 走 **DBG_EMIT**
* (与行缓冲共用一个数组,省一帧栈);要整体改道请重定义 `DBG_EMIT(p_line, ...)`
* ——它是所有日志的唯一落地点。只定义 DBG_PRINT 仅影响不带时间戳的 DBG_PRINT。
*
* 默认走单写者控制台(App/log/console_log.c):
* DBG_PRINT 在调用点用库 snprintf 有界格式化整行,再一次性交给 dbg_console_write_line
* DBG_EMIT 在调用点用库 snprintf 有界格式化整行,再一次性交给 dbg_console_write_line
* 入环形缓冲,由 defaultTask 唯一发送——
* ① 行与行不会字符级交错(旧 printf→fputc 每字符一次 HAL_UART_Transmit 会交错);
* ② 进 UART 的缓冲恒为控制台内部缓冲,调用方脏指针到不了 UART
@@ -55,13 +92,8 @@ extern "C" {
*/
#ifndef DBG_PRINT
#define DBG_PRINT(...) do { \
char __con_line[DBG_CON_LINE_MAX]; \
int __con_n = snprintf(__con_line, sizeof(__con_line), __VA_ARGS__); \
if (__con_n > 0) { \
unsigned __con_len = (__con_n >= (int)sizeof(__con_line)) \
? (unsigned)(sizeof(__con_line) - 1u) : (unsigned)__con_n; \
(void)dbg_console_write_line(__con_line, __con_len); \
} \
char __con_line[DBG_LINE_BUF_MAX]; \
DBG_EMIT(__con_line, __VA_ARGS__); \
} while (0)
#endif
@@ -75,29 +107,34 @@ extern "C" {
#ifdef APP_TIMESTAMP_ENABLE
/*
* 时间戳输出宏 — 在日志行首追加 [YYYY-MM-DD HH:MM:SS] 格式的系统墙钟时间
* sys_clock_get_str() 线程安全(内部使用 DWT US 计数,无锁)
* 兼容宏:单独取时间戳文本(当前无调用者;缓冲为复合字面量,仅在本表达式内有效)
*/
#define DBG_TS() sys_clock_get_str((char[32]){0}, 32)
#define DBG_TS() sys_clock_get_str((char[DBG_TS_BUF_MAX]){0}, DBG_TS_BUF_MAX)
/*
* DBG_ERROR / DBG_INFO / DBG_DEBUG(带时间戳版)
* 时间戳缓冲与行缓冲**共用一个数组**(每个调用点省一帧 24 B 栈):行文本占前
* DBG_LINE_BUF_MAX 字节,时间戳文本切片自其后。snprintf 的全部实参先于其执行
* 求值完毕,故"先写时间戳切片、再格式化行首"不冲突。
*/
/*
* DBG_ERROR - 错误输出,受 DBG_ENABLE + APP_TIMESTAMP_ENABLE 控制
*/
#define DBG_ERROR(fmt, ...) do { \
char __ts_buf[32]; \
(void)__ts_buf; \
DBG_PRINT("[%s]" "[ERR]" DBG_TAG " " fmt "\r\n", \
sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), ##__VA_ARGS__); \
char __con_line[DBG_LINE_BUF_MAX + DBG_TS_BUF_MAX]; \
DBG_EMIT(__con_line, "[%s]" "[ERR]" DBG_TAG " " fmt "\r\n", \
sys_clock_get_str(__con_line + DBG_LINE_BUF_MAX, DBG_TS_BUF_MAX), \
##__VA_ARGS__); \
} while (0)
/*
* DBG_INFO - 信息输出,受 DBG_ENABLE + APP_TIMESTAMP_ENABLE 控制
*/
#define DBG_INFO(fmt, ...) do { \
char __ts_buf[32]; \
(void)__ts_buf; \
DBG_PRINT("[%s]" DBG_TAG " " fmt "\r\n", \
sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), ##__VA_ARGS__); \
char __con_line[DBG_LINE_BUF_MAX + DBG_TS_BUF_MAX]; \
DBG_EMIT(__con_line, "[%s]" DBG_TAG " " fmt "\r\n", \
sys_clock_get_str(__con_line + DBG_LINE_BUF_MAX, DBG_TS_BUF_MAX), \
##__VA_ARGS__); \
} while (0)
/*
@@ -105,10 +142,10 @@ extern "C" {
*/
#ifdef DBG_DEBUG_ENABLE
#define DBG_DEBUG(fmt, ...) do { \
char __ts_buf[32]; \
(void)__ts_buf; \
DBG_PRINT("[%s]" DBG_TAG "(%s:%d) " fmt "\r\n", \
sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), __func__, __LINE__, ##__VA_ARGS__); \
char __con_line[DBG_LINE_BUF_MAX + DBG_TS_BUF_MAX]; \
DBG_EMIT(__con_line, "[%s]" DBG_TAG "(%s:%d) " fmt "\r\n", \
sys_clock_get_str(__con_line + DBG_LINE_BUF_MAX, DBG_TS_BUF_MAX), \
__func__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define DBG_DEBUG(fmt, ...)
-5
View File
@@ -1458,11 +1458,6 @@
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NETCFG\net_cfg.c</FilePath>
</File>
<File>
<FileName>net_cfg_legacy.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NETCFG\net_cfg_legacy.c</FilePath>
</File>
<File>
<FileName>nand_ftl.c</FileName>
<FileType>1</FileType>
+1 -1
View File
@@ -130,7 +130,7 @@ STM32F4-Base/
│ │ │ ├── net_socket.c/h # Socket API
│ │ │ ├── net_select.c/h # select/poll 多路复用
│ │ │ └── lftpd/ # 轻量 FTP 服务器
│ │ ├── NETCFG/ # 网络配置(加载/保存/旧档迁移)
│ │ ├── NETCFG/ # 网络配置(/config.ini [network] 段加载/保存)
│ │ ├── UI/ # LVGL UI 组件
│ │ └── dbg_log.h / dbg_cfg.h # 调试日志(DBG_INFO 等)
│ ├── STM32F4xx_HAL_Driver/ # ST HAL 库
+28 -11
View File
@@ -94,15 +94,24 @@ static int hook_ptr_ok(const void *p)
* defaultTask 1024 words:扛 mount+mkfs+FAT 探针+cfg 加载+syslog 写+配置引擎
* (全深层 FatFS I/O)。原 512,2KB 实测峰值溢出——开启
* configCHECK_FOR_STACK_OVERFLOW=2 后被钩子抓到(2026-09-21)。
* netTask 960 words:静态最坏链 2664 B(发送停滞→断连→重监听→SPI 故障→日志尾,
* 真实直调链,日志主导:链上 5 帧各含 192 B __con_line + 尾部 ~696 B);3840 B
* 配置余量 1176 B(30.6%),过 stack_report 规则。运行时水位仅 ~1.1 KB(错误尾未
* 2026-10-10 栈整改(**未抬栈**,靠削链达标):静态最坏链 5312 → 2776 B、
* 实测峰值 3392 → 2152 B(余 1944 B / 47%)。四个杠杆:① 删旧档 NCFG 迁移
* (net_cfg_legacy,−600 B 链);② 6 处局部 `FIL` 改函数级 static(FF_FS_TINY=0
* → 单个 FIL = 528 B 栈);③ 日志行缓冲按模块收紧 `DBG_LINE_BUF_MAX 128`
* + 时间戳缓冲并入行缓冲(每个日志调用点原内联 192+32 B);④ 断言/故障钩子
* 脱离 HAL 发送回退 + 缓冲静态化(configASSERT 在 FreeRTOS 内部可达 →
* 钩子在**每个**任务的最坏链尾)。详见 docs/内存管理/RAM使用分析与优化方案.md
* netTask 960 words:静态最坏链 1872 B(2026-10-10 整改后;整改前 2664 B:
* 发送停滞→断连→重监听→SPI 故障→日志尾,日志主导);3840 B 配置余量 1968 B
* (51%),过 stack_report 规则。运行时水位仅 ~0.9 KB(错误尾未
* 触发,不可作依据)。曾试 net_drain_chip_rx scratch 静态化,实测仅省 16 B 栈
* (ARMCC 已叠放不重叠局部量)反增 256 B BSS,已回退——见 net_socket.c 注释。
* testTask 2048 words:存储套件 deep call chain + 局部缓冲,原 1024(4 KB)实测溢出
* (2026-09-20)。仅启用测试套件时创建。
* timeSyncTask 512 words:本区下方手工定义(不在 `.ioc` 的 Tasks01 里 → 重生成会丢,
* 靠审计 WARN 提示),只做读 RTC + 置就绪事件位,无深调用链。
* 靠审计 WARN 提示),建监听 → net_bind → CH395F → 日志尾。2026-10-10 整改后
* 静态最坏链 1024 B(整改前 1736 B)、实测峰值 616 B;⚠ 余量 1024 B **正好**
* 等于 ISR 底线 → 本任务链上再加任何调用(或新日志行)都必须先抬栈。
*/
/* 存储测试变量(g_storage_stats / s_perf_buf)已迁至 test/board/storage_test_task.c */
osThreadId_t timeSyncTaskHandle;
@@ -435,7 +444,10 @@ void vApplicationAssertFailed(const char *file, long line)
{
static const char msg_head[] = "\r\n[ERR] ASSERT FAILED ";
const char *p = file;
char num[16];
/* 缓冲一律 static(不放栈上):本钩子是"内核状态已不可信"的终点路径,打印完即
* 复位,不存在并发/重入;放栈上会让 armlink 把这 81 B 挂进每个任务的最坏链
* (configASSERT 在 FreeRTOS 内部可达 → 每条链尾都有本钩子) */
static char num[16];
int i;
/* 文件名只取末段(全路径太长且无信息量) */
@@ -449,8 +461,8 @@ void vApplicationAssertFailed(const char *file, long line)
}
/* 有界拷贝:上限 48 字符 */
{
static char name[49]; /* static 存储(见上),作用域收到本块 */
uint16_t len = 0u;
char name[49];
while ((len < 48u) && (p[len] != '\0')) {
name[len] = p[len];
len++;
@@ -471,21 +483,26 @@ void vApplicationAssertFailed(const char *file, long line)
if (line <= 0) {
num[i++] = '0';
} else {
char rev[16];
int n = 0;
int j;
while ((line > 0) && (n < 15)) {
rev[n++] = (char)('0' + (line % 10));
num[n++] = (char)('0' + (line % 10)); /* 先逆序填入 */
line /= 10;
}
while (n > 0) {
num[i++] = rev[--n];
for (j = 0; j < n / 2; j++) { /* 原地反转:省一个 16 B 缓冲 */
char t = num[j];
num[j] = num[n - 1 - j];
num[n - 1 - j] = t;
}
i = n;
}
dbg_console_write_raw(num, (unsigned)i);
dbg_console_write_raw(" (resetting)\r\n", 15u);
HAL_Delay(200); /* 等 UART 移位寄存器排空 */
/* 无需 HAL_Delay:dbg_console_write_raw → con_tx_bounded 已等到 TC(移位寄存器
* 排空)才返回;去掉 HAL_Delay 亦少一条 HAL 依赖(断言现场 tick 未必可信) */
NVIC_SystemReset();
}
/* USER CODE END Application */
+1 -1
View File
@@ -80,7 +80,7 @@ docs/
| 文档 | 内容 |
|---|---|
| [`配置文件设计说明.md`](子系统设计/配置文件设计说明.md) | `/config.ini` 配置引擎:分层、解析、持久化与旧档迁移 |
| [`配置文件设计说明.md`](子系统设计/配置文件设计说明.md) | `/config.ini` 配置引擎:分层、解析、原子持久化(旧 NCFG 档已废弃) |
| [`网络配置说明.md`](子系统设计/网络配置说明.md) | 网口设置页:IP/掩码/网关,UI 保存即时生效 |
| [`系统墙钟时间维护说明.md`](子系统设计/系统墙钟时间维护说明.md) | `sys_clock`:RTC 基准、tick 回绕防护、30 天惰性再基准化 |
| [`控制台单写者日志设计说明.md`](子系统设计/控制台单写者日志设计说明.md) | 控制台日志单写者模型(避免多任务交错输出) |
+14 -14
View File
@@ -8,39 +8,39 @@
</style>
</defs>
<text x="440.0" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(48KB 堆,2026-09-21 已迁 CCM)</text>
<text x="440.0" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-09-21 @build + FTP 128KB 实测(data/stack_report_20260921.md)</text>
<text x="440.0" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-10 @build + 开机/空闲实测(data/stack_report_20261010.md);最坏 = max(静态最坏链, 实测峰值)</text>
<text x="190" y="54" class="legend-text" fill="#555">浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定</text>
<text x="186" y="72" text-anchor="end" dominant-baseline="central" class="label" fill="#333">defaultTask</text>
<rect class="bar-seg" x="190" y="64" width="162.5" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="64" width="110.1" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="91" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 2136B | 最坏 2776B | 余量 1320B (32%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="190" y="91" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 2152B | 最坏 2776B | 余量 1320B (32%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="112" text-anchor="end" dominant-baseline="central" class="label" fill="#333">netTask</text>
<rect class="bar-seg" x="190" y="104" width="162.5" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="104" width="23.3" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="131" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 588B | 最坏 588B | 余量 3508B (85%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect class="bar-seg" x="190" y="104" width="152.3" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="104" width="74.3" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="131" dominant-baseline="central" class="legend-text" fill="#333">配置 3840B | 实测峰值 920B | 最坏 1872B | 余量 1968B (51%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="152" text-anchor="end" dominant-baseline="central" class="label" fill="#333">ftpTask</text>
<rect class="bar-seg" x="190" y="144" width="162.5" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="144" width="47.0" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="171" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 1184B | 最坏 1184B | 余量 2912B (71%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect x="190" y="144" width="48.2" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="171" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 740B | 最坏 1216B | 余量 2880B (70%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="192" text-anchor="end" dominant-baseline="central" class="label" fill="#333">testTask</text>
<rect class="bar-seg" x="190" y="184" width="325.0" height="16" fill="#FFE0B2" rx="3"/>
<rect x="190" y="184" width="13.8" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="211" dominant-baseline="central" class="legend-text" fill="#333">配置 8192B | 实测峰值 348B | 最坏 348B | 余量 7844B (95%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect x="190" y="184" width="19.7" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="211" dominant-baseline="central" class="legend-text" fill="#333">配置 8192B | 实测峰值 496B | 最坏 496B | 余量 7696B (93%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="232" text-anchor="end" dominant-baseline="central" class="label" fill="#333">uiTask</text>
<rect class="bar-seg" x="190" y="224" width="650.0" height="16" fill="#FFE0B2" rx="3"/>
<rect x="190" y="224" width="151.7" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="251" dominant-baseline="central" class="legend-text" fill="#333">配置 16384B | 实测峰值 3824B | 最坏 3824B | 余量 12560B (76%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect x="190" y="224" width="159.3" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="251" dominant-baseline="central" class="legend-text" fill="#333">配置 16384B | 实测峰值 4016B | 最坏 4016B | 余量 12368B (75%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="272" text-anchor="end" dominant-baseline="central" class="label" fill="#333">timeSyncTask</text>
<rect class="bar-seg" x="190" y="264" width="81.2" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="264" width="16.7" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="291" dominant-baseline="central" class="legend-text" fill="#333">配置 2048B | 实测峰值 420B | 最坏 420B | 余量 1628B (79%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect x="190" y="264" width="40.6" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="291" dominant-baseline="central" class="legend-text" fill="#333">配置 2048B | 实测峰值 616B | 最坏 1024B | 余量 1024B (50%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="314" text-anchor="end" dominant-baseline="central" class="label" fill="#333">idle</text>
<rect class="bar-seg" x="190" y="308" width="20.3" height="12" fill="#90A4AE" rx="2"/>
<text x="214.3125" y="314" dominant-baseline="central" class="legend-text" fill="#333">512B(未实测)</text>
<text x="186" y="336" text-anchor="end" dominant-baseline="central" class="label" fill="#333">timer</text>
<rect class="bar-seg" x="190" y="330" width="40.6" height="12" fill="#90A4AE" rx="2"/>
<text x="234.625" y="336" dominant-baseline="central" class="legend-text" fill="#333">1024B(未实测)</text>
<text x="190" y="360" class="label" fill="#555">堆总量: 49152B (48KB, RW_CCM) 任务栈合计: 38912B (38.0KB) + 内核栈 1536B</text>
<text x="190" y="360" class="label" fill="#555">堆总量: 49152B (48KB, RW_CCM) 任务栈合计: 38656B (37.8KB) + 内核栈 1536B</text>
<text x="190" y="376" class="label" fill="#555">实测: free=8784B / min_ever=4848B(2026-09-21,含 FTP 负载) 判定: 6/6 PASS(余量 ≥ max(1024B, 配置×30%))</text>
<rect x="190" y="400" width="10" height="10" fill="#2196F3" rx="2"/>
<text x="204" y="408" class="legend-text" fill="#333">Normal 优先级</text>

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

+11 -10
View File
@@ -47,8 +47,6 @@ digraph refgraph_full {
"Drivers/BSP/NET/net_types.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NETCFG/net_cfg.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NETCFG/net_cfg.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/NETCFG/net_cfg_legacy.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/RS485/rs485.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/RS485/rs485.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/SD2506/sd2506.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -93,6 +91,7 @@ digraph refgraph_full {
"Drivers/BSP/dbg_log.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_cortex.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_dma.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_exti.h" [fillcolor="#f3f4f6", color="#9ca3af"];
@@ -106,6 +105,7 @@ digraph refgraph_full {
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_tim.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_uart.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Inc/FreeRTOSConfig.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/adc.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/dma.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/gpio.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/i2c.h" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -124,6 +124,7 @@ digraph refgraph_full {
"Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Src/adc.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/dma.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/freertos.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/gpio.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -207,7 +208,6 @@ digraph refgraph_full {
"App/fs/vol_lock.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/log/console_log.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"App/log/console_log.c" -> "App/log/console_log.h" [color="#2563eb", penwidth=1.0];
"App/log/console_log.c" -> "App/log/sys_log.h" [color="#2563eb", penwidth=1.0];
"App/log/console_log.c" -> "Inc/usart.h" [color="#2563eb", penwidth=1.0];
"App/log/console_log.c" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h" [color="#94a3b8", penwidth=0.7];
"App/log/console_log.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
@@ -343,16 +343,9 @@ digraph refgraph_full {
"Drivers/BSP/NET/net_types.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/NETCFG/net_cfg.c" -> "App/cfg/cfg_file.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg.c" -> "Drivers/BSP/NETCFG/net_cfg.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg.c" -> "Drivers/BSP/NETCFG/net_cfg_legacy.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/NETCFG/net_cfg.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" -> "App/cfg/cfg_file.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" -> "App/cfg/cfg_file_store.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" -> "Drivers/BSP/NETCFG/net_cfg.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" -> "Drivers/BSP/NETCFG/net_cfg_legacy.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/NETCFG/net_cfg_legacy.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/RS485/rs485.c" -> "Drivers/BSP/RS485/rs485.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/RS485/rs485.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/SD2506/sd2506.c" -> "Drivers/BSP/SD2506/sd2506.h" [color="#2563eb", penwidth=1.0];
@@ -363,6 +356,9 @@ digraph refgraph_full {
"Drivers/BSP/TPAFE5160/tpafe5160.c" -> "Drivers/BSP/TPAFE5160/tpafe5160.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/TPAFE5160/tpafe5160.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/TPAFE5160/tpafe5160.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/TPAFE5160/tpafe5160.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
"Drivers/BSP/TPAFE5160/tpafe5160.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [color="#94a3b8", penwidth=0.7];
"Drivers/BSP/TPAFE5160/tpafe5160.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/TPAFE5160/tpafe5160.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_content_page.c" -> "Drivers/BSP/UI/ui_content_page.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_content_page.c" -> "Drivers/BSP/UI/ui_deco.h" [color="#2563eb", penwidth=1.0];
@@ -409,6 +405,7 @@ digraph refgraph_full {
"Drivers/BSP/UI/ui_net_sink.c" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_net_sink.c" -> "Drivers/BSP/UI/ui_net_sink.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_net_sink.c" -> "Drivers/BSP/UI/ui_net_sink_backend.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_net_sink.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_net_sink.c" -> "test/board/net_latency_test.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_net_sink.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/UI/ui_net_sink.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
@@ -456,12 +453,14 @@ digraph refgraph_full {
"Drivers/BSP/dbg_log.h" -> "Drivers/BSP/dbg_cfg.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/dbg_log.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Inc/FreeRTOSConfig.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Inc/adc.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/dma.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/gpio.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/i2c.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/iwdg.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/main.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h" [color="#94a3b8", penwidth=0.7];
"Inc/spi.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_cortex.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_dma.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_exti.h" [color="#94a3b8", penwidth=0.7];
@@ -476,6 +475,7 @@ digraph refgraph_full {
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_uart.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Inc/usart.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Src/adc.c" -> "Inc/adc.h" [color="#2563eb", penwidth=1.0];
"Src/dma.c" -> "Inc/dma.h" [color="#2563eb", penwidth=1.0];
"Src/freertos.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"Src/freertos.c" -> "App/sync/time_sync.h" [color="#2563eb", penwidth=1.0];
@@ -508,6 +508,7 @@ digraph refgraph_full {
"Src/main.c" -> "Drivers/BSP/SD2506/sd2506.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Drivers/BSP/TPAFE5160/tpafe5160.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/adc.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/dma.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/gpio.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/i2c.h" [color="#2563eb", penwidth=1.0];
+123 -68
View File
@@ -221,14 +221,6 @@
"path": "Drivers/BSP/NETCFG/net_cfg.h",
"class": "fp"
},
{
"path": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"class": "fp"
},
{
"path": "Drivers/BSP/NETCFG/net_cfg_legacy.h",
"class": "fp"
},
{
"path": "Drivers/BSP/RS485/rs485.c",
"class": "fp"
@@ -537,6 +529,14 @@
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc_ex.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_cortex.h",
"class": "tp"
@@ -621,6 +621,10 @@
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_uart.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_adc.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_bus.h",
"class": "tp"
@@ -677,6 +681,14 @@
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc.c",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc_ex.c",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c",
"class": "tp"
@@ -757,6 +769,10 @@
"path": "Inc/FreeRTOSConfig.h",
"class": "fp"
},
{
"path": "Inc/adc.h",
"class": "fp"
},
{
"path": "Inc/dma.h",
"class": "fp"
@@ -4725,6 +4741,10 @@
"path": "Middlewares/Third_Party/FreeRTOS/Source/timers.c",
"class": "tp"
},
{
"path": "Src/adc.c",
"class": "fp"
},
{
"path": "Src/dma.c",
"class": "fp"
@@ -5883,12 +5903,6 @@
"kind": "fp-fp",
"include": "sys_events.h"
},
{
"from": "App/log/console_log.c",
"to": "App/log/sys_log.h",
"kind": "fp-fp",
"include": "sys_log.h"
},
{
"from": "App/log/console_log.h",
"to": "外部/系统头文件",
@@ -7029,12 +7043,6 @@
"kind": "fp-fp",
"include": "net_cfg.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg.c",
"to": "Drivers/BSP/NETCFG/net_cfg_legacy.h",
"kind": "fp-fp",
"include": "net_cfg_legacy.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg.c",
"to": "App/cfg/cfg_file.h",
@@ -7059,42 +7067,6 @@
"kind": "fp-ext",
"include": "stddef.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"to": "Drivers/BSP/NETCFG/net_cfg_legacy.h",
"kind": "fp-fp",
"include": "net_cfg_legacy.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"to": "Drivers/BSP/NETCFG/net_cfg.h",
"kind": "fp-fp",
"include": "net_cfg.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"to": "App/cfg/cfg_file.h",
"kind": "fp-fp",
"include": "cfg_file.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"to": "App/cfg/cfg_file_store.h",
"kind": "fp-fp",
"include": "cfg_file_store.h"
},
{
"from": "Drivers/BSP/NETCFG/net_cfg_legacy.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "Drivers/BSP/RS485/rs485.c",
"to": "Drivers/BSP/RS485/rs485.h",
@@ -7143,6 +7115,12 @@
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "Drivers/BSP/TPAFE5160/tpafe5160.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "string.h"
},
{
"from": "Drivers/BSP/TPAFE5160/tpafe5160.c",
"to": "Drivers/BSP/TPAFE5160/tpafe5160.h",
@@ -7155,6 +7133,18 @@
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "Drivers/BSP/TPAFE5160/tpafe5160.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h",
"kind": "fp-tp",
"include": "FreeRTOS.h"
},
{
"from": "Drivers/BSP/TPAFE5160/tpafe5160.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/task.h",
"kind": "fp-tp",
"include": "task.h"
},
{
"from": "Drivers/BSP/TPAFE5160/tpafe5160.c",
"to": "Drivers/BSP/dbg_log.h",
@@ -7473,6 +7463,12 @@
"kind": "fp-fp",
"include": "net_latency_test.h"
},
{
"from": "Drivers/BSP/UI/ui_net_sink.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "Drivers/BSP/UI/ui_net_sink.h",
"to": "外部/系统头文件",
@@ -8397,6 +8393,30 @@
"kind": "tp-tp",
"include": "stm32f4xx_hal_conf.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_def.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal_def.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_adc.h",
"kind": "tp-tp",
"include": "stm32f4xx_ll_adc.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc_ex.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal_adc_ex.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc_ex.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_def.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal_def.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_cortex.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_def.h",
@@ -8583,6 +8603,12 @@
"kind": "tp-tp",
"include": "stm32f4xx_hal_def.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_adc.h",
"to": "Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h",
"kind": "tp-tp",
"include": "stm32f4xx.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_bus.h",
"to": "Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h",
@@ -8667,6 +8693,18 @@
"kind": "tp-tp",
"include": "stm32f4xx_hal.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc.c",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc_ex.c",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
@@ -8787,6 +8825,12 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "Inc/adc.h",
"to": "Inc/main.h",
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "Inc/dma.h",
"to": "Inc/main.h",
@@ -8855,8 +8899,8 @@
},
{
"from": "Inc/stm32f4xx_hal_conf.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_adc.h",
"kind": "fp-tp",
"include": "stm32f4xx_hal_adc.h"
},
{
@@ -38853,6 +38897,12 @@
"kind": "fp-tp",
"include": "lvgl.h"
},
{
"from": "Src/adc.c",
"to": "Inc/adc.h",
"kind": "fp-fp",
"include": "adc.h"
},
{
"from": "Src/dma.c",
"to": "Inc/dma.h",
@@ -38991,6 +39041,12 @@
"kind": "fp-tp",
"include": "cmsis_os.h"
},
{
"from": "Src/main.c",
"to": "Inc/adc.h",
"kind": "fp-fp",
"include": "adc.h"
},
{
"from": "Src/main.c",
"to": "Inc/dma.h",
@@ -39970,13 +40026,13 @@
"stddef.h",
"stdint.h"
],
"Drivers/BSP/NETCFG/net_cfg_legacy.c": [
"string.h"
],
"Drivers/BSP/SD2506/sd2506.c": [
"stdio.h",
"string.h"
],
"Drivers/BSP/TPAFE5160/tpafe5160.c": [
"string.h"
],
"Drivers/BSP/UI/ui_content_page.c": [
"string.h"
],
@@ -40145,7 +40201,6 @@
"stdint.h"
],
"Inc/stm32f4xx_hal_conf.h": [
"stm32f4xx_hal_adc.h",
"stm32f4xx_hal_can.h",
"stm32f4xx_hal_can_legacy.h",
"stm32f4xx_hal_cec.h",
@@ -41600,13 +41655,13 @@
]
},
"stats": {
"files_indexed": 1420,
"includes_parsed": 5700,
"resolved": 4630,
"unresolved": 1070,
"files_indexed": 1425,
"includes_parsed": 5706,
"resolved": 4637,
"unresolved": 1069,
"nodes_fp": 147,
"nodes_tp": 1259,
"ext_names": 352
"nodes_tp": 1264,
"ext_names": 351
},
"include_dirs": [
"Inc",
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 361 KiB

+8 -6
View File
@@ -87,17 +87,19 @@ digraph refgraph_module {
"BSP/NET" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NET" -> "test" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NET" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×3"];
"BSP/NETCFG" -> "App" [color="#2563eb", penwidth=1.2, label="×3"];
"BSP/NETCFG" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/NETCFG" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×3"];
"BSP/NETCFG" -> "App" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/NETCFG" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/NETCFG" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×2"];
"BSP/RS485" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/SD2506" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/SD2506" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/SD2506" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×1"];
"BSP/TPAFE5160" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/TPAFE5160" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/TPAFE5160" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/TPAFE5160" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×1"];
"BSP/UI" -> "App/log" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/UI" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×5"];
"BSP/UI" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×6"];
"BSP/UI" -> "BSP/NET" [color="#2563eb", penwidth=1.2, label="×2"];
"BSP/UI" -> "BSP/NETCFG" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP/UI" -> "LVGL" [color="#2563eb", penwidth=1.2, label="×9"];
@@ -112,7 +114,7 @@ digraph refgraph_module {
"FreeRTOS" -> "CMSIS" [color="#94a3b8", penwidth=0.7, label="×1"];
"FreeRTOS" -> "Src/Inc (CubeMX)" [color="#94a3b8", penwidth=0.7, label="×1"];
"FreeRTOS" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×14"];
"HAL" -> "CMSIS" [color="#94a3b8", penwidth=0.7, label="×14"];
"HAL" -> "CMSIS" [color="#94a3b8", penwidth=0.7, label="×15"];
"HAL" -> "Src/Inc (CubeMX)" [color="#94a3b8", penwidth=0.7, label="×1"];
"HAL" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×1"];
"LVGL" -> "FatFs" [color="#94a3b8", penwidth=0.7, label="×1"];
@@ -133,7 +135,7 @@ digraph refgraph_module {
"Src/Inc (CubeMX)" -> "BSP/TPAFE5160" [color="#2563eb", penwidth=1.2, label="×1"];
"Src/Inc (CubeMX)" -> "CMSIS" [color="#2563eb", penwidth=1.2, label="×1"];
"Src/Inc (CubeMX)" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×4"];
"Src/Inc (CubeMX)" -> "HAL" [color="#2563eb", penwidth=1.2, label="×15"];
"Src/Inc (CubeMX)" -> "HAL" [color="#2563eb", penwidth=1.2, label="×16"];
"Src/Inc (CubeMX)" -> "lftpd" [color="#2563eb", penwidth=1.2, label="×1"];
"Src/Inc (CubeMX)" -> "test" [color="#2563eb", penwidth=1.2, label="×3"];
"Src/Inc (CubeMX)" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 104 KiB

@@ -372,9 +372,11 @@ sequenceDiagram
- 实现:`defaultTask` 每 2 s `f_stat` 比对 (size, mtime),连续 2 次稳定才重载(沉降去抖);`[network]` 段变化 → `SYS_EVT_NET_RECONFIG` → 网络重建(代次 +1,服务自愈)。
- 判据:FTP 改 IP 后 2~5 s 生效(网络重建含等链路 ≤3 s)。
**FR-CFG-4 旧档迁移**
- 需求:旧固件的 `/cfg/netcfg.bin`(NCFG 二进制)首启自动迁移为 INI 并删除旧文件。
- 判据:迁移后日志 `cfg file: legacy NCFG migrated`,配置保留。
**FR-CFG-4 旧档迁移**(已取消 · 2026-10-10)
- 原需求:旧固件的 `/cfg/netcfg.bin`(NCFG 二进制)首启自动迁移为 INI 并删除旧文件——**已取消,不再实现**。
- 现口径:旧档若存在则被**忽略**(不读、不删、不迁移),配置直接回落工厂默认(`/config.ini` `[network]` 段模板)。
- 取消理由:该一次性路径把整条配置写链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)挂进启动期调用图,defaultTask 静态最坏栈链 +600 B;本仓库不存在携带旧档的设备。
- 判据:卷上残留 `/cfg/netcfg.bin` 时开机不产生任何迁移日志、旧档不被改动,`/config.ini` 按工厂默认生成。
**FR-CFG-5 扩展性**
- 需求:未来所有模块配置(ADC/RS-485/端口等)进同一文件、按段组织。
@@ -577,7 +579,7 @@ mac=00:00:00:00:00:00
| 需求 | 要求 | 落地 |
|---|---|---|
| **自恢复** | 任务死锁/饿死/断言死循环 → 硬件复位 | IWDG + 心跳位图(§5.7) |
| **掉电不丢配置** | 配置原子写 + 旧档迁移 | tmp+rename + NCFG→INI 迁移 |
| **掉电不丢配置** | 配置原子写(旧档迁移已取消,2026-10-10) | tmp+rename;旧 `/cfg/netcfg.bin` 不读、不删、不迁移 |
| **错误可追溯** | 错误码分级/透传/留痕,不吞不猜 | 错误码通道四要素(分类/透传/分级/留痕) |
| **存储自愈** | 卷损坏可探针/自愈;丢弃可观测 | 卷健康探针 + 丢弃计数 + TX 僵尸通道自愈 |
| **不误杀** | 正常长耗时窗口不触发复位 | 启动段不喂狗 + 心跳切片 + 白名单 |
@@ -888,3 +890,4 @@ cfg-file-unit-test、test-suite-switch、gitlint。
| 日期 | 变更 | 说明 |
|---|---|---|
| 2026-10-05 | 初版 | 基于项目全量内容(架构 / 子系统 / UI / 测试 / 硬件网表 / 待办)编写完整产品需求设计文档:产品定位、六层架构与任务模型、V0.1 硬件规格、10 类功能需求(含 2 项待实现)、接口规范、非功能需求、UI 规格、待实现项与路线图、验收判据 |
| 2026-10-10 | FR-CFG-4 旧档迁移**取消** | 删除旧固件 `/cfg/netcfg.bin`(NCFG 二进制)的首启自动迁移:旧档若存在则被忽略(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 +600 B,而本仓库不存在携带旧档的设备;`net_cfg_legacy.c/h` 与 `cfg_store_legacy_path()` 一并移除。§7.1「掉电不丢配置」同步 |
@@ -18,7 +18,7 @@
- F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§1.2)
- 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成):RW_IRAM1 **84,960 / 131,072 B(64.8%)**、RW_CCM **49,184 / 65,280 B(75.3%)**。⚠️ **任务栈:2026-10-04 重采(产品构建 V5 30 min 负载)显示 `defaultTask` 峰值升至 3664B/4096(余量 432B ≈ 10%,已低于 30% 判据)**,详见 §2.3 注与 `../待处理内容.md` §6(登记 `待处理内容.md` S-1)。
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成):RW_IRAM1 **89,168 / 131,072 B(68.0%)**、RW_CCM **49,184 / 65,280 B(75.3%)**。✅ **任务栈:2026-10-10 削链后 `defaultTask` 实测峰值 2152B/4096(余 47%)、静态最坏链 2776B(余 32%)**,6 任务全过 `stack_report.py` 判据(曾于 2026-10-04 升至 3664B/4096 ≈ 10%,登记为 S-1,现已销项),详见 §2.3 与"2026-10-10 复测"小节。
本方案三个目标:
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 268,128 B (261.84 KB) | 1,048,576 B (1024 KB) | 25.6% |
| RW_IRAM1 | 85,040 B (83.05 KB) | 131,072 B (128 KB) | 64.9% |
| ER_IROM1 | 267,528 B (261.26 KB) | 1,048,576 B (1024 KB) | 25.5% |
| RW_IRAM1 | 89,168 B (87.08 KB) | 131,072 B (128 KB) | 68.0% |
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| **Flash (ROM 合计)** | **268,876 B (262.6 KB)** | 1,048,576 B (1024 KB) | **25.6%** |
| **Flash (ROM 合计)** | **268,276 B (262.0 KB)** | 1,048,576 B (1024 KB) | **25.6%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -51,7 +51,7 @@
| FreeRTOS 堆 (heap_4) | 48 KB(49,152 B),**位于 RW_CCM**;实测 `free=8,784 / min_ever=4,848` B | `Inc/FreeRTOSConfig.h:71` `configTOTAL_HEAP_SIZE` |
| C 库堆/栈 (startup) | 512 B / 1 KB,启动期 main() 用 | map `startup_stm32f407xx.o` HEAP/STACK 节 |
### 2.2 静态大项排行(2026-09-21 当前,完整台账 `data/ram_report_20260921.md`)
### 2.2 静态大项排行(2026-10-10 当前,完整台账 `data/ram_report_20261010.md`)
**RW_IRAM1(主 SRAM)**,按 ZI/RW 合计降序:
@@ -72,6 +72,11 @@
| 13–17 | ≤560 | `spi.o` / `usart.o` / `lftpd_io.o` / `lv_init.o` / `net_socket.o` | 外设句柄、`FIL`、LVGL 全局、socket 表 |
| 其余 | ~1.7 KB | 约 25 个 .o | 小型 RW/ZI |
> **2026-10-10 栈整改带来的静态增量(+4,128 B,本质是"把 528 B 的 `FIL` 从栈搬到 BSS")**:
> `sys_log_mcu.o` 1,912 B(3 处 `FIL` 静态化 = 1,584 + 证据行缓冲 192)、`default_task.o` 2,712 B
> (卷探针 `FIL` 528 + `default_fatfs`/`fs_work`)、`cfg_file_store_mcu.o` 1,104 B(2 处 `FIL` = 1,056)。
> 同期 Flash **−600 B**(删旧档迁移)。台账:`data/ram_report_20261010.md`。
**RW_CCM(CCM)**:`heap_4.o` 49,184 B(FreeRTOS 堆 49,152 B + RW 32 B)——2026-09-21 由主 SRAM 迁入(§1.2),不再计入主 SRAM。
**静态合计(以 §2.1 受管快照为准)**:RW_IRAM1 **87,040 B(85.00 KB / 128 KB,66.4%)** + RW_CCM **49,184 B(48.03 KB / 64 KB,75.0%)** = **136,224 B(RW+ZI)**。
@@ -86,20 +91,18 @@
`Src/freertos.c` 中 `osThreadAttr_t.stack_size` 单位为**字节**(CubeMX 生成代码,`1024 * 4` 表示 4096 字节):
> ⚠️ **下表"实测峰值"列为 2026-09-21 快照,已过时**:2026-10-04 产品构建(`suite=NONE`)V5 30 min
> 真实负载重采(串口 `[STACK]`,证据 `/tmp/v5_soak_serial_20261003.log`)——**defaultTask 3664B/4096
> (余量 432B ≈ 10%,已低于 30% 判据,登记 S-1)**、ftpTask 2516B/4096(38%)、netTask 1228B/4096(70%)、
> uiTask 4060B/16384(75%)、timeSyncTask 792B/2048(61%)、testTask 496B/8192(93%)。
> 栈水位随 A2/A3/A4 + IWDG + 卷锁 + 堆告警等陆续入链持续上升,**本表需随 `stack_report.py` 重新基线**。
> ✅ **本表已于 2026-10-10 重新基线**(产品构建 `suite=NONE`,开机 + 空闲采样,静态链取 `stack_report.py`)。
> 此前 2026-10-04 的 V5 30 min 重采曾把 defaultTask 推到 **3664B/4096(余量 432B ≈ 10%,登记 S-1)**;
> 2026-10-10 的栈整改(**未抬栈**,见 §4 阶段 0 的"2026-10-10 复测")把它压回 **2152B/4096**,S-1 已销项。
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-09-21 实测峰值(§0.6) |
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-10-10 实测峰值 / 静态最坏链 |
|---|---|---|---|---|
| uiTask | `4096*4` | 16KB | LVGL 1bpp 离屏渲染(渲染缓冲在静态区,栈只走调用链) | 3824 B(余量 76%)→ 可大幅缩减 |
| netTask | `1024*4` | 4KB | CH395F 轮询(DMA 搬运,栈压力小) | 588 B(余量 85%) |
| ftpTask | `1024*4` | 4KB | lftpd FTP 服务 | 1184 B(余量 71%,§1.4 静态化后) |
| testTask | `2048*4` | 8KB | 测试套件(产品构建空转;存储套件深链需要) | 348 B(产品构建) |
| defaultTask | `1024*4` | 4KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2136 B(余量 47%) |
| timeSyncTask | `512*4` | 2KB | TCP 时间同步 | 420 B(余量 79%) |
| uiTask | `4096*4` | 16KB | LVGL 1bpp 离屏渲染(渲染缓冲在静态区,栈只走调用链) | 4016 B(余 75%)/ 静态 1336 B → 可大幅缩减 |
| netTask | `960*4` | 3.75KB | CH395F 轮询(DMA 搬运,栈压力小) | 920 B(余 76%)/ 静态 1872 B |
| ftpTask | `1024*4` | 4KB | lftpd FTP 服务 | 740 B(余 81%)/ 静态 1216 B |
| testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B |
| defaultTask | `1024*4` | 4KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 47%)/ 静态 2776 B |
| timeSyncTask | `512*4` | 2KB | TCP 时间同步 | 616 B(余 69%)/ 静态 **1024 B**(= ISR 底线,无再加深空间) |
| idle(内核) | `configMINIMAL_STACK_SIZE=128` | 0.5KB | 空闲 | 未观测(未取句柄) |
| timer(内核) | `configTIMER_TASK_STACK_DEPTH=256` | 1KB | 软定时器 | 未观测 |
@@ -322,7 +325,7 @@ void vApplicationMallocFailedHook(void)
要点与边界:
- defaultTask 的静态最坏链是**配置保存/旧档迁移**路径,不是首启探针路径:
- defaultTask 的静态最坏链是**配置保存/旧档迁移**路径,不是首启探针路径(**2026-09-21 当时的链;旧档迁移已于 2026-10-10 删除、日志行缓冲已按模块收紧,现链见下方"2026-10-10 复测"**):
`net_cfg_load → net_cfg_legacy_migrate → net_cfg_save → cfg_file_save → s_write_template → s_write_doc → cfg_store_write_atomic → f_rename → follow_path → dir_next → create_chain → put_fat → move_window → sync_window → disk_write → ftl_flush_cache → dhara_map_write → prepare_write → auto_gc → dhara_map_gc → ... → trace_path → dhara_journal_read_meta → dhara_nand_read → gd5f_read_from_cache → HAL_SPI_Receive → ... → SPI_WaitFlagStateUntilTimeout`。
**2776 B > 2048 B** → 512 words 栈必然溢出,与 2026-09-21 实测完全一致。
- 帧宽榜直接点名宽度大户(自身帧):`s_append` 704 B(FIL 512 + 记录 64 + 时间戳)、`StartDefaultTask` 560 B(探针 `FIL __chk`)、`cfg_store_read`/`cfg_store_write_atomic` 576 B、LVGL 绘制 624/560/400 B——与 §1.4 的整改项对得上。**lftpd 三项已于 2026-09-21 静态化(§1.4),已退出帧宽榜前 15**(原 `handle_control_channel` 1104 B、`receive_file`/`send_file` 1088 B、`send_response` 800 B)。
@@ -330,6 +333,56 @@ void vApplicationMallocFailedHook(void)
- testTask 在产品构建里只是 `osDelay` 空循环(168 B);2048 words 是给**存储套件构建**(`TEST_SUITE_STORAGE`)预留的,测套件时应重跑本脚本看该构建的静态值。
- **测 ftpTask 的复现配方**:设备就绪后跑 `& '.venv\Scripts\python.exe' test/host/ftp_speed_test.py --size 128`(STOR 128KB + RETR + 逐字节比对),再按上面命令叠加串口日志出报告。**不要用裸 TCP 工具探测 21 端口**——CH395F 单客户端复用,探测会占掉唯一会话(见 FTP 指南 §8.9)。
#### 2026-10-10 复测:**不抬栈**把 defaultTask 从 FAIL 拉回 PASS(S-1 销项)
触发点:登记簿 S-1(2026-10-04 实测 defaultTask 3664B/4096,余量 10%)。叠加静态链后判据更严——
**静态最坏链已涨到 5312 B > 配置 4096 B(margin −1216 B)**。本次刻意**不动栈值**,改为削链:
| 任务 | 配置 (B) | 静态最坏 改前 → 改后 | 实测峰值 改前 → 改后 | 判定 |
|---|---|---|---|---|
| **defaultTask** | 4096 | **5312 → 2776** | 3392 → **2152** | FAIL → **PASS(余 1320 B / 32%)** |
| netTask | 3840 | 2664 → 1872 | 1008 → 920 | PASS(51%) |
| ftpTask | 4096 | 1800 → 1216 | 756 → 740 | PASS(70%) |
| uiTask | 16384 | 1536 → 1336 | 4024 → 4052 | PASS(75%) |
| **timeSyncTask** | 2048 | **1736 → 1024** | 760 → 616 | FAIL → **PASS(余 1024 B = ISR 底线,正好卡住)** |
| testTask | 8192 | 928 → 496 | —(产品构建不创建) | PASS(93%) |
四个杠杆(按收益排序,只动自研代码,第三方 dhara/FatFS/HAL 一行未改):
1. **删旧档 NCFG 迁移**(`Drivers/BSP/NETCFG/net_cfg_legacy.c/h` 整体移除,链 −600 B):
`net_cfg_load` 无条件调 `net_cfg_legacy_migrate()`,把整条配置**写**链(`net_cfg_save →
cfg_file_save → s_write_template → s_write_doc → cfg_store_write_atomic → f_rename → FTL`)
挂进启动期调用图。本仓库无携带 `/cfg/netcfg.bin` 的设备 → 按"不保留旧档兼容"处置;
旧档若存在则**忽略**(不读、不删、不迁移),配置回落工厂默认。
2. **6 处局部 `FIL` 改函数级 `static`**(每处 −528 B):`Lib/FatFs/ffconf.h` `FF_FS_TINY=0` →
`FIL` 内含 `buf[FF_MAX_SS]` = 512 B。落点 `App/log/sys_log_mcu.c`(3 处)、
`App/cfg/cfg_file_store_mcu.c`(2 处)、`App/task/default_task.c` 探针宏(1 处)。
安全性依据:整个 FIL 生命周期都在 `vol_lock_wait()/release()` 区间内(跨任务被卷锁串行化)
+ 配置引擎/syslog 写者单任务属主(P1-3)。先例 = 2026-09-21 lftpd 9 处大局部缓冲静态化。
3. **日志行缓冲按模块收紧 + 时间戳缓冲并入行缓冲**(每个日志调用点原内联 `192 + 32 = 224 B`):
`dbg_log.h` 新增 `DBG_LINE_BUF_MAX`(默认仍 = `DBG_CON_LINE_MAX` 192),深链模块
(`nand_ftl.c`/`gd5f2gq5ue.c`/`cfg_file.c`/`net_cfg.c`/`net_socket.c`/`ch395f.c`/`time_sync.c`/
`default_task.c`)收到 **128**;`DBG_TS_BUF_MAX` 32 → 24 且与行缓冲共用一个数组。
实测链上 9 个帧各含一份行缓冲 → **仅这一项就占 defaultTask 最坏链约 1.5 KB**。
⚠ 取值须 ≥ 该模块最长日志行(各文件注释里已给出实测行长),否则该行被截断。
4. **内核钩子脱离 HAL 发送路径**:`configASSERT` 在 FreeRTOS 内部可达 → `vApplicationAssertFailed`
挂在**每个**任务的最坏链尾;它原走 `dbg_console_write_raw → con_tx_bounded →
(DWT 不可用时)HAL_UART_Transmit → UART_WaitOnFlagUntilTimeout`。现 `con_tx_bounded` 的
DWT 回退改为**指令计数上界**(`CON_TX_GUARD_MAX`),HAL 尾巴从所有链上消失;钩子自身
`name[49]/num[16]/rev[16]` 改 static + 原地反转、去掉 `HAL_Delay`(断言现场 tick 未必可信)。
代价与复核:静态 BSS **+4,128 B**(ZI 133,524 → 137,652;RW_IRAM1 64.9% → 68.1%),Flash 反而 −36 B;
FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`@build` 0/0 → `@test` 真机 PASS(含烧录回读)
→ `stack_report.py` **6/6 PASS**。
顺带揪出并修掉的**两个静态虚高源**:① `dbg_console_drain` 故障路径调 `syslog_post` →
`write_line → con_append → drain → syslog_post → write_line` **调用图成环**(运行时有
`s_con_draining` 重入标志挡着不会真递归,但 armlink 把整条日志尾巴在每个任务上多叠一层)——
现改为置标志、由写者任务在 drain 之外补记 run.log(`dbg_console_tx_fault_log_take()`);
② 上述 HAL 发送回退。**教训:静态链异常高时先查"链尾是不是日志/钩子",再动业务代码。**
**留作候选、本轮刻意未做**:① **共享静态行缓冲池**(`dbg_console_line_claim()/release()` 发还栈上数组,改为从 2 个静态槽领取)——可再削每条日志帧的全部 152 B(全链再省 ~1.3 KB),但把"日志缓冲在调用者栈上"改成"跨任务共享 + 抢占失败即丢行",**改变日志并发语义**,需配套主机侧单测才动;② `timeSyncTask` 静态余量已正好等于 1024 B ISR 底线,**该链上再加任何调用都必须先抬栈**(已在 `Src/freertos.c` 栈注释里写明)。
#### 阶段 0 验收
- 串口可见 `[STACK]` 报告:每任务 `peak_used/free_min` + 堆 `free/min_ever`
@@ -393,7 +446,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
| netTask | 4KB | **2KB** | 实测 588B → 1024+1024=2048B |
| ftpTask | 4KB | **3KB** | 实测 1184B(§1.4 静态化后)→ 1776+1024=2800B → 3KB;再降须放宽 ISR 余量并复测 |
| testTask | 8KB | **2KB** | 产品构建空转(实测 348B)→ 1024+1024;存储套件构建按该构建静态值另放大 |
| defaultTask | 4KB | **保持 4KB** | 静态最坏 2776B(配置保存/旧档迁移深链,§0.6)→ 缩减必溢出 |
| defaultTask | 4KB | **保持 4KB** | 2026-10-10 削链后静态最坏 2776B / 实测 2152B(余 32%);曾达 5312B 溢出判据,见"2026-10-10 复测" |
| timeSyncTask | 2KB | **保持 2KB** | 实测 420B → 1024+1024=2048B,无缩减空间 |
缩减后任务栈合计 38,912 → 21,504 B(**省 17,408 B / ~17 KB**,全部在 CCM 堆内,主 SRAM 不受影响)。
@@ -0,0 +1,197 @@
# STM32F4-Base RAM/Flash Usage Report
> Generated: 2026-10-10 09:17 Map: `STM32F407-Demo.map`
## 1. Grand Totals (全局总量)
| Metric | Value |
|---|---|
| Code (inc. data) | 252,092 B (246.2 KB) |
| RO Data | 15,436 B (15.1 KB) |
| RW Data (initialized) | 748 B (0.73 KB) |
| ZI Data (zero-init) | 137,652 B (134.4 KB) |
| **Total RO (Code + RO)** | **267,528 B (261.3 KB)** |
| **Total RW (RW + ZI) = SRAM used** | **138,400 B (135.16 KB)** |
| **Total ROM (Code + RO + RW init)** | **268,276 B (262.0 KB)** |
## 2. 执行区水位 (Execution Regions)
| Region | Base | Size | Max | Util% |
|---|---|---|---|---|
| ER_IROM1 | 0x08000000 | 267,528 B (261.26 KB) | 1,048,576 B (1024 KB) | 25.5% |
| RW_CCM | 0x10000000 | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| UNINIT_CCM | 0x1000FF00 | 48 B (0.05 KB) | 256 B (0 KB) | 18.8% |
| RW_IRAM1 | 0x20000000 | 89,168 B (87.08 KB) | 131,072 B (128 KB) | 68.0% |
## 3. RW_IRAM1 逐符号明细(ZI + RW,按大小降序)
| # | Address | Size (B) | Size (KB) | Type | Section | Object |
|---|---|---|---|---|---|---|
| 1 | 0x200083F0 | 40,960 | 40.00 | Zero | .bss | lv_mem_core_builtin.o |
| 2 | 0x20013DF2 | 6,746 | 6.59 | Zero | .bss | ui_net_sink.o |
| 3 | 0x200123F0 | 6,408 | 6.26 | Zero | .bss | ui_port.o |
| 4 | 0x200048C4 | 6,200 | 6.05 | Zero | .bss | nand_ftl.o |
| 5 | 0x200060FC | 4,616 | 4.51 | Zero | .bss | lftpd.o |
| 6 | 0x200031B0 | 4,104 | 4.01 | Zero | .bss | ch395f.o |
| 7 | 0x200016E0 | 4,096 | 4.00 | Zero | .bss | console_log.o |
| 8 | 0x20002718 | 2,712 | 2.65 | Zero | .bss | default_task.o |
| 9 | 0x20000F68 | 1,912 | 1.87 | Zero | .bss | sys_log_mcu.o |
| 10 | 0x20007B48 | 1,720 | 1.68 | Zero | .bss | cmsis_os2.o |
| 11 | 0x2000756C | 1,220 | 1.19 | Zero | .bss | tasks.o |
| 12 | 0x20000B18 | 1,104 | 1.08 | Zero | .bss | cfg_file_store_mcu.o |
| 13 | 0x20015850 | 1,024 | 1.00 | Zero | | startup_stm32f407xx.o |
| 14 | 0x200041B8 | 800 | 0.78 | Zero | .bss | gd5f2gq5ue.o |
| 15 | 0x20000858 | 704 | 0.69 | Zero | .bss | cfg_file.o |
| 16 | 0x20004604 | 704 | 0.69 | Zero | .bss | net_socket.o |
| 17 | 0x200003B8 | 560 | 0.55 | Zero | .bss | spi.o |
| 18 | 0x200005E8 | 552 | 0.54 | Zero | .bss | usart.o |
| 19 | 0x20007304 | 552 | 0.54 | Zero | .bss | lftpd_io.o |
| 20 | 0x20008200 | 496 | 0.48 | Zero | .bss | lv_init.o |
| 21 | 0x20007A30 | 280 | 0.27 | Zero | .bss | timers.o |
| 22 | 0x200044F8 | 268 | 0.26 | Zero | .bss | rs485.o |
| 23 | 0x20000134 | 152 | 0.15 | Data | .data | lftpd.o |
| 24 | 0x20013CF8 | 152 | 0.15 | Zero | .bss | ui_menu_view.o |
| 25 | 0x20000358 | 84 | 0.08 | Zero | .bss | i2c.o |
| 26 | 0x20000310 | 72 | 0.07 | Zero | .bss | adc.o |
| 27 | 0x20000810 | 72 | 0.07 | Zero | .bss | stm32f4xx_hal_timebase_tim.o |
| 28 | 0x200002CC | 65 | 0.06 | Zero | .bss | freertos.o |
| 29 | 0x2000752C | 64 | 0.06 | Zero | .bss | queue.o |
| 30 | 0x200001D0 | 60 | 0.06 | Data | .data | tasks.o |
| 31 | 0x200026E0 | 56 | 0.05 | Zero | .bss | task_watchdog.o |
| 32 | 0x20013D90 | 49 | 0.05 | Zero | .bss | ui_page_runlog.o |
| 33 | 0x20013DC1 | 49 | 0.05 | Zero | .bss | ui_page_selftest.o |
| 34 | 0x20000064 | 44 | 0.04 | Data | .data | task_watchdog.o |
| 35 | 0x200000D4 | 44 | 0.04 | Data | .data | net_socket.o |
| 36 | 0x20000100 | 36 | 0.04 | Data | .data | net_cfg.o |
| 37 | 0x20000000 | 32 | 0.03 | Data | .data | freertos.o |
| 38 | 0x20000298 | 32 | 0.03 | Data | .data | ui_standby.o |
| 39 | 0x200044D8 | 32 | 0.03 | Zero | .bss | tpafe5160.o |
| 40 | 0x20000044 | 24 | 0.02 | Data | .data | console_log.o |
| 41 | 0x20000024 | 20 | 0.02 | Data | .data | cfg_file.o |
| 42 | 0x200000B4 | 20 | 0.02 | Data | .data | gd5f2gq5ue.o |
| 43 | 0x2000020C | 20 | 0.02 | Data | .data | timers.o |
| 44 | 0x20000250 | 18 | 0.02 | Data | .data | ui_menu.o |
| 45 | 0x20000124 | 16 | 0.02 | Data | .data | nand_ftl.o |
| 46 | 0x200002B8 | 16 | 0.02 | Data | .data | ui_net_sink.o |
| 47 | 0x20000038 | 12 | 0.01 | Data | .data | sys_log_mcu.o |
| 48 | 0x200000A0 | 12 | 0.01 | Data | .data | stm32f4xx_hal.o |
| 49 | 0x200000C8 | 12 | 0.01 | Data | .data | tpafe5160.o |
| 50 | 0x20000224 | 12 | 0.01 | Data | .data | port.o |
| 51 | 0x20000244 | 12 | 0.01 | Data | .data | ui_port.o |
| 52 | 0x2000026C | 12 | 0.01 | Data | .data | ui_content_page.o |
| 53 | 0x20000278 | 12 | 0.01 | Data | .data | ui_page_password.o |
| 54 | 0x200003AC | 12 | 0.01 | Zero | .bss | iwdg.o |
| 55 | 0x2000005C | 8 | 0.01 | Data | .data | sys_clock.o |
| 56 | 0x20000094 | 8 | 0.01 | Data | .data | default_task.o |
| 57 | 0x20000230 | 8 | 0.01 | Data | .data | ff.o |
| 58 | 0x20000238 | 8 | 0.01 | Data | .data | ffsystem.o |
| 59 | 0x20000264 | 8 | 0.01 | Data | .data | ui_menu_view.o |
| 60 | 0x20000288 | 8 | 0.01 | Data | .data | ui_page_runlog.o |
| 61 | 0x20000290 | 8 | 0.01 | Data | .data | ui_page_selftest.o |
| 62 | 0x20000020 | 4 | 0.00 | Data | .data | app_main.o |
| 63 | 0x20000090 | 4 | 0.00 | Data | .data | vol_lock.o |
| 64 | 0x2000009C | 4 | 0.00 | Data | .data | net_task.o |
| 65 | 0x200000AC | 4 | 0.00 | Data | .data | system_stm32f4xx.o |
| 66 | 0x200001CC | 4 | 0.00 | Data | .data | lftpd_io.o |
| 67 | 0x20000220 | 4 | 0.00 | Data | .data | cmsis_os2.o |
| 68 | 0x20000240 | 4 | 0.00 | Data | .data | lv_textarea.o |
| 69 | 0x200002C8 | 4 | 0.00 | Data | .data | mc_w.l(errno.o) |
| 70 | 0x200000B0 | 2 | 0.00 | Data | .data | ch395f.o |
| 71 | 0x20000284 | 2 | 0.00 | Data | .data | ui_page_net_cfg.o |
| | | **89,155** | **87.07** | | | **Total** |
## 4. Image Component Sizes(RW + ZI > 0,按大小降序)
| # | Object | Code | RO | RW | ZI | RW+ZI |
|---|---|---|---|---|---|---|
| 1 | heap_4.o | 512 | 69 | 32 | 49,152 | 49,184 |
| 2 | lv_mem_core_builtin.o | 410 | 0 | 0 | 40,960 | 40,960 |
| 3 | ui_net_sink.o | 1,102 | 67 | 16 | 6,746 | 6,762 |
| 4 | ui_port.o | 418 | 0 | 12 | 6,408 | 6,420 |
| 5 | nand_ftl.o | 2,082 | 83 | 16 | 6,200 | 6,216 |
| 6 | lftpd.o | 5,336 | 503 | 152 | 4,616 | 4,768 |
| 7 | console_log.o | 968 | 55 | 24 | 4,096 | 4,120 |
| 8 | ch395f.o | 2,796 | 345 | 2 | 4,104 | 4,106 |
| 9 | default_task.o | 2,048 | 229 | 8 | 2,712 | 2,720 |
| 10 | sys_log_mcu.o | 2,078 | 83 | 12 | 1,912 | 1,924 |
| 11 | cmsis_os2.o | 1,216 | 0 | 4 | 1,720 | 1,724 |
| 12 | tasks.o | 4,034 | 0 | 60 | 1,220 | 1,280 |
| 13 | cfg_file_store_mcu.o | 380 | 24 | 0 | 1,104 | 1,104 |
| 14 | startup_stm32f407xx.o | 36 | 392 | 0 | 1,024 | 1,024 |
| 15 | gd5f2gq5ue.o | 3,930 | 478 | 20 | 800 | 820 |
| 16 | net_socket.o | 6,682 | 779 | 44 | 704 | 748 |
| 17 | cfg_file.o | 5,922 | 810 | 20 | 704 | 724 |
| 18 | spi.o | 584 | 0 | 0 | 560 | 560 |
| 19 | lftpd_io.o | 1,198 | 0 | 4 | 552 | 556 |
| 20 | usart.o | 884 | 0 | 0 | 552 | 552 |
| 21 | lv_init.o | 156 | 0 | 0 | 496 | 496 |
| 22 | timers.o | 1,232 | 0 | 20 | 280 | 300 |
| 23 | rs485.o | 120 | 0 | 0 | 268 | 268 |
| 24 | ui_menu_view.o | 2,034 | 108 | 8 | 152 | 160 |
| 25 | task_watchdog.o | 1,098 | 137 | 44 | 104 | 148 |
| 26 | freertos.o | 656 | 452 | 32 | 65 | 97 |
| 27 | i2c.o | 168 | 0 | 0 | 84 | 84 |
| 28 | adc.o | 392 | 0 | 0 | 72 | 72 |
| 29 | stm32f4xx_hal_timebase_tim.o | 152 | 0 | 0 | 72 | 72 |
| 30 | queue.o | 2,840 | 0 | 0 | 64 | 64 |
| 31 | ui_page_runlog.o | 168 | 93 | 8 | 49 | 57 |
| 32 | ui_page_selftest.o | 168 | 93 | 8 | 49 | 57 |
| 33 | tpafe5160.o | 704 | 67 | 12 | 32 | 44 |
| 34 | net_cfg.o | 828 | 103 | 36 | 0 | 36 |
| 35 | ui_standby.o | 1,208 | 159 | 32 | 0 | 32 |
| 36 | ui_menu.o | 816 | 137 | 18 | 0 | 18 |
| 37 | (incl. Padding) | 312 | 87 | 6 | 7 | 13 |
| 38 | iwdg.o | 28 | 0 | 0 | 12 | 12 |
| 39 | port.o | 762 | 73 | 12 | 0 | 12 |
| 40 | stm32f4xx_hal.o | 116 | 0 | 12 | 0 | 12 |
| 41 | ui_content_page.o | 1,720 | 199 | 12 | 0 | 12 |
| 42 | ui_page_password.o | 686 | 167 | 12 | 0 | 12 |
| 43 | ff.o | 7,962 | 282 | 8 | 0 | 8 |
| 44 | ffsystem.o | 204 | 20 | 8 | 0 | 8 |
| 45 | sys_clock.o | 1,788 | 286 | 8 | 0 | 8 |
| 46 | app_main.o | 1,660 | 65 | 4 | 0 | 4 |
| 47 | lv_textarea.o | 3,632 | 48 | 4 | 0 | 4 |
| 48 | net_task.o | 1,452 | 72 | 4 | 0 | 4 |
| 49 | system_stm32f4xx.o | 16 | 24 | 4 | 0 | 4 |
| 50 | vol_lock.o | 92 | 0 | 4 | 0 | 4 |
| 51 | errno.o | 8 | 0 | 4 | 0 | 4 |
| 52 | ui_page_net_cfg.o | 1,004 | 93 | 2 | 0 | 2 |
| | **All .o files** | **252,092** | **15,436** | **748** | **137,652** | **138,400** |
## 5. Flash (Code + RO Data) Top-30(按大小降序)
| # | Object | Code | RO Data | Code+RO |
|---|---|---|---|---|
| 1 | ff.o | 7,962 | 282 | 8,244 |
| 2 | lv_draw_sw_blend_to_rgb565_swapped.o | 7,648 | 0 | 7,648 |
| 3 | net_socket.o | 6,682 | 779 | 7,461 |
| 4 | lv_draw_sw_blend_to_rgb565.o | 7,020 | 0 | 7,020 |
| 5 | cfg_file.o | 5,922 | 810 | 6,732 |
| 6 | lftpd.o | 5,336 | 503 | 5,839 |
| 7 | lv_draw_sw_blend_to_argb8888.o | 5,412 | 0 | 5,412 |
| 8 | lv_draw_sw_blend_to_i1.o | 5,272 | 0 | 5,272 |
| 9 | lv_indev.o | 4,958 | 0 | 4,958 |
| 10 | ui_font_cn_12.o | 0 | 4,626 | 4,626 |
| 11 | lv_draw_sw_blend_to_rgb888.o | 4,560 | 0 | 4,560 |
| 12 | lv_obj_pos.o | 4,526 | 16 | 4,542 |
| 13 | lv_refr.o | 4,486 | 0 | 4,486 |
| 14 | gd5f2gq5ue.o | 3,930 | 478 | 4,408 |
| 15 | lv_draw_sw_transform.o | 4,340 | 0 | 4,340 |
| 16 | lv_obj_style.o | 4,138 | 0 | 4,138 |
| 17 | lv_obj_scroll.o | 4,126 | 0 | 4,126 |
| 18 | lv_label.o | 4,042 | 45 | 4,087 |
| 19 | tasks.o | 4,034 | 0 | 4,034 |
| 20 | lv_draw_sw_mask.o | 3,862 | 0 | 3,862 |
| 21 | lv_draw_sw_blend_to_l8.o | 3,778 | 0 | 3,778 |
| 22 | lv_buttonmatrix.o | 3,586 | 125 | 3,711 |
| 23 | lv_textarea.o | 3,632 | 48 | 3,680 |
| 24 | stm32f4xx_hal_spi.o | 3,586 | 0 | 3,586 |
| 25 | lv_draw_sw_blend_to_al88.o | 3,560 | 0 | 3,560 |
| 26 | lv_draw_sw_blend_to_argb8888_premultiplied.o | 3,332 | 0 | 3,332 |
| 27 | lv_draw_sw_box_shadow.o | 3,246 | 0 | 3,246 |
| 28 | ch395f.o | 2,796 | 345 | 3,141 |
| 29 | lv_indev_scroll.o | 3,068 | 0 | 3,068 |
| 30 | lv_obj.o | 2,982 | 43 | 3,025 |
| | **All .o files** | **252,092** | **15,436** | **267,528** |
> 自洽校验:per-file 252,092/15,436/748/137,652 vs Grand Totals 252,092/15,436/748/137,652(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS**
@@ -0,0 +1,79 @@
# Stack budget report
- Source: `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm` (Keil static call graph)
- Linker run: Sat Oct 10 10:13:26 2026
- Global maximum stack usage: **2776 B** (any call tree)
- Runtime watermark: 5 task(s) from `tools\serial_tool\state\serial.log`
Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be >= max(1024 B ISR, 30% of cfg).
| Task | Entry | Cfg (B) | Static max (B) | Runtime peak (B) | Worst (B) | Margin (B) | Margin % | Verdict |
|---|---|---|---|---|---|---|---|---|
| defaultTask | `StartDefaultTask` | 4096 | 2776 | 2152 | 2776 | 1320 | 32% | PASS |
| netTask | `StartNetTask` | 3840 | 1872 | 920 | 1872 | 1968 | 51% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 1216 | 740 | 1216 | 2880 | 70% | PASS |
| uiTask | `StartUiTask` | 16384 | 1336 | 4016 | 4016 | 12368 | 75% | PASS |
| testTask | `start_test_task` | 8192 | 496 | - | 496 | 7696 | 93% | PASS |
| timeSyncTask | `time_sync_task` | 2048 | 1024 | 616 | 1024 | 1024 | 50% | PASS |
`Static max` = armlink worst-case call-tree depth; `Runtime peak` = measured high-water mark (boot + whatever workload ran before the sample); `Worst` = max of the two, which is what the verdict is computed from.
Runtime heap: free=17248 B, min_ever=9312 B
## Worst call chain per task
### defaultTask: 2776 B (own frame 0 B)
```
StartDefaultTask -> default_task_func -> storage_bring_up -> net_cfg_load -> cfg_file_load -> s_write_template -> s_write_doc -> cfg_store_write_atomic -> f_rename -> sync_fs -> disk_ioctl -> ftl_flush_cache -> dhara_map_write -> prepare_write -> auto_gc -> dhara_map_gc -> try_recover -> raw_gc -> trace_path -> dhara_journal_read_meta -> dhara_nand_read -> gd5f_read_from_cache -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> HAL_GPIO_Init
```
### netTask: 1872 B (own frame 0 B)
```
StartNetTask -> net_task_func -> net_process_messages -> net_send_locked -> net_poll -> handle_disconnect_event -> net_relisten_try -> net_relisten_locked -> ch395f_read_recv_buf -> ch395f_spi_dma_transfer -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> HAL_GPIO_Init
```
### ftpTask: 1216 B (own frame 24 B)
```
StartFtpTask -> lftpd_start -> lftpd_inet_listen -> net_bind -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> HAL_GPIO_Init
```
### uiTask: 1336 B (own frame 0 B)
```
StartUiTask -> ui_task_func -> ui_menu_init -> ui_menu_view_init -> lv_obj_create -> lv_obj_class_init_obj -> lv_theme_apply -> lv_obj_remove_style_all -> lv_obj_remove_style -> lv_obj_refresh_style -> refresh_children_style -> refresh_children_style (Cycle)
```
### testTask: 496 B (own frame 224 B)
```
start_test_task -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> HAL_GPIO_Init
```
### timeSyncTask: 1024 B (own frame 152 B)
```
time_sync_task -> time_sync_open_listener -> net_bind -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> HAL_GPIO_Init
```
## Frame-size ranking (own frame, top 15)
| Bytes | Function |
|---|---|
| 624 | `lv_draw_sw_arc` |
| 624 | `draw_indic` |
| 560 | `draw_main` |
| 400 | `draw_line_skew` |
| 352 | `lv_draw_sw_triangle` |
| 352 | `net_stack_bring_up` |
| 344 | `s_append` |
| 328 | `lv_obj_redraw` |
| 328 | `view_frame_draw_cb` |
| 320 | `wd_stall_evidence` |
| 312 | `nc_draw_cb` |
| 304 | `lv_draw_label_iterate_characters` |
| 304 | `lv_draw_sw_box_shadow` |
| 304 | `sys_clock_init` |
| 304 | `draw_cursor` |
@@ -193,9 +193,10 @@ sequenceDiagram
### 4.4 裸发(`dbg_console_write_raw`)
- 有界:`len > 256 → 256`(截尾);单次阻塞 `HAL_UART_Transmit`(超时 `0xFFFF`),不持临界区、不等待状态;
- 有界:`len > 256 → 256`(截尾);发送走 `con_tx_bounded`(**寄存器级轮询 TXE/TC**,超时判据 = DWT `CYCCNT`,`CON_TX_TIMEOUT_CYC` = 100 ms 对应周期数),不持临界区、不等待状态;
- **2026-10-10:DWT 不可用时改走 `con_tx_counted`(指令计数上界 `CON_TX_GUARD_MAX`),不再回退 `HAL_UART_Transmit`**。两个理由:① HAL 的超时判据是 `HAL_GetTick`——断言/停摆现场 tick 未必在走;② `HAL_UART_Transmit → UART_WaitOnFlagUntilTimeout` 这条尾巴会被 armlink 挂进**每个**碰日志的任务的最坏栈链(`configASSERT` 在 FreeRTOS 内部可达 → 三个内核钩子都在链尾),实测每条链多背 64 B;
- 适用:内核前 / 未注册期、ISR、异常钩子(栈溢出 / 堆失败)、崩溃上报;
- 调用方纪律:**只交已知合法的缓冲**——钩子侧先做指针范围校验(Flash / SRAM / CCM 窗口)并拷入本地缓冲再发,TCB 指针不接触 UART(`Src/freertos.c vApplicationStackOverflowHook`,2026-09-24 加固;指针不可信时发占位文本,"上报崩溃的代码不能再崩一次")。
- 调用方纪律:**只交已知合法的缓冲**——钩子侧先做指针范围校验(Flash / SRAM / CCM 窗口)并拷入本地缓冲再发,TCB 指针不接触 UART(`Src/freertos.c vApplicationStackOverflowHook`,2026-09-24 加固;指针不可信时发占位文本,"上报崩溃的代码不能再崩一次")。2026-10-10 补充:钩子里的 `name[49]/num[16]` 改**函数级 static**(终点路径、打印完即复位,无并发)——放栈上会让这 81 B 出现在每个任务的最坏链上;行号转换改原地反转,去掉 `HAL_Delay`(`con_tx_bounded` 已等 TC)。
### 4.5 `fputc` 重定向(MicroLIB `printf`)
@@ -225,16 +226,21 @@ sequenceDiagram
| `dbg_console_writer_attach` | `void (void)` | 写者任务、主循环前 | 注册唯一写者 | 仅一次(defaultTask) |
| `dbg_console_drain` | `void (void)` | **仅写者任务** | 排空环并发出 + 刷丢弃计数 | defaultTask 主循环周期调用 |
| `dbg_console_dropped` | `uint32_t (void)` | 任意 | 诊断:累计丢弃行数 | 外部读取 |
| `dbg_console_tx_fault_log_take` | `uint8_t (void)` | **仅写者任务** | 取并清"TX 故障待上报"标志 | 2026-10-10 加:drain 内**不得**调 `syslog_post`(会成环),由 defaultTask 在 drain 之外补记 run.log |
| `fputc` | `int (int ch, FILE *f)` | `printf` 调用点 | MicroLIB retarget 钩子 → 逐字节 `con_append` | 须为全局符号(C 库链接) |
## 6. 常量
| 常量 | 值 | 定义处 | 含义 |
|------|-----|--------|------|
| `DBG_CON_LINE_MAX` | 192 | `console_log.h` | 单行文本上限(含结尾 `'\0'`);`DBG_PRINT` 宏行缓冲尺寸;超限截断 |
| `DBG_CON_LINE_MAX` | 192 | `console_log.h` | 单行文本上限(含结尾 `'\0'`);环形缓冲/入环截断上限;超限截断 |
| `DBG_LINE_BUF_MAX` | = `DBG_CON_LINE_MAX`(深链模块设 **128**) | `dbg_log.h` | **调用点栈上行缓冲尺寸**(2026-10-10 加)。已收紧:`nand_ftl`/`gd5f2gq5ue`/`cfg_file`/`net_cfg`/`net_socket`/`ch395f`/`time_sync`/`default_task`。取值须 ≥ 该模块最长日志行,否则该行被截断 |
| `DBG_TS_BUF_MAX` | 24 | `dbg_log.h` | 时间戳文本缓冲(`sys_clock_get_str` 恒写 19 字符 + `'\0'`);与行缓冲**共用一个数组** |
| `DBG_CON_RING_SIZE` | 4096 | `console_log.c` | 环形缓冲字节数(必须为 2 的幂) |
| `DBG_CON_CHUNK_MAX` | 256 | `console_log.c` | 写者单次发送上限 = 裸发上限 |
| `DBG_CON_WAIT_MS` | 200 | `console_log.c` | 缓冲满时生产者最长等待(1ms/轮,仍满 → 丢弃 + 计数) |
| `CON_TX_TIMEOUT_CYC` | `SystemCoreClock/10` | `console_log.c` | 裸发硬上界 = 100 ms 对应 DWT 周期数 |
| `CON_TX_GUARD_MAX` | 20000 | `console_log.c` | DWT 不可用时的**计数**上界(每字节 / TC 各数一次;115200 下一字节约需 3000 次轮询,留 6 倍余量) |
| 写者循环超时 | 50 ms | `App/task/default_task.c` | `osEventFlagsWait` 兜底周期 |
## 7. 失效模式与可观测性
@@ -247,7 +253,7 @@ sequenceDiagram
| ISR 直发撞写者发送中 | 立即 `HAL_BUSY` 返回 → 丢失,不挂死 | 未计数 | ISR 日志频率极低,best-effort 语义 |
| 内核状态损坏(栈溢出 / 堆耗尽) | 钩子裸发固定文本 → 复位(栈溢出)/ 仅告警(堆) | `[ERR] STACK OVERFLOW task=…` / `[ERR] MALLOC FAILED …`;任务名范围校验 + 本地拷贝,TCB 指针不入 UART | — |
| 调用方指针损坏(脏指针) | 无法进入 UART(缓冲恒为环 / 模块本地) | 2026-09-24 HardFault 路径结构性消除 | — |
| UART 物理失效(发送卡死 / 超时) | `HAL_UART_Transmit` 返回 `HAL_TIMEOUT`;调用点忽略返回值(best-effort 控制台),该块丢弃 | 不入丢弃计数(与"缓冲满"独立);UART 故障由其他可观测面暴露(自检 / 其余日志) | 已知缺口:超时未计数(v2 可加超时计数) |
| UART 物理失效(发送卡死 / 超时) | `con_tx_bounded` 在 DWT 上界内返回 `HAL_TIMEOUT` → 该块丢弃 + **UART 重初始化**(僵尸通道自愈,§12) | 2026-10-10 起:drain 内**只置标志** `s_con_tx_fault_log`,由 defaultTask 在 drain 之外 `syslog_post(SYSLOG_RUN, "console tx fault, uart reinit")` 补记 run.log(旧实现直接在 drain 里调 `syslog_post` → `write_line → con_append → drain → syslog_post → write_line` **调用图成环**,静态栈在每个任务上多叠一条日志尾巴) | 已知缺口:丢弃计数与故障计数不分离 |
| `sys_evt` 对象缺失(句柄 NULL) | 唤醒空操作,50ms 兜底 | 至多 50ms 时延 | — |
## 8. 内存预算
@@ -256,10 +262,12 @@ sequenceDiagram
|----|------|------|
| `s_con_ring` | 4096 B | BSS(SRAM1,非 CCM) |
| `s_con_head/tail/drop/writer_up/writer_task` | ≈14 B | BSS |
| 生产者行缓冲 `__con_line`(`DBG_PRINT` 宏局部) | 192 B/调用 | 调用者任务栈(入环后即释放,峰值非累积) |
| 生产者行缓冲 `__con_line`(`DBG_EMIT` 宏局部) | 默认 192 B + 时间戳 24 B;**深链模块 128 + 24 B**/调用 | 调用者任务栈(入环后即释放,峰值非累积) |
**结论:静态 +4.1KB(SRAM1);FreeRTOS 堆零增量、无新任务/栈、无新内核对象。** 2026-09-25 实测:`heap free=8776B, min_ever=4816B`(CCM 堆不受影响);`__con_line` 192B 峰值在各任务既有栈预算内(任务栈按 2026-09-21 实测峰值 ×1.5 + 1KB ISR 余量定档,见 AGENTS.md 任务栈表;当日栈水位复测 6 任务全 PASS)。
> ⚠️ **2026-10-10 修正"行缓冲不贵"的结论**:行缓冲是**每个日志调用点**在栈上内联的一份数组,会进 armlink 最坏调用链。实测 defaultTask 那条最深链上 **9 个帧各含一份**(192 + 32 B)→ **约 1.5 KB 纯粹是日志行缓冲**,是该项目静态栈链的第一大项。处置 = `dbg_log.h` 新增 `DBG_LINE_BUF_MAX`(深链模块收到 128)+ 时间戳缓冲并入行缓冲(32 → 24),**未抬栈**即把该链 5312 → 2776 B。核算与前后对比见 `docs/内存管理/RAM使用分析与优化方案.md`"2026-10-10 复测"。
## 9. 设计决策与否决的替代方案
| # | 替代方案 | 否决 / 采纳理由 |
@@ -334,3 +342,4 @@ sequenceDiagram
| v1.0 | 2026-09-25 | 初版:完整设计说明(背景根因 / 单写者模型 / 详细设计 / API / 参数 / 失效模式 / 内存预算 / 设计决策 / 验证记录);实现位于 `App/log/console_log.{h,c}` |
| v1.1 | 2026-09-25 | 文件拆分同步:写者任务体迁至 `App/task/default_task.c`(`Src/freertos.c` 只留 CubeMX 接线与崩溃钩子);§3.2 文件清单 / §4.3 / §6 / §11 位置引用更新 |
| v1.2 | 2026-10-02 | 新增 §12 ORE 僵尸通道(台架 gen-288 实证:节奏性喂狗饥饿,非死等;缓解已做、TX-only 根治待办)+ §11 时间线两条 10-02 记录;§13 新增 O4;原 §12/§13 顺延为 §13/§14 |
| v1.3 | 2026-10-10 | 栈链整改(**未抬栈**)三处:① 裸发改为纯寄存器轮询——DWT 不可用时走 `con_tx_counted`(计数上界),**不再回退 `HAL_UART_Transmit`**(tick 依赖不可信 + 其调用链污染所有任务最坏栈链);② 切断 `drain → syslog_post → write_line` **调用图成环**,改为置标志 + 写者任务在 drain 之外补记(新 API `dbg_console_tx_fault_log_take`);③ 行缓冲按模块可收紧(`dbg_log.h` 新增 `DBG_LINE_BUF_MAX`,深链模块 128)+ 时间戳缓冲并入行缓冲(32 → 24)。§4.4 / §5 / §6 / §7 / §8 同步;实测 defaultTask 静态最坏链 5312 → 2776 B |
+13 -12
View File
@@ -1,6 +1,6 @@
# 网口设置页配置说明
> 版本:v2.1 日期:2026-09-14
> 版本:v2.2 日期:2026-10-10
> 适用工程:STM32F4-Base(STM32F407ZGTx + CH395F 以太网控制器)
> 范围:UI「网口设置」页可配置的网口参数(IP 地址 / 子网掩码 / 默认网关 / MAC 地址)——
> 参数与默认值、持久化、生效链路、页面操作与权限。
@@ -13,7 +13,7 @@
|---|---|
| **可配置** | IP 地址、子网掩码、默认网关、MAC 地址 |
| **入口** | 屏幕:`通讯参数 → 网口设置`(修改密码 `123` 可编辑;查看密码 `000` 只读) |
| **存储** | NAND Flash 文件 `/config.ini`(INI `[network]` 段,通用配置文件,掉电不丢);旧版 `/cfg/netcfg.bin` 首启自动迁移 |
| **存储** | NAND Flash 文件 `/config.ini`(INI `[network]` 段,通用配置文件,掉电不丢);旧版 `/cfg/netcfg.bin` **不再读取**(存在则忽略,见 §3.2) |
| **生效** | 按「保存」后**立即生效**(网络重建约 1~3s,既有 TCP 连接会断开,无需重启) |
| **工厂默认** | `192.168.1.100` / `255.255.255.0` / `192.168.1.1` / 芯片内置 MAC |
@@ -83,17 +83,18 @@ mac=00:00:00:00:00:00
完整设计(引擎 / 双端 / 并发模型 D15)见 `docs/子系统设计/配置文件设计说明.md`。
### 3.2 旧版档迁移(首启)
### 3.2 旧版二进制档 `/cfg/netcfg.bin`:已不再兼容(2026-10-10)
旧版格式 `/cfg/netcfg.bin`(28B:magic `'NCFG'` + ver + CRC-16/MODBUS + 18B 字段,格式拥有者 `net_cfg_legacy.c`):
旧版格式 `/cfg/netcfg.bin`(28B:magic `'NCFG'` + ver + CRC-16/MODBUS + 18B 字段)的"首次启动自动迁移"已于 **2026-10-10 删除**(`net_cfg_legacy.c/h` 整体移除,该格式的解析代码不再存在)。
| `/config.ini` 缺失 | `/cfg/netcfg.bin` | 行为 |
| `/config.ini` | `/cfg/netcfg.bin` | 行为 |
|---|---|---|
| 是 | 有效(magic/ver/CRC 通过) | 4 字段转 INI 行 → 写 `/config.ini`(带旧值)→ **删旧档** |
| 是 | 缺失 / 无效 | 生成出厂模板;**保留旧档**(留人工检查) |
| 否 | — | 正常加载现有文件(不迁移) |
| 缺失 | 存在(有效或无效都一样) | 生成出厂模板 → **工厂默认**;旧档**被忽略**(不读、不删、不迁移) |
| 缺失 | 缺失 | 生成出厂模板 → 工厂默认 |
| 存在 | — | 正常加载现有文件 |
迁移单向:旧档转换后删除(避免双数据源);新文件存在时恒跳过。
- **删除理由**:该一次性路径把整条配置**写**链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)无条件挂进启动期调用图,使 defaultTask 的 armlink 静态最坏栈链 **+600 B**;而本仓库不存在携带旧档的设备。
- **对使用方的影响**:卷上仍残留旧档的设备升级本版本后,网口配置**回落工厂默认**(`192.168.1.100/24`、gw `.1`),需经 UI「网口设置」或 FTP 上传 `/config.ini` 重新配置。
### 3.3 存储后端(双端)
@@ -125,7 +126,7 @@ sequenceDiagram
DT-->>NT: 置 SYS_EVT_STORAGE_READY(就绪屏障)
NT->>NT: 等 SYS_EVT_STORAGE_READY(≤10s;超时用工厂默认继续)
NT->>CFG: net_cfg_load()
Note over CFG: 注册 [network] 段 → 首启自动迁旧档 → 读 /config.ini;缺失/非法键 → 工厂默认
Note over CFG: 注册 [network] 段 → 读 /config.ini;缺失/非法键 → 工厂默认
CFG-->>NT: RAM 副本(有效)
NT->>CFG: net_cfg_get() → net_cfg_fmt_ipv4()
CFG-->>NT: "192.168.001.100"
@@ -212,7 +213,6 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| 文件 | 职责 |
|---|---|
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 配置核心:RAM 副本 + 工厂默认 + 加载/保存编排 + MAC 影子 + IPv4 格式化([network] 段注册) |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | 旧版 NCFG 二进制格式拥有者:首启一次性迁移(校验 → 物化 → 删旧档) |
| `App/cfg/cfg_file.h/.c` | 通用配置文件引擎(双端共享核心:解析/校验/外科保存/模板/沉降轮询) |
| `App/cfg/cfg_file_store.h` | 存储后端接口(带路径的文件操作) |
| `App/cfg/cfg_file_store_mcu.c` | MCU 后端:FatFS `/config.ini`(`/config.TMP` + `f_rename` 原子写) |
@@ -226,7 +226,7 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| API | 说明 |
|---|---|
| `net_cfg_load()` | 注册 `[network]` 段 + 旧档迁移 + 从 `/config.ini` 加载到 RAM 副本;缺失/非法键回退工厂默认(恒返回 0) |
| `net_cfg_load()` | 注册 `[network]` 段 + 从 `/config.ini` 加载到 RAM 副本;缺失/非法键回退工厂默认(恒返回 0) |
| `net_cfg_save()` | 经 cfg_file 引擎写 `/config.ini`(外科手术式保存);0 成功 / -1 失败 |
| `net_cfg_request_apply()` | 请求立即应用(页面保存成功后调用) |
| `net_cfg_set_apply_cb()` | 注册"立即应用"平台回调 |
@@ -265,5 +265,6 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| 日期 | 变更 |
|---|---|
| 2026-10-10 | v2.2:**删除旧档 NCFG 迁移**——`net_cfg_legacy.c/h` 整体移除、`cfg_store_legacy_path()` 撤销;旧档 `/cfg/netcfg.bin` 若存在则被忽略(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 +600 B,且本仓库无携带旧档的设备 |
| 2026-09-14 | v2.1:存储迁移至**通用配置文件** `/config.ini`(INI `[network]` 段,cfg_file 引擎:逐键校验 / 外科手术式保存 / 原子写 / 旧档首启迁移 / 沉降轮询);`net_cfg_store.*` 弃用(由 `cfg_file_store.*` 取代);对外 API 不变 |
| 2026-09-10 | MAC 显示改为常识格式 **`A4:94:B6:12:34:56`**(冒号分隔,17 字符);页面布局左移(名 x=4 / 值 x=54)以容纳 102px 宽度,编辑光标段偏移随之为 3i |
@@ -32,7 +32,7 @@
| 项 | 现状 |
|---|------|
| 卷 | GD5F2GQ5UE SPI NAND **256MB**(2048 块 × 64 页 × 2KB),dhara FTL(磨损均衡/坏块管理),FatFS FAT32,`FF_USE_LFN=0` → **文件名必须 8.3** |
| FTP | lftpd 根目录 = 卷根 `/`;现有文件 `/config.ini`、`/cfg/netcfg.bin`(均 8.3) |
| FTP | lftpd 根目录 = 卷根 `/`;现有文件 `/config.ini`(8.3);旧档 `/cfg/netcfg.bin` 已废弃——现固件不读不写也不删(仅可能残留在旧设备上) |
| 并发 | `FF_FS_REENTRANT=1` + 系统锁(`ffsystem.c` osMutex):单次 `f_*` 调用受保护,**多调用序列(open→seek→write→close)需单写者纪律** |
| 墙钟 | `sys_clock_get_str(buf,20)` → `"YYYY-MM-DD HH:MM:SS"`;`sys_clock_get()` → Unix 秒(失败 = `UINT32_MAX`);SD2506 RTC + timeSyncTask TCP 校时 |
| 挂载 | defaultTask:`f_mount(forced=1)`,空卷自动 `f_mkfs` FAT32,成功后置 `SYS_EVT_STORAGE_READY` |
@@ -34,7 +34,7 @@
| ③ | FatFS 零并发保护(`FF_FS_REENTRANT=0`,应用层无锁) | ftpTask(FTP 传输)/ defaultTask(配置加载/保存/轮询)并发触卷 → 卷损坏 | `FF_FS_REENTRANT=1` + ffsystem.c 互斥钩子(§5.4)+ 三条一致性协议(D15:tmp+rename / 沉降去抖 / 单写者) |
| ④ | 无变更检测 | FTP 上传后不生效(需重启) | defaultTask 每 2s `f_stat` 轮询(size + mtime)(§5.3) |
| ⑤ | `FF_FS_NORTC=1` → 时间戳恒为固定值 | mtime 不变 → 无法检测"同尺寸编辑"(如 IP 尾段 100→200) | `NORTC=0` + `get_fattime`(复用调试日志墙钟源,运行期零 I2C)(§5.5) |
| ⑥ | 固件升级无迁移 | 现场设备升级后用户配置丢失(回工厂默认) | NCFG blob → INI 自动迁移(§9) |
| ⑥ | 固件升级无迁移 | 现场设备升级后用户配置丢失(回工厂默认) | NCFG blob → INI 自动迁移(§9;该迁移已于 2026-10-10 撤销) |
| ⑦ | RAM 静态区仅余 5.8 KB(占用 95.4%) | 不能新增大静态缓冲 | 稳态零增量 + 暂态堆缓冲(§8) |
### 1.3 非目标(本期不做)
@@ -57,7 +57,7 @@
| D7 | 线程安全 | **`FF_FS_REENTRANT=1` + `Lib/FatFs/ffsystem.c`**(`ff_mutex_*` → osMutex,超时 1s) | FatFs 官方可重入方案;每次 f_* 调用加锁/放锁(逐次粒度,不长期阻塞 FTP 传输);`FR_TIMEOUT` 防死锁 |
| D8 | 时间戳 | **`FF_FS_NORTC=0` + `get_fattime`(复用 `sys_clock` 墙钟,与调试日志时间戳同源)** | mtime 为真实时间、与日志时间戳一致;墙钟 = 开机 RTC 基准 + tick 推进,运行期零 I2C;墙钟无效时回退固定值(有开机告警) |
| D9 | 写原子性 | **一切写入 = 临时文件 + `f_rename`**(设备保存 / FTP STOR / 出厂模板统一:先写 `<名>.TMP`(8.3 兼容),关文件成功后 rename 目标) | 同目录 rename = 单目录项写入,卷锁下原子 → 并发读者只见"旧完整文件或新完整文件",半截窗口结构消除;STOR 中途掉电仅残留 TMP(无害);逐键容错 + 下次保存自愈为次级兜底 |
| D10 | 旧档迁移 | 新固件首启:NCFG blob → INI,**迁移后删除旧文件** | 防现场升级丢配置;否决"保留旧文件"(双数据源、陈旧值混乱) |
| D10 | 旧档迁移 | ~~新固件首启:NCFG blob → INI,迁移后删除旧文件~~ → **2026-10-10 撤销:旧档不读、不删、不迁移** | 原理由:防现场升级丢配置;否决"保留旧文件"(双数据源、陈旧值混乱)。**撤销理由**:该一次性路径把整条配置写链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)挂进启动期调用图,defaultTask 静态最坏栈链 +600 B;本仓库不存在携带旧档的设备 → 按"不保留旧档兼容"处置(见 §9.1) |
| D11 | 出厂默认 | 文件缺失时**自动生成带注释模板**并落盘 | 首启 / 恢复出厂场景;文件自身即"格式手册" |
| D12 | 恢复出厂 | **FTP `DELE /config.ini`**(下次上电重新生成) | 本期不做 UI 入口(开放问题 O1) |
| D13 | 双端 | 共享 `cfg_file` 核心 + 平台 store 后端(沿用 net_cfg_store 模式) | MCU = FatFS `/config.ini`;模拟器 = 宿主文件 `sim_config.ini` |
@@ -130,7 +130,7 @@ mac=00:00:00:00:00:00
| 场景 | 加载行为 | 保存行为 |
|---|---|---|
| 文件不存在 | 工厂默认;自动生成模板(有旧 blob 时优先迁移,见 §9) | 写当前值 |
| 文件不存在 | 工厂默认;自动生成模板(旧 blob 若存在也**不读、不迁移**,见 §9.1) | 写当前值 |
| 段缺失 / 键缺失 | 该键取工厂默认 | 补写缺失键(段缺失则补整段) |
| 键值非法 | 该键取工厂默认 + 日志 | 重写为合法值(自愈) |
| 文件 > 4KB / 严重损坏 | 全部工厂默认 + `DBG_ERROR` | 整文件重生成(已知数据保留,未知内容丢失) |
@@ -155,7 +155,7 @@ mac=00:00:00:00:00:00
| `Lib/FatFs/ffconf.h` | 修改 | MCU | `FF_FS_REENTRANT` 0→1;`FF_FS_NORTC` 1→0 |
| `App/util/sys_clock.c/.h` | 修改 | MCU | 新增 `sys_clock_get_time()`(日历时间获取器);改归 ④中间件层,自 `App/` 移入 `App/util/` |
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 重构 | 双端 | `net_cfg_*` API 不变;load/save 走 cfg_file;注册 `[network]` 段 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | **新增** | 双端 | 旧 NCFG blob 解析 + 迁移 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | **已删除**(2026-10-10;曾于 P3 新增) | — | 旧 NCFG blob 解析 + 迁移;随"不保留旧档兼容"整体移除(见 §9.1) |
| `Drivers/BSP/NETCFG/net_cfg_store.h` / `net_cfg_store_mcu.c` | **删除** | — | 由 `cfg_file_store.*` 取代 |
| `simulator/sim/net_cfg_store_sim.c` | **删除** | 模拟器 | 由 `cfg_file_store_sim.c` 取代 |
| `App/task/net_task.c` | 修改 | MCU | 主循环每 2s `cfg_file_poll()` |
@@ -234,11 +234,7 @@ sequenceDiagram
participant NS as 网络层
DT->>FS: f_mount(无卷自动 f_mkfs)
alt /config.ini 不存在
alt 存在旧 /cfg/netcfg.bin
DT->>FS: 读旧 blob 校验 → 转写 /config.ini → 删旧文件
else 无旧文件
DT->>FS: 生成出厂模板 → 写 /config.ini
end
DT->>FS: 生成出厂模板 → 写 /config.ini(旧 /cfg/netcfg.bin 不读、不删、不迁移)
end
DT->>FS: net_cfg_load():读 /config.ini → 解析入 RAM 副本(含 [network] 段注册)
DT->>NT: 置 SYS_EVT_STORAGE_READY(= 卷挂载 + 配置就绪)
@@ -385,11 +381,11 @@ void *cfg_store_alloc(size_t size); /* 失败返回 NULL,由调用方
void cfg_store_free(void *p_buf); /* NULL 安全;只接受 cfg_store_alloc 返回值 */
uint32_t cfg_store_heap_free(void); /* 空闲堆诊断量;模拟器恒 0 */
/* 路径由后端提供(MCU: /config.ini、/config.TMP、/cfg/netcfg.bin;
* 模拟器: sim_config.ini、sim_config.ini.tmp、sim_netcfg.bin) */
/* 路径由后端提供(MCU: /config.ini、/config.TMP;
* 模拟器: sim_config.ini、sim_config.ini.tmp)
* 注:旧版遗留档 getter cfg_store_legacy_path() 已于 2026-10-10 随旧档迁移删除 */
const char *cfg_store_config_path(void);
const char *cfg_store_tmp_path(void);
const char *cfg_store_legacy_path(void);
```
### 6.3 未来模块接入模板
@@ -427,7 +423,7 @@ void adc_cfg_init(void)
### 6.4 net_cfg 兼容承诺
- `net_cfg_load/save/get/set/get_mac/set_active_mac/fmt_ipv4/set_apply_cb/request_apply` **签名与语义全部不变**
- 内部:`net_cfg_load` = 段注册 + 旧档迁移(`net_cfg_legacy_migrate`)+ `cfg_file_load`;`net_cfg_save` = `cfg_file_save`
- 内部:`net_cfg_load` = 段注册 + `cfg_file_load`(旧档迁移已于 2026-10-10 删除);`net_cfg_save` = `cfg_file_save`
- UI 页(`ui_page_net_cfg.c`)、启动流程(`net_task.c`)、模拟器入口(`main.c`)的**调用点无需改动**
## 7. 双端支持
@@ -437,11 +433,11 @@ void adc_cfg_init(void)
| 后端 | `cfg_file_store_mcu.c`(FatFS) | `cfg_file_store_sim.c`(宿主文件) |
| 文件路径 | `/config.ini`(NAND 卷根) | `sim_config.ini`(工作目录) |
| mtime 来源 | FAT 打包时间(`fdate<<16\|ftime`,分钟级粒度) | 宿主文件系统(秒级) |
| 旧档迁移 | `/cfg/netcfg.bin`(NCFG blob) | `sim_netcfg.bin`(同格式) |
| 旧档(已废弃) | `/cfg/netcfg.bin` **不读、不删、不迁移**(2026-10-10) | `sim_netcfg.bin` 同样不读、不删、不迁移 |
| 变更轮询 | defaultTask 2s | 主循环 2s(编辑器改动 2s 内反映) |
| 弃用 | — | `net_cfg_store_sim.c` 删除 |
模拟器行为:首跑无 `sim_config.ini` 而有 `sim_netcfg.bin` → 自动迁移并删旧文件;运行中用编辑器改 `sim_config.ini` → 2s 内自动重载(模拟器无网络重建概念,[network] 段"应用" = 刷新 RAM 副本)。
模拟器行为:首跑无 `sim_config.ini` → 生成出厂模板(残留的 `sim_netcfg.bin` 被忽略);运行中用编辑器改 `sim_config.ini` → 2s 内自动重载(模拟器无网络重建概念,[network] 段"应用" = 刷新 RAM 副本)。
## 8. 内存预算
@@ -464,27 +460,28 @@ void adc_cfg_init(void)
## 9. 兼容与迁移
### 9.1 旧文件迁移(新固件首启)
### 9.1 旧文件迁移:已删除(2026-10-10)
旧 NCFG 二进制档 `/cfg/netcfg.bin` 的"首启自动迁移"已整体删除(`net_cfg_legacy.c/h` 移除、`cfg_store_legacy_path()` 撤销)。现行为:
```
/config.ini 缺失?
├─ 是 → /cfg/netcfg.bin 存在且有效(magic/ver/CRC)?
│ ├─ 是 → 4 字段转 INI 行 → 原子写 /config.ini → 删旧文件
│ │ 日志: "cfg file: legacy NCFG migrated"
│ └─ 否 → 生成出厂模板
├─ 是 → 生成出厂模板 → 工厂默认
│ (/cfg/netcfg.bin 无论存在与否、有效与否:不读、不删、不迁移)
└─ 否 → 正常加载现有文件
```
- 迁移单向:旧文件转换后删除(避免双数据源)
- 旧 blob 损坏:按不存在处理 → 出厂模板(旧文件保留,可人工查看)
- 旧格式(28B:magic 'NCFG' + ver + CRC-16/MODBUS + 18B 字段)已无解析代码;`App/util` 的 CRC-16/MODBUS 模块随之失去唯一调用者
- **删除理由**:该一次性路径把整条配置**写**链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)挂进启动期调用图,使 defaultTask 的 armlink 静态最坏栈链 **+600 B**;而本仓库不存在携带旧档的设备
- 残留旧档不会被自动清理,需要时可经 FTP 手工删除(该文件对现固件无任何作用)
### 9.2 现场升级 / 降级场景
| 场景 | 行为 |
|---|---|
| 旧设备(有 netcfg.bin)升级新固件 | 自动迁移,用户配置保留 |
| 旧设备(有 netcfg.bin)升级新固件 | 旧档**被忽略**(不读、不删、不迁移)→ 配置回落工厂默认,需经 UI / FTP 重新配置(2026-10-10 起) |
| 新设备 / 格式化卷 | 出厂模板 |
| 新文件被**旧固件**读(降级) | 旧固件只读 `/cfg/netcfg.bin`(已删)→ 工厂默认;**降级需人工恢复网口配置**(已知限制,O2) |
| 新文件被**旧固件**读(降级) | 旧固件只读 `/cfg/netcfg.bin`——现固件不读也不删该文件,故它仍在卷上、旧固件按旧值运行;**新旧固件配置互不同步**(已知限制,O2) |
| 含未来段的新文件被旧固件读 | 旧固件不读该文件 → 文件不受影响 |
### 9.3 恢复出厂
@@ -515,7 +512,7 @@ void adc_cfg_init(void)
|---|---|---|
| `cfg file: loaded OK` | 上电加载成功 | Required(P4 起纳入 `@test`) |
| `cfg file: factory template written` | 首启 / 删文件 | 条件(手工) |
| `cfg file: legacy NCFG migrated` | 旧文件迁移 | 条件(真机首次升级) |
| `cfg file: legacy NCFG migrated` | 旧文件迁移 | **已作废**(2026-10-10 旧档迁移删除,该标记不再产生) |
| `cfg file: saved OK` | UI 保存 | 条件(手工) |
| `cfg file: change detected` | FTP 上传 | 条件(P7 闭环) |
| `cfg file: ... FAILED` | 任何失败 | Forbidden |
@@ -589,3 +586,4 @@ void adc_cfg_init(void)
| 2026-09-22 | v1.12:**E-01 超长守卫根治**(错误码传播审计第一档 #3,commit d030cb0)——poll 的"超长=损坏"检查写在截断后读长(`s_load_file` 读长钉死 4096)上**恒假=死码**,超长文件被截断当有效解析 → 界外静默回工厂默认 → 基线粘住 → 用户配置销毁链。① poll 沉降分支判定改用 `cfg_store_stat` 真实 `st.size` 且**先于读取**,超长走"工厂默认 + 重建模板 + `cfg poll oversized` 自检留痕",**刻意不触发段回调**(避免按工厂 IP 重建网络造成静默失联,保留 FTP 修复通道,保存/重启收敛)② `s_surgical` 输出上限 `CFG_OUT_MAX`(5120) 收紧至 `CFG_FILE_MAX_LEN`(4096),超限走溢出中止路径(原文件不动 + `cfg save overflow` 自检)——消除"合法写出 4097~5120B 文件 = 下次开机销毁"的雷 ③ `s_write_doc` 唯一落盘闸口硬拒 >4096B(结构性保证)④ load 超长分支补 `cfg load oversized` 自检;`cfg_file.h` v1.2 契约同步;模拟器 cfgtest 新增两回归锁(4160B 伪造 → poll 判损坏重建;4000B 注释文件 → save 拒写且原文件未动)。**台架验证暴露并修复**:`cfg_store_write_atomic` MCU 后端 `f_rename` 对已存在目标返回 `FR_EXIST` 不覆盖 → 设备端一切"目标存在的覆盖写"(UI 保存/模板重建)静默失败、仅首启建文件成功——v1.9 已为模拟器后端修过同款(MSVC `rename()` C1117),MCU 侧漏了对称修复;修复 = rename 前先 `unlink` 目标(commit 70f1cc8)。双端 0 错 0 警、cfgtest PASS、台架 FTP 5047B 用例 poll/load 双路径自愈 PASS(重建 523B 工厂模板 + selftest 留痕 + 不掉线)、`@test` 默认闭环 PASS、ST-LINK 全片回读一致 |
| 2026-10-03 | v1.13:**真机验证 PASS(A1 闭环)**——v1.11 的 P0-1 堆分配改造在真机上验证通过:`[CFG] load: OK (523B, heap free 8840B)`(**经 FreeRTOS 堆分配**,即 512B C 堆约束的验收点);Forbidden `FAIL`/`[ERR]` 零命中;FTP 改配置 IP→`192.168.1.101` 后**复位即加载生效**(`[NET_CFG] load: OK ip=192.168.1.101` + 芯片 `IP_INF` .101 + 21 端口探测可达),原档恢复后复测回 .100。真机全闭环 `@test` PASS 7/7。**台架步骤与判据见 `测试说明/实机测试实施指南.md` §3 / §10** |
| 2026-10-04 | **P0-1 真机独立复跑 PASS(二次确认,代码零改动)**——`@build` 0/0(增量,无源文件新于镜像)→ `@test` **VERDICT PASS(rc=0)**:`[2026-10-04 00:01:21][CFG] load: OK (523B, heap free 8840B)` + `[NET_CFG] load: OK ip=192.168.1.100`,Forbidden `FAIL`/`ERROR`/`HardFault`/`[ERR]` 零命中,`selftest: no fault record`(先清掉 2026-10-02/03 故障注入窗口遗留的 67 条台账)→ ST-LINK 全片回读 `No difference found`(`0x08000000~0x08040F9B`)。**本次为 `待处理内容.md` §4.1「待验证」唯一未闭环项(P0-1)的收口**,该节现已清空 |
| 2026-10-10 | v1.14:**删除旧档 NCFG 迁移(D10 撤销)**——`Drivers/BSP/NETCFG/net_cfg_legacy.{c,h}` 整体移除、`cfg_store_legacy_path()` 撤销(MCU / 模拟器后端与主机单测 `test/host/cfg_file/cfg_file_test.c` 同步删除),`net_cfg_load` 不再调用 `net_cfg_legacy_migrate()`;旧档 `/cfg/netcfg.bin` 若存在则被**忽略**(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 **+600 B**(该链整体 5312 → 2776 B,另三个杠杆见 `docs/内存管理/RAM使用分析与优化方案.md`),而本仓库不存在携带旧档的设备。连带影响:`App/util` 的 CRC-16/MODBUS 失去唯一调用者(现为零调用者);模拟器 `--cfgtest` 不再覆盖迁移场景;§2 D10 / §3.5 / §4.2 / §5.1 / §6.2 / §6.4 / §7 / §9 / §11.1 同步 |
+18 -18
View File
@@ -38,7 +38,7 @@
| 级别 | 数量 | 关键项 |
|---|---|---|
| **P1** | 1 (+1 收尾) | **P1-1 ADC 采样路径产品构建中死亡**;P1-10 测试固件构建实测闭环(主体已闭环,见 §3.1) |
| 登记簿 | 10 | 网络:N-2(49s 日志静默,**低风险观察项**)、N-3(L5 基线重验)、N-4(pending 二期)、N-16(A2/A4 专项负向验证)、N-17(探针窗口心跳缺口);存储/日志:**S-1(defaultTask 栈余量 10%,需尽快处理)**、S-3(USART1 TX-only 根治)、S-4(dhara 重试自旋)、S-5(可选全序列卷锁);硬件:**N-18(8 路电气量分属两转换器 = V0.1 缺陷,V0.2 改版项)** |
| 登记簿 | 9 | 网络:N-2(49s 日志静默,**低风险观察项**)、N-3(L5 基线重验)、N-4(pending 二期)、N-16(A2/A4 专项负向验证)、N-17(探针窗口心跳缺口);存储/日志:**S-3(USART1 TX-only 根治)**、S-4(dhara 重试自旋)、S-5(可选全序列卷锁);硬件:**N-18(8 路电气量分属两转换器 = V0.1 缺陷,V0.2 改版项)** |
| 测试/基础设施 | 5 | T-7(CI runner 未就绪)+ 测试架构专项 **S12**(断言宏)、**S1-c**(可选单测)、**A-4**(采集链路测试约束)、**A-5 残余**(CI 体积门禁盲区) |
> **网络侧另有 E-xx 未闭环项**(E-12/15/16/23/24/25/26/27/28/30/47/48/49/50/58/59/61,共 17 项)
@@ -51,8 +51,9 @@
2. **真正的产品功能缺口在 ADC 与 RS-485 采集链路**:`adc_task`/`rs485_task` 均为空桩,
DTU 的核心职责(数据采集与转发)**尚未实现**。这既是 P1-1 的根源,也是 E-59 那条
"休眠缺陷闸门"的另一面——不是缺陷难修,而是**功能没接**。
3. **验证债已基本还清,重心是"功能债" + 一项栈风险**:剩余验证债仅 **N-3(L5 基线重锁,
待 PC peer)** 与 **S-1(defaultTask 栈余量已跌至 10%,见 §6)**。
3. **验证债已基本还清,重心是"功能债"**:剩余验证债仅 **N-3(L5 基线重锁,待 PC peer)**
(原 **S-1 栈风险已于 2026-10-10 削链闭环并销项**:defaultTask 静态最坏链 5312 → 2776 B、
实测峰值 3392 → 2152 B,**未抬栈**,见 `docs/内存管理/RAM使用分析与优化方案.md`)。
**当前最值得投入的是阶段 B(ADC/RS485 功能)**。
---
@@ -72,7 +73,7 @@
| 序 | 任务 | 前置 | 说明 |
|---|---|---|---|
| **B0** | **产品定义确认**(阻塞项) | — | 需甲方明确:① 采集是否持续 ② 采样率(**通道语义已于 2026-10-09 确认并固化 → `docs/硬件平台/ADC通道映射说明.md`**:UA/UB/UC/UN=`AIN1~4`、IA/IB/IC/IN=`AIN8~11`、V_ZL2=温度、其余 8 路备用;主板只做监视/辅助测量,计量在子板。**注意:8 路电气量分属 TPAFE/STM32 两片转换器属 V0.1 硬件缺陷 → N-18**,主板侧不得做跨转换器的相位/功率类运算) ③ 转发目标(MODBUS RTU?上行 TCP?)④ `APP_ADC_ENABLE`/`APP_RS485_ENABLE` 语义。**B1/B2 的规模完全取决于此** |
| **B1** | **ADC 采集链路实现**(消化 P1-1;E-58 已闭环) | B0 + **E-59 门禁**(TPAFE 侧已满足) | 周期 CONVST 重触发 + `adc_task` 消费 **`tpafe5160_snapshot()`**(**2026-10-10 驱动契约已修**:E-44 FRSTDATA 有效性校验 + `init()` 首帧自检返 `-3`、E-45 帧序号取代就绪电平标志、E-58 临界区原子快照;旧 `get_buf`/`data_ready`/`clear_ready` **已删**,删除前零调用者);`adc_task.c` **已挂进 `uvprojx`**(此前 Keil/模拟器两边都不注册 = 永不编译;simulator 无任务层,只挂 Keil);**仍欠**:栈预算核算(两任务未入 `freertos.c` 栈表,建任务前后各跑 `stack_report.py`;先处理 S-1)+ 消费者本体 + 建任务走 **L1(`.ioc` 的 `FREERTOS.Tasks01`)**;**取数必须按 `docs/硬件平台/ADC通道映射说明.md`**(TPAFE `buf[2]=IA`/`buf[5]=UN`/`buf[6]=UA`;STM32 侧 ADC1 **已于 2026-10-09 生成**:`hadc1` + `MX_ADC1_Init()`、9 通道 Rank1~9、144 cycles、ADCCLK=21 MHz、软件触发,网名 ADC17=PC4 用 `ADC_CHANNEL_14`;⚠ 全仓库至今**无 `HAL_ADC_Start`** → STM32 侧一路未跑) |
| **B1** | **ADC 采集链路实现**(消化 P1-1;E-58 已闭环) | B0 + **E-59 门禁**(TPAFE 侧已满足) | 周期 CONVST 重触发 + `adc_task` 消费 **`tpafe5160_snapshot()`**(**2026-10-10 驱动契约已修**:E-44 FRSTDATA 有效性校验 + `init()` 首帧自检返 `-3`、E-45 帧序号取代就绪电平标志、E-58 临界区原子快照;旧 `get_buf`/`data_ready`/`clear_ready` **已删**,删除前零调用者);`adc_task.c` **已挂进 `uvprojx`**(此前 Keil/模拟器两边都不注册 = 永不编译;simulator 无任务层,只挂 Keil);**仍欠**:栈预算核算(两任务未入 `freertos.c` 栈表,建任务前后各跑 `stack_report.py`;原 S-1 栈风险已于 2026-10-10 **削链销项**,defaultTask 现静态 2776/4096 余 32%、实测 2152 B)+ 消费者本体 + 建任务走 **L1(`.ioc` 的 `FREERTOS.Tasks01`)**;**取数必须按 `docs/硬件平台/ADC通道映射说明.md`**(TPAFE `buf[2]=IA`/`buf[5]=UN`/`buf[6]=UA`;STM32 侧 ADC1 **已于 2026-10-09 生成**:`hadc1` + `MX_ADC1_Init()`、9 通道 Rank1~9、144 cycles、ADCCLK=21 MHz、软件触发,网名 ADC17=PC4 用 `ADC_CHANNEL_14`;⚠ 全仓库至今**无 `HAL_ADC_Start`** → STM32 侧一路未跑) |
| **B2** | **RS-485 采集链路实现**(消化 E-50、E-49) | B0 + **E-59 门禁**(**尚未满足**:E-50 / E-49 未修) | 双缓冲 + 序号(防包撕裂)+ DE 收尾(超时路径等 TC)+ 协议状态机(`App/protocol/` 正是为此预留) |
**建议**:驱动契约与编译接线已在 2026-10-10 铺完 → B1 的下一步是**栈预算 + 建任务(L1)**,
@@ -84,7 +85,6 @@
|---|---|---|---|
| **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** | 一档剩余 | E-49(RS485 DE 收尾)、E-15(ringbuf 定去留——**已确认零调用者,倾向删除**)、E-61(SD2506 注释订正)、E-16(cfg_file 段名长度)、E-26(UDP 定案,倾向移除)、E-23、E-30 | 一档/二档 |
| **C6** | **USART1 TX-only 结构根治**(S-3) | 灭 ORE 僵尸机制根源:改 `.ioc` 令 USART1 不使能 RX / PA10 不复用为 `USART1_RX` → CubeMX 重生成 → 跑 `check_cubemx_survival.py`。属结构性改动,需单开一轮 + 复跑台架闭环 | S-3 |
@@ -102,7 +102,7 @@
【阶段 B 功能】 B0 产品定义确认 ─┬─ B3 E-59 门禁固化(并行)
(当前最高优先) └─ E-58 原子快照 → B1 ADC → B2 RS485
↓
【阶段 C 结构】 C1 pending 二期 ─ C3 日志管线(N-2/S-3) ─ C4 栈(S-1)
【阶段 C 结构】 C1 pending 二期 ─ C3 日志管线(N-2/S-3)
─ C5 收口 ─ C6 USART1 TX-only
↓
【阶段 D 常态】 D2 N-3 / D5 N-16 负向 / D6 N-17 探针窗口
@@ -145,7 +145,6 @@
| # | 事项 | 发现 | 证据与现状 | 出处 |
|---|---|---|---|---|
| S-1 | defaultTask 静态最坏链余量贴线:`storage_bring_up → … → dhara GC → SPI` 链 | 2026-09-22;2026-10-04 重采升级 | **2026-10-04 重采(升级为需尽快处理)**:产品构建(`suite=NONE`)V5 30 min 真实负载实测 **3664B/4096(余量 432B = 10%)**,**已远低于 30% 判据**。处置:栈 4096→4608(CCM 堆余量可覆盖 +512B)或 GC 链缓冲局部削减;建议**提前到阶段 B 之前**处理 | 本文件 §2.2 C4 / §6 |
| S-3 | **USART1 TX-only 结构根治(灭 ORE 僵尸机制)**——ORE 锁存在外设软复位后毫秒级重锁(PA10 悬空 + RX 使能),是 2026-10-02 台架"背靠背 drain 拉长主循环 → 喂狗节奏饥饿 → IWDG 误复位"的**机制根源**。现仅有缓解(单次 drain 250ms 预算 + 发送故障后 300ms 冷却),**未根治** | 2026-10-02(台架 gen-288) | **待办**:改 `.ioc` 令 USART1 为 TX-only → CubeMX 重生成 → 跑 `scripts/check_cubemx_survival.py`。结构性改动,需单开一轮并复跑台架闭环 | `子系统设计/控制台单写者日志设计说明.md` §12(含 O4) |
| S-4 | **dhara 重试自旋残留**——`try_recover()` 的 `DHARA_MAX_RETRIES`(8) 自旋在坏块密集时仍可能长时间占用 defaultTask(喂狗点) | 2026-10-02 | 低优先:现有 `vol_lock` + 喂狗节奏缓解;若台架再出"存储侧喂狗饥饿"再排期 | `Lib/dhara/map.c` |
| S-5 | **可选全序列卷锁**——现卷锁仅覆盖 `syslog` / `cfg` / `lftpd` 三条路径(`vol_lock_wait(0)` try 语义),其它潜在访问点(如 UI 读日志)未纳入 | 2026-10-02 | 待评估:先确认是否真有第二写者;无则关闭本项 | `App/fs/vol_lock.{c,h}` |
@@ -244,8 +243,8 @@ S14 已在 workflow 显式提示"体积门禁未执行"(`::notice::`)并在
**完成判据**:存在**强制的**体积校验路径(CI 侧或本地侧),且文档明确其边界。
> **注**:本项与 §2.2 C4(**S-1:defaultTask 栈 3664/4096 ≈ 10%**,需尽快处理)是同一话题的两面
> ——S-1 是**具体风险**,本项是**门禁缺口**。
> **注**:本项与栈预算是同一话题的两面——**具体栈风险(原 S-1)已于 2026-10-10 削链闭环销项**
> (defaultTask 静态最坏链 5312 → 2776 B、实测 3392 → 2152 B,**未抬栈**),本项仍是**门禁缺口**。
---
@@ -311,21 +310,22 @@ S14 已在 workflow 显式提示"体积门禁未执行"(`::notice::`)并在
## 6. 诊断基线(供后续对照)
> **2026-10-04 重采**:栈/堆 = **运行时高水位**(产品构建 `suite=NONE`,V5 30 min 真实负载,串口 `[STACK]`,
> 证据 `/tmp/v5_soak_serial_20261003.log`);静态区 = `.map` 受管快照。
> ⚠️ **栈水位较 2026-09-21 基线明显上升**(defaultTask 2136→**3664 B**、ftpTask 1184→**2516 B**),
> 系功能与加固陆续入链所致。**S-1 需尽快处理**,见 §2.2 C4。
> **2026-10-10 重采(栈)/ 2026-10-04 重采(堆)**:栈 = **运行时高水位 + armlink 静态最坏链双口径**
> (产品构建 `suite=NONE`,开机 + 空闲采样,串口 `[STACK]` + `stack_report.py`);静态区 = `.map` 受管快照。
> ✅ **栈水位已回落**:2026-10-04 曾升到 defaultTask **3664 B**(登记 S-1);2026-10-10 削链
> (删旧档迁移 + `FIL` 静态化 + 日志行缓冲按模块收紧 + 钩子脱离 HAL)后实测 **2152 B**、
> 静态最坏链 **2776 B**,**未抬栈**即达标。完整核算见 `docs/内存管理/RAM使用分析与优化方案.md`。
| 项 | 当前值(2026-10-04) | 出处 |
| 项 | 当前值(栈:2026-10-10;其余:2026-10-04) | 出处 |
|---|---|---|
| 产品构建 Flash (ER_IROM1) | 266,632 B / 1024 KB(25.4%);ROM 合计 **267,372 B** | `.map` 受管快照 |
| 产品构建 RW_IRAM1 | **84,960 / 131,072 B(64.8%)** | `.map` 受管快照(`RAM使用分析与优化方案.md` §2.1) |
| 产品构建 RW_CCM | 49,184 / 65,280 B(75.3%) | 同上 |
| 实测堆 | free=8,664 / **min_ever=4,728 B** | V5 串口 `[STACK] heap` |
| **defaultTask 栈峰值** | **3664 / 4096(余量 432 B ≈ 10%,⚠️ 低于 30% 判据)** | V5 串口 `[STACK]`(产品构建) |
| netTask 栈峰值 | 1228 / 4096(余量 70%) | 同上 |
| ftpTask 栈峰值 | 2516 / 4096(余量 38%) | 同上 |
| uiTask 栈峰值 | 4060 / 16384(余量 75%) | 同上 |
| **defaultTask 栈峰值** | **2152 / 4096(余 1944 B = 47%);静态最坏链 2776 B(余 32%)** | `[STACK]` + `stack_report.py`(产品构建) |
| netTask 栈峰值 | 920 / 3840(余 76%);静态 1872 B | 同上 |
| ftpTask 栈峰值 | 740 / 4096(余 81%);静态 1216 B | 同上 |
| uiTask 栈峰值 | 4016 / 16384(余 75%);静态 1336 B | 同上 |
| timeSyncTask 栈峰值 | 792 / 2048(余量 61%) | 同上 |
| testTask 栈峰值(产品态空转) | 496 / 8192(余量 93%) | 同上 |
| 队列响应性(挂起客户端) | 92.5ms(pending 一期) | `e02_probe.py` |
@@ -126,7 +126,7 @@ if (sys_evtHandle != NULL) {
> 因此只在 netTask 上下文广播。
### Step 3 defaultTask 置位存储就绪(生产者)
在 `defaultTask` 完成 FTL/FatFS 挂载 **+ 网络配置加载(`net_cfg_load`,含首启旧档迁移;失败回退工厂默认)** 后:
在 `defaultTask` 完成 FTL/FatFS 挂载 **+ 网络配置加载(`net_cfg_load`;旧档 `/cfg/netcfg.bin` 不读、不迁移,失败回退工厂默认)** 后:
```c
osEventFlagsSet(sys_evtHandle, SYS_EVT_STORAGE_READY);
```
+3 -2
View File
@@ -163,7 +163,7 @@ flowchart LR
end
```
- 掉电持久化:`net_cfg` 配置(通用配置文件 `/config.ini`,INI `[network]` 段,cfg_file 引擎),保存后下次上电生效、或立即生效(网络重建)、外部变更 4s 沉降后自动重载;旧版 NCFG 二进制(`/cfg/netcfg.bin`)首启自动迁移。
- 掉电持久化:`net_cfg` 配置(通用配置文件 `/config.ini`,INI `[network]` 段,cfg_file 引擎),保存后下次上电生效、或立即生效(网络重建)、外部变更 4s 沉降后自动重载;旧版 NCFG 二进制(`/cfg/netcfg.bin`)**已不再读取**——存在则忽略(不读、不删、不迁移),配置回落工厂默认(自动迁移已于 2026-10-10 删除)。
- 日志记录:`sys_log` 引擎写定长 64B 文本记录到 `/log/run.log`(运行记录)与 `/log/selftest.log`(自检记录),defaultTask 唯一写者,FTP 可直下(详见 `docs/子系统设计/运行与自检记录设计说明.md`);实时调试流 = 控制台单写者日志(USART1,4KB 环形缓冲 + defaultTask 唯一发送,详见 `docs/子系统设计/控制台单写者日志设计说明.md`)。
- NAND 坏块表(BBT)持久化在保留块池,不交给 dhara 管理。
@@ -266,7 +266,7 @@ STM32F4-Base/
│ ├── SD2506/ RTC
│ ├── RS485/ 半双工串口
│ ├── NET/ net_socket / net_select / lftpd
│ ├── NETCFG/ 网口配置(INI [network] 段 + 旧档 NCFG 迁移)
│ ├── NETCFG/ 网口配置(INI [network] 段)
│ ├── UI/ LVGL UI(与模拟器共用)
│ └── dbg_log.h/dbg_cfg.h 分级日志
├── Lib/ 第三方:LVGL v9.2、FatFs、FreeRTOS、CMSIS
@@ -343,3 +343,4 @@ flowchart LR
| 2026-09-21 | **栈宽整改落地(内存方案 §1.4:lftpd 大局部缓冲静态化)**:`Drivers/BSP/NET/lftpd/lftpd.c` + `lftpd_log.c` 共 **9 处**大局部数组(`message[256]`/`response[512]`/`name[256]`×2/`buffer[1024]`×2/`read_buffer[512]`/`arg_buf[512]`/`client` 会话上下文/日志 `buffer[256]`)改为函数级 `static`(lftpd 单客户端、全部在 ftpTask 内串行);**ftpTask 实测峰值 3480 → 1184 B(余量 15% → 71%,判定 PASS)**,静态调用图 2528 → 472 B,代价静态 BSS +4,616 B(RW_IRAM1 82,048 / 131,072 B,余量仍 ~48 KB);§2.1 栈实测数据同步,原"涨栈到 1600 words"方案取消;FTP 功能回归 10/10 + 128KB 逐字节一致 |
| 2026-09-24 → 09-25 | **控制台单写者日志**(09-24 实证驱动:多任务并发日志字符级交错 + HardFault 脏指针直达 UART):4KB 环形缓冲 + defaultTask 唯一发送(行级原子 + 指针可控)+ 内核前/ISR/异常钩子有界直发回退 + `fputc` 收敛到单写者路径;09-24 初版实施于 `Src/usart.c` USER CODE 区,09-25 迁出至 `App/log/console_log.{h,c}`(HAL 生成文件不含应用代码),`dbg_log.h` 改 `#include "console_log.h"`(调用点零改动),`Src/usart.c` 恢复纯 CubeMX 形态;`@build` 0/0 + `@test` PASS;详见 `docs/子系统设计/控制台单写者日志设计说明.md` |
| 2026-09-25 | **defaultTask 任务体文件拆分**:`Src/freertos.c` 的 `StartDefaultTask` 任务体(存储 bring-up / 配置引擎属主 / 控制台单写者 attach+drain / syslog 落盘 / LED / 栈水位探针 `stack_probe_poll`)整体迁至 `App/task/default_task.c`(`default_task_func()`,与其余任务文件同址);`freertos.c` 恢复"CubeMX 接线 + 崩溃钩子"形态(719→361 行),`@build` 0/0 复验;栈配置属性仍定义于 `freertos.c`(`stack_report` 数据源不变) |
| 2026-10-10 | **删除旧档 NCFG 迁移(栈整改)**:`Drivers/BSP/NETCFG/net_cfg_legacy.{c,h}` 整体移除、`cfg_store_legacy_path()` 撤销,`net_cfg_load` 不再调用 `net_cfg_legacy_migrate()`;旧档 `/cfg/netcfg.bin` 若存在则被忽略(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 +600 B,本仓库无携带旧档的设备。同轮栈整改(未抬栈):defaultTask 静态最坏链 5312 → 2776 B / 实测峰值 3392 → 2152 B、timeSyncTask 1736 → 1024 B,6 任务全 PASS;代价静态 BSS +4,128 B |
@@ -328,7 +328,7 @@ static void phaseN_run(void) {
| `App/cfg/cfg_file.c` | 1250 行 | ✅ 已覆盖(139 断言,2026-10-02 S1-b) | 后端抽象(`cfg_file_store.h`) | 内存虚拟 FS 桩;**首测即抓出外科手术保存死码 P0**(见 §5) |
| `App/log/sys_log_mcu.c` | — | ⬜ 候选 | 记录编解码(定长 64B) | 编解码纯逻辑;写入端依赖 FatFS + 队列,测编解码即可 |
| `App/util/time_sync.c` | — | ⬜ 候选 | 协议帧编解码 | 报文组装/解析纯逻辑;网络收发不测 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | — | ⬜ 候选 | 旧档 NCFG 迁移 + 自复制 CRC | CRC 校验与旧格式解析;可与 cfg_file 同桩 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | — | ⛔ 已删除(2026-10-10) | — | 旧档 NCFG 迁移整体移除(旧档 `/cfg/netcfg.bin` 不读、不删、不迁移),测试候选随之撤销 |
| `App/net/*`(网络栈上层) | — | ⬜ 待评估 | 依赖 socket/CH395F | 纯协议状态机部分或可测,需先确认抽象质量 |
| `App/util/rs485.c` | — | ⏸ 待功能实现 | — | 上游 B2;当前为空桩 |
| `Drivers/BSP/TPAFE5160/*.c` | — | ❌ 不适格 | — | 寄存器序列为主,需真机时序 |
@@ -615,6 +615,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn
| 日期 | 变更 |
|---|---|
| 2026-10-10 | §4.2.1 台账:`Drivers/BSP/NETCFG/net_cfg_legacy.c` 由 `⬜ 候选` 改为 **`⛔ 已删除`**——旧档 NCFG 迁移整体移除(`net_cfg_legacy.{c,h}` 删除、`cfg_store_legacy_path()` 撤销;旧档 `/cfg/netcfg.bin` 不读、不删、不迁移,配置回落工厂默认),该测试候选随之撤销 |
| 2026-10-02 | **S6**:`test/run_all_suites.py` 新增 **`--check-build`**(只编译不烧录,纯离机,不需要 MCU/串口/ST-LINK);新增 **§3.6.1** 说明其解决的问题(测试构建无独立门禁,2026-08~09 CH395F 套件实例)、判定语义与用法。**实测 4 套件全部 `BUILD_OK: 0 errors, 0 warnings`**(即 T-4 提到的"CH395F 不可编译"已修复) |
| 2026-10-02 | **S1-b + S2**:新增 **§4.2.1「L0.5 适格模块台账」**(判据 + 模块状态表 + "两条实证":sys_clock / cfg_file 首测即各抓一处生产缺陷);§4.1 登记 `test/host/cfg_file/` 新单测。同步新增 [`配置文件单元测试说明.md`](配置文件单元测试说明.md)(含抓出的 `s_encode` 返回值误判 P0 缺陷记录) |
| 2026-10-02 | **S3 + S14**:§9.1 第 2 条改正(原文"强制跑 `mem-numbers`"自相矛盾 → 改为"按 `.map` 有无自适应跳过")并加 🔴 门禁盲区声明;§9.2 补 S3 结果可见性(`Tee-Object` 落盘 + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`)。另:§4.1 登记 `test/host/sys_clock/` 新单测(S1-a) |
@@ -507,12 +507,15 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 任务 | 栈大小 | 优先级 |
|---|---|---|
| defaultTask | 2048 | Normal |
| netTask | 4096 | Normal |
| ftpTask | 4096 | Normal |
| testTask | 4096 | BelowNormal |
| uiTask | 16384 | BelowNormal |
| timeSyncTask | 2048 | Normal |
| defaultTask | 1024 | Normal |
| netTask | 960 | Normal |
| ftpTask | 1024 | Normal |
| testTask | 2048 | BelowNormal |
| uiTask | 4096 | BelowNormal |
| timeSyncTask | 512 | Normal |
> 上表由 `scripts/gen_task_table.py` 同源(唯一来源 `Src/freertos.c` / `.ioc` 的 `FREERTOS.Tasks01`);
> 2026-10-10 栈整改**未改任何栈值**,靠削调用链达标(详见 `docs/内存管理/RAM使用分析与优化方案.md`)。
> 挖潜方向(未实施):SRAM3 64 KB / CCM 64 KB 未映射——详见 `docs/内存管理/`。
> 部署侧含义:**不要做任何需要额外 RAM 的固件定制**,新增功能先过内存预算。
@@ -540,7 +543,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 参数 | 工厂默认 | 修改方式 |
|---|---|---|
| IP | **192.168.1.100**(静态,无 DHCP) | 屏幕 `通讯参数 → 网口设置`(修改密码 `123`)保存后**立即生效**(网络重建约 1~3 s);或预置 `/config.ini`(INI;旧版 `/cfg/netcfg.bin` 首启自动迁移) |
| IP | **192.168.1.100**(静态,无 DHCP) | 屏幕 `通讯参数 → 网口设置`(修改密码 `123`)保存后**立即生效**(网络重建约 1~3 s);或预置 `/config.ini`(INI;旧版 `/cfg/netcfg.bin` **不再读取**——存在则忽略、不迁移,配置回落工厂默认,2026-10-10 起) |
| 子网掩码 | 255.255.255.0(/24) | 同上 |
| 默认网关 | 192.168.1.1 | 同上 |
| MAC | 全零 = 芯片内置 MAC | 同上(全零是"使用芯片内置 MAC"的哨兵,不是"MAC 为 0") |
@@ -644,6 +647,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 日期 | 变更 |
|---|---|
| 2026-10-10 | **删除旧档 NCFG 迁移**(升级/迁移口径变更):旧固件遗留的 `/cfg/netcfg.bin` 不再"首启自动迁移"——旧档若存在则被忽略(不读、不删、不迁移),配置直接回落工厂默认(`/config.ini` `[network]` 段模板)。`net_cfg_legacy.c/h` 与 `cfg_store_legacy_path()` 已移除;携带旧档的设备升级后需经 UI 或 FTP 重新配置网口。§五 现场部署 IP 行同步 |
| 2026-10-02 | **模拟器构建修复**(配置引擎重构遗留):`simulator/CMakeLists.txt` 的配置文件引擎引用同步到迁移后位置——包含目录 `App/`→`App/cfg/`(`cfg_file.h`/`cfg_file_store.h`)、源文件 `App/cfg_file.c`→`App/cfg/cfg_file.c`、包含目录 `test/`→`test/board/`(`test_config.h`/`net_latency_test.h`);全量 include 离线核查无其他断链,模拟器全量构建恢复(`--cfgtest` 四场景 PASS,rc=0)。§2.12 验证链 ⑤ 行同步更新 |
| 2026-10-02 | 更正 Device Pack 必装范围:`Keil::CMSIS` pack 不再列为必装项——本工程不依赖它(核心头文件由仓库 `Drivers/CMSIS/` 提供,`.uvprojx` 只引用 DFP pack;2026-10-02 本机实测未装 CMSIS pack 构建 0/0 通过),且其 keil.com 直链(5.7.0~6.1.0)均已下架 404;必装的只有 `Keil::STM32F4xx_DFP`(3.1.1,与 `.uvprojx` PackID 一致)。§2.2 / 第 2 步 / 搭建清单 / §2.10 同步更正 |
| 2026-10-02 | 新增 §2.12「跨机验证(第二台机器 / 换机验收清单)」:变量配置 + sanity check → 六步验证链(含期望结果与失败诊断,含模拟器既有 CMake 问题标注)→ 反向 fail-fast 测试 → 通过判定,作为方案跨机验收的标准操作 |
-1
View File
@@ -104,7 +104,6 @@ set(SYSLOG_SOURCES
# 网络配置(双端共享核心 + cfg_file 引擎 + 平台存储后端;MCU 端编 cfg_file_store_mcu.c,见 Keil 工程)
set(NETCFG_SOURCES
${NETCFG_DIR}/net_cfg.c
${NETCFG_DIR}/net_cfg_legacy.c
${PROJECT_ROOT}/App/cfg/cfg_file.c
sim/cfg_file_store_sim.c
)
+9 -9
View File
@@ -18,7 +18,7 @@ simulator/
│ ├── sim_port.c / .h # 显示/输入端口(SDL 窗口 + 键盘 + mono 主题),替代 MCU 端 ui_port.c
│ ├── net_socket_sim.c # 网络后端:Winsock 实现 net_* BSD API,替代 MCU 端 net_socket.c(CH395F)
│ ├── ui_net_sink_backend_sim.c # RemoDispBus 平台 backend:取帧/注键(配合共享 ui_net_sink.c)
│ └── cfg_file_store_sim.c # 配置文件存储后端:本地 sim_config.ini(旧档 sim_netcfg.bin 首启自动迁移)
│ └── cfg_file_store_sim.c # 配置文件存储后端:本地 sim_config.ini(旧档 sim_netcfg.bin 已废弃,不读不删)
├── lv_conf.h # 模拟器专用 LVGL 配置(与 MCU 端 Lib/lv_conf.h 相互独立)
├── i1_to_png.py # 截图转 PNG:把 --screenshot 的 I1(1bpp) 原始位图转成 PNG(见 §3.5)
├── tests/ # 无头回归脚本 + 本地黄金参考(见 §3.6,流程文档 docs/UI设计/UI_自动化测试流程.md)
@@ -47,11 +47,11 @@ simulator/
| `sim/sim_port.c` | 创建 SDL 窗口显示设备(`lv_sdl_window_create`)、初始化 **mono 主题**(`lv_theme_mono_init`,1bpp 必需)、创建键盘输入设备并挂默认输入组。**不创建鼠标**(纯键盘导航,与 MCU 端 keypad 一致)。`sim_port_get_screen_mono()` 经 `SDL_RenderReadPixels` 读回屏幕像素转 1bpp,供推流取帧 |
| `sim/net_socket_sim.c` | 网络后端:Winsock 实现 `net_*` BSD Socket API(`socket`/`bind`/`listen`/`accept`/`recv`/`send`/`close`),替代 MCU 端 `Drivers/BSP/NET/net_socket.c`(CH395F) |
| `sim/ui_net_sink_backend_sim.c` | RemoDispBus 平台 backend:`ui_net_sink_get_lcdmem`→`sim_port_get_screen_mono`、`ui_net_sink_inject_key`→`lv_group_send_data`;配合**共享** `Drivers/BSP/UI/ui_net_sink.c`(协议服务端,双端同一份,TCP 7003) |
| `sim/cfg_file_store_sim.c` | 通用配置文件存储后端:本地文件 `sim_config.ini`(`fopen`/`rename` 原子写),替代 MCU 端 `App/cfg/cfg_file_store_mcu.c`(FatFS);旧档 `sim_netcfg.bin` 首启自动迁移进 `sim_config.ini` |
| `sim/cfg_file_store_sim.c` | 通用配置文件存储后端:本地文件 `sim_config.ini`(`fopen`/`rename` 原子写),替代 MCU 端 `App/cfg/cfg_file_store_mcu.c`(FatFS);旧档 `sim_netcfg.bin` **已废弃**——不读、不删、不迁移(2026-10-10) |
| `lv_conf.h` | 模拟器专用 LVGL 配置。与 MCU 端 `Lib/lv_conf.h` 的关键差异见 §3.3 |
| `dbg_log.h` | MCU 版 `dbg_log.h` 依赖 `sys_clock.h`(RTC 时间戳),PC 无法编译。此 stub 把 `DBG_INFO/ERROR/DEBUG` 映射到 `printf`;因 `simulator/` 在 include 路径最前,**优先生效**覆盖 MCU 版 |
| `build_sim.bat` / `build_sim.ps1` | 一键构建入口:vswhere 探测本机 VS 大版本(16=2019 / 17=2022 / 18=2026,取已装最高)→ 映射 CMake 生成器;`build/` 缓存生成器与本机不符(换机/升级 VS)时自动删除重建(带重试,抗文件锁)。**构建入口一律走它,文档/命令中勿写死生成器版本**;`.bat` 仅负责绕过 PowerShell 执行策略并转交 `.ps1` |
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ 通用配置文件引擎(`../App/cfg_file.c`、`../Drivers/BSP/NETCFG` 的 net_cfg/net_cfg_legacy)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ 通用配置文件引擎(`../App/cfg_file.c`、`../Drivers/BSP/NETCFG` 的 net_cfg;`net_cfg_legacy` 已于 2026-10-10 删除)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
| `i1_to_png.py` | 把 `--screenshot` 产出的 I1(1bpp) 原始位图转成 PNG,便于离线比对设计稿。详见 §3.5 |
| `tests/` | 无头回归脚本(菜单 8 状态像素回归、内容页文本断言 + 黄金参考回归);`ref/` 为**本地生成物、不入库**(首次 `--update` 生成)。详见 §3.6 |
@@ -114,7 +114,7 @@ LVGL 场景(同一份 ui_menu.c)
| 输入 | 远端 keypad(`ui_port_queue_key`,网络回传键码) | SDL 键盘(`lv_sdl_keyboard_create`) |
| 主题 | 无主题(`LV_USE_THEME_* = 0`),控件用内置基础样式 | **mono 主题必须开启**(否则 1bpp 黑底黑字) |
| 日志 | `Drivers/BSP/dbg_log.h`(USART1 + RTC 时间戳) | `simulator/dbg_log.h`(printf stub) |
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无(仅编译纯软件部分的配置文件引擎 net_cfg/cfg_file,可离线验证配置加载/保存/旧档迁移) |
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无(仅编译纯软件部分的配置文件引擎 net_cfg/cfg_file,可离线验证配置加载/保存) |
| 时钟 | `lv_tick_inc` 由 ui_task 手动喂(10ms 周期) | SDL 驱动内部 `lv_tick_set_cb(SDL_GetTicks)`,**不可再手动喂** |
### 2.4 关键设计决策
@@ -123,7 +123,7 @@ LVGL 场景(同一份 ui_menu.c)
2. **CMake + MSVC**:`build_sim.ps1` 按本机 VS 版本自动选生成器(16=2019 / 17=2022 / 18=2026;不同机器版本不同,**勿写死**),`-A x64`;MSVC 下加 `/utf-8` 消除中文注释的 C4819 警告
3. **共享 UI 源码**:`ui_menu.c` / `ui_font_cn_12.c` 直接编译 `Drivers/BSP/UI/` 原件,改一处两端同时生效
4. **纯键盘导航**:不创建鼠标/触摸设备,与 MCU 端 keypad 操作语义一致(↑↓←→ / Enter / Esc)
5. **UI 为主**:不含 CH395F 网络 / NAND 存储 / ADC 等硬件业务;通用配置文件引擎(`cfg_file`)与 `net_cfg` 核心为纯软件,配置加载/保存/旧档迁移可在模拟器离线验证(`sim_config.ini` / `sim_netcfg.bin`)
5. **UI 为主**:不含 CH395F 网络 / NAND 存储 / ADC 等硬件业务;通用配置文件引擎(`cfg_file`)与 `net_cfg` 核心为纯软件,配置加载/保存可在模拟器离线验证(`sim_config.ini`;旧档 `sim_netcfg.bin` 已废弃、不参与)
---
@@ -232,15 +232,15 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化
### 3.7 配置文件自检(--cfgtest)
配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL),在当前工作目录产生 `sim_config.ini`(`sim_netcfg.bin` 参与迁移时):
配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL),在当前工作目录产生 `sim_config.ini`:
```powershell
.\build\Debug\lvgl_sim.exe --cfgtest
```
验证链:`net_cfg_load`(含旧档自动迁移)→ 第 1 次 poll 建基线 → 伪造"外部编辑"(原子写,等价 FTP 上传/文本编辑器)→ 2 次 poll 沉降(对应真机 4s 沉降)→ 断言 RAM 副本已更新为新值 → 对已存在文件做外科式保存、回读校验注释/值无损。
验证链:`net_cfg_load`(旧档迁移已于 2026-10-10 删除,只走 INI)→ 第 1 次 poll 建基线 → 伪造"外部编辑"(原子写,等价 FTP 上传/文本编辑器)→ 2 次 poll 沉降(对应真机 4s 沉降)→ 断言 RAM 副本已更新为新值 → 对已存在文件做外科式保存、回读校验注释/值无损。
空目录运行 = 验证出厂模板生成;预置有效 `sim_netcfg.bin` = 验证旧档迁移(迁移后删除);无效旧档(CRC 不符)= 保留旧文件 + 生成出厂模板。窗口模式主循环以与 MCU netTask 相同的 2s 周期调用 `cfg_file_poll()`:外部文件变更沉降后自动重载 `[network]` 段(模拟器无网络栈,变更回调为空操作,仅日志可见)。
空目录运行 = 验证出厂模板生成;超限文件(>4096 B)= 判损坏并重建出厂模板;超限文档保存 = 拒绝且不动原文件。窗口模式主循环以与 MCU netTask 相同的 2s 周期调用 `cfg_file_poll()`:外部文件变更沉降后自动重载 `[network]` 段(模拟器无网络栈,变更回调为空操作,仅日志可见)。
---
@@ -273,7 +273,7 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化
## 五、边界说明
- 模拟器**跑 UI + 配置文件引擎**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务;`cfg_file` 引擎与 `net_cfg`(纯软件部分)在模拟器编译运行,可离线验证配置加载/保存/旧档迁移
- 模拟器**跑 UI + 配置文件引擎**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务;`cfg_file` 引擎与 `net_cfg`(纯软件部分)在模拟器编译运行,可离线验证配置加载/保存
- 模拟器**含** `ui_net_sink`(Winsock 版 RemoDispBus 推流服务端,TCP 7003)——远端看图链路
可在模拟器上验证(`simulator/tests/check_remo_sink.py`);CH395F 真实网络栈仍只在 MCU 上验证
- `build/` 为生成物,可随时删除重建(已在 `.gitignore` 中忽略)
+4 -3
View File
@@ -20,7 +20,8 @@
*
* 配置轮询自检模式(无窗口、进程内闭环、退出码 0=PASS / 非0=FAIL):
* lvgl_sim.exe --cfgtest
* 验证链:net_cfg_load(含旧档迁移)→ poll 建基线 → 伪造外部编辑
* 验证链:net_cfg_load(只读 INI;旧 NCFG 档迁移已于 2026-10-10 删除)
* → poll 建基线 → 伪造外部编辑
* (原子写新文档,等价 FTP 上传/文本编辑器)→ poll 两次(4s 沉降)
* → 断言 RAM 副本已按新文件重载
*
@@ -239,7 +240,7 @@ static const uint8_t s_cfgtest_ip[4] = { 10u, 0u, 0u, 1u };
/*
* @brief 配置轮询自检(无窗口、无需 LVGL/SDL,进程内闭环)
* @note 验证链:net_cfg_load(含旧档迁移)→ poll 建基线 → 伪造外部编辑
* @note 验证链:net_cfg_load(只读 INI)→ poll 建基线 → 伪造外部编辑
* → poll 两次(沉降去抖)→ 重载并更新 RAM 副本
* @return 0 PASS,非 0 FAIL
*/
@@ -250,7 +251,7 @@ static int run_cfgtest(void)
setvbuf(stdout, NULL, _IONBF, 0);
(void)net_cfg_load(); /* 加载 / 旧档迁移(含 [network] 段注册) */
(void)net_cfg_load(); /* 加载 INI(含 [network] 段注册;旧 NCFG 档不再读取) */
cfg_file_poll(); /* 第 1 次 poll:建立变更基线 */
+3 -9
View File
@@ -2,7 +2,7 @@
* 模块名称:cfg_file_store (模拟器后端)
* 模块功能:cfg_file 存储后端——模拟器工作目录宿主文件 sim_config.ini;
* 原子写 = 临时文件 sim_config.ini.tmp + rename;mtime 变更令牌 =
* 宿主文件 mtime(秒级);旧版遗留文件 sim_netcfg.bin 供迁移读取
* 宿主文件 mtime(秒级)
* 适用平台:PC 模拟器
* 作者:王建锋
* 创建日期:2026-09-14
@@ -24,7 +24,6 @@
static const char s_path[] = "sim_config.ini";
static const char s_path_tmp[] = "sim_config.ini.tmp";
static const char s_path_legacy[] = "sim_netcfg.bin";
/* 函数定义区 */
@@ -144,14 +143,9 @@ const char *cfg_store_tmp_path(void) {
}
/*
* 函数功能:取旧版配置遗留文件路径
* 入口参数:无
* 返回值:"sim_netcfg.bin"(与 MCU 端旧格式一致,两端可互读) const char *
* 限定条件:无
* 旧版遗留档 getter(cfg_store_legacy_path)已于 2026-10-10 随旧档迁移
* net_cfg_legacy 一并删除:旧格式 sim_netcfg.bin 不再读取
*/
const char *cfg_store_legacy_path(void) {
return s_path_legacy;
}
/*
* 函数功能:分配共享核心临时缓冲(宿主 CRT 堆)
+1
View File
@@ -262,6 +262,7 @@ def main():
ok = True
# 清理手工运行残留的配置存储文件(模拟器工作目录),防"工厂默认值"像素参考被污染
# (sim_netcfg.bin 是旧档时代的产物:旧档迁移已于 2026-10-10 删除,此处只做兼容清理)
stale = Path.cwd() / "sim_netcfg.bin"
if stale.exists():
stale.unlink()
-2
View File
@@ -98,7 +98,6 @@ static int s_read_count = 0;
/* 路径常量(与生产逻辑无关,仅作为桩内部标识) */
static const char *s_cfg_path = "/config.ini";
static const char *s_tmp_path = "/config.tmp";
static const char *s_legacy_path = "/netcfg.dat";
/* ---- 堆:直接映射宿主 CRT(单测不模拟 MicroLIB 512B 堆约束) ---- */
void *cfg_store_alloc(size_t size) {
@@ -171,7 +170,6 @@ int cfg_store_remove(const char *p_path) {
const char *cfg_store_config_path(void) { return s_cfg_path; }
const char *cfg_store_tmp_path(void) { return s_tmp_path; }
const char *cfg_store_legacy_path(void) { return s_legacy_path; }
/* ---- sys_log 桩:只计数自检留痕(不落盘) ---- */
#include "sys_log.h"
+9 -9
View File
@@ -73,23 +73,23 @@ FLASH_CODE_SUB = [
("其他 (长尾)", 56638, "#E0E0E0"), # remainder
]
# ── Task stack budgets (2026-09-21 @build + FTP 128KB 实测; data/stack_report_20260921.md) ──
# ── Task stack budgets (2026-10-10 @build + 开机/空闲实测; data/stack_report_20261010.md) ──
# (name, cfg, prio, worst, peak_used, margin, margin_pct, verdict, desc)
# worst = max(静态最坏深度, 实测峰值); margin = cfg - worst
TASK_STACKS = [
("defaultTask", 4096, "Normal", 2776, 2136, 1320, 32, "PASS", "FatFS 挂载/探针 + 配置引擎 + syslog + 栈探针"),
("netTask", 4096, "Normal", 588, 588, 3508, 85, "PASS", "CH395F 轮询、消息处理"),
("ftpTask", 4096, "Normal", 1184, 1184, 2912, 71, "PASS", "lftpd FTP 服务(大缓冲已静态化)"),
("testTask", 8192, "BelowNormal", 348, 348, 7844, 95, "PASS", "测试套件(产品构建空转)"),
("uiTask", 16384, "BelowNormal", 3824, 3824, 12560, 76, "PASS", "LVGL UI 主循环"),
("timeSyncTask", 2048, "Normal", 420, 420, 1628, 79, "PASS", "TCP 时间同步"),
("defaultTask", 4096, "Normal", 2776, 2152, 1320, 32, "PASS", "FatFS 挂载/探针 + 配置引擎 + syslog + 栈探针"),
("netTask", 3840, "Normal", 1872, 920, 1968, 51, "PASS", "CH395F 轮询、消息处理"),
("ftpTask", 4096, "Normal", 1216, 740, 2880, 70, "PASS", "lftpd FTP 服务(大缓冲已静态化)"),
("testTask", 8192, "BelowNormal", 496, 496, 7696, 93, "PASS", "测试套件(产品构建不创建,取静态最坏链)"),
("uiTask", 16384, "BelowNormal", 4016, 4016, 12368, 75, "PASS", "LVGL UI 主循环"),
("timeSyncTask", 2048, "Normal", 1024, 616, 1024, 50, "PASS", "TCP 时间同步(余量正好卡在 ISR 底线)"),
]
KERNEL_STACKS = [("idle", 512), ("timer", 1024)] # 128/256 words
HEAP_TOTAL = 49152 # configTOTAL_HEAP_SIZE(位于 RW_CCM)
HEAP_FREE = 8784 # 2026-09-21 实测(含 FTP 负载)
HEAP_MIN_EVER = 4848
TOTAL_STACKS = sum(t[1] for t in TASK_STACKS) # 38,912
TOTAL_STACKS = sum(t[1] for t in TASK_STACKS) # 38,656
# ---------------------------------------------------------------------------
# SVG helpers
@@ -287,7 +287,7 @@ def gen_taskstack_svg():
svg = svg_header(W, H)
svg += f'<text x="{W/2}" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(48KB 堆,2026-09-21 已迁 CCM)</text>\n'
svg += f'<text x="{W/2}" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-09-21 @build + FTP 128KB 实测(data/stack_report_20260921.md)</text>\n'
svg += f'<text x="{W/2}" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-10 @build + 开机/空闲实测(data/stack_report_20261010.md);最坏 = max(静态最坏链, 实测峰值)</text>\n'
svg += f'<text x="{BAR_X}" y="{Y_START-10}" class="legend-text" fill="#555">浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定</text>\n'
y = Y_START