diff --git a/App/task/default_task.c b/App/task/default_task.c index 5db96784..6273d031 100644 --- a/App/task/default_task.c +++ b/App/task/default_task.c @@ -629,9 +629,17 @@ static FRESULT storage_bring_up(FATFS *fs) { * 函数说明:所有权按 docs/子系统设计/故障录波设计说明.md §5.1——环的生产者是 * adcTask,本道是唯一落盘消费者;跨任务握手只认 wave_ring_sealed()。 * 本道承担五条口径: - * ① 打开一律 FA_WRITE|FA_OPEN_APPEND:本移植 FA_OPEN_APPEND 不含 FA_WRITE - * (docs/架构设计/STM32F4-Base存储架构说明.md §10.1 陷阱一),少写位 - * f_write 恒 FR_DENIED; + * ① 打开一律 FA_WRITE|FA_CREATE_ALWAYS(**覆盖,不是追加**):段名是 100 个 + * 槽位的轮转复用(wave_store.h 的「号 0..99 轮转覆盖,写满回绕盖最老的」), + * 槽位被复用时同名文件已存在——用 FA_OPEN_APPEND 会把新段接在旧段尾巴上, + * 文件长成 5250×N,而回放页的单段读侧要求 size == 5250,于是逐件判 + * 「长度废」(reject=2),全板没有一段放得出来。2026-11-10 板级实测: + * 100 个文件 / 1868 段全部合法、零损坏,纯粹是容器尺寸与写侧追加语义相抵。 + * 本移植 FA_CREATE_ALWAYS 会 remove_chain 并清零目录项的 FileSize, + * 是真正的截断(Lib/FatFs/ff.c 的 f_open 覆盖分支); + * 注意 FA_CREATE_ALWAYS 单独用**不含 FA_WRITE**(本移植旧式标志位), + * 缺写位 f_write 恒 FR_DENIED(docs/架构设计/STM32F4-Base存储架构说明.md + * §10.1 陷阱一),两个位必须同时给; * ② 每次 f_write 比对写回字节数:本移植簇分配失败返 FR_OK+短写无错误码 * (同处陷阱二),短写一律判失败; * ③ 轮转号由本道自持(lead 转告(b)):ring.seg_idx 只是本 boot 本地计数, @@ -766,7 +774,7 @@ static void wave_store_drain(void) { s_wnext_idx) <= 0) { why = "seg_path"; } else { - fr = f_open(&s_wfil, s_wpath, FA_WRITE | FA_OPEN_APPEND); + fr = f_open(&s_wfil, s_wpath, FA_WRITE | FA_CREATE_ALWAYS); if (fr != FR_OK) { why = "f_open"; } else { diff --git a/docs/子系统设计/故障录波设计说明.md b/docs/子系统设计/故障录波设计说明.md index c0771fc4..90865357 100644 --- a/docs/子系统设计/故障录波设计说明.md +++ b/docs/子系统设计/故障录波设计说明.md @@ -158,7 +158,7 @@ flowchart TD | 陷阱 | 事实出处 | 本波处置 | |---|---|---| | **取窗竞态** | `App/algo/sample_ring.c` 的 `sample_ring_window()` 没有临界区(读 `s_head` 后逐点拷),现在只有 adcTask 同任务调用才安全 | 录波**不复用它**:adcTask 在自己的节拍里取「本拍帧」压专用环;跨任务只传递「已封存」标志,杜绝跨任务读活缓冲 | -| **FatFs 追加写** | 该移植 `FA_OPEN_APPEND` 不含 `FA_WRITE`,且 `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 打开一律 `FA_WRITE\|FA_OPEN_APPEND`;**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 | +| **FatFs 打开模式** | ① 该移植 `FA_OPEN_APPEND` 与 `FA_CREATE_ALWAYS` 都**不含** `FA_WRITE`,缺写位 `f_write` 恒返 `FR_DENIED`;② `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 录波段**一律 `FA_WRITE\|FA_CREATE_ALWAYS`(覆盖,不是追加)**——段名是 100 槽轮转复用,槽位复用时会撞上同名旧文件,追加语义会把新段接在旧段尾巴上,文件长成 5250×N 而读侧只认单段长度,全板判废(2026-11-10 收口,见 §10);日志类(一次 `f_open→写→f_close`、靠历史累积的)才用 `FA_WRITE\|FA_OPEN_APPEND`。**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 | | **只能 8.3 短名** | 出处同一张存储架构说明的短名限制:`FF_USE_LFN=0`,不得以点开头、不得长名 | 文件名 = WAVE 加两位十进制再加 .BIN,共 100 个名字轮转覆盖;目录 /wave(与 /log 平级,FTP 根下可见) | | **落盘途中环被解冻、甚至已换段**(实现期复核新增第四条,2026-10-29) | adcTask 优先级高于 defaultTask:搬段窗口里 adcTask 可能正好走到 SEALED 滞留 2 s 自弃,甚至已经封出下一段。**数据区 CRC 是对「本道实际读到的字节」算的**,半新半旧的段照样自洽——长度等式照过、两个 CRC 照对,文件骗得过任何读者,也骗过我们自己 | 写完关档后**复检环侧身份**:`wave_ring_sealed()` 仍为 1,且 `wave_ring_get_stat()` 的 `last_t0_ms` 与 `last_seq` 跟本道开始搬时那份段头一致;不符即走既有失败路——unlink 本道开过的文件 + `write_done(-1)`(此时多半回 `E_STATE`,环侧 `err` 已由自弃计过,不重复补账)+ 段号不推进(下一段同号重写覆盖)+ 单条 `[ERR] … seg drop: ring abandoned` 留痕。落点 `App/task/default_task.c`:818 到 823 行(复检与弃段处置),统一留痕打印在 843 行。**2026-11-07 板级已闭环**(#84 台架远程轮:临界占卷命中三判据逐条对上,见 §10「bench 定时自抓与占卷弃段观察闭环记录」小节) | @@ -573,6 +573,7 @@ ui_sink 17 / wave_ring 113 / wave_store 170 / wave_trig 110 全过;Keil 0 错 `default_task.c` 落盘道;11-06 的 STOR 全删全建掩盖了生长面)——拼接件对回放页单段读侧 (`size == total`)逐件判长度废,1866 段逐段过 `wave_read` 校验**全部合法**(零损坏,纯口径 相抵)。该面是否收口(写者截断 vs 读侧多段感知)是新的产品决定,本轮只如实记账,不并票。 +→ **2026-11-10 已裁定并收口为写者截断**,见下「段文件跨开机追加缺陷收口记录」。 板级验收(2026-11-07 台架远程轮,烧录经口头「烧」确认;证据 `docs/测试说明/测试数据/wave_fix_20261107/`):**F1 五条 / F2 五条全过**——F1-1 页关投放 @@ -588,6 +589,42 @@ ui_sink 17 / wave_ring 113 / wave_store 170 / wave_trig 110 全过;Keil 0 错 缺失即其重试成功不打日志的一面;间歇边界(是否断电重连可消)未在本轮验证。详见证据目录 README 的 Before/After 表与串口原文。 +### 段文件跨开机追加缺陷收口记录(2026-11-10,写者改截断) + +**问题**:手动录波后进回放页,一个波形都看不到,恒显「该段不可读」。根因不在数据,在文件形态: +段名是 100 个槽位轮转复用(`App/algo/wave_store.h` 的「号 0..99 轮转覆盖,写满回绕盖最老的」), +而写者用 `FA_OPEN_APPEND` 打开——槽位被复用时撞上同名旧文件,新段接在旧段尾巴上,文件长成 +5250×N。读侧(`App/task/ui_task.c` 的 `ui_task_wave_load_head`)按单段口径判 `size == total`, +于是逐件落进 `UI_WAVE_REJ_LEN`(reject=2),全板没有一段放得出来。 + +**板级取证**(FTP 直读 `192.168.31.100`,admin/admin;100 件全部归档到 +`ci-artifacts/wave_append_backup_20261110/`):`/wave` 共 100 个 `WAVE00.BIN`…`WAVE99.BIN`、 +9,807,000 B = 1868 段;尺寸 10,500~147,000 B **全部是 5250 的整数倍**,**恰好等于 5250 的文件数为 0**; +用 `test/host/wave_read.py` 的 `parse_segment()` 逐段校验 **1868 段全过、零损坏**。也就是说数据 +一直是好的,只是容器尺寸与写者语义相抵。 + +**修复(用户裁定取「写者截断」,否掉「读侧多段感知」——后者改动面大,还要同步 PC 侧 +`wave_read.py` 与 golden 帧)**:`App/task/default_task.c` 落盘道打开模式 +`FA_WRITE|FA_OPEN_APPEND` → **`FA_WRITE|FA_CREATE_ALWAYS`**。本移植 `Lib/FatFs/ff.c` 的 `f_open` +覆盖分支会 `remove_chain` 并把目录项 `DIR_FileSize` 清零,是真截断;⚠ `FA_CREATE_ALWAYS` +单独用**不含 `FA_WRITE`**(旧式标志位),缺写位 `f_write` 恒 `FR_DENIED`,两个位必须同时给。 + +**验收证据**(本工作树,串口 COM4 + TCP 7003): +- Keil `0 Error(s), 0 Warning(s)`;烧录 `Programming Complete` / `Application started` / `RC: 0` / `FLASH: OK`, + 固件戳 `Nov 10 2026 22:03:04` 与本次构建一致;启动锚点全过(`App init OK` / `Network Init` / + `net init: OK` / `protect task start: cycle=20ms elm=9` / 各 `[STACK]`),唯一 `[ERR]` 为既有的 + `[ERR][I104] i104: bind port 2404 FAIL, retry in loop`(既有面,非本次回归)。 +- **截断面**:清空 `/wave` 后触发一次手动录波,新段 `WAVE01.BIN` 尺寸**恰好 5250 B**; + 另在清理前对已有 28 段的 `WAVE00.BIN`(147,000 B)录一次,尺寸当场变成 **5250 B**、 + 全目录总字节 9,807,000 → 9,665,250(差值 141,750 = 147,000 − 5,250),覆盖写落地。 +- **画面面**:回放页从 `wave_play_bad` 态(ink=408,与 `simulator/tests/ref/shot_wave_play_bad.bin` + **逐比特相同**)转为正常态(ink=1087)——页眉 24/24 行、页脚 20/20 行与黄金参考 + `shot_wave_play.bin` **逐字节一致**,只有波形主体不同(真机信号 vs 模拟器注入,预期内); + 连按 12 次换段,ink 在 1065~1088 间变化,段间波形确实不同。 + +**未改动**:日志七本与 `soe.log` 仍用 `FA_WRITE|FA_OPEN_APPEND`(它们靠历史累积,语义正确); +读侧逐件判 `size == total` 的口径**保持不变**——现在写者保证单段长度,读侧不需要多段感知。 + --- ## 11. 与 `docs/算法/采集与算法层设计说明.md §13` 的口径变更(三处) diff --git a/docs/测试说明/测试数据/wave_fix_20261107/README.md b/docs/测试说明/测试数据/wave_fix_20261107/README.md index 9674ff9d..abad0a38 100644 --- a/docs/测试说明/测试数据/wave_fix_20261107/README.md +++ b/docs/测试说明/测试数据/wave_fix_20261107/README.md @@ -11,6 +11,10 @@ PNG 为 4× 放大目检件(非判据)。 `test/host/wave_read.py` 校验**全部合法、零损坏**)——写者 `FA_OPEN_APPEND` 跨开机累积 属既有设计,回放页单段读侧(`size == total`)按口径逐件判「长度废」:全板无可放段。 全量原件 RETR 备份 + sha256 台账在轮末**逐字节还原**(见「收工」节)。 + ⚠ 上面这段描述的是**当时**(2026-11-07)的盘面;「追加属既有设计」的口径已于 2026-11-10 + 推翻并收口为写者截断(`App/task/default_task.c` 改 `FA_WRITE|FA_CREATE_ALWAYS`), + 收口记录见 `docs/子系统设计/故障录波设计说明.md` §10「段文件跨开机追加缺陷收口记录」。 + 本目录是带日期的取证归档,正文按当时口径冻结、不回改。 - 串口唯一枚举 CH340 = **COM4**(仓库 `.serialtest/config.json` 钉 COM3 已过时,本仓文件 未动,用 `$env:DSH_SERIAL_CONFIG` 覆盖);板端 FTP `admin/admin`。 - 到场固件 = 修复前构建(当日 01:00 已在跑,串口捕获窗口起点晚于该启动,构建戳不可证; diff --git a/scripts/size_attestation.json b/scripts/size_attestation.json index 430f8efd..e112533d 100644 --- a/scripts/size_attestation.json +++ b/scripts/size_attestation.json @@ -1,15 +1,15 @@ { - "built_at": "2026-11-10T11:42:28+08:00", - "fingerprint": "bd97996ef0586e185e13beca01d36f43fdbc118c69c0833166065752aacae96c", + "built_at": "2026-11-10T22:31:07+08:00", + "fingerprint": "5080b00d80af5604476422c1afa383cb09e782e6bb76aa2abf9e96a77d5c60fd", "fingerprint_files": 1479, "fingerprint_source": "git-index", "gated_regions": [ "RW_IRAM1", "RW_CCM" ], - "git_head": "ae09c5737a0bc56cddc469fc2ca0afe78e6cbd74", + "git_head": "7b05d7bfd40fafa514e166d3fda5270f2b040c2b", "map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map", - "map_mtime": 1794282125, + "map_mtime": 1794321057, "pct": { "ER_IROM1": 27.31, "RW_CCM": 87.89,