From 0f7b5a87fb701e676fe747b96f0a2d4adeed26f8 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 26 Oct 2026 12:28:14 +0800 Subject: [PATCH] =?UTF-8?q?chore(=E7=BC=96=E5=8F=B7):=20=E5=AD=98=E9=87=8F?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E5=85=A8=E9=87=8F=E8=BD=AC=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E7=9F=AD=E8=AF=AD=EF=BC=8C=E9=97=A8=E7=A6=81=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E9=9D=A2=E4=B8=8E=E8=AF=AD=E6=96=99=E5=90=8C=E6=89=B9=E9=87=8D?= =?UTF-8?q?=E6=89=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。 --- .cubemx_survival_baseline.json | 2 +- .../serial-boot-test/scripts/run_boot_test.py | 2 +- .../serial-boot-test/scripts/verify_log.py | 6 +- .dsh/skills/task-worktree/SKILL.md | 2 +- .dsh/skills/ui-verify/SKILL.md | 2 +- .gitea/workflows/ci.yml | 24 +- .pre-commit-config.yaml | 48 +- AGENTS.md | 12 +- App/algo/README.md | 6 +- App/algo/adc_cal.c | 4 +- App/algo/adc_cal.h | 2 +- App/algo/adc_health.h | 2 +- App/algo/adc_input.c | 4 +- App/algo/adc_input.h | 4 +- App/algo/freq.h | 2 +- App/algo/phasor.h | 2 +- App/algo/prot_elm.h | 2 +- App/algo/run_par.h | 4 +- App/algo/sample_ring.h | 2 +- App/app/app_cfg.h | 4 +- App/app/app_main.c | 12 +- App/app/ui_data_feed.c | 2 +- App/cfg/cfg_file.c | 30 +- App/cfg/cfg_file.h | 14 +- App/cfg/cfg_file_store_mcu.c | 14 +- App/log/console_log.c | 20 +- App/log/soe_rec.h | 2 +- App/log/sys_log.h | 2 +- App/log/sys_log_mcu.c | 38 +- App/protocol/iec104/iec104_asdu.h | 2 +- App/protocol/iec104/iec104_link.h | 2 +- App/protocol/iec104/iec104_pts.c | 6 +- App/protocol/iec104/iec104_pts.h | 6 +- App/sync/time_sync.c | 14 +- App/task/adc_task.c | 4 +- App/task/default_task.c | 24 +- App/task/iec104_serv.c | 8 +- App/task/net_task.c | 18 +- App/task/net_task.h | 2 +- App/task/protect_task.c | 2 +- App/task/rs485_task.c | 2 +- App/task/ui_task.c | 4 +- App/util/sha256.c | 2 +- App/util/sha256.h | 2 +- App/util/task_watchdog.c | 52 +- App/util/task_watchdog.h | 20 +- Drivers/BSP/CH395F/ch395f.c | 24 +- Drivers/BSP/CH395F/ch395f.h | 4 +- Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c | 26 +- Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h | 20 +- Drivers/BSP/GD5F2GQ5UE/nand_ftl.c | 32 +- Drivers/BSP/GD5F2GQ5UE/nand_ftl.h | 6 +- Drivers/BSP/NET/README.md | 2 +- Drivers/BSP/NET/lftpd/lftpd.c | 10 +- Drivers/BSP/NET/lftpd/lftpd.h | 2 +- Drivers/BSP/NET/lftpd/lftpd_auth.c | 2 +- Drivers/BSP/NET/lftpd/lftpd_auth.h | 2 +- Drivers/BSP/NET/lftpd/lftpd_inet.c | 4 +- Drivers/BSP/NET/net_events.c | 26 +- Drivers/BSP/NET/net_listener.c | 8 +- Drivers/BSP/NET/net_listener.h | 2 +- Drivers/BSP/NET/net_pending.c | 12 +- Drivers/BSP/NET/net_pending.h | 2 +- Drivers/BSP/NET/net_select.c | 4 +- Drivers/BSP/NET/net_socket.c | 66 +- Drivers/BSP/NET/net_socket.h | 22 +- Drivers/BSP/NET/net_types.h | 6 +- Drivers/BSP/NETCFG/ftp_cfg.c | 2 +- Drivers/BSP/NETCFG/ftp_cfg.h | 4 +- Drivers/BSP/NETCFG/net_cfg.c | 4 +- Drivers/BSP/NETCFG/net_cfg.h | 2 +- Drivers/BSP/RS485/rs485.c | 2 +- Drivers/BSP/SD2506/sd2506.c | 4 +- Drivers/BSP/SD2506/sd2506.h | 2 +- Drivers/BSP/TPAFE5160/README.md | 6 +- Drivers/BSP/TPAFE5160/tpafe5160.c | 28 +- Drivers/BSP/TPAFE5160/tpafe5160.h | 16 +- Drivers/BSP/UI/ui_content_page.c | 4 +- Drivers/BSP/UI/ui_content_page.h | 2 +- Drivers/BSP/UI/ui_menu_tab.h | 2 +- Drivers/BSP/UI/ui_net_sink.c | 34 +- Drivers/BSP/UI/ui_net_sink.h | 2 +- Drivers/BSP/UI/ui_page_runlog.c | 4 +- Drivers/BSP/UI/ui_page_selftest.c | 4 +- Drivers/BSP/UI/ui_style.c | 2 +- Drivers/BSP/UI/ui_style.h | 2 +- Drivers/BSP/UI/ui_tokens.h | 2 +- Drivers/BSP/UI/ui_w_records.c | 2 +- Drivers/BSP/UI/ui_w_records.h | 2 +- README.md | 4 +- Src/freertos.c | 10 +- Src/main.c | 10 +- docs/CI/README.md | 2 +- docs/CI/远程CI完整指南.md | 10 +- docs/README.md | 30 +- docs/UI设计/UI_自动化测试流程.md | 2 +- docs/UI设计/UI_设计架构.md | 8 +- docs/UI设计/页面扩充详细方案.md | 4 +- docs/UI设计/页面补齐规划.md | 2 +- docs/images/网络不稳根因总览.svg | 8 +- docs/images/采样有效性校验数据流.svg | 2 +- docs/产品需求/产品需求设计文档.md | 14 +- docs/代码规范/pre-commit使用说明.md | 20 +- docs/任务规划/README.md | 203 +- docs/任务规划/UI补齐.md | 189 +- docs/任务规划/基线与待确认.md | 15 - docs/任务规划/并行分道方案.md | 238 --- docs/任务规划/排期详述.md | 35 - docs/任务规划/数据可信.md | 29 +- docs/任务规划/暂缓-RS485.md | 12 +- docs/任务规划/测试与架构专项.md | 14 +- docs/任务规划/登记簿.md | 90 +- docs/任务规划/采集保护收尾.md | 36 - docs/内存管理/RAM使用分析与优化方案.md | 44 +- docs/内存管理/RAM预算台账.md | 18 +- docs/内存管理/内存内容分配策略.md | 6 +- docs/内存管理/存储栈RAM优化调研.md | 54 +- docs/子系统设计/ADC采样节拍设计说明.md | 2 +- docs/子系统设计/IEC104上行设计说明.md | 10 +- docs/子系统设计/控制台单写者日志设计说明.md | 60 +- docs/子系统设计/看门狗设计说明.md | 30 +- docs/子系统设计/网络配置说明.md | 4 +- docs/子系统设计/运行与自检记录设计说明.md | 36 +- docs/子系统设计/配置文件设计说明.md | 14 +- docs/文档规范.md | 6 +- docs/架构设计/STM32F4-Base存储架构说明.md | 16 +- docs/架构设计/项目启动顺序管理.md | 2 +- docs/架构设计/项目架构总览.md | 2 +- docs/测试说明/ADC输入层单元测试说明.md | 20 +- docs/测试说明/CH395F_Test_Guide.md | 182 +- docs/测试说明/GD5F2GQ5UE_Test_Guide.md | 8 +- docs/测试说明/串口助手设计说明.md | 14 +- docs/测试说明/存储维护与容量恢复.md | 2 +- docs/测试说明/实机测试实施指南.md | 73 +- docs/测试说明/时间同步单元测试说明.md | 18 +- docs/测试说明/模拟器差距清单.md | 56 +- docs/测试说明/测试开发标准流程.md | 54 +- docs/测试说明/测试文档模板规范.md | 37 +- docs/测试说明/相量层单元测试说明.md | 4 +- docs/测试说明/看门狗单元测试说明.md | 2 +- docs/测试说明/链路延迟测试说明.md | 66 +- docs/环境与部署/并行任务工作树方案.md | 6 +- docs/环境与部署/项目部署环境要求.md | 4 +- docs/算法/继电保护功能复现规划.md | 48 +- docs/算法/采样有效性校验设计说明.md | 4 +- docs/算法/采集与算法层设计说明.md | 12 +- docs/编号规范.md | 702 ++----- docs/网络/BSD_Socket_API_使用指南.md | 16 +- docs/网络/BSD_Socket_API_函数调用关系.md | 14 +- docs/网络/Net层阻塞模型架构设计.md | 2 +- docs/网络/网络自愈架构设计.md | 30 +- docs/门禁说明/bsp-err.md | 2 +- docs/门禁说明/cubemx-survival.md | 2 +- docs/门禁说明/host-unit-tests.md | 8 +- docs/门禁说明/id-check.md | 73 +- docs/门禁说明/layer-deps.md | 2 +- docs/门禁说明/mem-budget.md | 2 +- docs/门禁说明/registration-check.md | 2 +- docs/问题记录/CH395F_Trap_Records.md | 6 +- docs/问题记录/GD5F2GQ5UE_Trap_Records.md | 14 +- docs/问题记录/采集与数据处理短板评测.md | 8 +- scripts/README.md | 14 +- scripts/check_bsp_err.py | 2 +- scripts/check_cubemx_survival.py | 4 +- scripts/check_doc_links.py | 2 +- scripts/check_font_charset.py | 6 +- scripts/check_ids.py | 48 +- scripts/check_layer_deps.py | 2 +- scripts/check_lv_conf_sync.py | 2 +- scripts/check_mem_numbers.py | 4 +- scripts/check_registration.py | 22 +- scripts/check_size_gate.py | 8 +- scripts/check_test_coverage.py | 2 +- scripts/check_test_suite.py | 4 +- scripts/check_test_traceability.py | 11 +- scripts/check_ui_golden.py | 2 +- scripts/ci_local.py | 20 +- scripts/gen_task_table.py | 2 +- scripts/id_aliases.json | 152 ++ scripts/id_known_backlog.json | 1846 +---------------- scripts/id_registry.json | 125 +- scripts/ram_baseline.py | 14 +- scripts/ram_baseline.tsv | 1 + scripts/rename_ids.py | 8 +- scripts/size_attestation.json | 8 +- scripts/v5_log_gap_report.py | 2 +- simulator/lv_conf.h | 4 +- simulator/main.c | 6 +- simulator/sim/cfg_file_store_sim.c | 2 +- simulator/sim/net_socket_sim.c | 2 +- simulator/tests/check_content_pages.py | 4 +- simulator/tests/check_menu_rows.py | 2 +- test/README.md | 2 +- test/board/README.md | 2 +- test/board/ch395f_reuse_bench.c | 8 +- test/board/ch395f_reuse_bench.h | 2 +- test/board/ch395f_test.h | 2 +- test/board/ch395f_test_task.c | 74 +- test/board/gd5f_test_task.c | 12 +- test/board/net_latency_test.c | 4 +- test/board/net_test_task.c | 2 +- test/board/storage_test_task.c | 22 +- test/board/test_common.h | 2 +- test/board/test_config.h | 2 +- test/host/README.md | 18 +- test/host/adc_input/adc_input_test.c | 84 +- test/host/adc_input/stub/tpafe5160.h | 2 +- test/host/cfg_file/cfg_file_test.c | 18 +- .../cfg_file_store_mcu_test.c | 14 +- test/host/cfg_file_store_mcu/run_unit_test.py | 4 +- test/host/ch395f_reuse_host.py | 4 +- test/host/ch395f_socket_test.py | 34 +- test/host/common/README.md | 2 +- test/host/common/fatfs_fake.c | 2 +- test/host/common/fatfs_fake.h | 2 +- test/host/common/unit_test_harness.py | 4 +- test/host/console_log/console_log_test.c | 4 +- test/host/console_log/run_unit_test.py | 2 +- test/host/e02_probe.py | 4 +- test/host/ftp_abort_probe.py | 2 +- test/host/ftp_auth_bench.py | 4 +- test/host/ftp_cfg/ftp_cfg_test.c | 2 +- test/host/ftp_cfg/run_unit_test.py | 2 +- test/host/ftp_regression.py | 2 +- test/host/iec104/iec104_test.c | 196 +- test/host/iec104_master.py | 6 +- test/host/latency_peer.py | 14 +- test/host/n30_repro.py | 2 +- test/host/nand_port/nand_port_test.c | 54 +- test/host/nand_port/run_unit_test.py | 8 +- test/host/net_cfg/net_cfg_test.c | 2 +- test/host/net_select/net_select_test.c | 2 +- test/host/net_socket/net_socket_test.c | 10 +- test/host/net_socket/ns_s3_msg.c | 6 +- test/host/net_socket/ns_s4_state.c | 8 +- test/host/net_socket/ns_s5_io.c | 10 +- test/host/net_socket/run_unit_test.py | 4 +- test/host/sha256/run_unit_test.py | 2 +- test/host/sha256/sha256_test.c | 2 +- test/host/soe/soe_test.c | 64 +- test/host/soe_store/soe_store_test.c | 2 +- test/host/sys_clock/run_unit_test.py | 2 +- test/host/sys_log_mcu/run_unit_test.py | 4 +- test/host/sys_log_mcu/sys_log_mcu_test.c | 14 +- test/host/time_sync/run_unit_test.py | 4 +- test/host/time_sync/time_sync_test.c | 8 +- test/host/watchdog/run_unit_test.py | 4 +- test/run_all_suites.py | 22 +- tools/arch_diagram/gen_adc_health_flow_svg.py | 2 +- tools/arch_diagram/gen_memory_svg.py | 2 +- tools/arch_diagram/gen_net_instability_svg.py | 4 +- tools/menu_gen/gen_menu.py | 2 +- ui_menu_tree.txt | 2 +- 253 files changed, 2234 insertions(+), 4541 deletions(-) delete mode 100644 docs/任务规划/基线与待确认.md delete mode 100644 docs/任务规划/并行分道方案.md delete mode 100644 docs/任务规划/排期详述.md delete mode 100644 docs/任务规划/采集保护收尾.md diff --git a/.cubemx_survival_baseline.json b/.cubemx_survival_baseline.json index f6aa57e7..1e577ebe 100644 --- a/.cubemx_survival_baseline.json +++ b/.cubemx_survival_baseline.json @@ -13,7 +13,7 @@ "stack-overflow-hook-impl": "a006edbd30d2c4d7", "malloc-failed-hook-impl": "fcd7250e5cd6403b", "stack-overflow-hook-helper": "cacd9fd028be9b5b", - "watchdog-module": "56bf2739f56a44e6", + "watchdog-module": "2bb051d572be4149", "net-msgq-adopt": "a0f913e3f34c8e9e", "reset-cause-latch": "983927d587cb9f48", "iwdg-hal-driver": "4b902875a84554e9", diff --git a/.dsh/skills/serial-boot-test/scripts/run_boot_test.py b/.dsh/skills/serial-boot-test/scripts/run_boot_test.py index 7f269cc2..f53d793e 100644 --- a/.dsh/skills/serial-boot-test/scripts/run_boot_test.py +++ b/.dsh/skills/serial-boot-test/scripts/run_boot_test.py @@ -355,7 +355,7 @@ def main(argv=None): help="forbidden patterns, ';' separated (config 'forbidden')") ap.add_argument("--require-suite-summary", default=None, help="suite names that MUST appear as '=== : N/M PASSED (failed=0) ===' " - "with N>0, ';' separated (T-2). Empty = off") + "with N>0, ';' separated. Empty = off") ap.add_argument("--require-selftest-pass", action="store_true", default=None, help="jointly judge /log/selftest.log (S7): missing/empty = PASS, " "any 64B record = FAIL") diff --git a/.dsh/skills/serial-boot-test/scripts/verify_log.py b/.dsh/skills/serial-boot-test/scripts/verify_log.py index 6f613a2a..c0759ad6 100644 --- a/.dsh/skills/serial-boot-test/scripts/verify_log.py +++ b/.dsh/skills/serial-boot-test/scripts/verify_log.py @@ -37,7 +37,7 @@ - 读取编码:先按 UTF-8,解码失败回退系统 locale 编码(旧版 Get-Content 行为) - 控制台输出重配置为 UTF-8(errors=replace):任何 Windows 控制台代码页 都只影响终端显示,不会导致本脚本崩溃 - - 汇总行(2026-10-01 加,T-2):纯 Required 子串匹配有个盲区——套件启动横幅 + - 汇总行(2026-10-01 加):纯 Required 子串匹配有个盲区——套件启动横幅 命中即判 PASS,**套件是否跑到汇总行、断言数是否为 0 完全不可见**(套件中途 挂死/复位、或 Phase 宏全关时会被误判为通过)。`--require-suite-summary` 要求日志中出现 `=== <名称>: N/M PASSED (failed=0) ===` 且 **N > 0**。 @@ -311,7 +311,7 @@ def verify(log_path, required=REQUIRED_DEFAULT, forbidden=FORBIDDEN_DEFAULT, "(run_boot_test.py adds it; --raw-log turns it off)") res["timing"] = timing - # 套件汇总行解析(T-2):抓"没跑到汇总行 / 断言数为 0"的盲区 + # 套件汇总行解析:抓"没跑到汇总行 / 断言数为 0"的盲区 bad_summary = [] if summary_list: parsed = {} @@ -407,7 +407,7 @@ def main(argv=None): help="forbidden patterns, ';' separated (case-insensitive)") ap.add_argument("--require-suite-summary", default="", help="suite names that MUST appear as '=== : N/M PASSED (failed=0) ===' " - "with N>0, ';' separated (T-2). Empty = off") + "with N>0, ';' separated. Empty = off") ap.add_argument("--show-tail-lines", type=int, default=40, help="show last N log lines (0 = off)") ap.add_argument("--require-selftest-pass", action="store_true", diff --git a/.dsh/skills/task-worktree/SKILL.md b/.dsh/skills/task-worktree/SKILL.md index 9eb3489c..310902a1 100644 --- a/.dsh/skills/task-worktree/SKILL.md +++ b/.dsh/skills/task-worktree/SKILL.md @@ -81,7 +81,7 @@ whenToUse: 要同时推进多个任务、担心同分支同文档互相冲突; - **`done` 前主工作树必须干净**:否则未提交改动会被卷进合并。 - **⚠ `done` 的门禁跑在 `@build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测): `done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `@build`」,而 - `mem-budget`(N-27)/ `mem-numbers` 是拿**文档快照 vs `.map`** 比对 —— 门禁跑时 + `mem-budget`(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/ `mem-numbers` 是拿**文档快照 vs `.map`** 比对 —— 门禁跑时 `.map` 还是**主工作树合并前**的旧产物,与刚合并进来的新台账**必然不一致** → 误报 `[FAIL] 台账受管区与 .map 不一致`。`@build` 跑完后 `.map` 才是新的。 **处置**:门禁失败且只差这两项时,按提示在主工作树跑 diff --git a/.dsh/skills/ui-verify/SKILL.md b/.dsh/skills/ui-verify/SKILL.md index a053509c..7f3cce02 100644 --- a/.dsh/skills/ui-verify/SKILL.md +++ b/.dsh/skills/ui-verify/SKILL.md @@ -41,7 +41,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警 | `MENU_POOL_BASELINE` | check_menu_rows.py | 5368 B | 菜单根框建完的 LVGL 池 used(x64 模拟器;2026-10-11 随 LVGL 控件裁剪由 4784 上移) | | `POOL_AFTER_ROUNDTRIP` | check_content_pages.py | 5448 B | 开页-关页往返后 = 基线 + 80 B 一次性残留 | -⚠ 两个常量量的是池的 **`used`**,与 `LV_MEM_SIZE` 总大小**无关** → 改池大小(如 2026-10-15 R-9 的 40→16 KB)**不该动这两个数**;动了就是页面结构变化或泄漏。 +⚠ 两个常量量的是池的 **`used`**,与 `LV_MEM_SIZE` 总大小**无关** → 改池大小(如 2026-10-15 缩池 40→16 KB)**不该动这两个数**;动了就是页面结构变化或泄漏。 **禁止为过门禁盲目改常量**——无法解释的漂移 = 内存回归 bug。 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 4d6f0085..f5b53ba0 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -14,7 +14,7 @@ # 构建产物、已 gitignore),无 `@build` 即无 `.map`。故本 workflow 在**检测不到 # .map 时自动跳过它们**(见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner 照常跑。 # -# 🔴 体积门禁:盲区已关闭为**强制路径**(A-5 残余,2026-10-25;原 S14 只做到"声明") +# 🔴 体积门禁:盲区已关闭为**强制路径**(2026-10-25 起;原先只做到"声明") # 跳过钩子只解释了"runner 没有 Keil",没解决**"没跑 = 通过"**:钩子在无 .map 时 # 自适应 SKIP 并返回 0,于是 CI 全绿仍可能被读成"体积也守住了"。现由 L0 作业**最后 # 一步** `python scripts/check_size_gate.py --ci` 在**不需要 Keil** 的前提下补两条判据: @@ -28,7 +28,7 @@ # 模块 docstring,此处不重复):绝对数字由本地真实构建产生(runner 无 Keil 许可证)、 # 编译器选项(uvprojx,UV4 每次改写且不入库)不在指纹范围内。 # -# 产物存档(S3,2026-10-02):两个 job 把关键输出 `Tee-Object` 到 `ci-artifacts/*.log`, +# 产物存档与结果可见性(2026-10-02 起):两个 job 把关键输出 `Tee-Object` 到 `ci-artifacts/*.log`, # 并写 $GITHUB_STEP_SUMMARY(Gitea UI 直接可见)+ `::notice::` 汇总行。 # **刻意不用 `actions/upload-artifact`**——内网可能拉不到外层 action(见 docs/CI/远程CI完整指南.md §5.3); # 零依赖方案下日志留在 runner 工作区,UI 通过 step-summary 即可排障。 @@ -247,12 +247,12 @@ jobs: # mem-numbers / mem-budget fact source is the Keil build artifact .map # (gitignored). No .map => those hooks cannot run => skip them EXPLICITLY. # - # A-5 (2026-10-25): two honesty fixes here. + # 2026-10-25: two honesty fixes here. # 1) mem-budget used NOT to be listed, yet it self-skips inside the script # when .map is missing (exit 0) -- so on a Keil-less runner it reported # "Passed" while checking nothing. A gate that fakes green is worse than # a gate that is visibly skipped: skip it here so the report says Skipped. - # 2) "skipped" alone is still the A-5 blind spot, so the compensation is a + # 2) "skipped" alone is still the blind spot, so the compensation is a # real step, not only a warning: see the "size gate" step at the end of # this job (scripts/check_size_gate.py --ci, no Keil needed: attestation # parity + 92% re-check on the recorded numbers). Boundary and rationale @@ -266,7 +266,7 @@ jobs: Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 } else { Write-Host "::warning::no .map found (no Keil build) -- SKIPPING mem-numbers and mem-budget, so absolute Flash/RAM numbers are NOT recomputed here." - Write-Host "::warning::that gap is no longer silent: the final step of this job runs scripts/check_size_gate.py --ci (A-5 forced path: attestation parity + 92% budget re-check). Locally: scripts/ci_local.py step L0c." + Write-Host "::warning::that gap is no longer silent: the final step of this job runs scripts/check_size_gate.py --ci (forced path: attestation parity + 92% budget re-check). Locally: scripts/ci_local.py step L0c." "value=mem-numbers,mem-budget" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii "sizegate=off" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii "> :warning: Volume gates (mem-numbers, mem-budget): **NOT RUN** -- no `.map` (no Keil build); compensated by the size-gate step below." | @@ -315,7 +315,7 @@ jobs: if: always() shell: powershell run: | - # S3: zero-dependency artifact archival. We do NOT use + # Result visibility: zero-dependency artifact archival. We do NOT use # actions/upload-artifact (inner networks may not fetch outer actions); # instead we keep the log on the runner AND surface its path + tail in # the step summary, so the UI is self-sufficient for triage. @@ -328,10 +328,10 @@ jobs: Write-Host "::notice::L0 artifacts kept on runner at: ci-artifacts\l0-pre-commit.log" # ------------------------------------------------------------ - # A-5 残余的强制体积门禁(2026-10-25):不需要 Keil,也不需要 .map + # 强制体积门禁(2026-10-25):不需要 Keil,也不需要 .map # 放在 L0 作业**最后**且 if: always()——L0 失败时这条结论同样要看得到, # 而它自己失败会让整个作业变红(这正是"强制"的含义)。 - - name: 体积门禁(A-5 强制路径:同码凭证 + 92% 红线,无需 Keil) + - name: 体积门禁(强制路径:同码凭证 + 92% 红线,无需 Keil) if: always() shell: powershell env: @@ -342,8 +342,8 @@ jobs: # # WHY THIS STEP EXISTS: mem-numbers / mem-budget read the Keil .map and # self-skip (exit 0) without it, so "not measured" used to look like - # "passed" -- that is exactly the A-5 blind spot ("CI all green does not - # mean the size budget held"). Declaring it (S14) did not close it. + # "passed" -- that is exactly the blind spot ("CI all green does not + # mean the size budget held"). Merely declaring it did not close it. # # This step enforces a check that needs NO Keil and NO .map: # * parity -- scripts/size_attestation.json (a committed attestation @@ -383,7 +383,7 @@ jobs: $ErrorActionPreference = $saved Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" -Encoding utf8 - Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### Volume gate (A-5 forced path)" -Encoding utf8 + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### Volume gate (forced path)" -Encoding utf8 Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "size gate: rc=$rc mode=$label (attestation parity + 92% re-check; no Keil)" -Encoding utf8 Write-Host "::notice::size gate: rc=$rc mode=$label (log: $log)" if ($rc -ne 0) { @@ -534,7 +534,7 @@ jobs: if: always() shell: powershell run: | - # S3: same zero-dependency archival as L0. + # Result visibility: same zero-dependency archival as L0. if (Test-Path 'ci-artifacts') { $logs = Get-ChildItem -Path 'ci-artifacts' -Filter '*.log' -ErrorAction SilentlyContinue foreach ($f in $logs) { diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 789e372c..140877b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -160,11 +160,11 @@ repos: Inc/ ) - # 5) 文档一致性门禁(D-7) + # 5) 文档一致性门禁 # 存在性:md 互链 / 反引号路径是否指向真实文件(防迁移/删除后残留死链) # 章节:`§N.M` 必须能在目标文档的编号标题里找到(--sections-strict:自引用失效也拦) - id: doc-links - name: 文档链接存在性 (D-3/D-7) + name: 文档链接存在性 entry: python scripts/check_doc_links.py --sections-strict language: system files: \.md$ @@ -177,23 +177,23 @@ repos: MDK-ARM/RTE/| docs/芯片手册/ ) - # 数值:README/AGENTS 任务表须与 Src/freertos.c 一致(D-2/D-7) + # 数值:README/AGENTS 任务表须与 Src/freertos.c 一致 - id: task-table - name: FreeRTOS 任务表一致性 (D-2/D-7) + name: FreeRTOS 任务表一致性 entry: python scripts/gen_task_table.py --check language: system files: ^(README\.md|AGENTS\.md|Src/freertos\.c)$ pass_filenames: false - # 体积:内存/Flash 数字须与最新 .map 一致(D-7 第三档) + # 体积:内存/Flash 数字须与最新 .map 一致(体积档) # 只在内存文档或代码变动时触发;.map 未生成时脚本自身给出明确提示 - id: mem-numbers - name: 内存/Flash 数字一致性 (D-7 体积档) + name: 内存/Flash 数字一致性(体积档) entry: python scripts/check_mem_numbers.py --scan language: system files: ^(docs/内存管理/.*\.md|scripts/check_mem_numbers\.py|App/.*\.c|App/.*\.h|Src/.*\.c|Inc/.*\.h)$ pass_filenames: false - # 5d) 两份 lv_conf.h 特性子集一致性(2026-10-11 加,随 F-1/F-2/F-3 落地) + # 5d) 两份 lv_conf.h 特性子集一致性(2026-10-11 加) # 背景:`Lib/lv_conf.h`(MCU)与 `simulator/lv_conf.h`(PC)是两份独立文件, # 此前长期漂移——模拟器早已 I1-only,MCU 端却让 11 个 `LV_DRAW_SW_SUPPORT_*`、 # 5 个未用控件类、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX/GRID` 靠 lv_conf_internal @@ -328,7 +328,7 @@ repos: pass_filenames: false # 7g) 时间同步(time_sync)主机侧单元测试(2026-10-10 加) - # 背景:time_sync.c 是 TCP 时间同步协议状态机,也是 N-17 号看门狗风险的**全部 + # 背景:time_sync.c 是 TCP 时间同步协议状态机,也是探针窗口心跳缺口那项看门狗风险的**全部 # 逻辑贡献者**——"listen 重建 -> 800ms~1s 心跳间隔"由本模块的分片延时/退避/ # 结算等待组成,板端只能看到"是否复位"这一结果,看不到各段延时是否如设计。本钩子 # 用 MSVC 离线编译真实源文件(#include 纳入)+ 可脚本化的网络/事件桩(含 longjmp @@ -440,7 +440,7 @@ repos: files: ^(App/algo/freq\.(c|h)|App/algo/phasor\.(c|h)|App/algo/sample_ring\.(c|h)|test/host/freq/.*|test/host/common/unit_test_harness\.py)$ pass_filenames: false - # 7k) SHA-256 主机侧单元测试(2026-10-11 加,N-19 FTP 鉴权) + # 7k) SHA-256 主机侧单元测试(2026-10-11 加,FTP 鉴权) # sha256 是 FTP pwd_hash 校验的地基——哈希算错是"正确口令被拒 / # 错误口令放行"的静默语义错误,板端只见 530/230 看不出根因; # FIPS 180-4 标准向量(空串/abc/56B 补位跨界/1MB)+ hex_eq 非法输入 @@ -452,7 +452,7 @@ repos: files: ^(App/util/sha256\.(c|h)|test/host/sha256/.*|test/host/common/unit_test_harness\.py)$ pass_filenames: false - # 7l) FTP 鉴权状态机主机侧单元测试(2026-10-11 加,N-19) + # 7l) FTP 鉴权状态机主机侧单元测试(2026-10-11 加,FTP 鉴权) # 锁定 60s/解锁/成功清零/tick 32 位回绕是"防口令爆破"契约核心, # 板端每验一条要真等 60s、回绕要跑满 49.7 天;lftpd_auth 纯函数 # 设计(tick 参数注入)就是为了让这些分支主机侧毫秒级穷举。 @@ -466,7 +466,7 @@ repos: # 7m) IEC 60870-5-104 被控站协议栈主机侧单元测试(2026-10-12 加,B4) # APCI 重组/STARTDT/总召分片/COV/k-w-t1~t3/遥控定值两段式/C_CS/错误 # 应答——link 层 tick 注入 + pts 数据源 vtable 抽象就是为 L0.5 全离线 - # 穷举设计(A-4 约束 1);板端复现这些分支要真主站+真等待。 + # 穷举设计(分层约束);板端复现这些分支要真主站+真等待。 # 触发面含数据源依赖(prot_set/prot_elm/phasor/sample_ring)——测试 # 直接 #include 这些生产源,签名变了要立刻在这里暴露。 - id: iec104-unit-test @@ -489,7 +489,7 @@ repos: files: ^(App/log/soe_rec\.(c|h)|App/log/soe_store\.(c|h)|test/host/soe/.*|test/host/common/unit_test_harness\.py)$ pass_filenames: false - # 7n) dhara NAND 移植层(nand_ftl)主机侧单元测试(2026-10-15 加,N-21 / 报告 N-12+N-13) + # 7n) dhara NAND 移植层(nand_ftl)主机侧单元测试(2026-10-15 加) # 背景:移植层六个 HAL 入口是**纯地址算术**,此前对越界页/块号(含 # DHARA_PAGE_NONE 哨兵)**不做任何上界校验**——加偏移送进 SPI 命令后被地址位 # 截断成某个真实页 → 静默读写/搬走无辜数据;而 is_free 旧实现只看页首 64 B, @@ -497,7 +497,7 @@ repos: # = 数据丢失。两类缺陷真机**极难复现**(要恰好撞上特定页形态 / 被污染的 meta), # 板端只能表现为"某次复位后丢数据",无从定位。 # 本钩子用 MSVC 离线编译真实 nand_ftl.c(#include 纳入)+ **RAM 假芯片** - # (32 块 x 64 页 x 2048 B):把"双编程"做成可数事件(N-12 探针)、记录全部 + # (32 块 x 64 页 x 2048 B):把"双编程"做成可数事件、记录全部 # 访问次数,于是"越界不产生任何芯片访问""越界标坏块次数为 0"可机械断言; # 并内联一份"只看页首 64 B"的旧算法做对照,证明这组断言真能咬人。 # 触发面含 dhara 本体(测试一并 #include map.c/journal.c 跑真链路场景)。 @@ -531,7 +531,7 @@ repos: # TEST_SUITE_CH395F 就是开启状态(.map 含 34 处 test 代码),即仓库里 # 躺着的是测试固件。本钩子抓这个静默漏网:默认要求产品构建,跑套件回归 # 确需提交测试构建时用 `--allow-test-build` / ALLOW_TEST_BUILD=1 显式放行。 - # 详见 docs/任务规划/登记簿.md T-1 + # 详见 docs/任务规划/登记簿.md - id: test-suite-switch name: 测试套件开关=产品构建 entry: python scripts/check_test_suite.py @@ -539,7 +539,7 @@ repos: files: ^test/board/test_config\.h$ pass_filenames: false - # 9) 双端注册反向校验(2026-10-12 加,登记簿 N-23 / 审查报告 N-06 残余) + # 9) 双端注册反向校验(2026-10-12 加,登记簿条目「rs485_task 白名单缺口」) # 背景:新 .c 必须手动注册两处(`MDK-ARM/*.uvprojx` + `simulator/CMakeLists.txt`) # 才会被编译;历史上 `App/task/rs485_task.c`、`Drivers/BSP/NET/lftpd/lftpd_log.c` # **两处都没注册** = 写了却从不参与任何一端编译的**死代码**,误导读者以为功能已生效。 @@ -556,22 +556,22 @@ repos: language: system files: (\.c$|\.uvprojx$|CMakeLists\.txt$|^scripts/check_registration\.py$) pass_filenames: false - # 10) RAM 预算 92% 阈值门禁(2026-10-12 加,登记簿 N-27 前半 / 审查报告 §5.5 三件套) + # 10) RAM 预算 92% 阈值门禁(2026-10-12 加,登记簿条目「缩堆迁 CCM 后半(暂缓)」的台账/门禁那半 / 审查报告 §5.5 三件套) # 背景:报告 §5.5 第 1 条要求"超阈值即失败 + Top-10 附表"——此前 mem-numbers 只防 # "文档数字漂移",**没有任何机械检查拦"占用超阈值"**;台账三件套(ram_baseline.py + # ram_baseline.tsv + docs/内存管理/RAM预算台账.md)落地后,本钩子把 92% 红线机械化。 # 逻辑:RW_IRAM1/RW_CCM 任一 >92% → FAIL + Top-10 RW+ZI 对象表;台账受管区漂移 → FAIL - # (提示 --write);无 .map → 自适应跳过(同 mem-numbers 在 CI 的行为,盲区登记 A-5 残余)。 + # (提示 --write);无 .map → 自适应跳过(同 mem-numbers 在 CI 的行为,盲区已登记)。 # ⚠ pre-commit 4.x 无 `always: true` → files 覆盖所有能改变 RAM 的触发面: # 全部自研代码目录 + lv_conf.h + 测试板配置 + 脚本/tsv/台账自身 + 本配置文件。 - id: mem-budget - name: RAM 预算 92% 阈值门禁 (N-27) + name: RAM 预算 92% 阈值门禁 entry: python scripts/ram_baseline.py --gate language: system files: ^(App/|Drivers/BSP/|Src/|Inc/|Lib/lv_conf\.h|test/board/|scripts/ram_baseline\.|docs/内存管理/|\.pre-commit-config\.yaml$) pass_filenames: false - # 11) UI 字符集差集检查(2026-10-15 加,报告 §6.5 U4 残余 / §7 交付物清单"字符集差集检查"行) + # 11) UI 字符集差集检查(2026-10-15 加,报告 §6.5 的 UI 字符集与像素回归门禁残余 / §7 交付物清单"字符集差集检查"行) # 背景:`ui_font_cn_12.c` 是**子集**字体,只含 `ui_cn_chars.txt` 列出的码位。 # 生成链 UI 源码 --extract_cn--> ui_cn_chars.txt --make_font--> 字体 .c,其中 # verify_coverage 只在**生成期**校验"txt ⊆ cmap";改完 UI 字符串忘了重跑 @@ -621,7 +621,7 @@ repos: files: ^(tools/setview_check/|Drivers/BSP/UI/ui_w_setview\.(c|h)$) pass_filenames: false - # 12) UI 像素回归门禁(2026-10-20 加,W6 U4 收尾第三件 / 报告 §6.5 U4、§6.1 golden 行) + # 12) UI 像素回归门禁(2026-10-20 加,W6 的 UI 字符集与像素回归门禁收尾第三件 / 报告 §6.5 同项、§6.1 golden 行) # 背景:golden 两脚本(check_menu_rows.py / check_content_pages.py)早已存在且一直在 # 人工跑,但**从未进自动门禁**——"有基线"不等于"有门禁"(报告 §6.1 原文即自认此缺口)。 # 形态选型依据实测:改一个 UI 源文件后 build_sim 增量重编 + 两条 golden 合计 **2~4 秒**, @@ -674,9 +674,9 @@ repos: # /形态/冻结/游标,逐项来自规范 §4 总表)+ `scripts/id_aliases.json`(旧号→新号, # 照搬 section_aliases.json 的成功范式)。 # 判据要点:「定义」= 字面量出现在该族**属主登记处文档**里;于是 §5.5 的 - # 「销项 = 删行 + 清全库引用」只做一半,校验 3 立刻报(N-31/N-32 正是这条)。 + # 「销项 = 删行 + 清全库引用」只做一半,校验 3 立刻报(销项号被重新取走那类撞号正是这条)。 # 盲区显式登记在 registry 的 `blind_spots` / `unmanaged_bare`:裸形同字母多义 - # (§1.2:P1 五义 / L1 四义 / U1 四义)与元器件位号同形(§4.4),机械全库判定 + # (§1.2:P1 五义 / L1 四义 / UI 状态机号四义)与元器件位号同形(§4.4),机械全库判定 # 必然一边倒误报 → 裸形族只在属主文档内判越游标、文档外降告警;收口属 §9 阶段 # 1-② 的迁移批次。**不是「检查通过」,是记在案上的盲区。** # ⚠ 当前 warn-only(§8 上线节奏:先清 backlog → 清零后 entry 加 --strict 拦提交, @@ -692,7 +692,7 @@ repos: files: \.(md|c|h|py|ya?ml|json|txt)$|^\.pre-commit-config\.yaml$ pass_filenames: false - # 14) 分层方向门禁(2026-10-22 加,报告 §0.4 结构债 S-1 的"防扩大"那一半) + # 14) 分层方向门禁(2026-10-22 加,结构债的"防扩大"那一半) # 背景:AGENTS.md 与 项目架构总览.md 早就写着"六层单向依赖",但**从来没有机械检查** # ——Drivers/BSP/ 里可以随手 include "cfg_file.h" 而无人报警。后果可测量:2026-09 审查 # 报告记"NETCFG 一处",2026-10-22 实测已长到 **8 个文件 9 处**。边界的静默腐蚀只能 @@ -711,7 +711,7 @@ repos: files: ^Drivers/BSP/|^scripts/check_layer_deps\.py$|^\.pre-commit-config\.yaml$ pass_filenames: false - # 15) BSP 错误码与超时命名约定(报告 S-5 的机械检查那一半)。 + # 15) BSP 错误码与超时命名约定(机械检查那一半)。 # 四项检查:公开 API 不得用 HAL_StatusTypeDef/bool 当错误通道(谓词 is_/has_/can_/valid # 不在此列);*_TIMEOUT 宏不得取负值(名字叫 TIMEOUT 其实是返回码);非 void 的公开 API # 必须有「返回值:」注释;**返回错误码的函数不得裸丢弃返回值**(接住或显式 (void)——实测 diff --git a/AGENTS.md b/AGENTS.md index b5545ecf..99541fcc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ ## 项目概况 -**现状一句话**:平台能力(网络 / 存储 / 时间 / UI / 日志 / 看门狗 / FTP / 配置)**已实现并台架验收**;采集主线已闭环到业务消费者(L1→L2 相量→L2.5 遥测快照→L3 保护判据 protectTask→104 遥信/遥测+UI,B5/批 2);⚠ **保护判据现处于「未标定闭锁」态**——产品构建下 `adc_cal_flags()` 的 `ADC_CAL_F_VALID` 未置位,`protect_task.c:98` 直接停走判据,等卡 12 两点标定写入 `[adc_cal] valid` 后自动解卡(用户裁定 2026-10-25:保持闭锁,不用万用表走捷径);**RS-485 采集尚未接线**(B2 按用户决策冻结,见 `docs/任务规划/暂缓-RS485.md`)——当前最大功能缺口,接线节奏受 E-59 门禁约束(见「平台约束」)。 +**现状一句话**:平台能力(网络 / 存储 / 时间 / UI / 日志 / 看门狗 / FTP / 配置)**已实现并台架验收**;采集主线已闭环到业务消费者(L1→L2 相量→L2.5 遥测快照→L3 保护判据 protectTask→104 遥信/遥测+UI,B5/批 2);⚠ **保护判据现处于「未标定闭锁」态**——产品构建下 `adc_cal_flags()` 的 `ADC_CAL_F_VALID` 未置位,`protect_task.c:98` 直接停走判据,等卡 12 两点标定写入 `[adc_cal] valid` 后自动解卡(用户裁定 2026-10-25:保持闭锁,不用万用表走捷径);**RS-485 采集尚未接线**(B2 按用户决策冻结,见 `docs/任务规划/暂缓-RS485.md`)——当前最大功能缺口,接线节奏受 **RS-485 接线门禁**约束(见「平台约束」)。 **数据主线**(能力视角;分层图与跨层依赖表 → `docs/架构设计/项目架构总览.md`): @@ -90,7 +90,7 @@ ## 提交门禁(pre-commit) -- 任一失败即拦截,共 **49 个钩子**(2026-10-25 道E 连加 `bom-guard` / `setview-check` / `uinav-selftest` 三条;**每种门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径)→ `docs/门禁说明/README.md` 索引**;通用机制与整体口径 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 23 个(`-unit-test` 后缀,2026-10-25 增补 adc-cal / prot-dim / vol-idle-sync / ui-data / meas / run-par / ui-sink 七个)+ `test-coverage`(**覆盖率总闸**:跑全部 40 套并守基线回退;缺 mscoverage 组件时按用户裁定 **FAIL**、不 skip,2026-10-24)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 18 页黄金参考,U4)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 3 条基线只减不增(2026-10-25 记录 reader 注入削掉 UI 记录 3 条边,6→3),报告 S-1)+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」、**返回错误码的函数不得裸丢弃返回值**(须接住或显式 `(void)`;实测编译器与 cppcheck 都不报这类,只有这条会响);约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 首清点 19 条 → 已还 18;`consume-result` 上线又暴露 37 条存量丢弃 → **现 38 条**),报告 S-5) +- 任一失败即拦截,共 **49 个钩子**(2026-10-25 道E 连加 `bom-guard` / `setview-check` / `uinav-selftest` 三条;**每种门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径)→ `docs/门禁说明/README.md` 索引**;通用机制与整体口径 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败;**2026-10-26 方案 B 同批加了第二根**:不可判定引用**绝对数**超过 `undecidable_total_ceiling` 即失败,因为比值会被"把可判定的引用改成散文"稀释(详见 `docs/门禁说明/id-check.md` §7.6))+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 23 个(`-unit-test` 后缀,2026-10-25 增补 adc-cal / prot-dim / vol-idle-sync / ui-data / meas / run-par / ui-sink 七个)+ `test-coverage`(**覆盖率总闸**:跑全部 40 套并守基线回退;缺 mscoverage 组件时按用户裁定 **FAIL**、不 skip,2026-10-24)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,登记簿条目「rs485_task 白名单缺口」)+ `mem-budget`(RAM 92% 预算门禁,登记簿条目「缩堆迁 CCM 后半(暂缓)」)+ `font-charset`(UI 字符集差集检查,防缺字白框)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 18 页黄金参考)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 3 条基线只减不增(2026-10-25 记录 reader 注入削掉 UI 记录 3 条边,6→3))+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」、**返回错误码的函数不得裸丢弃返回值**(须接住或显式 `(void)`;实测编译器与 cppcheck 都不报这类,只有这条会响);约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 首清点 19 条 → 已还 18;`consume-result` 上线又暴露 37 条存量丢弃 → **现 38 条**)) - **范围 = 项目代码**:第三方(`Lib/`、`Middlewares/`、CMSIS、HAL、`lftpd`、`dhara`、`MDK-ARM`)排除;`cppcheck`/`coding-standard` 另排除 `test/`,规范检查再排除 CubeMX 生成的 `Src/`/`Inc/`。**`simulator/` 已纳入全部钩子**(2026-10-11)——生成物只靠 `.gitignore` 挡,**不在 `exclude` 里写目录名**(教训见 `docs/代码规范/pre-commit使用说明.md` §5.5) - ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关 - **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/` @@ -119,7 +119,7 @@ - **ADC 取数只用 `tpafe5160_snapshot()`(临界区原子快照)**:旧的 `get_buf()`/`data_ready()`/`clear_ready()` 已从驱动**删除**(两步之间被抢占会拷回两帧混合数据;`s_ready` 三写者竞态)。消费者侧一律经 `adc_input_poll()` / `adc_input_raw()` **按语义**取数(不写死下标)→ `docs/子系统设计/ADC采样节拍设计说明.md`、`App/algo/README.md` - **F4 上 ADC1 的硬件触发源与 DMA 流是硬约束**:① ADC1 的外部触发清单**没有 TIM6/TIM7 TRGO**(F4 上它们只接 DAC)→ 同时驱动两片转换器必须用 **TIM3**(PSC 83 / ARR 999 → 1 kHz + TRGO_UPDATE);② ADC1 在 DMA2 **只挂 Stream0 或 Stream4** → 选 **Stream4** 才不挤走 SPI1/USART1 RX,且**换流会被重置为优先级 0**(违反"外设中断 ≥5")→ 改流后必须回 NVIC 核对 `Src/dma.c`;③ `DMAContinuousRequests` **必须 ENABLE**(关掉 = 一轮 9 通道只发一次请求,静默丢 8 路);④ DMA 目的缓冲必须在 SRAM(DMA2 不可达 CCM)→ `docs/硬件平台/ADC通道映射说明.md` - **STM32F4 ADC 上电必须自校准**:CubeMX **不生成**这一步,本仓裁剪版 HAL 没有 `HAL_ADCEx_Calibration_Start`、裁剪版 CMSIS 头没有 `ADC_CR2_CAL` → 按 RM0090 §13.3.6 手写 `adc1_calibrate()`(`Src/adc.c` 的 `USER CODE BEGIN 1` 区)+ `MX_ADC1_Init()` 末尾调用;缺它**不报任何错**,只是读数带失调 -- **接 ADC / RS-485 消费者前的强制门禁(E-59)**:先确认驱动契约已修(TPAFE E-44/E-45/E-58 ✅;RS485 E-36/E-37 ✅ 但 **E-50 单缓冲包撕裂、E-49 TX 超时拉 DE 未修 ❌** → `rs485_task` 仍被挡)+ 建任务前后各跑一次 `stack_report.py` +- **接 ADC / RS-485 消费者前的强制门禁(RS-485 接线门禁)**:先确认驱动契约已修——TPAFE 三条 ✅(原子快照取数 / 帧序号取代就绪电平标志 / FRSTDATA 数据有效性校验);RS485 已修两条 ✅,但 **单缓冲包撕裂、TX 超时拉 DE 两条未修 ❌** → `rs485_task` 仍被挡。建任务前后各跑一次 `stack_report.py` **存储与网络外设** @@ -135,7 +135,7 @@ - **完整引脚表(含 SD2506 I2C1、SWD、调试口)→ `docs/产品需求/产品需求设计文档.md` §4.2**(权威,V0.1 口径;勿在本文重复抄表) - ⚠ **网名 `AINn` ≠ 板级 `ADCn` ≠ 转换器通道号**(17 路里 13 路置换)→ 取数**必须查** `docs/硬件平台/ADC通道映射说明.md`(TPAFE `buf[2]=IA`/`buf[5]=UN`/`buf[6]=UA`;网名 ADC17=PC4 用 `ADC_CHANNEL_14`,**不是** `ADC_CHANNEL_12`) -- ⚠ **8 路电气量被分给两片转换器是 V0.1 硬件缺陷,不是有意分配**(跨转换器无共同采样触发 → 无同一时刻性、相位不确定)→ V0.1 上**禁止**跨转换器做相位/功率/功率因数/矢量运算;**V0.2 须把 8 路全接 TPAFE**(登记簿 N-18) +- ⚠ **8 路电气量被分给两片转换器是 V0.1 硬件缺陷,不是有意分配**(跨转换器无共同采样触发 → 无同一时刻性、相位不确定)→ V0.1 上**禁止**跨转换器做相位/功率/功率因数/矢量运算;**V0.2 须把 8 路全接 TPAFE**(登记簿条目「双转换器无共同采样触发」) ## 模块约定 @@ -151,7 +151,7 @@ ## 文档与图形 -- **登记簿类文档「完成即销项」**:`docs/任务规划/README.md` 这类待办登记簿,事项修复/关闭后**直接从表中删除该行**(不保留"已消化"行)——结论与证据应已沉淀进对应模块文档/问题记录。销项时必须:① 先核对目标文档确已含完整记录 ② 同步清理其他文档对该编号的引用 ③ 改完跑 `check_doc_links.py --all` + pre-commit。**新条目标识(用户裁定 2026-10-14)**:`#` 列填**语义短语**(如 `沉降窗 800ms 上限只留痕`),**不再新增字母+数字编号**——"编号不回收"只写规则却不留痕迹就不可执行(销项删行后仓库无痕迹,实测 `N-31` 于 2026-10-14 被复用撞号),语义标识自带唯一性;存量编号冻结沿用、**已随销项消失的号永久作废** → `docs/任务规划/README.md` §0 +- **登记簿类文档「完成即销项」**:`docs/任务规划/README.md` 这类待办登记簿,事项修复/关闭后**直接从表中删除该行**(不保留"已消化"行)——结论与证据应已沉淀进对应模块文档/问题记录。销项时必须:① 先核对目标文档确已含完整记录 ② 同步清理其他文档对该编号的引用 ③ 改完跑 `check_doc_links.py --all` + pre-commit。**新条目标识(用户裁定 2026-10-14)**:`#` 列填**语义短语**(如 `沉降窗 800ms 上限只留痕`),**不再新增字母+数字编号**——"编号不回收"只写规则却不留痕迹就不可执行(销项删行后仓库里毫无痕迹,2026-10-14 实测就发生过「销项号被重新取走」的撞号事故),语义标识自带唯一性;**存量编号(登记簿 N-x / S-x / T-x 与专项 A-x)已于 2026-10-26 全部转成语义短语**——旧号进 `scripts/id_aliases.json` 标 `retired-to-phrase`,不得再指;**已随销项消失的号永久作废**、不得复用 → `docs/任务规划/README.md` §0 - 图形走 **`draw-diagram` skill**:精确布局图用 `tools/arch_diagram/gen_*.py` 生成器(产物放 `docs/images/`,脚本与 .svg 一并提交);拓扑图用 Mermaid;**勿手绘 ASCII 框图**。文件引用关系图走 `tools/refgraph`(`& 'tools\refgraph\gen_refgraph.bat'`) - **改名 / 挪目录一律走 `scripts/rename_ref.py`(先 dry-run,确认后 `--apply`)**:`git mv` + 自动改写全库引用(反引号引用 / Markdown 链接 / 图片 `../` 层级换算 / `#include` / `uvprojx` `` / `CMakeLists.txt`),落盘后自动跑 `check_doc_links --all` 复核——手工扫引用既慢又漏(一次改名历史上波及 10~30 文件)。引用章节用「`路径.md` §N.M」**紧邻写法**(`doc-links` 校验编号是否还在:紧邻/裸名/前缀/别名/自引用**全部拦提交**(钩子带 `--sections-strict`);每次跑打印「可判定比例」,`--section-todo` 出待改清单,昵称歧义就登记 `scripts/section_aliases.json` 或写清楚)→ `docs/文档规范.md` §3.5 / §3.6。**编号改名走 `scripts/rename_ids.py`**(映射表驱动、**显式 scope**、干跑默认、自动写 `scripts/id_aliases.json` 的 scoped 旧号→新号条目;`[示例]`/`[计划]` 行与变更记录表不改写;**绝不全局查找替换**——同一裸形字面量可能同时属多个族,全局替换等于造新撞号,实测 `S3` 三义) - 文档写作规范(编码/BOM/链接/表格/一致性钩子)→ `docs/文档规范.md`;全量索引与推荐阅读顺序 → `docs/README.md` @@ -174,7 +174,7 @@ | 存储栈(FatFs / dhara / lftpd / 两条 FatFs 陷阱) | `docs/架构设计/STM32F4-Base存储架构说明.md` | | RAM / 栈预算 / 容量台账 | `docs/内存管理/RAM使用分析与优化方案.md`、`内存内容分配策略.md` | | **每个提交门禁一篇详解**(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径) | `docs/门禁说明/README.md`(49 个钩子 → 26 篇索引 + 1 指针) | -| 实体编号怎么写 / 前缀总表 / `id-check` 八项校验 | `docs/编号规范.md`(§4 总表、§8 门禁与实测基线) | +| **要不要给某个东西发编号**(默认:不发,写语义短语)/ 允许用号的 8 个族 / `id-check` 八项校验 | `docs/编号规范.md`(§1 先问三句、§4.1 族总表〔穷举〕、§8 门禁与实测基线) | | 提交门禁通用机制(阶段 / stash 陷阱 / 触发面 / 增量拦截三段式) | `docs/代码规范/pre-commit使用说明.md` §2 | | 代码规范 V1.0 / cppcheck 口径 | `docs/代码规范/嵌入式C语言代码规范_V1.0.md`、`cppcheck使用说明.md` | | 自动化测试 / 实机测试 / 板级套件 | `docs/测试说明/自动化测试流程.md`、`实机测试实施指南.md`、`board-test-suite` skill | diff --git a/App/algo/README.md b/App/algo/README.md index b5ca6b52..f76b3e1d 100644 --- a/App/algo/README.md +++ b/App/algo/README.md @@ -41,7 +41,7 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(L5-2,码值域)→ `s ## 3. 逐文件要点(只留"改代码时必须知道"的;机制与理由见设计文档) > **改本目录前先记五条红线**:① 取数只经 `adc_input_raw()`,不写死下标;② **禁止跨转换器**做 -> 相位 / 功率 / 矢量运算(N-18);③ 帧值已是 L1.5 工程量,但 **未标定(`adc_cal_flags()` 无 +> 相位 / 功率 / 矢量运算(双转换器无共同采样触发);③ 帧值已是 L1.5 工程量,但 **未标定(`adc_cal_flags()` 无 > VALID)时单位只是 ADC 引脚电压**,不是端子二次值——下游消费精度判据见「数据可信」§4.2 三态 > 表;④ 工程量 → 帧的唯一折算点是 `adc_cal_apply()`,禁止旁路再乘系数;⑤ 本层不建任务、 > 不等事件、不写日志(`adc_cal` 的 `[ERR]` 哨兵留痕是唯一例外,且每通道每开机一次), @@ -87,7 +87,7 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(L5-2,码值域)→ `s `freq_get() == 0 && freq_valid() == 1`。 - 精度靠**自适应窗长**(窗长 = 泄漏纹波周期的整数倍);定长平均压不到 0.02 Hz, 换窗长 = 换精度,二者不是独立旋钮。 -- 源切换即废相位基准重建(UA / IA 无共同相位基准,N-18)。 +- 源切换即废相位基准重建(UA / IA 无共同相位基准,即双转换器无共同采样触发)。 - 判据 **J1~J6** 与实测口径 → `docs/算法/采集与算法层设计说明.md` §7.2(与 `test/host/freq/freq_test.c` 文件头同源)。 ### 3.8 `adc_cal.c` / `.h` — L1.5 通道标定折算(「数据可信」卡 2 / 卡 4) @@ -129,7 +129,7 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(L5-2,码值域)→ `s ### 4.1 挂载与模拟器边界 - 九个 `.c` 已登记在 `MDK-ARM/STM32F407-Demo.uvprojx`;`../App/algo` 已在 `` 内。 - 本层**不进** `simulator/CMakeLists.txt`(UI 模拟器不跑采集链路):九个文件全部登记在 - `scripts/check_registration.py` 的 `SIM_EXEMPT`(附理由),`registration-check` 钩子反向校验(N-23)。 + `scripts/check_registration.py` 的 `SIM_EXEMPT`(附理由),`registration-check` 钩子反向校验(登记簿条目「rs485_task 白名单缺口」)。 ### 4.2 主机侧单测(L0.5)——生产源文件以 `#include` 原样纳入 diff --git a/App/algo/adc_cal.c b/App/algo/adc_cal.c index b3e7fce2..7810cf29 100644 --- a/App/algo/adc_cal.c +++ b/App/algo/adc_cal.c @@ -70,7 +70,7 @@ static adc_cal_bcfg_t s_bcfg; /* 生效零点副本 */ * (16 位 ±5 V 档),UB/UC/IB/IC/IN 走 STM32 ADC1;逐路对过 * docs/硬件平台/ADC通道映射说明.md §3(IN=ADC11/STM32,勿再按分组顺序误写)。 * ROM 表按**当前 V0.1 来源排布**显式书写(不查 desc 运行期派生),因为它是 - * 卡 1 定稿的存档事实;V0.2(N-18)改版时本表与 adc_input 映射表同步改, + * 卡 1 定稿的存档事实;V0.2 改版(消除双转换器无共同采样触发)时本表与 adc_input 映射表同步改, * 上层语义与 API 不动。CLIPPED 位则**运行期按来源派生**(见 adc_cal_flags) */ static const adc_cal_kcfg_t s_kcfg_rom = { /* idx0 UA / idx1 UB / idx2 UC / idx3 UN */ @@ -272,7 +272,7 @@ uint32_t adc_cal_flags(adc_semantic_t sem) { f |= ADC_CAL_F_VALID; } /* CLIPPED 由**来源**派生(R5:STM32 侧单极性无偏置 → 负半周削顶)—— - * V0.2 改版(N-18,8 路全接 TPAFE)后本位自动恒 0,无需改码, + * V0.2 改版(8 路全接 TPAFE,消除双转换器无共同采样触发)后本位自动恒 0,无需改码, * 对应「数据可信」§10「页面与 104 绑定零改动」的要求 */ if (s_rom_k_stm32((unsigned)sem) != 0) { f |= ADC_CAL_F_CLIPPED; diff --git a/App/algo/adc_cal.h b/App/algo/adc_cal.h index 207ce373..31427080 100644 --- a/App/algo/adc_cal.h +++ b/App/algo/adc_cal.h @@ -15,7 +15,7 @@ * 标定(卡 12)完成前不得把输出当二次值消费。TEMP 不参与线性标定 * (决策 5),adc_cal_apply() 原样透传码值。 * 本层**纯算法 + 纯数据**:不碰寄存器、不等事件;adc_cal_init() 内核前 - * 可调(注册进引擎必须先于 cfg_file_load,N-19 契约);apply/flags + * 可调(注册进引擎必须先于 cfg_file_load,FTP 鉴权确立的契约);apply/flags * 任意上下文可读(cfg RAM 副本 4 字节对齐字读写在 Cortex-M4 上原子, * 与 defaultTask 的 load/poll 复位存在 µs 级混窗——设计接受,见 .c 头)。 * 适用平台:STM32F407ZGTx(Cortex-M4F);主机侧 L0.5 单测 test/host/adc_cal/ diff --git a/App/algo/adc_health.h b/App/algo/adc_health.h index e9aec37d..0273d21d 100644 --- a/App/algo/adc_health.h +++ b/App/algo/adc_health.h @@ -8,7 +8,7 @@ * ④ 错误计数增量:FRSTDATA 跳变 / HAL ADC·DMA 错误 / STM32 停摆计数。 * ⚠ 本层**纯逻辑**:不碰寄存器、不取数、不打日志——输入是调用方 * 采集的计数快照,输出是故障位;日志与自检上报由消费者 - * (`App/task/adc_task.c`)按位执行 → 主机侧可单测(L0.5,A-4)。 + * (`App/task/adc_task.c`)按位执行 → 主机侧可单测(L0.5)。 * 适用平台:STM32F407ZGTx + TPAFE5160 * 作者:王建锋 * 创建日期:2026-10-11 diff --git a/App/algo/adc_input.c b/App/algo/adc_input.c index 65f8b6ba..76709547 100644 --- a/App/algo/adc_input.c +++ b/App/algo/adc_input.c @@ -41,7 +41,7 @@ * 权威口径:docs/硬件平台/ADC通道映射说明.md(2026-10-09 硬件确认的语义分配)。 * index 含义:TPAFE 源 = 驱动 buf 下标 0..7;STM32 源 = ADC1 通道号 0..15。 * ⚠ 消费侧一律经本表按语义取数,**不得写死下标**(V0.1 有 13/17 路被置换, - * 网名编号与通道号不等价);V0.2 改版(N-18)只改本表内容。 */ + * 网名编号与通道号不等价);V0.2 改版(消除双转换器无共同采样触发)只改本表内容。 */ static const adc_channel_desc_t k_adc_map[ADC_SEM_COUNT] = { [ADC_SEM_UA] = { ADC_SRC_TPAFE, 6U }, /* TPAFE buf[6](ch7) */ [ADC_SEM_UB] = { ADC_SRC_STM32, (uint8_t)ADC_CHANNEL_14 }, /* PC4 */ @@ -240,7 +240,7 @@ int adc_input_init(void) { * STM32 侧无新帧不影响 TPAFE 快照与返回值语义(其状态另由 * adc_input_stm32_rc() / adc_input_stm32_seq() 暴露)。 * ⚠ STM32 侧数据比 TPAFE 侧**滞后 1 拍**(乒乓双缓冲的固有延迟, - * 9 路一致);按 N-18,V0.1 本就禁止跨转换器做矢量运算。 + * 9 路一致);因双转换器无共同采样触发,V0.1 本就禁止跨转换器做矢量运算。 */ int adc_input_poll(void) { int r = tpafe5160_snapshot(s_tpafe_raw, (uint8_t)ADC_INPUT_TPAFE_CH_NUM); diff --git a/App/algo/adc_input.h b/App/algo/adc_input.h index a46c0188..e471cd96 100644 --- a/App/algo/adc_input.h +++ b/App/algo/adc_input.h @@ -5,7 +5,7 @@ * ① 语义通道映射表(L1-4)——权威口径 docs/硬件平台/ADC通道映射说明.md; * ② STM32 ADC1 9 通道采样(L1-3)——由 **TIM3 TRGO 硬件触发 + DMA 双缓冲 * 搬运**,本层只消费、不触发、不等待。 - * V0.1 上 8 路电气量分属两片转换器(硬件缺陷 N-18):UA/UN/IA 走 + * V0.1 上 8 路电气量分属两片转换器(硬件缺陷:双转换器无共同采样触发):UA/UN/IA 走 * TPAFE5160(16 位 / ±5 V 双极性),UB/UC/IB/IC/IN 走 STM32 ADC1 * (12 位 / 0~3.3 V 单极性)。V0.2 改版(8 路电气量全部接 TPAFE) * 只改映射表内容,本层 API 与上层语义不动。 @@ -47,7 +47,7 @@ typedef enum { ADC_SEM_COUNT /* 语义通道数(哨兵) */ } adc_semantic_t; -/* 数据来源:V0.1 的 8 路电气量分属两片转换器(N-18) */ +/* 数据来源:V0.1 的 8 路电气量分属两片转换器(双转换器无共同采样触发) */ typedef enum { ADC_SRC_TPAFE = 0, /* TPAFE5160:16 位、±5 V 双极性、8 通道同步 */ ADC_SRC_STM32 /* STM32 ADC1:12 位、0~3.3 V 单极性、9 通道顺序扫描 */ diff --git a/App/algo/freq.h b/App/algo/freq.h index 34a335dd..18b5c190 100644 --- a/App/algo/freq.h +++ b/App/algo/freq.h @@ -21,7 +21,7 @@ * - 信号源优先级:UA 基波 → IA 基波(UA 无幅值时用 IA,台架只注电流亦可测频)。 * * ⚠ 本层**纯算法**:不碰寄存器、不碰 RTOS、不自己取数(输入 = phasor 使用区) - * → 主机侧可免桩单测(L0.5,落实登记簿 A-4)。 + * → 主机侧可免桩单测(L0.5)。 * 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU) * 作者:DTU 项目组 * 创建日期:2026-10-12(B8) diff --git a/App/algo/phasor.h b/App/algo/phasor.h index cb5d63cb..048b6254 100644 --- a/App/algo/phasor.h +++ b/App/algo/phasor.h @@ -7,7 +7,7 @@ * 覆盖规划 §3.2:L2-1 系数表 / L2-2 实例 / L2-3 有效值输出 / * L2-6 每 1 ms 滑动窗 / L2-7 双区指针原子交换。 * ⚠ 本层**纯算法**:不碰寄存器、不碰任务调度、不自己取数(输入由 - * `sample_ring` 提供)→ 主机侧可单测(L0.5,落实登记簿 A-4)。 + * `sample_ring` 提供)→ 主机侧可单测(L0.5)。 * 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU) * 作者:王建锋 * 创建日期:2026-10-10 diff --git a/App/algo/prot_elm.h b/App/algo/prot_elm.h index ed04ee85..6d75fb5b 100644 --- a/App/algo/prot_elm.h +++ b/App/algo/prot_elm.h @@ -4,7 +4,7 @@ * 同 phasor 实例表模式)。判据为**纯逻辑**(吃测量值 + 定值), * 主机侧可离线单测;出口/SOE(L4)与任务编排为后续。 * - * V0.1 有效实例(N-18:仅 TPAFE 三路): + * V0.1 有效实例(双转换器无共同采样触发 → 仅 TPAFE 三路): * OC1/OC2/OC3 三段式相间过流(A 相 IA,L3-1) * OL 过负荷(A 相 IA,L3-3) * OV/UV 过压/欠压(UA,L3-4) diff --git a/App/algo/run_par.h b/App/algo/run_par.h index 8a77548b..68921261 100644 --- a/App/algo/run_par.h +++ b/App/algo/run_par.h @@ -53,7 +53,7 @@ extern "C" { * 入口参数:无 * 返 回 值:无 * 限定条件:**内核启动前**调用(app_main_init,adc_cal_init 之后);注册必须先 - * 于 defaultTask 的 cfg_file_load(N-19 契约,同 adc_cal 先例);幂等 + * 于 defaultTask 的 cfg_file_load(FTP 鉴权确立的契约,同 adc_cal 先例);幂等 * 函数说明:注册失败(段名重复/段数超限)打 [ERR] 后继续——工厂默认照常生效, * 仅文件整定失效(adc_cal_init 同款降级)。 */ @@ -118,7 +118,7 @@ uint8_t run_par_dirty_peek(void); * 入口参数:无 * 返 回 值:0 成功(或无脏可存);-1 写盘失败(脏保持,下轮重试) int * 限定条件:**defaultTask(配置引擎属主)上下文**,由 SYS_EVT_RUN_PAR_SAVE 驱动 - * 函数说明:save 走引擎唯一写闸(超长拒写/原子改名,E-01 契约);本函数不清 + * 函数说明:save 走引擎唯一写闸(超长拒写/原子改名);本函数不清 * 引擎以外模块的脏语义([adc_cal] 副本值已由写路径就位)。 */ int run_par_store_save(void); diff --git a/App/algo/sample_ring.h b/App/algo/sample_ring.h index 53f92110..c01b7da1 100644 --- a/App/algo/sample_ring.h +++ b/App/algo/sample_ring.h @@ -1,7 +1,7 @@ /* * 模块名称:采样环缓冲(L2 相量计算的输入) * 模块功能:按**语义通道**保存最近若干周波的采样值,供 L2 相量计算按窗取数。 - * V0.1 上 8 路电气量分属两片转换器(硬件缺陷 N-18),本层只做"按时序 + * V0.1 上 8 路电气量分属两片转换器(双转换器无共同采样触发),本层只做"按时序 * 存/取",**不做任何跨通道运算**——同刻性由上游(L1)保证,本层不假设。 * 容量取 **2 个周波(40 点)**,与参考系统 `CN_BUFSAM_CYCLE = 2` * (规划 §10.1)一致,为后续加长数据窗(1.5~2 周波,抗衰减直流, diff --git a/App/app/app_cfg.h b/App/app/app_cfg.h index 11d6bbed..58e6540f 100644 --- a/App/app/app_cfg.h +++ b/App/app/app_cfg.h @@ -6,8 +6,8 @@ * 创建日期:2026-07-19 * 修改记录: * v1.0 2026-07-19 王建锋 创建初始版本 - * v1.1 2026-10-04 王建锋 P2-12:如实标注三个 APP_*_ENABLE 为"未接线占位"(全库零消费点) - * v1.2 2026-10-22 王建锋 S-4 收口(报告 S-4 / 登记簿 C5):删除 4 个零消费点的宏 + * v1.1 2026-10-04 王建锋 如实标注三个 APP_*_ENABLE 为"未接线占位"(全库零消费点) + * v1.2 2026-10-22 王建锋 收口(登记簿 C5):删除 4 个零消费点的宏 * (APP_NET_ENABLE / APP_ADC_ENABLE / APP_RS485_ENABLE / * APP_SYS_TIME_SRC)。留"看起来能裁剪、其实不生效"的宏比不留更糟: * 评审者会误信模块可裁剪,而关掉它们不会产生任何编译期效果。 diff --git a/App/app/app_main.c b/App/app/app_main.c index ec4b3894..9fd4d0bd 100644 --- a/App/app/app_main.c +++ b/App/app/app_main.c @@ -30,7 +30,7 @@ #include "prot_set.h" /* L3 定值模型:prot_set_init()(B5,内核前封存) */ #include "prot_elm.h" /* L3 元件框架:prot_elm_init()(B5,解析相量索引) */ #include "sys_log.h" -#include "task_watchdog.h" /* task_wd_seg_report():round 8 复位不丢"进段打点"上报 */ +#include "task_watchdog.h" /* task_wd_seg_report():第 8 轮复位不丢"进段打点"上报 */ #include "test_config.h" /* 套件开关:身份戳的构建变体串(FW_BUILD_VARIANT)依赖本头, * 改 TEST_SUITE_* / ENABLE_DESTRUCTIVE_TESTS 必重编本 TU → * __DATE__/__TIME__ 随之刷新,见 app_main_init() 的戳注释 */ @@ -131,7 +131,7 @@ int app_main_init(void) { /* 日志引擎初始化(创建待发队列;须先于任何 syslog_post) */ (void)syslog_mcu_init(); - /* 启动锚点(2026-10-02 S10):本函数在内核启动前执行、不创建任何 RTOS 对象, + /* 启动锚点(2026-10-02 加):本函数在内核启动前执行、不创建任何 RTOS 对象, * 恒成功且无副作用;本行是"启动进度轨迹"的起点——之后的每一步都有结果日志, * 故某次启动卡死时,最后一行日志即可指向卡住的步骤。 */ DBG_INFO("boot: syslog init OK"); @@ -166,10 +166,10 @@ int app_main_init(void) { } } - /* 0b) round 8 取证:上报上一轮 defaultTask 主循环的**最后进段**。 + /* 0b) 第 8 轮取证:上报上一轮 defaultTask 主循环的**最后进段**。 * 轨迹槽在 .noinit(scatter UNINIT_CCM),IWDG/引脚复位不清 SRAM → * 误复位后开机第一时间可见 `PREVRUN seg=N`,直接指向主循环阻塞点 - * (round 7 观察"判定分支未执行"经 round 8b 更正:真因是 IWDG 超时被静默 + * (第 7 轮观察"判定分支未执行"经第 8b 轮更正:真因是 IWDG 超时被静默 * 配成复位默认值,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。 * 裸 UART 直发,且一次复位只报一次(内部清 magic)。 */ task_wd_seg_report(); @@ -178,7 +178,7 @@ int app_main_init(void) { * 1) 时间子系统初始化(尽早完成,后续所有日志带时间戳) * sd2506_init() 从 RTC 读取初始时间 * sys_clock_init() 将 RTC 时间转为 Unix epoch,建立墙钟基准 - * S10(2026-10-02):sd2506_init 原先所有失败路径静默返回,是启动段 + * 启动段可观测性(2026-10-02 加):sd2506_init 原先所有失败路径静默返回,是启动段 * 唯一无日志的初始化;现内部已逐路径打 DBG_ERROR,此处再补一行 * 带错误码的结果回执,使 I2C 不通时能看到"在哪一步、什么错"。 */ @@ -308,7 +308,7 @@ int app_main_init(void) { /* 6) 启动 1 kHz 采样节拍:TIM3 更新事件 → ① ISR 内脉冲 CONVST(Src/main.c) * ② 同一事件经 TRGO 硬件触发 STM32 ADC1 一轮 9 通道转换(DMA 自动搬运)。 * ⚠ 必须在 tpafe5160_init() 成功之后启动,否则 ISR 会在驱动首帧自检 - * (E-44 有效性探针)期间乱发 CONVST。消费端 adcTask 退化为纯消费者。 + * (有效性探针)期间乱发 CONVST。消费端 adcTask 退化为纯消费者。 * ⚠ 节拍源必须能触发 ADC1,故用 **TIM3**:F4 上 ADC1 的外部触发只认 * TIM1/TIM2/TIM3/TIM4/TIM5/TIM8 的 CC 或 TRGO,**TIM6/TIM7 的 TRGO 只接 * DAC**(查 CubeMX DB 与本仓库 stm32f4xx_hal_adc.h 的 EXTSEL 表证实) diff --git a/App/app/ui_data_feed.c b/App/app/ui_data_feed.c index e8b2e7de..877fad7c 100644 --- a/App/app/ui_data_feed.c +++ b/App/app/ui_data_feed.c @@ -149,7 +149,7 @@ static void feed_copy_str(char *p_dst, size_t dst_len, const char *a_src) { * 限定条件:uiTask 上下文,ui_data_feed_tick 首帧前调用 * 函数说明:只认 harm == 1 的实例;一个语义通道有多个基波实例时取**首个** * (现状 k_inst[] 每通道最多一个基波,取首个即唯一解)。 - * UB/UC/IB/IC/IN 无实例(V0.1 硬件削顶,N-18)→ 留 -1,该路发 IV + * UB/UC/IB/IC/IN 无实例(V0.1 硬件削顶:双转换器无共同采样触发)→ 留 -1,该路发 IV */ static void feed_map_phasor(void) { uint8_t n; diff --git a/App/cfg/cfg_file.c b/App/cfg/cfg_file.c index bbcb5af7..1811542e 100644 --- a/App/cfg/cfg_file.c +++ b/App/cfg/cfg_file.c @@ -8,20 +8,20 @@ * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成) - * v1.1 2026-09-18 王建锋 P0-1:临时缓冲改经 cfg_store_alloc(MCU = FreeRTOS 堆), + * v1.1 2026-09-18 王建锋 临时缓冲改经 cfg_store_alloc(MCU = FreeRTOS 堆), * 移除对 C 库 malloc 的全部依赖(MicroLIB C 堆仅 512B, * 原 4 处 KB 级 malloc 真机必然失败);增编译期堆峰值预算 * 校验 + 失败上报自检记录 - * v1.2 2026-09-21 王建锋 E-01(历史审计 P0,属主文档已删除):超长文件守卫根治——poll 判定 + * v1.2 2026-09-21 王建锋 超长文件守卫根治——poll 判定 * 前置到 cfg_store_stat 真实 st.size(旧检查位于截断后的 * len 上恒假=死码,超长文件被截断当有效解析 → 界外静默 * 回工厂默认 → 基线粘住 → 用户配置销毁链);save 输出上限 * 由 CFG_OUT_MAX 收紧至 CFG_FILE_MAX_LEN;s_write_doc 写盘 * 唯一闸口拒写超长文档;load/poll 超长处置补自检留痕 - * v1.3 2026-10-04 王建锋 P2-1:受管键行超 CFG_LINE_TEST_MAX 时拒绝保存—— + * v1.3 2026-10-04 王建锋 受管键行超 CFG_LINE_TEST_MAX 时拒绝保存—— * 原实现 Pass1 静默跳过超长行,Pass2 遂在段头补写新 KV 且 * 原文超长行原样保留 → 同一键出现两行(旧值残留、静默截断) - * v1.4 2026-10-15 王建锋 E-16(历史审计 P2,属主文档已删除):生成文档边界余量收口—— + * v1.4 2026-10-15 王建锋 生成文档边界余量收口—— * s_gen_document 两处 `off += snprintf(...)` 改检查式(截断即 * 返回 -1,不再跳过缓冲末尾);注册拒超长键名(≤ 15 字符), * 使每行 112 字节余量成为可编译期验证的前提 @@ -29,7 +29,7 @@ * len 1/2/4 小端补码)——s_decode_int() 用 strtol + ERANGE + * 宽度上下界校验,s_encode() 带负号输出;负零点偏移从此可 * 干净落盘(动因见 docs/任务规划/数据可信.md §4.4 与 §7 决策 3) - * 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save,见 P1-3; + * 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save; * 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证 * (见《配置文件设计说明》D15 A+ 方案) */ @@ -304,7 +304,7 @@ static int s_encode(const cfg_key_def_t *p_key, const uint8_t *p_src, char *p_ds case CFG_VAL_STR: { size_t slen; - /* P3-1:len==0 时 `p_key->len - 1u` 回绕为 SIZE_MAX → memcpy 巨长越界。 + /* len==0 时 `p_key->len - 1u` 回绕为 SIZE_MAX → memcpy 巨长越界。 * STR 字段至少 1 字节(含结尾 NUL),len==0 非法,直接拒绝 */ if (p_key->len == 0u) { return -1; @@ -619,7 +619,7 @@ static int s_load_file(uint8_t **p_pbuf, size_t *p_len) { * len - 数据长度 size_t * 返回值:0 - 成功;-1 - 写失败或超长拒写 int * 限定条件:p_buf 非空;len ≤ CFG_FILE_MAX_LEN - * 函数说明:E-01 纵深防御——超长文档一旦落盘,下次开机 load 守卫即判损坏、 + * 函数说明:纵深防御——超长文档一旦落盘,下次开机 load 守卫即判损坏、 * 整份用户配置连同注释/未知段销毁,故在唯一出口硬性拦截,使 * "引擎永不产出超长 config.ini"成为结构性质(调用者新增路径免疫) */ @@ -923,7 +923,7 @@ static int s_surgical(char *p_in, size_t in_len, char *p_out, size_t out_cap, si * i_sec - 段索引 int * 返回值:1 - 是受管键;0 - 不是(无 '=' 或键名不匹配) int * 限定条件:仅比较 '=' 前的键名(大小写无关、剥尾部空白),不解析值 - * 函数说明:P2-1 用于"超长行是否落在受管键上"的判定(值可能很长,无法拷入定长缓冲); + * 函数说明:用于"超长行是否落在受管键上"的判定(值可能很长,无法拷入定长缓冲); * 非修改式,故可安全用于仍供 Pass2 原样保留的行 */ static int s_line_key_managed(const char *p_line, int i_sec) { @@ -969,9 +969,9 @@ static int s_line_key_managed(const char *p_line, int i_sec) { /* * 函数功能:保存 Pass 1——定位段头行号与各受管键行号 * 入口参数:p_in - 原文本(可修改) char * - * 返回值:0 - 成功;-1 - 受管键行超 CFG_LINE_TEST_MAX(拒绝保存,见 P2-1) int + * 返回值:0 - 成功;-1 - 受管键行超 CFG_LINE_TEST_MAX(拒绝保存) int * 限定条件:行末被 NUL 化且不复原——调用方须传一次性副本 - * 函数说明:P2-1(2026-10-04):受管键行超过 CFG_LINE_TEST_MAX 时**不能**静默跳过 + * 函数说明:受管键行超过 CFG_LINE_TEST_MAX 时**不能**静默跳过 * ——Pass2 会因该行未登记而在段头补写新 KV,同时原文超长行原样保留,导致 * 同一键出现两行(旧值残留 + 新值,解析取谁取决于行序)。故此处显式拒绝: * 返回 -1,调用方中止保存、原文件不动、自检留痕。非受管键的超长行照旧跳过 @@ -1033,7 +1033,7 @@ static int s_surgical_pass1(char *p_in) { } } else if (i_cur >= 0) { - /* 超长行(> CFG_LINE_TEST_MAX):若其键名为本段受管键 → 拒绝保存(P2-1); + /* 超长行(> CFG_LINE_TEST_MAX):若其键名为本段受管键 → 拒绝保存; * 否则按未知内容放过(Pass2 原样保留) */ if (s_line_key_managed(p_line, i_cur) != 0) { return -1; @@ -1165,7 +1165,7 @@ int cfg_file_load(void) { } if (st.size > CFG_FILE_MAX_LEN) { - /* 文件超长 = 损坏:留工厂默认 + 重建模板(E-01:补自检留痕——销毁 + /* 文件超长 = 损坏:留工厂默认 + 重建模板(补自检留痕——销毁 * 用户文件属重大事件,仅串口 DBG_ERROR 在现场无调试器时不可追溯) */ if (s_write_template() != 0) { DBG_ERROR("load: corrupt file, template write FAIL"); @@ -1254,7 +1254,7 @@ int cfg_file_save(void) { (void)memcpy(p_in1, p_in, in_len + 1u); if (s_surgical_pass1((char *)p_in1) != 0) { - /* P2-1:受管键行超 CFG_LINE_TEST_MAX → 中止保存(原文件不动)。此类超长行 + /* 受管键行超 CFG_LINE_TEST_MAX → 中止保存(原文件不动)。此类超长行 * 只可能来自手工编辑(代码生成的受管 KV 行恒 <127B);若强行外科保存会 * 产生重复键行,故宁可拒存并留痕,也不静默写坏。 */ cfg_store_free(p_in1); @@ -1276,7 +1276,7 @@ int cfg_file_save(void) { return -1; } - /* E-01:输出上限由 CFG_OUT_MAX(5120) 收紧至 CFG_FILE_MAX_LEN(4096)—— + /* 输出上限由 CFG_OUT_MAX(5120) 收紧至 CFG_FILE_MAX_LEN(4096)—— * 旧上限允许把 >4096B 文档合法写盘(= 下一颗雷:下次开机 load 守卫 * 判损坏、整份用户文件连同注释/未知段一起销毁)。超限走下方既有 * 溢出中止路径:原文件不动 + 自检上报,RAM 副本保留(可修复后重试) */ @@ -1342,7 +1342,7 @@ void cfg_file_poll(void) { { int i; - /* E-01(2026-09-21 审计 P0):超长守卫必须基于 cfg_store_stat 的真实 + /* 2026-09-21 审计 P0:超长守卫必须基于 cfg_store_stat 的真实 * st.size 且先于 s_load_file——s_load_file 读长钉死 CFG_FILE_MAX_LEN * (nread ≤ 4096 恒成立),旧检查写在读后的 len 上恒假(死码): * 超长文件被截断当有效解析 → 界外段/键静默回工厂默认 → 基线粘住 → diff --git a/App/cfg/cfg_file.h b/App/cfg/cfg_file.h index 3da31079..3026d931 100644 --- a/App/cfg/cfg_file.h +++ b/App/cfg/cfg_file.h @@ -8,12 +8,12 @@ * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成) - * v1.1 2026-09-15 王建锋 属主任务由 netTask 改为 defaultTask(配置引擎单一属主,P1-3) - * v1.2 2026-09-21 王建锋 E-01(历史审计 P0,属主文档已删除):超长守卫契约同步——poll 沉降 + * v1.1 2026-09-15 王建锋 属主任务由 netTask 改为 defaultTask(配置引擎单一属主) + * v1.2 2026-09-21 王建锋 超长守卫契约同步——poll 沉降 * 后先按真实 st.size 判超长(旧实现查截断后读长恒假)、 * save 输出上限收紧至 CFG_FILE_MAX_LEN、写盘唯一闸口拒写 * 超长;超长处置补自检留痕 - * v1.3 2026-10-15 王建锋 E-16(历史审计 P2,属主文档已删除):新增公开常量 + * v1.3 2026-10-15 王建锋 新增公开常量 * CFG_FILE_MAX_NAME_LEN(段名/键名上限 15 字符) * v1.4 2026-10-25 DTU 项目组 数据可信卡 3:新增 CFG_VAL_INT 有符号十进制值类型 * (len = 1/2/4,小端补码)——标定零点偏移等负值需有符号 @@ -32,7 +32,7 @@ extern "C" { #include /* 配置文件长度上限(字节);超过 = 文件损坏,按出厂模板重建 - * (load 与 poll 同判,E-01:poll 在读取前基于真实 st.size 判定) */ + * (load 与 poll 同判:poll 在读取前基于真实 st.size 判定) */ #define CFG_FILE_MAX_LEN 4096u /* 引擎可注册段数上限 */ @@ -103,7 +103,7 @@ int cfg_file_register(const cfg_section_reg_t *p_reg); * 2. 文件缺失 → 按当前 RAM 副本生成出厂模板写盘(首启建文件) * 3. 文件存在 → 解析:有效键写入 RAM 副本;无效键留工厂默认并留痕 * 4. 文件超长(> CFG_FILE_MAX_LEN)= 损坏 → 工厂默认 + 重建模板 - * + 自检留痕(poll 检出运行期超长文件执行同一契约,E-01) + * + 自检留痕(poll 检出运行期超长文件执行同一契约) */ int cfg_file_load(void); @@ -116,7 +116,7 @@ int cfg_file_load(void); * 值原位改写,缺失段/键补写;文件缺失/损坏 → 按 RAM 副本重建 * 完整文档(含文件头注释);写盘 = 临时文件 + 原子改名;输出超 * CFG_FILE_MAX_LEN → 拒绝保存返回 -1(原文件不动 + 自检上报, - * 引擎写盘闸口同拒,E-01) + * 引擎写盘闸口同拒) */ int cfg_file_save(void); @@ -129,7 +129,7 @@ int cfg_file_save(void); * 函数说明:(size, mtime 变更令牌) 相对基线变化且连续 CFG_FILE_SETTLE_CNT * 次轮询稳定 → 先查 st.size 超 CFG_FILE_MAX_LEN = 损坏 → 工厂默认 * + 重建模板 + 自检留痕(不触发段回调:保留在线修复通道,重启 - * 收敛,E-01);否则重载全部段(各段复位工厂默认后解析);挂回调 + * 收敛);否则重载全部段(各段复位工厂默认后解析);挂回调 * 段有差异 → 调用其回调(如触发网络重建);重载后刷新基线 * (回调触发的写盘不引发二次重载) */ diff --git a/App/cfg/cfg_file_store_mcu.c b/App/cfg/cfg_file_store_mcu.c index cc07ab90..b94d5dc7 100644 --- a/App/cfg/cfg_file_store_mcu.c +++ b/App/cfg/cfg_file_store_mcu.c @@ -9,10 +9,10 @@ * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本 * v1.1 2026-09-18 王建锋 增 cfg_store_alloc / cfg_store_free / cfg_store_heap_free: - * 共享核心临时缓冲改走 FreeRTOS 堆(P0-1,MicroLIB C 堆仅 512B) + * 共享核心临时缓冲改走 FreeRTOS 堆(MicroLIB C 堆仅 512B) * v1.2 2026-09-22 王建锋 原子写修复:rename 前先 unlink 已存在目标——本 FatFs * 移植 f_rename 不覆盖既有目标(FR_EXIST),旧实现一切 - * "目标存在的覆盖写"必然失败(E-01 台架验证暴露) + * "目标存在的覆盖写"必然失败(台架验证暴露) * v1.3 2026-10-02 王建锋 四个后端函数纳入卷锁 try 语义(vol_lock_wait(0)): * 与 ftpTask 的 lftpd 文件操作互斥(并发 FTL 写 → * dhara journal 竞争实证);busy 返回 -1 由调用方 @@ -22,7 +22,7 @@ * FRESULT 里不存在的 FR_NO_SYSTEM,改成实际会返回的 * FR_NOT_READY / FR_NO_FILESYSTEM 并写明"非 FR_OK 一律 * 转 -1,不要枚举错误码集合"(无行为改动) - * v1.4 2026-10-05 王建锋 原子写临时文件由 p_path 推导(P3-2):与目标同目录的 + * v1.4 2026-10-05 王建锋 原子写临时文件由 p_path 推导:与目标同目录的 * 固定 8.3 名 config.TMP,不再硬编码卷根;目标恒为 * "/config.ini" 时推导结果与旧值逐字节一致 */ @@ -98,7 +98,7 @@ int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nre #define CFG_STORE_TMP_PATH_MAX 24u /* - * 函数功能:由目标路径推导同目录的临时文件路径(P3-2) + * 函数功能:由目标路径推导同目录的临时文件路径 * 入口参数:p_path - 目标文件路径 const char * * p_out - 输出缓冲 char * * cap - 缓冲容量 size_t @@ -144,7 +144,7 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) if (p_buf == NULL) { return -1; } - /* P3-2:临时文件由 p_path 推导(同目录 + 固定 8.3 名),不再硬编码卷根 */ + /* 临时文件由 p_path 推导(同目录 + 固定 8.3 名),不再硬编码卷根 */ if (s_build_tmp_path(p_path, tmp_path, sizeof(tmp_path)) != 0) { return -1; } @@ -165,7 +165,7 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) } /* 本 FatFs 移植的 f_rename 对已存在目标返回 FR_EXIST、不覆盖(ff.c * "new name already in use" 检查,上游同语义)——"目标存在的覆盖写"必须先 - * unlink 目标再 rename。2026-09-22 台架实证(E-01 验证暴露):缺本步时 + * unlink 目标再 rename。2026-09-22 台架实证:缺本步时 * 一切非首启写(UI 保存/模板重建)静默失败,模拟器后端(POSIX rename * 覆盖语义)照不出。unlink→rename 非原子窗口掉电 = 配置文件暂失, * 开机 load 按"首启"重建工厂模板——与函数失败语义一致,丢失边界 = @@ -262,7 +262,7 @@ const char *cfg_store_tmp_path(void) { * 返回值:缓冲指针(内容未清零);失败返回 NULL void * * 限定条件:任务上下文调用(heap_4 内部临界区自护);释放必须用 cfg_store_free * 函数说明:MCU 端 MicroLIB C 堆仅 512B(startup Heap_Size=0x200),共享核心的 KB 级 - * 缓冲若走 C 库 malloc 必然返回 NULL,故统一由本接口取 FreeRTOS 堆(P0-1) + * 缓冲若走 C 库 malloc 必然返回 NULL,故统一由本接口取 FreeRTOS 堆 */ void *cfg_store_alloc(size_t size) { return pvPortMalloc(size); diff --git a/App/log/console_log.c b/App/log/console_log.c index d93a0711..1e2fef84 100644 --- a/App/log/console_log.c +++ b/App/log/console_log.c @@ -8,17 +8,17 @@ * 修改记录: * 2026-09-24 王建锋 创建(自 Src/usart.c USER CODE 区迁出,实现逻辑不变, * 仅变更文件位置:HAL 库生成文件不得包含应用代码) - * 2026-10-02 王建锋 僵尸通道喂狗节奏保护(台架 gen-288 实证):round-3 的 + * 2026-10-02 王建锋 僵尸通道喂狗节奏保护(台架日志代次 288 实证):第 3 轮的 * "单次 drain 预算 250ms" 只限单次调用——ORE 锁存在外设软复位后 * 毫秒级重锁(PA10 悬空 + RX 使能),背靠背 drain 每轮再耗 100ms * 块超时 → 主循环周期拉到 450~500ms → 100ms 节流的喂狗抽样间隙 * 超过 IWDG 最坏容限 1.7s → 停喂 → 误复位(且心跳位全员健康、 * 无 stall 日志,纯节奏性饥饿)。修复:发送故障后立即结束本轮 * drain 并进入 300ms 发送冷却(冷却期内 drain 直接让位主循环), - * 保证喂狗节奏 ≤ ~300ms。根治(USART1 TX-only 灭 ORE 机制) - * 另行推进(.ioc 结构性修改 + CubeMX 重生成,见 docs/任务规划/登记簿.md S-3)。 - * 2026-10-02 王建锋 round 8b 加固:发送超时判据改用 DWT CYCCNT(时间源无关)。 - * 台架 round 8a 取证(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)定位到:defaultTask + * 保证喂狗节奏 ≤ ~300ms。根治(把 USART1 改成只发不收)已于 2026-10-26 + * 由用户裁定不做,本缓解即长期方案;机制详述 docs/子系统设计/控制台单写者日志设计说明.md §12。 + * 2026-10-02 王建锋 第 8b 轮加固:发送超时判据改用 DWT CYCCNT(时间源无关)。 + * 台架第 8a 轮取证(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)定位到:defaultTask * 冻结在 console 突发行内、无 STALL、复位为 IWDGRSTF。唯一自洽机理 = * HAL_UART_Transmit 的超时判据 `HAL_GetTick() - Tickstart` 依赖 * TIM7 的 uwTick —— TIM7 一旦停摆,超时永不成立 → 写者死等 TXE → @@ -75,13 +75,13 @@ #define DBG_CON_TX_TIMEOUT_MS 100U /* 单次发送超时:256B 块 @115200 标称 22ms;僵尸通道限时 + 自愈 */ #define DBG_CON_DRAIN_BUDGET_MS 250U /* 单次 drain 连续发送上限:让位喂狗轮询(缺失容忍 800ms) */ #define DBG_CON_FAULT_COOL_MS 300U /* 故障后发送冷却:ORE 锁存毫秒级重锁,背靠连发无益 - 且拖长主循环周期 → 喂狗节奏饥饿(gen-288 实证); + 且拖长主循环周期 → 喂狗节奏饥饿(台架日志代次 288 实证); 冷却期内 drain 只让位、不发送,下轮重试 */ -/* ==== round 8b:与时间源无关的硬上界(DWT CYCCNT)==== +/* ==== 第 8b 轮:与时间源无关的硬上界(DWT CYCCNT)==== * HAL_UART_Transmit 的超时判据是 `HAL_GetTick() - Tickstart > Timeout`,而 HAL 时基 * = TIM7 的 uwTick。TIM7 停摆 → 超时永不成立 → 写者死等 TXE → 唯一喂狗者停摆 → - * IWDG 误复位(round 8a 台架现场)。改用 DWT CYCCNT(内核硬件周期计数,不依赖任何 + * IWDG 误复位(第 8a 轮台架现场)。改用 DWT CYCCNT(内核硬件周期计数,不依赖任何 * 中断/uwTick)做判据;DWT 使能失败时退回 HAL_UART_Transmit(旧行为,功能不退化)。 */ #define CON_TX_TIMEOUT_CYC (SystemCoreClock / 10U) /* 发送硬上界 = 100ms 对应周期数 */ /* @@ -293,7 +293,7 @@ void dbg_console_drain(void) { t0 = (uint32_t)HAL_GetTick(); /* 故障冷却:僵尸通道(ORE 重锁)秒内不自愈,冷却期内不发送—— - * 本轮 drain 直接让位主循环,保住喂狗节奏(gen-288 停喂误复位的 + * 本轮 drain 直接让位主循环,保住喂狗节奏(日志代次 288 停喂误复位的 * 直接诱因 = 背靠连发的块超时链)。是否在冷却看 s_con_cooling 独立标志, * **不再拿 tick 值当哨兵**(2026-10-24 实测:故障恰好落在 tick==0 那一拍时,旧写法 * 记出的 cool_until==0 等于没记冷却,下一轮立刻再耗一次 100ms 块超时)。 @@ -403,7 +403,7 @@ void dbg_console_drain(void) { * 环满等待(vTaskDelay / DWT 判据)一律在临界区之外,区内绝不睡眠。 * 写者自身记录日志时先排空缓冲,避免自等。 * 等待上界优先用 DWT 真实时间(CON_APPEND_WAIT_CYC,时间源无关)—— - * tick 停摆时 vTaskDelay 本身也会停,故不能用它作唯一判据(round 8b)。 + * tick 停摆时 vTaskDelay 本身也会停,故不能用它作唯一判据(第 8b 轮加固)。 */ static int con_append(const char *buf, uint16_t len) { uint32_t waited = 0U; diff --git a/App/log/soe_rec.h b/App/log/soe_rec.h index 3f938980..153e74cb 100644 --- a/App/log/soe_rec.h +++ b/App/log/soe_rec.h @@ -3,7 +3,7 @@ * 模块功能:定长 64B SOE(Sequence-of-Events)记录的构建与解析: * CP56Time2a 时标 + 记录类型 + 元件号 + 单调序号 + 值 + 校验和。 * **零 I/O、零 OS 依赖**(仅 stdint/string)→ 主机侧 L0.5 离线单测 - * (落实登记簿 A-4 约束 1"协议/记录与硬件 I/O 分离")。 + * (分层约束:协议/记录与硬件 I/O 分离)。 * 记录口径(docs/算法/继电保护功能复现规划.md §3.4 L4-4/L4-5,B9): * 落盘文件 = /log/soe.log(8.3 合规;FF_USE_LFN=0 约束),定长 64B 一条 * (照 run.log 先例:count = filesize/64 O(1)、容量满删除重建)。 diff --git a/App/log/sys_log.h b/App/log/sys_log.h index 8ac8925b..2f34d1d7 100644 --- a/App/log/sys_log.h +++ b/App/log/sys_log.h @@ -152,7 +152,7 @@ void syslog_drain(void); * 返回值:0 - 成功;-1 - 卷占用或写失败(证据可能回滚,**不阻断判定**) int * 限定条件:poller(defaultTask)上下文调用;卷锁一律 try(绝不阻塞, * 本上下文 = 看门狗喂狗点) - * 函数说明:round 7 取证通道(设计见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录: + * 函数说明:第 7 轮取证通道(设计见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录: * 误复位后 RAM 态随复位消失、stall 行经僵尸串口丢失,故证据必须落盘)。 * 内部在释放卷锁后立刻追加一条 s_append(SYSLOG_RUN, ...) 写另一个 * 文件——FTL 单页缓存语义为"写到不同 LPN 才把前一脏页刷进 NAND", diff --git a/App/log/sys_log_mcu.c b/App/log/sys_log_mcu.c index 52e75e5e..0eb704e1 100644 --- a/App/log/sys_log_mcu.c +++ b/App/log/sys_log_mcu.c @@ -15,7 +15,7 @@ * (本任务 = 看门狗喂狗点,绝不阻塞等锁) * v1.3 2026-10-02 王建锋 新增 sys_log_evidence():把看门狗停喂现场 * 证据落 /log/wdev.log(掉电不丢)+ flush 诱导 - * (round 7 纯取证,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录) + * (第 7 轮纯取证,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录) * v1.5 2026-10-24 王建锋 主机单测批次照出的缺陷一起修,本文件内 5 条 * (A/B/C/D/F;E 在 App/cfg/cfg_file_store_mcu.c): * A 卷忙整批静默丢日志——syslog_drain 批量路径补 @@ -34,10 +34,10 @@ * 数组满各立 volatile 抑制位,与 B 的 drain 侧那位分 * 开):卷被长期占用 + A 项回投让队列常驻满时,串口 * 不再按投递频率刷 "queue full, drop" - * v1.4 2026-10-04 王建锋 P2-9:syslog_mcu_mounted() 改为幂等 + 卷锁保护; + * v1.4 2026-10-04 王建锋 syslog_mcu_mounted() 改为幂等 + 卷锁保护; * defaultTask 每 2s 复查,建 /log 曾失败时自动重试 * (原"仅调用一次"→ 失败后 drain 永久 no-op)。 - * P2-10:s_read_raw() 纳入卷锁 try 语义,与写者 + * 读者 s_read_raw() 纳入卷锁 try 语义,与写者 * s_append 的"容量满 unlink 重建"互斥,消除 * "UI 读记录(FA_READ) 时文件被 unlink"竞态窗口。 */ @@ -63,7 +63,7 @@ static const uint32_t s_caps[SYSLOG_COUNT] = { SYSLOG_RUN_MAX, SYSLOG_SELFTEST_M #define SYSLOG_QUEUE_MAX 8u -/* N-24(2026-10-15,报告 N-16 写侧):一次排空的合并写入上限。 +/* 日志读写放大收口(2026-10-15,写侧):一次排空的合并写入上限。 * 8 条 × 64 B = 512 B 静态缓冲(不吃任务栈),与队列深度 SYSLOG_QUEUE_MAX 同值 * ——即"一轮排空最多一次 open/write/close"。 */ #define SYSLOG_BATCH_MAX 8u @@ -84,13 +84,13 @@ typedef struct { } syslog_entry_t; static syslog_entry_t s_preboot[SYSLOG_QUEUE_MAX]; /* 启动前待发数组(RTOS 前单生产者/后单消费者,免锁) */ -static syslog_entry_t s_batch_src[SYSLOG_BATCH_MAX]; /* 本批条目的原始来源(N-24:未落盘的尾部要回投队列,故必须留住 id + desc 副本); +static syslog_entry_t s_batch_src[SYSLOG_BATCH_MAX]; /* 本批条目的原始来源(未落盘的尾部要回投队列,故必须留住 id + desc 副本); * 唯一写者 defaultTask,走静态不吃栈 */ static volatile uint8_t s_preboot_cnt; /* 数组条目数 */ static osMessageQueueId_t s_queue; /* 运行期消息队列(syslog_drain 首次调用时惰性创建) */ static const osMessageQueueAttr_t s_queue_attr = { .name = "syslog" }; static volatile uint8_t s_fs_ready; /* 卷挂载 + /log 目录就绪后置位 */ -static volatile uint8_t s_mount_fail_reported; /* P2-9:建目录失败日志去重(成功复位) */ +static volatile uint8_t s_mount_fail_reported; /* 建目录失败日志去重(成功复位) */ /* B 项整改(2026-10-24):syslog_drain 批量写失败"整批回投"这条留痕的节流位—— * 只在"从正常转入故障"的那一次打一条,成功一轮后复位。与 s_append_many 内部的 * s_batch_fault 各管一层:那位压的是"这个文件写坏了",本位压的是"这批条目回投了"。 @@ -108,7 +108,7 @@ static uint8_t s_requeue_reported[SYSLOG_COUNT]; static volatile uint8_t s_post_qfull_reported; /* 队列满丢弃:已留痕? */ static volatile uint8_t s_post_pboot_full_reported;/* 启动前数组满丢弃:已留痕? */ -/* N-24(2026-10-15,报告 N-16 读侧):8 条滑动读窗口 + 写者代数。 +/* 日志读写放大收口(2026-10-15,读侧):8 条滑动读窗口 + 写者代数。 * 窗口 512 B 走静态(不吃任务栈;defaultTask/uiTask 最坏链见 stack_report.py, * 预算余量见 docs/内存管理/RAM预算台账.md)。s_rd_gen 由写者自增、读者比对, * 对齐 32 位在 ARM 上原子,跨任务无需临界区。 */ @@ -195,7 +195,7 @@ static int s_is_selfheal_code(FRESULT fr) { * p_rec - 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t * * 返回值:无 * 限定条件:时间戳未就绪时落 "1970-01-01 08:00:00" 占位串 - * 函数说明:N-24(2026-10-15,报告 N-16)从 s_append 抽出,供"单条写"与 + * 函数说明:日志读写放大收口(2026-10-15)时从 s_append 抽出,供"单条写"与 * "批量写"共用——两条路径必须产出逐字节相同的记录,否则 UI 按定长 * 字段偏移解析会错位。 */ @@ -327,7 +327,7 @@ static int s_append(syslog_id_t id, const char *desc) { * 返回值:0 - 成功;-1 - 写失败;-2 - 卷被占用(顺延,非故障); * -3 - 剩余容量不足 n 条,本批不能走快路径(交回按条慢路径做轮转) int * 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载 - * 函数说明:N-24(2026-10-15,报告 N-16 写侧)。旧实现每条记录一轮 + * 函数说明:日志读写放大收口(2026-10-15,写侧)。旧实现每条记录一轮 * f_open + f_write(64 B) + f_close,而 f_close 要回写目录项、f_write * 要对目标扇区做读-改-写 → 每条日志 2~4 次 NAND 页读写。本函数把 * 同一次排空里属于同一文件的最多 8 条合并成一次 512 B 追加写。 @@ -405,15 +405,15 @@ static int s_append_many(syslog_id_t id, const uint8_t *p_buf, uint32_t n) { * idx - 记录序号(0 起) uint32_t * p_rec- 输出缓冲(SYSLOG_REC_SIZE 字节,不可为 NULL) uint8_t * * 返回值:0 - 成功;-1 - 越界/读失败/卷忙 int - * 函数说明:P2-10(2026-10-04):读者纳入卷锁(try),与写者 s_append 的 + * 函数说明:读者纳入卷锁(try),与写者 s_append 的 * "容量满 unlink 重建"互斥——原实现读时无锁,写者 unlink 与读者 * open/read 可交错,读到的可能是半删除态文件。try 语义(timeout=0): * 本路径可由 uiTask 调用,uiTask 在 required_mask 内、须每 100ms 喂 * 心跳,绝不能阻塞等锁;busy = 本次读失败(UI 下轮刷新重读,非故障)。 * - * N-24(2026-10-15,报告 N-16 读侧):加**8 条滑动窗口**。旧实现每读 + * 日志读写放大收口(2026-10-15,读侧):加**8 条滑动窗口**。旧实现每读 * 一条记录都要 open + lseek + read + close 一轮,UI 翻 20 条记录 = - * 20 次目录项/ FAT 往返(报告 N-16 的"UI 翻记录逐条 open/seek/close")。 + * 20 次目录项/ FAT 往返(即"UI 翻记录逐条 open/seek/close"这笔开销)。 * 记录定长 64 B,故一次 open + 一次 lseek + 一次 f_read 就能取回连续 * 8 条(512 B),命中窗口时零 I/O 直接从 RAM 返回。API 与调用方 * (ui_page_runlog / ui_page_selftest 的逐行取数)一律不变。 @@ -424,7 +424,7 @@ static int s_append_many(syslog_id_t id, const uint8_t *p_buf, uint32_t n) { */ static int s_read_raw(syslog_id_t id, uint32_t idx, uint8_t *p_rec) { static FIL f; /* static:省 528 B 栈;open~close 全在卷锁区间内 - * (uiTask 与写者任务由 vol_lock 串行化,见上方 P2-10 说明) */ + * (uiTask 与写者任务由 vol_lock 串行化,见上方读者卷锁说明) */ FRESULT fr; UINT br = 0u; uint32_t total; @@ -504,8 +504,8 @@ int syslog_mcu_init(void) { * 入口参数:无 * 返回值:无 * 限定条件:defaultTask f_mount/f_mkfs 成功后调用;**可重复调用(幂等)**—— - * 产品运行期 defaultTask 每 2s 复查一次(见下 P2-9 说明) - * 函数说明:P2-9(2026-10-04):原实现"仅调用一次",若此处建 /log 失败则 + * 产品运行期 defaultTask 每 2s 复查一次(见下方幂等说明) + * 函数说明:原实现"仅调用一次",若此处建 /log 失败则 * s_fs_ready 恒 0 → syslog_drain 永久 no-op(运行期日志与自检全部 * 滞留/丢弃),且无任何重试。现改为幂等:s_fs_ready 已置位则立即 * 返回;否则重试建目录。defaultTask 在 STORAGE_READY 置位后每 2s @@ -570,7 +570,7 @@ void syslog_drain(void) { return; } - /* N-24(2026-10-15,报告 N-16 写侧):一批一批地排空——同一文件、最多 + /* 日志读写放大收口(2026-10-15,写侧):一批一批地排空——同一文件、最多 * SYSLOG_BATCH_MAX 条合并成一次 open/write/close(旧实现每条一轮 * open/write/close,每条日志 2~4 次 NAND 页读写)。 * 取数规则:preboot 数组按下标"看而不删",整批落盘成功才真正删除; @@ -831,13 +831,13 @@ int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch) { } /* - * 函数功能:把一条取证行追加到 /log/wdev.log(掉电不丢;round 7 纯取证) + * 函数功能:把一条取证行追加到 /log/wdev.log(掉电不丢;第 7 轮纯取证) * 入口参数:p_desc - 取证描述行(调用方已格式化;不可为 NULL) const char * * 返回值:0 - 成功;-1 - 卷占用或写失败(本条证据丢失,不阻断判定) int * 限定条件:poller(defaultTask)上下文调用;卷锁一律 try(绝不阻塞) * 函数说明: * 为什么必须落盘:误复位后 RAM 态随复位消失、`[WD] task stall` 行经僵尸串口 - * 可能丢失(2026-10-02 台架 round 3~6 绕圈的判据缺口),故证据必须落到 + * 可能丢失(2026-10-02 台架第 3~6 轮绕圈的判据缺口),故证据必须落到 * **掉电不丢**的盘上,复位后经 FTP RETR 回读。 * 第二条写(旧称"flush 诱导"):写完 wdev.log 后立刻 s_append(SYSLOG_RUN, ...) * 再落一条 run.log。保留理由是**多一条独立证据**(run.log 有 UI 记录页与 FTP @@ -845,7 +845,7 @@ int syslog_last_epoch(syslog_id_t id, uint32_t *p_epoch) { * disk_ioctl(CTRL_SYNC) 已经调 ftl_flush_cache() 把 wdev.log 的脏页刷进 NAND * (nand_ftl.c 的 CTRL_SYNC 分支),所以"写另一个文件(不同 LPN)逼出前一脏页" * 从来不是必要条件。 - * ⚠ 2026-10-02 round 3~8 那次"复位后写入整段丢失"的根因是 dhara resume 锚错 + * ⚠ 2026-10-02 台架第 3~8 轮那次"复位后写入整段丢失"的根因是 dhara resume 锚错 * checkpoint 使 journal 整段回退(docs/架构设计/STM32F4-Base存储架构说明.md * §5.4.1),不是这层缓存:单页缓存最多丢最后未跨页那笔(≤512 B),丢不出 * "整段"。空闲周期 flush 兜底已接线(App/fs/vol_idle_sync.h:STORAGE_READY diff --git a/App/protocol/iec104/iec104_asdu.h b/App/protocol/iec104/iec104_asdu.h index da1a6e68..dc5063b1 100644 --- a/App/protocol/iec104/iec104_asdu.h +++ b/App/protocol/iec104/iec104_asdu.h @@ -2,7 +2,7 @@ * 模块名称:IEC 60870-5-104 ASDU 编解码(应用层,纯逻辑) * 模块功能:104 规约应用服务数据单元(ASDU)的类型/传送原因常量、组帧与解析。 * **零 I/O、零平台依赖**(仅 stdint.h)→ 主机侧 L0.5 可离线单测 - * (登记簿 A-4 约束 1"协议解析与硬件 I/O 分离")。 + * (分层约束:协议解析与硬件 I/O 分离)。 * 口径来源:docs/算法/继电保护功能复现规划.md §1.5(B0 产品定义)—— * 公共地址 2 字节、传送原因 2 字节、信息体地址 IOA **3 字节**(101 为 2 字节); * 遥测上送**短浮点** M_ME_NC_1(系数 = 1);遥信双点上双点、其余单点; diff --git a/App/protocol/iec104/iec104_link.h b/App/protocol/iec104/iec104_link.h index c150876c..8445116a 100644 --- a/App/protocol/iec104/iec104_link.h +++ b/App/protocol/iec104/iec104_link.h @@ -9,7 +9,7 @@ * → clock_sync 回调)、试验(C_TS)与进程复位(C_RP,清快照)。 * **零 I/O**:收 = on_rx 喂 TCP 流片段,发 = send_apdu 回调, * 时基 = 调用方注入 now_ms(tick ms,回绕安全:差值法) - * → 全栈(asdu+pts+link)主机侧 L0.5 可离线跑(登记簿 A-4 约束 1)。 + * → 全栈(asdu+pts+link)主机侧 L0.5 可离线跑(分层约束)。 * 参数(DL/T 634.5104 标准默认,§1.5 未另定):k=12 w=8 t1=15s t2=10s t3=20s。 * 端口:TCP **2404**(§1.5 B0 定义;本 DTU = 被控站/服务器,主站连入)。 * 线程约定:单实例单任务上下文(板端 = 104 服务在 netTask 消息循环内驱动, diff --git a/App/protocol/iec104/iec104_pts.c b/App/protocol/iec104/iec104_pts.c index d264e2ac..8cd3eda8 100644 --- a/App/protocol/iec104/iec104_pts.c +++ b/App/protocol/iec104/iec104_pts.c @@ -14,7 +14,7 @@ * 2026-10-25 DTU 项目组 卡 9/卡 11(「数据可信」,道D):YC 板端源改读 * L2 遥测快照(App/algo/meas.h,meas_get 无锁双区); * 削顶五路由快照品质 CLIPPED 驱动线上 IV(不再绑死 -1, - * V0.2/N-18 零改动自动启用);0x401d 绑频率源(卡 11); + * V0.2 消除双转换器无共同采样触发后零改动自动启用);0x401d 绑频率源(卡 11); * 死区改运行时副本 + set/get 入口;COV 遥测腿 = 死区 * 连续 2 点确认(板端按快照 seq 去重)+ 品质翻转即报。 * 单测 = test/host/iec104 TC-IEC104-025~028。 @@ -52,8 +52,8 @@ typedef struct { * 频率点不适用"额定百分比"(额定附近 0.5 Hz 与旧值同量级,维持 0.5 并随 * 卡 10 运行参数段可调)。自卡 9 起本表**只作工厂默认载体**:生效死区 = * App/algo/run_par.c 的 [run_par] 引擎副本(卡 10 真源收编,本层不再存副本);削顶五路不再 sem=-1 绑死,而是绑快照槽、由品质 CLIPPED - * 驱动线上 IV——V0.2(N-18)削顶消失后点位零改动自动启用(docs/任务规划/ - * 数据可信.md §10 的 N-18 行)。 + * 驱动线上 IV——V0.2 削顶消失后点位零改动自动启用(docs/任务规划/ + * 数据可信.md §10 的「双转换器无共同采样触发」行)。 * ⚠ 过渡期(eng=0 引脚电压域,卡 12 标定前):上送值本身非二次值,死区 * 数字按本域偏保守(迟报不漏报);标定落地后复核(「数据可信」§2.3 表 * 第 2 行)。判定 = 死区连续 2 点确认(卡 9),见 iec104_pts.h COV 说明。 */ diff --git a/App/protocol/iec104/iec104_pts.h b/App/protocol/iec104/iec104_pts.h index a8fb1871..db35a6f9 100644 --- a/App/protocol/iec104/iec104_pts.h +++ b/App/protocol/iec104/iec104_pts.h @@ -3,13 +3,13 @@ * 模块功能:104 点表(遥测 YC / 遥信 YX / 遥控 YK / 定值)的定义、读取与变化检测 * (COV:遥信变位 + 遥测死区)。**数据源经 vtable 注入**:板端绑 * phasor/prot_elm/prot_set 真实源;主机单测绑假源 → 协议栈全链 - * (asdu+pts+link)可离线跑(登记簿 A-4 约束 1)。 + * (asdu+pts+link)可离线跑(分层约束:协议解析与硬件 I/O 分离)。 * 点表口径(docs/算法/继电保护功能复现规划.md §1.5,验收 = 省电科院点表): * - YC:IOA 0x4011~0x4018 + 0x401d(频率),短浮点、系数 1;数据源 = * L2 遥测快照(App/algo/meas.h,「数据可信」卡 9/11):电气 8 路绑快照槽, * 有效性由快照品质驱动——V0.1 削顶路(docs/算法/继电保护功能复现规划.md - * §1.6)快照置 CLIPPED → 本层映射为线上 IV(V0.2/N-18 后自动启用,绑定零 - * 改动,见 docs/任务规划/数据可信.md §10 行 N-18);0x401d 绑频率源, + * §1.6)快照置 CLIPPED → 本层映射为线上 IV(V0.2 后自动启用,绑定零 + * 改动,见 docs/任务规划/数据可信.md §10「双转换器无共同采样触发」行);0x401d 绑频率源, * freq_valid()==0 → IV; * - YX:IOA 1~33(§1.5 表(3));双点 = 开关位置类(YX1~4,本期无硬件 → IV), * 保护动作类 YX15~23 映射 prot_elm 状态,闭锁类 YX31~33 映射 prot_set; diff --git a/App/sync/time_sync.c b/App/sync/time_sync.c index d9f9b57b..ad13fcf5 100644 --- a/App/sync/time_sync.c +++ b/App/sync/time_sync.c @@ -10,10 +10,10 @@ * v1.1 2026-08-28 王建锋 改为 TCP Server,迁入独立任务 timeSyncTask, * 所有 socket 操作经 net_socket 线程安全 API 走消息队列, * netTask 仅负责 net_poll/net_process_messages,多任务并发安全 - * v1.2 2026-10-02 王建锋 round 8b 消抖:等待片长 200ms→50ms,并把各处长 + * v1.2 2026-10-02 王建锋 第 8b 轮消抖:等待片长 200ms→50ms,并把各处长 * 等待(500/2000/200ms)统一走 time_sync_delay_fed() * 按 ≤50ms 分片、每片喂心跳——消除"心跳滑出两个连续 - * 100ms 抽样窗"的节拍抖动(round 7 台架 snap=0x17) + * 100ms 抽样窗"的节拍抖动(第 7 轮台架 snap=0x17) */ #include @@ -33,10 +33,10 @@ #define TIME_SYNC_PORT 8888 /* - * E-02 头阻塞修复 + L3 pending 化(timeSync 侧;lftpd 侧见 lftpd_inet.c): + * 头阻塞修复 + L3 pending 化(timeSync 侧;lftpd 侧见 lftpd_inet.c): * 旧实现用单次 30s 阻塞 net_recv(默认 NET_RECV_TIMEOUT_MS)等时间包——等待期间 * netTask(全部 socket 操作的唯一执行者)被独占,其余任务的网络操作(远端显示 / - * FTP)整体停摆(历史审计 E-02,属主文档已删除)。 + * FTP)整体停摆(历史审计遗留问题)。 * 2026-09-22 先落地分片有界读(5ms 片),同日 L3 pending 化后改为"挂起请求": * 每次等待 = 一次挂起登记(netTask 零占用,完成按主循环节拍检查),调用方 * 单次超时 = 剩余总预算(总等待 ≤ TIME_SYNC_TOTAL_BUDGET_MS,语义与旧 30s @@ -47,7 +47,7 @@ /* 看门狗分片等待:单次 net_recv_to 的等待片长上限。须显著小于看门狗抽样窗 * (task_watchdog.c WD_POLL_PERIOD_MS=100ms)——片长 ≤50ms 时每片前喂的心跳 * 必然落在同一个 100ms 抽样窗内,消除"心跳恰好滑过两个连续窗口"的边界抖动 - * (round 7 台架稳态 snap=0x17 即抽样窗 vs 心跳节拍的良性错位,round 8b 消抖)。 + * (第 7 轮台架稳态 snap=0x17 即抽样窗 vs 心跳节拍的良性错位,第 8b 轮消抖)。 * 语义上仅把一次长等待拆成多次短等待,总预算不变。 */ #define TIME_SYNC_WAIT_SLICE_MS 50U @@ -59,7 +59,7 @@ * 函数说明:看门狗抽样窗为 100ms(task_watchdog.c WD_POLL_PERIOD_MS)。把任意长 * 等待切成 ≤TIME_SYNC_WAIT_SLICE_MS 的短片、每片前喂心跳,使心跳必然 * 落在单个抽样窗内——语义等价于一次长延时,仅把"心跳节奏"钉在窗内 - * (round 8b 消抖;不改变总延时预算)。 + * (第 8b 轮消抖;不改变总延时预算)。 */ static void time_sync_delay_fed(uint32_t ms) { while (ms > 0U) { @@ -180,7 +180,7 @@ static void time_sync_handle_conn(int conn_sock) { DBG_INFO("time_sync: wall clock updated (epoch=%lu)", (unsigned long)epoch); } } else { - /* E-02 收口(docs/网络/网络自愈架构设计.md §5.2 E13):本层放弃会话 → 主动拆除。 + /* 头阻塞收口(docs/网络/网络自愈架构设计.md §5.2 E13):本层放弃会话 → 主动拆除。 * 挂起但 TCP 仍活的客户端(PC 内核自动 ACK keepalive,芯片僵尸判不死) * 会永久占住单连接监听;EOF 路径 drop 幂等返 0(net 层已 relisten)。 * 模拟器多连接后端 conn≠ls,由 sim 实现按数据 fd 关闭。 */ diff --git a/App/task/adc_task.c b/App/task/adc_task.c index 36814874..4218ddf8 100644 --- a/App/task/adc_task.c +++ b/App/task/adc_task.c @@ -28,7 +28,7 @@ * 创建日期:2026-07-19 * 修改记录: * v1.0 2026-07-19 王建锋 创建初始版本 - * v1.1 2026-10-05 王建锋 P3-11:注明"预留桩(未启用)",避免误认为已实现 + * v1.1 2026-10-05 王建锋 注明"预留桩(未启用)",避免误认为已实现 * v2.0 2026-10-10 王建锋 B1:实现真实消费者(1ms 节拍 + 原子快照 + 帧统计 + * 看门狗心跳),由 Src/freertos.c 创建为 adcTask * v2.1 2026-10-10 王建锋 日志门控:首帧/周期统计仅在 dbg_console_writer_ready() @@ -75,7 +75,7 @@ * 单调性/错误计数增量)——严重事件与故障电平上升沿 * 打 DBG_ERROR + 每类每次开机一次 SYSLOG_SELFTEST; * 恢复打 DBG_INFO;心跳日志增 tgap(TPAFE 侧丢帧, - * 与 adc1_gap 对称)。判定不在本文件(A-4:算法与 + * 与 adc1_gap 对称)。判定不在本文件(算法与 * I/O 分离),本文件只做"消费计数 → 告警"的编排 * v3.0 2026-10-21 王建锋 修复"任务初始化入口从未被调用":adc_task_func 进入 * 循环之前先调 adc_task_init()。原实现下判定层状态靠 diff --git a/App/task/default_task.c b/App/task/default_task.c index 5be4142b..34991ea8 100644 --- a/App/task/default_task.c +++ b/App/task/default_task.c @@ -2,7 +2,7 @@ * 模块名称:Default Application Task * 模块功能:主控任务(defaultTask)任务体实现: * ① 启动段:存储 bring-up(挂载 + 空卷自动建卷 + 卷健康探针两击 + - * 自愈重格式化,E-31 失败后主循环 3×2s 有界重试)+ 网络配置加载 + * 自愈重格式化,挂载失败后主循环 3×2s 有界重试)+ 网络配置加载 * + STORAGE_READY 屏障 + "保存网络配置"回调注册 * ② 控制台单写者:dbg_console_writer_attach 注册 + 主循环 dbg_console_drain * 排空(串口唯一发送者,见 App/log/console_log.c 设计说明) @@ -65,7 +65,7 @@ * 2026-10-12(B5):堆扩至 56KB(.ioc configTOTAL_HEAP_SIZE=57344,CCM 腾给 * protectTask/iec104Task 栈),阈值**不随堆缩放**——它是绝对字节安全线。 */ #define HEAP_LOW_WATER_B 3072u -/* E-31 自愈策略(2026-09-21 审计 P0 修复):启动期存储挂载失败后, +/* 存储自愈策略(2026-09-21 审计 P0 修复):启动期存储挂载失败后, * 主循环按 2s 周期重跑完整 bring-up 的次数上限;耗尽则维持无存储降级 * (自检已上报、卷保留、不重格式化),等下次上电重新 resume */ #define STORAGE_BRINGUP_RETRIES 3u @@ -106,7 +106,7 @@ static FRESULT storage_bring_up(FATFS *fs); * 只在内核运行期被调度,启动段允许使用 CMSIS 对象 * 函数说明:启动段 = 存储 bring-up + 保存回调注册 + 控制台写者注册; * 主循环 = 事件等待(配置保存/控制台待发,50ms 兜底)→ 控制台排空 - * → 配置保存执行 → 2s 配置轮询(含 E-31 有界重试)→ LED 心跳 + * → 配置保存执行 → 2s 配置轮询(含存储自愈有界重试)→ LED 心跳 * → syslog 落盘 → 栈水位观测 */ void default_task_func(void *argument) { @@ -121,7 +121,7 @@ void default_task_func(void *argument) { * 完整 bring-up 序列(挂载 + 空卷自动建卷 + 健康探针 + 自愈重格式化 * + 日志挂载 + 配置加载 + 就绪屏障)实现于 storage_bring_up()。 * - * E-31(2026-09-21 审计,P0 修复):dhara_map_resume 失败不再被当 + * 存储自愈(2026-09-21 审计,P0 修复):dhara_map_resume 失败不再被当 * "新卷"静默重格式化(扫描中任何一次瞬态毛刺都会销毁全部用户数据)—— * nand_ftl 仅把 DHARA_E_TOO_BAD(真空白)判为新卷,其余失败使 * disk_initialize 失败 → f_mount 失败 → 下方自检上报并保留卷; @@ -131,7 +131,7 @@ void default_task_func(void *argument) { * 小写两击确认、FR_INVALID_NAME 不入重格式化集、写保护类只上报, * 详见该函数注释)。 */ #if !defined(TEST_SUITE_STORAGE) - /* E-31 自愈:存储 bring-up 剩余重试次数(0 = 已就绪或已耗尽); + /* 存储自愈:存储 bring-up 剩余重试次数(0 = 已就绪或已耗尽); * 须在启动块之前声明(启动块失败路径写入、主循环读取) */ uint32_t storage_retry = 0u; #endif @@ -146,12 +146,12 @@ void default_task_func(void *argument) { if (fr != FR_OK) { /* 存储不可写:自检故障仅能滞留待发队列(串口已告警) */ syslog_post(SYSLOG_SELFTEST, "filesystem fault"); - /* E-31 自愈:启动主循环中的有界重试(每 2s 一次) */ + /* 存储自愈:启动主循环中的有界重试(每 2s 一次) */ storage_retry = STORAGE_BRINGUP_RETRIES; } /* 注册"保存网络配置"请求通知:UI(uiTask)投递 → 置事件 → 本任务立即执行写盘 - * (配置引擎属主 = 本任务;UI 保存不再直接触碰配置文件引擎,P1-3) */ + * (配置引擎属主 = 本任务;UI 保存不再直接触碰配置文件引擎) */ net_cfg_set_save_req_cb(default_task_net_cfg_save_notify); #else /* STORAGE 套件构建:挂载/配置引擎/日志盘写全部跳过,存储由 testTask 独占 */ @@ -177,9 +177,9 @@ void default_task_func(void *argument) { task_wd_start(); for (;;) { - /* round 8 取证:进段打点——每步前记录段号到 .noinit 槽(复位不丢)。 + /* 第 8 轮取证:进段打点——每步前记录段号到 .noinit 槽(复位不丢)。 * 误复位后开机 `PREVRUN seg=N` 即"上一轮主循环死在哪个调用点" - * (round 7 初判"poller 自身停摆"经 round 8b 更正:真因是 IWDG 超时被静默 + * (第 7 轮初判"poller 自身停摆"经第 8b 轮更正:真因是 IWDG 超时被静默 * 配成复位默认值,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。 */ task_wd_seg(TASK_WD_SEG_POLL); /* 心跳抽样与喂狗判定(内部节流至 100ms;全员到位才喂,否则累计停喂) */ @@ -240,7 +240,7 @@ void default_task_func(void *argument) { if ((cfg_poll_cnt++ % 40u) == 0u) { #if !defined(TEST_SUITE_STORAGE) task_wd_seg(TASK_WD_SEG_CFGPOLL); - /* E-31 自愈:启动期挂载失败时,每 2s 重跑完整 bring-up—— + /* 存储自愈:启动期挂载失败时,每 2s 重跑完整 bring-up—— * 瞬态毛刺(如 dhara resume 扫描的瞬时 SPI 故障)通常第二次 * 即恢复;成功后由 storage_bring_up 补跑启动时跳过的 * "存储就绪"序列(日志挂载/配置加载/屏障置位,均幂等); @@ -257,7 +257,7 @@ void default_task_func(void *argument) { syslog_post(SYSLOG_SELFTEST, "filesystem fault"); } } - /* P2-9:卷已就绪(STORAGE_READY)但日志后端未就绪(建 /log 曾失败, + /* 卷已就绪(STORAGE_READY)但日志后端未就绪(建 /log 曾失败, * s_fs_ready 恒 0 → syslog_drain 永久 no-op)→ 2s 周期重试(幂等; * 就绪后 syslog_mcu_mounted 立即返回,仅一次 volatile 读)。 */ if (sys_evtHandle != NULL @@ -410,7 +410,7 @@ static void stack_probe_poll(void) { * 下整体不参与编译(卷由 testTask 独占) * 函数说明: * 由 defaultTask 调用:启动期一次;若失败,主循环每 2s 重试至多 - * STORAGE_BRINGUP_RETRIES 次(E-31 自愈策略,2026-09-21):dhara_map_resume + * STORAGE_BRINGUP_RETRIES 次(存储自愈策略,2026-09-21):dhara_map_resume * 失败分类修复后(nand_ftl.c 仅真空白判新卷,其余失败保留卷并返回 * disk_initialize 失败),重试让瞬态毛刺(SPI 瞬时故障/扫描窗口抖动) * 在数秒内免重启恢复,成功时补跑启动期跳过的就绪序列(日志挂载/配置 diff --git a/App/task/iec104_serv.c b/App/task/iec104_serv.c index 2497f103..37cc3af5 100644 --- a/App/task/iec104_serv.c +++ b/App/task/iec104_serv.c @@ -3,7 +3,7 @@ * 模块功能:2404 监听 + 单连接 socket 收发 + 把字节流喂给 iec104_link 纯逻辑 * 层;对时经 sys_clock_set_unix 落墙钟;数据源走 iec104_pts 默认源 * (phasor / prot_elm / prot_set)。协议语义全部在 App/protocol/iec104, - * 本文件只做 I/O 编排(A-4 约束 1)。 + * 本文件只做 I/O 编排(分层约束)。 * 线程约定:iec104Task 独占(NET_READY 屏障后循环,100ms 节拍 + 看门狗心跳)。 * 创建日期:2026-10-12(B4) */ @@ -33,7 +33,7 @@ /* 兜底节拍:门控判"无数据"时每 32 轮无条件收一次(ui_net_sink RM 先例, * 防 RECV_OK 事件未收到把数据永久藏住);须为 2 的幂 */ #define IEC104_LIVENESS_DIV 32u -/* 连续发送失败阈值:同 ui_net_sink E-04(短写=对端收坏帧) */ +/* 连续发送失败阈值:同 ui_net_sink 先例(短写=对端收坏帧) */ #define IEC104_SEND_FAIL_MAX 3u /* ---------------- 服务状态(iec104Task 独占) ---------------- */ @@ -59,7 +59,7 @@ static const iec104_link_cfg_t s_cfg = { 1u, s_send_apdu, s_clock_sync, s_epoch_ * 入口参数:ctx 未用;apdu/len = 完整 APCI 帧 * 返回值:无 * 限定条件:s_client >= 0(在 link 调用栈内执行,即本任务上下文) - * 函数说明:短写/失败累计(E-04 先例);连续 3 次 → 置 s_conn_dead, + * 函数说明:短写/失败累计(ui_net_sink 先例);连续 3 次 → 置 s_conn_dead, * 由 s_service_link 在 link 调用返回后统一拆链(严禁在回调里拆) */ static void s_send_apdu(void *ctx, const uint8_t *apdu, uint16_t len) { @@ -191,7 +191,7 @@ static void s_service_link(uint32_t now) { * 返回值:无 * 限定条件:NET_READY 已置位;s_cfg/s_link 已 init * 函数说明:门控(net_listener_armed/net_can_read 纯 RAM 读)+ 兜底节拍照 - * ui_net_sink I-1/I-2;EOF 时 client==listen 严禁 close(net 层 + * ui_net_sink 先例;EOF 时 client==listen 严禁 close(net 层 * 已 auto_relisten,close 会连监听一起毁) */ static void s_serv_poll(void) { diff --git a/App/task/net_task.c b/App/task/net_task.c index 7d093f06..461cd5e8 100644 --- a/App/task/net_task.c +++ b/App/task/net_task.c @@ -11,12 +11,12 @@ * v1.2 2026-07-21 王建锋 网络初始化移入本任务(net_init + TCP 监听) * v1.3 2026-07-22 王建锋 单连接模式重构,增加大文件/边界/KeepAlive 测试 * v1.4 2026-09-14 王建锋 新增自检埋点:PHY 链路 3s 未建立 → sys_log "comms fault" - * v1.5 2026-09-15 王建锋 配置加载移交 defaultTask(配置引擎属主,P1-3); + * v1.5 2026-09-15 王建锋 配置加载移交 defaultTask(配置引擎属主); * 本任务只读 RAM 副本 - * v1.6 2026-09-15 王建锋 P1-6:入口加 CH395F 硬件层测试模式自挂起守卫 + * v1.6 2026-09-15 王建锋 入口加 CH395F 硬件层测试模式自挂起守卫 * (基础线程无条件创建,该模式下不运行); * freertos.c USER CODE 区重复创建已删除 - * v1.7 2026-10-02 王建锋 P1-8:初始组网失败/重建失败 → 自检记录 + 30s 周期重试 + * v1.7 2026-10-02 王建锋 初始组网失败/重建失败 → 自检记录 + 30s 周期重试 * (原实现:初始失败 = netTask 无限驻留;重建失败 = 静默丢配置) * v1.8 2026-10-10 王建锋 任务耗时测量埋点:net_poll() 单独计时—— * 本任务每 ~1ms 分派一次消息队列,若驱动轮询被拉长, @@ -52,7 +52,7 @@ * 函数说明:只置事件位;真正的重建在主循环内完成(CH395F SPI 归 netTask 独占, * UI 不得直接操作) */ -/* P1-8(2026-10-02):重建失败延迟重试的截止 tick;0 = 无待重试 */ +/* 重建失败延迟重试的截止 tick;0 = 无待重试 */ static uint32_t s_reinit_retry_at; static void net_reconfig_request_cb(void) { @@ -186,7 +186,7 @@ void net_task_func(void const *arg) { * 2026-10-02 台架修复:10s 超时等待改 100ms 分片 + 每片喂心跳——原实现 * 在等待期无心跳:若存储 bring-up 失败,本等待跑满 10s 超时,而 IWDG * 此时已启动(defaultTask 在 bring-up 后即启动),NET 位长期缺失 → - * 必需掩码不可达 → 复位循环,E-31 的 2s 自愈重试根本没有机会。 + * 必需掩码不可达 → 复位循环,存储自愈的 2s 重试根本没有机会。 * 分片喂位保证"存储未就绪"降级态下 NET 位持续新鲜。 */ uint32_t rounds_left = 100u; /* 100 × 100ms = 原 10s 超时语义 */ for (;;) { @@ -202,7 +202,7 @@ void net_task_func(void const *arg) { } } - /* P1-8(2026-10-02):初始组网失败不自驻留——自检台账留痕 + 30s 周期重试 + /* 初始组网失败不自驻留——自检台账留痕 + 30s 周期重试 * (1s 分片、每片喂心跳):线缆/芯片恢复即可当场完成组网;原 for(;;)osDelay * 会令看门狗 NET 位持续陈旧,并阻塞全部下游就绪屏障 */ for (;;) { @@ -235,7 +235,7 @@ void net_task_func(void const *arg) { /* UI 保存网口配置后请求立即应用:在此重建网络(新 IP/掩码/网关/MAC)。 * 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。 - * P2-11(2026-10-04):用 osEventFlagsWait(timeout=0) 原子"取走并清除"RECONFIG + * 用 osEventFlagsWait(timeout=0) 原子"取走并清除"RECONFIG * 位,替换原 Get+Clear 两步;原写法在 Get 与 Clear 之间若有生产者在同一位上 * Set,该次请求会被 Clear 一并吞掉(丢事件窗口)。 */ if (sys_evtHandle != NULL) { @@ -247,7 +247,7 @@ void net_task_func(void const *arg) { (void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY); s_reinit_retry_at = 0U; } else { - /* P1-8(2026-10-02):重建失败 → 自检留痕 + 30s 延迟重试(重事件位), + /* 重建失败 → 自检留痕 + 30s 延迟重试(重事件位), * 新配置不再静默丢失(原实现仅一条 DBG_ERROR 后丢弃) */ DBG_ERROR("netTask: re-init FAIL, retry in 30s"); (void)syslog_post(SYSLOG_SELFTEST, "net reinit fail"); @@ -255,7 +255,7 @@ void net_task_func(void const *arg) { } } else if ((s_reinit_retry_at != 0U) && ((int32_t)((uint32_t)HAL_GetTick() - s_reinit_retry_at) >= 0)) { - /* P1-8:30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */ + /* 30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */ s_reinit_retry_at = 0U; (void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG); } diff --git a/App/task/net_task.h b/App/task/net_task.h index 98e75e88..c56c38ac 100644 --- a/App/task/net_task.h +++ b/App/task/net_task.h @@ -8,7 +8,7 @@ * v1.0 2026-07-19 王建锋 创建初始版本 * v1.1 2026-08-24 王建锋 提取 net_stack_bring_up() 供硬件层/NET 层两种测试模式共用 * v1.2 2026-09-15 王建锋 硬件层模式描述修正:netTask 恒创建(基础线程), - * 该模式下自挂起不运行(P1-6 守卫) + * 该模式下自挂起不运行(入口自挂起守卫) * v1.3 2026-10-21 王建锋 删除 net_task_init() 声明(空壳入口、无调用点) */ diff --git a/App/task/protect_task.c b/App/task/protect_task.c index 6caa4abc..7ca019fe 100644 --- a/App/task/protect_task.c +++ b/App/task/protect_task.c @@ -84,7 +84,7 @@ static uint8_t s_prev_cal_lock; /* 上拍未标定闭锁态(边沿日志用 * 返 回 值:1 - 闭锁(判据停走);0 - 放行 uint8_t * 限定条件:仅本任务上下文(adc_cal_flags 纯读,任意上下文可读) * 函数说明:判据 = UA/UN/IA 三路 VALID **全**置位——恰好是 V0.1 唯一有效的 - * 三个测量通道(N-18);UB/UC/IB/IC/IN 即使将来标定也另受 CLIPPED + * 三个测量通道(双转换器无共同采样触发);UB/UC/IB/IC/IN 即使将来标定也另受 CLIPPED * 品质约束(M5 快照层处理),不掺和进本闭锁源。VALID 随 cfg * load/poll 与卡 12 标定自动翻转,本函数无自有状态(每拍现查, * 无陈旧标志位)。台架调试旁路 = 编译期 -DDTU_BENCH(§4.3 决策 1 diff --git a/App/task/rs485_task.c b/App/task/rs485_task.c index 494561bd..8572aed6 100644 --- a/App/task/rs485_task.c +++ b/App/task/rs485_task.c @@ -9,7 +9,7 @@ * 创建日期:2026-07-19 * 修改记录: * v1.0 2026-07-19 王建锋 创建初始版本 - * v1.1 2026-10-05 王建锋 P3-11:注明"预留桩(未启用)",避免误认为已实现 + * v1.1 2026-10-05 王建锋 注明"预留桩(未启用)",避免误认为已实现 */ #include "rs485_task.h" diff --git a/App/task/ui_task.c b/App/task/ui_task.c index bd2fca41..35b2867c 100644 --- a/App/task/ui_task.c +++ b/App/task/ui_task.c @@ -25,7 +25,7 @@ * (2026-10-25 实测 bit4 8 连击、uisn=154ms 仍在段内; * 按位连击判据 = docs/子系统设计/看门狗设计说明.md §5.3)。 * 单个往返自身仍无上界——该上界与彻底解耦(事件驱动 - * accept)归登记簿 E-27/E-28(批 3),本票只做心跳续命。 + * accept)归登记簿「net 层跨上下文收口」(批 3),本票只做心跳续命。 */ #include "ui_task.h" @@ -295,7 +295,7 @@ void ui_task_func(void *argument) { * 一次心跳,救不了 → 彻底解耦须走事件驱动 accept,见文件头修改记录 v1.3。 * v1.4 补充:sink 内每个往返**返回点**已各自补喂(set_beat_cb 注入), * 「单轮多个慢往返之和」不再构成缺席链;剩单往返 >800ms 才报——那是 - * netTask 自身停摆,按位判据抓对了人(上界归 E-27/E-28)。 */ + * netTask 自身停摆,按位判据抓对了人(上界归「net 层跨上下文收口」)。 */ task_wd_beat(TASK_WD_UI); /* 推流轮询段单独 enter/exit:它内部经 net_accept → net_wait_result 走到 * xTaskNotifyWait(portMAX_DELAY) 的**无限期阻塞**。若 uiTask 永久卡在这里, diff --git a/App/util/sha256.c b/App/util/sha256.c index 72c0c86d..aadda773 100644 --- a/App/util/sha256.c +++ b/App/util/sha256.c @@ -5,7 +5,7 @@ * 作者:王建锋 * 创建日期:2026-10-11 * 修改记录: - * v1.0 2026-10-11 王建锋 创建初始版本(N-19 FTP 鉴权;标准向量由 + * v1.0 2026-10-11 王建锋 创建初始版本(FTP 鉴权;标准向量由 * test/host/sha256 主机单测钉死) */ diff --git a/App/util/sha256.h b/App/util/sha256.h index dacc7025..86244830 100644 --- a/App/util/sha256.h +++ b/App/util/sha256.h @@ -7,7 +7,7 @@ * 作者:王建锋 * 创建日期:2026-10-11 * 修改记录: - * v1.0 2026-10-11 王建锋 创建初始版本(N-19 FTP 鉴权:[ftp] pwd_hash 校验) + * v1.0 2026-10-11 王建锋 创建初始版本(FTP 鉴权:[ftp] pwd_hash 校验) */ #ifndef SHA256_H diff --git a/App/util/task_watchdog.c b/App/util/task_watchdog.c index 8b0ef43f..44ed6c3f 100644 --- a/App/util/task_watchdog.c +++ b/App/util/task_watchdog.c @@ -8,17 +8,17 @@ * 创建日期:2026-09-29 * 修改记录: * v1.0 2026-09-29 王建锋 创建:心跳位图 + 抽样判定 + IWDG 延后启动 - * v1.1 2026-10-02 王建锋 round 7 纯取证埋点:近 8 次抽样位图环 + 最大喂狗 + * v1.1 2026-10-02 王建锋 第 7 轮纯取证埋点:近 8 次抽样位图环 + 最大喂狗 * 间隔统计 + 停喂现场落 /log/wdev.log(掉电不丢); * 全部在 TASK_WATCHDOG_UNIT_TEST 守卫内,判定状态机 * 与主机侧单测逐字不变(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录) - * v1.2 2026-10-02 王建锋 round 8 取证:复位不丢的"进段打点"(.noinit 槽 + - * PREVRUN 开机上报 + STALL 行附 seg)——round 7 结论为 + * v1.2 2026-10-02 王建锋 第 8 轮取证:复位不丢的"进段打点"(.noinit 槽 + + * PREVRUN 开机上报 + STALL 行附 seg)——第 7 轮结论为 * "poller 自身停摆",需定位 defaultTask 主循环阻塞点 - * v1.3 2026-10-02 王建锋 round 8b 取证:第二个 .noinit 槽"时间源存活性" + * v1.3 2026-10-02 王建锋 第 8b 轮取证:第二个 .noinit 槽"时间源存活性" * (TIM7 ISR 每 1ms 写 uwTick/FreeRTOS tick/计数)+ * TICKRUN 开机上报(含相对进段时刻的增量 duw/dfr)—— - * round 8a 定位到 console 通道后,需钉死是 TIM7(HAL tick) + * 第 8a 轮定位到 console 通道后,需钉死是 TIM7(HAL tick) * 还是 SysTick(FreeRTOS tick) 停摆(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录) * * v1.5 2026-10-25 DTU 项目组 **停喂判定按位分账**(大窗判读 F2):stall/停喂的唯一 @@ -35,7 +35,7 @@ * HAL_IWDG_Init() 时,一并丢掉了**唯一**写这两只寄存器的 * 路径 → 实机停在复位默认值 PR=0/RLR=4095(≈349~512ms), * 比设计短约 5 倍 → 800ms 容忍分支成死代码、任何 >400ms - * 心跳缺口直接 IWDG 误复位(round 3~8 全部"空 STOR 必 + * 心跳缺口直接 IWDG 误复位(第 3~8 轮全部"空 STOR 必 * 复位"的共同根因)。SWD 现场改回设计值后同一用例 100% 通过 * * 设计依据:docs/子系统设计/看门狗设计说明.md @@ -64,7 +64,7 @@ #include "FreeRTOS.h" /* xTaskGetCurrentTaskHandle / TaskHandle_t(FTL 心跳路由) */ #include "task.h" #include "lftpd.h" /* lftpd_task_wd_beat() 弱钩子声明(本文件提供强符号,见下方) */ -#include "sys_log.h" /* SYSLOG_RUN(flush 诱导落盘)+ sys_log_evidence()(round 7 取证落盘) */ +#include "sys_log.h" /* SYSLOG_RUN(flush 诱导落盘)+ sys_log_evidence()(第 7 轮取证落盘) */ #include "console_log.h" /* dbg_console_tx_fault()(僵尸通道计数) */ /* 测试构建可见性(2026-10-25 台架实测缺陷):wd_required_mask() 里那条 * ENABLE_HW_LAYER_TESTS 分支要按"硬件层独占测试构建"剔除 netTask,而该宏定义在 @@ -134,7 +134,7 @@ static uint32_t s_started; /* 1 = IWDG 已启动 */ static uint32_t s_last_tick; /* 上次抽样时刻(100ms 节流基准) */ static uint32_t s_stall_evidenced; /* 1 = 本停摆 episode 已落盘证据(门闩;见 task_wd_poll) */ -/* ==== round 7 纯取证状态(仅 MCU 构建;判定状态机逐字不变)==== +/* ==== 第 7 轮纯取证状态(仅 MCU 构建;判定状态机逐字不变)==== * 设计背景见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录:误复位后 RAM 态随复位消失, * 故把"停喂现场"快照落盘(/log/wdev.log)供复位后回读定性。 */ #ifndef TASK_WATCHDOG_UNIT_TEST @@ -145,7 +145,7 @@ static uint32_t s_last_feed_tick; /* 上次喂狗时刻(ms) * static uint32_t s_max_feed_gap; /* 观测到的最大喂狗间隔(ms) */ static uint32_t s_feed_seen; /* 1 = 已记录首个喂狗时刻 */ -/* ==== round 8 取证:复位不丢的"进段打点"槽(.noinit → SRAM 保留)==== +/* ==== 第 8 轮取证:复位不丢的"进段打点"槽(.noinit → SRAM 保留)==== * IWDG/引脚复位不改变 SRAM 内容,故复位后仍能读出"上一轮 defaultTask 主循环 * 最后走到哪一段"。magic 用于区分"本模块写过"与上电时的随机值。 * 布局:[0]=magic [1]=段号 [2]=进段时刻(ms) [3]=循环序号 @@ -155,7 +155,7 @@ static uint32_t s_feed_seen; /* 1 = 已记录首个喂狗时 #define WD_SEG_MAGIC 0x57445347u /* 'WDSG' */ __attribute__((section(".noinit"), zero_init)) volatile uint32_t s_wd_seg_trail[4]; -/* ==== round 8b 取证:复位不丢的"时间源存活性"槽(.noinit → SRAM 保留)==== +/* ==== 第 8b 轮取证:复位不丢的"时间源存活性"槽(.noinit → SRAM 保留)==== * 两个槽(均在 UNINIT_CCM,复位不清): * s_wd_tick_cur [0]=magic [1]=uwTick(最新) [2]=本轮 TIM7 中断计数 [3]=保留 * —— TIM7 ISR 每 1ms 更新("本轮活动值") @@ -238,7 +238,7 @@ static uint32_t wd_required_mask(void) { #ifndef TASK_WATCHDOG_UNIT_TEST /* - * 函数功能:记录喂狗时刻并累计最大喂狗间隔(round 7 纯取证) + * 函数功能:记录喂狗时刻并累计最大喂狗间隔(第 7 轮纯取证) * 入口参数:无 * 返回值:无 * 限定条件:由 wd_platform_feed() 内调用(任意喂狗路径均经过) @@ -260,7 +260,7 @@ static void wd_feed_track(void) { } /* - * 函数功能:把停喂现场落盘到 /log/wdev.log(round 7 纯取证) + * 函数功能:把停喂现场落盘到 /log/wdev.log(第 7 轮纯取证) * 入口参数:snapshot - 本次抽样到的位图 uint32_t * 返回值:无 * 限定条件:task_wd_poll 停喂分支(defaultTask 上下文) @@ -298,14 +298,14 @@ static void wd_stall_evidence(uint32_t snapshot) { } } -/* ==================== round 8 取证:复位不丢的"进段打点" ==================== - * 见 task_watchdog.h 的说明(round 7 初判"poller 自身停摆"经 round 8b 更正: +/* ==================== 第 8 轮取证:复位不丢的"进段打点" ==================== + * 见 task_watchdog.h 的说明(第 7 轮初判"poller 自身停摆"经第 8b 轮更正: * 真因是 IWDG 超时被静默配成复位默认值,见 docs/子系统设计/看门狗设计说明.md * 2026-10-02 变更记录)→ 需定位 defaultTask 主循环冻结在哪个调用点。 * 槽放 .noinit(scatter UNINIT_CCM):IWDG/引脚复位不改变 SRAM,复位后仍可读。 */ /* - * 函数功能:记录"即将进入的主循环段"到复位不丢的 .noinit 槽(round 8 取证) + * 函数功能:记录"即将进入的主循环段"到复位不丢的 .noinit 槽(第 8 轮取证) * 入口参数:seg - 段号(task_wd_seg_t) uint32_t * 返回值:无 * 限定条件:defaultTask 主循环调用(任意上下文亦可,纯 SRAM 写) @@ -320,7 +320,7 @@ void task_wd_seg(uint32_t seg) { } /* - * 函数功能:开机快照上一轮的"时间源存活性"(round 8b 取证) + * 函数功能:开机快照上一轮的"时间源存活性"(第 8b 轮取证) * 入口参数:无 * 返回值:无 * 限定条件:**必须在 HAL_Init() 之前调用**(main() 的 USER CODE BEGIN 1) @@ -346,7 +346,7 @@ void task_wd_tick_snapshot(void) { } /* - * 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(round 8b 取证) + * 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(第 8b 轮取证) * 入口参数:无 * 返回值:无 * 限定条件:TIM7 中断上下文调用(HAL_TIM_PeriodElapsedCallback 内,每 1ms) @@ -363,13 +363,13 @@ void task_wd_tick_probe(void) { } /* - * 函数功能:开机上报上一轮最后进段 + 时间源存活性(round 8 取证) + * 函数功能:开机上报上一轮最后进段 + 时间源存活性(第 8 轮取证) * 入口参数:无 * 返回值:无 * 限定条件:启动早期调用(内核启动前亦可,裸 UART 直发) * 函数说明:magic 命中才打一行 `PREVRUN seg=<段> seq=<序号> t=`(裸 UART, * 不依赖环形缓冲写者),随后清 magic(一次复位只报一次)。 - * round 8b:紧接着打 `TICKRUN uw= n= duw=`——uw 为 TIM7 最后一次记下的 + * 第 8b 轮:紧接着打 `TICKRUN uw= n= duw=`——uw 为 TIM7 最后一次记下的 * HAL tick、n 为 TIM7 中断计数、duw = uw - t(时间源是否活到复位)。 */ void task_wd_seg_report(void) { @@ -391,7 +391,7 @@ void task_wd_seg_report(void) { } s_wd_seg_trail[0] = 0u; /* 一次复位只报一次 */ - /* round 8b:时间源存活性(取自开机快照 prev,增量相对上一行的 t = 最后进段时刻)。 + /* 第 8b 轮:时间源存活性(取自开机快照 prev,增量相对上一行的 t = 最后进段时刻)。 * duw≈0 → TIM7 与 defaultTask 同刻停摆(HAL 时基停摆);duw≈2000+ → TIM7 活到 * 复位(冻结另有阻塞点);n = 上一轮 TIM7 中断总数(≈该轮存活毫秒数)。 */ if (s_wd_tick_prev[0] == WD_TICK_MAGIC) { @@ -487,7 +487,7 @@ void task_wd_poll(void) { s_last_mask = snapshot; #ifndef TASK_WATCHDOG_UNIT_TEST - /* round 7 取证:留存近 8 次抽样位图(环形),停喂时随证据落盘 */ + /* 第 7 轮取证:留存近 8 次抽样位图(环形),停喂时随证据落盘 */ s_snap_hist[s_snap_hist_idx] = snapshot; s_snap_hist_idx = (s_snap_hist_idx + 1u) % WD_SNAP_HIST_DEPTH; #endif @@ -524,7 +524,7 @@ void task_wd_poll(void) { s_miss_total++; if (s_miss_streak == 0u) { /* 复合缺席 episode 起点裸 UART 标记(每 episode 一条):证明 poller 仍在 - * 抽样、并给出**首次**缺席位图——定位谁先停跑(round 7 出身)。⚠ v1.5 起 + * 抽样、并给出**首次**缺席位图——定位谁先停跑(第 7 轮出身)。⚠ v1.5 起 * 本行为**纯诊断**——复合边沿 ≠ 停喂,stall 判定权在下面的按位连击。 */ #ifndef TASK_WATCHDOG_UNIT_TEST char m[96]; @@ -549,7 +549,7 @@ void task_wd_poll(void) { (unsigned)wd_required_mask(), (unsigned long)s_miss_total); #ifndef TASK_WATCHDOG_UNIT_TEST - /* round 7 取证:把现场落 /log/wdev.log(掉电不丢)。 + /* 第 7 轮取证:把现场落 /log/wdev.log(掉电不丢)。 * 串口 stall 行经僵尸通道可能丢失,故此处独立留盘上证据。 * ⚠ 本 episode 只写**一次**(s_stall_evidenced 门闩;注意 worst 连击达 * 上限后会**饱和**在容忍值,不能用它判"首次"):证据落盘经 @@ -615,7 +615,7 @@ uint32_t task_wd_is_armed(void) { */ __weak void wd_platform_feed(void) { IWDG->KR = WD_IWDG_KEY_FEED; /* 重装计数器(喂狗) */ - wd_feed_track(); /* round 7 取证:记录喂狗时刻/最大间隔 */ + wd_feed_track(); /* 第 7 轮取证:记录喂狗时刻/最大间隔 */ } /* ==== 为什么 IWDG 参数必须在这里写(2026-10-02 台架实测,P0 根因)==== @@ -627,10 +627,10 @@ __weak void wd_platform_feed(void) { * 实测读数:`IWDG->PR = 0`、`IWDG->RLR = 4095` → 实际超时 4×4096/LSI * ≈ 349~512ms,比设计的 1701~2498ms **短约 5 倍**。后果有二: * ① `WD_MISS_TOLERANCE`(8×100ms=800ms)**永远等不到**就被 IWDG 复位 - * → 停喂判定分支成为死代码(这正是 round 7 观察到"STALL 从未执行" + * → 停喂判定分支成为死代码(这正是第 7 轮观察到"STALL 从未执行" * 的原因,当时误判为"poller 自身停摆"); * ② 任何 >400ms 的心跳缺口都直接 IWDG 误复位。空 STOR(0 字节截断) - * → dhara/FTL flush 使 NET/FTP/UI 心跳缺 ~400ms → 复位(round 3~8 + * → dhara/FTL flush 使 NET/FTP/UI 心跳缺 ~400ms → 复位(第 3~8 轮 * 全部"空 STOR 必复位"的共同根因,P0-B 出口阻塞点)。 * 实测验证:SWD 现场把 PR/RLR 改成 4/1249 后,同一个空 STOR 由 5/5 复位 * 变为 100% 成功(`226 Closing data connection.`)。 diff --git a/App/util/task_watchdog.h b/App/util/task_watchdog.h index 0acab18e..7c9aff28 100644 --- a/App/util/task_watchdog.h +++ b/App/util/task_watchdog.h @@ -85,10 +85,10 @@ uint32_t task_wd_miss_count(void); /* 累计未达标轮数 */ uint32_t task_wd_is_armed(void); /* 1 = IWDG 已启动 */ /* - * ==================== round 8 取证:复位不丢的"进段打点" ==================== - * 背景(2026-10-02 台架 round 7~8 取证,完整链条见 + * ==================== 第 8 轮取证:复位不丢的"进段打点" ==================== + * 背景(2026-10-02 台架第 7~8 轮取证,完整链条见 * docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录):误复位时判定分支(STALL)从未 - * 执行,当时判为**喂狗点 task_wd_poll 自身停摆**(round 8b 已更正:真因是 IWDG 超时 + * 执行,当时判为**喂狗点 task_wd_poll 自身停摆**(第 8b 轮已更正:真因是 IWDG 超时 * 被静默配成复位默认值),而非某个被监视任务饿死。RAM 态随复位消失、串口又可能来不及打, * 故需要一条**复位后仍可读**的"defaultTask 主循环最后走到哪一段"轨迹。 * @@ -96,7 +96,7 @@ uint32_t task_wd_is_armed(void); /* 1 = IWDG 已启动 */ * MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct 的 UNINIT_CCM)——IWDG/引脚复位 * 不改变 SRAM 内容,故复位后仍能读出上一轮的最后进段。槽布局: * [0]=magic(校验是否为本模块写过)[1]=段号 [2]=进段时刻(ms) [3]=循环序号 - * round 8b 追加"时间源存活性"槽(同样在 .noinit): + * 第 8b 轮追加"时间源存活性"槽(同样在 .noinit): * cur = [magic, uwTick, 本轮 TIM7 计数, 0](TIM7 ISR 每 1ms 更新) * prev = [magic, uwTick, 上一轮 TIM7 计数, 0](main() 在 HAL_Init 之前快照) * 注意:**仅 MCU 构建存在**(TASK_WATCHDOG_UNIT_TEST 下不编入,主机单测不受影响)。 @@ -117,7 +117,7 @@ typedef enum { } task_wd_seg_t; /* - * 函数功能:记录"即将进入的主循环段"(round 8 取证;**仅 MCU 构建**) + * 函数功能:记录"即将进入的主循环段"(第 8 轮取证;**仅 MCU 构建**) * 入口参数:seg - 段号(task_wd_seg_t) * 返回值:无 * 限定条件:defaultTask 主循环调用(任意上下文亦可,纯 SRAM 写) @@ -126,20 +126,20 @@ typedef enum { void task_wd_seg(uint32_t seg); /* - * 函数功能:开机上报上一轮最后进段 + 时间源存活性(round 8 取证;**仅 MCU 构建**) + * 函数功能:开机上报上一轮最后进段 + 时间源存活性(第 8 轮取证;**仅 MCU 构建**) * 入口参数:无 * 返回值:无 * 限定条件:启动早期调用(内核启动前亦可,裸 UART 直发) * 函数说明:magic 命中才打一行 `PREVRUN seg=<段> seq=<序号> t=`(裸 UART, * 不依赖环形缓冲写者),随后清 magic(一次复位只报一次)。 - * round 8b:紧接着打一行 `TICKRUN uw= n= duw=`——uw 为 TIM7 在**上一轮** + * 第 8b 轮:紧接着打一行 `TICKRUN uw= n= duw=`——uw 为 TIM7 在**上一轮** * 最后一次记下的 HAL tick、n 为上一轮 TIM7 中断总数、duw = uw - t * (相对进段时刻的增量),据此判定 TIM7 是否在冻结同刻停摆。 */ void task_wd_seg_report(void); /* - * 函数功能:开机快照上一轮的"时间源存活性"(round 8b 取证;**仅 MCU 构建**) + * 函数功能:开机快照上一轮的"时间源存活性"(第 8b 轮取证;**仅 MCU 构建**) * 入口参数:无 * 返回值:无 * 限定条件:**必须在 HAL_Init() 之前调用**(main() 的 USER CODE BEGIN 1) @@ -150,13 +150,13 @@ void task_wd_seg_report(void); void task_wd_tick_snapshot(void); /* - * 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(round 8b 取证;**仅 MCU 构建**) + * 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(第 8b 轮取证;**仅 MCU 构建**) * 入口参数:无 * 返回值:无 * 限定条件:TIM7 中断上下文调用(`HAL_TIM_PeriodElapsedCallback` 内,每 1ms) * 函数说明:更新 cur 槽 = [magic, uwTick, 本轮 TIM7 中断计数, 0](纯 SRAM 写,无阻塞)。 * 与 task_wd_seg 的"进段打点"配合,复位后由 task_wd_seg_report() 上报。 - * 背景(2026-10-02 台架 round 8b 取证,见 docs/子系统设计/看门狗设计说明.md + * 背景(2026-10-02 台架第 8b 轮取证,见 docs/子系统设计/看门狗设计说明.md * 2026-10-02 变更记录):HAL_UART_Transmit 的超时判据 * 依赖 uwTick,时间源停摆即死等——必须先把"TIM7 是否停摆"钉死再定向修复。 * ⚠ **本函数内严禁调用任何 FreeRTOS/CMSIS-RTOS API**:TIM7 在 HAL_Init diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index 07af0409..ad284d21 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -8,9 +8,9 @@ * 修改记录: * 2026-03-14 王建锋 创建初始版本,按代码规范重构 * 2026-07-18 王建锋 改进write_send_buf/read_recv_buf使用SPI2 DMA批量传输 - * 2026-10-02 王建锋 E-34:3 处字节级 SPI 调用由 HAL_MAX_DELAY 改有限超时(100ms) + * 2026-10-02 王建锋 3 处字节级 SPI 调用由 HAL_MAX_DELAY 改有限超时(100ms) * + 返回值检查 + 故障锁存(沿程只记一条日志);原 SPI 异常时调用方永久挂死 - * 2026-10-04 王建锋 P2-2:ch395f_spi_dma_transfer 由 void 改为返回状态(0 成功/-1 失败), + * 2026-10-04 王建锋 ch395f_spi_dma_transfer 由 void 改为返回状态(0 成功/-1 失败), * DMA 初始化失败/超时中止均锁存通道故障;read_recv_buf 在 DMA 失败时不再把 * rx_buf 内容拷入调用方缓冲(改零填充),消除"失败当成功"的静默数据损坏 */ @@ -40,7 +40,7 @@ #define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试数据 */ #define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */ #define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms) */ -#define CH395F_SPI_BYTE_TIMEOUT_MS 100U /* E-34:字节级SPI有限超时(原 HAL_MAX_DELAY) */ +#define CH395F_SPI_BYTE_TIMEOUT_MS 100U /* 字节级SPI有限超时(原 HAL_MAX_DELAY) */ /* DMA缓冲大小使用ch395f.h中公开的CH395F_SPI_DMA_BUF_SIZE */ #define CH395F_DMA_BUF_SIZE CH395F_SPI_DMA_BUF_SIZE @@ -48,7 +48,7 @@ * 静态变量定义区 - DMA传输相关 */ static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */ -static uint8_t s_spi_fault = 0U; /* E-34/P2-2:SPI 通道故障锁存(字节级或 DMA 级超时/出错置位,MCU复位清除) */ +static uint8_t s_spi_fault = 0U; /* SPI 通道故障锁存(字节级或 DMA 级超时/出错置位,MCU复位清除) */ __attribute__((aligned(4))) static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */ @@ -90,7 +90,7 @@ static void ch395f_spi_end(void) { } /* - * 函数功能:锁存一次 SPI 通道故障(E-34/P2-2:仅首条故障记日志) + * 函数功能:锁存一次 SPI 通道故障(仅首条故障记日志) * 入口参数:kind - 故障类别标签("byte"/"dma-hal"/"dma-timeout") const char* * 返回值:无 * 限定条件:无 @@ -114,7 +114,7 @@ static void ch395f_spi_fault_set(const char *kind) { * 2. 阻塞等待DMA完成回调置标志位 * 3. 超时(1秒)时强制中止SPI DMA并打印错误信息 * 4. 传输完成后检查SPI错误码并清除溢出标志 - * 5. P2-2(2026-10-04):失败路径锁存通道故障并返回 -1,调用方据此 + * 5.(2026-10-04):失败路径锁存通道故障并返回 -1,调用方据此 * 判定 rx_buf 内容不可信,不再"失败当成功"继续消费 */ static int ch395f_spi_dma_transfer(uint16_t len) { @@ -209,7 +209,7 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { uint8_t ch395f_write_cmd(uint8_t cmd) { uint8_t rx_data = 0xFFU; - /* 通过SPI发送命令并接收回显(E-34:有限超时+返回值检查;失败返回0xFF占位并锁存故障) */ + /* 通过SPI发送命令并接收回显(有限超时+返回值检查;失败返回0xFF占位并锁存故障) */ if (HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { ch395f_spi_fault_set("byte"); @@ -226,7 +226,7 @@ uint8_t ch395f_write_cmd(uint8_t cmd) { uint8_t ch395f_write_data(uint8_t data) { uint8_t rx_data = 0xFFU; - /* 通过SPI发送数据并接收回显(E-34:有限超时+返回值检查) */ + /* 通过SPI发送数据并接收回显(有限超时+返回值检查) */ if (HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { ch395f_spi_fault_set("byte"); @@ -244,7 +244,7 @@ uint8_t ch395f_read_data(void) { uint8_t tx_dummy = 0xFFU; uint8_t rx_data = 0xFFU; - /* 发送0xFF作为时钟源,读取芯片返回数据(E-34:有限超时+返回值检查) */ + /* 发送0xFF作为时钟源,读取芯片返回数据(有限超时+返回值检查) */ if (HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { ch395f_spi_fault_set("byte"); @@ -253,7 +253,7 @@ uint8_t ch395f_read_data(void) { } /* - * 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2) + * 函数功能:查询 SPI 通道故障锁存 * 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级) * 限定条件:无 * 函数说明:锁存式(不自动清除,MCU 复位前一直有效);上层据此判定 @@ -1197,7 +1197,7 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { /* 数据同步屏障:确保memcpy写入对DMA可见 */ __DSB(); - /* CS低 → DMA批量传输 → CS高(P2-2:失败已由 transfer 锁存通道故障并记日志, + /* CS低 → DMA批量传输 → CS高(失败已由 transfer 锁存通道故障并记日志, * 调用方经 ch395f_spi_byte_fault() 查询;此处无后续处理,失败即放弃本次发送) */ ch395f_spi_begin(); (void)ch395f_spi_dma_transfer(total); @@ -1250,7 +1250,7 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { rc = ch395f_spi_dma_transfer(total); ch395f_spi_end(); - /* P2-2(2026-10-04):DMA 失败时 rx_buf 内容不可信,禁止拷入调用方缓冲 + /* (2026-10-04):DMA 失败时 rx_buf 内容不可信,禁止拷入调用方缓冲 * (否则把总线残留/半截数据当有效载荷 → 静默数据损坏);改零填充并返回, * 调用方据 ch395f_spi_byte_fault() 判定通道异常。 */ if (rc != 0) { diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h index 06972d67..ea5ceedb 100644 --- a/Drivers/BSP/CH395F/ch395f.h +++ b/Drivers/BSP/CH395F/ch395f.h @@ -307,7 +307,7 @@ uint8_t ch395f_write_data(uint8_t data); uint8_t ch395f_read_data(void); /* - * 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2) + * 函数功能:查询 SPI 通道故障锁存 * 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级) * 限定条件:无 * 函数说明:锁存式(MCU 复位前一直有效);上层据此判定"SPI 通道异常" @@ -584,7 +584,7 @@ uint16_t ch395f_get_recv_len(uint8_t sock); * 任何未配对 SINT 消费的读取都会吞掉其它 Socket 的事件通知。 * **唯一读者 = net_poll(net_socket.c);测试/台架代码同样禁自行读取** * (用户裁定 2026-10-13)——等事件一律直读本 Socket ch395f_get_sock_int_status()。 - * 唯一例外:被测对象就是本 API 的板级用例(P1-06 寄存器断言 / Phase 12 L1 + * 唯一例外:被测对象就是本 API 的板级用例(寄存器断言 / Phase 12 L1 * 时延基准,硬件层独占上下文、无并发消费者)。 */ uint16_t ch395f_get_glob_int_status_all(void); diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c index c1896db1..cdfdc00a 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c @@ -62,7 +62,7 @@ static volatile uint8_t s_spi1_dma_done; * 源/目的全部为文件作用域静态缓冲。 */ /* DMA 可达内存窗口 GD5F_DMA_RAM_BASE/END 与 CCM 窗口常量已上移到 gd5f2gq5ue.h - * (N-26,2026-10-12:nand_ftl.c 的 init 期自检也要用),并带编译期守卫 + * (DMA 可达性三层防线,2026-10-12:nand_ftl.c 的 init 期自检也要用),并带编译期守卫 * (白名单窗口与 CCM 永不相交、阈值契约)。此处只留语义提醒: * 窗口必须与分散加载 RW_IRAM1(0x20000000,128KB,SRAM1+SRAM2)一致; * CCM(0x10000000,64KB)DMA1/DMA2 物理不可达,RW_CCM 区任何缓冲都不得进 DMA。 @@ -130,7 +130,7 @@ static int gd5f_private_bbt_find_best(void); static int gd5f_bbt_load(void); static int gd5f_bbt_save(void); -/* DMA 可达性守卫:实现见下方(N-26 起为公开 API,声明在 gd5f2gq5ue.h) */ +/* DMA 可达性守卫:实现见下方(随 DMA 可达性三层防线升为公开 API,声明在 gd5f2gq5ue.h) */ /* ======================== DMA 可达性守卫 ======================== */ @@ -174,7 +174,7 @@ int gd5f_dma_buf_ok(const void *p_buf, size_t size) { * GD5F_ERROR(-1) - SPI 传输真故障(不可重试,须上报自检) * 限定条件:SPI 已初始化 * 函数说明:循环读取状态寄存器直到 OIP 位清零或超时。 - * E-53(2026-10-03,GD5F 错误通道统一):三处 HAL 调用的返回值**必须**检查 + * (2026-10-03,GD5F 错误通道统一):三处 HAL 调用的返回值**必须**检查 * ——原实现全部丢弃,失败时 `status` 保持初值 0 → `(status & OIP) == 0` * 立即成立 → **把"操作完成"伪造出来**,后续命令在未就绪的芯片上执行, * 最终表现为"静默报成功但数据没落盘"。现区分两类失败: @@ -217,7 +217,7 @@ int32_t gd5f_wait_busy(uint32_t timeout_ms) { GD5F_CS_HIGH(); if (hs != HAL_OK) { - /* E-53:SPI 真故障——绝不伪造成"操作完成"(详见函数说明) */ + /* SPI 真故障——绝不伪造成"操作完成"(详见函数说明) */ return GD5F_ERROR; } @@ -500,8 +500,8 @@ int32_t gd5f_check_ecc(void) { * 返回值:GD5F_OK - 成功;其它 - SPI 传输/等待失败错误码 * 限定条件:SPI 已初始化 * 函数说明:先写使能,发 1Fh + 地址 + 数据,等待操作完成。 - * E-53(2026-10-03):HAL 传输与 wait_busy 的返回值必须检查并上报—— - * 原实现恒返回 GD5F_OK,"配置未生效"对上层不可见(E-10 同族问题)。 + * (2026-10-03):HAL 传输与 wait_busy 的返回值必须检查并上报—— + * 原实现恒返回 GD5F_OK,"配置未生效"对上层不可见(同族问题)。 */ static int gd5f_set_feature(uint8_t addr, uint8_t data) { uint8_t cmd[3]; @@ -534,10 +534,10 @@ static int gd5f_set_feature(uint8_t addr, uint8_t data) { * 出口参数:p_data - 读到的字节输出指针 uint8_t* 不为 NULL * 返回值:GD5F_OK - 成功;GD5F_ERROR - SPI 传输失败 * 限定条件:SPI 已初始化 - * 函数说明:E-10(2026-10-03):init 需**回读** B0h 校验 ECC 使能位真正生效 + * 函数说明:(2026-10-03):init 需**回读** B0h 校验 ECC 使能位真正生效 * (原实现只写不读,"init 返回 OK 但 ECC 未打开"→ 存储栈在无 ECC * 保护下静默降级)。与 gd5f_read_status 同构(后者固定 C0h), - * 此处参数化地址;同样检查 HAL 返回值(E-53)。 + * 此处参数化地址;同样检查 HAL 返回值。 */ static int gd5f_private_get_feature(uint8_t addr, uint8_t *p_data) { uint8_t cmd = GD5F_CMD_GET_FEATURE; @@ -600,7 +600,7 @@ int32_t gd5f_block_erase(uint32_t block_addr) { * 限定条件:SPI 已初始化,ECC 尚未使能 * 函数说明:读取每个块首页(page 0)的 spare byte 0, * 若不为 0xFF 则为出厂坏块(符合 datasheet §12.4)。 - * E-42(2026-10-03,GD5F 错误通道统一):原实现丢弃 page_read / + * (2026-10-03,GD5F 错误通道统一):原实现丢弃 page_read / * read_from_cache 的返回值且对外恒返回 GD5F_OK——SPI 读失败时 * `spare[8]` 保持上一轮的**陈旧内容**,且"整片扫描全失败也返回成功", * 上层以为坏块表可用(rebuild 路径还会把错表持久化覆盖好副本)。 @@ -689,7 +689,7 @@ int32_t gd5f2gq5ue_bbt_rebuild(void) { /* 3. 重新使能 ECC:无论扫描成败都必须恢复,而且要**验证真的生效** * (2026-10-23,由 bsp-err 的 consume-result 检查照出)。原先这两行返回值全丢:SPI 一抖 ECC 就保持关闭,而 * gd5f_check_ecc() 在 ECC 关闭时恒读"无 ECC 错"——整条链再没有任何信号,设备 - * 从此在无位翻转保护下读写却"一切正常"。init 路径有 E-10 的"写完回读 B0h 校验 + * 从此在无位翻转保护下读写却"一切正常"。init 路径有"写完回读 B0h 校验 * ECC_EN 位",这里补同一套;ECC 没恢复比扫描失败更严重,所以优先返回它。 */ restore_ret = gd5f_set_feature(0xB0, 0x10); if ((gd5f_wait_busy(100) != GD5F_OK) && (restore_ret == GD5F_OK)) { @@ -710,7 +710,7 @@ int32_t gd5f2gq5ue_bbt_rebuild(void) { return restore_ret; } - /* E-42(2026-10-03,数据安全优先):扫描失败 → **放弃持久化**(保留持久区 + /* (2026-10-03,数据安全优先):扫描失败 → **放弃持久化**(保留持久区 * 既有好副本),并从盘上回读 BBT 恢复 RAM 视图。原实现无论扫描成败都照常 * save(),会把"读失败拼出来的错表"覆盖掉正确副本。 */ if (ret != GD5F_OK) { @@ -806,7 +806,7 @@ int32_t gd5f2gq5ue_init(void) { DBG_INFO("Scanning bad blocks..."); ret = gd5f_bbt_scan(); if (ret != GD5F_OK) { - /* E-10:扫坏块失败 → init 失败,不静默继续(上层据此上报自检并降级) */ + /* 扫坏块失败 → init 失败,不静默继续(上层据此上报自检并降级) */ DBG_ERROR("BBT scan failed: %d", ret); return ret; } @@ -817,7 +817,7 @@ int32_t gd5f2gq5ue_init(void) { DBG_ERROR("Enable ECC failed: %d", ret); return ret; } - /* E-10:回读 B0h 校验 ECC 使能位(bit4)**真正生效**——原实现只写不读, + /* 回读 B0h 校验 ECC 使能位(bit4)**真正生效**——原实现只写不读, * "init 返回 OK 但 ECC 未真正打开"会使存储栈在无 ECC 保护下静默降级。 */ { uint8_t feat = 0; diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h index 8d0b15e4..08bdb09f 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h @@ -77,7 +77,7 @@ extern "C" { */ #define GD5F_DMA_THRESHOLD 1024 -/* ==================== DMA 可达内存窗口(N-26,2026-10-12) ==================== +/* ==================== DMA 可达内存窗口(DMA 可达性三层防线,2026-10-12) ==================== * DMA1/DMA2 物理不可达 CCM(0x10000000~0x1000FFFF):以 CCM 缓冲做 DMA 源/目的 * 会静默不落地(2026-09-20 首轮 CCM 扩容实验实证根因;口径见 * docs/内存管理/内存内容分配策略.md §2 与 §4 规则 R2/R3)。 @@ -93,7 +93,7 @@ extern "C" { #define GD5F_CCM_RAM_BASE 0x10000000UL #define GD5F_CCM_RAM_END 0x10010000UL -/* 编译期守卫(N-26):AC5 无 _Static_assert(C11 不支持),用项目惯用的 +/* 编译期守卫(DMA 可达性三层防线一环):AC5 无 _Static_assert(C11 不支持),用项目惯用的 * 负数组 typedef(同 App/cfg/cfg_file.c、Drivers/BSP/UI/ui_menu_tab.h)—— * 条件不成立则数组尺寸 -1 → 编译错。 * ① DMA 白名单窗口与 CCM 窗口**永不得重叠**(有人改上面常量时立即爆炸); @@ -116,7 +116,7 @@ typedef char gd5f_dma_threshold_contract_check_t[ /* 返回值定义 */ #define GD5F_OK 0 #define GD5F_ERROR -1 -#define GD5F_WAIT_EXPIRED -2 /* 旧名 GD5F_BUSY_TIMEOUT;S-5 的 timeout-name 检查:**返回码不许叫 *_TIMEOUT** */ +#define GD5F_WAIT_EXPIRED -2 /* 旧名 GD5F_BUSY_TIMEOUT;timeout-name 检查:**返回码不许叫 *_TIMEOUT** */ #define GD5F_ECC_ERROR -3 #define GD5F_PROGRAM_FAIL -4 #define GD5F_ERASE_FAIL -5 @@ -160,7 +160,7 @@ int32_t gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did); * 返回值:GD5F_OK(0) - SPI 传输完成;其它 - 错误码 * 限定条件:gd5f2gq5ue_init() 已成功调用 * 函数说明:自动处理跨页读取,每次读取不超过当前页剩余空间。 - * ⚠ **raw 契约(E-56,2026-10-03 显式文档化)**:返回 0 只表示 **SPI 传输完成**, + * ⚠ **raw 契约(2026-10-03 显式文档化)**:返回 0 只表示 **SPI 传输完成**, * **不代表数据无位翻转**——ECC 不可纠正页(ECCS 非零)在 SPI 层属正常完成, * 本函数仍返回 0。数据正确性由 **ECC 层**保证:生产主路径经 dhara glue * (`nand_ftl.c`)自有 ECC 检查保护;**直接使用本 raw API** 的调用方 @@ -277,7 +277,7 @@ int32_t gd5f_bbt_reload(void); * 入口参数:timeout_ms - 等待上限 uint32_t 毫秒 * 返 回 值:GD5F_OK - 已空闲;GD5F_WAIT_EXPIRED - 时限内未空闲(**可重试**,旧名 BUSY_TIMEOUT);GD5F_ERROR - SPI 读取失败 * 限定条件:SPI 已初始化;不得在中断里调用 - * 函数说明:E-32 把"超时"与"读状态本身失败"分开返回,两者处置不同(前者再等、后者查硬件) + * 函数说明:把"超时"与"读状态本身失败"分开返回,两者处置不同(前者再等、后者查硬件) */ int32_t gd5f_wait_busy(uint32_t timeout_ms); @@ -286,7 +286,7 @@ int32_t gd5f_wait_busy(uint32_t timeout_ms); * 入口参数:无 * 返 回 值:GD5F_OK - 成功;其它 - SPI 传输或等待失败错误码 * 限定条件:SPI 已初始化 - * 函数说明:E-53 规定本函数返回值必须被检查并上报,忽略它会让后续 program/erase 静默无效 + * 函数说明:本函数返回值必须被检查并上报(错误通道统一纪律),忽略它会让后续 program/erase 静默无效 */ int32_t gd5f_write_enable(void); @@ -295,7 +295,7 @@ int32_t gd5f_write_enable(void); * 入口参数:p_status - 状态字节输出 uint8_t* 不得为 NULL * 返 回 值:GD5F_OK - 成功(*p_status 有效);GD5F_ERROR - SPI 失败(*p_status 不可信,调用方不得读用) * 限定条件:SPI 已初始化 - * 函数说明:E-42 同族纪律——失败时出参内容不可信,故判成败看返回值而不是看 *p_status 是否像样 + * 函数说明:同族纪律——失败时出参内容不可信,故判成败看返回值而不是看 *p_status 是否像样 */ int32_t gd5f_read_status(uint8_t *p_status); @@ -315,7 +315,7 @@ int32_t gd5f_page_read(uint32_t page_addr); * size - 读取字节数 uint32_t 1 - GD5F_PAGE_SIZE-column * 返 回 值:GD5F_OK - 成功;GD5F_ERROR - 参数非法或 SPI 失败(**此时 p_buf 内容不可信**) * 限定条件:必须先成功调用 gd5f_page_read() 完成阵列→缓存装载 - * 函数说明:E-42 同族——调用方不得在失败分支继续使用 p_buf(FTL 侧据此把读失败保守判为"非空页") + * 函数说明:同族——调用方不得在失败分支继续使用 p_buf(FTL 侧据此把读失败保守判为"非空页") */ int32_t gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, uint32_t size); @@ -353,7 +353,7 @@ int32_t gd5f_block_erase(uint32_t block_addr); * 入口参数:无 * 返 回 值:GD5F_OK - 无不可纠错误(可能含已纠正的位翻转);GD5F_ECC_ERROR - **存在不可纠正的 ECC 错误** * 限定条件:SPI 已初始化且 ECC 已使能(init 期扫描在使能前调用本函数无意义) - * 函数说明:这是数据正确性的唯一判定点——SPI 层读到"内容"不等于"内容对"(见 §8.1 与本头 E-42/E-53 注释) + * 函数说明:这是数据正确性的唯一判定点——SPI 层读到"内容"不等于"内容对"(见 §8.1 与本头"同族纪律"注释) */ int32_t gd5f_check_ecc(void); @@ -363,7 +363,7 @@ int32_t gd5f_check_ecc(void); * size - 传输字节数 size_t * 返回值:1 - 整段 DMA 可达;0 - 不可达(含 size=0 / 地址回绕 / 越窗) * 限定条件:无 - * 函数说明:N-26(2026-10-12)由 gd5f2gq5ue.c 私有守卫升级为公开 API—— + * 函数说明:2026-10-12 由 gd5f2gq5ue.c 私有守卫升级为公开 API(DMA 可达性三层防线一环)—— * 驱动内部每次 >阈值 传输前用它(不可达即回落 CPU 轮询), * FTL 层(nand_ftl.c)在 init 期用它自检静态页缓冲的落位。 */ diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c index 5f9877ff..429618a6 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c @@ -6,13 +6,13 @@ * 创建日期:2026-07-20 * 修改记录: * 2026-07-20 王建锋 创建初始版本 - * 2026-09-21 E-31 resume 失败按错误分类:仅真空白判新卷,其余失败 + * 2026-09-21 resume 失败按错误分类:仅真空白判新卷,其余失败 * 返回 mount 层(禁止静默清 map 触发整卷重格式化) * 2026-10-02 王建锋 resume 成功后增打 journal head/tail/epoch:复盘 * "复位后所有写入丢失"时 sectors used 不足以判定 * journal 回退(覆盖写不改 count),head/tail/epoch 是 * 唯一能暴露 resume 锚错 checkpoint 的证据 - * 2026-10-15 王建锋 N-21(报告 N-12/N-13):① is_free 改**整页扫描**—— + * 2026-10-15 王建锋 落实 dhara 移植层两条硬约束:① is_free 改**整页扫描**—— * 旧实现只看页首 64 B,会把"页首 0xFF、后段有数据"的 * 已编程页误判空闲,dhara 遂往已编程页再编程 = 数据丢失 * (页首有数据的绝大多数页走快路径,成本与旧实现同价); @@ -65,7 +65,7 @@ * 而忘了同步这里的字面量,守卫会静默算错——偏大就是越界守卫失效,正是它要防的 * 那一类错误,而且不报任何错。下面两条把"字面量 == GD5F 常量的 log2"钉死, * 不一致直接编译错。负数组 typedef 是 AC5 下的项目惯用法(AC5 不支持 C11 - * _Static_assert),同本目录 gd5f2gq5ue.h 的 N-26 DMA 契约守卫。 */ + * _Static_assert),同本目录 gd5f2gq5ue.h 的 DMA 契约守卫。 */ typedef char ftl_geometry_log2_page_size_check_t[ (FTL_PAGE_SIZE == (1 << FTL_LOG2_PAGE_SIZE)) ? 1 : -1]; typedef char ftl_geometry_log2_ppb_check_t[ @@ -76,7 +76,7 @@ static inline uint32_t ftl_phy_block(dhara_block_t b) { return b + FTL_START_BLO static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAGE; } /* - * N-21(2026-10-15,报告 N-13):移植层地址守卫 + * dhara 移植层硬约束(2026-10-15):移植层地址守卫 * * 旧实现把 dhara 传来的页/块号直接加偏移送进 SPI 命令,**不做任何上界校验**。 * 越界页号(含 dhara 的"无页"哨兵 DHARA_PAGE_NONE = 0xffffffff,journal.h:46) @@ -89,8 +89,8 @@ static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAG * 为什么不用 DHARA_E_BAD_BLOCK:journal.c:904 的 recover_from 只对 BAD_BLOCK * 走"标坏块 + 跳块重试"——对一个凭空的地址标坏块 = 毁掉一个无辜的好块。 * 为什么不会触发静默清卷:map.c:275 的 try_recover 只对 E_RECOVER 启动回收, - * 其余错误一律原样上抛(E-31 教训:单次瞬态毛刺不得销毁整卷数据)。 - * is_free / is_bad 没有错误通道 → 一律保守取值(判非空 / 判坏),与 E-42 同族 + * 其余错误一律原样上抛(教训:单次瞬态毛刺不得销毁整卷数据)。 + * is_free / is_bad 没有错误通道 → 一律保守取值(判非空 / 判坏),与同族 * 口径一致:宁可少用一个块,不可把不可信的内容当有效。 */ static int ftl_page_in_range(const struct dhara_nand *n, dhara_page_t p) { @@ -156,7 +156,7 @@ static const char *ftl_err_name(dhara_error_t err) { } /* - * 函数功能:把 dhara 错误归类成统一失败类(C2,报告 S-5) + * 函数功能:把 dhara 错误归类成统一失败类(C2) * 入口参数:err - dhara 错误枚举值 * 返回值:BSP_ERR_CRC(位翻转不可纠 / 页太坏);BSP_ERR_NO_SPACE(journal 或映射层预算 * 耗尽);其余(坏块 / 找回失败 / 映射损坏 / 越界)→ BSP_ERR_IO @@ -164,10 +164,10 @@ static const char *ftl_err_name(dhara_error_t err) { * 函数说明:分这三类的理由是它们在**处置上真的不同**,不是为了码好看: * · ECC/TOO_BAD → 该重试读或标页坏,**不是**卷的问题; * · JOURNAL_FULL/MAP_FULL → 介质还健康,是**块池/日志预算见底**,此时重格式化只是 - * 把问题推回去(E-31 的教训正是"别把非空白错误当新卷清 map"),该走容量恢复流程; + * 把问题推回去(教训正是"别把非空白错误当新卷清 map"),该走容量恢复流程; * · BAD_BLOCK/CORRUPT_MAP/RECOVER → 映射或介质完整性故障,该保卷 + 上报,不许自愈重格式化。 * 本函数先把这三类**打出来**(就地日志,最早且唯一同时知道 LPN 与原始码的位置); - * 要让 defaultTask 的卷健康探针按类分支,还需要一个跨调用存活的快照(S-5 的 C4 待还)。 + * 要让 defaultTask 的卷健康探针按类分支,还需要一个跨调用存活的快照(C4 待还)。 */ static bsp_err_t ftl_class_from_dhara(dhara_error_t err) { switch (err) { @@ -284,7 +284,7 @@ int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *d } /* - * N-21(2026-10-15,报告 N-12):整页扫描 + * dhara 移植层硬约束(2026-10-15):整页扫描 * * 旧实现只看页首 64 B。dhara 的 journal 用 is_free 判断"检查点组是否未编程" * (journal.c:416 cp_free、journal.c:520 找下一空闲页),其正确性前提是 @@ -317,7 +317,7 @@ int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) { for (off = 0; off < FTL_PAGE_SIZE; off += sizeof(buf)) { if (gd5f_read_from_cache((uint16_t)off, buf, sizeof(buf)) != GD5F_OK) { - /* E-42 同族(2026-10-03):读失败 → buf 可能为陈旧/垃圾内容, + /* 同族(2026-10-03):读失败 → buf 可能为陈旧/垃圾内容, * 保守判为非空(块不可复用),避免把"没读到内容的块"判成空闲。 */ return 0; } @@ -350,7 +350,7 @@ int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, s return -1; } - /* E-42 同族(2026-10-03):read_from_cache 返回值必须检查——失败时 data 内容 + /* 同族(2026-10-03):read_from_cache 返回值必须检查——失败时 data 内容 * 不可信,原实现仍返回 0(成功),会把未读到的数据当有效数据交给上层。 */ if (gd5f_read_from_cache((uint16_t)offset, data, length) != GD5F_OK) { dhara_set_error(err, DHARA_E_ECC); @@ -360,7 +360,7 @@ int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, s } /* - * N-21(2026-10-15,报告 N-13):src / dst 双端守卫 + * dhara 移植层硬约束(2026-10-15):src / dst 双端守卫 * * 报告原判断"dhara map 在回收/擦除路径会用 DHARA_NO_PAGE 试探"经逐点核对 * **不成立**:本仓裁剪版没有 DHARA_NO_PAGE,哨兵叫 DHARA_PAGE_NONE @@ -401,7 +401,7 @@ DSTATUS disk_initialize(BYTE pdrv) { DBG_INFO("Initializing NAND FTL..."); - /* N-26 启动期地址自检(2026-10-12):本文件三个页缓冲是 SPI1 DMA 路径上 + /* DMA 可达性三层防线——启动期地址自检(2026-10-12):本文件三个页缓冲是 SPI1 DMA 路径上 * 唯一的**静态**缓冲(dhara_map_read/write → dhara_nand_read/prog/copy, * 2048 B > GD5F_DMA_THRESHOLD;s_page_buf 页内虽只攒 132 B 元数据, * 但编程的是**整页**(journal.c:992),传输长度同为 2048 B、同样走 DMA, @@ -410,7 +410,7 @@ DSTATUS disk_initialize(BYTE pdrv) { * **静默不落地**(数据错而传输"成功")。缓冲地址链接期才确定,armcc * 无法编译期断言(见 gd5f2gq5ue.h 的窗口常量注释),故在 init 期自检: * 任一缓冲越出 DMA 白名单窗口 → 上报并拒绝挂载(存储降级、不挂死, - * 与 E-31 的"保留卷、上报自检"形态一致)。每次传输的运行期守卫 + * 与"保留卷、上报自检"形态一致)。每次传输的运行期守卫 * (gd5f_read_from_cache/gd5f_program_load 内)保留作第二层兜底。 */ if ((gd5f_dma_buf_ok(s_cache_buf, FTL_PAGE_SIZE) == 0) || (gd5f_dma_buf_ok(s_copy_buf, FTL_PAGE_SIZE) == 0) || @@ -442,7 +442,7 @@ DSTATUS disk_initialize(BYTE pdrv) { DBG_INFO("No valid map found (dhara=%s), creating fresh", ftl_err_name(err)); dhara_map_clear(&s_map); } else { - /* E-31(2026-09-21 审计 P0 修复):非"空白"错误(ECC / + /* (2026-09-21 审计 P0 修复):非"空白"错误(ECC / * BAD_BLOCK / CORRUPT_MAP / 瞬态 I/O 等)= 真实完整性故障—— * 芯片上 journal 可能仍持有有效 checkpoint。旧行为把任何失败 * 当新卷清 map,上层随即整卷重格式化 → 单次瞬态毛刺即静默销毁 diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h index c989c402..86dea1d8 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h @@ -6,7 +6,7 @@ * 作者:王建锋 * 创建日期:2026-09 * 修改记录: - * 2026-10-23 王建锋 补模块头与函数注释(S-5 的 return-doc 检查);返回值语义写明"失败类目前 + * 2026-10-23 王建锋 补模块头与函数注释(return-doc 检查);返回值语义写明"失败类目前 * 只进日志、不外传",避免调用方以为能拿到 ECC / 预算耗尽的区别 */ @@ -27,8 +27,8 @@ extern "C" { * 函数说明:⚠ 这里的 -1 **抹掉了失败类**——"ECC 读不动"、"journal 满"与"映射损坏"对外 * 都只是 -1。分类信息目前只在失败点就地进日志(见 nand_ftl.c 的 ftl_class_from_dhara * 与 ../架构设计/STM32F4-Base存储架构说明.md §10.4);要让调用方按类分支,需要跨调用 - * 存活的失败类快照(报告 S-5 的 C4 待还项,当前无消费者故未做)。 - * 调用方**不得**把 -1 解读成"卷坏了,可以重格式化"——E-31 的教训正在这条上。 + * 存活的失败类快照(C4 待还项,当前无消费者故未做)。 + * 调用方**不得**把 -1 解读成"卷坏了,可以重格式化"——教训正在这条上。 */ int nand_ftl_init(void); diff --git a/Drivers/BSP/NET/README.md b/Drivers/BSP/NET/README.md index 4e3a690f..e573950d 100644 --- a/Drivers/BSP/NET/README.md +++ b/Drivers/BSP/NET/README.md @@ -43,7 +43,7 @@ 真正兜底 = D10 预算到期 + 对账形态④ 重新登记) - 仅异常路径生效,正常 FIN 路径零改动 -### 错误码(E-03 修复) +### 错误码 - **按任务隔离**:`s_net_errno_slots[8]`(每槽 `{任务句柄, err}`),替代原全局 `s_net_errno` - `net_get_errno()` 只读本任务槽位,**对外语义不变**(成功不清零) diff --git a/Drivers/BSP/NET/lftpd/lftpd.c b/Drivers/BSP/NET/lftpd/lftpd.c index 66dbdb8a..85e3c10e 100644 --- a/Drivers/BSP/NET/lftpd/lftpd.c +++ b/Drivers/BSP/NET/lftpd/lftpd.c @@ -64,7 +64,7 @@ static command_t commands[] = { { NULL, NULL }, }; -/* N-19 FTP 鉴权:全局失败计数/锁定状态(跨会话持久——单连接服务器,全局 +/* FTP 鉴权:全局失败计数/锁定状态(跨会话持久——单连接服务器,全局 * 计数即可;成功清零、锁定期满自动放行,见 lftpd_auth.c) */ static ftp_auth_state_t s_auth_state; @@ -343,7 +343,7 @@ static int cmd_cwd(lftpd_client_t *client, const char *arg) static int cmd_dele(lftpd_client_t *client, const char *arg) { - /* N-19:写命令门——[ftp] write=0 时拒绝(鉴权门已保证此处必为已登录态) */ + /* 写命令门——[ftp] write=0 时拒绝(鉴权门已保证此处必为已登录态) */ const ftp_cfg_t *cfg = ftp_cfg_get(); if ((cfg == NULL) || (cfg->write_en == 0u)) { @@ -638,7 +638,7 @@ static int cmd_size(lftpd_client_t *client, const char *arg) static int cmd_stor(lftpd_client_t *client, const char *arg) { - /* N-19:写命令门——[ftp] write=0 时拒绝(鉴权门已保证此处必为已登录态) */ + /* 写命令门——[ftp] write=0 时拒绝(鉴权门已保证此处必为已登录态) */ const ftp_cfg_t *cfg = ftp_cfg_get(); if ((cfg == NULL) || (cfg->write_en == 0u)) { @@ -757,7 +757,7 @@ static int handle_control_channel(lftpd_client_t *client) command_tmp[i] = (char)toupper((int)command_tmp[i]); } - /* N-19:未鉴权命令门——仅 QUIT/FEAT/SYST/NOOP 放行(客户端兼容需要, + /* 未鉴权命令门——仅 QUIT/FEAT/SYST/NOOP 放行(客户端兼容需要, * 且无信息量);其余一律 530。USER/PASS 本身也在放行名单外, * 但它们在 gate 之前不可能通过 → 单独放行见下(USER/PASS 例外) */ if (client->auth_logged_in == 0) { @@ -817,7 +817,7 @@ int lftpd_start(const char *directory, int port, lftpd_t *lftpd) lftpd->port = port; lftpd->server_socket = -1; /* memset 清零后显式置"无监听器"(0 是合法 fd) */ - /* N-19:出厂默认口令在用告警(台架锚点;改了 [ftp] pwd_hash 即消失) */ + /* 出厂默认口令在用告警(台架锚点;改了 [ftp] pwd_hash 即消失) */ if (ftp_cfg_is_default_pwd() != 0) { lftpd_log_info("using factory default password"); } diff --git a/Drivers/BSP/NET/lftpd/lftpd.h b/Drivers/BSP/NET/lftpd/lftpd.h index 92c5d671..136fd857 100644 --- a/Drivers/BSP/NET/lftpd/lftpd.h +++ b/Drivers/BSP/NET/lftpd/lftpd.h @@ -6,7 +6,7 @@ typedef struct { char directory[256]; int socket; int data_socket; - /* N-19 FTP 鉴权会话态(每会话 memset 0 = 未登录;跨会话锁定计数在 + /* FTP 鉴权会话态(每会话 memset 0 = 未登录;跨会话锁定计数在 * lftpd.c 的 s_auth_state,不在此处) */ char auth_user[32]; /* USER 阶段暂存的用户名 */ uint8_t auth_user_ok; /* 1 = 用户名与 [ftp] user 匹配 */ diff --git a/Drivers/BSP/NET/lftpd/lftpd_auth.c b/Drivers/BSP/NET/lftpd/lftpd_auth.c index 7755e7ae..c2cc12b0 100644 --- a/Drivers/BSP/NET/lftpd/lftpd_auth.c +++ b/Drivers/BSP/NET/lftpd/lftpd_auth.c @@ -5,7 +5,7 @@ * 作者:王建锋 * 创建日期:2026-10-11 * 修改记录: - * v1.0 2026-10-11 王建锋 创建初始版本(N-19 FTP 鉴权;契约由 + * v1.0 2026-10-11 王建锋 创建初始版本(FTP 鉴权;契约由 * test/host/ftp_auth 主机单测钉死) */ diff --git a/Drivers/BSP/NET/lftpd/lftpd_auth.h b/Drivers/BSP/NET/lftpd/lftpd_auth.h index ed43ceea..0dee68f0 100644 --- a/Drivers/BSP/NET/lftpd/lftpd_auth.h +++ b/Drivers/BSP/NET/lftpd/lftpd_auth.h @@ -7,7 +7,7 @@ * 作者:王建锋 * 创建日期:2026-10-11 * 修改记录: - * v1.0 2026-10-11 王建锋 创建初始版本(N-19 FTP 鉴权;lftpd.c 为唯一消费者) + * v1.0 2026-10-11 王建锋 创建初始版本(FTP 鉴权;lftpd.c 为唯一消费者) */ #ifndef LFTPD_AUTH_H diff --git a/Drivers/BSP/NET/lftpd/lftpd_inet.c b/Drivers/BSP/NET/lftpd/lftpd_inet.c index e14a269d..076c050d 100644 --- a/Drivers/BSP/NET/lftpd/lftpd_inet.c +++ b/Drivers/BSP/NET/lftpd/lftpd_inet.c @@ -6,7 +6,7 @@ #include /* - * E-02 头阻塞修复 + recv pending 化(M3 台架实证): + * 头阻塞修复 + recv pending 化(M3 台架实证): * netTask 是全部 socket 操作的唯一执行者;lftpd 若用**阻塞** net_recv 等待对端 * 下一行/下一块数据 → 只要客户端连上却不发完整行,netTask 就被长期占用, * 其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP 登录让 LCDMEM @@ -109,7 +109,7 @@ int lftpd_inet_accept_timeout(int socket, int timeout_ms) /* F-NET-15 M2(自愈专项实验 2026-10-12):镜像门控退役——原直读 p_sock->state 在 * relisten 忙期(CLOSED 瞬态)误判"已关闭",扔掉 ~4.9s 等待预算;armed 门控 * 认"监听器在服役(LISTENING)或待重建且 RX 有残留(closed-with-data 救回窗)", - * 与 :7003 的 N-32 P1 同型修复 */ + * 与 :7003 的监听生命周期 P1 同型修复 */ if (net_listener_armed(socket) == 0) { /* **故意留 error**:控制连接还活着、我方数据监听却已不在服役 → * 这是本侧异常,不是对端行为 */ diff --git a/Drivers/BSP/NET/net_events.c b/Drivers/BSP/NET/net_events.c index c52d9bbe..0ff7629d 100644 --- a/Drivers/BSP/NET/net_events.c +++ b/Drivers/BSP/NET/net_events.c @@ -123,7 +123,7 @@ void handle_connect_event(net_sock_t *p_sock) { p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); /* 状态 → ESTABLISHED(监听自身转数据通道) */ p_sock->state = NET_SOCK_STATE_ESTABLISHED; - /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置 + /* 监听生命周期 P1:新会话开始 —— 清"上一条会话已结束"电平。必须在置 * ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内 * recv 仍会误得 EOF(把新客户端踢开) */ p_sock->session_dead = 0U; @@ -164,7 +164,7 @@ void handle_connect_event(net_sock_t *p_sock) { p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); /* 状态 → ESTABLISHED */ p_sock->state = NET_SOCK_STATE_ESTABLISHED; - /* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING + /* 监听生命周期 P1:新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING * 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */ p_sock->session_dead = 0U; /* F-NET-17 M3 前置:清可疑电平(同 LISTENING 分支) */ @@ -201,9 +201,9 @@ void handle_disconnect_event(net_sock_t *p_sock) { /* 监听态:DISCONNECT 落在监听器上 */ if (p_sock->state == NET_SOCK_STATE_LISTENING) { - /* TRAP-NET-17 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡; + /* TRAP-NET-17:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡; * 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现); - * N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交 + * 监听生命周期 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交 * net_reconcile_tick 形态③(每 100 ms 读驱动缓存,监听失位即重建)。原处直接 * relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先" * 于消费者 recv(P1 已消除其后果,P2 消除其可能)。 @@ -275,7 +275,7 @@ void handle_disconnect_event(net_sock_t *p_sock) { /* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的 * 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是 * 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测); - * 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空, + * 门原保护目标(:21 @0ms 快复连)已被监听生命周期单一重建入口延迟化(恢复窗实测 0.3~1s)架空, * +100ms 沉降落在窗内不可见。沉降窗口同时让残渣 DISCONNECT 在 state==CLOSED * (无害、仅留痕)时被消费,断开"杀下一条 → 新拆链 → 再杀"的繁殖链 */ { @@ -288,12 +288,12 @@ void handle_disconnect_event(net_sock_t *p_sock) { } /* 正常释放(对端 FIN):断连时挂起项以 EOF(0) 结束 */ p_sock->released_abnormal = 0U; - /* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**, + /* 监听生命周期 P1 不变量 I:置"本会话已结束"电平。须在置 CLOSED **之前**, * 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间 * recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据 * 落到本标志 → 仍得 EOF,不因排空慢而漏报 */ p_sock->session_dead = 1U; - /* N-30(TRAP-NET-20):登记本连接对端端口(幽灵态甄别基准,须在状态 + /* TRAP-NET-20:登记本连接对端端口(幽灵态甄别基准,须在状态 * 迁移前记录——remote_port 此刻仍有效,relisten 清零) */ p_sock->last_peer_port = p_sock->remote_port; /* 状态 → CLOSED */ @@ -382,9 +382,9 @@ void handle_timeout_event(net_sock_t *p_sock) { * NET_ERR_CONNRESET 结束(而非 EOF),应用(lftpd STOR)回报 5xx * 而非误报 226(见 net_pending_tick 断连分支) */ p_sock->released_abnormal = 1U; - /* N-30(TRAP-NET-20):异常释放同样登记对端端口(幽灵甄别基准) */ + /* TRAP-NET-20:异常释放同样登记对端端口(幽灵甄别基准) */ p_sock->last_peer_port = p_sock->remote_port; - /* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST + /* 监听生命周期 P1 不变量 I:异常结束同样是"本会话已结束"电平(TIMEOUT/RST * 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与 * released_abnormal(结束得好不好看)正交) */ p_sock->session_dead = 1U; @@ -564,7 +564,7 @@ void net_reconcile_tick(void) { } else if (p_sock->state == NET_SOCK_STATE_LISTENING) { if (c.tcp_state == CH395F_TCP_ESTABLISHED) { /* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept - * N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实 + * TRAP-NET-20幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实 * 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报), * 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给 * 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。 @@ -590,7 +590,7 @@ void net_reconcile_tick(void) { * 被动修正零风险:不去抖、不占冷却(原 pump 同口径) */ DBG_INFO("sock%d reconcile: chip listening, resync CLOSED->LISTENING (U11)", i); p_sock->state = NET_SOCK_STATE_LISTENING; - /* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 + /* TRAP-NET-20:死连接对端端口转登幽灵甄别基准 + 探针计数清零 * —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的, * 必须转登为基准,否则形态② 的幽灵拦截会失效 */ p_sock->last_peer_port = p_sock->remote_port; @@ -629,7 +629,7 @@ void net_reconcile_tick(void) { DBG_INFO("sock%d reconcile: chip closed (0x%02X/0x%02X) -> disconnect", i, c.open, c.tcp_state); /* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发); - * 只置事实,重建由本 tick 形态③④统一做(N-32 P2 单一所有者) */ + * 只置事实,重建由本 tick 形态③④统一做(监听生命周期 P2 单一所有者) */ handle_disconnect_event(p_sock); } else if (bad == 5) { /* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体 @@ -640,7 +640,7 @@ void net_reconcile_tick(void) { i, c.open, c.tcp_state); /* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */ p_sock->state = NET_SOCK_STATE_LISTENING; - /* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 */ + /* TRAP-NET-20:死连接对端端口转登幽灵甄别基准 + 探针计数清零 */ p_sock->last_peer_port = p_sock->remote_port; p_sock->lost_conn_seen = 0U; /* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */ diff --git a/Drivers/BSP/NET/net_listener.c b/Drivers/BSP/NET/net_listener.c index 4afc48fe..f0a5ada2 100644 --- a/Drivers/BSP/NET/net_listener.c +++ b/Drivers/BSP/NET/net_listener.c @@ -109,7 +109,7 @@ static void net_drain_chip_rx(int sockfd) { * 3. 复用原本地端口重新 OPEN + LISTEN,仅把 state 置回 LISTENING、不 memset * —— 实现 BSD 风格"监听 Socket 常驻",应用层无需手动重监听。 * 4. fd 由参数传入(不用 `p_sock - s_net_socks` 反推),控制块经 net_get_sock() - * 取 —— 本模块**不访问** net_socket.c 的 static 数组(N-32 §10)。 + * 取 —— 本模块**不访问** net_socket.c 的 static 数组(监听生命周期模块化)。 */ static int net_relisten_locked(int sockfd) { uint8_t status; @@ -206,12 +206,12 @@ static int net_relisten_locked(int sockfd) { /* 状态置回 LISTENING(fd 保留;auto_relisten 保持为 1,下次断连仍自动重监听) */ p_sock->state = NET_SOCK_STATE_LISTENING; - /* ⚠ 此处**刻意不清 session_dead**(N-32 P1):重建监听只是"芯片侧恢复就位",与 + /* ⚠ 此处**刻意不清 session_dead**(监听生命周期 P1):重建监听只是"芯片侧恢复就位",与 * "应用是否已确认上一条会话结束"是两件事;清了它,抢先 relisten 之后消费者就再也 * 得不到 EOF(B10"消费者卡死"原型)。它只由新 CONNECT 成功或 memset 清零。 */ /* 新监听不得继承上一条连接的释放原因(TRAP-NET-18 待办 #3) */ p_sock->released_abnormal = 0U; - /* N-30(TRAP-NET-20):清零前把对端端口转登为幽灵甄别基准;去抖计数随之清零 */ + /* TRAP-NET-20:清零前把对端端口转登为幽灵甄别基准;去抖计数随之清零 */ p_sock->last_peer_port = p_sock->remote_port; p_sock->lost_conn_seen = 0U; /* 以下四项 = 清上一条的对端信息,新连接待用 */ @@ -361,7 +361,7 @@ void net_listener_tick(void) { /* 已到放弃时限(0 = 尚未起算,见下) */ if ((s_relisten_dead[i] != 0U) && ((int32_t)(now - s_relisten_dead[i]) >= 0)) { net_listener_clear(i); - /* 预算耗尽 ≠ 放弃:交 reconciler 形态③④ 周期接管(N-32 单一重建入口), + /* 预算耗尽 ≠ 放弃:交 reconciler 形态③④ 周期接管(监听生命周期单一重建入口), * 监听不会永久失效——旧文案 "give up" 夸大了失效语义 */ DBG_ERROR("sock%d relisten retry budget exhausted, reconciler takes over", i); continue; diff --git a/Drivers/BSP/NET/net_listener.h b/Drivers/BSP/NET/net_listener.h index e88464e8..3cbdc83e 100644 --- a/Drivers/BSP/NET/net_listener.h +++ b/Drivers/BSP/NET/net_listener.h @@ -9,7 +9,7 @@ * 作者:王建锋 * 创建日期:2026-10-12 * 修改记录: - * 2026-10-12 王建锋 从 net_socket.c 抽出监听生命周期(N-32 §10 模块化) + * 2026-10-12 王建锋 从 net_socket.c 抽出监听生命周期(模块化,详述 docs/网络/网络自愈架构设计.md §9.3) * 2026-10-14 王建锋 单一所有者收口:net_listener_try(各调用点自带门、当场动作) * 改为 net_listener_request(只登记意图)+ net_listener_tick * (唯一执行者,门集与芯片在听判据集中一处) diff --git a/Drivers/BSP/NET/net_pending.c b/Drivers/BSP/NET/net_pending.c index 74ffeade..673ada68 100644 --- a/Drivers/BSP/NET/net_pending.c +++ b/Drivers/BSP/NET/net_pending.c @@ -10,7 +10,7 @@ * 限定条件:本文件真机专属——模拟器走 sim/net_socket_sim.c, * simulator/CMakeLists.txt 不编译本文件(同 net_listener.c 先例) * - * 阻塞 recv / send 挂起模型(E-02 结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成 + * 阻塞 recv / send 挂起模型(结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成 * "无数据 / 发送缓冲未空 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回 * net_pending_tick / net_send_pending_tick(与 D10 重监听重试同节奏, * net_process_messages 开头每轮调用) @@ -73,7 +73,7 @@ void net_pending_capture_owner(void) { * 返回值:无 * 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知) * 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方直接按通知值 - * 取结果);失败记入 netTask 自己的 errno 槽位(E-03) + * 取结果);失败记入 netTask 自己的 errno 槽位 */ static void net_pending_complete(int sockfd, int result) { /* 本 fd 挂起请求指针 */ @@ -89,7 +89,7 @@ static void net_pending_complete(int sockfd, int result) { if (p->active == 0U) { return; } - /* 失败结果:记入 netTask 自己的错误码槽位(E-03;不再写入跨任务共享全局) */ + /* 失败结果:记入 netTask 自己的错误码槽位(不再写入跨任务共享全局) */ if (result < 0) { (void)net_set_errno(result); } @@ -233,7 +233,7 @@ void net_pending_tick(net_sock_t *p_socks) { } /* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。 - * 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED || + * 判据同 net_recv_locked(监听生命周期 P1 不变量 I):`state == CLOSED || * session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED) * 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时 * (lftpd 阻塞 recv 的"消费者卡死"原型)。无限等待登记没有 ②, @@ -259,7 +259,7 @@ void net_pending_tick(net_sock_t *p_socks) { * 原内层 `state == CLOSED` 门已**上收到 tick**——它防的是"对已回 * LISTENING 的 fd 再 open+listen → 因 USE 错误走进关闭分支、关掉 * 正听着的 Socket"(U12 同源),属执行侧安全不变量,不由登记点复述。 - * N-32 P2 修正:**保留本处即时登记** —— 它只在有**挂起 recv** + * 监听生命周期 P2 修正:**保留本处即时登记** —— 它只在有**挂起 recv** * 时到达(`p->active != 0`),属"消费者在等这条连接结局"的 * 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */ if (p_sock->auto_relisten != 0U) { @@ -312,7 +312,7 @@ void net_pending_tick(net_sock_t *p_socks) { * 出口参数:无 * 返回值:1 - 有挂起;0 - 无(含描述符越界) int * 限定条件:任意上下文(纯 RAM 读,不发起 RPC) - * 函数说明:1. N-32 §10 模块化:net_listener_tick 用它跳过"重开 Socket + * 函数说明:1. 监听生命周期模块化:net_listener_tick 用它跳过"重开 Socket * 会摧毁在途连接"的 fd(挂起 recv 保险,原为直接读 s_net_pending[i].active) * 2. 声明在 net_pending.h(配对头,规范 S17 2.2) */ diff --git a/Drivers/BSP/NET/net_pending.h b/Drivers/BSP/NET/net_pending.h index 2251eca2..ffec901d 100644 --- a/Drivers/BSP/NET/net_pending.h +++ b/Drivers/BSP/NET/net_pending.h @@ -123,7 +123,7 @@ void net_pending_settle_close(int sockfd, net_sock_t *p_sock); * 出口参数:无 * 返回值:1 - 有挂起;0 - 无(含描述符越界) int * 限定条件:任意上下文(纯 RAM 读,不发起 RPC) - * 函数说明:N-32 §10 模块化:net_listener_tick 用它跳过"重开 Socket + * 函数说明:监听生命周期模块化:net_listener_tick 用它跳过"重开 Socket * 会摧毁在途连接"的 fd(挂起 recv 保险) */ int net_pending_active(int sockfd); diff --git a/Drivers/BSP/NET/net_select.c b/Drivers/BSP/NET/net_select.c index 5677e288..fb0f8354 100644 --- a/Drivers/BSP/NET/net_select.c +++ b/Drivers/BSP/NET/net_select.c @@ -255,7 +255,7 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) { * 出口参数:无 * 返回值:1 - 可读(接收缓冲非空);0 - 不可读或描述符无效 int * 限定条件:net_init() 已调用 - * 函数说明:1. P2-7(2026-10-04):LISTENING 态**不再**恒判可读。本网络层无 + * 函数说明:1.(2026-10-04):LISTENING 态**不再**恒判可读。本网络层无 * "待 accept 的挂起连接队列"——新连接到达时 CONNECT 事件把该 socket * 自身直接转为 ESTABLISHED(handle_connect_event:"监听自身转数据通道"), * 故监听态恒无数据可读。原实现对 LISTENING 恒返回 1,会使 @@ -272,7 +272,7 @@ static int check_socket_readable(int sockfd) { return 0; } - /* 监听态恒不可读(见上方 P2-7 说明) */ + /* 监听态恒不可读(见上方说明) */ if (NET_SOCK_STATE_LISTENING == p_sock->state) { return 0; } diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 4073b50f..8e7afd9f 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -141,7 +141,7 @@ int net_msg_queue_adopt(osMessageQueueId_t queue_id) { return 0; } -/* ================= 错误码存储(E-03 修复:按任务隔离,消除跨任务覆写竞态) ================= +/* ================= 错误码存储(按任务隔离,消除跨任务覆写竞态) ================= * * 旧实现是单个全局 `static int s_net_errno`:netTask 在处理消息时写入本层的 * 「最后错误码」,调用方包装器随后在**自己的上下文**再写一次。两条写路径分属不同 @@ -227,7 +227,7 @@ static uint32_t net_errno_slot(void) { * 返回值:err 原值(便于写成 `return net_set_errno(NET_ERR_XXX);`) * 限定条件:无 * 函数说明:1. 只写本任务的槽位,其他任务读不到、也改不了 → 消除跨任务覆写竞态 - * 2. N-32 §10 模块化:由 static 提升为**模块间可见**(net_listener.c + * 2. 监听生命周期模块化:由 static 提升为**模块间可见**(net_listener.c * 也需要设 errno),声明与 NET_ERR_RETURN_1 宏见 net_socket.h */ int net_set_errno(int err) { @@ -332,7 +332,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) { * 无关),且违反保留位规定 → 0x09 改 0x08(2026-10-11 定案:官方手册 * V1C/V1E/V2.2/V2.3 的 bit0 全是保留位,官方例程 bit0=0 下 Socket 0~7 照常 * 可用、FTP 例程本身就传 0x08;证据链见 TRAP-NET-15, - * 0x08 下的多 Socket 台架复测登记 N-31) */ + * 0x08 下的多 Socket 台架复测登记为 Phase 7 复跑项) */ ch395f_set_fun_para(fun_para); /* 设置 TCP MSS:维持 1024(512 裕量方案未采纳,当年支撑它的台架判据不可信, @@ -358,7 +358,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) { /* 设置 MAC(手册 §9.2.1 步骤①:SET_MAC 必须在 INIT_CH395 之前——SET_MAC 写芯片 * EEPROM,而 INIT_CH395 会初始化/加载 MAC;顺序反了则本次上电芯片仍用旧 MAC, * UI 配置的 MAC 不生效(回落芯片内置/上次值),设备在线身份与 UI 显示不符。 - * P1-7,2026-10-04);NULL 或全零 = 保持芯片内置 MAC,不下发; + * 2026-10-04);NULL 或全零 = 保持芯片内置 MAC,不下发; * 写 EEPROM,每上电重放同值幂等 */ if (mac != NULL && s_mac_is_zero(mac) == false) { ch395f_set_mac_addr((uint8_t *)mac); @@ -572,7 +572,7 @@ int net_poll(void) { for (int i = 0; i < NET_MAX_SOCKETS; i++) { if (s_net_socks[i].in_use != 0U) { (void)ch395f_get_sock_state((uint8_t)i); - /* E-02 收口:驱动 sock_reset 状态机在 net_poll 步进 + /* 驱动 sock_reset 状态机在 net_poll 步进 * (驱动头注释契约"在 net_poll 或测试循环里周期调用")。IDLE 槽位 * 纯 RAM 判返回 0 无 SPI;仅活跃复位机做短 SPI。作用:同步包装 * (net_sock_reset_sync)600ms 放弃后复位机不搁浅,芯片条件满足 @@ -663,7 +663,7 @@ void net_process_messages(void) { net_send_pending_tick(s_net_socks); /* TRAP-NET-17 状态对账 + 监听重建合一(单一 100ms tick): * 判据读驱动缓存(net_poll 每轮全量刷新),①②⑤ 失同步 + ③④ 重建 + - * U11 重同步全部在此(本层唯一周期重建入口,N-32 P2 单一所有者) */ + * U11 重同步全部在此(本层唯一周期重建入口,监听生命周期 P2 单一所有者) */ net_reconcile_tick(); /* 队列排空:超时 0 非阻塞取,每条处理一轮,直到本轮队列为空 */ @@ -758,8 +758,8 @@ void net_process_messages(void) { break; } case NET_MSG_DROP_CONN: { - /* E-02 收口:应用放弃会话的主动拆除(拆连接、保监听)。监听型 - * 登记即返(N-17:拆链排渣交 net_listener_tick,本消息不再占用 + /* 应用放弃会话的主动拆除(拆连接、保监听)。监听型 + * 登记即返(探针窗口心跳缺口修复:拆链排渣交 net_listener_tick,本消息不再占用 * netTask 数百 ms) */ net_sock_t *p_sock = net_get_sock(msg.sockfd); if (p_sock == NULL) { @@ -813,7 +813,7 @@ void net_process_messages(void) { uint32_t notify_val = (uint32_t)msg.result; /* 失败:回传本条消息**自身**的精确错误码(NET_ERR_WOULDBLOCK 等)。 - * E-03 后取的是 netTask 自己的错误码槽位——锁定实现(*_locked)在 + * 取的是 netTask 自己的错误码槽位——锁定实现(*_locked)在 * 失败返回前记录的码,不受其他任务影响;未置位时回退通用 NET_ERR。 */ if (msg.result < 0) { int te = s_net_errno_slots[net_errno_slot()].err; @@ -909,7 +909,7 @@ int net_socket(int domain, int type) { p_sock->blocking = 1; /* 新 fd 不得继承上一条连接的释放原因(TRAP-NET-18 待办 #3) */ p_sock->released_abnormal = 0; - /* N-32(P1):新 fd 不得继承"上一条会话已结束"电平(否则新建即 EOF, + /* 监听生命周期 P1:新 fd 不得继承"上一条会话已结束"电平(否则新建即 EOF, * 消费侧连数据都收不到)——与 released_abnormal 同理,显式清零 */ p_sock->session_dead = 0; /* 新 fd 不得继承上一次的待重试状态(D10) */ @@ -1165,7 +1165,7 @@ int net_listen_locked(int sockfd) { p_sock->state = NET_SOCK_STATE_LISTENING; /* 单连接模式:连接断开后由 net 层自动回到 LISTEN(模拟 BSD 监听常驻) */ p_sock->auto_relisten = 1; - /* N-30(TRAP-NET-20):新监听会话,事件未收到去抖计数清零 */ + /* TRAP-NET-20:新监听会话,事件未收到去抖计数清零 */ p_sock->lost_conn_seen = 0U; /* 监听建立完成 */ @@ -1232,7 +1232,7 @@ int net_listen(int sockfd) { * 限定条件:Socket 处于 LISTENING 状态 * 函数说明:单连接:监听 Socket 自身在 CONNECT 后已转为 ESTABLISHED */ -/* ===== N-30 accept 路径"CONNECT 事件未收到补 accept"(2026-10-11 台架实证; +/* ===== TRAP-NET-20 accept 路径"CONNECT 事件未收到补 accept"(2026-10-11 台架实证; * E14 复核 2026-10-14:事件非芯片侧丢失,是被读清消费——机制不变)===== * 台架结论(剥离 OS/net/FTP 直接对芯片做受控 close→open→listen→connect 矩阵): * 芯片关闭连接后的拆链收尾窗口**变长且不可见**(状态寄存器显示 CLOSED 也不代表 @@ -1274,7 +1274,7 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen /* 先轮询一次:把状态机刷新到芯片侧最新状态 */ net_poll(); - /* N-30(TRAP-NET-20):监听等待期兼做芯片状态第二信源——CONNECT 事件未收到时 + /* TRAP-NET-20:监听等待期兼做芯片状态第二信源——CONNECT 事件未收到时 * 去抖补 accept(台架实证:拆链窗口事件未收到是数据被吞/425 的主根因, * E14 复核:事件非芯片丢失、被读清消费吞掉;纯事件等待无自愈可能; * 详见 net_lost_connect_probe 区块注释) */ @@ -1286,13 +1286,13 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen * (含上一步探针补 accept 的命中);其余失同步由 net_reconcile_tick() * 周期对账(带去抖)兜底 */ if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) { - /* N-30(2026-10-11 手册 §5.23⑤ + 生产时间线定案): + /* TRAP-NET-20(2026-10-11 手册 §5.23⑤ + 生产时间线定案): * "closed-but-readable" 接受——STOR 类传输客户端**发完数据立即关数据连接** * (正常 FTP 行为,Python ftplib sendall 后即 close),芯片按 * FUN_PARA **bit3 SOCKET_CLOSE=1**(`0x08`,§5.52 ②)规格保留 Socket 与 RX 缓冲供 MCU 排空 * (实测 DISCONNECT 后 st=0x05/0x00、recv_len=1027=完整载荷)。 * 若 accept 轮询晚于对端 FIN,盲目 relisten(close+open 清缓冲)会把 - * 已到达的数据销毁 → 425(此前 N-30"425/被吞"的主根因,并非芯片杀连接)。 + * 已到达的数据销毁 → 425(此前 TRAP-NET-20"425/被吞"的主根因,并非芯片杀连接)。 * 处置:芯片 RX 缓冲有数据 → 接受这条"已关但可读"连接,应用经 * net_recv_locked 快路径(无状态门槛)排空,排空后 EOF 自然触发 relisten。 */ int drain_pending = 0; @@ -1303,7 +1303,7 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen } else { /* 连接已断开(CLOSED)且无残留数据:登记重建请求,下一次 accept * 即可等待新连接(BSD 风格监听常驻)。 - * N-32 P2 修正:**保留本处即时登记** —— 它由消费者调 accept 触发 + * 监听生命周期 P2 修正:**保留本处即时登记** —— 它由消费者调 accept 触发 * (消费者正在等新连接),不是周期性抢先;删掉会把 CLOSED 窗口 * 拉长、导致调用方高频空转 accept(实测 [WD] task stall)。 * 单一所有者收口后:动作发生在下一轮 net_listener_tick(≤1ms),非阻塞 */ @@ -1446,7 +1446,7 @@ int net_listener_armed(int sockfd) { return 1; } /* 断连待重建态:**仅当芯片 RX 可能仍有残留数据**时放行,让 - * net_accept_locked 的 "closed-but-readable" 接受(N-30,对端"发完即 FIN") + * net_accept_locked 的 "closed-but-readable" 接受(TRAP-NET-20,对端"发完即 FIN") * 仍能拿到该连接。 * ⚠ 无残留时必须返回 0 —— 否则调用方会在整个 CLOSED 窗口内每轮都发起 * net_accept(RPC:入队 + xTaskNotifyWait 回执**无上界**)→ 队列积压 → @@ -1694,11 +1694,11 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a * 交给 CONNECT/TIM_OUT 裁定,不得当成失败(否则慢芯片会被误杀)。 * 其余非 SUCCESS 均为明确的命令级失败(VAL/ARG/USE/OPEN/IF/MEM/BUF/RTE…): * 此前返回值被直接丢弃 → 本层停在 CONNECTING 干等 recv 30 s 超时才暴露 - * (2026-10-12 审计 B2)。按 §9.2.2 ⑧"再次连接需从 ⑤ 重开"回滚。 */ + * (2026-10-12 审计)。按 §9.2.2 ⑧"再次连接需从 ⑤ 重开"回滚。 */ if ((status != CH395F_ERR_SUCCESS) && (status != CH395F_ERR_BUSY)) { DBG_ERROR("[CONN] sock%d TCP_CONNECT rejected by chip: 0x%02X", sockfd, status); (void)ch395f_close_socket((uint8_t)sockfd); - /* N-32(P1 不变量 I):命令级失败同样是"本会话已结束"——此前这里只置 + /* 监听生命周期 P1 不变量 I:命令级失败同样是"本会话已结束"——此前这里只置 * CLOSED,而 CONNECTING 态从未置 session_dead,挂起的 recv 只能干等 * deadline(B2 审计发现的"停在 CONNECTING 干等 30s 超时才暴露"同源) */ p_sock->session_dead = 1U; @@ -1891,7 +1891,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len, * 函数说明:1. 数据已在(快速路径)→ 立即读出返回 * 2. 非阻塞(NET_MSG_DONTWAIT 单次覆盖 **或** socket blocking 属性为 0, * 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK) - * 3. 无数据 + 阻塞等待 → 登记挂起请求(pending 化,E-02 结构演进) + * 3. 无数据 + 阻塞等待 → 登记挂起请求(pending 化,结构演进) * 4. netTask 零占用立即返回;完成条件(数据/到点[仅显式预算]/断连)由 net_pending_tick 按主循环节拍检查 * 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式结束(取消必通知) * 6. CLOSED 状态语义(排空 + EOF + 自动重监听)逐项保留在 tick 内 @@ -1936,7 +1936,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, /* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听, * 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */ /* 连接已断开(CLOSED)**或**本会话已结束(session_dead):按 EOF 处理。 - * 判据从纯 `state == CLOSED` 放宽为 `|| session_dead`(N-32 P1 不变量 I): + * 判据从纯 `state == CLOSED` 放宽为 `|| session_dead`(监听生命周期 P1 不变量 I): * 抢先 relisten 后 state 已是 LISTENING 而会话确实已结束,旧判据在此 * 恒返 WOULDBLOCK → EOF 永久失踪(新连接被旧会话静默吸收)。 * 有数据时走上方快路径(recv_len>0 优先),本判据只在"无数据"时生效, @@ -1948,7 +1948,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, * ch395f_close_socket → 把正听着新 SYN 的 Socket 当场关掉"(U12 同源 * 事故)。这是**执行侧安全不变量**,不该由每个登记点各自复述—— * 漏一处即事故;收口后 tick 另加"芯片已在听即了结",结构上不可能再犯。 - * N-32 P2 修正(2026-10-12 台架实证):**保留本处即时登记**—— + * 监听生命周期 P2 修正(2026-10-12 台架实证):**保留本处即时登记**—— * 它是"消费者动作触发"(消费者正在等这条连接的结局),不是周期性 * "抢先";删掉它会把 CLOSED 窗口从 ~0 拉长到 reconciler 周期+隔离期 * (~100ms),期间调用方高频发起 net_accept(RPC 无上界)→ @@ -2020,7 +2020,7 @@ static int net_close_locked(net_sock_t *p_sock) { } /* 清零控制块(memset 0 = 释放本 fd 全部状态) */ - /* N-30(TRAP-NET-20):幽灵甄别基准跨 fd 释放保留(幽灵属芯片 Socket 级状态, + /* TRAP-NET-20:幽灵甄别基准跨 fd 释放保留(幽灵属芯片 Socket 级状态, * 与 fd 复用无关——close 后新 listen 落在同一芯片 Socket 上仍可能撞幽灵) */ uint16_t keep_peer = p_sock->remote_port; memset(p_sock, 0, sizeof(net_sock_t)); @@ -2042,11 +2042,11 @@ static int net_close_locked(net_sock_t *p_sock) { * 出口参数:无 * 返回值:0 - 拆除完成(监听型 fd 已重监听或交 D10) int * 限定条件:netTask 上下文;p_sock 在用且为 TCP 流 - * 函数说明:1. E-02 收口(《网络自愈架构设计》§5.2 E13):应用放弃的会话若 TCP 仍活 + * 函数说明:1.(《网络自愈架构设计》§5.2 E13):应用放弃的会话若 TCP 仍活 * (PC 内核自动 ACK keepalive,僵尸判不死),单连接监听被永久 * 占住;应用又不得 net_close 监听 fd(毁监听)——本函数补上 * "拆连接不拆监听"的应用侧形态 - * 2. 芯片侧拆除(TRAP-NET-18 异常释放语义,N-17 收口改异步):监听型 fd + * 2. 芯片侧拆除(TRAP-NET-18 异常释放语义,探针窗口心跳缺口收口改异步):监听型 fd * = net_listener_settle(QUARANTINE) + REQ_DROP 登记即返,动作全部交 * net_listener_tick(隔离窗内持续排空/关闭正是该机的既有语义); * 非监听 client fd = 同步 sock_reset QUARANTINE(600ms 封顶,原形态) @@ -2064,7 +2064,7 @@ static int net_drop_conn_locked(net_sock_t *p_sock) { * 在本函数体内**没有任何机器表达**,而函数体是无条件的:置 CLOSED、清 * readable、给监听型 fd 登记重建。也就是说——将来任何新增路径绕过 * NET_MSG_DROP_CONN 的分派门直接调它,就会把一个**健康监听器**当会话拆掉 - * (症状是端口偶发不再 accept,与 N-30 一族同源难查)。断言把这条"进来时 + * (症状是端口偶发不再 accept,与 TRAP-NET-20 一族同源难查)。断言把这条"进来时 * 必须处于活动会话态"钉成机器可查的不变量,正是"不该发生"而不是"外部输入"。 * ⚠ 分派处(case NET_MSG_DROP_CONN)的三条错误返回原样保留——它们负责真机 * 上把误用变成 -1,本断言负责在测试构建里把误用**当场喊出来**。 @@ -2091,7 +2091,7 @@ static int net_drop_conn_locked(net_sock_t *p_sock) { * FIN 5/5 的通过版本)。旧形态在本处理器里 net_sock_reset_sync 同步等待, * netTask 被占用 400ms 底/600ms 顶:串行总线整窗停摆、UI/FTP/对时的 API 回 * 执顺延、NET 与对时位同窗丢 100ms 抽样 → 800ms 停喂声明 stall,距 IWDG - * 最坏超时余量仅 ~0.7s(登记簿 N-17;方向评估与否决论证 → 《网络自愈架构 + * 最坏超时余量仅 ~0.7s(探针窗口心跳缺口;方向评估与否决论证 → 《网络自愈架构 * 设计》§4.6)。返回语义随之明确:返回 = 已登记,芯片未必已拆完。 * 非监听 client fd:无监听可保,维持同步确定性排渣(600ms 封顶;全仓 * 零生产调用者,形态原样保留防漂移)。 */ @@ -2105,7 +2105,7 @@ static int net_drop_conn_locked(net_sock_t *p_sock) { /* 会话簿记收口(保留 fd:不 memset、不动 local_port/auto_relisten) */ p_sock->state = NET_SOCK_STATE_CLOSED; p_sock->readable = 0U; - /* N-30(TRAP-NET-20):新会话起点,事件未收到去抖计数清零 */ + /* TRAP-NET-20:新会话起点,事件未收到去抖计数清零 */ p_sock->lost_conn_seen = 0U; if (p_sock->auto_relisten) { @@ -2293,7 +2293,7 @@ int net_recv_nb(int sockfd, void *buf, int len) { * 出口参数:无 * 返回值:>0 字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据** * 限定条件:非 netTask 上下文;net 层已初始化 - * 函数说明:1. E-02 修复配套接口:lftpd 等"等对端下一行/下一块数据"的调用方 + * 函数说明:1. 修复配套接口:lftpd 等"等对端下一行/下一块数据"的调用方 * 若无限阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住 * 2. 导致其余任务的网络操作整体冻结(M3 实测一次空闲 FTP 登录冻结远端显示 17.7s) * 3. 用本接口把等待切成 ≤timeout_ms 的片,调用方自行累计总预算:总超时语义不变、单次占用有界 @@ -2335,7 +2335,7 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) { msg.len = len; /* 阻塞式:netTask 侧轮询至"有数据"或"本片超时"(勿置 NET_MSG_DONTWAIT) */ msg.flags = 0; - /* 本片预算:netTask 最多等待 timeout_ms,保证对 netTask 的占用有界(E-02) */ + /* 本片预算:netTask 最多等待 timeout_ms,保证对 netTask 的占用有界 */ msg.timeout_ms = timeout_ms; /* 记录调用任务句柄,netTask 处理后经 xTaskNotify() 精准回执 */ msg.caller = xTaskGetCurrentTaskHandle(); @@ -2440,9 +2440,9 @@ int net_close(int sockfd) { * (net 层 auto_relisten 已接管,见 AGENTS 单连接断开语义) * 函数说明:1. 单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(keepalive * 被 PC 内核自动 ACK 判不死,《网络自愈架构设计》§5.2 E13);应用不得 - * net_close 监听 fd → 本 API 拆连接不拆监听(E-02 收口) + * net_close 监听 fd → 本 API 拆连接不拆监听 * 2. 监听型 fd:netTask 侧仅登记隔离沉降 + 重建请求即返回(动作在 - * net_listener_tick,netTask 零占用,登记簿 N-17 收口);非监听 client + * net_listener_tick,netTask 零占用,探针窗口心跳缺口收口);非监听 client * fd 仍同步排渣(600ms 封顶)。见 net_drop_conn_locked */ int net_drop_conn(int sockfd) { @@ -2886,7 +2886,7 @@ int net_setblocking(int sockfd, int on) { * 出口参数:无 * 返回值:错误码(NET_OK=0, NET_ERR_xxx=-N);本任务无失败记录时为 NET_OK * 限定条件:无(任何上下文可调用;中断上下文读 [0] 槽) - * 函数说明:1. E-03 修复:错误码**按任务隔离**——只读本任务自己的槽位, + * 函数说明:1. 错误码**按任务隔离**——只读本任务自己的槽位, * 其他任务的 net 调用再也覆盖不了它(旧实现为单一全局量,跨任务竞态) * 2. 调用成功时**不清零**(保持历史语义,向后兼容既有调用方) * 3. 超时场景(net_recv_to)请直接用返回码 -2 判定,不要依赖本接口 diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index ac0688df..9014ea1c 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -38,7 +38,7 @@ typedef enum { NET_MSG_CONNECT, NET_MSG_LISTEN, NET_MSG_ACCEPT, - NET_MSG_DROP_CONN, /* E-02 收口:主动拆会话、保监听(见 net_drop_conn) */ + NET_MSG_DROP_CONN, /* 主动拆会话、保监听(见 net_drop_conn) */ /* F-NET-2/3:NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删—— * 静默回收由对账⑤自动认领(net_reconcile_tick),重建由 reconciler * 形态③④/D10 单一 @@ -186,7 +186,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen); * 函数说明:1. 不变量 II(单一重建所有者;补齐 ESTABLISHED 放行):取代已退役的 * `net_can_accept()`(只认 ESTABLISHED)作为监听 accept 门控——原门控使 * :7003 永不进 `net_accept()`, - * `net_lost_connect_probe`(CONNECT 事件未收到补 accept,N-30 实证的 + * `net_lost_connect_probe`(CONNECT 事件未收到补 accept,TRAP-NET-20 实证的 * "数据被吞/425"主根因修复)对该端口等于从未运行 * 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在 * `net_accept_locked` **内部**,门控挡在外面就永远到不了 @@ -196,7 +196,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen); * (i104 200ms 抢不过对账② ~400ms 去抖)永不 accept、会话整场饿死 * (实测全 18 项 FAIL)。此态 accept 必返 fd,无风暴风险 * 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据): - * 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**, + * 保住 TRAP-NET-20 的 "closed-but-readable" 接受;**无残留时必须返回 0**, * 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界) * → 队列积压 → 喂不上狗(2026-10-12 P2a 台架实测 48 条 task stall)。 * 重建由 `net_reconcile_tick` 形态③④ 负责,调用方无需催促 @@ -251,7 +251,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen); * 拷贝数据后触发发送——健康链路下(实测 219us 量级)行为与旧版逐字节一致 * 2. 慢路径:自旋超预算后把请求**挂起登记**给 netTask,由 * net_send_pending_tick 在发送缓冲区空闲时续传(分片 NET_SEND_CHUNK_MAX), - * 调用方在任务通知上睡眠——**不再占用 netTask 执行者**(消除 E-02 队头阻塞) + * 调用方在任务通知上睡眠——**不再占用 netTask 执行者**(消除队头阻塞) * 3. 短写语义:无论快路径超时还是挂起完成,只要存在未写完字节即按已发数返回, * 不假装全部成功;调用方按短写重试剩余部分(见 BSD_Socket_API 使用指南 §3.5) * 4. ⚠ 禁止在 IWDG required_mask 内任务做**无预算**的整块发送;挂起路径本身 @@ -319,7 +319,7 @@ int net_setblocking(int sockfd, int on); * 函数说明:1. **显式预算**便捷入口(等价 Python settimeout, * 不再有隐藏默认值):net_recv 的无限阻塞 + 本接口的显式预算构成 * 超时语义的全部来源;超时首选仍是 net_select - * 2. E-02 修复配套:lftpd 等"等对端下一行/下一块数据"的调用方 + * 2. 修复配套:lftpd 等"等对端下一行/下一块数据"的调用方 * 若无限阻塞 recv,会把 netTask(全部 socket 操作的唯一执行者)长期占住 * 3. 用本接口把等待切成 ≤timeout_ms 的片,调用方自行累计总预算:总超时语义不变、单次占用有界 * 4. 超时用独立返回码 -2(而非靠 errno 区分)→ 无跨任务 errno 竞态 @@ -394,7 +394,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags, int net_close(int sockfd); /* - * 函数功能:主动拆掉当前 TCP 会话,**保留监听**(E-02 收口) + * 函数功能:主动拆掉当前 TCP 会话,**保留监听** * 入口参数:sockfd - socket 描述符 * 返回值:0 成功(**监听型 fd 的“成功”= 拆除与重建已登记**,芯片未必已拆完, * 见函数说明 2;含幂等:无活动会话),-1 失败 @@ -403,14 +403,14 @@ int net_close(int sockfd); * 单连接模型禁止对监听 fd net_close(连监听一起毁,AGENTS 约定), * 而挂起但 TCP 仍活的客户端会永久占住单连接监听(PC 内核自动 * ACK keepalive,芯片僵尸判不死——《网络自愈架构设计》§5.2 E13 定案缺口) - * 2. 实现(2026-10-25 N-17 起):auto_relisten fd = 登记 QUARANTINE 隔离 + * 2. 实现(2026-10-25 探针窗口心跳缺口修复起):auto_relisten fd = 登记 QUARANTINE 隔离 * 沉降 + REQ_DROP 重建请求,**登记即返**——“FIN 关闭 + 排渣 + 排空 * 事件 + 等 byte0=00H”(TRAP-NET-18 异常释放语义)与重建由 * net_listener_tick 按 30ms 节奏异步执行,端口恢复窗 ~0.4-1s 与被动 * 断连同型;**调用方不得按旧语义假设“返回即芯片已 CLOSED”**——返回 * 后 accept/recv 得 WOULDBLOCK 直至重建完成(time_sync 既有轮询天然 * 容忍)。旧同步形态曾使 netTask 占用 400-600ms、五任务心跳同窗 - * 缺席声明 stall(《网络自愈架构设计》§4.6 N-17 记录,勿复活)。 + * 缺席声明 stall(《网络自愈架构设计》§4.6 记录,勿复活)。 * 非 auto_relisten(client fd)无监听可保,仍同步排渣(600ms 封顶) * 3. 幂等:非 ESTABLISHED/CONNECTING 直接返 0,不动监听 */ @@ -441,7 +441,7 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg); * 出口参数:无 * 返回值:错误码(NET_ERR_xxx);本任务尚无失败记录时为 NET_OK(0) * 限定条件:无(任何上下文可调用,含中断——查询为纯本任务索引读) - * 函数说明:1. **按任务隔离**(2026-09-25 E-03 修复):错误码存在每个任务自己的 + * 函数说明:1. **按任务隔离**(2026-09-25):错误码存在每个任务自己的 * 槽位里,任务间互不干扰,不存在跨任务覆写竞态——旧实现的单一全局 * 变量会被 netTask/其他任务的 net 调用交错覆盖,调用方可能读到别人的码 * 2. 语义:本任务最后一次返回 -1 的 net_*() 调用所对应的精确码; @@ -562,13 +562,13 @@ net_sock_t *net_sock_raw(int sockfd); * 返回值:err 原值(便于写成 `return net_set_errno(NET_ERR_XXX);`) * 限定条件:无 * 函数说明:1. 只写本任务的槽位,其他任务读不到、也改不了 → 消除跨任务覆写竞态 - * 2. N-32 §10 模块化:由 net_socket.c 的 static 提升为**模块间可见**, + * 2. 监听生命周期模块化:由 net_socket.c 的 static 提升为**模块间可见**, * 供 net_listener.c 等同目录内部模块复用(定义仍在 net_socket.c) */ int net_set_errno(int err); /* 「记本任务错误码 + 统一失败返回 -1」的紧凑写法(取代 `s_net_errno = X; return -1;`) - * N-32 §10:原定义在 net_socket.c,随 net_set_errno 一并提升到本头文件 */ + * 监听生命周期模块化:原定义在 net_socket.c,随 net_set_errno 一并提升到本头文件 */ #define NET_ERR_RETURN_1(err) (net_set_errno(err), -1) /* diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h index 8aa4bbf5..72a565c8 100644 --- a/Drivers/BSP/NET/net_types.h +++ b/Drivers/BSP/NET/net_types.h @@ -203,18 +203,18 @@ typedef struct { * 连接的释放原因。 */ uint8_t released_abnormal; - /* N-30(2026-10-11 裸机芯片台架实证):本 Socket 上一条被释放连接的**对端端口**。 + /* TRAP-NET-20(2026-10-11 裸机芯片台架实证):本 Socket 上一条被释放连接的**对端端口**。 * 芯片拆链收尾窗口内旧连接未拆净,Socket 如实为 ESTABLISHED(幽灵态,E15: * bit3=1 保留现场契约,非谎报),此时回读的对端端口 * = 旧连接端口;新连接的临时端口几乎必然不同。accept 路径补 accept 与对账② * 均以此甄别幽灵态(见 net_socket.c TRAP-NET-20)。跨 fd 释放保留(幽灵属芯片 * Socket 级,与 fd 复用无关),新连接断开时刷新。 */ uint16_t last_peer_port; - /* N-30:accept 路径"CONNECT 事件未收到补 accept"的去抖计数(连续确认次数, + /* TRAP-NET-20:accept 路径"CONNECT 事件未收到补 accept"的去抖计数(连续确认次数, * 节拍 = accept 轮询 100ms;新监听/补 accept 后清零) */ uint8_t lost_conn_seen; - /* N-32 / B10 根治 P1(2026-10-12,设计文档 §2.3 不变量 I):本会话"已结束"的 + /* 监听生命周期 P1(B10 根治,2026-10-12,设计文档 §2.3 不变量 I):本会话"已结束"的 * **电平**(粘滞),取代 `state == CLOSED` 这一**边沿**。 * 为什么必须电平化:单连接复用模型下同一个 fd 既是监听器又是连接(handle * aliasing),"关掉一条连接"与"毁掉一个监听器"在类型上无法区分;而"会话结束" diff --git a/Drivers/BSP/NETCFG/ftp_cfg.c b/Drivers/BSP/NETCFG/ftp_cfg.c index 8301b711..1ddc902c 100644 --- a/Drivers/BSP/NETCFG/ftp_cfg.c +++ b/Drivers/BSP/NETCFG/ftp_cfg.c @@ -5,7 +5,7 @@ * 作者:王建锋 * 创建日期:2026-10-11 * 修改记录: - * v1.0 2026-10-11 王建锋 创建初始版本(N-19 FTP 鉴权) + * v1.0 2026-10-11 王建锋 创建初始版本(FTP 鉴权) */ /* 头文件包含区 */ diff --git a/Drivers/BSP/NETCFG/ftp_cfg.h b/Drivers/BSP/NETCFG/ftp_cfg.h index 04d63037..edb5f509 100644 --- a/Drivers/BSP/NETCFG/ftp_cfg.h +++ b/Drivers/BSP/NETCFG/ftp_cfg.h @@ -7,7 +7,7 @@ * 作者:王建锋 * 创建日期:2026-10-11 * 修改记录: - * v1.0 2026-10-11 王建锋 创建初始版本(N-19 FTP 鉴权) + * v1.0 2026-10-11 王建锋 创建初始版本(FTP 鉴权) */ #ifndef FTP_CFG_H @@ -24,7 +24,7 @@ extern "C" { /* 工厂默认:user=admin、口令=admin(pwd_hash = SHA256("adminadmin"))、write=1。 * write=1 保持既有维护工作流(FTP 推 config.ini);生产配置应改口令并按需 write=0。 - * 出厂默认口令在用期间,lftpd_start 每次启动打 INFO 告警锚点(N-19) */ + * 出厂默认口令在用期间,lftpd_start 每次启动打 INFO 告警锚点(FTP 鉴权) */ #define FTP_CFG_DEF_USER "admin" #define FTP_CFG_DEF_HASH "d82494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892" #define FTP_CFG_DEF_WRITE 1u diff --git a/Drivers/BSP/NETCFG/net_cfg.c b/Drivers/BSP/NETCFG/net_cfg.c index 82c4b3c4..c3c94619 100644 --- a/Drivers/BSP/NETCFG/net_cfg.c +++ b/Drivers/BSP/NETCFG/net_cfg.c @@ -10,7 +10,7 @@ * v1.0 2026-09-10 王建锋 创建初始版本 * v1.1 2026-09-14 王建锋 存储迁移至 cfg_file 引擎(INI);旧 NCFG 代码移至 net_cfg_legacy * v1.2 2026-09-15 王建锋 保存改跨任务交接(request/state/run + 通知回调); - * 引擎属主 = defaultTask(P1-3 竞态修复) + * 引擎属主 = defaultTask(竞态修复) * v1.3 2026-10-10 王建锋 **删除旧档 NCFG 迁移**(net_cfg_legacy 整体移除):该兼容 * 路径把整条配置写链(net_cfg_save → cfg_file_save → * cfg_store_write_atomic → f_rename → FTL)挂进启动期调用图, @@ -148,7 +148,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) { */ int net_cfg_load(void) { s_net_register(); - /* N-19:[ftp] 段一并注册(所有受管段必须先注册、后 load,引擎按注册表解析) */ + /* [ftp] 段一并注册(所有受管段必须先注册、后 load,引擎按注册表解析) */ ftp_cfg_register(); /* 旧档 NCFG 迁移已于 2026-10-10 删除(net_cfg_legacy 移除;理由见文件头 v1.3)。 diff --git a/Drivers/BSP/NETCFG/net_cfg.h b/Drivers/BSP/NETCFG/net_cfg.h index bbe32a9b..b3ebf4c9 100644 --- a/Drivers/BSP/NETCFG/net_cfg.h +++ b/Drivers/BSP/NETCFG/net_cfg.h @@ -17,7 +17,7 @@ * v1.0 2026-09-10 王建锋 创建初始版本 * v1.1 2026-09-14 王建锋 存储迁移至 cfg_file 引擎(INI [network] 段) * v1.2 2026-09-15 王建锋 保存改跨任务交接(request/state/run + 通知回调); - * 引擎属主明确为 defaultTask(P1-3 竞态修复) + * 引擎属主明确为 defaultTask(竞态修复) */ #ifndef NET_CFG_H diff --git a/Drivers/BSP/RS485/rs485.c b/Drivers/BSP/RS485/rs485.c index a3a5b8a9..ca2ff235 100644 --- a/Drivers/BSP/RS485/rs485.c +++ b/Drivers/BSP/RS485/rs485.c @@ -10,7 +10,7 @@ * 2026-07-18 王建锋 创建初始版本 * 2026-07-20 王建锋 修复 DE 时序:添加 DE 建立延时替代 for 循环 * 2026-07-20 王建锋 清理中文注释乱码,对齐代码规范 - * 2026-10-05 王建锋 P3-4:模块描述订正为实际实现 HAL_Delay(1) + * 2026-10-05 王建锋 模块描述订正为实际实现 HAL_Delay(1) * (原描述称 DWT CYCCNT 微秒延时,与代码不符) */ diff --git a/Drivers/BSP/SD2506/sd2506.c b/Drivers/BSP/SD2506/sd2506.c index 698b8106..819b7867 100644 --- a/Drivers/BSP/SD2506/sd2506.c +++ b/Drivers/BSP/SD2506/sd2506.c @@ -38,7 +38,7 @@ uint8_t sd2506_dec_to_bcd(uint8_t dec) { } /* - * 函数功能:把 HAL 的 I2C 状态映射成统一错误码(S-5,2026-10-23) + * 函数功能:把 HAL 的 I2C 状态映射成统一错误码(2026-10-23) * 入口参数:status - HAL_I2C_* 返回的状态 * 返回值:HAL_OK → BSP_OK;HAL_TIMEOUT → BSP_ERR_TIMEOUT(可重试);其余 → BSP_ERR_IO * 限定条件:仅本文件内部使用 @@ -206,7 +206,7 @@ bsp_err_t sd2506_init(void) { /* 验证 I2C 通信: 尝试读取 8 字节 ID */ ret = sd2506_read_regs(SD2506_REG_ID_START, a_id, SD2506_ID_SIZE); if (ret != BSP_OK) { - /* 启动段可观测性(2026-10-02 S10):本函数原先所有失败路径均静默返回, + /* 启动段可观测性(2026-10-02 加):本函数原先所有失败路径均静默返回, * 而它是启动死机最可疑的源头(I2C1 硬件不通即卡此步且无任何输出)。 * 日志字符串必须纯 ASCII(文件无 BOM,ARMCC 按 CP936 解码)。 */ DBG_ERROR("sd2506_init: ID read FAIL (I2C err=%d)", ret); diff --git a/Drivers/BSP/SD2506/sd2506.h b/Drivers/BSP/SD2506/sd2506.h index 579965a9..b2c61eb4 100644 --- a/Drivers/BSP/SD2506/sd2506.h +++ b/Drivers/BSP/SD2506/sd2506.h @@ -137,7 +137,7 @@ extern "C" { #define SD2506_CTR1_WRITE_OFF 0x7BU /* ======================== 返回值定义 ======================== - * 本模块**不再自带错误码**(2026-10-23,S-5):统一走 Drivers/BSP/bsp_err.h。 + * 本模块**不再自带错误码**(2026-10-23):统一走 Drivers/BSP/bsp_err.h。 * 旧码映射 —— SD2506_OK → BSP_OK;SD2506_ERROR(参数类)→ BSP_ERR_PARAM; * SD2506_I2C_ERROR → BSP_ERR_IO,其中 I2C 超时刻意单独归成 **BSP_ERR_TIMEOUT * (可重试)**:旧码把"总线上没人应答"和"超时"混成一个 -2,调用方没法规避。 diff --git a/Drivers/BSP/TPAFE5160/README.md b/Drivers/BSP/TPAFE5160/README.md index f017462c..9b75ec12 100644 --- a/Drivers/BSP/TPAFE5160/README.md +++ b/Drivers/BSP/TPAFE5160/README.md @@ -18,12 +18,12 @@ - **周期重触发由 TIM3 更新事件(1 kHz)驱动**(2026-10-10):`app_main_init` 末尾 `HAL_TIM_Base_Start_IT(&htim3)` 启动节拍,`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 TIM3 分支内调 `tpafe5160_start_conv_irq()`(ISR 内仅两次 GPIO 写)。B1 阶段曾由 `adcTask` 每 1 ms 软触发(已改)。⚠ **节拍源必须是 TIM3 而非 TIM6**:TIM3 的同一个更新事件还经 **TRGO** 硬件触发 STM32 ADC1,两片转换器由此同源对齐;F4 上 TIM6/TIM7 的 TRGO 只接 DAC,喂不到 ADC1 - **取数只用 `tpafe5160_snapshot(int16_t *dst, uint8_t count)`**(**仅任务上下文**,内部临界区原子拷贝;返回 1=新帧 / 0=无新帧 / -1=参数无效)。配套的 `tpafe5160_frame_seq()` 给帧序号、`tpafe5160_frstdata_err()` 给"FRSTDATA 无跳变"帧计数 -- ⚠️ **不要用缓冲区指针取数**:旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已于 2026-10-10 **删除**(审计 E-45 / E-58)——`get_buf` 只给指针、任务再逐通道拷,中间被 BUSY EXTI 抢占就拷回**两帧混合**数据;`s_ready` 电平标志在 `start_conv_irq` 里被清 0,等于**用重触发去丢帧**。现由**帧序号**(只增不清)+ **临界区快照**取代 -- **`tpafe5160_init()` 做首帧有效性自检**(E-44):完整跑一帧转换+读取,若整帧读下来 FRSTDATA **没有发生任何跳变**(芯片没在驱动并行总线;板上上拉会把它钉死为高)→ 返回 `BSP_ERR_NO_DEVICE`(旧 `TPAFE5160_FRSTDATA_ERR(-3)`,2026-10-23 S-5 收编)并打 `[ERR] Init FRSTDATA no-toggle`。判据刻意不锁死"哪个 RD 边沿拉高/拉低"(手册只给"RD 下降沿 → FRSTDATA 高/低"两个跳变,本板未实测边沿次序),只要求"一帧内必须跳变" → 既抓死芯片,也不会因极性假设错误误杀好芯片 +- ⚠️ **不要用缓冲区指针取数**:旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已于 2026-10-10 **删除**——`get_buf` 只给指针、任务再逐通道拷,中间被 BUSY EXTI 抢占就拷回**两帧混合**数据;`s_ready` 电平标志在 `start_conv_irq` 里被清 0,等于**用重触发去丢帧**。现由**帧序号**(只增不清)+ **临界区快照**取代 +- **`tpafe5160_init()` 做首帧有效性自检**:完整跑一帧转换+读取,若整帧读下来 FRSTDATA **没有发生任何跳变**(芯片没在驱动并行总线;板上上拉会把它钉死为高)→ 返回 `BSP_ERR_NO_DEVICE`(旧 `TPAFE5160_FRSTDATA_ERR(-3)`,2026-10-23 收编)并打 `[ERR] Init FRSTDATA no-toggle`。判据刻意不锁死"哪个 RD 边沿拉高/拉低"(手册只给"RD 下降沿 → FRSTDATA 高/低"两个跳变,本板未实测边沿次序),只要求"一帧内必须跳变" → 既抓死芯片,也不会因极性假设错误误杀好芯片 - 采样数据的消费者 `App/task/adc_task.c` **已实现**(B1,2026-10-10:`tpafe5160_snapshot()` 原子取数 + 帧统计;`adcTask` 在 `Src/freertos.c` 的 `USER CODE` 区创建)。**v2.2(2026-10-10)**:采样节拍改由定时器更新中断硬件触发,本任务退化为纯消费者;**v2.8(2026-10-10)**:节拍源改 TIM3(同时经 TRGO 触发 ADC1 + DMA),STM32 侧改为消费 DMA 乒乓缓冲,单拍耗时 122 µs → **8 µs** - CONVST→BUSY→RD 三段时序对**建立/保持时间敏感**,改动需对照芯片手册 - ⚠️ 本项目 ADC 相关功能**新增任务/缓冲前须核算 RAM**(见 `docs/内存管理/`);接消费者前的强制门禁见根 `AGENTS.md`「接 ADC / RS-485 消费者前的强制门禁」 -- ⚠️ **通道语义不是通道号**:`buf[i]` 对应哪个电气量必须查 `docs/硬件平台/ADC通道映射说明.md`(`buf[2]=IA`、`buf[5]=UN`、`buf[6]=UA`);V0.1 上 8 路电气量分属两片转换器属硬件缺陷(N-18)→ **禁止**跨转换器做相位/功率/功率因数类运算 +- ⚠️ **通道语义不是通道号**:`buf[i]` 对应哪个电气量必须查 `docs/硬件平台/ADC通道映射说明.md`(`buf[2]=IA`、`buf[5]=UN`、`buf[6]=UA`);V0.1 上 8 路电气量分属两片转换器属硬件缺陷(登记簿条目「双转换器无共同采样触发」)→ **禁止**跨转换器做相位/功率/功率因数类运算 ## 已知问题 diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.c b/Drivers/BSP/TPAFE5160/tpafe5160.c index cb04e1d1..fb77ae00 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.c +++ b/Drivers/BSP/TPAFE5160/tpafe5160.c @@ -8,7 +8,7 @@ * 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册 * 2026-07-17 王建锋 增加 EXTI 中断读取模式 * 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变) - * 2026-10-10 王建锋 消费者接入前置修复(E-44 / E-45 / E-58),详见 tpafe5160.h + * 2026-10-10 王建锋 消费者接入前置修复,详见 tpafe5160.h */ /* 头文件包含区 */ @@ -17,7 +17,7 @@ #include "tpafe5160.h" #include "main.h" -/* taskENTER_CRITICAL:把"选缓冲 + 拷数据"收进一个不可分割动作(E-58)。 +/* taskENTER_CRITICAL:把"选缓冲 + 拷数据"收进一个不可分割动作。 * 与 App/log/console_log.c、Drivers/BSP/NET/net_socket.c 同一写法,仅任务上下文可用 */ #include "FreeRTOS.h" #include "task.h" @@ -73,7 +73,7 @@ static bsp_err_t tpafe5160_read_channels(uint8_t count, int16_t *buf); * 函数说明:1. 设置过采样为无过采样 (000) * 2. 确保 RD 为高、CONVST 为低 * 3. 等待 BUSY 释放(转换空闲) - * 4. 采一帧并校验 FRSTDATA 有跳变(E-44:旧实现只看 BUSY,芯片死亡 + * 4. 采一帧并校验 FRSTDATA 有跳变(旧实现只看 BUSY,芯片死亡 * 时照样报 Init OK,此后所有读数都是上拉/总线残值——静默死亡) */ bsp_err_t tpafe5160_init(void) { @@ -91,7 +91,7 @@ bsp_err_t tpafe5160_init(void) { return ret; } - /* 首帧有效性自检(E-44):一次完整转换 + 读取,确认芯片真的在响应 RD */ + /* 首帧有效性自检:一次完整转换 + 读取,确认芯片真的在响应 RD */ int16_t probe[TPAFE5160_CH_NUM]; tpafe5160_start_conv(); ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); @@ -171,7 +171,7 @@ uint8_t tpafe5160_is_busy(void) { * 限定条件:GPIO 已初始化 * 函数说明:1. 启动转换并等待 BUSY 释放 * 2. 连续8次拉低RD读取各通道数据 - * 3. 校验 FRSTDATA 在这一帧内发生跳变(E-44) + * 3. 校验 FRSTDATA 在这一帧内发生跳变 */ bsp_err_t tpafe5160_read_all(int16_t *buf) { bsp_err_t ret = BSP_OK; @@ -266,7 +266,7 @@ uint16_t tpafe5160_read_bus(void) { * t14 数据建立 ≤ 21ns → 数据在 RD 下降沿后 21ns 内有效 * 5 个 NOP (~30ns) 覆盖建立时间 * - * E-44(数据有效性):FRSTDATA 由芯片主动驱动,板上 R24 4.7k 上拉(V0.1 网表)。 + * 数据有效性:FRSTDATA 由芯片主动驱动,板上 R24 4.7k 上拉(V0.1 网表)。 * 手册 Parallel Data Read:FRSTDATA 随首个 RD 下降沿变高标记通道 1 可读,t26/t27 * 给出"RD 下降沿 → FRSTDATA 高/低"两个跳变。判据刻意**不锁死具体是哪个 RD 边沿 * 拉高/拉低**(本板未实测确认边沿次序),只要求"一帧读下来必须发生跳变": @@ -304,11 +304,11 @@ static bsp_err_t tpafe5160_read_channels(uint8_t count, int16_t *buf) { static int16_t s_buf_a[TPAFE5160_CH_NUM]; static int16_t s_buf_b[TPAFE5160_CH_NUM]; static volatile uint8_t s_buf_sel = 0; /* 0 = 写 B / 读 A, 1 = 写 A / 读 B */ -/* E-45:帧序号取代"就绪电平标志"。旧 s_ready 有三个写者,其中 start_conv_irq +/* 帧序号取代"就绪电平标志"。旧 s_ready 有三个写者,其中 start_conv_irq * 在触发前清 0 —— 重触发这个动作本身就会抹掉一帧还没被消费的数据(高速采样 * 系统性丢样,且无从发现)。序号只增不清,消费者比较序号即可 */ static volatile uint32_t s_frame_seq = 0; -/* E-44:FRSTDATA 无跳变帧计数。中断里只加计数(不打日志),由消费者/自检侧上报 */ +/* FRSTDATA 无跳变帧计数。中断里只加计数(不打日志),由消费者/自检侧上报 */ static volatile uint16_t s_frstdata_err = 0; /* 消费者游标:只在任务上下文读写(snapshot 内部),中断不碰 */ static uint32_t s_consumer_seq = 0; @@ -341,7 +341,7 @@ void tpafe5160_irq_disable(void) { * 返回值:无 * 限定条件:已调用 tpafe5160_irq_enable() * 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据。 - * E-45:这里**不再清就绪标志**——旧实现"触发前置 0"等于用重触发去丢帧 + * 这里**不再清就绪标志**——旧实现"触发前置 0"等于用重触发去丢帧 */ void tpafe5160_start_conv_irq(void) { TPAFE5160_CONVST_PULSE(); @@ -352,7 +352,7 @@ void tpafe5160_start_conv_irq(void) { * 入口参数:无 * 返回值:uint32_t 帧序号(EXTI 每读完一帧 +1) * 限定条件:中断模式已启用 - * 函数说明:见 tpafe5160.h 同名声明的契约说明(E-45) + * 函数说明:见 tpafe5160.h 同名声明的契约说明 */ uint32_t tpafe5160_frame_seq(void) { return s_frame_seq; @@ -363,7 +363,7 @@ uint32_t tpafe5160_frame_seq(void) { * 入口参数:无 * 返回值:uint16_t 计数 * 限定条件:中断模式已启用 - * 函数说明:非 0 = 至少一帧的 8 个值不可信(E-44),消费者据此上报自检 + * 函数说明:非 0 = 至少一帧的 8 个值不可信,消费者据此上报自检 */ uint16_t tpafe5160_frstdata_err(void) { return s_frstdata_err; @@ -375,7 +375,7 @@ uint16_t tpafe5160_frstdata_err(void) { * count - 拷贝通道数 uint8_t 1 - 8 * 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效 * 限定条件:**任务上下文**(内部 taskENTER_CRITICAL,中断里不可调用) - * 函数说明:E-58——把"选缓冲 + 拷数据 + 记游标"收进一个临界区。旧 get_buf() 只给 + * 函数说明:把"选缓冲 + 拷数据 + 记游标"收进一个临界区。旧 get_buf() 只给 * 指针、任务再逐通道拷,两步之间被 BUSY EXTI 抢占并跨过一帧周期就拷回 * 两帧混合的数据(8 通道失去同一时刻性)且无任何错误指示 */ @@ -422,11 +422,11 @@ void tpafe5160_exti_handler(uint16_t GPIO_Pin) { /* 连续读取8个通道(内含 FRSTDATA 有效性校验),约 0.5µs @ 168MHz */ int ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, p_wr); - /* 切换缓冲区并推进帧序号(E-45:序号只增不清,重触发不会抹掉未消费帧) */ + /* 切换缓冲区并推进帧序号(序号只增不清,重触发不会抹掉未消费帧) */ s_buf_sel ^= 1u; s_frame_seq++; - /* E-44:中断里不打日志,只累计;消费者据 tpafe5160_frstdata_err() 上报自检 */ + /* 中断里不打日志,只累计;消费者据 tpafe5160_frstdata_err() 上报自检 */ if (ret != BSP_OK && s_frstdata_err < 0xFFFFu) { s_frstdata_err++; } diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.h b/Drivers/BSP/TPAFE5160/tpafe5160.h index 2686c65c..81e67a50 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.h +++ b/Drivers/BSP/TPAFE5160/tpafe5160.h @@ -11,7 +11,7 @@ * 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册 * 2026-07-17 王建锋 增加 EXTI 中断读取模式 * 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变) - * 2026-10-10 王建锋 消费者接入前置修复(历史审计 E-44 / E-45 / E-58,属主文档已删除): + * 2026-10-10 王建锋 消费者接入前置修复: * FRSTDATA 数据有效性校验 + 帧序号取代就绪电平标志 + * 原子快照 API;移除会撕裂的 get_buf/data_ready/clear_ready */ @@ -69,11 +69,11 @@ typedef enum { } tpafe5160_range_t; /* 返回值定义 */ -/* 本模块的成败码已收编到 Drivers/BSP/bsp_err.h(2026-10-23,S-5 C1): +/* 本模块的成败码已收编到 Drivers/BSP/bsp_err.h(2026-10-23,C1): * 旧 TPAFE5160_OK(0) → BSP_OK;旧 TPAFE5160_ERROR(-1) → BSP_ERR_PARAM(三处全是参数校验, * 数值也同为 -1,**行为零变化**);旧 TPAFE5160_BUSY_TIMEOUT(-2) → BSP_ERR_TIMEOUT, * 于是 BUSY 不释放终于变成跨模块可判定的"可重试"; - * 旧 TPAFE5160_FRSTDATA_ERR(-3) → BSP_ERR_NO_DEVICE——它就是 E-44 的"芯片没驱动 DB 总线", + * 旧 TPAFE5160_FRSTDATA_ERR(-3) → BSP_ERR_NO_DEVICE——它就是"芯片没驱动 DB 总线", * 语义是**器件不在响应**,归到 NO_DEVICE 比留在私有 -3 更有用(且避开与 BSP_ERR_TIMEOUT 撞值)。 * ⚠ 例外:**tpafe5160_snapshot() 不是成败 API**,它是 1=有新帧 / 0=无新帧 / -1=参数错 的 * 三态**状态**返回(0 不是错误,是"这一拍没有新数据"),因此**不套 bsp_err_t**, @@ -136,11 +136,11 @@ uint8_t tpafe5160_is_busy(void); * 函数功能:读取全部8通道转换结果 * 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL * 返回值:BSP_OK - 成功;BSP_ERR_TIMEOUT - BUSY 超时;BSP_ERR_NO_DEVICE - FRSTDATA 无跳变 - * (E-44:芯片没驱动 DB 总线,读回的是上拉残值,不可信) + * (芯片没驱动 DB 总线,读回的是上拉残值,不可信) * 限定条件:GPIO 已初始化 * 函数说明:1. 启动转换并等待 BUSY 释放 * 2. 连续8次拉低RD读取各通道数据 - * 3. 校验 FRSTDATA 在这一帧内发生过跳变(= 芯片确实在驱动数据线,E-44) + * 3. 校验 FRSTDATA 在这一帧内发生过跳变(= 芯片确实在驱动数据线) */ bsp_err_t tpafe5160_read_all(int16_t *buf); @@ -207,7 +207,7 @@ void tpafe5160_start_conv_irq(void); * 入口参数:无 * 返回值:uint32_t 帧序号,EXTI 每读完一帧 +1 * 限定条件:中断模式已启用 - * 函数说明:取代旧的 data_ready/clear_ready **电平标志**(E-45)——电平标志有三个 + * 函数说明:取代旧的 data_ready/clear_ready **电平标志**——电平标志有三个 * 写者,其中 start_conv_irq 在触发前清 0,于是"重触发"本身就会抹掉一帧 * 还没被消费的数据(高速采样系统性丢样且无从发现)。序号只增不清, * 消费者比较 seq 即可,重触发不会丢 @@ -220,7 +220,7 @@ uint32_t tpafe5160_frame_seq(void); * count - 拷贝通道数 uint8_t 1 - 8 * 返回值:1 - 取到新帧,0 - 无新帧,BSP_ERR_PARAM(-1) - 参数无效(**三态状态返回,故意不套成败码**:0 表示"这一拍没有新数据",不是错误) * 限定条件:**任务上下文**调用(内部用 taskENTER_CRITICAL,不可在中断里调) - * 函数说明:E-58——旧 get_buf() 只返回缓冲区指针,任务再逐通道拷;两步之间被 BUSY + * 函数说明:旧 get_buf() 只返回缓冲区指针,任务再逐通道拷;两步之间被 BUSY * EXTI 抢占并跨过一帧周期,拷回的就是**两帧混合**的 8 通道数据(同一时刻 * 的 8 路值互相错位),且没有任何错误指示。本函数把"选缓冲 + 拷数据 + * 记游标"收进一个临界区(只盖 16 字节拷贝),撕裂在设计上消除 @@ -232,7 +232,7 @@ int tpafe5160_snapshot(int16_t *dst, uint8_t count); * 入口参数:无 * 返回值:uint16_t 计数(中断里只加计数不打日志) * 限定条件:中断模式已启用 - * 函数说明:E-44——非 0 表示至少有一帧的 8 个值不可信;消费者据此上报自检 + * 函数说明:非 0 表示至少有一帧的 8 个值不可信;消费者据此上报自检 */ uint16_t tpafe5160_frstdata_err(void); diff --git a/Drivers/BSP/UI/ui_content_page.c b/Drivers/BSP/UI/ui_content_page.c index 7625bd58..2a49f153 100644 --- a/Drivers/BSP/UI/ui_content_page.c +++ b/Drivers/BSP/UI/ui_content_page.c @@ -28,9 +28,9 @@ * 描述符 init_cb(开页后调一次,页面模块重置运行态) * 2026-09-13 王建锋 cp_draw_cb 按区块拆为四个绘制子函数(cp_draw_deco/header/rows/footer), * 主函数仅编排(行数取一次供行窗口/页脚共用),行为零变化 - * 2026-10-15 王建锋 U1 设计令牌层:屏幕/骨架/行距/缓冲/1bpp 语义色字面量收进 + * 2026-10-15 王建锋 UI 设计令牌层:屏幕/骨架/行距/缓冲/1bpp 语义色字面量收进 * ui_tokens.h(纯替换,像素逐位一致) - * 2026-10-15 王建锋 U3 页面原型化:删 content_page_id_t 枚举与 s_defs[],open() 改收 + * 2026-10-15 王建锋 UI 页面原型化:删 content_page_id_t 枚举与 s_defs[],open() 改收 * 描述符指针、s_open 变指针(NULL = 未打开);7 个页面头包含移交 * ui_page_registry.c;日志页身份改打描述符 id(ASCII 页名,比原 * id=%u 更可定位);绘制/按键/几何一律未动 diff --git a/Drivers/BSP/UI/ui_content_page.h b/Drivers/BSP/UI/ui_content_page.h index 63eaca69..5a936200 100644 --- a/Drivers/BSP/UI/ui_content_page.h +++ b/Drivers/BSP/UI/ui_content_page.h @@ -25,7 +25,7 @@ * 是否过密码页/所输密码决定);描述符新增 edit_capable(页面 * 是否支持编辑模式)+ 新 API ui_content_page_is_editable * (页面钩子据以决定编辑行为:edit_capable 且修改权限) - * 2026-10-15 王建锋 U3 页面原型化:删 content_page_id_t 枚举与框架侧 s_defs[], + * 2026-10-15 王建锋 UI 页面原型化:删 content_page_id_t 枚举与框架侧 s_defs[], * open() 改收描述符指针;节点→页面绑定移交给生成物 * ui_page_registry.c(菜单树 #page= 注记为唯一真源), * 逻辑层手写 menu_content_page_for() 随之删除; diff --git a/Drivers/BSP/UI/ui_menu_tab.h b/Drivers/BSP/UI/ui_menu_tab.h index ee19dc55..f82b4c25 100644 --- a/Drivers/BSP/UI/ui_menu_tab.h +++ b/Drivers/BSP/UI/ui_menu_tab.h @@ -53,7 +53,7 @@ uint8_t ui_menu_tab_children(uint8_t node, uint8_t out[], uint8_t max); * 入口参数:node - 节点下标 uint8_t 0 - 表长-1 * 返回值:true - 有子项(显示为菜单,带 ▶);false - 无子项(叶子,可进内容页) * 限定条件:纯查询,无副作用;node 越界返回 false - * 函数说明:这是**谓词**(bool 合法),不是成败码——S-5 的 err-type 检查里白名单放行的形态。 + * 函数说明:这是**谓词**(bool 合法),不是成败码——err-type 检查里白名单放行的形态。 */ bool ui_menu_tab_has_children(uint8_t node); diff --git a/Drivers/BSP/UI/ui_net_sink.c b/Drivers/BSP/UI/ui_net_sink.c index e9cdf164..c213035f 100644 --- a/Drivers/BSP/UI/ui_net_sink.c +++ b/Drivers/BSP/UI/ui_net_sink.c @@ -12,10 +12,10 @@ * 2026-09-10 王建锋 改为双端共用:底层改用 net_* API + 平台后端;接收改为逐字节 * 状态机(失步自动重同步);断开/网络重建后经 net_get_sock 判活 * 并重建监听(修复 MCU 单连接模型断线后无法重连的隐患) - * 2026-10-02 王建锋 E-04:remo_send 校验 net_send 返回值(原丢弃)——短写/失败 + * 2026-10-02 王建锋 remo_send 校验 net_send 返回值(原丢弃)——短写/失败 * 告警,连续 3 次失败阻断推送,新连接/网络重建解除阻断 * (MCU 单连接模型不得 net_close 数据 fd,会连监听一起毁掉) - * 2026-10-10 王建锋 I-1 门控 + I-2 兜底(销项 N-10):accept/recv + * 2026-10-10 王建锋 判活门控 + 兜底收帧:accept/recv * 前先用 net_can_accept()/net_can_read()(纯 RAM 读控制块)判 * "有没有活",无活直接返回——台架常态(无客户端)从每 10 ms * 一次 RPC(100 msg/s,与 FTP 抢深度 8 的队列)降到 0 次。 @@ -23,7 +23,7 @@ * "RX 非空但 readable=0",故每 32 轮无条件 recv 一次(≈320 ms); * 门控放行时若监听控制块已消失则置 g_listen=-1 触发重建, * 避免"门控把端口永久不再接受新客户端"这一新卡死路径 - * 2026-10-12 王建锋 N-32(B10 根治 P1,设计文档 §2.3 不变量 II):accept 门控 + * 2026-10-12 王建锋 监听生命周期 P1(B10 根治,设计文档 §2.3 不变量 II):accept 门控 * 由 net_can_accept()(只认 ESTABLISHED)改判 * net_listener_armed()(LISTENING 或 CLOSED+auto_relisten)—— * 旧门控在断连待重建窗口恒 0,:7003 永不进 net_accept, @@ -35,7 +35,7 @@ * (accept / recv_nb / send / close / 重建三件套)返回点 * 调用——风暴期单轮多往返串成 UI 心跳缺口(实测 bit4 * 8 连击、uisn=154ms 仍在段内样本),往返本身不动 - * (语义不变,上界收口 = 登记簿 E-27/E-28 批 3),心跳 + * (语义不变,上界收口为登记簿批 3 事项),心跳 * 在往返边界续命。钩子未注册 = 零行为变化(模拟器/黄金 * 回归不注册)。判读与设计口径见 * docs/UI设计/UI_设计架构.md §5.8 @@ -51,7 +51,7 @@ #include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ #include "net_latency_test.h" /* M3 探针:prod_cycle_mcu(LCDMEM 处理净成本) */ -/* E-04(2026-10-02):发送短写/失败告警(MCU=控制台日志,模拟器=printf 桩) */ +/* (2026-10-02):发送短写/失败告警(MCU=控制台日志,模拟器=printf 桩) */ #define DBG_TAG "[UI_NET_SINK]" #include "dbg_log.h" @@ -77,11 +77,11 @@ static int g_listen = -1; /* 监听 Socket fd(未启动或已停止时 static int g_client = -1; /* 当前客户端 Socket fd(无连接时为 -1) */ static uint16_t g_port; /* 监听端口(网络重建 / 断线后重监听用) */ static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */ -static uint8_t s_send_fail_run; /* 连续发送失败计数(E-04:0 = 正常) */ +static uint8_t s_send_fail_run; /* 连续发送失败计数(0 = 正常) */ static uint8_t s_sink_blocked; /* 推送阻断标志(1 = 停发帧,等客户端重连解除) */ /* - * 兜底收帧节拍(门控失效保险,2026-10-10 I-2): + * 兜底收帧节拍(门控失效保险,2026-10-10): * net_can_read() 的 readable 只由 RECV_OK 中断置位,而 TRAP-NET-17 的 * net_reconcile_tick() 三类对账**不含**"芯片 RX 非空但 readable=0" → * 该中断一旦丢失,纯门控会把已到达的数据永久藏住(改前靠每轮 recv 内的 @@ -170,7 +170,7 @@ static void put_u32(uint8_t *p, uint32_t v) { * 限定条件:g_tx 空间足够容纳 4+len+1 字节;g_client >= 0(已有连接) * 函数说明:帧格式 [TAG_DEVICE][CMD][LEN_H][LEN_LO][DATA][CRC];CRC 为 payload 异或; * 无连接时直接返回 - * E-04(2026-10-02):校验 net_send 返回值(原丢弃)——短写 = 客户端收到坏帧 + * (2026-10-02):校验 net_send 返回值(原丢弃)——短写 = 客户端收到坏帧 * (CRC 不符 → 画面卡死黑屏,而设备侧日志全正常);连续 3 次失败 → * 停发:客户端察觉无帧自行重连,芯片侧 DISCONNECT/TIMEOUT 自动重开 * 监听重建连接;MCU 单连接模型严禁 net_close 数据 fd(会连监听一起毁) @@ -516,10 +516,10 @@ int ui_net_sink_start(uint16_t port) { * 3. 无客户端时**先门控**(net_listener_armed,纯 RAM 读)再 accept,成功则复位状态机。 * 门控判据 = "监听器还在服役"(LISTENING 或 CLOSED+auto_relisten)而非 * "本轮必然有连接"——后者会让断连待重建窗口内永不进 net_accept, - * 把 CONNECT 事件未收到的补 accept 探测永久挡在门外(N-32) + * 把 CONNECT 事件未收到的补 accept 探测永久挡在门外(监听生命周期模块化) * 4. 有客户端时**先门控**(net_can_read)再非阻塞 recv 逐字节喂入状态机; * 门控判为"无数据"时仍每 RM_LIVENESS_DIV 轮无条件收一次(兜底) - * 5. 对端关闭/真错误时关闭客户端。**不再自行重建监听**(N-32 不变量 II): + * 5. 对端关闭/真错误时关闭客户端。**不再自行重建监听**(监听生命周期不变量 II): * recv 的 EOF/错误分支已触发 net 层 auto_relisten,另有对账④兜底; * 仅 g_listen 的 fd 真消失(net_get_sock 返 NULL)才置 -1 交回 2 重启 * ⚠ **门控只消除"无谓的往返",不消除"往返本身无上界"**:一旦真有活要干 @@ -543,7 +543,7 @@ void ui_net_sink_poll(void) { sink_beat(); /* F3:重建前的一对 close 往返结束 */ g_client = -1; rm_parser_reset(); - s_send_fail_run = 0U; /* 网络重建:失败累计清零、解除推送阻断(E-04) */ + s_send_fail_run = 0U; /* 网络重建:失败累计清零、解除推送阻断 */ s_sink_blocked = 0U; s_liveness_ctr = 0U; /* 网络重建:兜底节拍重新起算 */ (void)ui_net_sink_start(g_port); @@ -556,7 +556,7 @@ void ui_net_sink_poll(void) { return; } - /* 无客户端时尝试接受连接(N-32 P1 不变量 II:门控从 net_can_accept〔只认 + /* 无客户端时尝试接受连接(监听生命周期 P1 不变量 II:门控从 net_can_accept〔只认 * ESTABLISHED〕换成 net_listener_armed〔LISTENING 或 CLOSED+auto_relisten〕) * 原因:MCU 单连接模型下断连后 fd 会停在 CLOSED 等 relisten,旧门控在该窗口 * 内恒 0 → :7003 永不进 net_accept → net_lost_connect_probe 对该端口等于 @@ -577,14 +577,14 @@ void ui_net_sink_poll(void) { if (c >= 0) { g_client = c; rm_parser_reset(); - s_send_fail_run = 0U; /* 新连接:失败累计清零、解除推送阻断(E-04) */ + s_send_fail_run = 0U; /* 新连接:失败累计清零、解除推送阻断 */ s_sink_blocked = 0U; s_liveness_ctr = 0U; /* 新连接:兜底节拍重新起算 */ } return; } - /* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底;DONTWAIT 已转正 + /* 有客户端时非阻塞收帧(判活门控 + 兜底节拍;DONTWAIT 已转正 * 为具名 API net_recv_nb——uiTask 在 IWDG required_mask 内且有 10ms UI 推流 * 周期职责,禁止无限阻塞 recv,非阻塞轮询是义务): * 无数据可读 → 多数轮次直接返回,省掉一次 recv 往返(该往返内部 @@ -612,7 +612,7 @@ void ui_net_sink_poll(void) { } g_client = -1; rm_parser_reset(); - /* N-32(P1 不变量 II):重建**不再**由本处自行 bind_listen。 + /* 监听生命周期 P1 不变量 II:重建**不再**由本处自行 bind_listen。 * ① net_recv_locked 的 EOF 分支已触发 auto_relisten(D10 非阻塞重试); * ② 对账④还会兜底补一次 relisten(TRAP-NET-17 实证事件面不是可靠唯一信源—— * E14 复核:非芯片丢事件——两层不可互替)。 @@ -625,7 +625,7 @@ void ui_net_sink_poll(void) { } else { /* n < 0:区分"暂无数据"(继续等待)与"真错误"(断开并视情况重建监听) */ if (net_get_errno() != NET_ERR_WOULDBLOCK) { - /* N-32(P1):加 `g_client != g_listen` 护栏,与上面 n==0 分支 + /* 监听生命周期 P1:加 `g_client != g_listen` 护栏,与上面 n==0 分支 * 的 :552 一致——MCU 单连接模型下 g_client 可能就是 g_listen 本身, * 无护栏的 net_close 会直接关掉监听 fd(:563,与 :134 注释"严禁" * 自相矛盾),端口随 TRAP-NET-09 沉降窗短死。PC 模拟器多连接后端下 @@ -636,7 +636,7 @@ void ui_net_sink_poll(void) { sink_beat(); /* F3:EOF/错误路径的 close 往返结束 */ g_client = -1; rm_parser_reset(); - /* N-32(P1 不变量 II):同 EOF 分支——不再自行 bind_listen, + /* 监听生命周期 P1 不变量 II:同 EOF 分支——不再自行 bind_listen, * 重建交回 net 层(recv 的错误分支同样会走 relisten)。仅 fd 真消失 * 时置 -1 交回主循环走 start */ if (net_get_sock(g_listen) == NULL) { diff --git a/Drivers/BSP/UI/ui_net_sink.h b/Drivers/BSP/UI/ui_net_sink.h index 7a2f2d05..1b031d56 100644 --- a/Drivers/BSP/UI/ui_net_sink.h +++ b/Drivers/BSP/UI/ui_net_sink.h @@ -71,7 +71,7 @@ void ui_net_sink_poll(void); * 重建三件套)返回处调用钩子。动因:netTask 重建风暴期,单轮内多个 * 往返串行排队,把宿主(uiTask,IWDG 必需位)的心跳间隔拖过 800ms * (2026-10-25 台架实测 bit4 单任务 8 连击窗、uisn=154ms 仍在段内); - * 往返时长本身不改(上界收口 = 登记簿 E-27/E-28,批 3),但心跳在每个 + * 往返时长本身不改(上界收口为登记簿批 3 事项),但心跳在每个 * 往返边界续命——单任务停摆判据(按位连击,docs/子系统设计/看门狗设计说明.md * §5.3)只应 * 抓真挂死,不该抓「多个都回来了但各自偏慢的往返之和」。 diff --git a/Drivers/BSP/UI/ui_page_runlog.c b/Drivers/BSP/UI/ui_page_runlog.c index 3264ca98..601c6373 100644 --- a/Drivers/BSP/UI/ui_page_runlog.c +++ b/Drivers/BSP/UI/ui_page_runlog.c @@ -10,7 +10,7 @@ * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §5.3) - * v1.1 2026-10-20 王建锋 改用 ui_w_records 共享组件(W6 U2:合并两页重复实现) + * v1.1 2026-10-20 王建锋 改用 ui_w_records 共享组件(W6:合并两页重复实现) * v1.2 2026-10-25 王建锋 记录 reader 注入(道B task-6):不再 include sys_log.h, * 数据经组件内注入的 ui_rec_reader_t 取;日志 ID 换 * 不透明编号 UI_W_REC_LOG_RUN(映射在两端绑定处) @@ -37,7 +37,7 @@ * 返回值:总行数 uint16_t */ /* - * 记录页薄跳板:内容全部走 ui_w_records 共享组件(W6 U2)。 + * 记录页薄跳板:内容全部走 ui_w_records 共享组件(W6)。 * 实例 s_rec 携带日志 ID 与**本实例自己的**读盘缓存——两页共用组件代码, * 但缓存不得共享,否则切页会命中上一记录号而显示另一页的陈旧行。 */ diff --git a/Drivers/BSP/UI/ui_page_selftest.c b/Drivers/BSP/UI/ui_page_selftest.c index a61bf8c8..f8b97bc3 100644 --- a/Drivers/BSP/UI/ui_page_selftest.c +++ b/Drivers/BSP/UI/ui_page_selftest.c @@ -10,7 +10,7 @@ * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §5.4) - * v1.1 2026-10-20 王建锋 改用 ui_w_records 共享组件(W6 U2:合并两页重复实现) + * v1.1 2026-10-20 王建锋 改用 ui_w_records 共享组件(W6:合并两页重复实现) * v1.2 2026-10-25 王建锋 记录 reader 注入(道B task-6):不再 include sys_log.h, * 数据经组件内注入的 ui_rec_reader_t 取;日志 ID 换 * 不透明编号 UI_W_REC_LOG_SELFTEST(映射在两端绑定处) @@ -37,7 +37,7 @@ * 返回值:总行数 uint16_t */ /* - * 记录页薄跳板:内容全部走 ui_w_records 共享组件(W6 U2)。 + * 记录页薄跳板:内容全部走 ui_w_records 共享组件(W6)。 * 实例 s_rec 携带日志 ID 与**本实例自己的**读盘缓存——两页共用组件代码, * 但缓存不得共享,否则切页会命中上一记录号而显示另一页的陈旧行。 */ diff --git a/Drivers/BSP/UI/ui_style.c b/Drivers/BSP/UI/ui_style.c index c609449b..f01392b0 100644 --- a/Drivers/BSP/UI/ui_style.c +++ b/Drivers/BSP/UI/ui_style.c @@ -6,7 +6,7 @@ * 作者:王建锋 * 创建日期:2026-10-15 * 修改记录: - * v1.0 2026-10-15 王建锋 创建初始版本(U3:原三处逐字相同的定义合并到此单一来源) + * v1.0 2026-10-15 王建锋 创建初始版本(原三处逐字相同的定义合并到此单一来源) */ /* 头文件包含区 */ diff --git a/Drivers/BSP/UI/ui_style.h b/Drivers/BSP/UI/ui_style.h index 7146daa3..ffc484a5 100644 --- a/Drivers/BSP/UI/ui_style.h +++ b/Drivers/BSP/UI/ui_style.h @@ -11,7 +11,7 @@ * 作者:王建锋 * 创建日期:2026-10-15 * 修改记录: - * v1.0 2026-10-15 王建锋 创建初始版本(U3:合并三处逐字重复的 flat 常量样式定义) + * v1.0 2026-10-15 王建锋 创建初始版本(合并三处逐字重复的 flat 常量样式定义) */ #ifndef UI_STYLE_H #define UI_STYLE_H diff --git a/Drivers/BSP/UI/ui_tokens.h b/Drivers/BSP/UI/ui_tokens.h index 10977ea3..9257edd8 100644 --- a/Drivers/BSP/UI/ui_tokens.h +++ b/Drivers/BSP/UI/ui_tokens.h @@ -10,7 +10,7 @@ * 作者:王建锋 * 创建日期:2026-10-15 * 修改记录: - * v1.0 2026-10-15 王建锋 创建初始版本(U1:收编散落各 UI 文件的布局/颜色/缓冲魔数, + * v1.0 2026-10-15 王建锋 创建初始版本(收编散落各 UI 文件的布局/颜色/缓冲魔数, * 纯替换、零行为变化;像素与替换前逐位一致) */ #ifndef UI_TOKENS_H diff --git a/Drivers/BSP/UI/ui_w_records.c b/Drivers/BSP/UI/ui_w_records.c index 6b5566ac..35eda51e 100644 --- a/Drivers/BSP/UI/ui_w_records.c +++ b/Drivers/BSP/UI/ui_w_records.c @@ -6,7 +6,7 @@ * 作者:王建锋 * 创建日期:2026-10-20 * 修改记录: - * v1.0 2026-10-20 王建锋 创建(W6 U2) + * v1.0 2026-10-20 王建锋 创建(W6) * v2.0 2026-10-25 王建锋 reader 注入(道B task-6):syslog_count/syslog_get_record * 直调改为 s_reader 函数表调用;未注入按空态渲染 */ diff --git a/Drivers/BSP/UI/ui_w_records.h b/Drivers/BSP/UI/ui_w_records.h index fd9c16ae..d62c5451 100644 --- a/Drivers/BSP/UI/ui_w_records.h +++ b/Drivers/BSP/UI/ui_w_records.h @@ -11,7 +11,7 @@ * 作者:王建锋 * 创建日期:2026-10-20 * 修改记录: - * v1.0 2026-10-20 王建锋 创建(W6 U2:合并 ui_page_runlog / ui_page_selftest 的重复实现) + * v1.0 2026-10-20 王建锋 创建(W6:合并 ui_page_runlog / ui_page_selftest 的重复实现) * v2.0 2026-10-25 王建锋 记录 reader 注入(道B task-6):删 #include "sys_log.h", * 改 ui_rec_reader_t 函数表 + ui_w_records_bind(); * log_id 换不透明编号(UI_W_REC_LOG_*);缓冲容量宏本地化 diff --git a/README.md b/README.md index b526b262..5d723469 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ main.c > 上表与 `Src/freertos.c` 的 `osThreadAttr_t` 一致(2026-10-14 校准,9 任务);改动栈大小前先读 -> `docs/内存管理/RAM使用分析与优化方案.md` 的定栈规则与 `docs/任务规划/基线与待确认.md` 的内存预算项。 +> `docs/内存管理/RAM使用分析与优化方案.md` 的定栈规则与 `docs/内存管理/RAM预算台账.md` 的预算项。 ### 任务启动顺序管理 @@ -170,7 +170,7 @@ STM32F4-Base/ │ └── host/ # 主机端脚本(PC 侧;@test 入口已迁至 .dsh/skills/serial-boot-test/) │ ├── ch395f_socket_test.py # Socket 通信测试 │ ├── ftp_test.py / ftp_up_dl_test.py / ftp_regression.py / ftp_integrity_test.py / ftp_capture.py -│ ├── e02_probe.py # E-02 专项探针 +│ ├── e02_probe.py # 队头阻塞专项探针 │ ├── latency_peer.py # 链路时延 PC 对端 │ └── time_sync_server.py # 时间同步推送服务 ├── docs/ # 项目文档(全量索引见 docs/README.md) diff --git a/Src/freertos.c b/Src/freertos.c index 8a6d89c8..c8cd063f 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -104,11 +104,11 @@ static int hook_ptr_ok(const void *p) * + 时间戳缓冲并入行缓冲(每个日志调用点原内联 192+32 B);④ 断言/故障钩子 * 脱离 HAL 发送回退 + 缓冲静态化(configASSERT 在 FreeRTOS 内部可达 → * 钩子在**每个**任务的最坏链尾)。详见 docs/内存管理/RAM使用分析与优化方案.md - * N-33(2026-10-12):B7 定值持久化把静态最坏链推到 **3016 B**(storage_bring_up → + * 栈余量收口(2026-10-12):B7 定值持久化把静态最坏链推到 **3016 B**(storage_bring_up → * cfg 写 → f_rename → sync_fs → dhara GC → 控制台回显尾),1024 words 时余 * 1080 B < 30% 线(4096×30% = 1228 B)→ 抬至 **1280 words**(5120 B),余 * 2104 B / 41%;堆开销 +1024 B。栈值事实源仍是 `.ioc` 的 `FREERTOS.Tasks01`。 - * netTask 960 words:静态最坏链 1944 B(2026-10-12 N32 监听模块化后复测,链尾经 + * netTask 960 words:静态最坏链 1944 B(2026-10-12 监听生命周期模块化后复测,链尾经 * net_listener_pump→net_relisten_locked→SPI 故障→日志尾;2026-10-10 整改后曾为 * 1872 B,整改前 2664 B);3840 B 配置余量 1896 B * (49%),过 stack_report 规则。运行时水位仅 ~0.9 KB(错误尾未 @@ -120,7 +120,7 @@ static int hook_ptr_ok(const void *p) * 靠审计 WARN 提示),建监听 → net_bind → CH395F → 日志尾。2026-10-10 整改后 * 静态最坏链 1024 B(整改前 1736 B)、实测峰值 616 B;⚠ 余量 1024 B **正好** * 等于 ISR 底线 → 本任务链上再加任何调用(或新日志行)都必须先抬栈。 - * N-33(2026-10-12):链已加深到 **1264 B**(同一控制台回显尾:`dbg_console_drain` + * 栈余量收口(2026-10-12):链已加深到 **1264 B**(同一控制台回显尾:`dbg_console_drain` * → `MX_USART1_UART_Init` → `Error_Handler` → 回显 Cycle),512 words 时余 * 784 B < 1024 B ISR 底线 → 抬至 **640 words**(2.5 KB),余 1296 B / 50%; * 堆开销 +512 B。 @@ -353,7 +353,7 @@ void MX_FREERTOS_Init(void) { uiTaskHandle = osThreadNew(StartUiTask, NULL, &uiTask_attributes); /* USER CODE BEGIN RTOS_THREADS */ - /* netTask = 基础线程:上方生成区的实例是全构建唯一实例(P1-6:旧 + /* netTask = 基础线程:上方生成区的实例是全构建唯一实例(旧 * #ifdef ENABLE_NET_LAYER_TESTS 的重复创建已删除)。 * - 产品 / NET 层测试模式:正常运行 bring_up + net_poll * - CH395F 硬件层测试模式:net_task_func 入口自挂起(testTask 独占 CH395F) */ @@ -373,7 +373,7 @@ void MX_FREERTOS_Init(void) { iec104TaskHandle = osThreadNew(iec104_serv_task, NULL, &iec104Task_attributes); protectTaskHandle = osThreadNew(protect_task, NULL, &protectTask_attributes); - /* P2-13(2026-10-04):产品构建(未选任何 TEST_SUITE_*)不需要 testTask。 + /* 2026-10-04:产品构建(未选任何 TEST_SUITE_*)不需要 testTask。 * 上方生成区按 .ioc FREERTOS.Tasks01 无条件创建它,其 2048 words(8KB) 栈 + TCB * 取自 CCM 堆;而产品构建下 start_test_task() 仅打印一行后 osDelay(1000) 永久空转, * 白白占用 8KB CCM。此处于内核启动前(MX_FREERTOS_Init 先于 osKernelStart 调用) diff --git a/Src/main.c b/Src/main.c index 4e9ea681..ad56045b 100644 --- a/Src/main.c +++ b/Src/main.c @@ -41,7 +41,7 @@ #include "net_select.h" #include "app_main.h" #include "sys_clock.h" -#include "task_watchdog.h" /* task_wd_tick_probe():round 8b 时间源存活性取证 */ +#include "task_watchdog.h" /* task_wd_tick_probe():第 8b 轮时间源存活性取证 */ #include "adc_task.h" /* adc_tick_irq_notify():TIM3 采样节拍 → 唤醒 adcTask */ /* USER CODE END Includes */ @@ -57,7 +57,7 @@ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ -/* N-20(2026-10-12):本文件唯一的 DBG_* 调用点是 Error_Handler;其帧经 +/* Error_Handler 统一降级(2026-10-12):本文件唯一的 DBG_* 调用点是 Error_Handler;其帧经 * MX_*_Init 调用链叠在 MSP 上,而 MSP 仅 1KB(startup Stack_Size 0x400) * → 行缓冲收紧到 128 B(本文件最长行 = Error_Handler 行 ≈78 B < 128,不截断)。 * 日志成本口径见 dbg_log.h 的 DBG_LINE_BUF_MAX 注释。 */ @@ -92,7 +92,7 @@ int main(void) { /* USER CODE BEGIN 1 */ - /* round 8b 取证:**必须在 HAL_Init() 之前**把上一轮的"时间源存活性"终值 + /* 第 8b 轮取证:**必须在 HAL_Init() 之前**把上一轮的"时间源存活性"终值 * 快照到 .noinit 的 prev 槽——TIM7 在 HAL_Init 里就 HAL_TIM_Base_Start_IT * 开始 1ms 中断,而开机上报要到 app_main_init(≈117ms)才跑;不先搬走, * 上一轮终值会被本轮开机瞬间的 tick 覆盖(见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。 */ @@ -238,7 +238,7 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) HAL_IncTick(); } /* USER CODE BEGIN Callback 1 */ - /* round 8b 取证:TIM7 每 1ms 把"时间源存活性"写进复位不丢的 .noinit 槽 + /* 第 8b 轮取证:TIM7 每 1ms 把"时间源存活性"写进复位不丢的 .noinit 槽 * (uwTick + 中断计数)。复位后由 task_wd_seg_report() 打 TICKRUN 行, * 与"进段打点"时刻对比,判定 TIM7(HAL 时基)是否在冻结同刻停摆 * (见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。 @@ -278,7 +278,7 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ - /* N-20 统一降级(2026-10-12):旧形态 __disable_irq()+while(1) = 单处外设初始化 + /* Error_Handler 统一降级(2026-10-12):旧形态 __disable_irq()+while(1) = 单处外设初始化 * 失败即整机挂死,只剩 IWDG 兜底且无任何自检日志。现:打 1 条自检日志 + * NVIC_SystemReset()(与 4 个 Fault 处理同款自恢复;IWDG 是最后兜底而非唯一 * 出路;复位归因 SFTRSTF → 开机日志 "software (NVIC_SystemReset)")。 diff --git a/docs/CI/README.md b/docs/CI/README.md index 035ca866..3bd3898c 100644 --- a/docs/CI/README.md +++ b/docs/CI/README.md @@ -23,7 +23,7 @@ | 想干什么 | 去看 | |---|---| | 先搞清 CI 需要哪三样东西就位、本机拓扑、部署前置清单 | §0 / §0.1 / §0.2 | -| **CI 体积门禁现在怎么守**(同码凭证强制,S14 盲区已关闭;gitlint 空转仍在) | §0.0 + §7 排障表"钩子条数对不上"行 | +| **CI 体积门禁现在怎么守**(同码凭证强制,「只声明不执行」那处盲区已关闭;gitlint 空转仍在) | §0.0 + §7 排障表"钩子条数对不上"行 | | 服务端开启 Actions、`DEFAULT_ACTIONS_URL` 到底管什么 | §1(含 §1.2.1,**这条最容易搞反**) | | 下载 / 注册 runner(`--instance` 用回环、labels 必须精确匹配 `runs-on`) | §2.2 | | `config.yaml` 各字段语义(capacity / timeout / labels / 日志目录…) | §2.3 | diff --git a/docs/CI/远程CI完整指南.md b/docs/CI/远程CI完整指南.md index cdab1ee4..9665c80c 100644 --- a/docs/CI/远程CI完整指南.md +++ b/docs/CI/远程CI完整指南.md @@ -36,9 +36,9 @@ --- -## 0.0 ⚠️ CI 体积门禁:从盲区到同码凭证强制(必读,S14 → A-5 残余已销项) +## 0.0 ⚠️ CI 体积门禁:从盲区到同码凭证强制(必读,「体积门禁 CI 强制路径」残余已销项) -> **曾经的盲区已关闭为强制路径(2026-10-25,A-5 残余销项,`task/A5-size-gate`→564715c)。** +> **曾经的盲区已关闭为强制路径(2026-10-25,「体积门禁 CI 强制路径」残余销项,`task/A5-size-gate`→564715c)。** > `mem-numbers` 钩子的事实源是 Keil 构建产物 `MDK-ARM/**/*.map`(已 gitignore),CI 刻意不跑 > `@build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。 > 补上的强制路径 = **同码凭证**:本地 `@build` 0/0 后 `ram_baseline.py --update` 顺手盖 @@ -797,7 +797,7 @@ Get-ChildItem 'C:\ProgramData\anaconda3\Lib\site-packages' -Filter 'cfgv*' #### 4.1.2 ⚠️ 关键坑:`doc-links` 钩子「本地过、CI 挂」= 引用了 gitignore 的运行时文件 -**现象**:`文档链接存在性 (D-3/D-7)` 钩子在**本地** `pre-commit run --all-files` 通过, +**现象**:`文档链接存在性` 钩子在**本地** `pre-commit run --all-files` 通过, 一上 CI 就 `Failed`(exit 1),报的行/路径你在本地明明存在。 **根因**:CI 的 `actions/checkout@v4` 拉的是**干净克隆**,而钩子校验的是 @@ -955,7 +955,7 @@ github.com 会在 checkout 步骤卡住/失败。三种处置: **建议**:**先按原样试一次**。若 checkout 步骤报网络错误,再换成上面的零依赖写法(方案 3)。 -### 5.4 产物存档与结果汇总(S3,2026-10-02) +### 5.4 产物存档与结果汇总(2026-10-02 起) **目标**:CI 失败时**不查 MySQL / 不翻 runner 磁盘**,在 Gitea 页面即可看到完整输出与摘要。 @@ -1210,7 +1210,7 @@ runner 的日志走 **stderr**,于是上面两个日志文件都栽在这两 `commit-msg` stage 的钩子(gitlint)不在其中,靠 `ci_gitlint_range.ps1` 单独守(见 §7); - **`@build` / L1 / L2**:永远留在本地,不要往 CI 加(需 Keil 授权与真机); - **runner 长时间不可用**时:CI 只是"多一道网",**提交前的本地 `ci_local.py` 仍是 L0 的唯一保障**, - 不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的 T-7「CI 远端闭环未验证(runner 本机已就绪)」条)。 + 不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的条目「CI 远端闭环未验证」)。 --- diff --git a/docs/README.md b/docs/README.md index 461279ee..e9bb3bbb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,7 @@ ``` docs/ ├── README.md 本索引(阅读顺序 + 全量清单) -├── 任务规划/ 统一待办登记簿(README 排序权威 + 8 个拆分文件) +├── 任务规划/ 统一待办(README 单一入口 + 5 个职责文件) ├── 产品需求/ 产品需求设计文档(面向产品的需求基线) ├── 架构设计/ 全局架构与设计方法论 ├── 子系统设计/ 各功能子系统的设计说明 @@ -31,7 +31,7 @@ docs/ ``` > ⚠️ `docs/芯片手册/`(约 120 MB / 1754 张图)为**只读外部输入资料**,与代码演进无关; -> 后续按原报告 D-1 建议拆分独立仓库或降采样(登记簿 **N-29**,已决策暂不处理)。 +> 后续拆分独立仓库或降采样(登记簿条目「仓库体积膨胀观察」,已决策暂不处理)。 --- @@ -89,7 +89,7 @@ docs/ | [`系统墙钟时间维护说明.md`](子系统设计/系统墙钟时间维护说明.md) | `sys_clock`:RTC 基准、tick 回绕防护、30 天惰性再基准化 | | [`控制台单写者日志设计说明.md`](子系统设计/控制台单写者日志设计说明.md) | 控制台日志单写者模型(避免多任务交错输出) | | [`运行与自检记录设计说明.md`](子系统设计/运行与自检记录设计说明.md) | `/log/run.log`·`selftest.log` 定长记录与待机显示 | -| [`看门狗设计说明.md`](子系统设计/看门狗设计说明.md) | IWDG(CubeMX 配置)+ 任务心跳位图:选型与超时推导、掩码与埋点、取证机制、验证记录;已台架闭环(V2/V4/V5)。**§11 = 任务级饥饿的诊断方法与 `[PROF]` 实测基线**(Task #32 沉淀:判读口径、量化归因、三条脆弱点、明确不做的四条) | +| [`看门狗设计说明.md`](子系统设计/看门狗设计说明.md) | IWDG(CubeMX 配置)+ 任务心跳位图:选型与超时推导、掩码与埋点、取证机制、验证记录;已台架闭环(V2/V4/V5)。**§11 = 任务级饥饿的诊断方法与 `[PROF]` 实测基线**(诊断沉淀:判读口径、量化归因、三条脆弱点、明确不做的四条) | | [`ADC采样节拍设计说明.md`](子系统设计/ADC采样节拍设计说明.md) | **ADC 采集链路采样节拍(现状说明)**:TIM3 1 kHz 更新事件同时触发 TPAFE CONVST 与(经 TRGO)STM32 ADC1 + DMA 乒乓缓冲;节拍源为什么是 TIM3、DMA 硬约束、事件驱动消费与就绪门、实现落点、观测计数、性能特征与设计边界 | | [`IEC104上行设计说明.md`](子系统设计/IEC104上行设计说明.md) | **B4 上行规约**:IEC 60870-5-104 被控站(TCP 2404)范围/点表(V0.1 有效子集)/链路流程(k/w/t1~t3、总召、COV、遥控两段式、定值)/netTask 接入决策/预算与测试方案;协议核心纯逻辑 + L0.5 全栈单测 | | [`网络/BSD_Socket_API_使用指南.md`](网络/BSD_Socket_API_使用指南.md) | **上层视图**:BSD Socket API 公开语义、示例、常见问题 | @@ -105,7 +105,7 @@ docs/ |---|---| | [`傅氏算法详解.md`](算法/傅氏算法详解.md) | 全周波傅氏(单频 DFT)原理与工程实现:正交性、梳状带通、滑动窗、系数表折算有效值;保护/遥测双轨有效值的算法基础 | | [`采集与算法层设计说明.md`](算法/采集与算法层设计说明.md) | **`App/algo/` 的现状设计说明**:L1 语义取数与有效性判定、L2 采样环/相量/测频、L3 定值模型与元件框架的契约、关键机制(双区原子切换、乒乓半区、自适应测频窗、定值封存-自检-闭锁)、精度判据 J1~J6 与资源足迹 | -| [`继电保护功能复现规划.md`](算法/继电保护功能复现规划.md) | **保护功能复现规划**:把参考系统(CLS100)保护链路映射到本项目(STM32F4)的分层功能/性能/参数清单、平台适配(N-18 等)与分阶段路线图 | +| [`继电保护功能复现规划.md`](算法/继电保护功能复现规划.md) | **保护功能复现规划**:把参考系统(CLS100)保护链路映射到本项目(STM32F4)的分层功能/性能/参数清单、平台适配(「双转换器无共同采样触发」等)与分阶段路线图 | | [`采样有效性校验设计说明.md`](算法/采样有效性校验设计说明.md) | **`App/algo/adc_health.c` 单文件深设计**:四类判据(静默 / 丢帧 / 序号倒退 / 错误计数增量)的判据推导与 100 ms 门限依据、电平-锁存-事件三种位语义与位号对齐、闭锁与看门狗兜底的责任划分、观测盲区、主机侧验证分工与现场判读手册(含三张配图生成器) | --- @@ -133,7 +133,7 @@ docs/ | [`ADC输入层单元测试说明.md`](测试说明/ADC输入层单元测试说明.md) | adc_input 语义通道映射 / ADC1 顺序扫描(**Rank↔通道判据**)/ poll 契约 / HAL 故障路径,70 用例 | | [`相量层单元测试说明.md`](测试说明/相量层单元测试说明.md) | phasor 全周傅氏系数表 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区交换,57 用例 | | [`卷锁单元测试说明.md`](测试说明/卷锁单元测试说明.md) | vol_lock 创建 / 获取 / 释放契约(**fail-open 不变量**:创建失败仍返回 1 且零阻塞)/ 幂等 / 超时透传,33 断言点 | -| [`时间同步单元测试说明.md`](测试说明/时间同步单元测试说明.md) | time_sync 分片喂狗延时 / 监听退避 / 时间包重组 / 任务主循环(**N-17 的全部逻辑贡献者**),80 断言点 | +| [`时间同步单元测试说明.md`](测试说明/时间同步单元测试说明.md) | time_sync 分片喂狗延时 / 监听退避 / 时间包重组 / 任务主循环(探针窗口心跳缺口的全部逻辑贡献者),80 断言点 | | [`主机单测覆盖率口径.md`](测试说明/主机单测覆盖率口径.md) | **跨全部 16 套 L0.5 的覆盖率**:`scripts/check_test_coverage.py` 用法 / 统计面与生成物例外 / 基线增量门禁(只拦回退)/ **为什么本机拿不到分支覆盖** / 九条限制与盲区 / 未挂 pre-commit 的三条理由与待粘 YAML | **模块测试指南**(动手写测试前先读) @@ -183,7 +183,7 @@ docs/ | [`RAM使用分析与优化方案.md`](内存管理/RAM使用分析与优化方案.md) | **内存主文档**:占用分析、优化方案、定栈规则 | | [`内存内容分配策略.md`](内存管理/内存内容分配策略.md) | 各内存区(SRAM/CCM)的分配策略 | | [`存储栈RAM优化调研.md`](内存管理/存储栈RAM优化调研.md) | 存储栈 RAM 优化调研与结论 | -| [`RAM预算台账.md`](内存管理/RAM预算台账.md) | **预算台账**:子系统配额、CCM 分配、趋势基线与 92% 门禁(N-27 三件套,2026-10-12) | +| [`RAM预算台账.md`](内存管理/RAM预算台账.md) | **预算台账**:子系统配额、CCM 分配、趋势基线与 92% 门禁(登记簿条目「缩堆迁 CCM 后半(暂缓)」,2026-10-12) | | `data/` | `ram_report_*.md` / `stack_report_*.md` 快照数据(由 `scripts/` 生成) | ### 7.3 问题记录(**改驱动前必读**) @@ -225,7 +225,7 @@ docs/ > 未闭环项 → [`任务规划/登记簿.md`](任务规划/登记簿.md);内存与 Flash 结论(含整簇暂缓的**重开条件**) > → [`内存管理/RAM使用分析与优化方案.md`](内存管理/RAM使用分析与优化方案.md) §2.5 / §2.6.4 / §4; > UI 侧硬约束 → [`UI设计/UI_设计架构.md`](UI设计/UI_设计架构.md) §1.4; -> 六个自有编号族 → [`编号规范.md`](编号规范.md) §4.3(属主文档已删,存量冻结)。 +> 六个自有编号族 → [`编号规范.md`](编号规范.md) §4.5(属主文档已删,只能删号留事实,别再引用那些号)。 ### 9.2 环境与部署 | 文档 | 内容 | @@ -238,7 +238,7 @@ docs/ | 位置 | 内容 | |---|---| -| [`任务规划/`](任务规划/README.md) | **统一待办登记簿(文件夹)**——`README.md` = 排序权威(推进顺序 + 波次语义名)+ 编号空间 + 维护约定;`UI补齐.md` = **当前波次**(21 叶补齐,7 张卡);`数据可信.md` = 收口态(软件侧已 100%,剩 3 张台架/外部卡);`采集保护收尾.md` = 「采集保护收尾」剩余(只剩 M3 送检);`登记簿.md` = N/S/T-x 长期观察;`排期详述.md` / `暂缓-RS485.md` / `测试与架构专项.md` = 波内详表 / 冻结簇 / 专项;`基线与待确认.md` = 待确认(开放问题,OQ 族属主);**`并行分道方案.md` = 待办横向归道(写域 域1~域8 + 道E~道H + 批次 + 合并次序)**;只保留未闭环项,完成即删 | +| [`任务规划/`](任务规划/README.md) | **统一待办入口(6 个文件)**:`README.md` = 现在做什么与全项目顺序;`UI补齐.md` = 当前唯一执行清单;`登记簿.md` = 后续任务、外部窗口与长期观察;`数据可信.md` = 三项阻塞任务及代码引用的设计约束;`暂缓-RS485.md` 与 `测试与架构专项.md` = 两个专项。完成即删,重复内容只保留一个归属 | | `硬件平台/` | 网表详细分析报告与语义图(V0.0 / V0.1);**[`ADC通道映射说明.md`](硬件平台/ADC通道映射说明.md)——V0.1 十七路 ADC 语义/网名/通道号权威映射表(固件取数、UI 显示、自检注入以此为准)** | | `芯片手册/` | 外部芯片数据手册转录(CH395F / GD5F2GQ5UE / SD2506 / TPAFE5160 / RTL8305NBI 等) | | `images/` | 文档技术配图(`.svg` 产物,由 `tools/arch_diagram/`、`tools/refgraph/` 生成) | @@ -254,26 +254,26 @@ docs/ 5. **配图**:走 [`架构设计/图形绘制流程.md`](架构设计/图形绘制流程.md)(`draw-diagram` skill),产物统一放 `images/`,脚本与 `.svg` 一并提交。 6. **门禁**:文档提交同样过 pre-commit(乱码 / 行尾空白 / gitlint + **文档链接存在性 `doc-links` / 任务表一致性 `task-table`**,见 [`文档规范.md`](文档规范.md) §4 与 [`代码规范/pre-commit使用说明.md`](代码规范/pre-commit使用说明.md))。 -> **目录/文件名一律不含空格**(D-6,2026-09-25 起强制):空格会迫使脚本、链接、CI 处处加引号, +> **目录/文件名一律不含空格**(2026-09-25 起强制):空格会迫使脚本、链接、CI 处处加引号, > 且 URL 编码形态(`UI%20设计`)易被漏改。现全库已无含空格目录。 --- ## 11. 已知文档问题(待清理) -> 来源:原报告 D-3/D-7(两条已销项,正文已移入 [`文档规范.md`](文档规范.md) 与 pre-commit 门禁)。 -> **原报告 D-3 / D-6 / D-7 已处理完毕**(2026-09-25):全库链接存在性检查通过(66 文档 0 失效)、任务表与 +> 来源:原审查报告两条已销项(正文已移入 [`文档规范.md`](文档规范.md) 与 pre-commit 门禁)。 +> **原报告三条已处理完毕**(2026-09-25):全库链接存在性检查通过(66 文档 0 失效)、任务表与 > `Src/freertos.c` 一致、文档规范已建立。下表保留**仍需跟踪**的项。 | 问题 | 位置 | 说明 | |---|---|---| | ~~指向未实现工具~~ | ~~`UI_界面制作流程.md`、`UI_自动化测试流程.md` → `tools/ui_mock/`~~ | ✅ 已改指(2026-10-14 核实):两份 UI 文档正文已无该路径(仅存变更记录里的历史提及);golden 回归落在 `simulator/tests/`(四脚本) | -| ~~产物缺失~~ | ~~`内存管理/RAM使用分析与优化方案.md` → `scripts/ram_baseline.tsv`~~ | ✅ 已建(2026-10-12,N-27 前半):`ram_baseline.py` + `ram_baseline.tsv` + [`RAM预算台账.md`](内存管理/RAM预算台账.md) + `mem-budget` 钩子 | -| ~~目录名含空格~~ | ~~`UI 设计/`~~ | ✅ 已修复(2026-09-25,D-6):改名为 `UI设计/`,全仓库 67 处引用同步;命名约定见 [`文档规范.md`](文档规范.md) §1 | -| ~~缺一致性门禁~~ | — | ✅ 已落地(2026-09-25/26,D-7 三档齐):`scripts/check_doc_links.py`(存在性)+ `scripts/gen_task_table.py`(数值)+ `scripts/check_mem_numbers.py`(体积),接入 pre-commit `doc-links` / `task-table` / `mem-numbers` 钩子 | +| ~~产物缺失~~ | ~~`内存管理/RAM使用分析与优化方案.md` → `scripts/ram_baseline.tsv`~~ | ✅ 已建(2026-10-12,「缩堆迁 CCM 后半(暂缓)」的前半):`ram_baseline.py` + `ram_baseline.tsv` + [`RAM预算台账.md`](内存管理/RAM预算台账.md) + `mem-budget` 钩子 | +| ~~目录名含空格~~ | ~~`UI 设计/`~~ | ✅ 已修复(2026-09-25):改名为 `UI设计/`,全仓库 67 处引用同步;命名约定见 [`文档规范.md`](文档规范.md) §1 | +| ~~缺一致性门禁~~ | — | ✅ 已落地(2026-09-25/26,文档门禁三档齐):`scripts/check_doc_links.py`(存在性)+ `scripts/gen_task_table.py`(数值)+ `scripts/check_mem_numbers.py`(体积),接入 pre-commit `doc-links` / `task-table` / `mem-numbers` 钩子 | | ~~`docs/文档规范.md` 未创建~~ | — | ✅ 已创建(2026-09-25):[`文档规范.md`](文档规范.md) | -> 未闭环的文档类问题**只剩原报告 D-1 仓库膨胀**(登记簿 **N-29**,已决策暂不处理)。 +> 未闭环的文档类问题**只剩仓库膨胀**(登记簿条目「仓库体积膨胀观察」,已决策暂不处理)。 --- diff --git a/docs/UI设计/UI_自动化测试流程.md b/docs/UI设计/UI_自动化测试流程.md index 8f2fc605..5bd71b17 100644 --- a/docs/UI设计/UI_自动化测试流程.md +++ b/docs/UI设计/UI_自动化测试流程.md @@ -92,7 +92,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警 | 多页大面积像素差异 | 字体重生成(`ui_font_cn_12.c` 变化)→ 确认字形正确后 `--update` 刷参考 | | 单行/单字差异 | 几何或文案变更 → 先判断是否有意修改;无意 = bug | | 池水位门禁失败但像素一致 | 样式/结构变更(如动态样式转常量、增删框体)→ 确认新值合理后更新常量并登记 | -| **只有 `netcfg_save` 一页失败**(保存行值列是 `ERR` 而非 `OK`),其余 10 页全过 | **不是代码回归,是工作目录被污染**:模拟器按**当前工作目录**读写 `sim_config.ini`,而 `--cfgtest` 的 E-01 场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言"拒写后原文件不动"——**连通过路径也会把它留在原地**。之后每次运行都加载它 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎**按设计拒写** → 保存行显示 ERR,与"工厂默认态"的黄金参考对不上。处置:两套回归已改为**在空临时目录里跑**(传 `cwd=tmp`),`--cfgtest` 已加外壳无论成败一律删除伪造档;手工跑 `lvgl_sim` 时删掉当前目录的 `sim_config.ini` 即恢复。**禁止为此刷黄金参考**(参考是对的) | +| **只有 `netcfg_save` 一页失败**(保存行值列是 `ERR` 而非 `OK`),其余 10 页全过 | **不是代码回归,是工作目录被污染**:模拟器按**当前工作目录**读写 `sim_config.ini`,而 `--cfgtest` 超长守卫的场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言"拒写后原文件不动"——**连通过路径也会把它留在原地**。之后每次运行都加载它 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎**按设计拒写** → 保存行显示 ERR,与"工厂默认态"的黄金参考对不上。处置:两套回归已改为**在空临时目录里跑**(传 `cwd=tmp`),`--cfgtest` 已加外壳无论成败一律删除伪造档;手工跑 `lvgl_sim` 时删掉当前目录的 `sim_config.ini` 即恢复。**禁止为此刷黄金参考**(参考是对的) | | `找不到 lvgl_sim.exe` | 模拟器未构建或 build/ 被删 → 先按 §一 重建 | | 终端输出中文乱码 | PowerShell 代码页渲染问题,不影响判定(看 PASS/FAIL 与退出码) | diff --git a/docs/UI设计/UI_设计架构.md b/docs/UI设计/UI_设计架构.md index 0a00e2a6..6824956a 100644 --- a/docs/UI设计/UI_设计架构.md +++ b/docs/UI设计/UI_设计架构.md @@ -64,13 +64,13 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓 `lv_display_set_theme()` + 对**活动屏幕**再 `lv_theme_apply()`(实测 `bg_before=F5F5F5` → `bg_after=000000`)。 另外 I1 缓冲头 8 B 调色板 LVGL 不替调用方初始化(上电全 0 = 两项透明黑),须显式写 `[0]=黑 [1]=白`; 渲染器本身按亮度阈值置位(`lv_draw_sw_blend_to_i1.c`:亮 → 位=1),与客户端"位=1 即亮"的约定一致。 -10. **两份 `lv_conf.h` 必须是同一特性子集(2026-10-11 定;F-1/F-2/F-3 落地时暴露的漂移)**:`Lib/lv_conf.h`(MCU)与 `simulator/lv_conf.h`(PC)是**两份独立文件**,此前已漂移——模拟器早已 I1-only,MCU 端却让 11 个 `LV_DRAW_SW_SUPPORT_*`、5 个未用控件类、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX/GRID` 全靠 `lv_conf_internal` 默认值隐式生效,把用不到的代码整份链进镜像 = **实测 64,576 B 死代码(旧镜像的 23.5%)**。现两端对齐为同一子集: +10. **两份 `lv_conf.h` 必须是同一特性子集(2026-10-11 定;裁剪 LVGL 死代码时暴露的漂移)**:`Lib/lv_conf.h`(MCU)与 `simulator/lv_conf.h`(PC)是**两份独立文件**,此前已漂移——模拟器早已 I1-only,MCU 端却让 11 个 `LV_DRAW_SW_SUPPORT_*`、5 个未用控件类、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX/GRID` 全靠 `lv_conf_internal` 默认值隐式生效,把用不到的代码整份链进镜像 = **实测 64,576 B 死代码(旧镜像的 23.5%)**。现两端对齐为同一子集: * 绘制核只留 `LV_DRAW_SW_SUPPORT_I1`(显示是 1bpp;字形 A8 位图是作为 **mask** 传入、混合按**目的**格式分派,故不需要 A8/L8/AL88); * 控件全关(`label`/`button`/`buttonmatrix`/`bar`/`textarea`/`image`/`keyboard`)——全仓(`Drivers/BSP/UI`、`App`、`simulator`)**零控件构造函数**,只用 `lv_obj_create` + `lv_theme_mono_init`; * `LV_USE_THEME_SIMPLE 0`:它是把上述控件类拉进镜像的**锚点**(`lv_display_create()` 会强链 `lv_theme_simple_init`);⚠ 9.3 **没有** `LV_USE_THEME_BASIC`,旧配置里那条是无效项,已删; * `LV_USE_FLEX 0` / `LV_USE_GRID 0`:全仓无 `lv_obj_set_flex_*` / `lv_obj_set_grid_*` 调用。 ⚠ 裁剪后 ARMCC 报 `#177-D`(上游 9.3.0 在 `lv_draw_sw_blend_to_i1.c` 漏了 `lv_color16_from_u16` 的定义处守卫)→ 本仓已按使用点补 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED`(原型 + 定义两处),升级 LVGL 时若上游已修可去掉。 - ⚠ 将来引入控件型页面(U2 原型库)时**两端同步**逐项打开并复跑 `ui-verify`。量化与验证记录见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4。 + ⚠ 将来引入控件型页面(原型库)时**两端同步**逐项打开并复跑 `ui-verify`。量化与验证记录见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4。 ✅ **已入门禁**(2026-10-11):`scripts/check_lv_conf_sync.py` = pre-commit 钩子 `lv-conf-sync`。比对的是**生效值**(文件未定义则取 `lv_conf_internal.h` 兜底默认),并抓"写了 LVGL 不认的宏名"与同文件重复定义。首次跑即揪出两端共 **26 个 9.3 不存在的选项**(LVGL 8 / 9.4 模板残留,与 `LV_USE_THEME_BASIC` 同类)、**3 处同文件重复定义**、**10 项 MCU 端靠默认值隐式生效的开关**(含默认 =1 的 `LV_USE_OBSERVER` / `LV_USE_DRAW_PXP` / `LV_USE_IMAGEBUTTON`)——已全部清理/显式固化。 ✅ **门禁触发范围**(2026-10-11):`lv-conf-sync` 的 `files:` 同时匹配 `Lib/lv_conf.h`、**`simulator/lv_conf.h`** 与脚本自身 → **两端任一改动都会跑**。此前顶层 `exclude` 整目录挡掉 `simulator/`,模拟器侧改动拦不到;已删该条并把 `simulator/` 下自研代码整体纳入 pre-commit(cppcheck 当天即揪出 `sim_syslog.c` 一处真实空指针解引用),详见 `../代码规范/pre-commit使用说明.md` §5.5。 @@ -277,7 +277,7 @@ ENTER/→ 进子框: accept / recv_nb / send / close / 重建三件套的**返回处**调用钩子,uiTask 注册 `task_wd_beat(TASK_WD_UI)`。效果 = 心跳间隔从「整轮往返之和」收敛到「单个往返」; 只要每次往返都会回来,UI 就不该被记停摆。往返本身**不改**(有界化/请求序号 = - `docs/任务规划/README.md` 的 E-27/E-28 跨上下文收口,批 3;§11.6 否决过的 + `docs/任务规划/README.md` 的跨上下文收口条目,批 3;§11.6 否决过的 「给 net_* 裸加超时」不重演)。 - **分层**:Drivers/BSP 不得 include App 头(layer-deps),故心跳经回调注入而非直调; 钩子内只做置位类动作、不阻塞(契约写在 `Drivers/BSP/UI/ui_net_sink.h`)。 @@ -287,7 +287,7 @@ ENTER/→ 进子框: 护栏不变)。 - **残留暴露**(刻意不归本票):单个往返自身仍无上界(osMessageQueuePut(osWaitForever) + xTaskNotifyWait(portMAX_DELAY)),netTask 若被单消息拖 >800ms,UI 位连击照成立—— - 那是「判据抓对了人」,处置在 net 侧(E-27/E-28),不在心跳侧。 + 那是「判据抓对了人」,处置在 net 侧,不在心跳侧。 ## 六、变更记录(简表) diff --git a/docs/UI设计/页面扩充详细方案.md b/docs/UI设计/页面扩充详细方案.md index 35fcf4b6..84bbe833 100644 --- a/docs/UI设计/页面扩充详细方案.md +++ b/docs/UI设计/页面扩充详细方案.md @@ -83,7 +83,7 @@ | 日志 | `syslog_count/get_record` | ✅ 真 | 仅运行、自检 2 类 | | SOE | **无读取 API**(只有 `soe_store_post/drain`) | ❌ 需新增 | `/log/soe.log` 二进制 64 B | | 元件状态 | `prot_elm_state/active_mask/name` | ✅ 真 | 当前态,无历史 | -| 功率/电度 | — | ❌ 禁止 | N-18:跨转换器不同步,V0.1 不得算功率 | +| 功率/电度 | — | ❌ 禁止 | 双转换器无共同采样触发:跨转换器不同步,V0.1 不得算功率 | | 直流通道 | — | ❌ 无硬件 | `ADC_SEM` 只有 8 电气 + 温度 | | 开入/开出 | — | ❌ 无代码 | 固件完全没有开入开出实现 | @@ -379,7 +379,7 @@ int run_par_set_deadband(uint8_t yc_idx, float v); /* 范围校验 */ | 1 | `104端口` | `IEC104_PORT`(App 侧常量) | | 2 | `公共地址` | `iec104_serv` 的 CA(当前固定 1) | | 3 | `显示端口` | RemoDispBus 端口常量 | -| 4 | `FTP用户` | FTP 配置(`admin`,N-19 鉴权后) | +| 4 | `FTP用户` | FTP 配置(`admin`,鉴权启用后) | - 需要在 `ui_data_rt_t` **尾部追加**一个 `comm` 子结构(`port`/`ca`/`disp_port` + 8 B 用户名),按 `docs/UI设计/UI_数据供给设计说明.md` §4.3 的扩展规则升 `UI_DATA_VER`。 - 规模:**S**(快照 +4 字段,页面 60 行)。 diff --git a/docs/UI设计/页面补齐规划.md b/docs/UI设计/页面补齐规划.md index fe4c6198..a15e714a 100644 --- a/docs/UI设计/页面补齐规划.md +++ b/docs/UI设计/页面补齐规划.md @@ -266,6 +266,6 @@ ui_page_ac / ui_page_yx / ui_page_setlist ... ## 9. 与既有规划的关系 -- 本文替代"剩余页面尚未规划"的状态(原 `docs/任务规划/README.md` §2.0 中「UI 扩展」的 U5 条目按"暂缓、未规划"记载)。 +- 本文替代"剩余页面尚未规划"的状态(原 `docs/任务规划/README.md` §2.0 中「UI 扩展」的剩余 21 叶页面落地项按"暂缓、未规划"记载)。 - 页面工作**不应独立于数据层推进**:批 1 的第 1 项(快照层)与采集/遥测链共享同一批数据,建议与"测量子系统"同波次排期。 - 与 `docs/问题记录/采集与数据处理短板评测.md` 的对应:该文的标定(`docs/问题记录/采集与数据处理短板评测.md` §4.1)与遥测链(同文 §4.2)落地后,交流量/直流量/遥测系数三页才有真实数据可显示。 diff --git a/docs/images/网络不稳根因总览.svg b/docs/images/网络不稳根因总览.svg index 83c32523..4128415d 100644 --- a/docs/images/网络不稳根因总览.svg +++ b/docs/images/网络不稳根因总览.svg @@ -69,8 +69,8 @@ ⑤ 传达员被按在椅子上 队头阻塞 - -症状:FTP banner 迟到 25~30 s(E-02 实测),netTask 频繁冻结半秒 + +症状:FTP banner 迟到 25~30 s(台架实测),netTask 频繁冻结半秒 机制:30 s 阻塞收 + 500 ms 盲等忙等,而 netTask 是全楼唯一传达员 @@ -101,8 +101,8 @@ ⑦ 只会礼貌送客不会轰人 close 无 RST(手册定案) - -症状:FIN_WAIT_2 长尾,监听恢复受限于对端 close(E-02 定案) + +症状:FIN_WAIT_2 长尾,监听恢复受限于对端 close(台架定案) 机制:芯片 close 只发 FIN;对端内核存活但应用挂死 = 回 ACK 却不起身 diff --git a/docs/images/采样有效性校验数据流.svg b/docs/images/采样有效性校验数据流.svg index 109e45de..fe54e996 100644 --- a/docs/images/采样有效性校验数据流.svg +++ b/docs/images/采样有效性校验数据流.svg @@ -17,7 +17,7 @@ tpafe5160.c 的 BUSY EXTI s_frame_seq++:每读完一帧 +1 -s_frstdata_err++:无跳变帧累计(E-44) +s_frstdata_err++:无跳变帧累计计数 ISR 内只加计数,不打日志 diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index fe193476..82c90978 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -446,10 +446,10 @@ sequenceDiagram ### 5.9 ADC 采集(FR-ADC)🔶 采集链路已实现(B1);保护/遥测算法与上行待实现 - **需求(B0 已确认,2026-10-10)**:**持续采集** 8 路电气量(Ua/Ub/Uc/U0、Ia/Ib/Ic/I0,**1000 Hz / 20 点/周波**),经保护/遥测算法后**上送 IEC 60870-5-104**(以太网 TCP 2404;遥测上送短浮点、采集上传系数 = 1)。 -- **现状(采集链路 B1 + L1 已实现,2026-10-10)**:启动序列调用一次 `tpafe5160_init()`;**运行期周期 CONVST 重触发已由 TIM6 更新中断(1 kHz)接管**(`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback`,2026-10-10)+ `adcTask` 经 L1 层 `adc_input_poll()` 消费(`tpafe5160_snapshot()` 8 通道 + STM32 ADC1 9 通道扫描)+ 帧统计,台架 `@test` 确认 `frame_seq` 严格 1000 Hz 递增、`frst_err=0`、`adc1_rc=0` / `adc1_err=0`;STM32 侧 ADC1 **消费者已落地**(`App/algo/adc_input.c` 的 `stm32_scan()`,L1-3/L1-4,2026-10-10),按 N-18 仅逐通道独立监视。 +- **现状(采集链路 B1 + L1 已实现,2026-10-10)**:启动序列调用一次 `tpafe5160_init()`;**运行期周期 CONVST 重触发已由 TIM6 更新中断(1 kHz)接管**(`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback`,2026-10-10)+ `adcTask` 经 L1 层 `adc_input_poll()` 消费(`tpafe5160_snapshot()` 8 通道 + STM32 ADC1 9 通道扫描)+ 帧统计,台架 `@test` 确认 `frame_seq` 严格 1000 Hz 递增、`frst_err=0`、`adc1_rc=0` / `adc1_err=0`;STM32 侧 ADC1 **消费者已落地**(`App/algo/adc_input.c` 的 `stm32_scan()`,L1-3/L1-4,2026-10-10),受双转换器无共同采样触发所限,仅逐通道独立监视。 - **后果**:上电后仅采集 1 个样本,此后数据永久陈旧。 - **B0 结论**(2026-10-10,详见 `docs/算法/继电保护功能复现规划.md` §1.5):① 持续采集 ② 1000 Hz(20 点/周波),通道 = 4U+4I(点表 `4011~4018`)③ 上行 **IEC 60870-5-104**(2404,非 ModBus RTU 上行)④ ADC 采集链路为遥测/保护的必需来源 → **常驻编译**;原 `APP_ADC_ENABLE` 占位宏零消费点,已于 2026-10-22 删除(将来若要裁剪,需在真实初始化点接入门控并登记进 `App/app/app_cfg.h`)。 -- **实现约束**(见 §9):周期 CONVST 重触发 + `adc_task` 消费 `tpafe5160_snapshot()`;**原子快照 API 已就位**(2026-10-10 修 E-44 / E-45 / E-58,旧的 `get_buf` / `data_ready` / `clear_ready` 已删 → 取数不再撕裂、重触发不再丢帧、芯片不驱动总线会被 `init()` 首帧自检抓到);栈预算核算;V0.1 通道映射已固化 → `docs/硬件平台/ADC通道映射说明.md`(13/17 路置换;语义 UA/UB/UC/UN、IA/IB/IC/IN、V_ZL2=温度,其余备用;网名 ADC17 = PC4 用 `ADC_CHANNEL_14`,**不是** `ADC_CHANNEL_12`)。⚠ **8 路电气量分属 TPAFE / STM32 两片转换器属 V0.1 硬件缺陷**(跨转换器无同一时刻性、精度与量程不一致,登记簿 N-18,V0.2 改版要求全部走 TPAFE)→ 在 V0.1 上主板侧只做**逐通道独立监视**,功率 / 功率因数 / 相角 / 矢量类量一律取子板计量模块结果,不得由主板混算。 +- **实现约束**(见 §9):周期 CONVST 重触发 + `adc_task` 消费 `tpafe5160_snapshot()`;**原子快照 API 已就位**(2026-10-10 修三条采集契约缺陷,旧的 `get_buf` / `data_ready` / `clear_ready` 已删 → 取数不再撕裂、重触发不再丢帧、芯片不驱动总线会被 `init()` 首帧自检抓到);栈预算核算;V0.1 通道映射已固化 → `docs/硬件平台/ADC通道映射说明.md`(13/17 路置换;语义 UA/UB/UC/UN、IA/IB/IC/IN、V_ZL2=温度,其余备用;网名 ADC17 = PC4 用 `ADC_CHANNEL_14`,**不是** `ADC_CHANNEL_12`)。⚠ **8 路电气量分属 TPAFE / STM32 两片转换器属 V0.1 硬件缺陷**(跨转换器无同一时刻性、精度与量程不一致,登记簿条目「双转换器无共同采样触发」,V0.2 改版要求全部走 TPAFE)→ 在 V0.1 上主板侧只做**逐通道独立监视**,功率 / 功率因数 / 相角 / 矢量类量一律取子板计量模块结果,不得由主板混算。 ### 5.10 RS-485 采集(FR-RS485)⬜ 待实现 @@ -513,7 +513,7 @@ sequenceDiagram - **实现**:lftpd(轻量 FTP 服务器),root = `/`(FatFS 卷根),端口 21,**单客户端**。 - **支持命令**:登录 / TYPE I / PASV / STOR / RETR / LIST / DELE / QUIT。 -- **鉴权**(已鉴权 · 2026-10-11,N-19):口令登录(`[ftp]` 段 `user` / `pwd_hash` = SHA-256(user‖password) / `write`);未登录仅 USER/PASS/QUIT/FEAT/SYST/NOOP 放行,其余一律 530;`write=0` 可禁 STOR/DELE;连续 5 次失败锁 60 s;出厂默认 `admin`/`admin`,默认口令在用有启动告警。口径详见 `docs/子系统设计/网络配置说明.md` §3.4。 +- **鉴权**(已鉴权 · 2026-10-11):口令登录(`[ftp]` 段 `user` / `pwd_hash` = SHA-256(user‖password) / `write`);未登录仅 USER/PASS/QUIT/FEAT/SYST/NOOP 放行,其余一律 530;`write=0` 可禁 STOR/DELE;连续 5 次失败锁 60 s;出厂默认 `admin`/`admin`,默认口令在用有启动告警。口径详见 `docs/子系统设计/网络配置说明.md` §3.4。 - **启动门**:`ftpTask` 必须等 `NET_READY` **且** `STORAGE_READY`(两位 `NoClear`)才 `lftpd_start`。 - **注意**:同一时刻只服务 1 个会话,旧会话异常断开后约 30 s 恢复监听;测试脚本须内置连接重试,**不要用裸 TCP 工具探测 21 端口**。 - **实测**:128 KB 上传下载逐字节一致(约 49/60 KB/s)。 @@ -680,7 +680,7 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | 项 | 现状 | |---|---| -| FTP 鉴权 | **已鉴权**(2026-10-11,N-19):口令登录(`[ftp]` 段存 SHA-256 摘要,不存明文)+ 连续 5 次失败锁 60 s + `write=0` 可选只读;出厂默认 `admin`/`admin`,默认口令在用有启动告警。边界:FTP 明文传口令,挡未授权访问、不挡网络嗅探(详见 `docs/子系统设计/网络配置说明.md` §3.4) | +| FTP 鉴权 | **已鉴权**(2026-10-11):口令登录(`[ftp]` 段存 SHA-256 摘要,不存明文)+ 连续 5 次失败锁 60 s + `write=0` 可选只读;出厂默认 `admin`/`admin`,默认口令在用有启动告警。边界:FTP 明文传口令,挡未授权访问、不挡网络嗅探(详见 `docs/子系统设计/网络配置说明.md` §3.4) | | UI 菜单权限 | 受保护叶子(行尾 `#P`)先经密码页;密码**仅编译期写入**(查看 `000` / 修改 `123`),不落 Flash、无运行期设置页 | | 远程显示 | 无鉴权,单连接;现场同网段部署 | @@ -772,7 +772,7 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | 缺口 | 现状 | 影响 | |---|---|---| -| **ADC 采集链路** | ✅ **采集链路已实现(B1 + L1,2026-10-10)**:采样节拍由 TIM6 更新中断(1 kHz)硬件触发;`adcTask` 经 L1 层 `adc_input_poll()` 消费(TPAFE 8 通道快照 + **STM32 ADC1 9 通道扫描**),台架 `@test` 闭环(1000 Hz、`adc1_rc=0`);取数按语义(L1-3/L1-4,N-18 逐通道独立监视) | 数据路径已通;剩余为上层保护/遥测算法与 104 上行 | +| **ADC 采集链路** | ✅ **采集链路已实现(B1 + L1,2026-10-10)**:采样节拍由 TIM6 更新中断(1 kHz)硬件触发;`adcTask` 经 L1 层 `adc_input_poll()` 消费(TPAFE 8 通道快照 + **STM32 ADC1 9 通道扫描**),台架 `@test` 闭环(1000 Hz、`adc1_rc=0`);取数按语义(L1-3/L1-4,双转换器无共同采样触发下逐通道独立监视) | 数据路径已通;剩余为上层保护/遥测算法与 104 上行 | | **RS-485 采集链路** | `rs485_task.c` 空桩;`App/protocol/` 仅 README | 无 RS-485 数据收发(B0 定:**对下 ModBus RTU 采集**) | | **DTU 核心转发职责** | **B0 已定(2026-10-10)**:上行 **IEC 60870-5-104**(以太网 TCP 2404,遥测短浮点系数 1);RS-485 对下 ModBus RTU 采集 | 转发目标已明确,待实现 | @@ -790,7 +790,7 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | 开关 | 位置 | 现状 | |---|---|---| -| 功能裁剪开关 | `App/app/app_cfg.h`(当前无宏) | **没有任何生效的裁剪开关**:原三个 `APP_*_ENABLE` 占位宏因全库零消费点已于 2026-10-22 删除(报告 S-4),各模块一律编译进产品——B0 已确认 ADC / RS-485 均为必需链路(ADC=遥测/保护来源,RS-485=对下 ModBus RTU 采集)。将来确需裁剪时在真实初始化点接入门控,并同步登记回该文件 | +| 功能裁剪开关 | `App/app/app_cfg.h`(当前无宏) | **没有任何生效的裁剪开关**:原三个 `APP_*_ENABLE` 占位宏因全库零消费点已于 2026-10-22 删除,各模块一律编译进产品——B0 已确认 ADC / RS-485 均为必需链路(ADC=遥测/保护来源,RS-485=对下 ModBus RTU 采集)。将来确需裁剪时在真实初始化点接入门控,并同步登记回该文件 | | `TEST_SUITE_*` | `test/board/test_config.h` | 测试套件**互斥**,一次构建只开一个;全注释 = 正常产品固件 | --- @@ -836,7 +836,7 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | V2 | 任务饿死 → IWDG 复位 | ✅ 800 ms 声明 stall,≤2.5 s 复位(`reset cause: IWDG`) | | V4 | 关中断死循环 → IWDG 复位 | ✅ 每 ~4.9 s 稳定复位(IWDG 独立于中断) | | V5 | 30 min 真实负载长跑 | ✅ 120/120 FTP PASS,零 IWDG 复位 | -| P0-1 | 配置引擎堆分配改造(真机) | ✅ `[CFG] load: OK`(经 FreeRTOS 堆);FTP 改 IP 复位即生效 | +| 堆分配改造 | 配置引擎堆分配改造(真机) | ✅ `[CFG] load: OK`(经 FreeRTOS 堆);FTP 改 IP 复位即生效 | | 存储/网络套件 | 16/16 PASS | ✅ | --- diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index 2f63426f..5997fd48 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -18,23 +18,23 @@ git commit ├─ cppcheck C 静态分析(自有代码) ├─ no-mojibake 阻止 U+FFFD 乱码文件 ├─ coding-standard C 代码规范检查(规范V1.0 可机械校验项) - ├─ doc-links 文档链接存在性(D-3/D-7,.md) - ├─ task-table FreeRTOS 任务表一致性(D-2/D-7) - ├─ mem-numbers 内存/Flash 数字一致性(D-7 体积档) + ├─ doc-links 文档链接存在性(.md) + ├─ task-table FreeRTOS 任务表一致性 + ├─ mem-numbers 内存/Flash 数字一致性(体积档) ├─ lv-conf-sync 两份 lv_conf.h 特性子集一致性(比对生效值) ├─ cubemx-survival CubeMX 重生成存活审计(易失定制清单 + 基线漂移) ├─ watchdog-unit-test 看门狗判定状态机主机侧单测(L0.5) ├─ test-suite-switch 测试套件开关=产品构建(防测试固件静默提交) - ├─ registration-check 双端注册反向校验(uvprojx + CMakeLists,N-23;含 UI 页面描述符表接线,U4) - ├─ mem-budget RAM 预算 92% 阈值门禁(N-27 三件套,2026-10-12) - └─ font-charset UI 字符集差集检查(防缺字白框,U4,2026-10-15) + ├─ registration-check 双端注册反向校验(uvprojx + CMakeLists,登记簿「rs485_task 白名单缺口」;含 UI 页面描述符表接线,「UI 字符集与像素回归门禁」) + ├─ mem-budget RAM 预算 92% 阈值门禁(登记簿条目「缩堆迁 CCM 后半(暂缓)」,2026-10-12) + └─ font-charset UI 字符集差集检查(防缺字白框,「UI 字符集与像素回归门禁」,2026-10-15) └─ commit-msg(.git/hooks/commit-msg) └─ gitlint 校验 commit message 格式 ``` **两阶段钩子**:`pre-commit` 阶段拦截代码问题,`commit-msg` 阶段拦截格式问题。任一失败 → commit 中止。 -> **CI 侧的同一套门禁(2026-10-01 T-6)**:Gitea Actions(`.gitea/workflows/ci.yml`)在 push / PR 时 +> **CI 侧的同一套门禁(2026-10-01 起)**:Gitea Actions(`.gitea/workflows/ci.yml`)在 push / PR 时 > 以 `pre-commit run --all-files` 复跑**全部上述钩子**(`mem-numbers` 在无 Keil `.map` 的 runner 上自适应跳过)。 > 本地提交前想跑同一套判定:`& '.venv\Scripts\python.exe' scripts/ci_local.py`。详见 `../测试说明/测试开发标准流程.md` §9。 @@ -241,9 +241,9 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env | `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) | | `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 | | `scripts/check_coding_standard.py` | scripts/ | 代码规范检查脚本(规范 V1.0 可机械校验项) | -| `scripts/check_doc_links.py` | scripts/ | 文档链接存在性检查(D-3/D-7) | -| `scripts/gen_task_table.py` | scripts/ | FreeRTOS 任务表生成/校验(D-2/D-7) | -| `scripts/check_mem_numbers.py` | scripts/ | 内存/Flash 数字一致性(D-7 体积档) | +| `scripts/check_doc_links.py` | scripts/ | 文档链接存在性检查 | +| `scripts/gen_task_table.py` | scripts/ | FreeRTOS 任务表生成/校验 | +| `scripts/check_mem_numbers.py` | scripts/ | 内存/Flash 数字一致性(体积档) | | `scripts/check_cubemx_survival.py` | scripts/ | CubeMX 重生成存活审计 | | `scripts/check_lv_conf_sync.py` | scripts/ | 两份 `lv_conf.h` 特性子集一致性(比对生效值 + 无效宏名 + 重复定义) | | `scripts/check_test_suite.py` | scripts/ | 测试套件开关门禁(防测试固件静默提交) | diff --git a/docs/任务规划/README.md b/docs/任务规划/README.md index d967601d..f07bb080 100644 --- a/docs/任务规划/README.md +++ b/docs/任务规划/README.md @@ -1,158 +1,93 @@ -# 任务规划(统一待办登记簿 · 索引与排序权威) +# 任务规划 -> **这个文件夹是什么**:全项目唯一的未闭环事项清单 + 全项目唯一权威的工作先后顺序。 -> 2026-10-12 按「变更频率 + 职责」解耦成多文件——并行任务写范围互不相交,各改各的文件。 -> -> **文件地图(谁改哪个)**: -> -> | 文件 | 内容 | 变更频率 / 写者 | -> |---|---|---| -> | `README.md`(本文件) | 编号空间(§0)· 未闭环一览(§1)· **推进顺序权威(§2)** · 维护约定(§3) | 低频;仅主工作树 | -> | `采集保护收尾.md` | 「采集保护收尾」剩余:只剩 M3 送检(B10) | 低频——送检窗口前后改 | -> | `UI补齐.md` | **当前波次(2026-10-25 用户定项)**:菜单树剩 21 叶未绑,按数据源就绪度分三梯队;含现状核对、三梯队、**7 张任务卡** | **高频**——开工后状态流转只改它 | -> | `数据可信.md` | **收口态**:软件侧已 100%,只剩卡 0 / 0b / 12 三张台架与外部卡;含事实基线、折算模型、前置决策(**代码头注释引用其 §2/§4/§7/§8/§9/§10 为口径出处**) | 低频——只在上机或外部资源到位时改 | -> | `排期详述.md` | 「结构债」/「验证常态」波内详表 | 低频 | -> | `并行分道方案.md` | **待办横向归道**:文件级写域(域1~域8)· 四条道(道E~道H)· 批次与合并次序 · 不能并行的事项 | 低频——分道或批次变化时改;**明细不复制**,只做归道 | -> | `暂缓-RS485.md` | B2 RS-485 整簇冻结清单(用户决定) | 极少 | -> | `登记簿.md` | N-x / S-x / T-x 长期登记(网络 / 存储 / 硬件 / 测试 / 报告并轨) | 中频——新发现进对应表 | -> | `测试与架构专项.md` | S1x / A-x 专项**未闭环项** | 低频 | -> | `基线与待确认.md` | **只放还没答案的问题**(OQ 族属主登记处);基线类内容已移出到模块文档 | 参考,少改 | -> -> **怎么用**:想知道"现在该做什么、按什么顺序" → 本文件 **§2**;想知道"当前波次任务卡状态 / 验收判据 / 并行道" → `UI补齐.md`; -> 想知道"某条待办的来龙去脉" → 按编号查 `登记簿.md` / `测试与架构专项.md`;想知道"还差哪些验证" → `排期详述.md` §2。 -> -> **只保留待办**:已完成条目直接删除,不留变更记录(历史见 `git log` 与各模块文档); -> 已闭环项的**结论与证据沉淀进对应模块文档/问题记录**,本文件夹不再重复。 -> **消化即销项**:事项修复/关闭后,直接删除对应条目,并同步清理其它文档对本编号的引用。 -> -> **不在本文件夹范围**:`../测试说明/实机测试实施指南.md`(执行流程)。 +> **现在只做 UI 补齐。第一项是实现 `param_request` 写路径,然后做整定 4 页。** +> 详细任务与验收见 `UI补齐.md`;UI 完成后的第一项是 net 层跨上下文收口。 + +## 怎么用这份规划 + +| 你想知道什么 | 只看哪个文件 | +|---|---| +| 现在做什么、接下来做什么 | 本文件 | +| 当前任务怎么做、怎样算完成 | `UI补齐.md` | +| UI 之后还有什么、哪些事在等条件 | `登记簿.md` | +| ADC 标定为什么没做、保护为什么不动作 | `数据可信.md` | +| RS-485 为什么冻结、重启要满足什么 | `暂缓-RS485.md` | +| 测试与架构还剩什么约束 | `测试与架构专项.md` | + +**一件事只有一个归属。** 本文件只负责导航与顺序,不复制各任务的验收明细; +`登记簿.md` 不再复制当前 UI 任务,`数据可信.md` 的其余章节是被代码引用的设计约束, +不是额外待办。 --- -## 0. 编号空间与来源 +## 0. 条目与引用规则 -| 编号 | 含义 | 备注 | -|---|---|---| -| **语义标识**(**2026-10-14 起新条目唯一形式**) | `#` 列填一行短语直接说清是什么事,如 `沉降窗 800ms 上限只留痕`、`RS485 采集接线` | **新条目一律用它,不再新增字母+数字编号**;同一表内语义天然不重复 → 结构上防号;引用时带文档名 | -| `P0-x`~`P3-x` | 审计缺陷/改进项(2026-09-15/09-21 审计) | P0=功能 bug、P1=疑似 bug/健壮性、P2=改进、P3=卫生 | -| `N-x` / `S-x` / `T-x` | 台架/审计观察登记项 / 存储·日志管线 / 测试与工具链 | **存量冻结沿用、不再新增**(新条目见上一行);`N-x` 含硬件改版项(需下一版硬件,固件侧只记口径,如 N-18) | -| `S1~S14` / `A-1~A-7` | 2026-10-02 复审的测试充分性与架构专项 | 编号**不回收** | -| `E-xx` | 错误码传播审计发现(**历史族**) | 属主文档已于 2026-10-15 删除;编号冻结(`docs/编号规范.md` §4.1 / §4.3),存量引用仅作历史指称,新发现一律 `DEF` | -| `B` 前缀 | 功能主线排期条目 `B0`~`B10`(见 §2) | 存量冻结、不再新增。原 `C`(结构债)/ `D`(验证窗口)两族 **2026-10-25 废止**,条目改语义名——它们与 UI 设计约束、设计决策记录同字母不同义 | -| 报告 `N-x`/`R-x`/`F-x`/`S-x`/`U-x`/`D-x` | 《项目审查与优化建议报告》**自有编号**(**该报告 2026-10-25 已删除**) | ⚠ 曾与本登记簿**同名不同义**(报告 N-16 = 日志逐条开关,登记簿 N-16 = A2/A4 负向台架)。其未闭环项已于 2026-10-11 并入本登记簿(`N-19`~`N-29` + U 簇),**现存引用只是历史指称,不再新增** | - -> ⚠ **为什么 2026-10-14 改用语义标识**(一条实测失效案例):原规则写着"编号**不回收**",但 -> 销项 = **删行**,删完仓库里就不留任何痕迹——登记簿 `N-31`(2026-10-11 的 Phase 7 复跑项) -> 销项后,2026-10-14 新建条目按"现存最大值 +1"又取了 `N-31`,与 `CH395F_Test_Guide.md`、 -> `自动化测试流程.md` 里那两处历史引用**撞号**。"不回收"要靠人先想起去翻 `git log` -> = 不可执行的规则。语义标识自带唯一性(重名一眼可见);**已随销项消失的字母+数字号一律作废**, -> 不得作为新条目候选,历史引用(如上述两处 `N-31`)指向的是当年的旧条目。 - -**严重度定义**(沿用审计口径):P0 = 确认功能 bug|P1 = 疑似 bug 或健壮性风险|P2 = 改进|P3 = 卫生。 -**置信度**:high = 代码级确证|medium = 依赖外部条件(手册/硬件/产品定义)。 +- **新条目使用语义短语**,一行直接说清是什么事,不再新增字母加数字编号。 +- 旧编号只用于追溯历史,不复用、不重新发号;编号族与冻结规则见 `docs/编号规范.md`。 +- 引用条目时写清所在文档和语义名,例如:`docs/任务规划/登记簿.md` 的 + 「net 层跨上下文收口」。不要只写一个短号让读者猜。 +- 任务状态只维护在任务所属文件;本文件不另建一套状态表。 --- -## 1. 未闭环一览(按类别) +## 1. 当前执行:UI 补齐 -| 类别 | 数量 | 条目 | +菜单树共 33 叶,已绑 12 叶,剩 21 叶。其中 **16 叶纳入当前执行**,另外 5 叶因缺少 +开入硬件、录波模块或 SNTP 产品定义而不做。唯一执行清单见 `UI补齐.md`。 + +| 顺序 | 要做的事 | 交付结果 | |---|---|---| -| 网络 | 4 | N-3(L5 基线重验)· N-16(A2/A4 负向台架)· N-30(relisten 沉降段统一受阻,观察)· **reconcile 突发饿死 uiTask**(🟦并档 E-27/28,批 4 评估)——2026-10-25 台架两轮分箱定案的饿死型签名,详 `登记簿.md` §1 | -| 存储 / 日志 | 3 | **S-3(USART1 TX-only 根治)** · S-4(dhara 重试自旋)· S-5(可选全序列卷锁) | -| 结构 / 健壮性 | 3 | N-23(注册校验残余白名单)· N-25(性能/语义簇)· N-28(uvprojx 空 TU 观察) | -| 仓库 | 1 | N-29(`.git` **233 MB**,**已决策暂不处理、登记观察**) | -| 硬件 | 1 | N-18(V0.2 改版项) | -| 测试 / 基础设施 | 3 | T-7(CI 远端两 job 闭环 + 第二机交叉验证)· A-4(采集链路测试约束,RS485 侧随 B2 暂缓)· 串口助手 A6/A7 补验(待 CH340) | -| **UI**(当前波次) | 1 簇 · **21 叶** | **U5 页面落地**——33 叶已绑 12、剩 **21**(不是原口径的 4 叶):第一梯队 9 叶(整定 4 + 维护 3 + 遥测 2,数据源全就绪)· 第二梯队 4 叶(通讯设置 + 遥信 3 页)· 第三梯队 6 叶(记录 5 + 运行报告)· 不排 2 叶(手动录波缺模块 / SNTP 缺口径)。组件层三件套已落地但**零消费者**,详表 `UI补齐.md` | -| **暂缓** | 2 簇 | **B2 RS-485 整簇**(`暂缓-RS485.md`)· **N-27 内存优化簇剩余**(R-2 后半 / R-3 / R-4 / R-6;重开条件见 `../内存管理/RAM使用分析与优化方案.md` §4)。⚠ 已落地的 R-9 与 `mem-budget` 门禁/台账/趋势**不回退** | -| **数据可信**(收口态) | 1 簇 · 剩 3 张外部卡 | **软件侧已 100%**,只剩 🔴 已知电压注入(卡 0,一块万用表即可)· 🟡 端子变比(0b)· 🟡 标准源两点标定(12),详表 `数据可信.md` §5。⚠ **保护现处于未标定闭锁态**——`protect_task.c:98` 要求 UA/UN/IA 的 `ADC_CAL_F_VALID` 全置位,而 VALID 只在标定写入 `[adc_cal] valid` 后才置位;用户裁定(2026-10-25)**保持闭锁到标准源到位**,不缩短解锁路径 | +| 1 | **`param_request` 写路径** | UI 能安全提交参数修改,并收到成功或失败状态 | +| 2 | 整定 4 页 | 参数、定值、控制字、软压板可修改并持久化 | +| 3 | 装置维护 3 页 | 时钟设置、强制复归、清除记录 | +| 4 | 遥测死区与系数 2 页 | 修改既有 `run_par` / `adc_cal`,不新增第二份真源 | +| 5 | 通讯设置 | 新增 `[comm]` 配置段并接入 104 | +| 6 | 记录 5 页 | 事故、操作、保护告警、保护启动、遥控记录有写入方并可查看 | +| 7 | 运行报告 | 上电次数、运行时长、复位原因、104 统计 | -> **网络侧 E-xx 存量项**(历史编号族,属主文档已于 2026-10-15 删除,编号冻结见 -> `docs/编号规范.md` §4.3):E-27 / E-28 挂「net 层跨上下文收口」(见 `排期详述.md` §1); -> E-47 待 CH395F 驱动侧定案;E-12 为 `FF_FS_LOCK` / 单写者纪律记录项;E-31 后续与 E-62 -> 为回归监督项(纪律已固化于 `AGENTS.md`,无需改动)。 -> UI 标准化只剩 U5 页面落地(21 叶),见 §2「UI 补齐」;lftpd 主机测试缺口为整簇排期项。 - -### 1.1 结构性判断(排序依据) - -1. **功能主线优先(用户决定 2026-10-12)**:把"采集→相量→保护→上送"链路收口到"台架闭环 + 功能面完整 + 可送检"。前两项已达成,「采集保护收尾」只剩 M3 送检(`采集保护收尾.md`)。**RS-485 对下采集(B2)暂缓**(`暂缓-RS485.md`),主线不等它。 -2. **产品功能面在 V0.1 已到顶**(2026-10-25 复核):保护元件的三相类(B/C 相过流、零序过流、PT/CT 断线、方向/功率类)**全部被 N-18 硬件缺陷挡住**(跨转换器无同一时刻性 + STM32 侧削顶),剩余可行的只有 A 相涌流制动 / 突变量启动 / 无压无流闭锁 / 元件级校验和 4 项,而它们**没有标准源就无法验证**。加上**保护当前整条链处于未标定闭锁态**(`protect_task.c:98`),保护侧的工作在标准源到位前**没有可闭环的增量**。 -3. **数据可信软件侧已尽**:剩下的卡 0 / 0b / 12 三张全是台架或外部资源(万用表 / 子板 / 标准源),软件道插不上手。 -4. **内存预算红线已机械化**:`mem-budget` 钩子拦 RW_IRAM1/RW_CCM >92%,子系统配额 / CCM 台账 / 趋势基线 → `docs/内存管理/RAM预算台账.md`。**R-2 后半与 R-3 / R-4 / R-6 整簇暂缓**(CCM 只剩 **7,904 B**,重开条件见 `../内存管理/RAM使用分析与优化方案.md` §4)——**门禁与台账继续常开,它们正是"何时该重开"的传感器**。 -5. **验证债仅剩窗口性事项**(L5 基线重锁 / A2-A4 负向台架 / 串口助手补验 / T-7 CI 两 job),不单独排期、逢机顺手做。 - -> **结论(2026-10-25 重排的实质)**:第 1~3 条说明**高优先级波次全部被外部资源或硬件代次卡住**, -> 而 UI 补齐是唯一"数据源已就绪、不依赖台架、能整波闭环"的工作。故本轮**只做 UI**, -> 结构债押后——不是它不重要,是它没有外部依赖、随时能捡起来,而 UI 的 21 叶一直压着产品可见度。 +**执行纪律:单道串行。** 菜单树与页面注册表是生成物,字库也是整文件生成物; +不要同时开多条 UI 页面分支。上板验证可累积成一批,但保存、重启、主站读回等判据不能删除。 --- -## 2. 统一推进顺序(全项目唯一权威) +## 2. 全项目推进顺序 -> **本节是全项目所有工作流唯一的权威顺序**:`产品需求设计文档.md` §9.2、`../算法/继电保护功能复现规划.md`、 -> 各模块文档里的路线图均为局部视图,**顺序变更只改本节,其它文档只留指针**。 -> 排序原则:**安全风险 > 产品功能主线 > 数据正确性 > 预算/结构债 > 验证常态 > UI 扩展 > 仓库决策**(当前无未闭环安全项)。 -> **但优先级只在"能开工"的项之间比较**:高优先级波次若全部被外部资源(标准源 / 子板 / 台架 / runner) -> 或硬件代次卡住,波次表按"**现在能整波闭环**"重排,阻塞原因写进「为什么在这」列—— -> 2026-10-25 的重排就是这种情况(保护元件被 N-18 挡、数据可信被标准源挡 → UI 补齐顶上)。 -> 波内详表在挂接文件里(`采集保护收尾.md` / `UI补齐.md` / `排期详述.md` / `暂缓-RS485.md`),但**波次增删与先后只改 §2.0**。 +> 本节是全项目唯一的顺序权威。模块文档只描述局部设计,不另排一套路线图。 -### 2.0 波次总览 +### 2.0 顺序表 -> **波次不带编号**(2026-10-25 起):波次名本身就是标识。原先的 `W1`~`W8` 已废止——它们看着像 -> 执行顺序,实际 `W8` 排在 `W3`/`W4` 之前,读者必须先查表才知道谁先谁后。现在**执行顺序就是 -> 本表从上到下**,不再多一层映射。 +| 顺序 | 工作 | 当前状态 | 开始或切换条件 | +|---|---|---|---| +| **1** | **UI 补齐** | **当前执行** | 已开始;按 §1 的 1→7 顺序推进 | +| **2** | **net 层跨上下文收口** | 下一步 | UI 当前清单完成后;裸指针收口与 reconcile 突发饿死 uiTask 同批评估 | +| **3** | 其余结构与可靠性评估 | 排队 | net 收口后;逐项见 `登记簿.md`,观察项不为了“清清单”而强做 | +| **4** | 数据可信剩余三项 | 等待台架或外部资源 | 已知电压注入 → 端子变比 → 标准源两点标定;资源到位可插入优先处理 | +| **5** | 标准源送检与第三方 104 主站验收 | 等待标定完成 | 两点标定完成且保护 VALID 已置位;送检窗口前 2 日冻结特性 | +| **6** | 保护元件与采集增强 | 等待标准源或硬件改版 | 保护先解除未标定闭锁;三相同时刻类功能等 V0.2 硬件 | +| **7** | 验证窗口事项 | 逢条件顺带 | CI runner、PC peer、CH340、负向故障注入条件到位时执行 | +| **8** | 独立模块与暂缓项 | 当前不排 | 开入开出、故障录波、SNTP 单独立项;RS-485 与内存优化按各自重启条件 | -| 波次 | 内容 | 为什么在这 | -|---|---|---| -| **采集保护收尾** | **只剩 M3 送检**(B10,Fixed Date)——任务卡与判据见 `采集保护收尾.md` | 收口目标"台架闭环 + 功能面完整 + 可送检"的前两项已达成;**真约束只剩送检窗口**,窗口一到全道让路、窗口前 2 日特性冻结。⚠ B10 的「L1-5 标定复核」依赖卡 12,而卡 12 要标准源;保护现处于未标定闭锁态(`protect_task.c:98`),送检前必须先解卡 | -| **数据可信**(收口态) | **软件侧已 100%**:**只剩 3 张台架与外部卡**——卡 0 已知电压注入(万用表)· 卡 0b 端子变比(子板)· 卡 12 标准源两点标定。详表 `数据可信.md` | **用户决定(2026-10-23):通道标定先于 UI 与结构债**——它是唯一"改一处、全链受益"的入口(`App/task/adc_task.c:335` 是码值→工程量的唯一位置)。⚠ **剩下的三张卡没有软件道可做**:0 要万用表与台架、0b 要子板、12 要标准源。**台架不在手边(2026-10-25 确认)→ 本波整体顺延**,不占编码道 | -| **UI 补齐**(当前) | 菜单树剩 **21 叶未绑**(33 叶已绑 12):第一梯队 9 叶零依赖 · 第二梯队 4 叶需新配置段 · 第三梯队 6 叶需写入方或计数器;7 张任务卡见 `UI补齐.md` | **用户决定(2026-10-25):下一波只做 UI 补齐,结构债押后**。判据是"能开工"——产品功能面被 N-18 挡、数据可信被标准源挡,而结构债无外部依赖、随时能捡;UI 是唯一**数据源已就绪、不依赖台架、能整波闭环**的工作,且 21 叶一直压着产品可见度。组件层三件套(编辑器 / 定值表 / 操作页)已落地但**零消费者**,不写页面等于白做 | -| **结构债**(押后) | **net 层跨上下文收口(含 2026-10-25 台架实证的 reconcile 饿死签名)→ 注册校验白名单清理 → 性能语义簇 / 空 TU**;USART1 改为只发不收**单独独占一轮**(CubeMX 重生成,需清空全部工作树)。详表 `排期详述.md` §1 | **无人在等、无外部依赖 → 随时能捡起来**,故排在 UI 之后。其中 net 收口优先于 USART1:饿死签名是台架**实测捕获**的可用性缺陷(8 连窗 UI 缺席 800 ms+,v1.1 判据下 3 事件 8 行、漏检与误报均为零),而 USART1 那条只是 IWDG 误复位的机制根源、且已有 250 ms drain 预算 + 300 ms 冷却的缓解 | -| **预算与内存** | ⏸ **整簇暂缓**:R-2 后半(缩堆 56→32 KB → 29.5 KB 迁 CCM)· R-3 · R-4 · R-6;R-5 已判定不可行 | **不再排期**:没有功能在等这块预算,而搬迁要动 scatter、要逐条过 DMA 可达性清单、缩堆还要上板复测。重开条件 → `../内存管理/RAM使用分析与优化方案.md` §4 | -| **验证常态** | T-7 CI · L5 基线重锁 · A2/A4 负向台架 · 串口助手 A6/A7(详表 `排期详述.md` §2) | 窗口性,逢机顺带,不单独排期 | -| **仓库决策** | **N-29** `.git` **233 MB** + 图片 36.3 MB | **已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评(`git filter-repo` 历史重写影响未推送提交与协作者) | -| **暂缓** | **B2 RS-485 整簇**(`暂缓-RS485.md`) | **用户决定(2026-10-12):当前不处理 RS-485** | -| **硬件改版** | N-18 8 路电气量全接 TPAFE(V0.2) | 下一版硬件;固件侧已用语义映射隔离,改版只动映射表 | +### 2.1 关键依赖 -### 2.1 顺序一图 - -``` -采集保护收尾 只剩 B10 送检(M3, Fixed Date) 详表 采集保护收尾.md - ↓ ⚠ 送检的「L1-5 标定复核」依赖卡 12(标准源),且保护要先解卡 -数据可信 ⏸ 软件侧已 100%(只剩台架与外部卡) - 只剩 卡 0(万用表)→ 0b(子板变比)→ 12(标准源) 详表 数据可信.md - 台架不在手边(2026-10-25)→ 整体顺延,不占编码道 - ↓ -UI 补齐 21 叶:第一梯队 9(零依赖)→ 第二梯队 4(新配置段) - → 第三梯队 6(写入方 / 计数器) 详表 UI补齐.md - 单道串行(菜单树与页面注册表是单写者生成物) - ↓ -结构债 net 收口(含 reconcile 饿死签名)→ 白名单 → 性能簇 / 空 TU - USART1 只发不收 = 单独独占一轮(需清空全部工作树) - ↓ -预算与内存 ⏸ 整簇暂缓(重开条件见 `../内存管理/RAM使用分析与优化方案.md` §4) -验证常态 T-7 CI · L5 基线重锁 · A2/A4 负向台架 · 串口助手 -仓库决策 N-29 .git 233MB(已决策暂不处理) 硬件:N-18 V0.2 -暂缓 B2 RS-485 整簇(见 暂缓-RS485.md) -``` +- **UI**:`param_request` 写路径 → 整定 4 页;其余页面按 §1 串行推进。 +- **送检**:已知电压注入 → 端子变比确认 → 标准源两点标定 → 保护解除闭锁 → 送检。 +- **保护扩展**:标准源未到位前不新增无法验证的判据;跨转换器相位、功率、对称分量类 + 功能必须等 V0.2 硬件。 +- **结构债**:UI 完成后先做 net 收口;RS-485 白名单、内存优化、仓库体积等观察项不抢占 + 当前编码道。 --- ## 3. 维护约定 -- 本文件夹是**全项目唯一的未闭环事项登记簿**——"盘点 + 排序 + 明细"三合一,明细按职责拆进本文件夹各文件(2026-10-12 解耦),不再分散到其它文档。 -- **并行写纪律**:任务分支(worktree)**不改本文件夹任何文件**;任务卡状态列(⬜/🟦)与销项一律由 Lead 在主工作树更新。解耦的收益 = 不同波次 / 不同登记之间互不冲突,**不是**任务分支可以写。 -- **任一项完成后直接删除对应条目**(先销本文件夹条目,再清理其它文档对本编号的引用);**不留变更记录**,历史见 `git log` 与各模块文档。 -- 已闭环条目的**结论与证据沉淀进模块文档**,本文件夹不再重复。 -- **2026-10-14 起新条目用「语义标识」**:`#` 列填一行短语(如 `沉降窗 800ms 上限只留痕`), - **不再新增字母+数字编号**(规则见 `docs/任务规划/README.md` §0 表首行);存量编号 - (P-x / N-x / S-x / T-x / S1x / A-x / B 前缀)**冻结沿用**,而**已随销项消失的号永久作废**、 - 不得作为新条目候选——"不回收"在只写规则、不留痕迹的情况下不可执行(撞号实测见 - `docs/任务规划/README.md` §0 注);E-xx(历史编号族)冻结于 `docs/编号规范.md` §4.3。 -- **引用审查报告编号一律带文档名**(报告 N-16 ≠ 登记簿 N-16、报告 S-3 ≠ 登记簿 S-3); - 报告并轨项用本登记簿新编号(N-19~N-29 + U 簇),出处列保留报告编号。 -- **§2 是全项目唯一的推进顺序权威**:`产品需求设计文档.md` 等文档中的路线图只留指针,顺序变更只改 §2.0(详表文件跟着挪,不改顺序)。 -- **暂缓不是销项**:条目仍在簿,只是不占波次。有独立清单文件的(`暂缓-RS485.md` = B2)重启或关闭时更新该文件与 §1/§2.0;**只在原处标注的**(N-27 内存优化簇,2026-10-15 用户裁定,理由与重开条件在 `../内存管理/RAM使用分析与优化方案.md` §4)重启时改 `登记簿.md` 该行 + 本文件 §1/§2.0 三处。 -- 新增发现按既有惯例分流:能立即修的进 §2 波次(结构债);需排期的进 `登记簿.md` / `测试与架构专项.md` 对应编号空间;跨版本观察的进 `登记簿.md`。 -- 上游引用关系:`测试说明/实机测试实施指南.md`(执行流程)——不在本文件夹合并范围。 +1. **完成即删除待办条目**,同时清理其他文档对它的引用;结论和证据写入模块文档, + 历史由 `git log` 保留。 +2. **当前任务只改 `UI补齐.md`,后续事项只改 `登记簿.md`**;顺序变化才改本文件 §2。 +3. `数据可信.md` 的 §2、§4、§7~§10 仍被代码头注释引用为设计依据,不能按“已完成” + 删掉;其待办只认 §5 的三项。 +4. `测试与架构专项.md` 的断言与体积门禁章节仍是代码和门禁的规格锚点,即使事项已完成 + 也不能删;当前未闭环的只有 RS-485 侧采集链路测试约束。 +5. 任务分支不直接修改 `docs/任务规划/**`;状态更新与销项由主工作树统一处理。 +6. 并行开发机制、工作树和锁的使用见 `docs/环境与部署/并行任务工作树方案.md`。 + 本文件夹不再另维护一份分道、批次和写域方案。 +7. 暂缓不是完成:条目必须保留重启条件;没有重启条件的“以后再说”应写成观察项或删除。 diff --git a/docs/任务规划/UI补齐.md b/docs/任务规划/UI补齐.md index 2e7c532a..b0581b64 100644 --- a/docs/任务规划/UI补齐.md +++ b/docs/任务规划/UI补齐.md @@ -1,124 +1,89 @@ -# UI 补齐(U5 页面落地 · 当前波次) +# 当前任务:UI 补齐 -> **一句话**:菜单树 33 个叶子已绑 12、剩 **21**;组件层三件套(数值编辑器 / 定值表 / 操作页) -> 已落地但**零消费者**。本波按**数据源就绪度**把 21 叶分三梯队,先做零依赖的 9 叶。 -> -> **本文定位**:本波次的任务卡与验收判据。**排序权威在 `docs/任务规划/README.md` §2**。 -> 逐页行表与代码改动量见 `docs/UI设计/页面扩充详细方案.md`;清单与就绪度见 -> `docs/UI设计/页面补齐规划.md`;数据供给契约见 `docs/UI设计/UI_数据供给设计说明.md`。 -> -> **修订**:2026-10-25 建档(用户决定:下一波只做 UI 补齐,结构债押后;台架不在手边, -> 卡 0 顺延)。统计口径 = **按菜单树叶计**(不按页面批次计),当前 33 叶已绑 12、剩 21。 +> **从第 1 项 `param_request` 写路径开始。完成它以后再绑定整定 4 页。** +> 本文件是当前唯一执行清单;全项目顺序见 `docs/任务规划/README.md` §2。 + +## 1. 目标与范围 + +菜单树 33 叶,已绑 12 叶,未绑 21 叶: + +| 分类 | 数量 | 当前怎么处理 | +|---|---:|---| +| 数据源已就绪 | 9 | 当前执行:整定 4 + 装置维护 3 + 遥测死区/系数 2 | +| 需补配置或写入方 | 7 | 当前执行:通讯设置 1 + 记录 5 + 运行报告 1 | +| 无开入硬件 | 3 | 不做:遥信类型、遥信防抖、双点遥信 | +| 缺独立模块或产品定义 | 2 | 不做:手动录波、SNTP 设置 | + +现有基础:`ui_w_editor` / `ui_w_setlist` / `ui_page_action` 已就绪; +`ui_data` 的 rt / par / info 三通道已接线;像素黄金用例与字符集门禁已存在。 +**这些基础当前没有完整页面消费者,本任务就是把它们真正用起来。** + +逐页形态见 `docs/UI设计/页面补齐规划.md`;数据契约见 +`docs/UI设计/UI_数据供给设计说明.md`。本文件只维护执行顺序和完成判据。 --- -## 1. 现状(2026-10-25 代码级核对) +## 2. 执行清单 -| 项 | 数 | 说明 | +> 状态只在这里维护。完成一项就删除对应行,并同步调整后续顺序;不要在本文件留完成史。 + +| 顺序 | 任务 | 完成判据 | 主要写范围 | 状态 | +|---|---|---|---|---| +| **1** | **`param_request` 写路径** | 实现 `ui_data_param_request/state`:深度 4 环形队列,满返 -1;状态含 `OK` / `ERR_RANGE` / `ERR_LOCKED` / `ERR_BUSY`;`ui_data_feed_tick()` 取出后调用 `prot_set_set_sealed()` 并置脏;主机单测覆盖满队列、越界、闭锁、正常往返 | `Drivers/BSP/UI/ui_data.*`、`App/app/ui_data_feed.*` | 未开始 | +| **2** | **整定 4 页** | 参数、定值、控制字、软压板 4 叶绑定页面;就地修改并保存后,重启值保持且校验和通过;主站 `C_RD` 读到同值;越界行高亮且值不落盘;黄金用例入库 | `Drivers/BSP/UI/`、`ui_menu_tree.txt`、生成物 | 未开始 | +| **3** | **装置维护 3 页** | 时钟设置写入 `sys_clock_set_unix()` 并回读;强制复归确认后动作位复归且有 SOE;清除记录先补 `sys_log` 删除 API,覆盖删后重建、计数归零、不存在时幂等 | `Drivers/BSP/UI/`、`App/log/sys_log.*` | 未开始 | +| **4** | **遥测死区与系数 2 页** | 修改死区能改变 104 上送门限;修改 `k` 后页面值按比例变化;两页只走既有 `[run_par]` / `[adc_cal]` 落盘链,不新增真源 | `Drivers/BSP/UI/`、`ui_menu_tree.txt` | 未开始 | +| **5** | **通讯设置** | 新建 `[comm]` 段承载公共地址、端口、主站地址;修改并重启后连接与公共地址生效;核对配置段数和键数上限 | `App/cfg/`、`App/protocol/iec104/`、`Drivers/BSP/UI/` | 未开始 | +| **6** | **记录 5 页** | 事故、操作、保护告警、保护启动、遥控各有一个写入点和记录类型;可翻最近 20 条;空记录明确显示“无记录”;共用 reader | `App/log/`、`Drivers/BSP/UI/` | 未开始 | +| **7** | **运行报告** | 展示上电次数、运行时长、复位原因、104 统计;复用 info 通道的已有数据;新计数器明确“掉电保持”或“本次运行”,不得含糊 | `App/log/` 或 `App/app/`、`Drivers/BSP/UI/` | 未开始 | + +### 依赖关系 + +- 1 → 2 是硬依赖;3、4 虽可独立开发,但当前仍按表序单道执行。 +- 3 的“清除记录”包含 `sys_log` 删除 API,不允许只做一个没有实际效果的按钮。 +- 5 可先完成配置层再接页面,但最终必须作为一整项验收。 +- 6、7 先补真实写入方或计数器,再做页面;不允许用静态演示数据充数。 + +--- + +## 3. 数据源对照 + +| 页面 | 现有数据源 | 还缺什么 | |---|---|---| -| 菜单树叶总数 | **33** | `ui_menu_tree.txt` 实际叶子(不含 9 个父节点) | -| 已绑页面 | **12** | 装置信息 / 交流量 / 直流量 / 遥信量 / 查看 4 页 / 网口设置 / SOE / 自检记录 / 运行记录(明细见 `docs/UI设计/页面补齐规划.md`) | -| 未绑叶子 | **21** | 见 §2 三梯队 | -| 组件层 | **3 就绪** | `ui_w_editor` / `ui_w_setlist` / `ui_page_action` 均已落地,**当前无任何页面消费**——写了不用等于没写 | -| 数据供给 | 3 通道 | `ui_data` 的 rt / par / info 三通道 + `ui_data_feed` 已接线 | -| 黄金用例 | 18 页 | `ui-golden` 像素基线 + 字符集差集检查均已挂 pre-commit | - -> ⚠ **未绑叶子 ≠ 空菜单项**:未绑的叶子按菜单树规则是"无内容页",进得去但只显示空框架。 -> 21 叶里 8 叶(整定 4 + 维护 3 + 通讯设置)**数据源已就绪**,只是没写页面; -> 6 叶(遥信三页 + 记录 5 页中的 5 叶)**缺数据层或写入方**;1 叶(手动录波)缺模块。 +| 整定 4 页 | `prot_set` 31 项、`ui_w_editor` | 第 1 项写路径 | +| 时钟设置 | `sys_clock_set_unix()` | 页面与回读校验 | +| 强制复归 | `prot_elm_request_reset()`、`ui_page_action` | 页面与 SOE 留痕 | +| 清除记录 | `sys_log` | 删除 API、确认交互 | +| 遥测死区 / 系数 | `run_par`、`adc_cal` | 可编辑页面 | +| 通讯设置 | 网口配置保存范式可复用 | `[comm]` 段及 104 消费 | +| 记录 5 页 | SOE、自检、运行记录已有 reader 先例 | 五类写入方与记录类型 | +| 运行报告 | `uptime_s`、复位原因已有 | 上电次数与 104 统计 | --- -## 2. 三梯队(按数据源就绪度分,不按菜单顺序) +## 4. 每页的统一完成动作 -### 2.1 第一梯队:数据源已就绪(9 叶,可立即开工) +1. 新增 `ui_page_xxx.c/.h`,优先复用现有组件。 +2. 在 `ui_menu_tree.txt` 对应叶子增加 ` #page=xxx`。 +3. 运行 `python tools/menu_gen/gen_menu.py`,检查生成物差异。 +4. 新文案先过字符集提取和字库生成,不能显示方块。 +5. 加像素黄金用例与必要的主机单测。 +6. 涉及保存、重启、主站读回的项目,台架可用后统一补做实机判据并留证。 -| 叶 | 数据源 | 为什么现在能做 | +**单写者约束**:`ui_menu_tree.txt`、页面注册表、字库生成物不得由多个分支同时修改。 +`MDK-ARM/*.uvprojx` 的新源文件注册按双端注册规则处理。 + +**保护闭锁必须可见**:整定页可以修改定值,但当前产品在 UA/UN/IA 未标定时保护判据闭锁。 +页面应显示该状态,不能让用户误以为“修改定值后保护就会动作”。口径见 +`docs/任务规划/数据可信.md` §4.3。 + +--- + +## 5. 当前不做 + +| 项目 | 不做原因 | 重开条件 | |---|---|---| -| 参数 / 定值 / 控制字 / 软压板(整定 4 页) | `prot_set` 31 项 + `ui_w_editor` | 编辑器已落地;只缺**写路径**(UI 只投递 → `prot_set_set_sealed` → 置脏 → defaultTask 落盘) | -| 时钟设置 | `sys_clock_set_unix()` | API 就绪,6 字段表单 + 回读校验 | -| 强制复归 | `prot_elm_request_reset()` | 单字标志,任意上下文可投;`ui_page_action` 直接套 | -| 清除记录 | `sys_log` | ⚠ **缺删除 API**(按 ID 删文件 + 重建 + 计数归零),需先补 ~40 行 | -| 遥测死区 / 遥测系数(2 页) | `run_par`(9 键死区,卡 10)+ `adc_cal`(k/b,卡 2) | 两个数据层都已落地,页面只做可编辑列表 | - -### 2.2 第二梯队:需先补数据层(4 叶,但只立 1 张卡) - -> 遥信类型 / 防抖 / 双点这 3 叶**不立卡**——V0.1 没有开入硬件,三类配置没有可配的对象, -> 做出来只是"改一组没人读的参数"。随开入固件(独立立项)一起做,见 §5。 - -| 叶 | 缺什么 | -|---|---| -| 通讯设置 | `[comm]` 段(104 公共地址 / 端口 / 主站地址)——当前编译期固定。照 `net_cfg` 的"RAM 副本 + 保存请求交接"范式 | -| 遥信类型 / 遥信防抖 / 双点遥信(3 页) | 开入量本身不存在(V0.1 无开入硬件),三类遥信配置**没有可配的对象**。⚠ 这三页做出来只能是"改一组没人读的参数" | - -### 2.3 第三梯队:需先立模块或补写入方(6 叶) - -| 叶 | 缺什么 | -|---|---| -| 事故 / 操作 / 保护告警 / 保护启动 / 遥控记录(5 页) | 每类记录 = 一个写入点 + 一个 `syslog_id_t`。当前只有 SOE 与 selftest/run 三类有写入方 | -| 运行报告 | 计数器:上电次数 / 104 统计;`uptime_s` 与复位原因**已有**(`ui_data_feed` 的 info 通道 + `app_main` 锁存) | - ---- - -## 3. 任务卡(五字段;完成即销项删行) - -> 资源图例:🔴 台架(COM6/ST-LINK,`bench.lock` 独占)· 🟡 外部 · 🟢 纯软件。 -> 状态:⬜ 未开始 · 🟦 进行中。**条目用语义标识,不新增字母+数字编号** -> (规则见 `docs/任务规划/README.md` §0)。序栏只为阅读顺序。 - -| 序 | 任务 | DoD(可验证验收判据) | 硬依赖 | 资源 | 写范围 | 状态 | -|---|---|---|---|---|---|---| -| 1 | **`param_request` 写路径** | `ui_data_param_request/state` 落地:小环形队列(深度 4,满返 -1)+ 状态四值 `OK` / `ERR_RANGE` / `ERR_LOCKED` / `ERR_BUSY`;`ui_data_feed_tick()` 侧取出后调 `prot_set_set_sealed()` + 置脏;**主机单测覆盖**:满队列、越界拒绝(`bad_mask` 指出越界项)、闭锁态拒绝、正常往返 | `ui_data` 三通道(已落) | 🟢 | `Drivers/BSP/UI/ui_data.*` + `App/app/ui_data_feed.*` | ⬜ | -| 2 | **整定 4 页** | 4 叶绑页面;就地改一条定值 → 保存 → **掉电重启后值保持且校验和自检通过**;主站 `C_RD` 读到同一值;越界时高亮越界行、页脚"越界"、值不落盘;黄金用例入库 | 1、`ui_w_editor`(已落) | 🟢 | `Drivers/BSP/UI/` + `ui_menu_tree.txt` + 生成物 | ⬜ | -| 3 | **装置维护 3 页** | ① 时钟设置:写 `sys_clock_set_unix()` 后回读校验(RTC 慢墙钟 8 h 的现象要在证据里说明);② 强制复归:确认后 YX 动作位复归、SOE 有复归记录;③ 清除记录:选类型 → 确认 → 计数归零且文件重建(**先补 `sys_log` 删除 API**,单测覆盖删后重建/计数归零/不存在时幂等) | `ui_page_action`(已落);③ 需 `sys_log` 删除 API | 🟢 | `Drivers/BSP/UI/` + `App/log/sys_log.*` | ⬜ | -| 4 | **遥测死区 / 系数 2 页** | 改死区 → 104 上送门限随之变化(PC 主站读回核对);改 `k` → 页面显示值按比例变化;两页都走 `[run_par]` / `[adc_cal]` 的既有落盘链,**不新增第三份真源** | `run_par` / `adc_cal`(均已落) | 🟢 | `Drivers/BSP/UI/` + `ui_menu_tree.txt` | ⬜ | -| 5 | **通讯设置(`[comm]` 段)** | 新建 `[comm]` 段(公共地址 / 端口 / 主站地址);改后重启,主站仍能连上且公共地址生效;`CFG_FILE_MAX_KEYS` / 段数上限核对(当前 5/8 段) | — | 🟢 | `App/cfg/` + `App/protocol/iec104/` + `Drivers/BSP/UI/` | ⬜ | -| 6 | **记录 5 页** | 每类记录一个写入点 + 一个 `syslog_id_t`;页面能翻到最近 20 条,空记录显示"无记录"而非空白;5 页共用一份 reader | — | 🟢 | `App/log/` + `Drivers/BSP/UI/` | ⬜ | -| 7 | **运行报告** | 4 组:上电次数 / 运行时长 / 复位原因 / 104 统计;前两组与 info 通道一致,后两组新建计数器(掉电不丢则需落盘,否则页面标注"本次运行") | — | 🟢 | `App/log/` 或 `App/app/` + `Drivers/BSP/UI/` | ⬜ | - -> **遥信三页(类型/防抖/双点)不立卡**:V0.1 无开入硬件,三类配置没有可配对象—— -> 做出来只是"改一组没人读的参数"。随开入固件(独立立项)一起做,见 §5。 - ---- - -## 4. 顺序与并行 - -``` -1 param_request 写路径 ─→ 2 整定 4 页 -3 装置维护 3 页(独立;③ 先补 sys_log 删除 API) -4 遥测死区/系数 2 页(独立) - ↓ 第一梯队 9 叶 -5 通讯设置([comm] 段)→ 6 记录 5 页 → 7 运行报告 -``` - -- **单道串行**(2026-10-25 用户决定:本波只做 UI):`ui_menu_tree.txt` 与 - `ui_page_registry.c` 是**单写者生成物**,两条道并行会在菜单树与注册表上互相覆盖。 - 一条道 = 零冲突;需要并行度时优先拆"数据层"与"页面层"(如 `[comm]` 段可先行)。 -- **每页标准动作**(已被生成器收敛成 3 处人工 + 1 条命令):写 `ui_page_xxx.c/.h` → - 菜单树加 ` #page=xxx` → 跑 `python tools/menu_gen/gen_menu.py` → 登记黄金用例。 -- **字符集**:新文案的汉字必须先过 `tools/font_gen/extract_cn.py` → `make_font.py`, - 否则显示方块(`font-charset` 门禁会拦)。40 条定值名建议沿用 2~4 字缩写 - ("过流Ⅰ段定值"→"Ⅰ段定值")。 -- **台架**:本波 21 叶里只有整定 4 页与遥测 2 页需要上板验证(保存/重启/C_RD 回读)。 - 台架不在手边时先做页面 + 黄金用例,**把上板项累积成一批,下次上机一次过**。 - ---- - -## 5. 范围边界(本波不做) - -| 不做 | 理由 / 归属 | -|---|---| -| 遥信类型 / 防抖 / 双点(3 叶) | V0.1 无开入硬件;随开入固件独立立项(`docs/任务规划/并行分道方案.md` §4.2 的 L4-1~L4-3 同批) | -| 手动录波(1 叶) | 录波模块未立项;且录波通道必须先有工程量(`docs/任务规划/数据可信.md` §9) | -| SNTP 设置(1 叶) | 当前对时走 8888 端口私有协议,SNTP 是另一条链;产品口径亦未定。只做页面 = 又一个"看着能用其实不生效"的页 | -| 结构债 / net 收口 | 用户决定(2026-10-25):本波只做 UI,结构债押后(`docs/任务规划/README.md` §2) | - ---- - -## 6. 与其它波次的关系 - -| 波次 | 关系 | -|---|---| -| **数据可信** | 本波消费其产出:`ui_data` 的 `eng` 字段由 `adc_cal_flags()` 决定(现 `eng=0` = 引脚电压,卡 0b 拿到变比后才升 1);遥测死区页直接改 `run_par`。**保护当前处于未标定闭锁态**(`protect_task.c:98`),整定页可改定值但判据不会动作——页面须显示闭锁原因,不能让用户以为"改了没用" | -| **采集保护收尾** | B10 送检不依赖 UI 页面;无交集 | -| **结构债** | 押后;`sys_log` 删除 API 与日志管线同域,本波只补这一个 API,不动管线 | +| 遥信类型 / 防抖 / 双点 3 页 | V0.1 无开入硬件,配置没有消费者 | 开入固件立项后,与开入开出一起做 | +| 手动录波 | 录波模块不存在,且工程量口径尚未完成实测闭环 | 故障录波独立立项 | +| SNTP 设置 | 当前使用 8888 私有对时协议,SNTP 链路与产品口径均未建立 | SNTP 客户端立项 | +| net 层与其他结构债 | 当前只做 UI,避免两条主线互相拖慢 | UI 当前清单完成后,按 README §2 切换 | diff --git a/docs/任务规划/基线与待确认.md b/docs/任务规划/基线与待确认.md deleted file mode 100644 index e0f58bad..00000000 --- a/docs/任务规划/基线与待确认.md +++ /dev/null @@ -1,15 +0,0 @@ -# 待确认(开放问题清单) - -> 本文件是 **OQ 族(待确认 / 开放问题)的属主登记处**,见 `docs/编号规范.md` §4.2。 -> **只放还没有答案的问题**——有答案的立刻删行,答案落进对应模块文档。 -> ⚠ 文件名沿用旧称「基线与待确认」,但**基线类内容已全部移出**:设计基线在模块文档里 -> (net pending 化 → `docs/网络/Net层阻塞模型架构设计.md` §10;错误码四要素 → -> `docs/产品需求/产品需求设计文档.md` §7.1),内存数字的权威在 `docs/内存管理/RAM预算台账.md`。 - -## 1. 待确认 - -1. **功能裁剪门控是否要在真实初始化点接入**——原 `APP_ADC_ENABLE` / `APP_RS485_ENABLE` 占位宏 - 已于 2026-10-22 删除(零消费点),当前没有任何开关。要确认的是**是否需要**在真实初始化点 - 接入门控;B1 取数不依赖它。RS-485 侧随 B2 暂缓(`暂缓-RS485.md`)。 - -> RS-485 PD0 DE 的半双工方向语义确认不在此列——已登记在 `暂缓-RS485.md`「待确认-3」,随 B2 重启时一并做。 diff --git a/docs/任务规划/并行分道方案.md b/docs/任务规划/并行分道方案.md deleted file mode 100644 index ee97efb8..00000000 --- a/docs/任务规划/并行分道方案.md +++ /dev/null @@ -1,238 +0,0 @@ -# 并行分道方案(待办归道 · 写域 · 合并次序) - -> **本文只做三件事**:把未闭环待办**归到道**、划**文件级写域**、定**合并次序**。 -> **明细不复制**——DoD / 硬依赖 / 验收判据仍在 `docs/任务规划/登记簿.md`、 -> `docs/任务规划/UI补齐.md`、`docs/任务规划/数据可信.md`、`docs/任务规划/采集保护收尾.md`、 -> `docs/算法/继电保护功能复现规划.md`。机制(worktree / 两把锁 / `done` 流程)见 -> `docs/环境与部署/并行任务工作树方案.md`。 -> -> **为什么需要它**:待办分散在 5 个文件、编号体系有 6 套,但"能不能并行"只取决于一件事—— -> **写域是否相交**。这个判断原先只散在每条任务卡的"写范围"列里,没有人横向拉通看过一遍。 -> -> 修订:2026-10-23 建档(用户要求:把保护、ADC、UI、结构债的全部待办做并行分工)。 - ---- - -## 1. 一句话结论 - -> **当前只有一条编码道**:道E UI 补齐(21 叶,单道串行)。 -> 道F 结构债排批 4;道G 保护元件与道H 采集增强排批 5(等标准源 / 标定)。 -> 台架道空转(台架不在手边);外部窗口 = 卡 0 / 0b / 12、N-16、N-3、B10。 - -``` -批 3 道E UI 补齐(21 叶,单道串行) ← 当前 - ↓ -批 4 结构债:net 收口(含饿死签名)∥ 白名单 ∥ 性能簇 - → 「USART1 改为只发不收」独占一轮(CubeMX 重生成,清空全部工作树) - ↓ -批 5 道G 保护元件 V0.1 子集 ∥ 道H 采集增强 ← 等标准源 / V0.2 硬件 - 台架/外部窗口:卡 0 / 0b / 12、N-16、N-3、B10 -``` - -**道名对照**(全文只用这 4 个标签): - -| 道 | 名称 | 批 | 写域 | -|---|---|---|---| -| **道E** | UI 页面(21 叶补齐) | **3(当前)** | 域5 | -| **道F** | 结构债与网络(net 收口 / 白名单 / 性能簇 / USART1) | 4 | 域6+域7 | -| **道G** | 保护元件扩展(V0.1 子集) | 5(等标准源) | 域3+域2 | -| **道H** | 采集增强(等周波/零序/谐波轨) | 5(等标定) | 域1+域3 | - -**关键判断**:用户最关心的"保护待做"**大部分不能与标定并行**——因为保护元件的判据全是 -"与定值比",而定值是工程单位(10 A / 120 V),标定没落地时新元件写出来就是"码值比二次值", -和现在 `prot_elm` 的错配是同一个病。真正能提前的只有**比值类判据**(涌流制动、CT 断线、 -突变量启动),但它们要往 `prot_set` 里加定值项 → 改 `PS_ID_COUNT` 与 blob VER → 与「数据可信」 -的落盘/闭锁卡互斥。**所以保护整簇排在「数据可信」之后**(批 5,等标准源),逐项归道见 §4.2。 - ---- - -## 2. 硬约束(决定"最多能切多细") - -| # | 约束 | 出处 | 对分道的含义 | -|---|---|---|---| -| 1 | **同时最多 3 棵工作树** | `docs/环境与部署/并行任务工作树方案.md` §4 | 并发上限 3,不是想切几条切几条 | -| 2 | **`@build` 必须串行**(UV4 不能两个实例同时 `-b`) | `并行任务工作树方案.md` §7.2 | 三条道能**同时编码**,构建排队;增量 4~5 s,不是瓶颈 | -| 3 | **台架必须串行**(COM6 / ST-LINK / 192.168.1.100 各一份) | `并行任务工作树方案.md` §7.1 | 真正的瓶颈。台架道单列,跑在主工作树、不占工作树名额 | -| 4 | **单写者文件**:`docs/任务规划/**`、`AGENTS.md` 任务表块、`MDK-ARM/*.uvprojx`、`.pre-commit-config.yaml`、内存快照 | `并行任务工作树方案.md` §6.1 | 任务分支不碰登记簿;uvprojx 手工并;内存数字合并后重生成 | -| 5 | **分支存活 ≤2 天、绿即合** | `并行任务工作树方案.md` §7.3 规则 3 | 一条道不是一条长分支,是**连续若干个短分支** | -| 6 | **`done` 只能在主工作树、必须串行** | `.dsh/skills/task-worktree/SKILL.md` 铁律 1 | 合并不是并行的,只能排队 | -| 7 | **CubeMX 重生成会打回手工定制**(需跑 `check_cubemx_survival.py`) | `docs/任务规划/排期详述.md` §1「USART1 改为只发不收」 | 该任务**独占**,不能与任何分支并存 | -| 8 | **`check_layer_deps.py` 基线只减不增** | `scripts/check_layer_deps.py` | 谁减基线谁写这个文件——只能有一条道写 | - -> 约束 1 与 3 合起来给出真正的并行度:**3 条编码道 + 1 条台架道**,台架道跑在主工作树里, -> 不额外占工作树名额(它验证的是已合入的镜像)。 - ---- - -## 3. 写域划分(域1~域8) - -> 划道的唯一依据。**同一域同时只能有一条道在写**;跨域任务必须拆成两张卡或声明合并次序。 - -| 域 | 文件(glob) | 典型归属 | -|---|---|---| -| **域1 采集与算法** | `App/algo/*`(`adc_cal.*` / `meas.*` 新增)、`App/task/adc_task.c`、`test/host/adc_*/` | 标定、遥测快照、采集增强 | -| **域2 定值与配置** | `App/algo/prot_set.*`、`App/algo/run_par.*`(新增)、`App/cfg/*`、`test/host/cfg_file/` | 定值表、配置引擎、运行参数段 | -| **域3 保护元件与出口** | `App/algo/prot_elm.*`、`App/algo/freq.*`、`App/task/protect_task.c`、`App/algo/prot_do.*`(新增) | 保护判据、出口、闭锁 | -| **域4 104 上行** | `App/protocol/iec104/*`、`App/task/iec104_serv.c`、`test/host/iec104/` | 点表、死区、频率点、运行参数映射 | -| **域5 UI** | `Drivers/BSP/UI/*`、`ui_menu_tree.txt`、`tools/menu_gen/*`、`tools/font_gen/*`、`ui_cn_chars.txt`、`App/app/ui_data_feed.*`(新增) | 页面、组件、数据供给、字库 | -| **域6 存储与日志** | `App/log/*`、`App/fs/*`、`Lib/dhara/*` | 写回 flush、日志管线、卷锁 | -| **域7 网络** | `Drivers/BSP/NET/*`、`Drivers/BSP/NETCFG/*`、`App/task/net_task.c`、`App/sync/*` | net 收口、探针缺口、PHY | -| **域8 工具链与门禁** | `test/host/**`(各道新增子目录除外)、`scripts/*`、`.gitea/*`、`.pre-commit-config.yaml` | 单测、CI、断言宏、门禁 | - -**跨域但必须独占的两个文件**: - -| 文件 | 为什么独占 | 处理 | -|---|---|---| -| `MDK-ARM/STM32F407-Demo.uvprojx` | 每条新增 `.c` 都要注册,UV4 构建时还会自动改写 | 任务分支照常加 ``,`done` 时**手工并条目** → 跑 `registration-check` → `@build` | -| `docs/内存管理/RAM使用分析与优化方案.md` | `mem-numbers` 快照 | **绝不手工合数字**;每次 `done` 后 `scripts/check_mem_numbers.py --write` 重生成 | - ---- - -## 4. 全量待办归道 - -> 覆盖 `docs/任务规划/` 全部未闭环项 + 保护复现规划 L 项 + UI 两篇规划。 -> "批"列 = 第几轮开工;`—` = 现在不排(理由见 §8)。 - -### 4.1 数据可信(剩 3 卡) - -| 条目 | 写域 | 道 | 批 | -|---|---|---|---| -| 卡 0 已知电压注入验证 | 台架 + 文档 | **台架道** | 顺延(等台架) | -| 卡 0b 端子/子板变比确认 | 台架 + 文档(🟡 外部) | **台架道** | 顺延(等子板) | -| 卡 12 8 通道两点标定 | 台架(🟡 标准源) | **台架道** | 顺延(等标准源) | - -> 三张全是台架或外部资源,**不占编码道**;台架不在手边 → 整体顺延 -> (`docs/任务规划/数据可信.md` §5)。顺序:0 → 0b → 12。 - -### 4.2 保护待做(L 项) - -| 条目 | 判据是否依赖工程量 | 写域 | 道 | 批 | -|---|---|---|---|---| -| L3-7 涌流制动(二次谐波占比) | **否**(比值) | 域3 + 域2(加定值) | **道G 保护** | 5(等标准源) | -| L3-9 CT 断线(电流不平衡) | **否**(比值) | 域3 + 域2 | **道G 保护** | 5(等标准源) | -| L3-11 故障启动(突变量) | **否**(相对量) | 域3 + 域2 + 新 `prot_start.*` | **道G 保护** | 5(等标准源) | -| L3-8 PT 断线 | **是**(电压阈值) | 域3 + 域2 | **道G 保护** | 5(等标准源) | -| L3-12 无压/无流闭锁 | **是** | 域3 + 域2 | **道G 保护** | 5(等标准源) | -| L3-10 母线接地告警 | **是** | 域3 + 域2 | **道G 保护** | 5(等标准源) | -| L5-1 残余(元件级校验和常量表) | 否 | 域2 + 域3 | **道G 保护** | 5(等标准源) | -| L5-3 硬件自检 | 否(但需出口硬件) | 域3 | **道G 保护** | 5(等标准源) | -| L4-1 跳闸出口 / L4-2 告警出口 / L4-3 出口自检 | 否 | 新模块 `prot_do.*` | **独立立项** | — | -| L1-7 等周波调频 | 是(依赖标定后幅值) | 域1 + 域3 | **道H 采集增强** | 5(等标定) | -| L1-8 零序通道接入 | 是 | 域1 | **道H 采集增强** | 5(等标定) | -| L2-8 遥测谐波轨 | 是 | 域1(`phasor`) | **道H 采集增强** | 5(等标定) | -| L3-2 零序过流 / L2-5 线·负序·零序合成 | — | 域1 + 域3 | **V0.2 硬件** | — | -| L4-6 故障录波 | — | 新子系统 | **独立立项** | — | - -> **为什么 L4-1~L4-3 是独立立项而不是一条道**:固件里**完全没有开入/开出代码** -> (`docs/问题记录/采集与数据处理短板评测.md` 登记),出口要驱动 U18 桥 + 继电器, -> 是一整个模块(GPIO 安全默认电平 + 出口保持/脉冲 + 回读自检),量级与"加一个元件"不同。 - -> **为什么道G 整簇押后到批 5(2026-10-25 复核)**:表里 10 项只有 4 项在 V0.1 上可行 -> (L3-7 A 相涌流制动 / L3-11 突变量启动 / L3-12 无压无流闭锁 / L5-1 元件级校验和)—— -> L3-8 PT 断线、L3-9 CT 断线要三相同时刻,被 N-18 硬件缺陷挡死;L3-10 母线接地只有电压侧可判; -> L5-3 硬件自检要出口硬件。而这 4 项**没有标准源就无法验证**,加上保护现处于未标定闭锁态, -> 写出来只能"建接口不使能"。**等标准源到位或 V0.2 硬件改版后再开**。 - -### 4.3 UI(批 3 唯一在跑的道) - -**剩余 21 叶按数据源就绪度分三梯队**(明细与 DoD 见 `docs/任务规划/UI补齐.md`): - -| 梯队 | 条目 | 叶数 | 写域 | 前置 | -|---|---|---|---|---| -| **一** | `param_request` 写路径 → 整定 4 页 · 装置维护 3 页(时钟/复归/清除记录)· 遥测死区/系数 2 页 | 9 | 域5 + `App/app/` + `App/log/`(仅补删除 API) | 组件层已就绪;清除记录需 `sys_log` 删除 API | -| **二** | 通讯设置(`[comm]` 段)· 遥信类型/防抖/双点 3 页 | 4 | 域2 + 域4 + 域5 | `[comm]` 段不存在;遥信三页**无开入硬件,不立卡** | -| **三** | 记录 5 页(事故/操作/告警/启动/遥控)· 运行报告 | 6 | 域6 + 域5 | 记录写入方 / 计数器不存在 | -| **不排** | 手动录波(缺录波模块)· SNTP(对时是另一条链) | 2 | — | — | - -> **为什么批 3 只有一条道**:`ui_menu_tree.txt` 与 `ui_page_registry.c` 是**单写者生成物**, -> 两条道并行会在菜单树与注册表上互相覆盖;`uvprojx` 也要逐条手工并。单道串行 = 零冲突, -> 而 UI 是当前唯一在跑的波次,并行度换来的收益抵不过合并成本。 - -### 4.4 结构债、验证与其它 - -| 条目 | 写域 | 道 | 批 | -|---|---|---|---| -| **net 层跨上下文收口**(E-27 / E-28 裸指针收口 + reconcile 饿死签名) | 域7 | **道F 结构债** | 4 | -| N-25 性能与语义簇(PASV / PHY / SOCKETS) | 域7 | **道F 结构债** | 4 | -| S-4 dhara 重试自旋 · S-5 全序列卷锁评估 | 域6 | **道F 结构债** | 4 | -| N-23 注册校验白名单清理(只剩 `rs485_task.c`,随 B2) | 域8 | **道F 结构债** | 4(或随 B2) | -| **USART1 改为只发不收**(S-3) | `.ioc` + `Src/*`(重生成) | **独占** | 4(清空全部工作树后单独一轮) | -| **L5 队列往返基线重锁**(N-3) | 无码(需 PC peer) | **台架道** | 窗口 | -| **A2/A4 负向台架**(N-16) | 无码 | **台架道** | 窗口 | -| **串口助手 A6/A7 补验** | 无码(需 CH340) | **台架道** | 待硬件 | -| B10 送检(标准源 + 第三方主站) | 无码 | **外部窗口** | Fixed Date | -| T-7 CI runner | 外部基础设施 | **外部** | 待 runner | -| 道G 保护元件 V0.1 子集 · 道H 采集增强 | 域1+域3 | **道G / 道H** | 5(等标准源 / 标定) | -| N-30 relisten 收口 / N-28 空 TU / N-29 `.git` / N-18 V0.2 / N-27 内存簇 / B2 RS-485 / 开入开出 / 录波 | — | **不排** | — | - ---- - -## 5. 每次 `done` 的固定动作 - -1. `done --dry-run` 预演(`git merge-tree`,rc≠0 就先在任务分支解冲突) -2. `done `:合入 → 并集残留审计(三方对比,不用 `tail` 目测)→ 重生成产物 → 门禁 -3. 主工作树补:销项删行(`docs/任务规划/` 各文件)→ `AGENTS.md` 任务表重生成 → 内存快照重生成 -4. `@build` 0/0 + 台架 `@test`(若该道改了行为) - ---- - -## 6. 三件"看起来能并行、实际不能"的事 - -| 事项 | 为什么不能并行 | 正确做法 | -|---|---|---| -| **USART1 改为只发不收**(S-3) | 改 `.ioc` → CubeMX 重生成会**打回 5 处手工定制**(IWDG 延迟启动、Fault Handler、FreeRTOSConfig 加固区等),且重写 `Src/*` 大片生成文件 → 与任何分支必冲突 | **独占一轮**:清空所有工作树 → 在主工作树做 → `check_cubemx_survival.py` 全过 → 台架闭环 | -| **字库重生成** | `make_font.py` 重写 `ui_font_cn_12.c`(整文件生成物),两人同时扩字会互相覆盖 | 归道E 一个分支独占;后续加字必须重新走一次 | -| **往 `prot_set` 加定值项** | 改 `PS_ID_COUNT` → blob VER 2→3 → 影响落盘格式、104 定值段、UI 整定页分组 | 只在**没有任何定值卡在飞**的时候做(批 5 的道G);⚠ 整定 4 页(批 3)会改 `prot_set` 的读写路径,**道G 必须在它之后** | - ---- - -## 7. 执行清单(批 3 起步) - -```powershell -$PY = ".venv\Scripts\python.exe" -$WT = ".dsh\skills\task-worktree\scripts\task_worktree.py" - -# 先确认没有残骸占名额(当前有一棵:D:/Code/DTU Code/_wt/v5tool2-v11,分支已合入) -& $PY $WT list - -# 批 3 道E:单道串行,一次只开一棵工作树(菜单树/注册表是单写者生成物) -& $PY $WT new UI-param write # param_request 写路径(无页面,先落通道) -& $PY $WT new UI-set edit # 整定 4 页 -& $PY $WT new UI-maint pages # 装置维护 3 页(含 sys_log 删除 API) -& $PY $WT new UI-cal par # 遥测死区 / 系数 2 页 -``` - -> ⚠ **`new` 必须给两个参数**(` `),少一个直接 rc=3;**ID 不能重复**—— -> 台账是 `.state/.json`,同 ID 第二次 `new` 会报"任务已存在"。分支名是 -> `task/-`(上面四段即 `task/UI-param-write` / `task/UI-set-edit` / …)。 -> -> ⚠ **活跃任务数按"台账 + 目录是否存在"统计**,不是按 git 注册表。遗留目录会让名额被白占—— -> 开工前先 `list` 确认没有 `` 显示 `? ?`(那是分支已删、目录还在的残骸)。 -> 处置:`& $PY $WT abort `;若该目录的 `.git` 已失效,`abort` 会停在 -> `失败:移除工作树失败:fatal: '' is not a working tree`(git 已不认识它), -> 此时脚本**不会**替你 `rmtree`,退化为"删目录 + 删 `.state/.json`"两件事, -> 删完补一条 `git worktree prune`。 -> **删前先确认目录里没有联接**(`.venv` / `simulator/SDL2`)——直接删会把主工作树的 `.venv` 带走。 - -> 分支 ID 用语义 slug(`UI-set-edit` / `cal-scale-doc`),与 `docs/任务规划/README.md` §0 的 -> "新条目用语义标识"一致;存量编号(N-x / S-x)仍可直接当 ID 用。 - ---- - -## 8. 现在不排的(避免误以为漏了) - -| 项 | 状态 | 重开条件 | -|---|---|---| -| B2 RS-485 整簇(含 E-49/E-50、A-4 RS485 侧、N-25 DE 项、待确认-3) | ⏸ 用户决定 2026-10-12 暂缓 | 见 `docs/任务规划/暂缓-RS485.md` | -| N-27 内存优化簇剩余(原报告 R-2 后半 / R-3 / R-4 / R-6) | ⏸ 用户裁定 2026-10-15 | `docs/内存管理/RAM使用分析与优化方案.md` §4 | -| N-29 `.git` 233 MB | 决策:登记观察 | 推送/协作前重评 | -| N-18 V0.2 硬件改版 | 下一版硬件 | 硬件改版立项 | -| **道G 保护元件 V0.1 子集**(A 相涌流制动 / 突变量启动 / 无压无流闭锁 / 元件级校验和) | ⏸ 等标准源(2026-10-25 复核) | 标准源到位 **或** V0.2 硬件改版;且保护须先解卡(当前未标定闭锁,`protect_task.c:98`) | -| **道H 采集增强**(L1-7 等周波 / L1-8 零序 / L2-8 谐波轨) | ⏸ 等标定 | 卡 12 两点标定完成(幅值类判据依赖工程量) | -| **遥信类型 / 防抖 / 双点 3 页** | ⏸ 无开入硬件 | 开入固件立项(与 L4-1~L4-3 出口同批) | -| **SNTP 设置页** | ⏸ 对时是另一条链 | SNTP 客户端立项(当前走 8888 私有协议) | -| N-30 relisten 收口 | 观察(原语版实测回退) | 三条语义前置满足后 | -| 原报告 F-5 空 TU(N-28 簇) | 观察 | 有全量构建耗时痛点时 | -| T-7 CI runner | 外部基础设施 | runner 就绪 | -| 串口助手 A6/A7 补验 | 待硬件 | CH340 到位 | -| 开入/开出固件、故障录波 + 104 文件传输 | 独立立项 | 产品定义 / 单独立项 | diff --git a/docs/任务规划/排期详述.md b/docs/任务规划/排期详述.md deleted file mode 100644 index a2e10e95..00000000 --- a/docs/任务规划/排期详述.md +++ /dev/null @@ -1,35 +0,0 @@ -# 结构债与验证常态 - -> 波次总览与顺序权威在 `docs/任务规划/README.md` §2;当前波次任务卡在 `UI补齐.md`, -> 「数据可信」已转收口态(`数据可信.md`),「采集保护收尾」只剩 M3 送检(`采集保护收尾.md`)。 -> 本文件只放**已排期但非当前波次**的详述表——低频改动,随窗口更新。 -> -> **结构债整体押后**(2026-10-25 用户决定:下一波只做 UI 补齐)。押后不等于降级: -> 它**没有外部依赖、随时能捡起来**,这正是它让位给 UI 的原因——UI 那 21 叶一旦等台架就卡死, -> 结构债不会。 -> -> **条目一律用语义名**(2026-10-25 起):原先「C 组短码」(结构债 1/3/5/6)与「D 组短码」 -> (验证窗口 2/5/6/7)已废止。原因是它们**同字母不同义**——`C` 组在 -> `docs/UI设计/UI_界面制作流程.md` 里是 UI 设计约束(屏幕/字符集/文本/编码),`D` 组在 -> `docs/子系统设计/控制台单写者日志设计说明.md` 与 `docs/子系统设计/配置文件设计说明.md` -> 里是决策记录。同一个短码在三处指三件事,读的人只能猜。 - -## 1. 结构债(按性价比排序) - -> 顺序 = 先灭实测缺陷,后可靠性根治,再大体量重构;评估簇随窗口。 - -| 条目 | 内容 | 出处 | -|---|---|---| -| **net 层跨上下文收口(含 reconcile 饿死签名)** | 两件事同批做:① 裸指针收口为状态查询 API(E-27 / E-28);② **对时 reconcile/relisten 突发把 uiTask 压成 800 ms+ 单串缺席**——2026-10-25 台架两轮分箱定案:同负载换 `:7003` churn 2200 周期 stall=0(阻塞型已被 F3 治愈),探针轮 3 事件 8 行全为该签名(漏检零、误报零)。处置两案并评:优先级矩阵复核 ∥ reconcile 突发分片让路 | E-27 / E-28;`../网络/网络自愈架构设计.md` §4.6;`../子系统设计/看门狗设计说明.md` §11.3 | -| **USART1 改为只发不收**(独占一轮) | 灭 ORE 僵尸机制根源(2026-10-02 台架 IWDG 误复位的机制根源):改 `.ioc` 令 USART1 不使能 RX / PA10 不复用为 `USART1_RX` → CubeMX 重生成 → 跑 `check_cubemx_survival.py`。结构性改动,**需清空全部工作树后独占一轮** + 复跑台架闭环 | S-3 | -| **注册校验白名单清理** | 剩余动作:清 `KNOWN_GAPS` 白名单(现只剩 `App/task/rs485_task.c`,见 `登记簿.md`)。**RS-485 DE 收尾已移入 `暂缓-RS485.md`** | N-23 残余 | -| **性能与语义簇 · 空 TU 观察** | 两项随窗口评估,逐项见 `登记簿.md` 对应行 | N-25 / N-28 | -| **dhara 重试自旋 · 全序列卷锁** | 两项低优先,先确认是否真有第二写者/是否再出"存储侧喂狗饥饿" | S-4 / S-5 | - -## 2. 验证常态(逢窗口顺带,不单独排期) - -| 条目 | 内容 | 出处 | -|---|---|---| -| **L5 队列往返基线重锁** | 下次 Phase 13 窗口顺带做(低风险:阈值余量 >8×;**需 PC peer**) | N-3 | -| **A2/A4 负向台架窗口** | PHY 拔线(需断 DTU 侧 / 关受管交换机端口)、A2 芯片级 `net_init` 失败(SPI 故障注入)、A4 短写压测 | N-16 | -| **串口助手 A6/A7 正路径补验** | 需 CH340 硬件接入后补验(`../测试说明/串口助手设计说明.md` 状态行 A1–A5/A8 已过) | — | diff --git a/docs/任务规划/数据可信.md b/docs/任务规划/数据可信.md index 7df195f6..8fdd08ab 100644 --- a/docs/任务规划/数据可信.md +++ b/docs/任务规划/数据可信.md @@ -1,10 +1,11 @@ -# 数据可信(通道标定 → 遥测链 → 运行参数) +# 数据可信(等待台架与外部资源) -> **一句话结论**:软件侧已收口,**只剩三张卡**——卡 0 已知电压注入(万用表)→ 卡 0b 端子变比 -> (子板)→ 卡 12 标准源两点标定。三张全是台架或外部资源,**没有软件道可做**。 -> **台架不在手边(2026-10-25 确认)→ 本波整体顺延**,不占编码道,当前波次在 `UI补齐.md`。 +> **只想知道要做什么:直接看 §5。** 当前只剩三项:卡 0 已知电压注入(万用表)→ +> 卡 0b 端子变比(子板)→ 卡 12 标准源两点标定。它们都等待台架或外部资源, +> **当前不占编码道;现在做 UI,见 `UI补齐.md`。** > -> **本文定位**:本波次剩余的验收判据 + **设计权威**。**排序权威在 `docs/任务规划/README.md` §2**。 +> **本文同时是设计约束的归属文件,不是纯任务卡。** 排序只看 +> `docs/任务规划/README.md` §2;后续事项统一索引在 `登记簿.md` §7。 > ⚠ §2.1 / §2.3 / §2.6 / §4.1~§4.5 / §7 / §8 / §9 / §10 与卡 0 / 0b / 12 都被**代码头注释**引用为 > 口径出处(`adc_cal.*` / `meas.*` / `cfg_file.*` / `prot_elm` / `protect_task.c` / `ui_data*`)—— > 它们是现行规范、不是"已完成记录",不要当历史删掉。上游分析见 @@ -124,8 +125,8 @@ int32 上限 2.15×10⁹,`k` 最大可表达 32767 µV/码,远大于实际 ### 4.2 三态可用性:标定 ≠ 可测 -`k` 默认值是**板级理论系数**,此时工程量单位是"**端子电压**",不是二次值——因为还缺 -互感器/子板变比。所以通道状态必须能区分三档,不能让消费者把端子电压当二次值: +`k` 默认值是**板级理论系数**,此时工程量单位是"**ADC 引脚电压**",不是二次值——因为还缺 +互感器/子板变比。所以通道状态必须能区分三档,不能让消费者把引脚电压当二次值: ```c #define ADC_CAL_F_VALID 0x01u /* 该通道 k 已按变比标定 → 工程量为二次值 */ @@ -143,7 +144,7 @@ UI 快照的 `qds`)。 ### 4.3 未标定即闭锁(本波唯一的行为变更) -保护判据拿"端子电压"跟"二次值定值"比,必然误动作。所以 M4 必须让 +保护判据拿"ADC 引脚电压"跟"二次值定值"比,必然误动作。所以 M4 必须让 `protect_task` 多一个闭锁源:**UA/UN/IA 的 `VALID` 位未全置位 → 判据停走**。 - 与 L5-4 采样故障闭锁同构(`PROT_HF_LOCK_MASK` 那一套),不新增机制。 @@ -184,7 +185,7 @@ typedef struct { uint32_t tick_ms; uint8_t valid; /* 整帧有效 */ uint8_t rsv[3]; - float yc[9]; /* 工程量(二次值或端子电压,由 flags 决定) */ + float yc[9]; /* 工程量(二次值或 ADC 引脚电压,由 flags 决定) */ uint16_t qds[9]; /* 每点品质:IV / NT / OV / CLIPPED */ uint8_t freq_valid; float freq_hz; @@ -280,11 +281,11 @@ typedef struct { --- -## 10. 与其它波次的关系 +## 10. 与其它工作的关系 -| 波次 | 关系 | +| 工作 | 关系 | |---|---| -| **采集保护收尾** | 该波的 B10 送检卡里"L1-5 标定复核"依赖本波卡 12;本波是 B10 的前置 | -| **UI 扩展** | 本波交付 `meas_snap_t` + `adc_cal_flags()` → 「UI 扩展」的 `ui_data_rt_t` 才能填 `eng`:现为 0 = 引脚电压,卡 0b 拿到变比后才升 1 = 二次值,交流量页"标定状态"才有真值。**「UI 扩展」已开工**(当前波次 `UI补齐.md`),不必等卡 0b | +| **送检验收** | 标准源送检的标定复核依赖本波卡 12;验收索引见 `登记簿.md` §7 | +| **UI 补齐** | 已交付的 `meas_snap_t` + `adc_cal_flags()` 供 `ui_data_rt_t` 填 `eng`:现为 0 = 引脚电压,卡 0b 拿到变比后才升 1 = 二次值,交流量页"标定状态"才有真值。当前 UI 任务见 `UI补齐.md`,不必等卡 0b | | **结构债** | 无交集 | -| **N-18(V0.2 硬件改版)** | 本波的 `CLIPPED` 位就是 N-18 在软件里的可见形态;V0.2 改版后该位恒 0,页面与 104 绑定零改动 | +| **双转换器无共同采样触发(V0.2 硬件改版)** | 本波的 `CLIPPED` 位就是「双转换器无共同采样触发」在软件里的可见形态;V0.2 改版后该位恒 0,页面与 104 绑定零改动 | diff --git a/docs/任务规划/暂缓-RS485.md b/docs/任务规划/暂缓-RS485.md index fb1ddd6c..bbfb4812 100644 --- a/docs/任务规划/暂缓-RS485.md +++ b/docs/任务规划/暂缓-RS485.md @@ -1,12 +1,12 @@ # 暂缓:RS-485 整簇(用户决定,2026-10-12) -> **当前没有处理 RS-485 的打算**,以下条目整体冻结、不占波次;**重启 B2 时先回本文件评估前置,勿直接开工**。 -> 本文件**不是销项**:条目仍在簿,只是不占波次(`docs/任务规划/README.md` §3 维护约定)。 +> **当前没有处理 RS-485 的打算**,以下条目整体冻结、不占执行队列;**重启 B2 时先回本文件评估前置,勿直接开工**。 +> 本文件**不是销项**:条目仍在簿,只是暂缓(`docs/任务规划/README.md` §3 维护约定)。 | 项 | 内容 | 重启时的前置 | |---|---|---| -| **B2** | **RS-485 采集链路实现**(消化 E-50、E-49):双缓冲 + 序号(防包撕裂)+ DE 收尾(超时路径等 TC)+ ModBus RTU 状态机(`App/protocol/` 正是为此预留) | **E-59 门禁尚未满足**:先修 E-50(单缓冲包撕裂)、E-49(TX 超时拉 DE 未等 TC),修完驱动契约才允许接消费者 | -| E-49 | RS485 DE 收尾(原属结构债一档剩余,已移出) | 同上 | -| A-4 RS485 侧 | 采集链路测试可验证性约束(双缓冲 + 序号、DE 超时路径覆盖,`测试与架构专项.md` §2.2) | 随 B2 设计文档落地 | +| **B2** | **RS-485 采集链路实现**:双缓冲 + 序号(防包撕裂)+ DE 收尾(超时路径等 TC)+ ModBus RTU 状态机(`App/protocol/` 正是为此预留) | **RS-485 接线门禁尚未满足**:先修单缓冲包撕裂、TX 超时拉 DE 未等 TC 两条,修完才允许接消费者 | +| RS485 DE 收尾 | RS485 DE 收尾(原属结构债一档剩余,已移出) | 同上 | +| 采集链路测试可验证性约束 · RS485 侧 | 双缓冲 + 序号、DE 超时路径覆盖,`测试与架构专项.md` §2.2) | 随 B2 设计文档落地 | | 待确认-3 | RS-485 PD0 DE 半双工方向语义硬件实测确认(本次仅静态审查) | 需硬件实测窗口 | -| N-25 中 DE 项 | `HAL_Delay(1)` 阻塞 DE | 随 B2 | +| 网络性能资源建议簇中 DE 项 | `HAL_Delay(1)` 阻塞 DE | 随 B2 | diff --git a/docs/任务规划/测试与架构专项.md b/docs/任务规划/测试与架构专项.md index b82426cb..08762bed 100644 --- a/docs/任务规划/测试与架构专项.md +++ b/docs/任务规划/测试与架构专项.md @@ -1,7 +1,7 @@ # 测试与架构改进专项(P1 收尾 / S1x / A-x) -> 编号空间见 `docs/任务规划/README.md` §0;排期位置见 `docs/任务规划/README.md` §2「验证常态」。 -> **S1~S14 / T-1~T-5 / A-1~A-7** 是 2026-10-02 版复审的**条目编号**——编号**不随条目删除而回收**; +> 编号空间见 `docs/任务规划/README.md` §0;排期位置见 `docs/任务规划/README.md` §2 顺序表。 +> **`S` 裸形 / `T-x` / `A-x` 三族**是 2026-10-02 版复审的**条目编号**——编号**不随条目删除而回收**(现存有效条目 = 「采集链路测试可验证性约束」「体积门禁 CI 强制路径」与「CI 远端闭环未验证」); > 其他文档若引用这些编号,其证据见对应模块文档与 `git log`。已完成事项的证据沉淀: > `../测试说明/系统时钟单元测试说明.md`(S1-a)、`../测试说明/配置文件单元测试说明.md`(S1-b)、 > `../测试说明/测试开发标准流程.md` §4.2.1(L0.5 适格台账)、`../测试说明/模拟器差距清单.md`(S13)、 @@ -9,12 +9,12 @@ > `../测试说明/主机单测覆盖率口径.md` §9「P1 落地」与「P2 落地」(S1-c:`sys_log_mcu` 106 例、 > `time_sync` 既有 4 例,两套均已被 `test-coverage` 总闸覆盖)。 > -> **本文件只剩 1 项未闭环**(A-4,且 RS485 侧随 B2 暂缓)。2026-10-25 复核时把已销项条目 +> **本文件只剩 1 项未闭环**(「采集链路测试可验证性约束」,且 RS485 侧随 B2 暂缓)。2026-10-25 复核时把已销项条目 > 压缩成证据指针——原来的详述与「完成即销项」规则冲突,留着会让读者以为还有活。 ## 1. P1 收尾(0 项) -**P1-10 测试固件静态 RAM 预算** 已于 2026-10-25 销项(`task/P1-10-test-fw`→73f9e10): +**测试固件静态 RAM 预算** 已于 2026-10-25 销项(`task/P1-10-test-fw`→73f9e10): CH395F 套件 5 档构建全部链接通过,最坏态 `RW_IRAM1` 91,088 B / 69.5%(距 92% 线余 29,498 B); `@test` f3 轮 VERDICT PASS(Phase 1/12/15 = 9/9、1/1、21/21)。**数字与规范正文的唯一出处**是 `test/board/ch395f_test.h` 的开关区注释,本文件不再重复。 @@ -27,7 +27,7 @@ CH395F 套件 5 档构建全部链接通过,最坏态 `RW_IRAM1` 91,088 B / 69 | 编号 | 事项 | 状态 | 详述 | |---|---|---|---| -| **A-4** | ADC / RS485 采集链路的**测试可验证性约束** | ⏸ ADC 侧随 B1 闭环;**RS485 侧随 B2 暂缓(`暂缓-RS485.md`)** | §2.2 | +| **采集链路测试可验证性约束** | ADC / RS485 采集链路的**测试可验证性约束** | ⏸ ADC 侧随 B1 闭环;**RS485 侧随 B2 暂缓(`暂缓-RS485.md`)** | §2.2 | ### 2.1 S12:模块级轻量断言宏(✅ 2026-10-25 销项) @@ -35,7 +35,7 @@ CH395F 套件 5 档构建全部链接通过,最坏态 `RW_IRAM1` 91,088 B / 69 的理由 = layer-deps 方向约束**,载于该头注释与提交正文。生产构建 `APP_ASSERT_ENABLE` 关闭时 宏展开为 `((void)0)`,零符号零开销——口径唯一正文 = 该头文件注释。 -### 2.2 A-4:ADC / RS485 采集链路的测试可验证性约束 ⏸ +### 2.2 采集链路测试可验证性约束:ADC / RS485 ⏸ **背景**:`adc_task.c` 已实现(B1,2026-10-10),`rs485_task.c` 仍是空桩,`App/protocol/` 只有 README 与 `.gitkeep`。 本项不是"缺陷",而是**对上游 B2 的设计约束**(B1 已完成;**B2 暂缓中,见 `暂缓-RS485.md`——重启 B2 动手前必须先落实本约束**,否则后续无法补测)。 @@ -51,7 +51,7 @@ CH395F 套件 5 档构建全部链接通过,最坏态 `RW_IRAM1` 91,088 B / 69 **完成判据**:B2 设计文档体现上述三条;协议解析函数有对应 L0.5 单测。(B1 侧:ADC 无协议解析面,其"同一时刻性"由原子快照保证、已闭环。) -### 2.3 A-5 残余:CI 体积门禁盲区(✅ 2026-10-25 销项) +### 2.3 「体积门禁 CI 强制路径」残余:CI 体积门禁盲区(✅ 2026-10-25 销项) 同码凭证 + CI 复核→564715c、跨树稳定修复→7c4628b。**口径唯一正文** = `scripts/check_size_gate.py` 的 docstring(含"CI 侧跳过 `mem-numbers` 的边界"与本地强制路径)。`docs/内存管理/RAM预算台账.md` diff --git a/docs/任务规划/登记簿.md b/docs/任务规划/登记簿.md index cc5c915f..b6fa1afd 100644 --- a/docs/任务规划/登记簿.md +++ b/docs/任务规划/登记簿.md @@ -1,49 +1,89 @@ -# 登记簿(长期观察项;存量 `N-x / S-x / T-x`,**新条目用语义标识**) +# 后续待办与长期观察登记簿 -> 编号空间与"不回收"规则见 `docs/任务规划/README.md` §0;排期位置见 `docs/任务规划/README.md` §2 波次总览; -> 当前波次任务卡(带 DoD / 写范围 / 状态)在 `UI补齐.md`(`数据可信.md` 已转收口态)——**「采集保护收尾」的 M1/M2 已收口,只剩 M3 送检**; -> 并行归道(写域 / 道 / 合并次序)见 `并行分道方案.md`。 -> **本文件是低频文件**:新发现按类别进对应表;销项 = 删行 + 清引用(`docs/任务规划/README.md` §3)。 -> **2026-10-14 起新条目的 `#` 列填「语义标识」**——一行短语直接说清是什么事(如 -> `沉降窗 800ms 上限只留痕`、`RS485 采集接线`),**不再新增字母+数字编号**;存量编号冻结沿用, -> **已随销项消失的号永久作废**、不得复用(原因与撞号实测见 `docs/任务规划/README.md` §0)。 +> **这里不放当前 UI 任务。** 当前执行只看 `UI补齐.md`;本文件登记 UI 之后的工作、 +> 等待外部条件的事项、独立立项和长期观察。推进顺序只看 `docs/任务规划/README.md` §2。 +> +> 条目一律用语义短语标识;完成后删行并清理引用。§6~§9 是由原排期、送检、分道和 +> 开放问题文件合并而来的唯一归属,不再在多处复制同一事项。 ## 1. 网络(CH395F / net 层) | # | 事项 | 发现 | 证据与现状 | 出处 | |---|---|---|---|---| -| N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ | -| N-16 | **A2/A4 专项场景台架验证待办**(正常路径已实测零回归):① PHY 拔线负向测试——断链需断 **DTU 侧**线缆或关**受管交换机端口**;② A2 芯片级 `net_init` 失败分支(需 SPI 故障注入);③ A4 短写压测(需远显客户端 + UI 推流压力) | 2026-10-03(台架拓扑 = CH395↔交换机,拔线操作受限) | 待专项台架窗口 | `实机测试实施指南.md` §10 | -| reconcile 突发饿死 uiTask | **对时 reconcile/relisten 突发(0~4s 窗)把唯一 BelowNormal 的 uiTask 压成同一任务 800ms+ 单串缺席(饿死型,非等待型——F3 返回点喂位射程外)**:8 连窗级 UI 缺席期间喂狗面本身未断(cf=0 seg=1)、uisn=0(不在 sink 段内)、uip 在途 307→1083ms 而 uis 峰仅 183ms——被拖的 1s 在 feed/port 纯计算段墙钟。**2026-10-25 台架两轮分箱定案**:同负载换 :7003 churn 2200 周期 stall=0(阻塞型已被 F3 治愈),探针轮 3 事件 8 行全为该签名(漏检零、误报零,v1.1 口径 FAIL 面精确收缩)。处置方向 = 自愈设计 §11.3 脆弱点 1 实测样本并档 **E-27/28**:优先级矩阵复核 与 reconcile 突发分片让路 两案并评;**排期 = 批 4(结构债)**,与 E-27/E-28 裸指针收口同批 | 2026-10-25(F2/F3 验收轮②,v1.1 判据下捕获) | `docs/测试说明/boot_log_data/f3rounds_r2_probe_30min.log.gz`(3 事件 8 行 + STALL 证据行 + 同行 PROF);对照轮 `f3rounds_r1_7003_churn_30min.log.gz`;机理归档 `../网络/网络自愈架构设计.md` §4.6、`../子系统设计/看门狗设计说明.md` §11.3、`../UI设计/UI_设计架构.md` §5.8 | `../算法/继电保护功能复现规划.md`(E-27/28 属主文档指针) | -| N-30 | **relisten 沉降段统一到驱动 `sock_reset` 原语受阻**——"去重复实现"的收口已实测回退:原语版 `ftp_abort_probe` RST **0/10**、FIN **0/5**(中止后首笔复连仍被截断到 ≤2048B),另新增 `sock_reset_sync timeout (600ms)` 与 `close: sock_reset not done` 两条 `[ERR]`;同场 net 层自建序列 **10/10 + 5/5** | 2026-10-14(同一块板 A/B,判据 `test/host/ftp_abort_probe.py`;快复连 50/50、背靠背 30/30 两版都过 → 失效面精确落在"中止后首笔复连") | **现状 = `net_listener.c` 保留自建排渣序列**(排空 + CLOSE + 排事件 + 等 CLOSED,30 ms 重试轮持续清)。重开前置三条语义:① 隔离窗内**持续**排空/关闭(残渣在 CLOSE 之后才落进 RX,一次排空接不住)② **复用让位**(复位机不得拆上层刚在同一芯片 Socket 上重建的监听)③ `net_sock_reset_sync` 改"已在运行 = 加入等待"(`begin` 返 -1 ≠ 失败) | `../网络/网络自愈架构设计.md` §8 延后项 1、§5.2 E16;`../问题记录/CH395F_Trap_Records.md` TRAP-NET-18 | +| L5 队列往返基线待重验 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ | +| 网络负向场景台架验证 | **负向场景台架验证待办**(拔线断链 / 芯片级 `net_init` 失败分支 / 短写压测;正常路径已实测零回归):① PHY 拔线负向测试——断链需断 **DTU 侧**线缆或关**受管交换机端口**;② 芯片级 `net_init` 失败分支(需 SPI 故障注入);③ 短写压测(需远显客户端 + UI 推流压力) | 2026-10-03(台架拓扑 = CH395↔交换机,拔线操作受限) | 待专项台架窗口 | `实机测试实施指南.md` §10 | +| reconcile 突发饿死 uiTask | **对时 reconcile/relisten 突发(0~4s 窗)把唯一 BelowNormal 的 uiTask 压成同一任务 800ms+ 单串缺席(饿死型,非等待型——F3 返回点喂位射程外)**:8 连窗级 UI 缺席期间喂狗面本身未断(cf=0 seg=1)、uisn=0(不在 sink 段内)、uip 在途 307→1083ms 而 uis 峰仅 183ms——被拖的 1s 在 feed/port 纯计算段墙钟。**2026-10-25 台架两轮分箱定案**:同负载换 :7003 churn 2200 周期 stall=0(阻塞型已被 F3 治愈),探针轮 3 事件 8 行全为该签名(漏检零、误报零,v1.1 口径 FAIL 面精确收缩)。处置方向 = 自愈设计 §11.3 脆弱点 1 实测样本并档「net 层跨上下文收口」项:优先级矩阵复核与 reconcile 突发分片让路两案并评;**排期 = UI 完成后的第一项**,与裸指针收口同批 | 2026-10-25(F2/F3 验收轮②,v1.1 判据下捕获) | `docs/测试说明/boot_log_data/f3rounds_r2_probe_30min.log.gz`(3 事件 8 行 + STALL 证据行 + 同行 PROF);对照轮 `f3rounds_r1_7003_churn_30min.log.gz`;机理归档 `../网络/网络自愈架构设计.md` §4.6、`../子系统设计/看门狗设计说明.md` §11.3、`../UI设计/UI_设计架构.md` §5.8 | `../算法/继电保护功能复现规划.md`(裸指针收口项属主文档指针) | +| relisten 沉降段原语化受阻 | **relisten 沉降段统一到驱动 `sock_reset` 原语受阻**——"去重复实现"的收口已实测回退:原语版 `ftp_abort_probe` RST **0/10**、FIN **0/5**(中止后首笔复连仍被截断到 ≤2048B),另新增 `sock_reset_sync timeout (600ms)` 与 `close: sock_reset not done` 两条 `[ERR]`;同场 net 层自建序列 **10/10 + 5/5** | 2026-10-14(同一块板 A/B,判据 `test/host/ftp_abort_probe.py`;快复连 50/50、背靠背 30/30 两版都过 → 失效面精确落在"中止后首笔复连") | **现状 = `net_listener.c` 保留自建排渣序列**(排空 + CLOSE + 排事件 + 等 CLOSED,30 ms 重试轮持续清)。重开前置三条语义:① 隔离窗内**持续**排空/关闭(残渣在 CLOSE 之后才落进 RX,一次排空接不住)② **复用让位**(复位机不得拆上层刚在同一芯片 Socket 上重建的监听)③ `net_sock_reset_sync` 改"已在运行 = 加入等待"(`begin` 返 -1 ≠ 失败) | `../网络/网络自愈架构设计.md` §8 延后项 1、§5.2 E16;`../问题记录/CH395F_Trap_Records.md` TRAP-NET-18 | ## 2. 存储 / 日志管线 | # | 事项 | 发现 | 证据与现状 | 出处 | |---|---|---|---|---| -| 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}` | +| dhara 重试自旋残留 | **dhara 重试自旋残留**——`try_recover()` 的 `DHARA_MAX_RETRIES`(8) 自旋在坏块密集时仍可能长时间占用 defaultTask(喂狗点) | 2026-10-02 | 低优先:现有 `vol_lock` + 喂狗节奏缓解;若台架再出"存储侧喂狗饥饿"再排期 | `Lib/dhara/map.c` | +| 可选全序列卷锁 | **可选全序列卷锁**——现卷锁仅覆盖 `syslog` / `cfg` / `lftpd` 三条路径(`vol_lock_wait(0)` try 语义),其它潜在访问点(如 UI 读日志)未纳入 | 2026-10-02 | 待评估:先确认是否真有第二写者;无则关闭本项 | `App/fs/vol_lock.{c,h}` | ## 3. 硬件(V0.1 缺陷 → V0.2 改版项) | # | 事项 | 发现 | 证据与现状 | 出处 | |---|---|---|---|---| -| N-18 | **8 路电气量分属两片转换器(V0.1 硬件缺陷,需下一版硬件修改)**——UA/UN/IA 走 TPAFE5160(16 位、8 通道同步、±5 V 双极性),UB/UC/IB/IC/IN 走 STM32 ADC(12 位、顺序扫描、0~3.3 V 单极性):① 两片转换器无共同采样触发 + STM32 侧顺序扫描(9 通道一轮 ≈ 67 µs)→ 跨转换器通道**无同一时刻性、相位关系不确定**,主板侧功率/功率因数/相角/对称分量类运算不成立;② 同族电气量精度与量程不一致(LSB ≈ 152.6 µV vs ≈ 806 µV,且 STM32 侧前端无中点偏置、负半周削顶);③ 语义与硬件归属交叉,固件映射/UI 通道序/自检注入须逐通道查表绕开 | 2026-10-10 定性更正(**撤销** 2026-10-09 建档时"有意分配、不要修正"的说法) | **V0.2 改版要求**:8 路电气量 UA/UB/UC/UN/IA/IB/IC/IN **全部接 TPAFE5160**,与 TPAFE 上 5 路备用路(AIN5/13/7/15/12)对调串联电阻在转换器一侧的目的地——元件数量与料号不变,属走线级改动。**V0.1 现板软件口径**:主板侧只做逐通道独立监视,禁止跨转换器做相位/功率/功率因数/矢量类运算(此类量取子板计量模块结果)。固件侧消费者已落地(B1,2026-10-10,按语义映射表取数)→ V0.2 改版只改映射表内容,上层语义与代码不动 | `../硬件平台/ADC通道映射说明.md` §1/§7;`../硬件平台/V0.1_网表详细分析报告.md` §1.4 V10、§6-3 | +| 双转换器无共同采样触发 | **8 路电气量分属两片转换器(V0.1 硬件缺陷,需下一版硬件修改)**——UA/UN/IA 走 TPAFE5160(16 位、8 通道同步、±5 V 双极性),UB/UC/IB/IC/IN 走 STM32 ADC(12 位、顺序扫描、0~3.3 V 单极性):① 两片转换器无共同采样触发 + STM32 侧顺序扫描(9 通道一轮 ≈ 67 µs)→ 跨转换器通道**无同一时刻性、相位关系不确定**,主板侧功率/功率因数/相角/对称分量类运算不成立;② 同族电气量精度与量程不一致(LSB ≈ 152.6 µV vs ≈ 806 µV,且 STM32 侧前端无中点偏置、负半周削顶);③ 语义与硬件归属交叉,固件映射/UI 通道序/自检注入须逐通道查表绕开 | 2026-10-10 定性更正(**撤销** 2026-10-09 建档时"有意分配、不要修正"的说法) | **V0.2 改版要求**:8 路电气量 UA/UB/UC/UN/IA/IB/IC/IN **全部接 TPAFE5160**,与 TPAFE 上 5 路备用路(AIN5/13/7/15/12)对调串联电阻在转换器一侧的目的地——元件数量与料号不变,属走线级改动。**V0.1 现板软件口径**:主板侧只做逐通道独立监视,禁止跨转换器做相位/功率/功率因数/矢量类运算(此类量取子板计量模块结果)。固件侧消费者已落地(B1,2026-10-10,按语义映射表取数)→ V0.2 改版只改映射表内容,上层语义与代码不动 | `../硬件平台/ADC通道映射说明.md` §1/§7;`../硬件平台/V0.1_网表详细分析报告.md` §1.4 V10、§6-3 | ## 4. 测试与验证基础设施 | # | 事项 | 优先级 | 问题与证据 | 处置方向 | 出处 | |---|---|---|---|---|---| -| T-7 | **CI 远端闭环未验证**(runner 本机已就绪) | 中 | **剩余两件**:③ 推送远端跑一次两 job 闭环验证(**跑通证据尚无留档**,故仍按"未验证"对待);④ 第二机交叉验证(不同工具安装路径,验 `DTU_*` 环境变量方案,→ `../环境与部署/本地工具路径环境变量方案.md` 变更记录 TODO)。①② 已就绪,见 `../CI/远程CI完整指南.md` §0 仓库状态行 | 完成前**提交前仍须在本地跑 `scripts/ci_local.py` 或 `pre-commit run --all-files` 自查**(L0 唯一保障,不能因"已配 CI"而放宽) | `.gitea/workflows/ci.yml`;`../CI/远程CI完整指南.md`;`../测试说明/测试开发标准流程.md` §9 | +| CI 远端闭环未验证 | **CI 远端闭环未验证**(runner 本机已就绪) | 中 | **剩余两件**:③ 推送远端跑一次两 job 闭环验证(**跑通证据尚无留档**,故仍按"未验证"对待);④ 第二机交叉验证(不同工具安装路径,验 `DTU_*` 环境变量方案,→ `../环境与部署/本地工具路径环境变量方案.md` 变更记录 TODO)。①② 已就绪,见 `../CI/远程CI完整指南.md` §0 仓库状态行 | 完成前**提交前仍须在本地跑 `scripts/ci_local.py` 或 `pre-commit run --all-files` 自查**(L0 唯一保障,不能因"已配 CI"而放宽) | `.gitea/workflows/ci.yml`;`../CI/远程CI完整指南.md`;`../测试说明/测试开发标准流程.md` §9 | -## 5. 审查报告并轨(2026-10-11;⚠ 报告编号与本登记簿同名不同义,出处列保留报告编号,引用一律带文档名) +## 5. RS-485、内存与仓库观察 -| # | 事项 | 影响 / 现状 | 处置方向 | 波次 | 出处 | +| # | 事项 | 影响 / 现状 | 处置方向 | 状态 | 出处 | |---|---|---|---|---|---| -| N-23 残余 | **KNOWN_GAPS 白名单清理**——现存唯一缺口 = `App/task/rs485_task.c`(E-59 门禁下刻意不接线,随 B2 暂缓,见 `暂缓-RS485.md`) | 白名单不清 = "写了不编译"盲区仍在 | 随 B2 一并出白名单(`--strict` 供审计复跑) | 结构债 | 报告 N-06 残余 | -| N-25 | 性能/资源建议簇:PASV 每次新建监听;PHY 强制 100M 无自协商回退;`NET_MAX_SOCKETS 8` 与单连接语义不符;`HAL_Delay(1)` 阻塞 DE(**DE 项随 B2 暂缓,见 `暂缓-RS485.md`**) | 次级性能/语义一致性 | 随窗口评估,逐项机理见 `../网络/Net层阻塞模型架构设计.md` 与 `Drivers/BSP/NET/README.md` | 结构债 | 报告 N-09/PHY/P3-x/rs485(报告已删,仅作历史指称) | -| N-27 | **内存优化簇剩余**:原报告 R-2 后半(缩堆 56→32 KB → 迁 CCM)· R-3 · R-4 · R-6 | ⏸ **整簇暂缓**(2026-10-15 用户裁定):没有功能在等这块预算,而搬迁要动 scatter、要逐条过 DMA 可达性清单、缩堆还要上板复测。⚠ 代价落在"以后加任务 / 抬栈 / 加堆"那一刻——CCM 只剩 7,904 B、FreeRTOS 堆 `min_ever` 8,464 B,届时 `mem-budget` 会先报警 | 重开条件见 `../内存管理/RAM使用分析与优化方案.md` §4。⚠ **原报告 R-5 已判定不可行**(CMSIS-RTOS2 `freertos_os2.h` 有 `#if (configMAX_PRIORITIES != 56) #error`,改小直接编译失败),不再列入 | **暂缓** | 报告 R-2 剩余/R-3/R-4/R-6/§5.5 | -| N-28 | **报告 F-5:uvprojx 里 127 个空 TU**(201 个 LVGL `.c` 中 127 个 `.map` 零产出) | 增量构建实测 4~5 s、远程 CI 刻意不跑 `@build` → **非产品指标**;报告原"编译时长 −40%"无基线记录 | 🟡 观察:等有实测的全量构建耗时痛点再做——用 Keil 原生 `0`(可逆)而非删条目,且排在任何 `lv_conf.h` 改动之后;动手前先补一次全量构建计时做基线 | 结构债 | 报告 F-5 | -| N-29 | **仓库膨胀**:`.git` **233 MB**(2026-10-25 实测)+ 手册图片 36.3 MB(受控文件合计仅 79.3 MB,`.git` 已是它的 3 倍) | clone/备份慢,且持续恶化 | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**(三项都不解决历史膨胀)。**已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评 | **仓库决策** | 报告 D-1 | -| U 簇 | **U5 页面落地**——33 叶已绑 **12**、剩 **21**("27 页"是目标数、不是差集;`ui_page_registry.c` 是生成物不算页面)。组件层三件套(`ui_w_editor` / `ui_w_setlist` / `ui_page_action`)已落地但**零消费者** | 地基已齐(U1~U4 落地;`ui-golden` 与字符集差集检查均已挂 pre-commit),新增一页 = 3 处人工 + 1 条生成器命令 | **当前波次**(2026-10-25 用户决定:下一波只做 UI 补齐、结构债押后)。21 叶按数据源就绪度分三梯队,第一梯队 9 叶零依赖——明细与 DoD 见 `UI补齐.md` | UI 扩展 | `../UI设计/页面补齐规划.md` | +| rs485_task 白名单缺口 | **KNOWN_GAPS 白名单清理**——现存唯一缺口 = `App/task/rs485_task.c`(RS-485 接线门禁未满足、刻意不接线,随 B2 暂缓,见 `暂缓-RS485.md`) | 白名单不清 = "写了不编译"盲区仍在 | 随 B2 一并出白名单(`--strict` 供审计复跑) | 结构债 | 报告 N 族残余(报告已删) | +| 缩堆迁 CCM 后半(暂缓) | **内存优化簇剩余**:原报告缩堆建议的后半(缩堆 56→32 KB → 迁 CCM)· 同类三项 | ⏸ **整簇暂缓**(2026-10-15 用户裁定):没有功能在等这块预算,而搬迁要动 scatter、要逐条过 DMA 可达性清单、缩堆还要上板复测。⚠ 代价落在"以后加任务 / 抬栈 / 加堆"那一刻——CCM 只剩 7,904 B、FreeRTOS 堆 `min_ever` 8,464 B,届时 `mem-budget` 会先报警 | 重开条件见 `../内存管理/RAM使用分析与优化方案.md` §4。⚠ **原报告另有一条已判定不可行**(CMSIS-RTOS2 `freertos_os2.h` 有 `#if (configMAX_PRIORITIES != 56) #error`,改小直接编译失败),不再列入 | **暂缓** | 报告 R 族剩余/§5.5(报告已删) | +| 仓库体积膨胀观察 | **仓库膨胀**:`.git` **233 MB**(2026-10-25 实测)+ 手册图片 36.3 MB(受控文件合计仅 79.3 MB,`.git` 已是它的 3 倍) | clone/备份慢,且持续恶化 | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**(三项都不解决历史膨胀)。**已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评 | **仓库决策** | 报告 D 族 | + +--- + +## 6. UI 完成后的编码队列 + +| 顺序 | 条目 | 要做什么 | 完成或关闭判据 | +|---|---|---|---| +| 1 | **net 层跨上下文收口** | 同批处理两件事:裸指针收口为状态查询 API;对时 reconcile/relisten 突发导致 uiTask 800 ms 以上连续缺席,评估优先级矩阵与突发分片让路 | 同类台架负载下不再出现目标饿死签名;网络与 UI 回归通过;证据写入网络与看门狗模块文档 | +| 2 | **网络性能资源建议簇** | 分别评估 PASV 重复建监听、PHY 强制 100M 无回退、`NET_MAX_SOCKETS` 与单连接语义不一致;DE 项随 RS-485,不在本项做。机理见 `../网络/Net层阻塞模型架构设计.md` 与 `Drivers/BSP/NET/README.md` | 每项形成“修改并验证”或“有理由不做”的结论;不为了清清单做无收益重构 | +| 3 | **dhara 重试自旋与卷锁评估** | 先确认是否仍出现存储侧喂狗饥饿、是否确有第二写者,再决定是否修改 | 有新证据才排修改;无第二写者则关闭全序列卷锁项 | +| 4 | **uvprojx 空 TU 观察** | 201 个 LVGL `.c` 中 127 个在 `.map` 零产出,但当前增量构建仅 4~5 秒;有全量构建耗时痛点时先补计时基线,再用 Keil 原生 `0` 可逆排除 | 有实测收益才保留改动;否则继续观察 | + +> RS-485 白名单随 `暂缓-RS485.md` 整簇处理,不列为 UI 后的独立编码任务。 +> 并行写域、工作树与合并纪律统一见 `docs/环境与部署/并行任务工作树方案.md`。 + +## 7. 等台架或外部资源的事项 + +| 条目 | 需要条件 | 要做的事 | 去向 | +|---|---|---|---| +| **数据可信三项** | 台架与万用表;子板实物或原理图;标准源 | 已知电压注入 → 端子变比 → 8 通道两点标定 | 唯一任务卡与验收在 `数据可信.md` §5 | +| **标准源送检与第三方 104 验收** | 两点标定完成、保护 VALID 置位、第三方主站 | 标准源注入判据全部通过;总召、周期、变位、遥控全流程通过;证据归档。送检窗口前 2 日冻结特性 | `../子系统设计/IEC104上行设计说明.md` §7/§8 | +| **L5 队列往返基线待重验** | 台架 + PC peer | 下次 Phase 13 窗口重锁基线 | 本文件 §1 | +| **网络负向场景台架验证** | 拔线、SPI 故障注入、远显压力条件 | PHY 拔线、芯片级 `net_init` 失败、短写压测 | 本文件 §1 | +| **串口助手正路径补验** | CH340 接入 | 补齐尚未覆盖的正路径 | `../测试说明/串口助手设计说明.md` | +| **CI 远端闭环未验证** | runner 与第二台机器 | 验证远端两 job 闭环和不同工具路径 | 本文件 §4 | + +**送检顺序不可倒过来**:产品构建当前要求 UA/UN/IA 的标定 VALID 全部置位才走保护判据。 +未完成标准源标定就送检,保护会表现为不动作。保持该闭锁,不用非标准源标定走捷径。 + +## 8. 等立项或硬件代次的事项 + +| 条目 | 当前不做的原因 | 重开条件 | +|---|---|---| +| **保护元件 V0.1 可行子集** | A 相涌流制动、突变量启动、无压无流闭锁、元件级校验和虽能设计,但没有标准源就无法验收,且保护仍在闭锁 | 标准源到位,且标定与闭锁验证完成 | +| **采集增强** | 等周波调频、零序通道、遥测谐波轨依赖可信工程量 | 标定完成后单独立项 | +| **跨转换器三相与功率类功能** | 两片转换器无共同采样触发,V0.1 上相位、功率、对称分量不成立 | V0.2 硬件改版 | +| **开入开出与遥信配置 3 页** | 固件和 V0.1 硬件均无可配开入对象;出口还涉及 GPIO 安全电平、保持/脉冲和回读自检 | 开入开出作为独立模块立项,再与 UI 页面同批 | +| **故障录波与 104 文件传输** | 是独立子系统,不是补一个页面 | 产品定义与工程量口径明确后立项 | +| **SNTP 设置** | 当前对时走 8888 私有协议,SNTP 客户端与产品口径未定 | SNTP 客户端立项 | +| **RS-485 整簇** | 用户决定暂缓 | `暂缓-RS485.md` | + +## 9. 开放问题 + +| 问题 | 为什么还没答案 | 处理时机 | +|---|---|---| +| **是否在真实初始化点接入功能裁剪门控** | 旧占位宏没有消费者且已删除;是否需要真实门控属于产品定义,不是单纯代码问题 | 产品提出按配置裁剪需求时决定;决定前不新增空开关 | diff --git a/docs/任务规划/采集保护收尾.md b/docs/任务规划/采集保护收尾.md deleted file mode 100644 index bf47c8f4..00000000 --- a/docs/任务规划/采集保护收尾.md +++ /dev/null @@ -1,36 +0,0 @@ -# 采集 + 保护收尾(只剩 M3 送检) - -> **本波只剩余一项未开工的事**:M3 可送检(B10)。台架闭环与功能面完整这两项已闭环,本文件 -> 不保留其任务卡与证据链(证据在模块文档与 `git log`)。 -> 排序权威(波次总览)在 `docs/任务规划/README.md` §2;并行 / 工作树 / 两把锁的机制见 -> `docs/任务规划/并行分道方案.md`。 - -## 1. 本波目标(用户决定 2026-10-12) - -把"采集→相量→保护→上送"链路从"代码落地"收口到**台架闭环 + 功能面完整 + 可送检**。 -前两项已达成,**本文件只剩"可送检"**。 - -## 2. 里程碑 - -| 里程碑 | 退出判据(全部满足才算完成,随后销项) | 服务分类 | -|---|---|---| -| **M3 可送检** | B10 注入判据 + 第三方主站全流程通过,证据归档 | **Fixed Date**(送检窗口一到全道让路;窗口前 2 日特性冻结,只修稳定——Zephyr RC 语义) | - -## 3. 任务卡(五字段:DoD / 硬依赖 / 资源 / 写范围 / 状态;完成即销项删行) - -> 资源图例:🔴 台架(COM6/ST-LINK,`bench.lock` 独占)· 🟡 外部(标准源/第三方主站)· 🟢 纯软件(可并行)。 -> 状态:⬜ 未开始 · 🟦 进行中(销项行直接删除,见 `docs/任务规划/README.md` §3 维护约定)。 - -| 序 | 任务 | DoD(可验证验收判据) | 硬依赖 | 资源 | 写范围 | 状态 | 出处 | -|---|---|---|---|---|---|---|---| -| **B10** | **标准源注入 + 第三方 104 主站往返(送检窗口)** | 标准源注入:L1-5 标定复核 / L1-7 等周波 / P1 过流·过负荷 / P2 电压 + 频率类判据全通过;第三方主站总召/周期/变位/遥控全流程(`../子系统设计/IEC104上行设计说明.md` §7/§8 核对单);证据归档 | 卡 12(「数据可信」,标准源) | 🟡 | —(窗口内缺陷修复走 Expedite 类) | ⬜ | 保护规划 §7 P1/P2 验收;`../子系统设计/IEC104上行设计说明.md` §7/§8 | - -> ⚠ **B10 与「数据可信」的依赖**:B10 的"L1-5 标定复核"依赖「数据可信」的通道标定卡(卡 12), -> 即 **「数据可信」是 B10 的前置**。该依赖已在 `docs/任务规划/数据可信.md` §10 登记,两边不要 -> 各写一套;送检窗口若早于卡 12,先按卡 0 的注入结果对齐口径。 -> -> ⚠ **送检前必须先解保护闭锁**(2026-10-25 复核):产品构建下 `protect_task.c:98` 要求 -> UA/UN/IA 的 `ADC_CAL_F_VALID` 全置位才走判据,而 VALID 只在标定写入 `[adc_cal] valid` -> 后才置位——**当前保护不动作**。B10 要验"过流/过负荷/电压+频率类判据全通过", -> 必须先完成卡 12 把 VALID 置起来,否则送检现场所有保护判据都表现为"不动作"。 -> 用户裁定(2026-10-25):**保持闭锁到标准源到位**,不用万用表标定走捷径。 diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 4556355c..48ef367b 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -3,9 +3,9 @@ > 版本:v1.3 日期:2026-10-22(v1.2 2026-09-21) > 变更:v1.3 §4 的阶段子项改为**层级编号**(见下"编号约定");同步 2026-10-22 现状数字与 > `data/ram_report_20261022.md` / `data/stack_report_20261022.md` 两份新快照。 -> 变更:2026-10-23 补 `data/ram_report_20261023.md` 快照(S-5 试点:RS485 改报 `bsp_err_t`, -> `Code` 213,436 → **213,648**(S-5 全波 +212:契约试点 +48、C3 SD2506 +44、C1 GD5F/TPAFE +36、 -C2 dhara 失败类 +84;RO 另 +208,全是 C2 的失败类名表)→ `ER_IROM1` 当日 **227.1 KB / 22.2%**(字节值见 `data/ram_report_20261023.md`,正文不留会过期的裸 B 值); +> 变更:2026-10-23 补 `data/ram_report_20261023.md` 快照(BSP 错误码契约试点:RS485 改报 `bsp_err_t`, +> `Code` 213,436 → **213,648**(该契约全波 +212:RS485 试点 +48、SD2506 +44、GD5F/TPAFE +36、 +dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` 当日 **227.1 KB / 22.2%**(字节值见 `data/ram_report_20261023.md`,正文不留会过期的裸 B 值); > **RAM 全部字段零增量**;受管表已由 > `check_mem_numbers.py --write` 同步,配图常量同步)。 > 变更:2026-10-24 合入 `task/cov-host-line-coverage`(覆盖率口径 §9 的 P1 全批)——RAM **+8 B** @@ -29,7 +29,7 @@ C2 dhara 失败类 +84;RO 另 +208,全是 C2 的失败类名表)→ `ER_IR - F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§4.1.2) - 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋 -当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-24 `@build`):RW_IRAM1 **75,632 / 131,072 B(57.7%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,登记为 S-1,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。 +当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-24 `@build`):RW_IRAM1 **75,632 / 131,072 B(57.7%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,曾登记在案,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。 本方案三个目标: @@ -109,9 +109,9 @@ C2 dhara 失败类 +84;RO 另 +208,全是 C2 的失败类名表)→ `ER_IR > **同日 L1-6 有效性校验后抬栈 512→640 words**:`adc_health_report`→`syslog_post` 控制台回显尾把静态 > 最坏链推到 **1080 B**(512 words 时余 968 B < 1024 B ISR 底线)→ 抬至 2.5 KB,余 **1480 B / 57%**; > 堆 `min_ever` 7152 → **6640 B**(-512 B,符合预算)。 -> 此前 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-10-12 N-33 栈收口(登记簿销项,「采集保护收尾」M1)**:B7 定值持久化把 defaultTask 静态链推到 **3016 B** +> 此前 2026-10-04 的 V5 30 min 重采曾把 defaultTask 推到 **3664B/4096(余量 432B ≈ 10%,曾登记在案)**; +> 2026-10-10 的栈整改(**未抬栈**,见 §4 阶段 0 的"2026-10-10 复测")把它压回 **2152B/4096**,该项已销项。 +> **2026-10-12 栈余量收口(登记簿销项,「采集保护收尾」M1)**:B7 定值持久化把 defaultTask 静态链推到 **3016 B** > (storage_bring_up→cfg 写→f_rename→dhara GC→控制台回显尾),1024 words 余 1080 B < 30% 线; > timeSyncTask 链 **1264 B**(同一控制台回显尾),512 words 余 784 B < ISR 底线 → **抬栈** > defaultTask 1024→**1280**、timeSyncTask 512→**640**(堆 +1536 B,台账 §6 已登记); @@ -124,8 +124,8 @@ C2 dhara 失败类 +84;RO 另 +208,全是 C2 的失败类名表)→ `ER_IR | netTask | `960*4` | 3.75KB | CH395F 轮询(DMA 搬运,栈压力小) | **1116 B(余 70%)**/ 静态 **1912 B**(2026-10-14 E19 `force open` 降级后复测,链尾经 net_listener_tick→net_relisten_locked;前值 **1920 B** = 同日 E17 单一所有者收口后、**1944 B** = SEND pending 台架复测(含 `net_send_pending_tick` 新增栈帧),更早 920 B 为一期 recv 挂起时实测;FTP 传输期实测峰 **1164 B**) | | ftpTask | `1024*4` | 4KB | lftpd FTP 服务 | 740 B(余 81%)/ 静态 1216 B | | testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B | -| defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(N-33 抬栈 1024→1280,2026-10-12) | -| timeSyncTask | `640*4` | 2.5KB | TCP 时间同步 | 616 B(余 50%)/ 静态 **1264 B**(N-33 抬栈 512→640,2026-10-12) | +| defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(栈余量收口时抬栈 1024→1280,2026-10-12) | +| timeSyncTask | `640*4` | 2.5KB | TCP 时间同步 | 616 B(余 50%)/ 静态 **1264 B**(栈余量收口时抬栈 512→640,2026-10-12) | | adcTask | `640*4` | 2.5KB | TPAFE5160 采样消费 + L2 相量 + L1-6 有效性校验(TIM3 1 kHz 节拍,B1→L2→L1-6 2026-10-11) | **952 / 2560**(余 1608 B / 62%,台架 `@test` 实测 2026-10-11);静态 **1320 B**(2026-10-12 复测,L3 接线后) | | iec104Task | `640*4` | 2.5KB | IEC104 上行服务(2404 监听 + link 状态机,B4 2026-10-12) | 未上板 / 静态 **1456 B**(拆链→重建监听→net_bind→SPI 故障→日志尾,日志主导;512 words 时余 592 B < ISR 底线 → 抬至 640,余 1104 B / 43%) | | protectTask | `512*4` | 2KB | 保护判据节拍(prot_elm_run 消费者 + L5-4 采样闭锁,B5 2026-10-12) | 未上板 / 静态 **872 B**(float 判据 + phasor 快照 + 日志尾,余 1176 B / 57%) | @@ -224,7 +224,7 @@ C2 dhara 失败类 +84;RO 另 +208,全是 C2 的失败类名表)→ `ER_IR > 本表随代码变化;权威数字与占比见 §2.1 受管快照(`ER_IROM1` / **Flash (ROM 合计)** 行)。 > 历史快照:2026-09-21 为 Code 234,976 / RO 13,824 / 合计 ROM 249,432(23.8%);2026-10-10 为 Code 258,516 / RO 16,208 / 合计 ROM 275,604(26.3%)。 -> **2026-10-11 裁剪 LVGL 死代码(原审查报告 F-1/F-2/F-3,已销项)**:`Lib/lv_conf.h` 显式关掉 10 个 `LV_DRAW_SW_SUPPORT_*`(只留 I1)、5 个未用控件类(label / button / buttonmatrix / bar / textarea)、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX` / `LV_USE_GRID` → **`ER_IROM1` 274,724 → 210,148 B(省 64,576 B = 旧镜像的 23.5%)**,Flash 占比 26.3% → **20.1%**。 +> **2026-10-11 裁剪 LVGL 死代码(原审查报告的三条裁剪建议,已销项)**:`Lib/lv_conf.h` 显式关掉 10 个 `LV_DRAW_SW_SUPPORT_*`(只留 I1)、5 个未用控件类(label / button / buttonmatrix / bar / textarea)、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX` / `LV_USE_GRID` → **`ER_IROM1` 274,724 → 210,148 B(省 64,576 B = 旧镜像的 23.5%)**,Flash 占比 26.3% → **20.1%**。 > 对象级对照(`.map` 组件表,Code+RO+RW):`blend_to_*` 40,582 → 1,338(其中 `blend_to_i1` 自身 5,272 → 1,338——它按**源**格式的分支也一并消失);控件类 13,397 → 0;`lv_theme_simple` 866 → 0;`lv_draw_sw_transform` 4,340 → 598;`lv_theme_mono` 1,400 → 1,036;镜像内对象数 227 → 212。实测比原报告估算(54,630 B)多省约 10 KB,差额正是这些"锚点消失后连带消失的分支"。 > ⚠ **配套本地补丁**:`lv_draw_sw_blend_to_i1.c` 的 `lv_color16_from_u16` 上游 9.3.0 漏了定义处守卫(唯一使用点全在 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED` 内)→ 关掉该格式后 ARMCC 报 `#177-D declared but never referenced`(破坏"0 警告"门禁)。已按使用点补同一守卫,升级 LVGL 时若上游已修可去掉。 > 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`@build` 0 错 0 警;`@test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §4.1.4 第 10 条。 @@ -420,9 +420,9 @@ 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 单客户端复用,探测会占掉唯一会话(见 `docs/测试说明/FTP_Test_Guide.md` §8.9)。 -#### 2026-10-10 复测:**不抬栈**把 defaultTask 从 FAIL 拉回 PASS(S-1 销项) +#### 2026-10-10 复测:**不抬栈**把 defaultTask 从 FAIL 拉回 PASS(该项销项) -触发点:登记簿 S-1(2026-10-04 实测 defaultTask 3664B/4096,余量 10%)。叠加静态链后判据更严—— +触发点:2026-10-04 实测 defaultTask 3664B/4096(余量 10%)的登记项。叠加静态链后判据更严—— **静态最坏链已涨到 5312 B > 配置 4096 B(margin −1216 B)**。本次刻意**不动栈值**,改为削链: | 任务 | 配置 (B) | 静态最坏 改前 → 改后 | 实测峰值 改前 → 改后 | 判定 | @@ -445,7 +445,7 @@ void vApplicationMallocFailedHook(void) `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 处大局部缓冲静态化。 + + 配置引擎/syslog 写者单任务属主(单写者契约)。先例 = 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`/ @@ -609,15 +609,15 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/ | 步骤 | 内容 | 验证 | 状态 | |---|---|---|---| -| S1 | 0.1 + 0.2(溢出钩子 + `stack_probe_poll` 栈水位探针,`Src/freertos.c` USER CODE 区) | `@build` 0错0警;`@test` 串口出现每任务 `[STACK]` 报告 | ✅ 2026-09-21 | -| S2 | 0.3 `ram_report.py` + 基线 | 脚本输出与 map 人工核对一致 | ✅ 2026-09-21(`data/ram_report_20260921.md`) | -| S3 | 全功能回归跑数(菜单/FTP/网络/格式化/浸泡 1h) | 拿到各任务 `free_min` 与堆 free 底值 | ◐ boot + FTP 路径已实测(09-21);全菜单遍历 + 1h 浸泡待补 | -| S4 | §4.1.4 lftpd 静态化 + §4.2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 | ✅(§4.2.1 于 2026-09-10、§4.1.4 于 2026-09-21 完成) | -| S5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `@test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) | -| S6 | §4.1.2 CCM(scatter + section + 门禁宏) | map 显示 `RW_CCM`;关 `APP_CCM_ENABLE` 也能编译通过 | ⏸ **暂缓(2026-10-15 裁定)**;已完成部分仅 `RW_CCM` + 堆迁入(09-21),缓冲 section 迁移与门禁宏不再排期 | -| S7 | §4.1.3 栈缩减(按 S3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 | ⏸ **暂缓(2026-10-15 裁定)**(目标值已按 09-21 实测修正,重开时可直接用) | -| S8 | §4.2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` | ⏸ **暂缓(2026-10-15 裁定)**;⚠ 2026-10-12 B5 反向扩容至 56 KB,重开时须重跑 `stack_report.py` + 上板 `min_ever` | -| S9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) | +| 步骤 1 | 0.1 + 0.2(溢出钩子 + `stack_probe_poll` 栈水位探针,`Src/freertos.c` USER CODE 区) | `@build` 0错0警;`@test` 串口出现每任务 `[STACK]` 报告 | ✅ 2026-09-21 | +| 步骤 2 | 0.3 `ram_report.py` + 基线 | 脚本输出与 map 人工核对一致 | ✅ 2026-09-21(`data/ram_report_20260921.md`) | +| 步骤 3 | 全功能回归跑数(菜单/FTP/网络/格式化/浸泡 1h) | 拿到各任务 `free_min` 与堆 free 底值 | ◐ boot + FTP 路径已实测(09-21);全菜单遍历 + 1h 浸泡待补 | +| 步骤 4 | §4.1.4 lftpd 静态化 + §4.2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 | ✅(§4.2.1 于 2026-09-10、§4.1.4 于 2026-09-21 完成) | +| 步骤 5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `@test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) | +| 步骤 6 | §4.1.2 CCM(scatter + section + 门禁宏) | map 显示 `RW_CCM`;关 `APP_CCM_ENABLE` 也能编译通过 | ⏸ **暂缓(2026-10-15 裁定)**;已完成部分仅 `RW_CCM` + 堆迁入(09-21),缓冲 section 迁移与门禁宏不再排期 | +| 步骤 7 | §4.1.3 栈缩减(按步骤 3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 | ⏸ **暂缓(2026-10-15 裁定)**(目标值已按 09-21 实测修正,重开时可直接用) | +| 步骤 8 | §4.2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` | ⏸ **暂缓(2026-10-15 裁定)**;⚠ 2026-10-12 B5 反向扩容至 56 KB,重开时须重跑 `stack_report.py` + 上板 `min_ever` | +| 步骤 9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) | 每步都必须 `@build` 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 `simulator` 先过一遍再上实机。 diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index 245f9054..ba36afed 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -1,7 +1,7 @@ # RAM 预算台账(配额 · 趋势 · 门禁) > **定位**:`docs/内存管理/RAM使用分析与优化方案.md` §8.2 模板与 -> 内存治理三件套的**落地件**(2026-10-12,登记簿 N-27 前半)。 +> 内存治理三件套的**落地件**(2026-10-12,登记簿条目「缩堆迁 CCM 后半(暂缓)」的已落地前半)。 > 回答三个问题:**每个子系统还能用多少 RAM**(配额)、**总量趋势往哪走**(基线 TSV)、 > **超了怎么拦**(92% 门禁)。 > @@ -32,7 +32,7 @@ - **门禁**:pre-commit `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)—— RW_IRAM1 / RW_CCM 任一 >92% 即拦提交,并打印 Top-10 RW+ZI 对象定位增量; - 无 `.map` 时自适应跳过(CI 盲区登记在 `docs/任务规划/测试与架构专项.md` A-5 残余)。 + 无 `.map` 时自适应跳过(CI 盲区登记在 `docs/任务规划/测试与架构专项.md`「体积门禁 CI 强制路径」残余)。 - **阈值依据**:92% 由 2026-09 体系评审提出、2026-10-12 落地为门禁(提出时的报告条目已随销项删除,闭环说明见 `RAM使用分析与优化方案.md` §4 阶段 3「长效机制」与本文)——留 ~8% 给突发缓冲与栈抬升, 触线即触发排期,而不是静默逼近溢出。 - **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `@build`)。 @@ -56,9 +56,9 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名 **现状判读(2026-10-12 建档)**: -- **UI ⚠ 超配额 = 已知长期挂账,不是新增违规**:LVGL 池 16 KB(R-9 已于 2026-10-15 落地,由 40 KB 收缩)+ - 帧/远显缓冲 ≈12.8 KB,实测 30,412 B。配额 16 KB 描述的是 **R-2 把 UI 侧缓冲迁进 CCM 之后的目标态**, - 而 **R-2 后半已按用户裁定 2026-10-15 整簇暂缓**(`docs/任务规划/README.md` §2「预算与内存」已标暂缓)→ +- **UI ⚠ 超配额 = 已知长期挂账,不是新增违规**:LVGL 池 16 KB(2026-10-15 落地,由 40 KB 收缩)+ + 帧/远显缓冲 ≈12.8 KB,实测 30,412 B。配额 16 KB 描述的是 **把 UI 侧缓冲迁进 CCM 之后的目标态**, + 而 **该搬迁的后半已按用户裁定 2026-10-15 整簇暂缓**(`docs/任务规划/登记簿.md` §5 保留重启条件)→ **这条 ⚠ 会长期挂着**,判读时按已知挂账处理,别每轮当新问题追;重开条件 → `RAM使用分析与优化方案.md` §4。 - **RTOS 堆 = 配额本身**:56 KB 即 `configTOTAL_HEAP_SIZE`(2026-10-12 B5 由 48 KB 扩容,见 §6; `heap_4.o` 另含 32 B 元数据,不计超);**改堆 = 改配额**,必须先过本文件 §6 登记。 @@ -72,9 +72,9 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名 |---|---|---| | CCM 容量 | `RW_CCM` Max 见受管区 | scatter `RW_CCM`;**仅 CPU 可达,DMA 不可达**(RM0090 §3.1.1) | | 现占用 | 仅 FreeRTOS 堆 `heap_4`(56 KB,2026-09-21 迁入;2026-10-12 B5 扩容 48→56 KB) | 其余候选全部仍在 SRAM1 | -| 现余量 | 受管区 RW_CCM 容量 − 占用 = **7,904 B** | **不够** R-2 后半的 UI 侧待迁体量(R-9 后 **29,570 B**)——⏸ 该搬迁已暂缓,见下行 | -| R-2 后半解锁链 | ⏸ **整簇暂缓(用户裁定 2026-10-15)**,本行保留作重开时的现状基线:① **R-9 ✅ 2026-10-15**:`LV_MEM_SIZE` 40→16 KB → 待迁体量 54,146 → **29,570 B**(SRAM1 74.9% → **56.1%**,实测池峰值 6,568 B)→ ② ~~现在卡在这一步~~:堆 56→32 KB(`docs/内存管理/RAM使用分析与优化方案.md` §4.2.2(阶段 2 第 2 项"缩堆"),再腾 24 KB;⚠ 2026-10-12 B5 反向扩容至 56 KB——9 任务栈 + 队列实测需要,缩回 32 KB 前必须重跑 `stack_report.py` + 上板 min_ever 复测)→ ③ CCM 余 ≈31.9 KB ≥ 29,570 B → 迁入 | **暂缓期间不启动**;重开条件 → `RAM使用分析与优化方案.md` §4。**若重开,顺序不可颠倒**;每步前后跑 `stack_report.py` / `ram_report.py`(铁律) | -| 迁入前置 | 目标缓冲必须过 DMA 可达性清单(`内存内容分配策略.md`) | 违例 = 静默传错(N-11 同类坑);克隆芯片 CCM 可靠性风险见 `docs/内存管理/RAM使用分析与优化方案.md` §7 | +| 现余量 | 受管区 RW_CCM 容量 − 占用 = **7,904 B** | **不够** 迁 CCM 后半的 UI 侧待迁体量(缩池后 **29,570 B**)——⏸ 该搬迁已暂缓,见下行 | +| 迁 CCM 后半解锁链 | ⏸ **整簇暂缓(用户裁定 2026-10-15)**,本行保留作重开时的现状基线:① **缩池 ✅ 2026-10-15**:`LV_MEM_SIZE` 40→16 KB → 待迁体量 54,146 → **29,570 B**(SRAM1 74.9% → **56.1%**,实测池峰值 6,568 B)→ ② ~~现在卡在这一步~~:堆 56→32 KB(`docs/内存管理/RAM使用分析与优化方案.md` §4.2.2(阶段 2 第 2 项"缩堆"),再腾 24 KB;⚠ 2026-10-12 B5 反向扩容至 56 KB——9 任务栈 + 队列实测需要,缩回 32 KB 前必须重跑 `stack_report.py` + 上板 min_ever 复测)→ ③ CCM 余 ≈31.9 KB ≥ 29,570 B → 迁入 | **暂缓期间不启动**;重开条件 → `RAM使用分析与优化方案.md` §4。**若重开,顺序不可颠倒**;每步前后跑 `stack_report.py` / `ram_report.py`(铁律) | +| 迁入前置 | 目标缓冲必须过 DMA 可达性清单(`内存内容分配策略.md`) | 违例 = 静默传错(越界不校验的同类坑);克隆芯片 CCM 可靠性风险见 `docs/内存管理/RAM使用分析与优化方案.md` §7 | ## 4. 趋势基线(`scripts/ram_baseline.tsv`) @@ -94,5 +94,5 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名 | 日期 | 变更 | 原因 / 出处 | |---|---|---| | 2026-10-12 | 建档:三件套落地(本台账 + `scripts/ram_baseline.py` + `scripts/ram_baseline.tsv` + pre-commit `mem-budget` 钩子) | 登记簿 N-27 前半 | -| 2026-10-12 | **RTOS 堆配额 48 KB → 56 KB**(`QUOTAS` 同步) | B5 protectTask 立项:iec104Task(2.5KB)+protectTask(2KB)+TCB 入堆后 48KB 池 min_ever 预估跌破 3,072 B 告警线 → `.ioc` L1 参数 `configTOTAL_HEAP_SIZE` 49152→57344(CCM 水位 75.3%→87.9%,仍 <92% 门禁;CCM 空闲降至 ≈8 KB)。连带影响见 §3 R-2 后半链注记 | +| 2026-10-12 | **RTOS 堆配额 48 KB → 56 KB**(`QUOTAS` 同步) | B5 protectTask 立项:iec104Task(2.5KB)+protectTask(2KB)+TCB 入堆后 48KB 池 min_ever 预估跌破 3,072 B 告警线 → `.ioc` L1 参数 `configTOTAL_HEAP_SIZE` 49152→57344(CCM 水位 75.3%→87.9%,仍 <92% 门禁;CCM 空闲降至 ≈8 KB)。连带影响见 §3 迁 CCM 后半链注记 | | 2026-10-12 | **任务栈 +1536 B(堆内用量,配额 56 KB 不变)**:defaultTask 1024→1280 words(+1024 B)、timeSyncTask 512→640 words(+512 B);产品任务栈合计 37,632→39,168 B | 登记簿 N-33 栈收口:B7 定值持久化把 defaultTask 静态链推到 3016 B(余 1080 < 30% 线)、timeSyncTask 链 1264 B(余 784 < ISR 底线 1024);B6 台架背书 min_ever=10000 → 抬后实测 `heap free=16400 / min_ever=8464`(与预算 −1536 完全吻合,>3072 告警线);`stack_report` 9 任务 0 FAIL + `@test` PASS,见 `RAM使用分析与优化方案.md` §1.4 | diff --git a/docs/内存管理/内存内容分配策略.md b/docs/内存管理/内存内容分配策略.md index d64ef180..dd5184db 100644 --- a/docs/内存管理/内存内容分配策略.md +++ b/docs/内存管理/内存内容分配策略.md @@ -2,7 +2,7 @@ > 版本:v1.0 日期:2026-09-21 > 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2,Keil MDK v5 / ARMCC) -> 状态:**当前有效策略**(台架验证:产品构建 gen68 干净启动 61 行;存储套件 20/20 PASSED;恢复跑 3/3 PASSED) +> 状态:**当前有效策略**(台架验证:产品构建台架日志代次 68 干净启动 61 行;存储套件 20/20 PASSED;恢复跑 3/3 PASSED) > 关联文档:`RAM使用分析与优化方案.md`(**内存管理主文档**:容量审计 + 优化方案 + 观测工具 + 基线数据) > 布局实现:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct` @@ -91,7 +91,7 @@ CCM 64KB 只能容纳 {FreeRTOS 堆 48KB} 与 {LVGL 集合 ~52.5KB} 之一(两 1. **致命类优先**:方案 B 把 48KB 调度内存放回 SRAM,与全部潜在溢出源(含 FatFS、dhara 两个有实证事故史的第三方组件)同处一域;本项目实证过的故障族正是 1 字节类越界。 2. **DMA 不可达对堆是防护**:任何 DMA 野地址/错地址传输都无法落入 CCM(静默不落地,发起方表现为可检测的"数据未到"),调度器因此免疫 DMA 类故障;方案 B 中该防护失效。 3. **LVGL 残余风险已被现有手段兜住**:32B 保护带 + canary 实证 + `configCHECK_FOR_STACK_OVERFLOW=2` + malloc 失败钩子;已发生的 1 字节越界被完整 containment。 -4. **"UI bug 最多弄坏屏幕、弄不死系统"是台架既成事实**:越界发生时 CCM 中的调度器毫发无伤(gen68 干净启动、存储套件 20/20),这正是方案 A 的直接收益。 +4. **"UI bug 最多弄坏屏幕、弄不死系统"是台架既成事实**:越界发生时 CCM 中的调度器毫发无伤(台架日志代次 68 干净启动、存储套件 20/20),这正是方案 A 的直接收益。 ### 5.4 方案 B 的胜出点(如实记录,不足以翻盘) @@ -107,7 +107,7 @@ CCM 64KB 只能容纳 {FreeRTOS 堆 48KB} 与 {LVGL 集合 ~52.5KB} 之一(两 ### 5.6 若未来确需改判 -触发条件:测试构建 SRAM 余量不足,**且**已耗尽 `RAM使用分析与优化方案.md` 的 R-4 帧副本去重(−6.4~9.6KB)与 LVGL 池缩减等低代价手段。 +触发条件:测试构建 SRAM 余量不足,**且**已耗尽 `RAM使用分析与优化方案.md` 的帧副本去重(−6.4~9.6KB)与 LVGL 池缩减等低代价手段。 流程:改 scatter → 走 §7 完整验证阶梯 → 同步更新本文档、`AGENTS.md`、`.sct` 头注释。在此之前,方案 A 是唯一有效布局。 ## 6. 当前容量台账(2026-10-12,产品构建;2026-09-21 最坏构建=存储套件的历史基线见 `docs/内存管理/RAM预算台账.md` 趋势) diff --git a/docs/内存管理/存储栈RAM优化调研.md b/docs/内存管理/存储栈RAM优化调研.md index cf7c9664..02d2e63d 100644 --- a/docs/内存管理/存储栈RAM优化调研.md +++ b/docs/内存管理/存储栈RAM优化调研.md @@ -1,17 +1,17 @@ # 存储栈 RAM 优化调研 -> 版本 v1.2 日期 2026-10-23(v1.2:新增 §5 页缓冲可合并性论证,承接报告的 N-15) 作者 AI 辅助调研 +> 版本 v1.2 日期 2026-10-23(v1.2:新增 §5 页缓冲可合并性论证,承接原报告那条待评估项) 作者 AI 辅助调研 > 数据来源:`data/ram_report_20260921.md`(.map 静态占用)+ 逐行代码核验(`nand_ftl.c` / `gd5f2gq5ue.c` / `Lib/dhara` / `Lib/FatFs/ffconf.h` / `lftpd*`) > 定位:`RAM使用分析与优化方案.md` 的存储专项补充;优化项编号 P0/P1/P2,与 `docs/内存管理/RAM使用分析与优化方案.md` §6 的 S 序号在"规划"一节对齐 -> v1.1 清理(2026-09-21 同日):移除已落地的 P0-1 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05),以及过期逐模块基线表;仅保留未实施项与回归要求 -> ⏸ **实施状态(用户裁定 2026-10-15)**:本文**全部未实施项整簇暂缓、不再排期**——P0-2 页缓冲合并(= 报告 R-3)、P1-5 ①② 迁 CCM、以及依赖"堆 48K→32K"的第三步。当前没有功能在等这块预算,而搬迁要逐条过 DMA 可达性清单(违例是静默传错不是报错)。**保持常开**的是测量与门禁面(`ram_report.py` / `stack_report.py` / `mem-budget` 92% 门禁 / 台账 / 趋势)。理由与重开条件 → `RAM使用分析与优化方案.md` §4;本文保留作重开时的实施依据,**不代表现在要做**。 +> v1.1 清理(2026-09-21 同日):移除已落地的 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05),以及过期逐模块基线表;仅保留未实施项与回归要求 +> ⏸ **实施状态(用户裁定 2026-10-15)**:本文**全部未实施项整簇暂缓、不再排期**——`s_copy_buf` 分块(原报告的同一评估项)、`APP_CCM_ENABLE` 的 ①② 迁 CCM、以及依赖"堆 48K→32K"的第三步。当前没有功能在等这块预算,而搬迁要逐条过 DMA 可达性清单(违例是静默传错不是报错)。**保持常开**的是测量与门禁面(`ram_report.py` / `stack_report.py` / `mem-budget` 92% 门禁 / 台账 / 趋势)。理由与重开条件 → `RAM使用分析与优化方案.md` §4;本文保留作重开时的实施依据,**不代表现在要做**。 ## 1. 现状约束(不变,是下述全部方案的依据) - 存储栈静态 RAM 全在 **RW_IRAM1(主 SRAM 128KB)**;页缓冲(nand_ftl 3×2KB + gd5f 页缓冲/位图)走 SPI1 DMA(阈值 >1024B),**DMA1/DMA2 物理不可达 CCM** → 不能直迁 CCM(唯一途径:迁移 + `gd5f_dma_buf_ok()` 运行时守卫自动回落 CPU 轮询(2026-09-21 加固),不会静默失败,代价仅每页零点几 ms CPU) -- 3×2KB 页缓冲是结构成本不可直缩:dhara journal 要求整页 checkpoint(`journal.c:196` 整页 memset);512B 扇区落 2KB 页必须整页 RMW;可动的只有 `s_copy_buf`(→ P0-2) -- CPU-only 缓冲(lftpd static ~3.6K / cfg_file 0.7K / FATFS win 0.6K / lftpd_io 0.6K)迁 CCM 零代价(→ P1-5①) -- 栈上 FIL 重复占深链:syslog 两处、cfg 存储两处、开机探针各一个局部 `FIL`(每个含 512B 私有 buf,≈552B),全压在 defaultTask 最坏链 2,776B 内(→ P1-4 靶点) +- 3×2KB 页缓冲是结构成本不可直缩:dhara journal 要求整页 checkpoint(`journal.c:196` 整页 memset);512B 扇区落 2KB 页必须整页 RMW;可动的只有 `s_copy_buf`(见 §2 分块方案) +- CPU-only 缓冲(lftpd static ~3.6K / cfg_file 0.7K / FATFS win 0.6K / lftpd_io 0.6K)迁 CCM 零代价(见 §2 迁 CCM 方案 ①) +- 栈上 FIL 重复占深链:syslog 两处、cfg 存储两处、开机探针各一个局部 `FIL`(每个含 512B 私有 buf,≈552B),全压在 defaultTask 最坏链 2,776B 内(靶点见 §2 `FF_FS_TINY` 行) - FatFs 配置已低 RAM 取向:`FF_MAX_SS=512`、`FF_USE_LFN=0`、`FF_VOLUMES=1`、`FF_FS_LOCK=0`;`FF_FS_REENTRANT=1` 为 defaultTask/ftpTask 并发访问必需,勿动 - 教训(2026-09-21):任何比对式校验必须在**两份独立副本**间进行(U-Boot `sf test` 双缓冲形态)——旧 GC 拷贝自比对缺陷已随回读校验整体移除而消失;若未来测试构建恢复回读诊断,勿重蹈此坑 @@ -19,13 +19,13 @@ | # | 方案 | 省 RAM | 区域 | 风险 | 验证 | |---|---|---:|---|---|---| -| P0-2 | `s_copy_buf` 2048→512:GC 拷贝改分段 read→program_load(`program_load`/`read_from_cache` 支持列地址);纯 RAM 优化(GC 拷贝暂存减半) | **−1,536B(可选,不计入当前预期)** | SRAM1 | 低(仅动拷贝机制) | 待 RAM 余量需要时再议 | -| P0-3 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `@build` + FTP 上传/下载/LIST 闭环 + `@test` | -| P1-4 | `FF_FS_TINY=1`:删每 FIL 私有的 512B buf,文件数据走 `FatFs->win` 共享窗 | 静态 **−528B**;且栈上每处 FIL 峰值 **−528B**(defaultTask 最坏链 2,776→~2,250,实测峰 2,136→~1,600) | SRAM1 + CCM 栈 | 中(所有文件 I/O 经 512B 窗 bounce,FTP 吞吐 −20~40%;LIST 与文件读交错只影响速度) | 存储套件 + FTP 吞吐实测 + 日志/配置闭环;**与 S7 缩栈捆绑**(直接支撑 defaultTask 降档) | -| P1-5 | `APP_CCM_ENABLE` 门(与 S6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `@test` + FTP 吞吐 | -| P2-6 | NAND copy-back 命令(0x15→0x10)替代主机搬页:省 `s_copy_buf` 的拷贝用途 + 减半 SPI 流量 | −512B(P0-2 后) | SRAM1 | 高(片上 ECC 在 copy-back 下的语义须台架实证) | 仅评估,暂缓 | -| P2-7 | lftpd 数据块 2×1024→512 | −1,024B | SRAM1 | 吞吐 −~30% | RAM 仍紧时才做 | -| P2-8 | syslog/cfg 共享一个静态 FIL(同任务串行) | 净≈0(静态+528/栈−528) | — | 不推荐:P1-4 已覆盖同一收益面 | — | +| `s_copy_buf` 分块 | `s_copy_buf` 2048→512:GC 拷贝改分段 read→program_load(`program_load`/`read_from_cache` 支持列地址);纯 RAM 优化(GC 拷贝暂存减半) | **−1,536B(可选,不计入当前预期)** | SRAM1 | 低(仅动拷贝机制) | 待 RAM 余量需要时再议 | +| lftpd 缓冲收紧 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `@build` + FTP 上传/下载/LIST 闭环 + `@test` | +| `FF_FS_TINY` 共享窗 | `FF_FS_TINY=1`:删每 FIL 私有的 512B buf,文件数据走 `FatFs->win` 共享窗 | 静态 **−528B**;且栈上每处 FIL 峰值 **−528B**(defaultTask 最坏链 2,776→~2,250,实测峰 2,136→~1,600) | SRAM1 + CCM 栈 | 中(所有文件 I/O 经 512B 窗 bounce,FTP 吞吐 −20~40%;LIST 与文件读交错只影响速度) | 存储套件 + FTP 吞吐实测 + 日志/配置闭环;**与 RAM方案 实施步骤 7 的缩栈捆绑**(直接支撑 defaultTask 降档) | +| `APP_CCM_ENABLE` 迁 CCM | `APP_CCM_ENABLE` 门(与 RAM方案 实施步骤 6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `@test` + FTP 吞吐 | +| NAND copy-back | NAND copy-back 命令(0x15→0x10)替代主机搬页:省 `s_copy_buf` 的拷贝用途 + 减半 SPI 流量 | −512B(`s_copy_buf` 分块后) | SRAM1 | 高(片上 ECC 在 copy-back 下的语义须台架实证) | 仅评估,暂缓 | +| lftpd 数据块减半 | lftpd 数据块 2×1024→512 | −1,024B | SRAM1 | 吞吐 −~30% | RAM 仍紧时才做 | +| 共享静态 FIL | syslog/cfg 共享一个静态 FIL(同任务串行) | 净≈0(静态+528/栈−528) | — | 不推荐:`FF_FS_TINY` 那条已覆盖同一收益面 | — | **明确不做**:移除 dhara(丢掉电一致性与坏块重映射,代价不对称);journal checkpoint 分片(违反 dhara 协议);调 `gc_ratio`(无 RAM 收益);`FF_FS_REENTRANT=0`(defaultTask+ftpTask 并发访问 FatFs 必需)。 @@ -33,20 +33,20 @@ | 阶段 | 内容 | 收益 | 前置 | |---|---|---:|---| -| **第一步**(独立小批) | P0-3(P0-2 搁置,待 RAM 余量需要时再议) | **−1,024B** | 无 | -| **第二步**(并入 S7 缩栈批) | P1-4 `FF_FS_TINY=1` → 复跑 `stack_report.py` → defaultTask 降档(最坏链 ~2,250 支撑 3,584B 配置,余量 37%)+ 其余任务按 S7 目标 | −528B 静态 + 栈档下调释放 CCM 堆 | 第一步合入 | -| **第三步**(与 S6/S8 同批) | 先 S8 堆 48K→32K(CCM 释放 ~16K),再 P1-5 ①→② 分批迁 CCM | SRAM1 再 **−10.3K**;CCM 占用 +10.3K(由 S8 腾挪覆盖) | S8 或同批评估 CCM 水位 | +| **第一步**(独立小批) | lftpd 缓冲收紧(`s_copy_buf` 分块搁置,待 RAM 余量需要时再议) | **−1,024B** | 无 | +| **第二步**(并入 RAM方案 步骤 7 的缩栈批) | `FF_FS_TINY=1` → 复跑 `stack_report.py` → defaultTask 降档(最坏链 ~2,250 支撑 3,584B 配置,余量 37%)+ 其余任务按该步骤 7 目标 | −528B 静态 + 栈档下调释放 CCM 堆 | 第一步合入 | +| **第三步**(与 RAM方案 步骤 6/8 同批) | 先做步骤 8 的堆 48K→32K(CCM 释放 ~16K),再按迁 CCM 方案 ①→② 分批 | SRAM1 再 **−10.3K**;CCM 占用 +10.3K(由该步腾挪覆盖) | 与步骤 8 同批或同批评估 CCM 水位 | -**全部落地后存储栈预期**:SRAM1 常驻降至 **~1.9KB**(现 13.7KB,其中 P0-1 已省 1,776B 不重复计;余下仅小状态量),页缓冲驻 CCM(轮询),未实施项合计换回 SRAM1 ≈ **−11.9K**(P0-2 搁置,其 −1,536B 不计);叠加 S5(LVGL 40K→16K)后 SRAM1 余量 >65KB。 +**全部落地后存储栈预期**:SRAM1 常驻降至 **~1.9KB**(现 13.7KB,其中 BBT 页缓冲收缩已省 1,776B 不重复计;余下仅小状态量),页缓冲驻 CCM(轮询),未实施项合计换回 SRAM1 ≈ **−11.9K**(`s_copy_buf` 分块搁置,其 −1,536B 不计);叠加 RAM方案 步骤 5(LVGL 40K→16K)后 SRAM1 余量 >65KB。 ## 4. 回归与基线 -- **FTP 吞吐基线(2026-09-21 实测,4MB 文件,端到端含网络;瓶颈在存储侧,100M 网络非瓶颈)**:STOR **126.9 KB/s**(32.3s)/ RETR **321.4 KB/s**(12.7s),回传逐字节 MATCH(测试文件已自清理)→ P0-3/P1-4 前后各留一轮实测对比(P1-4 预期吞吐降幅 20~40%) -- P1-4 前后各跑一次 `stack_report.py` + `ram_report.py`(AGENTS.md 门禁) -- P1-5 的 ②(页缓冲入 CCM 走轮询)须以实测吞吐定去留:若 NAND 读吞吐降幅 >30%,则只迁 CPU-only 侧(①) -- P0-2/P2-x:待 RAM 余量需要时再议 +- **FTP 吞吐基线(2026-09-21 实测,4MB 文件,端到端含网络;瓶颈在存储侧,100M 网络非瓶颈)**:STOR **126.9 KB/s**(32.3s)/ RETR **321.4 KB/s**(12.7s),回传逐字节 MATCH(测试文件已自清理)→ 两条 FTP 相关方案前后各留一轮实测对比(`FF_FS_TINY` 那条预期吞吐降幅 20~40%) +- `FF_FS_TINY` 前后各跑一次 `stack_report.py` + `ram_report.py`(AGENTS.md 门禁) +- 迁 CCM 方案的 ②(页缓冲入 CCM 走轮询)须以实测吞吐定去留:若 NAND 读吞吐降幅 >30%,则只迁 CPU-only 侧(①) +- `s_copy_buf` 分块与 copy-back、数据块减半等小项:待 RAM 余量需要时再议 - 下次存储改动照常执行台架破坏性套件 + 掉电 resume -- 基线数字可用 `data/ram_report_20260921.md`(P0-1 前快照)复核;实施后重生成基线报告并回填总方案 +- 基线数字可用 `data/ram_report_20260921.md`(BBT 页缓冲收缩前快照)复核;实施后重生成基线报告并回填总方案 ## 5. 页缓冲可合并性论证(2026-10-23) @@ -55,7 +55,7 @@ > (该注释已在本轮改为指向本节)。 > 结论:**三块不能合并**(最坏路径上同时存活,合并后的失败形态是**静默数据损坏**,不是报错); > 可削减的只有 `s_copy_buf` 一块,上限 −2,048 B,而**收益落点不是红线区** → -> 原报告的 N-15 由"待评估"改为"**已评估、判定不做**"(报告已删除,结论以本节为准), +> 原报告的该项由"待评估"改为"**已评估、判定不做**"(报告已删除,结论以本节为准), > 与本文件 §2 那两条残余方案一起留在暂缓簇里。 ### 5.1 6,216 B 的构成(`.map` 实证) @@ -87,7 +87,7 @@ 这是**跨调用存活的状态**,不是暂存。 于是第 4 步执行时三块同时持有活数据:待写的缓存页、已攒的 checkpoint 元数据、搬页暂存。 -任取两块合并,被覆盖的一方都会变成垃圾,而**没有一处会报错**——这与 E-31 / N-21 两次 +任取两块合并,被覆盖的一方都会变成垃圾,而**没有一处会报错**——这与越界校验、挂载失败处置两次 "宁可报错,不可静默"的教训同族,属于必须回避的失败形态。 ### 5.3 逐对否证 @@ -116,7 +116,7 @@ - 三块页缓冲全在 `RW_IRAM1`(0x2000_0000 窗口内,`gd5f_dma_buf_ok()` 白名单覆盖),该区 2026-10-23 实测 **75,264 / 57.4%,余 55,808 B**; - 红线在 **`RW_CCM`:57,376 / 87.9%,余 7,904 B**(FreeRTOS 堆 56 KB 吃掉了它); -- 所以这条线即使做满(−2,048 B)也**一分不减红线**;反过来把页缓冲搬进 CCM(本文件 §2 的 P1-5②) +- 所以这条线即使做满(−2,048 B)也**一分不减红线**;反过来把页缓冲搬进 CCM(本文件 §2 的迁 CCM 方案 ②) 才是"对红线有害"的方向——6,144 B 会吃掉 CCM 余量的 78%; - 把 `s_copy_buf` 挪到栈上同理:defaultTask 静态最坏链 3,024 B / 5,120 B,再塞 2 KB 直接顶到上限, 而任务栈本身就在 CCM。 @@ -133,8 +133,8 @@ grep -n "s_page_buf\|s_cache_buf\|s_copy_buf" MDK-ARM/STM32F407-Demo/STM32F407-D ### 5.7 顺带订正的两处注释 -- `nand_ftl.c:300` 原写"页缓冲三块不可合并的评估见报告 R-3",而报告里此前**没有**这份评估 +- `nand_ftl.c:300` 原写"页缓冲三块不可合并的评估见报告",而报告里此前**没有**这份评估 → 改为指向本节。 - `nand_ftl.c:405` 原写"`s_page_buf` 现仅装 dhara meta 132 B 走轮询",**不成立**:`s_page_buf` 是 按整页编程的(`journal.c:992` → `dhara_nand_prog` → `gd5f_program_load(0, data, 2048)`), - 与另两块一样走 DMA。原文这句话会误导后来做 P1-5②(迁 CCM)的人,已按实况改写。 + 与另两块一样走 DMA。原文这句话会误导后来做迁 CCM ②的人,已按实况改写。 diff --git a/docs/子系统设计/ADC采样节拍设计说明.md b/docs/子系统设计/ADC采样节拍设计说明.md index 3fca4f2a..c23b82ef 100644 --- a/docs/子系统设计/ADC采样节拍设计说明.md +++ b/docs/子系统设计/ADC采样节拍设计说明.md @@ -320,7 +320,7 @@ t = 1000 µs 下一个更新事件 | 项 | 现状 | 说明 / 出口 | |---|---|---| -| **8 路电气量分属两片转换器** | 跨转换器**没有共同采样触发点** → 无同一时刻性、相位不确定 | **V0.1 硬件缺陷**(登记簿 N-18):V0.1 上**禁止**跨转换器做相位 / 功率 / 功率因数 / 矢量运算;根治要改板(8 路全接 TPAFE) | +| **8 路电气量分属两片转换器** | 跨转换器**没有共同采样触发点** → 无同一时刻性、相位不确定 | **V0.1 硬件缺陷**(登记簿条目「双转换器无共同采样触发」):V0.1 上**禁止**跨转换器做相位 / 功率 / 功率因数 / 矢量运算;根治要改板(8 路全接 TPAFE) | | STM32 侧数据滞后 | 固定 1 拍(1 ms) | 乒乓半区的结构代价;因上一条,不新增误差(§4.4) | | TPAFE 侧仍是"脉冲 + 中断读" | ISR 内 GPIO 脉冲、`BUSY` EXTI 逐通道读 8 次,**未 DMA 化** | 单点成本约 0.5 µs,非当前瓶颈;DMA / FMC 化的**前提未定**:接口类型与 `DRDY`、读时序须先用逻辑分析仪确认 | | 等周波调频 | **硬件侧已具备**:1 µs 分辨率 + ARR 预装载 + 唤醒与生产同源 → 运行期 `__HAL_TIM_SET_AUTORELOAD(&htim3, 周期µs - 1)` 即可无毛刺改频,**消费节拍自动跟随** | 采样率随动到相量窗与定值不在本层;测频见 `App/algo/freq.c`,上位需求见 `docs/算法/继电保护功能复现规划.md` §3.1 | diff --git a/docs/子系统设计/IEC104上行设计说明.md b/docs/子系统设计/IEC104上行设计说明.md index 75972421..4a113d39 100644 --- a/docs/子系统设计/IEC104上行设计说明.md +++ b/docs/子系统设计/IEC104上行设计说明.md @@ -3,7 +3,7 @@ > **定位**:DTU 上行规约 IEC 60870-5-104(DL/T 634.5104-2009)的设计基线—— > 范围、点表、链路流程、板端接入、预算与测试。规格来源 > `docs/算法/继电保护功能复现规划.md` §1.5(B0 产品定义,验收 = 省电科院点表); -> 代码落点 `App/protocol/iec104/`;未闭环的验收项挂 `docs/任务规划/采集保护收尾.md`(B10 送检)。 +> 代码落点 `App/protocol/iec104/`;未闭环的送检验收挂 `docs/任务规划/登记簿.md` §7。 > **角色**:本 DTU = **被控站(受控端)**,104 **TCP 服务器,端口 2404**;主站 > (控制端,点表参考 `192.168.1.199`)主动连入。 @@ -18,7 +18,7 @@ | 定值 | C_RD 读 + C_SE_NC 两段写(地址 = prot_set `comm_addr`) | 定值区切换/下装文件 | | 其他 | C_CS 对时(→ `sys_clock`)、C_TS、C_RP | 固有参数 8001H~800AH(字符串类)、C_CI 电度、文件传输(录波) | -## 2. 分层与模块(A-4 约束 1:协议解析与硬件 I/O 分离) +## 2. 分层与模块(采集链路测试可验证性约束:协议解析与硬件 I/O 分离) | 模块 | 文件 | 职责 | I/O | |---|---|---|---| @@ -37,7 +37,7 @@ | IOA | 点 | 来源 | V0.1 状态 | |---|---|---|---| | 0x4011 | 1Ua | L2 遥测快照槽 `ADC_SEM_UA`(卡 9) | ✅ 有效 | -| 0x4012/0x4013 | 1Ub/1Uc | 同表槽位;STM32 侧削顶(`docs/算法/继电保护功能复现规划.md` §1.6) | ❌ 发 IV——**品质驱动**(快照 CLIPPED 位映射,非点表绑死;V0.2/N-18 消失后零改动自动启用) | +| 0x4012/0x4013 | 1Ub/1Uc | 同表槽位;STM32 侧削顶(`docs/算法/继电保护功能复现规划.md` §1.6) | ❌ 发 IV——**品质驱动**(快照 CLIPPED 位映射,非点表绑死;V0.2 消除双转换器无共同采样触发后零改动自动启用) | | 0x4014 | 1U0 | 快照槽 `ADC_SEM_UN`(外部零序专用通道) | ✅ 有效 | | 0x4015 | 1Ia | 快照槽 `ADC_SEM_IA` | ✅ 有效 | | 0x4016~0x4018 | 1Ib/1Ic/1I0 | 同 Ub/Uc:绑槽 + 品质驱动 | ❌ 发 IV | @@ -169,11 +169,11 @@ - **门控 + 兜底**:`net_listener_armed`/`net_can_read`(纯 RAM 读)省无谓 RPC 往返 (门控不用只认 ESTABLISHED 的 `net_can_accept`——那会把监听 服役窗口全挡掉,CONNECT 事件补 accept 探针从不运行); - 每 32 轮无条件 recv 一次防 RECV_OK 事件未收到(ui_net_sink I-1/I-2 先例)。 + 每 32 轮无条件 recv 一次防 RECV_OK 事件未收到(沿用 UI 推流侧「周期 recv 兜事件丢失」的先例)。 - **网络代次自愈**:`net_get_generation()` 变化 → 关旧 fd 重建监听(netTask NET_RECONFIG 重建路径的既有约定)。 - **主动拆链**(与 EOF 路径相对):协议故障(坏帧/N(R) 倒退/t1 超时)或发送连续 - 3 次失败(E-04 短写先例)→ 本端 `net_close` + 重建监听,促主站重连——违例时 + 3 次失败(短写先例)→ 本端 `net_close` + 重建监听,促主站重连——违例时 主站不会自行断开,必须本端断(time_sync 会话收尾先例,非芯片 auto_relisten 风暴路径)。 - **抢占策略修正(V0.1 如实登记)**:建档时定"后连优先",实现后确认单连接 socket 在连接态无法 accept(监听即数据通道),**V0.1 实为首到优先**——旧会话 diff --git a/docs/子系统设计/控制台单写者日志设计说明.md b/docs/子系统设计/控制台单写者日志设计说明.md index dc6be972..b5ea0fed 100644 --- a/docs/子系统设计/控制台单写者日志设计说明.md +++ b/docs/子系统设计/控制台单写者日志设计说明.md @@ -3,7 +3,7 @@ > **版本**:v1.5(已实施;修订史见文末「修订记录」) > **日期**:2026-10-24(v1.5;初版 v1.0 于 2026-09-25) > **状态**:已实施并验证——`@build` 0 错误 0 警告、`@test` 硬件闭环 PASS(2026-09-25,见 §10);设计 2026-09-24 加固实施(当时位于 `Src/usart.c` USER CODE 区),2026-09-25 实现迁出至 `App/log/`(HAL 生成文件不含应用代码);同日写者任务体自 `Src/freertos.c` 迁至 `App/task/default_task.c`(与其余 5 任务文件同址归位) -> **关联文档**:`项目架构总览.md` · `运行与自检记录设计说明.md`(姊妹模块:持久化日志,同一写者任务) · `测试说明/链路延迟测试说明.md`(D6 日志税 / S8 非阻塞日志,需求出处) · AGENTS.md「平台约束」 +> **关联文档**:`项目架构总览.md` · `运行与自检记录设计说明.md`(姊妹模块:持久化日志,同一写者任务) · `测试说明/链路延迟测试说明.md`(D6 日志税 / 步骤 8 非阻塞日志,需求出处) · AGENTS.md「平台约束」 ## 1. 背景:实测故障与根因 @@ -14,7 +14,7 @@ | F1 | 日志行字符级交错 | 崩溃前两行日志逐字符交错:`"[2026-[2026-0909-24 15:2-24 15:27:37:39]…"` | @test 必检标记可能假性丢失、日志不可读 | | F2 | HardFault(脏指针直接进入 UART) | BFAR=0x10010000(恰为 CCM 末尾+1),PC 落在 `HAL_UART_Transmit` 的缓冲读取处 | 崩溃上报路径自身再崩;被破坏的字符串指针被 UART 逐字节读取 | -前兆:2026-09-22 链路延迟测试实测**开机连续丢行**(多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行,~2s 自恢复;`链路延迟测试说明.md` §9.2)——同类竞态的温和形态,当时列为 S8 待办(日志非阻塞化)。 +前兆:2026-09-22 链路延迟测试实测**开机连续丢行**(多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行,~2s 自恢复;`链路延迟测试说明.md` §9.2)——同类竞态的温和形态,当时列为待办(日志非阻塞化)。 ### 1.2 根因分析(旧路径:`DBG_*` → `printf` → `fputc`) @@ -320,12 +320,13 @@ sequenceDiagram 没有任何任务真的死等,只是主循环周期被 drain 拉长到 450~500ms,而喂狗抽样是 100ms 节流的、IWDG 最坏容限仅 1.7s,间歇被拉长就触发停喂。**看日志会以为"系统很健康"**。 -**已做(缓解)**:单次 drain 250ms 预算(round-3)+ 发送故障后 300ms 冷却(round-6) -+ 超时判据换 DWT(round 8b)。 +**已做(缓解)**:单次 drain 250ms 预算(第 3 轮)+ 发送故障后 300ms 冷却(第 6 轮) ++ 超时判据换 DWT(第 8b 轮台架)。 -**未做(根治)**:让 USART1 为 **TX-only**(.ioc 中不再使能 RX / PA10 不作 USART1_RX), -从机制上消除 ORE。属 `.ioc` 结构性改动 + CubeMX 重生成,需单开一轮并复跑台架闭环 -→ 已登记为 `docs/任务规划/登记簿.md` **S-3**。 +**已裁定不做(2026-10-26 用户决定)**:让 USART1 变成**只发不收**(`.ioc` 里不使能 RX、PA10 不再复用为 +`USART1_RX`)确实能从机制上消除 ORE,但它是 `.ioc` 结构性改动 + CubeMX 重生成——要清空全部工作树、 +独占一轮、复跑台架闭环,代价不抵收益。**现行缓解即长期方案**(单次 drain 250 ms 预算 + 发送故障后 +300 ms 冷却 + DWT 时间源无关的超时判据)。机制根源继续记录在本节,将来若要重评不必重新踩一遍。 ## 13. 开放问题 @@ -334,31 +335,31 @@ sequenceDiagram | O1 | 未注册期裸发无互斥 | 启动窗口(内核启动 → attach)`dbg_console_write_raw` 无互斥 → 多任务并发会**逐字节交错**(非旧 `HAL_BUSY` 丢整行)。2026-10-10 已固化"周期任务用 `dbg_console_writer_ready()` 门控"纪律;若未来开机日志密度上升,可升级为"内核前静态待发行 + attach 后统一补发"或给裸发加轻量互斥 | | O2 | `HAL_UART_Transmit` 超时未计数 —— **已处置(2026-10-24,v1.5)** | 三本账已分离:`dbg_console_dropped()` = **真没落地**的数(缓冲满丢的一行,或没能确认送达而出环的一个块);`dbg_console_tx_fault()` = 外设**软复位自愈次数**(语义不变);新增 `dbg_console_tx_timeout()` = 写者排空时看到的**块超时次数**(TXE 与 TC 两条硬上界都算)。**口径边界**:裸发路径(内核前 / ISR / 两条告警行自身)的超时**不进**超时账——裸发没有「块」的概念,混进来读数失去意义。**尚未接线**:本读口暂无生产调用者;候选位置 `App/util/task_watchdog.c` 的 `STALL` 行是**被解析的证据行**,加字段要连解析端与台架判据一起改 → 列为后续项 | | O3 | `printf` 逐字节开销 | 遗留 `printf` 路径 = 逐字节临界区 + 逐字节事件置位;遗留调用点全部切 `DBG_*` 宏后开销自然消失(不改变机制) | -| O4 | **ORE 僵尸未根治** | §12:现仅有缓解(250ms 预算 / 300ms 冷却 / DWT 超时),机制根源是 USART1 使能了 RX。根治需 `.ioc` 改 TX-only + CubeMX 重生成 → 已登记 `docs/任务规划/登记簿.md` **S-3** | +| O4 | **ORE 僵尸未根治** | §12:现仅有缓解(250ms 预算 / 300ms 冷却 / DWT 超时),机制根源是 USART1 使能了 RX。根治 = `.ioc` 改只发不收 + CubeMX 重生成,**2026-10-26 用户裁定不做** → O4 是**长期现状**而非待办(见 §12) | ## 14. 相关文档 - `项目架构总览.md` —— 六层分层 / 启动时序 / 6 任务(写者任务与就绪屏障位置) - `运行与自检记录设计说明.md` —— sys_log 持久化日志(姊妹模块:同写者任务、不同通道) -- `测试说明/链路延迟测试说明.md` —— D6 日志税 / S8 非阻塞日志(需求出处与 09-22 丢行实证) +- `测试说明/链路延迟测试说明.md` —— D6 日志税 / 步骤 8 非阻塞日志(需求出处与 09-22 丢行实证) - `测试说明/自动化测试流程.md` —— `@test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义) - AGENTS.md —— 平台约束(内核前禁内核对象 / C 堆禁令 / 事件位语义 / 内存预算) -## 附录:N-2 × S8 合并评估(2026-10-25,道F 批 2 / task-18,零代码改动) +## 附录:日志静默观察项 × 步骤 8 合并评估(2026-10-25,道F 批 2 / task-18,零代码改动) -> 评估任务(`docs/任务规划/登记簿.md` N-2 行「残余动作:与 S8 合并评估」):N-2 -> (49s 全系统日志静默,2026-09-22 L3 台架世代号 181)与 S8(日志非阻塞化,需求 +> 评估任务(登记簿曾登记的**日志静默观察项**,已销项;残余动作是「与步骤 8 合并评估」):该观察项 +> (49s 全系统日志静默,2026-09-22 L3 台架世代号 181)与步骤 8(日志非阻塞化,需求 > 出处 = `docs/测试说明/链路延迟测试说明.md` §9.2/§10.2)的合并性价比判定。 > 评估纪律 = 结论即交付,不顺手重构;本节结论:**无需最小实现**(不新增代码)。 -### A.1 S8:目标已由「单写者形态」在**生产者侧**达成 +### A.1 步骤 8:目标已由「单写者形态」在**生产者侧**达成 -S8 要治的是阻塞式日志税(L10 实测 `logtax_line` **4402 µs/行**,`fputc` 逐字节 +步骤 8 要治的是阻塞式日志税(L10 实测 `logtax_line` **4402 µs/行**,`fputc` 逐字节 @115200 阻塞调用方整行)与并发丢行(实测 `docs/测试说明/链路延迟测试说明.md` §9.2: 必检标记被竞态吞)。2026-09-24 单写者 改造正是同一需求落的方案(§1/§2 G1-G5): -| S8 子项(需求出处 `docs/测试说明/链路延迟测试说明.md` §10.2 第 9 条) | 原案 | 现状(代码实证) | +| 步骤 8 子项(需求出处 `docs/测试说明/链路延迟测试说明.md` §10.2 第 9 条) | 原案 | 现状(代码实证) | |---|---|---| | `fputc` → 环形缓冲 | 串口 TX 环形 | ✅ 4KB BSS 环 + 事件位唤醒(§4.1/§4.2);生产者 = 亚微秒~µs 级临界区整行入环(≤192B 拷贝),日志税较 4402µs/行**降 3 个量级** | | TX 中断/DMA | 非阻塞发送 | ❌ 有意不做(轮询单写者,D4);复议时机见 A.2 | @@ -366,25 +367,24 @@ S8 要治的是阻塞式日志税(L10 实测 `logtax_line` **4402 µs/行**, | HardFault 冲刷 | 待处理 | ✅ 有界裸发 ≤256B(G4/§4.4,DWT/计数硬上界,不依赖 tick、零内核对象) | 热路径日志税另已由 D6 单独削掉(删 per-recv 日志,`queue_rt_recv` 5997→1223µs)。 -**结论:S8 按“生产者不阻塞业务”的意图已闭环**;剩余的“阻塞”只在写者任务 +**结论:步骤 8 按“生产者不阻塞业务”的意图已闭环**;剩余的“阻塞”只在写者任务 (defaultTask)自身——drain 预算 250ms + 故障冷却 300ms + DWT 硬上界(§11/§12), **不在业务任务关键链上**,且预算值按“让位 800ms 停喂容忍”定档,属设计内行为。 -### A.2 TX 中断/DMA(原案后半):维持否决,排序绑定 S-3 +### A.2 TX 中断/DMA(原案后半):维持否决 D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏;IT/DMA 引入新内核 对象、缓冲所有权/双缓冲复杂度、DMA 可达性清单)。三点补充判断: -1. 把 drain 拖长的真凶是 **ORE 僵尸通道**(登记簿 S-3:PA10 悬空 + RX 使能, - 软复位后毫秒级重锁 → 每轮块超时 100ms 级)——`.ioc` 改 TX-only(批 2.5 - 独占轮,CubeMX 重生成约束见 AGENTS)落地后该形态整体消失; +1. 把 drain 拖长的真凶是 **ORE 僵尸通道**(PA10 悬空 + RX 使能,软复位后毫秒级重锁 → 每轮块超时 + 100ms 级);根治办法(`.ioc` 改只发不收)已于 2026-10-26 裁定不做 → **该形态长期存在**,靠缓解措施兜住; 2. 根治前后数据已可机读:`dbg_console_tx_timeout()`(§13-O2 三本账,v1.5 起) 就是为“IT/DMA 还剩多少收益”留的量尺; -3. S-3 之前动 TX 路径 = 给即将被移除的场景写两遍代码。 +3. 根治既已裁定不做,TX 路径的形态就是终态:动它不再需要"等根治",但必须拿 `tx_timeout` 的实测收益说话。 -**结论:不采。复议触发条件 = S-3 落地后实测 `tx_timeout` 增量仍 > 0。** +**结论:不采。**原复议触发条件绑定根治项,已随 2026-10-26 裁定不做而失效;将来若再议 TX 中断/DMA,直接以 `dbg_console_tx_timeout()` 的实测增量为依据。 -### A.3 N-2:观察窗收敛到 V5 复跑;“真停滞 vs 无源”判别成立 +### A.3 日志静默观察项:观察窗收敛到 V5 复跑;“真停滞 vs 无源”判别成立 - 判据②(静默 → 可观测)= **已具备**:控制台三本账 `dbg_console_dropped / dbg_console_tx_fault / dbg_console_tx_timeout` + syslog 的 @@ -392,7 +392,7 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏 (`App/log/sys_log_mcu.c`);任何“静默丢行”现形为 `[ERR]` → `@test` FAIL。 - 判据①(复现 49s 并定位):V5 30min 未复现;实测(`docs/测试说明/实机测试实施指南.md` §7.2) 运行期 >10s 静默 24 处**全部落在 e02_probe 窗口、长度 ≈28s**——其 - 同源阻塞机制(`drop_conn` 同步占用 netTask 400-600ms)已随 N-17 修复 + 同源阻塞机制(`drop_conn` 同步占用 netTask 400-600ms)已随登记式拆链改造修复 (登记式拆链,`docs/网络/网络自愈架构设计.md` §4.6)。本评估补充一个此前 缺位的替代解释:**探针窗内的静默很可能本来就是“无日志源”的正常形态**—— 探针一轮(挂起 30s + 短暂复连)业务日志天然稀疏,defaultTask 无周期串口输出 @@ -402,19 +402,19 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏 丢弃账——账目零增量且静默后无 `[ERR] CONSOLE dropped` = **无源**(正常, 不开卡);账目有增量或出现 dropped 行 = **真停滞**(开卡,按 §12 ORE 与 冷却路径核对)。 -- **处置建议**:N-2 保留为低风险观察,至 N-17 修复版(merge 969fc5c)的 V5 +- **处置建议**:该观察项保留为低风险观察,至拆链修复版(merge 969fc5c)的 V5 复跑(判据 = 运行期 `[WD] task stall` 与非开机 `MISS start` 双零,台架由 - lead 排窗)达成后销项;本附录即销项结论落点。登记簿 N-2/N-17 行与波次行 + lead 排窗)达成后销项;本附录即销项结论落点。登记簿相应两行与波次行 的清理归单写者(lead)。 ### A.4 评估后的遗留面(全部既有,无新增) | 项 | 归属 | 处置 | |---|---|---| -| ORE 僵尸根治(TX-only) | 登记簿 S-3 / §12 / §13-O4 | 批 2.5 独占轮;落地后触发 A.2 复议 | +| ORE 僵尸根治(改只发不收) | §12 / §13-O4 | **2026-10-26 用户裁定不做**;量尺 `dbg_console_tx_timeout()` 保留,将来重评直接用 | | `tx_timeout` 读口接线(STALL 证据行加字段) | §13-O2 | 涉 `App/util/task_watchdog.c` 与被解析证据行的台架判据,非本卡写域;维持 O2“后续项” | | 未注册期裸发互斥 / `printf` 遗留开销 | §13-O1/O3 | 现状 = 纪律已落地,开机日志密度上升再议 | -| N-2 观察项 | 登记簿 §1 | 见 A.3:V5 复跑双零后销项(归 lead) | +| 日志静默观察项 | 登记簿曾登记(已销项) | 见 A.3:V5 复跑双零后销项(归 lead) | --- @@ -424,8 +424,8 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏 |------|------|------| | 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.2 | 2026-10-02 | 新增 §12 ORE 僵尸通道(台架日志代次 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 | | v1.4 | 2026-10-10 | 新增 `dbg_console_writer_ready()`(写者就绪查询)并固化"未注册期只有 defaultTask 记日志、周期任务用就绪门控"纪律;更正 §3.4 / §7 / §9-D5 / §13-O1 的"只丢不交错"旧结论(裸发已改寄存器级轮询、无 `gState` 互斥) | -| v1.6 | 2026-10-25 | 新增文末**附录:N-2 × S8 合并评估**(道F 批 2 / task-18,零代码):A.1 S8 已由单写者形态在生产者侧达成(4402µs/行 → µs 级入环);A.2 TX 中断/DMA 维持 D4 否决、复议绑定 S-3 落地后 `tx_timeout` 实测;A.3 N-2 判据②已具备、①给出「无源 vs 真停滞」判别口径与 V5 复跑销项条件;A.4 遗留面全部既有、无新增。§11 时间线同步 | +| v1.6 | 2026-10-25 | 新增文末**附录:日志静默观察项 × 步骤 8 合并评估**(道F 批 2 / task-18,零代码):A.1 步骤 8 已由单写者形态在生产者侧达成(4402µs/行 → µs 级入环);A.2 TX 中断/DMA 维持 D4 否决、复议绑定 S-3 落地后 `tx_timeout` 实测;A.3 该观察项判据②已具备、①给出「无源 vs 真停滞」判别口径与 V5 复跑销项条件;A.4 遗留面全部既有、无新增。§11 时间线同步 | | v1.5 | 2026-10-24 | **主机单测批次照出的四条现状全部处置**(生产码在 `App/log/console_log.c`,用例在 `test/host/console_log/`):① 冷却判据改用独立 `s_con_cooling` 标志,不再拿 `HAL_GetTick()` 的 0 值当哨兵——旧写法在 tick==0 那一拍(开机头 1 ms,或 49.7 天回绕到 0)冷却等于没记;② `con_append` 的空位判据 / 推进 head / 整行拷贝收进同一临界区——旧实现是临界区外 check-then-act,多生产者可在环 >95% 满时双双过判据把 head 推过容量 → 静默覆盖且**既不打 [ERR] 也不计 dropped**;③ `Instance == NULL`(未初始化)与僵尸通道**分诊**,未初始化绝不 reinit(旧路径会在 HAL 未就绪时经 MspInit 重配 RCC/GPIO);④ 三本账,见本文 §13-O2。**dropped 的单位口径变了**:一次 = 一行(缓冲满)或一个块(没能确认送达 / 通道不可用),故 `[ERR] CONSOLE dropped N lines` 的 N **不恒等于行数**(为兼容 `@test` 标记匹配,文案未改词)。本文 §3.1 与 §4.1 的「单生产者 vs 全上下文生产者」矛盾一并消除 | diff --git a/docs/子系统设计/看门狗设计说明.md b/docs/子系统设计/看门狗设计说明.md index cc5df461..208c37e5 100644 --- a/docs/子系统设计/看门狗设计说明.md +++ b/docs/子系统设计/看门狗设计说明.md @@ -6,13 +6,12 @@ > **日期**:2026-09-29 创建;**2026-10-10 按当前代码重写正文**(旧正文含大量立项前的 > 描述,已清理;实施史与逐次决策见 §10 与文末变更记录,未删)。 > **相关文档**: -> [`../任务规划/README.md`](../任务规划/README.md)(登记簿未办项 N-17 / S-3,见 `../任务规划/登记簿.md`)、 > [`运行与自检记录设计说明.md`](运行与自检记录设计说明.md)(复位记录与 `selftest.log`)、 > [`../测试说明/看门狗单元测试说明.md`](../测试说明/看门狗单元测试说明.md)(L0.5 判定状态机单测)、 > [`../测试说明/实机测试实施指南.md`](../测试说明/实机测试实施指南.md)(故障注入台架流程)、 > [`../架构设计/项目启动顺序管理.md`](../架构设计/项目启动顺序管理.md)(启动时序与就绪屏障)、 > [`../测试说明/ST-LINK_CLI_复位使用说明.md`](../测试说明/ST-LINK_CLI_复位使用说明.md)(复位语义)、 -> 《项目审查与优化建议报告》(N-03 的立项出处;该条已修复并按「修复即销项」从报告删除,**完整记录即本文**;报告本身 2026-10-25 已删除) +> 《项目审查与优化建议报告》(本模块缺口的立项出处;该条已修复并按「修复即销项」从报告删除,**完整记录即本文**;报告本身 2026-10-25 已删除) --- @@ -55,7 +54,7 @@ ### 1.3 立项前状态(历史,2026-09-29) -当时全库无 IWDG/WWDG 初始化、HAL IWDG 驱动源码不在仓库、`.ioc` 无看门狗条目、无复位原因读取——缺陷登记为 N-03(原登记于《项目审查与优化建议报告》,已修复并按销项删除正文)。**该节只作立项背景**:现状是 IWDG 已启用(`.ioc` + `HAL_IWDG_MODULE_ENABLED` + 驱动已挂载),复位原因每次开机都在上报。 +当时全库无 IWDG/WWDG 初始化、HAL IWDG 驱动源码不在仓库、`.ioc` 无看门狗条目、无复位原因读取——缺陷原登记于《项目审查与优化建议报告》(已修复并按销项删除正文)。**该节只作立项背景**:现状是 IWDG 已启用(`.ioc` + `HAL_IWDG_MODULE_ENABLED` + 驱动已挂载),复位原因每次开机都在上报。 --- @@ -387,9 +386,9 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 | 层 | 载体 | 开机读出并打印 | |---|---|---| -| round 7 | RAM(近 8 次抽样位图环、最大喂狗间隔、console tx_fault 计数) | 停喂时 `STALL snap=… req=… h=… mg=… cf=… seg=…`(裸 UART + `/log/wdev.log` 各一份) | -| round 8 | `.noinit` 槽 `s_wd_seg_trail[4]`(magic/段号/时刻/循环序号) | `PREVRUN seg=N seq=N t=ms`——直接指向 defaultTask 主循环最后进段 | -| round 8b | `.noinit` 槽 `s_wd_tick_cur/prev[4]`(uwTick + TIM7 中断计数) | `TICKRUN uw=… n=… duw=…`——判定 HAL 时基(TIM7)是否与停摆同刻冻结 | +| 第 7 轮 | RAM(近 8 次抽样位图环、最大喂狗间隔、console tx_fault 计数) | 停喂时 `STALL snap=… req=… h=… mg=… cf=… seg=…`(裸 UART + `/log/wdev.log` 各一份) | +| 第 8 轮 | `.noinit` 槽 `s_wd_seg_trail[4]`(magic/段号/时刻/循环序号) | `PREVRUN seg=N seq=N t=ms`——直接指向 defaultTask 主循环最后进段 | +| 第 8b 轮 | `.noinit` 槽 `s_wd_tick_cur/prev[4]`(uwTick + TIM7 中断计数) | `TICKRUN uw=… n=… duw=…`——判定 HAL 时基(TIM7)是否与停摆同刻冻结 | 要点: - `.noinit` 必须 `section(".noinit")` **与** `zero_init` 同时给,并配 scatter 的 `UNINIT` 执行区;只给 section 时 armcc 会当"带初值 RW"→ 启动 scatterload 抹掉保留内容。 @@ -435,13 +434,13 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 | V2 | **任务饿死**(netTask 插 `while(1)`) | ✅ **2026-10-03 闭环** | 800 ms 声明 `task stall: mask=0x00000001 req=0x00000037` → ≤2.5 s IWDG 复位(`reset cause: IWDG`,`RCC_CSR=0x24000000`)。首轮曾不复位,根因是证据落盘自身喂狗 → 加门闩(§5.3) | | V3 | 断言失败 | 🟡 未单独注入 | 宏与钩子已在用(`configASSERT` 全内核可达);行为经编译与代码路径核对,未做定向注入用例 | | V4 | **关中断死循环** | ✅ **2026-10-03 闭环** | `__disable_irq()` + `while(1)` → 每 ~4.9 s 稳定复位(20+ 周期,`IWDG`)→ 证实 IWDG 独立于中断 | -| V5 | **长期不误杀**(真实负载 ≥30 min) | ✅ **2026-10-03 闭环** | FTP 512 KB 上/下传 + `e02_probe` 探针 30 min → **120/120 次传输全 PASS、零 IWDG 复位**。遗留观察 44 次 stall(探针场景专属)→ 登记 **N-17** | +| V5 | **长期不误杀**(真实负载 ≥30 min) | ✅ **2026-10-03 闭环** | FTP 512 KB 上/下传 + `e02_probe` 探针 30 min → **120/120 次传输全 PASS、零 IWDG 复位**。遗留观察 44 次 stall(探针场景专属)→ 登记「探针窗口心跳缺口」 | | V6 | 测试套件共存 | 🟡 部分 | 2026-10-02 曾出现测试构建 IWDG 复位循环,已按 §3.5 掩码规则修复;未跑完整套件用例矩阵 | | V7 | 上电复位辨识 | 🟡 未单独注入 | `reset cause:` 每次开机都在打(POR 串已实现),未做专门断电对照 | | V8 | 引脚复位辨识 | ⏳ 未做 | 未做定向注入(`external pin (NRST)` 分支已实现) | -| U1 | 判定状态机主机侧单测 | ✅ 39/39 | `test/host/watchdog/run_unit_test.py`(L0.5,pre-commit `watchdog-unit-test`) | -| U2 | 门禁接入 | ✅ | 改 `App/util/task_watchdog*.{c,h}` 或 `test/host/watchdog/**` 触发 | -| S1 | 模拟器 `--watchdogtest` | ⏳ **已降级不做** | 位图与判定逻辑已由 U1 覆盖;模拟器不编译本模块 | +| 主机单测 | 判定状态机主机侧单测 | ✅ 39/39 | `test/host/watchdog/run_unit_test.py`(L0.5,pre-commit `watchdog-unit-test`) | +| 门禁触发 | 门禁接入 | ✅ | 改 `App/util/task_watchdog*.{c,h}` 或 `test/host/watchdog/**` 触发 | +| 模拟器 | `--watchdogtest` | ⏳ **已降级不做** | 位图与判定逻辑已由主机单测覆盖;模拟器不编译本模块 | 用例细节见 [`../测试说明/看门狗单元测试说明.md`](../测试说明/看门狗单元测试说明.md);故障注入流程见 [`../测试说明/实机测试实施指南.md`](../测试说明/实机测试实施指南.md) §4。 @@ -471,18 +470,17 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 |---|---|---| | R1 | **启动段无保护**(复位 → `task_wd_start()` 约数秒) | 接受;表现为"无输出 + LED 不闪",人工辨识。要覆盖需"短超时 + 手动多喂",复杂度收益比不划算 | | R2 | **LSI 精度差** | 已按 47 kHz 最坏值校验,停喂到复位仍留 ~900 ms | -| R3 | **喂狗节奏饥饿** | 控制台 drain 曾把主循环拉长导致误复位(2026-10-02 gen-288)。现缓解 = drain 单次 250 ms 预算 + 发送故障 300 ms 冷却;**结构根治(USART1 TX-only)未做** → 登记 **S-3** | +| R3 | **喂狗节奏饥饿** | 控制台 drain 曾把主循环拉长导致误复位(2026-10-02 台架日志代次 288)。现缓解 = drain 单次 250 ms 预算 + 发送故障 300 ms 冷却;**结构根治(USART1 改只发不收)2026-10-26 已裁定不做 → 现行缓解即长期方案**(机制详述 `控制台单写者日志设计说明.md` §12) | | R4 | **NAND 真实卡死时 FTL 直接喂狗** | 设备存活但存储功能冻结(与 ORE 自愈同类边界)。若要求"NAND 死也必须复位",需给 `nand_ftl_wd_feed()` 加次数/时限上限 | | R5 | **栈溢出与断言在复位原因上不可区分**(都是 `SFTRSTF`) | 可接受;串口分别打 `[ERR] STACK OVERFLOW` / `[ERR] ASSERT FAILED`,`selftest.log` 侧另有记录 | | R6 | **`RCC_CSR` 读后即清** | 已在 `main.c` 统一处理;漏清会让下次软复位读回上次标志 | -| R7 | **UI 推流的网络往返卡死不占心跳位**(产品掩码仍 `0x77`) | 刻意:往返卡死的根因在 netTask,netTask 已在掩码内、由它暴露;为"远端显示卡了"新增一位只会把观感问题升级成整机复位。UI 心跳已由 P1-4a 自产、不受推流影响;推流耗时看 `[PROF]` 的 `uis`/`uisn`。成因与判读见本文 **§11.3 第 2 条**;门控方案与残留暴露见 `../UI设计/RemoDispBus协议与接收设计说明.md` §3 | +| R7 | **UI 推流的网络往返卡死不占心跳位**(产品掩码仍 `0x77`) | 刻意:往返卡死的根因在 netTask,netTask 已在掩码内、由它暴露;为"远端显示卡了"新增一位只会把观感问题升级成整机复位。UI 心跳已由自身喂位自产、不受推流影响;推流耗时看 `[PROF]` 的 `uis`/`uisn`。成因与判读见本文 **§11.3 第 2 条**;门控方案与残留暴露见 `../UI设计/RemoDispBus协议与接收设计说明.md` §3 | ### 9.2 未办项(登记簿同步) -| 编号 | 内容 | 与本案关系 | +| 条目 | 内容 | 与本案关系 | |---|---|---| -| **N-17** | `time_sync` 30 s 挂起客户端掉线 + 监听重建期间 NET/FTP/UI 心跳缺口 800 ms~1 s(声明 stall 但未复位),**余量仅 ~0.7 s** | 待定位监听重建为何阻塞其它任务;必要时缩短重试 | -| **S-3** | USART1 TX-only 结构根治(灭 ORE 僵尸机制) | 是 R3 节奏饥饿的机制根源 | +| 探针窗口心跳缺口 | `time_sync` 30 s 挂起客户端掉线 + 监听重建期间 NET/FTP/UI 心跳缺口 800 ms~1 s(声明 stall 但未复位),**余量仅 ~0.7 s** | 待定位监听重建为何阻塞其它任务;必要时缩短重试 | | O1 | 是否需要复位计数与"连续 N 次看门狗复位后进入安全模式" | 当前不做;若现场出现复位风暴再评估 | | O2 | 细粒度复位原因是否上 `run.log`/UI | 倾向不上(避免中文字符集重生成),只保留串口 + `selftest.log` | @@ -552,7 +550,7 @@ python scripts/check_cubemx_survival.py --write # 确认改动无误后重建 --- -## 11. 任务级饥饿的诊断方法与实测基线(Task #32 沉淀,2026-10-10) +## 11. 任务级饥饿的诊断方法与实测基线(2026-10-10 结案沉淀) > **来源**:`[ERR][WD] task stall: mask=0x00000067 req=0x00000077`(缺 bit4 = `TASK_WD_UI`) > 的完整归因与处置。故障本身已结案——**累计 38 次上板启动 0 复现**(原 L2 基线 3/10), diff --git a/docs/子系统设计/网络配置说明.md b/docs/子系统设计/网络配置说明.md index ea3bc0df..0a511c1c 100644 --- a/docs/子系统设计/网络配置说明.md +++ b/docs/子系统设计/网络配置说明.md @@ -109,9 +109,9 @@ mac=00:00:00:00:00:00 INI 格式两端一致,文件可互读;mtime 变更令牌:MCU = FAT 打包时间(`fdate<<16\|ftime`,分钟级粒度),模拟器 = 宿主文件 mtime(秒级)。 -### 3.4 `[ftp]` 段:FTP 鉴权(2026-10-11 新增,N-19) +### 3.4 `[ftp]` 段:FTP 鉴权(2026-10-11 新增) -FTP 服务(lftpd,:21)自 N-19 起启用口令鉴权。配置与 `[network]` **同文件、同引擎**(`net_cfg_load()` 内统一注册,键表与工厂默认见 `Drivers/BSP/NETCFG/ftp_cfg.c/h`;鉴权状态机在 `Drivers/BSP/NET/lftpd/lftpd_auth.c/h`,纯函数、主机侧可测): +FTP 服务(lftpd,:21)自 2026-10-11 起启用口令鉴权。配置与 `[network]` **同文件、同引擎**(`net_cfg_load()` 内统一注册,键表与工厂默认见 `Drivers/BSP/NETCFG/ftp_cfg.c/h`;鉴权状态机在 `Drivers/BSP/NET/lftpd/lftpd_auth.c/h`,纯函数、主机侧可测): ```ini [ftp] diff --git a/docs/子系统设计/运行与自检记录设计说明.md b/docs/子系统设计/运行与自检记录设计说明.md index 40baee4b..910e21e9 100644 --- a/docs/子系统设计/运行与自检记录设计说明.md +++ b/docs/子系统设计/运行与自检记录设计说明.md @@ -2,7 +2,7 @@ > **版本**:v1.1(已实施) > **日期**:2026-09-14 -> **状态**:S1–S9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2) +> **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2) > **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg) ## 1. 背景与需求 @@ -140,20 +140,20 @@ min_ever=8464` 说明余量充足。回归由 **TC-SYSLOG-108**(投出去之 defaultTask(唯一写者,挂载成功后): 1) f_mkdir("/log")(幂等) 2) 排空启动前数组 + 队列 → 按文件合并落盘(一批最多 8 条:一次 - f_open FA_OPEN_APPEND → f_write(n×64B) → f_close,见下方 N-24 说明) + f_open FA_OPEN_APPEND → f_write(n×64B) → f_close,见下方「日志读写放大收口」说明) 3) 写"上电/重启"分类记录(§5.3,基于 run.log 末条 epoch) 4) 主循环:osMessageQueueGet(阻塞, 10s 超时) → 落盘;超时空转继续 LED 闪烁 ``` -- **N-24(2026-10-15,报告 N-16 写侧):一次排空合并成"每文件一次 open/write/close"**。旧实现每条记录一轮,而 `f_close` 要回写目录项、`f_write` 要对目标扇区做读-改-写 → **每条日志 2~4 次 NAND 页读写**。现在同一文件的一批(≤8 条 = 512 B 静态缓冲 `s_batch`)合并成一次追加写: - - **掉电语义不变**:批次仍在**同一次 `syslog_drain()` 调用内**写完并 `f_close`;`f_close` 内部 `sync_fs` → `disk_ioctl(CTRL_SYNC)` 会 `ftl_flush_cache()` 把 FTL 的单页写回缓存刷进 NAND(`Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 的 `CTRL_SYNC` 分支)。落盘时机与旧实现每条 close 等价,**批次不跨调用驻留 RAM**:"写完即关"的路径关文件即落盘,残余只到"单次写事务进行中掉电/复位"为止(`../任务规划/登记簿.md` N-22:无空闲/掉电前周期 flush,2026-10-22 用户裁定暂不做)。 +- **日志读写放大收口(2026-10-15,写侧):一次排空合并成"每文件一次 open/write/close"**。旧实现每条记录一轮,而 `f_close` 要回写目录项、`f_write` 要对目标扇区做读-改-写 → **每条日志 2~4 次 NAND 页读写**。现在同一文件的一批(≤8 条 = 512 B 静态缓冲 `s_batch`)合并成一次追加写: + - **掉电语义不变**:批次仍在**同一次 `syslog_drain()` 调用内**写完并 `f_close`;`f_close` 内部 `sync_fs` → `disk_ioctl(CTRL_SYNC)` 会 `ftl_flush_cache()` 把 FTL 的单页写回缓存刷进 NAND(`Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 的 `CTRL_SYNC` 分支)。落盘时机与旧实现每条 close 等价,**批次不跨调用驻留 RAM**:"写完即关"的路径关文件即落盘,残余只到"单次写事务进行中掉电/复位"为止(`../任务规划/登记簿.md` 条目「写回缓存空闲 flush」:无空闲/掉电前周期 flush,2026-10-22 用户裁定暂不做)。 - **条目不丢**:启动前数组按下标"看而不删",整批成功才删;队列条目在落盘失败(-2 卷忙 / -1 写失败 / -3 容量不足)时**回投队列**。 ⚠ **2026-10-24 更正本条**:旧实现里 -2 只在**按条慢路径**被正确回投,**批量路径根本没有 -2 分支**(`done` 保持 `n` → 回投循环空转)→ 卷忙时一批(≤8 条)既不落盘也不回投、静默丢失,而本节长期写着"条目不丢"替它背书。现已补 `rc == -2` 分支整批回投(不计故障、不打日志、保序),由 `test/host/sys_log_mcu/` 的 TC-SYSLOG-035 组逐条钉住。**真机必验**:FTP 传输期间持续投日志,传输结束后条数 == 投递条数、顺序不乱。 - **容量轮转仍只在按条慢路径做**:批量路径发现剩余容量装不下一批时返回 -3,退回逐条 `s_append()`("满了 unlink 重建"只保留一处实现,避免一批跨多轮重建)。 - 自愈重试(L1 丢弃重建 + 重试一次)与错误去重(L2 只报第一次)判据与单条路径一致。 - **自愈触发集分类(2026-10-24,代码侧唯一入口 `s_is_selfheal_code()`)**:只有"删了这个文件真能修好"的介质/结构类码才触发 unlink 重建——`FR_DISK_ERR` / `FR_INT_ERR` / `FR_NO_FILESYSTEM` / `FR_DENIED`;**名字/参数类严禁归入**(`FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT`,另显式排除 `FR_NOT_READY` / `FR_NOT_ENABLED` / `FR_TIMEOUT`)。同名同因下 unlink 必然同样失败,旧集合的真实后果是**每轮排空删一次自己的历史日志且永远修不好**,违反 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 10 条。`f_write` 之后的"失败/短写 → unlink 重试一次"**不接**本判定(能走到那里说明已打开成功;而簇分配失败是 `FR_OK` + 短写、根本没有码可判,必须无条件重试)。钉住:TC-SYSLOG-101 / 101b / 102 / 103;`App/log/soe_store.c` 侧同源但取**更保守子集**(TC-SOESTORE-022 / 022b)。 - **留痕节流分两层、各自独立抑制位**:drain 侧"整批回投"只在正常→故障那一次报(`write fail, requeue %lu entries`,落盘成功一轮复位;旧文案写作 "drop N entries" 与实际行为相反——整批其实一条没丢);post 侧 `queue full, drop` / `preboot full, drop` 各一位(**丢弃语义不变,满则丢照旧**,只压留痕次数)。节流之后**没有丢弃计数**——要计数属新增对外可见信息,另列后续项。 -- 读路径与写路径并发安全:UI 页在 uiTask 经 `syslog_get_record()` 读记录(内部走 `s_read_raw()`,见 §4.5);读写两侧都取卷锁 try 语义(P2-10),FatFS 系统锁保护单次调用。 +- 读路径与写路径并发安全:UI 页在 uiTask 经 `syslog_get_record()` 读记录(内部走 `s_read_raw()`,见 §4.5);读写两侧都取卷锁 try 语义,FatFS 系统锁保护单次调用。 - **存储未就绪/挂载失败**:待发条目滞留 RAM(下次上电丢失),串口打印告警——存储故障时本就无法持久化,属预期行为。 ### 4.5 读路径(UI / 模拟器) @@ -164,7 +164,7 @@ int syslog_get_record(syslog_id_t id, uint32_t idx, char *ts, size_t ts_len char *desc, size_t desc_len); /* 越界 / 读失败 / 缓冲容量不足 均返回 -1 */ ``` -- MCU:**8 条滑动读窗口**(N-24,报告 N-16 读侧)——`s_read_raw()` 先查窗口(同 id + 代数未变 + 落在区间内 → **零 I/O** 直接从 512 B 静态窗口返回);未命中才 `f_open(FA_READ) → f_lseek(窗口首条×64) → f_read(≤8×64B) → f_close` **一次取回连续 8 条**,再从中切出请求的那条。旧实现每读一条都要 open/lseek/read/close 一轮,UI 翻 20 条记录 = 20 次目录项/FAT 往返。 +- MCU:**8 条滑动读窗口**(日志读写放大收口,读侧)——`s_read_raw()` 先查窗口(同 id + 代数未变 + 落在区间内 → **零 I/O** 直接从 512 B 静态窗口返回);未命中才 `f_open(FA_READ) → f_lseek(窗口首条×64) → f_read(≤8×64B) → f_close` **一次取回连续 8 条**,再从中切出请求的那条。旧实现每读一条都要 open/lseek/read/close 一轮,UI 翻 20 条记录 = 20 次目录项/FAT 往返。 - 失效机制:写者每次落盘后把 `s_rd_gen[id]` 自增,读者比对填充时记录的代数,不匹配就重填。对齐 32 位在 ARM 上原子,跨任务(uiTask 填、defaultTask 增)无需临界区;最坏情况多读一轮,不会读错。 - **API 与调用方一律不变**(`syslog_get_record()` 签名照旧,`ui_page_runlog` / `ui_page_selftest` 零改动)。 - **容量契约(2026-10-24 写死在 `App/log/sys_log.h`)**:`ts_len > SYSLOG_TS_LEN`、`desc_len > SYSLOG_DESC_MAX`,**不足一律 -1,既不静默截断也不写调用方一个字节**(描述在写入侧已截到 `SYSLOG_DESC_MAX`,按最小容量给就一定装得下,读侧不需要第二道截断)。旧实现里那句 `if (dl >= desc_len) dl = desc_len - 1` 由上面的守卫保证**永不可达**,已删除;它的存在还会误导调用方"给 28 字节缓冲能读到 27 字符"——实际是整条读不到。UI 侧现用 32 B 列缓冲,走允许路径、行为不变。钉住:TC-SYSLOG-057、057b(0x7A 哨兵逐字节回比)、098。 @@ -285,21 +285,21 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。 | O2 | 故障"已复归"/持续监测(mockup 语义) | v2:周期自检 + 活动故障状态机 + 恢复记录 | | O3 | "清除记录"菜单项(node 27 #P,现为空框) | v2:UI 动作 = 删除/重建 log 文件(与满时策略同路径) | | O4 | 待机页电流值接实时数据 | 待交流量标定管线(AC 页占位数据替换)落地后接入 `tpafe5160_snapshot()`(原子快照;旧 `tpafe5160_get_buf()` 已于 2026-10-10 删除,见 `AGENTS.md`) | -| R-1 | FAT32 小追加簇开销 | §3 调研结论:写频率极低可接受;容量上限约束增长 | -| R-2 | UI 读与落盘并发 | 只读既有内容 + EOF 追加 + FatFS 系统锁,无互斥需求(§4.4) | +| — | FAT32 小追加簇开销 | §3 调研结论:写频率极低可接受;容量上限约束增长 | +| — | UI 读与落盘并发 | 只读既有内容 + EOF 追加 + FatFS 系统锁,无互斥需求(§4.4) | ## 10. 实施步骤(按序执行,每步双端构建 0/0) -> ✅ **S1–S9 已于 2026-09-14 全部完成**:Keil `@build` 0 错 0 警 + 模拟器 0 警告;S8 = `--logtest` PASS(期间修复 ts 19 字符 / epoch 10 位两处格式 bug)+ ui-verify 五门禁全绿(池基线 5440/5520B、pwc 参考更新)+ 记录页/待机屏/密码错误提示截图目检通过;S9 = 文档同步。 +> ✅ **步骤 1–9 已于 2026-09-14 全部完成**:Keil `@build` 0 错 0 警 + 模拟器 0 警告;步骤 8 = `--logtest` PASS(期间修复 ts 19 字符 / epoch 10 位两处格式 bug)+ ui-verify 五门禁全绿(池基线 5440/5520B、pwc 参考更新)+ 记录页/待机屏/密码错误提示截图目检通过;步骤 9 = 文档同步。 | # | 步骤 | 产物 | |---|------|------| -| S1 | 字体补字 + 重生成 | `ui_cn_chars.txt`、`ui_font_cn_12.c` | -| S2 | 日志引擎:`sys_log.h`(共享 API/格式)+ `sys_log_mcu.c` + `sim/sim_syslog.c` | 双端可 post/count/get | -| S3 | 自检埋点 + defaultTask 落盘循环 + 上电/重启分类 | app_main/freertos/net_task 接线 | -| S4 | 密码错误提示 | ui_page_password.c | -| S5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 | -| S6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c | -| S7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill | -| S8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 | -| S9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs | +| 步骤 1 | 字体补字 + 重生成 | `ui_cn_chars.txt`、`ui_font_cn_12.c` | +| 步骤 2 | 日志引擎:`sys_log.h`(共享 API/格式)+ `sys_log_mcu.c` + `sim/sim_syslog.c` | 双端可 post/count/get | +| 步骤 3 | 自检埋点 + defaultTask 落盘循环 + 上电/重启分类 | app_main/freertos/net_task 接线 | +| 步骤 4 | 密码错误提示 | ui_page_password.c | +| 步骤 5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 | +| 步骤 6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c | +| 步骤 7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill | +| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 | +| 步骤 9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs | diff --git a/docs/子系统设计/配置文件设计说明.md b/docs/子系统设计/配置文件设计说明.md index 97a6e387..19a0a59b 100644 --- a/docs/子系统设计/配置文件设计说明.md +++ b/docs/子系统设计/配置文件设计说明.md @@ -211,9 +211,9 @@ flowchart TD L --> M["f_stat 更新变更基线"] ``` -- **超长守卫(E-01,v1.12)**:外科手术输出超 `CFG_FILE_MAX_LEN`(4096B)→ **拒绝保存返回 -1**(原文件不动 + 自检上报 `cfg save overflow`);`s_write_doc`(模板/外科同闸的唯一落盘出口)硬性拒写 >4096B —— "引擎永不产出超长 config.ini"是结构性质。超长文件一律按损坏处置(load/poll 同判,见 §5.3) +- **超长守卫(v1.12)**:外科手术输出超 `CFG_FILE_MAX_LEN`(4096B)→ **拒绝保存返回 -1**(原文件不动 + 自检上报 `cfg save overflow`);`s_write_doc`(模板/外科同闸的唯一落盘出口)硬性拒写 >4096B —— "引擎永不产出超长 config.ini"是结构性质。超长文件一律按损坏处置(load/poll 同判,见 §5.3) -**(3) 变更检测** —— `cfg_file_poll()`(defaultTask / 模拟器主循环,2s 节拍):`f_stat` 取 (size, mtime-epoch) 与基线比对 → 沉降后**先按真实 `st.size` 查超长**(E-01,v1.12):>4096B = 损坏 → 各段复位工厂默认 + 重建模板 + 自检留痕(`cfg poll oversized`),**不触发段回调**(保留 FTP/在线修复通道,下次保存或重启收敛);正常路径重载 → 段变化则触发该段回调 → 刷新基线。 +**(3) 变更检测** —— `cfg_file_poll()`(defaultTask / 模拟器主循环,2s 节拍):`f_stat` 取 (size, mtime-epoch) 与基线比对 → 沉降后**先按真实 `st.size` 查超长**(v1.12):>4096B = 损坏 → 各段复位工厂默认 + 重建模板 + 自检留痕(`cfg poll oversized`),**不触发段回调**(保留 FTP/在线修复通道,下次保存或重启收敛);正常路径重载 → 段变化则触发该段回调 → 刷新基线。 **(4) 段变更回调** —— 未来模块的接入点: @@ -356,7 +356,7 @@ typedef struct { ... } cfg_section_reg_t; int cfg_file_register(const cfg_section_reg_t *p_sec); /* 注册段(首次 load 前;变更回调随段注册) */ int cfg_file_load(void); /* 0 = 正常(含回退默认)/ -1 = 严重错误 */ int cfg_file_save(void); /* 0 = 成功 / -1 = 失败(原文件保持不动;输出超 4096B 拒绝保存,v1.12) */ -void cfg_file_poll(void); /* 变更检测 + 沉降去抖(defaultTask / 模拟器主循环,2s 节拍);沉降后先判超长(E-01) */ +void cfg_file_poll(void); /* 变更检测 + 沉降去抖(defaultTask / 模拟器主循环,2s 节拍);沉降后先判超长 */ void *cfg_file_section_data(const char *p_name); /* 取段 RAM 副本(未注册 = NULL) */ ``` @@ -374,7 +374,7 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st); /* 0 = 存在 / -1 = 缺失/失败 */ int cfg_store_remove(const char *p_path); -/* 平台堆(P0-1,2026-09-18):共享核心的 KB 级临时缓冲一律经此三件套取用, +/* 平台堆(2026-09-18 起):共享核心的 KB 级临时缓冲一律经此三件套取用, * 核心禁止直接调用 C 库 malloc —— MCU 端 = pvPortMalloc/vPortFree(FreeRTOS 堆), * 模拟器端 = malloc/free(宿主 CRT 堆,堆余量以 MB 计,不参与 MCU 预算) */ void *cfg_store_alloc(size_t size); /* 失败返回 NULL,由调用方打印 FAIL */ @@ -441,7 +441,7 @@ void adc_cfg_init(void) ## 8. 内存预算 -依据:最新 map(2026-09-18,P0-1 修复后):静态区(128 KB 映射 SRAM1)已用 **126,064 B(96.2%)**,**余 5,008 B**;FreeRTOS 堆 `configTOTAL_HEAP_SIZE = 49,152 B`,扣除六任务栈 32 KB + 空闲/定时器栈 + IPC 后**空闲 ≈14 KB**(首启实测值直接看串口 `load: OK (…B, heap free …B)` 行)。 +依据:最新 map(2026-09-18 平台堆改造后):静态区(128 KB 映射 SRAM1)已用 **126,064 B(96.2%)**,**余 5,008 B**;FreeRTOS 堆 `configTOTAL_HEAP_SIZE = 49,152 B`,扣除六任务栈 32 KB + 空闲/定时器栈 + IPC 后**空闲 ≈14 KB**(首启实测值直接看串口 `load: OK (…B, heap free …B)` 行)。 | 项 | 大小 | 性质 | 说明 | |---|---|---|---| @@ -454,9 +454,9 @@ void adc_cfg_init(void) | **堆峰值(save 期间)** | **≤ 9,217 B** | 暂态 | 编译期由 `CFG_HEAP_BUDGET = 10,240 B` 钉死,放大缓冲宏会先编译失败 | | **静态 .bss 增量** | **≈ 0.7 KB(实测 706 B)** | 稳态 | 段表 + 行号表 + 快照 + 基线 << 静态余量 5.0 KB ✓ | -**P0-1(2026-09-18)实现纠偏**:本表原设计即"暂态走堆",但实现落在 C 库 `malloc` 上——本机工具链为 **MicroLIB**,其 C 堆只有 `Heap_Size = 0x200`(512 B),真机上 4 处 KB 级分配**必然返回 NULL**,症状是"配置读不到 → 静默回落工厂默认值"(模拟器用宿主 CRT 堆,故两端表现不一致)。现全部改走 `cfg_store_alloc`;map 复核:`malloc.o` 不再参与链接,`Removing startup_stm32f407xx.o(HEAP), (512 bytes)` → **静态区反而净增 512 B 余量**。 +**实现纠偏(2026-09-18)**:本表原设计即"暂态走堆",但实现落在 C 库 `malloc` 上——本机工具链为 **MicroLIB**,其 C 堆只有 `Heap_Size = 0x200`(512 B),真机上 4 处 KB 级分配**必然返回 NULL**,症状是"配置读不到 → 静默回落工厂默认值"(模拟器用宿主 CRT 堆,故两端表现不一致)。现全部改走 `cfg_store_alloc`;map 复核:`malloc.o` 不再参与链接,`Removing startup_stm32f407xx.o(HEAP), (512 bytes)` → **静态区反而净增 512 B 余量**。 -**结论**:配置引擎自身不需要内存优化专项;但 9,217 B 暂态峰值已占 FreeRTOS 堆空闲的一半以上,且与 lftpd 的每命令分配共存——**流式读写(免整文件驻留内存)仍列为后续优化项**,方案见本文 §6.2/§8(原报告 §4.4.1 已随 N-01 修复按销项删除,完整记录沉淀在本文)。 +**结论**:配置引擎自身不需要内存优化专项;但 9,217 B 暂态峰值已占 FreeRTOS 堆空闲的一半以上,且与 lftpd 的每命令分配共存——**流式读写(免整文件驻留内存)仍列为后续优化项**,方案见本文 §6.2/§8(原报告该节已随修复按销项删除,完整记录沉淀在本文)。 ## 9. 兼容与迁移 diff --git a/docs/文档规范.md b/docs/文档规范.md index 117d3a9d..6bc3a87c 100644 --- a/docs/文档规范.md +++ b/docs/文档规范.md @@ -12,7 +12,7 @@ | 规则 | 说明 | |---|---| -| **目录名一律不含空格** | 空格迫使脚本/链接/CI 处处加引号,且 URL 编码形态(`UI%20设计`)极易被漏改(D-6,2026-09-25 起强制;全库已无含空格目录) | +| **目录名一律不含空格** | 空格迫使脚本/链接/CI 处处加引号,且 URL 编码形态(`UI%20设计`)极易被漏改(2026-09-25 起强制;全库已无含空格目录) | | **按领域归入子目录** | 顶层不放散落 `.md`;新领域先建无空格子目录,再在 [`README.md`](README.md) 登记 | | 现有目录 | `架构设计/` `子系统设计/` `算法/` `网络/` `测试说明/` `代码规范/` `内存管理/` `问题记录/` `环境与部署/` `UI设计/` `硬件平台/` `芯片手册/` `images/` | @@ -81,7 +81,7 @@ | `docs/xxx.md`(docs 根下) | `![标题](images/RemoDispBus帧格式.svg)` | | `docs/子目录/xxx.md` | `![标题](../images/RemoDispBus帧格式.svg)` | -> ⚠️ **踩坑记录**:2026-09-25 目录重组时,5 篇文档从 `docs/` 迁入子目录,`images/xxx.svg` 未同步改为 `../images/xxx.svg` → **7 处真实断链**(由 D-7 门禁 `check_doc_links.py` 揪出)。迁移含图文档时务必检查此项。 +> ⚠️ **踩坑记录**:2026-09-25 目录重组时,5 篇文档从 `docs/` 迁入子目录,`images/xxx.svg` 未同步改为 `../images/xxx.svg` → **7 处真实断链**(由 `doc-links` 门禁揪出)。迁移含图文档时务必检查此项。 ### 3.3 引用尚未创建 / 已删除的文件 @@ -167,7 +167,7 @@ --- -## 4. 门禁:文档-代码一致性(D-7) +## 4. 门禁:文档-代码一致性 三个层次,全部由 pre-commit 机械校验: diff --git a/docs/架构设计/STM32F4-Base存储架构说明.md b/docs/架构设计/STM32F4-Base存储架构说明.md index 94aa83a1..bc31b120 100644 --- a/docs/架构设计/STM32F4-Base存储架构说明.md +++ b/docs/架构设计/STM32F4-Base存储架构说明.md @@ -336,10 +336,10 @@ dhara 通过 7 个回调函数访问物理 NAND: 所有回调自动将 dhara 逻辑块/页加 `FTL_START_BLOCK` 偏移转换为物理地址。 -⚠ **两条移植层硬约束(2026-10-15,登记簿 N-21 / 报告 N-12、N-13)**: +⚠ **两条移植层硬约束(2026-10-15)**: 1. **`is_free` 必须整页精确**。dhara 的 journal 用它判断"检查点组是否未编程"(`journal.c:416 cp_free`、`journal.c:520` 找下一空闲页),其正确性前提是"答空闲的页必须真的全 0xFF"(`journal.c:405-414` 的注释论证)。旧实现只看页首 64 B → 一个"页首 0xFF、后段有数据"的已编程页会被误判空闲,dhara 遂**往已编程页再编程一次** = 位碰撞、journal/map 指向垃圾(数据丢失,且不报任何错)。本卷跑的是 FatFS 数据页,这种页型很常见(刚格式化的卷、稀疏文件块、日志尾部)。成本上做了快路径:页首 64 B 有数据就当场返回非空,与旧实现同价,只有"目前读到的全是 0xFF"的页才继续扫后段。 -2. **六个 HAL 入口一律做几何上界校验**(上界 = `n->num_blocks << n->log2_ppb`)。越界页/块号(含 dhara 哨兵 `DHARA_PAGE_NONE` = 0xffffffff)旧实现直接加偏移送进 SPI 命令,会被芯片地址位**截断成某个真实页** → 静默读写/搬走无辜数据。现越界时 prog/read/copy/erase 返回 -1 且 `*err == DHARA_E_CORRUPT_MAP`,`is_free` 返回 0(保守判非空)、`is_bad` 返回 1(保守判坏)、`mark_bad` 直接返回(绝不标无辜好块)。**错误码选择是刻意的**:不能用 `DHARA_E_BAD_BLOCK`(`journal.c:904 recover_from` 会据此标坏块并跳块),也不能用 `DHARA_E_RECOVER`(`map.c:275 try_recover` 会据此启动回收);其余错误一律原样上抛,不会静默清 map(E-31 教训)。 +2. **六个 HAL 入口一律做几何上界校验**(上界 = `n->num_blocks << n->log2_ppb`)。越界页/块号(含 dhara 哨兵 `DHARA_PAGE_NONE` = 0xffffffff)旧实现直接加偏移送进 SPI 命令,会被芯片地址位**截断成某个真实页** → 静默读写/搬走无辜数据。现越界时 prog/read/copy/erase 返回 -1 且 `*err == DHARA_E_CORRUPT_MAP`,`is_free` 返回 0(保守判非空)、`is_bad` 返回 1(保守判坏)、`mark_bad` 直接返回(绝不标无辜好块)。**错误码选择是刻意的**:不能用 `DHARA_E_BAD_BLOCK`(`journal.c:904 recover_from` 会据此标坏块并跳块),也不能用 `DHARA_E_RECOVER`(`map.c:275 try_recover` 会据此启动回收);其余错误一律原样上抛,不会静默清 map(教训:宁报错不销毁数据)。 ### 5.4 初始化流程 (disk_initialize) @@ -596,11 +596,11 @@ if (nand_ftl_format() == 0) { 13. **BBT 恢复模式** — 运行期坏块会随 TC-STORAGE-03 注入累积;当介质被**误标脏**(如 DMA 误打 CCM 栈导致随机块被写坏并持久化)时,打开 `test/board/storage_test.h` 的 `ENABLE_STORAGE_RECOVERY_TESTS` 编译**恢复构建**,testTask 会执行 `gd5f2gq5ue_bbt_rebuild()`(出厂扫描清除运行期误标)+ `nand_ftl_format()` 清账后复位。恢复后容量回到基线(即扣除 BBT 保留池后的正常容量,非出厂裸容量)。 14. **lftpd 路径口径(归档更正 2026-10-13:原误记于 `docs/问题记录/CH395F_Trap_Records.md`——非 CH395F 议题,移入本节)** — FTP STOR/CWD 曾频发 `FR_INVALID_NAME`(6):真约束即上条第 10 项的严格 8.3。原记录"FatFS 不接受 `/` 前缀 / 空字符串失败"两条与仓内 `Lib/FatFs/ff.c` 矛盾(`ff.c:3101-3109` 会剥前导分隔符、`:3127-3129` 空名返回根目录),**已删**。现行口径:`lftpd_io.c` 的 `to_fatfs_path()` 剥 `/` 前缀(行为正确,保留)+ 本仓路径用 `"0:"` 卷前缀(`FF_FS_RPATH=0` 下 `"."` 不是合法路径段)。长文件名需 `FF_USE_LFN=1` + `FF_LFN_UNICODE`(显著增 RAM,未启用)。 -### 10.2 FTP 服务(lftpd)鉴权语义(2026-10-11,N-19) +### 10.2 FTP 服务(lftpd)鉴权语义(2026-10-11 起启用) -FTP 根目录 = 卷根,lftpd 是卷的网络文件入口(`Drivers/BSP/NET/lftpd/`)。N-19 起启用口令鉴权——`[ftp]` 配置段、鉴权流程与改口令方法的**权威口径**在 `docs/子系统设计/网络配置说明.md` §3.4,此处只登记存储侧语义: +FTP 根目录 = 卷根,lftpd 是卷的网络文件入口(`Drivers/BSP/NET/lftpd/`)。FTP 鉴权启用——`[ftp]` 配置段、鉴权流程与改口令方法的**权威口径**在 `docs/子系统设计/网络配置说明.md` §3.4,此处只登记存储侧语义: -- **未登录命令门** — 登录成功前仅 `USER`/`PASS`/`QUIT`/`FEAT`/`SYST`/`NOOP` 放行,其余命令(含 RETR/STOR/LIST/DELE)一律 `530`。修复前 `cmd_user`/`cmd_pass` 对任意口令无条件回 `230` = **匿名全读写整卷**(N-19 根因)。 +- **未登录命令门** — 登录成功前仅 `USER`/`PASS`/`QUIT`/`FEAT`/`SYST`/`NOOP` 放行,其余命令(含 RETR/STOR/LIST/DELE)一律 `530`。修复前 `cmd_user`/`cmd_pass` 对任意口令无条件回 `230` = **匿名全读写整卷**(鉴权缺失的根因)。 - **STOR/DELE 写门** — `[ftp] write=0` 时写命令一律 `550` 拒,卷对 FTP 侧只读(`/log`、`/config.ini` 仍可下载不可改写)。 - **全局失败锁定** — 连续 5 次鉴权失败 → 锁定 60 s(跨会话全局、易失、重启清零);纯 lftpd 层状态,不触存储。 - **默认口令告警锚点** — 出厂 `pwd_hash` 在用期间每次启动打 INFO `[FTP] using factory default password`。 @@ -633,7 +633,7 @@ lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]` "error reading next command"改成"control channel closed by peer"——原先那句把正常收尾 写成了故障,误导过排查方向。 -### 10.4 dhara 失败类:ECC / 预算耗尽 / 完整性故障(2026-10-23,S-5 C2) +### 10.4 dhara 失败类:ECC / 预算耗尽 / 完整性故障(2026-10-23) 一次 NAND 读写要穿过 **SPI → `gd5f_*` → dhara(`dhara_error_t`)→ FTL glue → FatFs `RES_ERROR`** 五段,而 glue 以前把 dhara 的所有失败**压成一个 `GD5F_ERROR`**——上层分不清"该重试"、 @@ -647,11 +647,11 @@ LPN 与原始码的位置**就地分类并打印(`ftl_err_name()` 给可读名 | `JOURNAL_FULL` / `MAP_FULL` | `BSP_ERR_NO_SPACE` | 介质还健康,是 **journal / 映射层块池见底**:该走容量恢复(`../测试说明/存储维护与容量恢复.md`),此时重格式化只是把问题推回去 | | `BAD_BLOCK` / `RECOVER` / `NOT_FOUND` / `CORRUPT_MAP` / 越界 | `BSP_ERR_IO` | 映射或介质**完整性**故障:保卷 + 上报,不许自愈重格式化 | -- 这条分类与 **E-31** 是同一条纪律的两端:E-31 规定"只有扫描全程 I/O 干净且无 +- 这条分类与真空白卷判据是同一条纪律的两端:那边规定"只有扫描全程 I/O 干净且无 checkpoint(`DHARA_E_TOO_BAD`)才算真空白卷",本表则保证失败**可读**——两者合起来 才是"别把瞬态毛刺当卷损坏去 `f_mkfs`"(旧行为:单次 I2C/SPI 抖动 → 清 map → 整卷 重格式化 → 静默销毁全部用户数据)。 - **仍未闭环**:分类目前**只进日志**;`disk_read`/`disk_write` 对 FatFs 仍只能回 `RES_ERROR` (那是 FatFs 的契约)。要让 defaultTask 的卷健康探针**按类分支**,还需要一个跨调用 存活的失败类快照(`nand_ftl_last_error()`,8 B;代价是要先定多写者纪律—— - defaultTask 与 testTask 都会碰存储)→ 报告 S-5 的 C4 待还项。 + defaultTask 与 testTask 都会碰存储)→ 即登记簿条目「可选全序列卷锁」的待还项。 diff --git a/docs/架构设计/项目启动顺序管理.md b/docs/架构设计/项目启动顺序管理.md index 6adc1e11..e0f91efc 100644 --- a/docs/架构设计/项目启动顺序管理.md +++ b/docs/架构设计/项目启动顺序管理.md @@ -219,7 +219,7 @@ if (sys_evtHandle != NULL) { - **修法**:所有**状态位**等待加 `osFlagsNoClear`(已改:`Src/freertos.c` 的 ftpTask、`App/task/ui_task.c`、`App/sync/time_sync.c`、`App/task/net_task.c`、`test/board/ch395f_test_task.c`);**命令位**(`CFG_SAVE`/`NET_RECONFIG`)保持消费型等待。 - **工程化防线**:① 关键消费者加"启动超时告警"(如 ftpTask 10s 内未到 listen 就 `DBG_ERROR`);② 用 `scripts/stack_report.py` 看任务栈水位——**某任务水位异常低 = 它根本没干活**(本次正是靠 ftpTask 只有 156B 才发现)。 -8. **启动段(`osKernelStart` 之前)死机的判读法(2026-10-02 S10 落地)**:`app_main_init` 全程运行在**内核启动前**——此时没有任务、没有看门狗保护,一旦卡死就是"无输出 + LED 不闪"(N-03b 的显式代价)。得益于 S10 补齐的可观测性,现在**每一步都有结果日志**,判读规则很简单: +8. **启动段(`osKernelStart` 之前)死机的判读法(2026-10-02 落地)**:`app_main_init` 全程运行在**内核启动前**——此时没有任务、没有看门狗保护,一旦卡死就是"无输出 + LED 不闪"(立项时明示的代价)。得益于那次整改补齐的可观测性,现在**每一步都有结果日志**,判读规则很简单: > **开机日志的最后一行 = 卡住的那一步**(该步的"结果日志"没打出来)。 diff --git a/docs/架构设计/项目架构总览.md b/docs/架构设计/项目架构总览.md index 38da3aad..34b25abf 100644 --- a/docs/架构设计/项目架构总览.md +++ b/docs/架构设计/项目架构总览.md @@ -56,7 +56,7 @@ | `defaultTask` | 5120 | Normal | FatFS 挂载(无卷 `f_mkfs`)→ 卷健康探针/自愈 → `net_cfg_load`(配置引擎属主)→ 置 `STORAGE_READY`(= 存储+配置就绪)→ 日志落盘(唯一写者,`syslog_drain`)+ 配置保存执行/2s poll + LED 闪烁 + **栈水位观测**(`stack_probe_poll`:首次 5s 后报告,此后仅在出现更深峰值时打印) | `StartDefaultTask` | | `netTask` | 3840 | Normal | 等存储(配置已由 defaultTask 就绪,本任务只读 RAM 副本)→ `net_stack_bring_up()`(不触碰配置文件引擎)→ 置 `NET_READY` → 循环 `net_poll` + `net_process_messages`;消费 `SYS_EVT_NET_RECONFIG` → 按网络代次号重建监听(CH395F 硬件层测试模式下入口自挂起) | `StartNetTask` | | `ftpTask` | 4096 | Normal | 等网络**且**存储(`WaitAll`)→ `lftpd_start("/", 21)` → 单客户端会话循环(断开后经 `net_listener` 重建监听) | `StartFtpTask` | -| `testTask` | 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**:CubeMX 生成区无条件建后,`MX_FREERTOS_Init()` 在未选套件时(`!TEST_SUITE_SELECTED`)立即 `vTaskDelete` 回收其 8KB CCM 栈(P2-13,2026-10-04) | `start_test_task` | +| `testTask` | 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**:CubeMX 生成区无条件建后,`MX_FREERTOS_Init()` 在未选套件时(`!TEST_SUITE_SELECTED`)立即 `vTaskDelete` 回收其 8KB CCM 栈(2026-10-04) | `start_test_task` | | `uiTask` | 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60s 切待机屏)/`ui_net_sink` → 10ms 刷新 | `ui_task_func` | | `timeSyncTask` | 2560 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC | `time_sync_task` | | `adcTask` | 2560 | AboveNormal | 纯消费者:`osThreadFlagsWait` 等 TIM3 1kHz 唤醒(ISR 内 `adc_tick_irq_notify` 置位)→ `adc_input_poll()`(TPAFE 原子快照 + STM32 ADC1 DMA 双缓冲)→ L1/L2 消费,不取数不做保护运算 | `adc_task_func` | diff --git a/docs/测试说明/ADC输入层单元测试说明.md b/docs/测试说明/ADC输入层单元测试说明.md index 0755adb8..d84bfd3b 100644 --- a/docs/测试说明/ADC输入层单元测试说明.md +++ b/docs/测试说明/ADC输入层单元测试说明.md @@ -13,7 +13,7 @@ > [`相量层单元测试说明.md`](相量层单元测试说明.md)(同批落地的 L2 姊妹篇)、 > [`../硬件平台/ADC通道映射说明.md`](../硬件平台/ADC通道映射说明.md)(**语义映射权威口径**)、 > [`../算法/继电保护功能复现规划.md`](../算法/继电保护功能复现规划.md)(L1-3 / L1-4 设计依据)、 -> 历史审计 E-44 / E-58(快照语义约定;属主文档已于 2026-10-15 删除,编号仅作指称) +> 快照语义约定的历史审计(属主文档已于 2026-10-15 删除) --- @@ -135,7 +135,7 @@ test/host/adc_input/adc_input_test.c | **TC-ADCIN-017** | `IN` → STM32 `CH2` | 同上 | | **TC-ADCIN-018** | `TEMP` → STM32 `CH5`(V_ZL2) | 同上 | | **TC-ADCIN-019** | `adc_input_desc(ADC_SEM_COUNT)` → `NULL` | 越界防御 | -| **TC-ADCIN-01A** | `adc_input_desc((adc_semantic_t)-1)` → `NULL` | 无符号比较兜底 | +| **TC-ADCIN-01a** | `adc_input_desc((adc_semantic_t)-1)` → `NULL` | 无符号比较兜底 | ### 2.3 映射表自检(`map_check`,静态) @@ -158,15 +158,15 @@ test/host/adc_input/adc_input_test.c | **TC-ADCIN-027** | `UC` = CH3 / rank4 = 103 | 同上 | | **TC-ADCIN-028** | `IB` = CH6 / rank7 = 106 | 同上 | | **TC-ADCIN-029** | `IC` = CH1 / rank2 = 101 | 同上 | -| **TC-ADCIN-02A** | `IN` = CH2 / rank3 = 102 | 同上 | -| **TC-ADCIN-02B** | `TEMP` = CH5 / rank6 = 105 | 同上 | -| **TC-ADCIN-02C** | 取帧后 `UA`(TPAFE)仍为 -2 | 两来源互相独立 | +| **TC-ADCIN-02a** | `IN` = CH2 / rank3 = 102 | 同上 | +| **TC-ADCIN-02b** | `TEMP` = CH5 / rank6 = 105 | 同上 | +| **TC-ADCIN-02c** | 取帧后 `UA`(TPAFE)仍为 -2 | 两来源互相独立 | | **TC-ADCIN-080..085** | ⭐ **乒乓双缓冲**:往"正在被 DMA 写的另一半区"填 900/800 系干扰值,`stm32_take()` 仍必须读到**刚完成那一半**(108 / 208) | **撕裂帧防线**(读错半区 = 半新半旧混合帧) | | **TC-ADCIN-086..089** | 两档超拍:连投 2 帧不消费 → 取到最新帧(408)且 `gaps()` = 1;再连投 3 帧不消费(一次跨 2 帧)→ `gaps()` 累计到 3,把单位钉成**丢掉的帧点数**(2026-10-21 由「次数」改来,与判定层 `tgap` 同单位);`errs()` 始终 = 0 | **丢帧与停摆分开计数**(消费者超拍 ≠ 节拍源停);两档并列才分得出「次数 vs 点数」 | | **TC-ADCIN-090..09A** | `HAL_ADC_ErrorCallback(&hadc1)` ×2 → `hw_errs()` = 2;用**别的句柄**调三个回调 → `seq` / `hw_errs` 均不动 | 三个 HAL 弱回调的 `hadc == &hadc1` **句柄守卫** | | **TC-ADCIN-030..034** | `HAL_ADC_Start_DMA` 返回 `HAL_ERROR` → `init()` 返回 **-2**、状态码 `RC_START`、计数 +1、STM32 侧无有效数据 | 挂载失败(一次性,非每拍) | | **TC-ADCIN-035..039** | 无任何 DMA 完成 → `RC_STALL`、**单拍不计故障**、`seq` 不前进、STM32 侧无有效数据 | 无新帧路径 + 相位伪影不误报 | -| **TC-ADCIN-03A..03E** | 连续两拍无新帧 → `errs()` = 1;第三拍 → 2;恢复拍成功且 `gaps()` 仍 0 | 停摆判据 = **连续 ≥2 拍**;跨次单调累计 | +| **TC-ADCIN-03a..03e** | 连续两拍无新帧 → `errs()` = 1;第三拍 → 2;恢复拍成功且 `gaps()` 仍 0 | 停摆判据 = **连续 ≥2 拍**;跨次单调累计 | | **TC-ADCIN-070..078** | ⭐ **停摆拍不得沿用上一拍旧值**:先成功一拍让 STM32 侧进入有效态(`UB`=108),再注入无新帧拍 → `adc_input_raw()` 必须返 **-2**;恢复拍重新可取数 | **陈旧帧**(2026-10-10 真机实测发现的缺陷,见下注) | > ⚠ **为什么 TC-ADCIN-034 照不出这条缺陷**:它是"`adc_input_init()` 后**立刻**失败",此时 @@ -192,8 +192,8 @@ test/host/adc_input/adc_input_test.c | **TC-ADCIN-044** | TPAFE 无有效帧 → -2 | 有效标志语义 | | **TC-ADCIN-045** | STM32 无有效扫描 → -2 | 有效标志语义 | | **TC-ADCIN-049** | `UA` → `buf[6]` 的值(注入 3006) | 按**下标**取数(非按枚举序) | -| **TC-ADCIN-04A** | `UN` → `buf[5]` 的值(注入 2005) | 同上 | -| **TC-ADCIN-04B** | `IA` → `buf[2]` 的值(注入 **-1234**) | 16 位补码负值原样保留(不截断 / 不取绝对值) | +| **TC-ADCIN-04a** | `UN` → `buf[5]` 的值(注入 2005) | 同上 | +| **TC-ADCIN-04b** | `IA` → `buf[2]` 的值(注入 **-1234**) | 16 位补码负值原样保留(不截断 / 不取绝对值) | ### 2.6 采集一拍(`adc_input_poll`) @@ -260,9 +260,9 @@ test/host/adc_input/adc_input_test.c `adc_input_raw()` 返回的是**未标定原始码值**(TPAFE = 16 位补码,STM32 = 0..4095)。 一次系数 / 零漂标定属 **L1-5,尚未实现**,故本单测不对"物理量换算"做任何断言。 -### 4.3 不覆盖 V0.1 的硬件缺陷(N-18) +### 4.3 不覆盖 V0.1 的硬件缺陷(双转换器无共同采样触发) -V0.1 上 8 路电气量分属两片转换器且**无共同采样触发**(N-18),本层只做**逐通道独立监视**。 +V0.1 上 8 路电气量分属两片转换器且**无共同采样触发**(登记簿条目「双转换器无共同采样触发」),本层只做**逐通道独立监视**。 "跨转换器相量合成"(线 / 负序 / 零序)不得在本层或 L2 上做——属 L2-5 且 V0.1 受限 (见 `docs/算法/继电保护功能复现规划.md` §1.6)。 diff --git a/docs/测试说明/CH395F_Test_Guide.md b/docs/测试说明/CH395F_Test_Guide.md index 55b34fe9..1ed2e8ee 100644 --- a/docs/测试说明/CH395F_Test_Guide.md +++ b/docs/测试说明/CH395F_Test_Guide.md @@ -2,16 +2,16 @@ ## 1. 概述 -本文档定义 CH395F 以太网驱动(STM32F407ZGTx,SPI2 接口)的测试规范。当前固件 `FUN_PARA=0x08`(bit3 SOCKET_CLOSE=1,bit0/bit1 均为 0):8 个 Socket 0~7 全部可用(**由 Socket 索引 `_SN` 命令族 + 缓冲显式分配实现,与 FUN_PARA 无关**,见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15),Socket 0 作 TCP Server 单连接,覆盖单连接与多并发客户端场景。测试按阶段(TM-PHY-NN)组织,每个阶段包含独立测试用例(TC-NET-NNN)。 +本文档定义 CH395F 以太网驱动(STM32F407ZGTx,SPI2 接口)的测试规范。当前固件 `FUN_PARA=0x08`(bit3 SOCKET_CLOSE=1,bit0/bit1 均为 0):8 个 Socket 0~7 全部可用(**由 Socket 索引 `_SN` 命令族 + 缓冲显式分配实现,与 FUN_PARA 无关**,见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15),Socket 0 作 TCP Server 单连接,覆盖单连接与多并发客户端场景。测试按阶段组织,每个阶段包含独立测试用例(TC-NET-NNN)。 ### 设计原则 | 原则 | 描述 | 验证用例 | |------|------|----------| -| DP-01 | 单连接 socket:Socket 0 唯一,复用监听和数据通道;CONNECT 后切换数据通道;DISCONNECT/TIMEOUT 后重新 OPEN → LISTEN | TC-NET-201~203, 601~606, 701~703, 801, 802, 901, 902, 1001~1006 | -| DP-02 | 中断集中处理:所有 CH395F 中断在 `net_poll()` 中通过 `GET_GLOB_INT_STATUS` 统一读取并清除,ISR 及其他位置不处理 | TC-NET-101 | -| DP-03 | 线程安全:所有 CH395F 操作通过 `netMsgQueue` 在 netTask 串行处理 | TC-NET-301~302 | -| DP-04 | DMA 边界安全:`CH395F_SPI_DMA_BUF_SIZE=4100`,单次事务最大载荷 `4096B`,超限自动分块 | TC-NET-1001~1005 | +| 单连接 socket | Socket 0 唯一,复用监听和数据通道;CONNECT 后切换数据通道;DISCONNECT/TIMEOUT 后重新 OPEN → LISTEN | TC-NET-201~203, 601~606, 701~703, 801, 802, 901, 902, 1001~1006 | +| 中断集中处理 | 所有 CH395F 中断在 `net_poll()` 中通过 `GET_GLOB_INT_STATUS` 统一读取并清除,ISR 及其他位置不处理 | TC-NET-101 | +| 线程安全 | 所有 CH395F 操作通过 `netMsgQueue` 在 netTask 串行处理 | TC-NET-301~302 | +| DMA 边界安全 | `CH395F_SPI_DMA_BUF_SIZE=4100`,单次事务最大载荷 `4096B`,超限自动分块 | TC-NET-1001~1005 | ### CH395F 缓冲架构(关键参数) @@ -97,7 +97,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) ## 2. 阶段 1:寄存器与 SPI 命令验证 -| 阶段 ID | TM-PHY-01 | +| 阶段 | 阶段 1 | |---------|-----------| | **类型** | 独立运行 | | **耗时** | ~1 秒 | @@ -130,13 +130,13 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:9/9 通过,0 失败。任一失败 → 阶段标记 FAIL,后续阶段不执行 -**覆盖原则**:DP-02 +**覆盖原则**:中断集中处理 --- ## 3. 阶段 2:TCP Client(CH395F 主动发起连接) -| 阶段 ID | TM-PHY-02 | +| 阶段 | 阶段 2 | |---------|-----------| | **类型** | 需 PC 配合 | | **耗时** | ~60 秒 | @@ -171,7 +171,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:7/7 通过,0 失败。任一失败 → 阶段标记 FAIL,后续阶段不执行 -**覆盖原则**:DP-01(open→connect→send→recv→close 主路径,硬件层) +**覆盖原则**:单连接 socket(open→connect→send→recv→close 主路径,硬件层) --- @@ -198,7 +198,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:3/3 轮全部 6 步通过,0 失败。任一失败 → 阶段标记 FAIL,后续不执行 -**覆盖原则**:DP-01(open→connect→close→re-open→reconnect,硬件层) +**覆盖原则**:单连接 socket(open→connect→close→re-open→reconnect,硬件层) --- @@ -223,7 +223,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:检测到 `SOCK_TIMEOUT` / `DISCONNECT` 且系统稳定,0 崩溃。任一异常 → 阶段标记 FAIL -**覆盖原则**:DP-01(异常路径的 connect 失败处理,硬件层) +**覆盖原则**:单连接 socket(异常路径的 connect 失败处理,硬件层) --- @@ -250,13 +250,13 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:45/45。任一模式偏离上表 → 说明芯片关闭语义或 `FUN_PARA` 配置变了,须回查 `问题记录/CH395F_Trap_Records.md` TRAP-NET-07。 -**覆盖原则**:DP-03(状态机边界:关闭次序 × Socket 复用) +**覆盖原则**:线程安全(状态机边界:关闭次序 × Socket 复用) --- ## 4. 阶段 3:UDP Server Echo(CH395F 作为 UDP Server) -| 阶段 ID | TM-PHY-03 | +| 阶段 | 阶段 3 | |---------|-----------| | **类型** | 需 PC 配合 | | **耗时** | ~60 秒 | @@ -279,7 +279,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) | **标题** | 30 轮 64B UDP 数据报回显 | | **前置条件** | PC: `python test/host/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000` | -> **会话结构**(固件单会话,与 udp_client 脚本约定):累计 **60 个有效数据包**后关闭 Socket——第 1~30 轮对应本用例(PC 发 64B),第 31~60 轮对应 TC-302(变长);脚本 `"HELLO"` 就绪探针只回显、**不计数**,不占用用例预算。 +> **会话结构**(固件单会话,与 udp_client 脚本约定):累计 **60 个有效数据包**后关闭 Socket——第 1~30 轮对应本用例(PC 发 64B),第 31~60 轮对应 TC-NET-302(变长);脚本 `"HELLO"` 就绪探针只回显、**不计数**,不占用用例预算。 **测试步骤与判定标准**: @@ -292,7 +292,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:30/30 轮回显成功,0 失败。任一失败 → 阶段标记 FAIL -**覆盖原则**:DP-03(UDP 收发路径,包头源地址解析) +**覆盖原则**:线程安全(UDP 收发路径,包头源地址解析) --- @@ -310,13 +310,13 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) **通过标准**:30/30 轮回显成功且 PC 端比对无误,0 失败。任一失败 → 阶段标记 FAIL -**覆盖原则**:DP-03(变长载荷边界) +**覆盖原则**:线程安全(变长载荷边界) --- ## 5. 阶段 4:DHCP 动态获取 IP -| 阶段 ID | TM-PHY-04 | +| 阶段 | 阶段 4 | |---------|-----------| | **类型** | PC 配合(脚本绑定 UDP/67,Windows 需**管理员权限**) | | **耗时** | ~10s(上限 30s DISCOVER 轮询 + 30s HELLO 等待) | @@ -377,7 +377,7 @@ CH395F PC (dhcp_server) **通过标准**:步 2~4 全部满足,串口 IP 与 PC 分配一致。任一失败 → 阶段标记 FAIL -**覆盖原则**:DP-03(DHCP 自动配置路径) +**覆盖原则**:线程安全(DHCP 自动配置路径) --- @@ -401,13 +401,13 @@ CH395F PC (dhcp_server) **通过标准**:步 1~3 全部满足(HELLO 至少 1 次完整往返;固件设计为单次回显后关闭)。任一失败 → 阶段标记 FAIL -**覆盖原则**:DP-03(动态 IP 下的端到端 UDP 通路) +**覆盖原则**:线程安全(动态 IP 下的端到端 UDP 通路) --- ## 6. 阶段 5:大文件传输(硬件层) -| 阶段 ID | TM-PHY-05 | +| 阶段 | 阶段 5 | |---------|-----------| | **类型** | PC 配合(`tcp_bulk`:先收 256KB 不回声 → 再按 LCG 回放 256KB) | | **耗时** | ~20 秒(256KB 顺序双向:TX 256KB + RX 256KB,视链路/SPI 吞吐) | @@ -463,7 +463,7 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081) - **为什么顺序双测向**:单向隔离,分别测得**纯 TX / 纯 RX** 吞吐与完整性——不被"回声耦合"掩盖各自链路的真实能力 - **TX 节奏**:写 4096B → 等 `SENBUF_FREE`(芯片把 socket 缓冲数据搬入内部协议栈/MAC 缓冲后触发)→ 写下一块,全程不等回显;单向发送阶段 PC 只收不回声,故无回读死锁问题 - **RX 校验**:PC 按相同 LCG 模式回放,MCU 期望流按块生成 `exp[]` 字节对位 + `sum_rx` 累加,天然处理 TCP 分包/粘包 -- 单块 4096B 数据 = 触发 DP-04 的 DMA 单次读边界(注意:单 Socket 发送缓冲实际为 1KB,net_send 写入需受此约束) +- 单块 4096B 数据 = 触发 DMA 边界安全原则的单次读边界(注意:单 Socket 发送缓冲实际为 1KB,net_send 写入需受此约束) - 双重完整性:逐字节比对 + 全量 32 位累加和 `SUM(TX)==SUM(RX)`(双向同源 LCG,相等即双向一致) - 双向独立吞吐打印(TX/RX 各自 KB/s),作为 SPI 时钟/驱动调整的回归基线 @@ -498,7 +498,7 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081) | 3 | close 并输出结论 | "TX phase: 64/64 blocks" + "RX phase: ..." + "checksum TX==RX" | bad_chunks>0 / 累加和不一致 | **通过标准**:步 1~3 全部满足。任一失败 → 阶段标记 FAIL -**覆盖原则**:DP-02(两级中断轮询)、DP-04(4KB DMA 读边界) +**覆盖原则**:中断集中处理(两级中断轮询)、DMA 边界安全(4KB DMA 读边界) --- @@ -516,12 +516,12 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081) | 步 | 操作 | 成功标准 | 失败标准 | |----|------|----------|----------| -| 1 | `PHASE5_TOTAL_BYTES=64*1024` 重编译,PC `--bulk 65536` 跑一遍 | 全绿 | 同 TC-501 | +| 1 | `PHASE5_TOTAL_BYTES=64*1024` 重编译,PC `--bulk 65536` 跑一遍 | 全绿 | 同 TC-NET-501 | | 2 | `PHASE5_TOTAL_BYTES=1024*1024` 重编译,PC `--bulk 1048576` 跑一遍 | 全绿且吞吐无劣化趋势 | 同上 | | 3 | `PHASE5_CHUNK_SIZE=4093`(非 4 对齐,制造跨包边界),PC `--chunk 4093 --bulk 261952` 跑一遍 | 全绿(4093 档实际回传 `64×4093=261952B`,不足一块的尾数不上送,完成判定按实际上送字节) | 尾块校验失败 | **通过标准**:三组配置均全绿。任一失败 → 阶段标记 FAIL -**覆盖原则**:DP-04(跨包/非对齐边界) +**覆盖原则**:DMA 边界安全(跨包/非对齐边界) > **记录(2026-08-24 实测)**:三档全部 **6/6 PASSED**,checksum 逐档相等 —— > 64KB(4096B 块): TX 420 / RX 364 KB/s;1MB(4096B 块): TX 449 / RX 391 KB/s; @@ -532,14 +532,14 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081) ## 7. 阶段 6:NET 层 TCP Server 回显 -| 阶段 ID | TM-PHY-06 | +| 阶段 | 阶段 6 | |---------|-----------| | **类型** | PC 配合,netTask 运行 | | **耗时** | 持续运行(Server 模式)/ 约 60s(Client 模式) | | **入口** | `ENABLE_PHASE6_TESTS` → `net_layer_test_main()`(`net_test_task.c`,持续监听回显循环) | | **出口** | 用户终止(Server)/ 用例结束自动回到 IDLE(Client) | -本阶段在 NET 层(单连接 socket,DP-01)经 `net_socket` API 驱动,MCU 作 TCP Server 监听 8080,PC 主动连接,验证回显通路、单连接生命周期与字节级完整性 —— 已实现(net 层自动重监听,应用层零生命周期代码) +本阶段在 NET 层(单连接 socket)经 `net_socket` API 驱动,MCU 作 TCP Server 监听 8080,PC 主动连接,验证回显通路、单连接生命周期与字节级完整性 —— 已实现(net 层自动重监听,应用层零生命周期代码) > Client 主动连接(原 TC-NET-607/608/609,现 701/702/703)已独立为 **阶段 7**(见 §8),使用独立宏 `ENABLE_PHASE7_TESTS` 与独立入口 `net_layer_client_test_main()`。 @@ -581,7 +581,7 @@ sequenceDiagram ``` - `phase6_run()` 为**纯回显**:非阻塞 `net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT)` 收到 N 字节后,`net_send(sockfd, s_rx_buf, N, 0)` 原样回显;不做固件侧字节自检(字节完整性由 PC 侧 `data == test_data` 校验)。 -- 单连接约束(DP-01):Socket 0 复用监听/数据通道;连接断开(DISCONNECT / KeepAlive TIMEOUT / 静默丢失)后由 **net 层自动重监听**(`auto_relisten`:`net_recv` 收到 EOF 时内部重新 `OPEN` + `TCP_LISTEN`),应用层无需 `net_close`/`net_listen`,下一次 `net_accept` 即接到新连接,行为等同 Linux BSD Socket 监听常驻。 +- 单连接约束:Socket 0 复用监听/数据通道;连接断开(DISCONNECT / KeepAlive TIMEOUT / 静默丢失)后由 **net 层自动重监听**(`auto_relisten`:`net_recv` 收到 EOF 时内部重新 `OPEN` + `TCP_LISTEN`),应用层无需 `net_close`/`net_listen`,下一次 `net_accept` 即接到新连接,行为等同 Linux BSD Socket 监听常驻。 > Client 主动连接(原 TC-NET-607/608/609,现 701/702/703)已独立为 **阶段 7**,架构与用例见 §8。 @@ -605,7 +605,7 @@ sequenceDiagram | 4 | 串口观测 `net_get_sock` 状态 | 仅 Socket 0 状态变化,无其它 Socket 干扰 | 出现非预期 Socket 状态跳变 | **通过标准**:步 1~4 全部满足,单连接生命周期正常。任一失败 → 标记 FAIL -**覆盖原则**:DP-01(OPEN→LISTEN→ESTABLISHED 单连接主路径) | +**覆盖原则**:单连接 socket(OPEN→LISTEN→ESTABLISHED 单连接主路径) | ### TC-NET-602: 64 字节小包回显 @@ -626,7 +626,7 @@ sequenceDiagram | 3 | PC 汇总 10 轮并逐字节校验 | 10/10 匹配 | 任一轮失败 | **通过标准**:10/10 匹配,0 失败。任一失败 → 标记 FAIL -**覆盖原则**:DP-01(单连接数据通路,小包) | +**覆盖原则**:单连接 socket(单连接数据通路,小包) | ### TC-NET-603: 1460 字节回显(当前固件 MSS=1024,1460B 会按 MSS 分片) @@ -647,7 +647,7 @@ sequenceDiagram | 3 | PC 校验 | 1460B 完全匹配 | 任意字节不符 | **通过标准**:1460B 完全匹配,无截断。任一失败 → 标记 FAIL -**覆盖原则**:DP-04(1460 ≤ 2KB 接收缓冲,单次 DMA 读空) | +**覆盖原则**:DMA 边界安全(1460 ≤ 2KB 接收缓冲,单次 DMA 读空) | ### TC-NET-604: 4096 字节缓冲满回显 @@ -668,7 +668,7 @@ sequenceDiagram | 3 | PC 校验 | 4096B 完全匹配 | 任意字节不符 | **通过标准**:4096B 完全匹配。任一失败 → 标记 FAIL -**覆盖原则**:DP-04(2KB 接收缓冲边界 + 单次 DMA 上限) | +**覆盖原则**:DMA 边界安全(2KB 接收缓冲边界 + 单次 DMA 上限) | > **2026-09-23 结案**:TC-NET-604 的"缓冲满回显"路径**判定为非缺陷**——真实生产路径(产品固件 + 真 lftpd FTP 上传,判据 = FTP 协议结果 + **上传后 RETR 回读 SHA-256**)实测 **227 轮全过**(8KB~1MB,含零间隔快速重连与 100 轮浸泡),并发生产 UI 流(`:7003`)600 帧 CRC 全过;原台架"停摆"系**测试程序自吞数据 + 判读工具误判**(详见 `CH395F_Trap_Records.md`「台架纪律」节)。 > - **本用例判据(不变)**:PC 发 4096B → 固件分次取空并回显 → **PC 侧内容完全匹配**即 PASS;上传/中止类真实缺陷见 **TRAP-NET-18**(回归工具 `test/host/ftp_abort_probe.py`)。 @@ -695,7 +695,7 @@ sequenceDiagram | 4 | PC 发 64B → 固件原样回显 | 回显正常且 PC `data == test_data` 通过 | 回显失败 / PC 校验不符 | **通过标准**:重连成功且回显正常,0 失败。任一失败 → 标记 FAIL -**覆盖原则**:DP-01(DISCONNECT → CLOSED → re-OPEN → LISTEN 单连接恢复) | +**覆盖原则**:单连接 socket(DISCONNECT → CLOSED → re-OPEN → LISTEN 单连接恢复) | ### TC-NET-606: 连接异常恢复(静默断开 / KeepAlive 超时)(未实现:不发 FIN 的静默断开与 KeepAlive 超时后 `auto_relisten` 能否自动回 LISTEN,**无任何可重复观测**——PC 侧 TC-NET-605 走的是干净 close(DISCONNECT 事件),与本条要求的 TIMEOUT 路径机制不同,不算覆盖;断连恢复语义见 docs/网络/网络自愈架构设计.md,2026-10-21 用例清点) @@ -716,7 +716,7 @@ sequenceDiagram | 3 | PC 重连 → 发 64B → 验证回显 | 回显正常 | 回显失败 | **通过标准**:自动恢复监听,无需手动复位。任一异常 → 标记 FAIL -**覆盖原则**:DP-01(异常路径恢复;KeepAlive 探测机制见 TC-NET-1101) | +**覆盖原则**:单连接 socket(异常路径恢复;KeepAlive 探测机制见 TC-NET-1101) | --- @@ -726,7 +726,7 @@ sequenceDiagram ## 8. 阶段 7:NET 层 TCP Client 主动连接 -| 阶段 ID | TM-PHY-07 | +| 阶段 | 阶段 7 | |---------|-----------| | **类型** | PC 配合,netTask 运行 | | **耗时** | 约 60s | @@ -804,7 +804,7 @@ sequenceDiagram **通过标准**:6/6 通过,0 失败。任一失败 → 标记 FAIL -**覆盖原则**:DP-01(net_socket→net_connect→net_send→net_recv→net_close 主路径,NET 层) +**覆盖原则**:单连接 socket(net_socket→net_connect→net_send→net_recv→net_close 主路径,NET 层) ### TC-NET-702: TCP Client 关闭重连 ×3 轮 @@ -827,7 +827,7 @@ sequenceDiagram **通过标准**:3/3 轮全部通过,0 失败。任一失败 → 标记 FAIL -**覆盖原则**:DP-01(net 层 open→connect→close→re-open→reconnect) +**覆盖原则**:单连接 socket(net 层 open→connect→close→re-open→reconnect) ### TC-NET-703: TCP Client 连接超时 @@ -849,7 +849,7 @@ sequenceDiagram **通过标准**:`net_select` 超时且资源正确释放,0 崩溃。任一异常 → 标记 FAIL -**覆盖原则**:DP-01(NET 层异常路径 connect 失败处理) +**覆盖原则**:单连接 socket(NET 层异常路径 connect 失败处理) > **实现说明**:TC-NET-703 与 TC-NET-701/702 的 PC 环境互斥(前者要求 PC 服务关闭、后两者要求开启),故固件中 TC-NET-703 由子开关 `PHASE7_TEST_TIMEOUT` 单独启用——默认 `ENABLE_PHASE7_TESTS` 仅跑 701/702;单独开启 `PHASE7_TEST_TIMEOUT` 并关闭 PC 服务后重编,方可执行 703。 @@ -873,17 +873,17 @@ sequenceDiagram **通过标准**:5/5 路全部通过,0 失败。任一失败 → 标记 FAIL -**覆盖原则**:DP-01(多 Socket 并发主路径)、缓冲架构(8×6 块分配,MSS=1024) +**覆盖原则**:单连接 socket(多 Socket 并发主路径)、缓冲架构(8×6 块分配,MSS=1024) > **注**:PC 端 `--max-conn` 须为**并发**计数(连接关闭即减),否则 Phase 7 全量(701+702+704 共 12 连接)会按累计计数超限而拒掉 4~7。该用例已随 Phase 7 实测通过(并发回显落在 sock2/4/5/6/7,见 TRAP-NET-15 末)。 > -> **2026-10-11 复跑(N-31,`FUN_PARA=0x08`)**:**Phase 7 34/34 全通过**,5 路并发 ESTABLISHED + 64 B 回显字节级比对 OK,芯片 Socket 实测落在 **sock2/4/5/6/7**(0/1/3 被 FTP/对时/远显三个监听占用)→ 证明 Socket 可用性不依赖 FUN_PARA。两条**前置排雷**(否则会得到全量假 FAIL):① PC 适配器落在 **Public** 防火墙配置文件会静默丢入站 8081,症状是 TC701/702/704 全部 `CONNECT TIMEOUT`;② 残留的旧 `tcp_server` 进程占着 8081,新服务器绑不上端口不报错。另:`PHASE7_MULTI_COUNT=5`(2026-10-15 由 8 降至此;`NET_MAX_SOCKETS=8` 减去三个常驻监听)。证据链见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15。 +> **2026-10-11 复跑(`FUN_PARA=0x08`)**:**Phase 7 34/34 全通过**,5 路并发 ESTABLISHED + 64 B 回显字节级比对 OK,芯片 Socket 实测落在 **sock2/4/5/6/7**(0/1/3 被 FTP/对时/远显三个监听占用)→ 证明 Socket 可用性不依赖 FUN_PARA。两条**前置排雷**(否则会得到全量假 FAIL):① PC 适配器落在 **Public** 防火墙配置文件会静默丢入站 8081,症状是 TC701/702/704 全部 `CONNECT TIMEOUT`;② 残留的旧 `tcp_server` 进程占着 8081,新服务器绑不上端口不报错。另:`PHASE7_MULTI_COUNT=5`(2026-10-15 由 8 降至此;`NET_MAX_SOCKETS=8` 减去三个常驻监听)。证据链见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15。 --- ## 9. 阶段 8:大文件 / 批量传输(100KB,文件收发) -| 阶段 ID | TM-PHY-08 | +| 阶段 | 阶段 8 | |---------|-----------| | **类型** | PC 配合(PC 作 TCP Client 连接 MCU:8080),netTask 运行 | | **耗时** | 取决于文件大小与速率(100KB 约数秒) | @@ -915,7 +915,7 @@ sequenceDiagram | **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(先连接等待数据)
2. `ENABLE_PHASE8_TESTS`,`PHASE8_FILE_SIZE=102400`,`PHASE8_DIRECTION=SEND`
3. `net_init` 完成,MSS=1024 | | **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)
2. 循环填充一个滑动窗口(≤ s_tx_buf)并调用 `net_send`;`net_send` 内部按硬件发送 FIFO(`NET_SEND_CHUNK_MAX`=1KB,见 TRAP-NET-16)自动拆分写入,直至 100KB 发完
3. PC 接收全部字节写入 `rx_100k.bin`,按递增模式或预置 CRC32 校验 | | **通过标准** | PC 收满 102400B 且逐字节/CRC32 校验一致,MCU 无发送错误 | -| **覆盖原则** | DP-04(分块发送、DMA 边界)、缓冲架构(1KB 发送缓冲) | +| **覆盖原则** | DMA 边界安全(分块发送、DMA 边界)、缓冲架构(1KB 发送缓冲) | | **实测** | ✅ PASS(2026-08-26):固件 `PHASE8_DIRECTION=SEND`,PC `tcp_file --op recv` 收满 102400B,递增模式逐字节校验通过,CRC32=0x9A0E0C8C,耗时 1209ms / 82.7 KB/s,落盘 `rx_100k.bin` 成功。 | ### TC-NET-802: 100KB 文件接收(PC→MCU) @@ -929,7 +929,7 @@ sequenceDiagram | **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op send --file tx_100k.bin --size 102400`(先连接并发送;无文件则生成 100KB 递增模式)
2. 同 TC-NET-801(`PHASE8_DIRECTION=RECV`) | | **测试步骤** | 1. MCU `net_accept` 建链(PC 已连接)
2. 循环 `net_recv`(≤2KB/次,保守一次读空)→ 按 offset 字节校验 + 累计 CRC32,直至收满 100KB
3. PC 在文件末尾附 4 字节 CRC32(小端);MCU 收齐后比对 CRC32 | | **通过标准** | MCU 收满 102400B,字节校验全过且 CRC32 匹配,无溢出/截断 | -| **覆盖原则** | DP-04(接收缓冲 2KB 一次读空) | +| **覆盖原则** | DMA 边界安全(接收缓冲 2KB 一次读空) | | **实测** | 🟡 待验证(参考:默认递增模式 100KB 的 CRC32 经 SEND 实测为 `0x9A0E0C8C`,RECV 方向 MCU 累计 CRC32 应与之相等) | ### TC-NET-803: 100KB 回显 round-trip(可选) @@ -943,7 +943,7 @@ sequenceDiagram | **前置条件** | MCU: `PHASE8_DIRECTION=ECHO`(net_accept 监听 8080);PC: `python ./test/host/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op echo`(PC 作 Client 连 MCU 并原样回显) | | **测试步骤** | MCU 发 100KB → PC 回显 → MCU 收 100KB 字节校验 | | **通过标准** | 100KB 全过,验证 100KB 经回显链路完整 | -| **覆盖原则** | DP-01、DP-04 | +| **覆盖原则** | 单连接 socket、DMA 边界安全 | | **实测** | 🟢 已验证(MCU 收齐 102400B 逐字节校验通过;PC 回显客户端 1241ms @ 80.6 KB/s,对端关闭后正常退出;修复了 phase8_run 未关闭连接导致 MCU 在同连接上无限重复、PC 挂起的问题) | ### 实现说明 @@ -963,7 +963,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON ## 10. 阶段 9:Select/Poll I/O 多路复用(单连接) -| 阶段 ID | TM-PHY-09 | +| 阶段 | 阶段 9 | |---------|-----------| | **类型** | PC 配合,netTask 运行 | | **耗时** | 持续运行 | @@ -980,7 +980,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON | **前置条件** | Socket 0 ESTABLISHED | | **测试步骤** | 1. PC 发送数据
2. `net_select(1, &readfds, ...)` → 返回 > 0
3. `NET_FD_ISSET(0, &readfds)` == true
4. `net_recv()` → `net_send()` 回显 | | **通过标准** | 回显成功 | -| **覆盖原则** | DP-02(select 内部调 net_poll) | +| **覆盖原则** | 中断集中处理(select 内部调 net_poll) | ### TC-NET-902: net_select() 超时 @@ -998,7 +998,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON ## 11. 阶段 10:边界条件测试 -| 阶段 ID | TM-PHY-10 | +| 阶段 | 阶段 10 | |---------|-----------| | **类型** | PC 配合,netTask 运行 | | **耗时** | 按需 | @@ -1015,7 +1015,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON | **前置条件** | Socket 0 ESTABLISHED | | **测试步骤** | MCU 生成 4095B 递增模式数据 → `net_send_sock(sk, data, 4095)` → 等待 PC 回显 → 验证 | | **通过标准** | 4095B 完全匹配 | -| **覆盖原则** | DP-04 | +| **覆盖原则** | DMA 边界安全 | ### TC-NET-1002: DMA 边界(4096 字节) @@ -1027,7 +1027,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON | **标题** | 发送 4096 字节(等于 SPI 最大载荷,填满单次 DMA) | | **测试步骤** | MCU 发 4096B → 等待回显 → 验证 | | **通过标准** | 4096B 完全匹配 | -| **覆盖原则** | DP-04 | +| **覆盖原则** | DMA 边界安全 | ### TC-NET-1003: DMA 边界 +1(4097 字节) @@ -1039,7 +1039,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON | **标题** | 发送 4097 字节(超过 SPI 最大载荷,自动拆两次 DMA:4096+1) | | **测试步骤** | MCU 发 4097B → `net_send_locked` 自动拆 4096+1 → 等待回显 → 验证 | | **通过标准** | 4097B 完全匹配,两次 DMA 无缝衔接 | -| **覆盖原则** | DP-04 | +| **覆盖原则** | DMA 边界安全 | ### TC-NET-1004: 零长度收发 @@ -1078,7 +1078,7 @@ IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DON ## 12. 阶段 11:KeepAlive 超时断开 -| 阶段 ID | TM-PHY-11 | +| 阶段 | 阶段 11 | |---------|-----------| | **类型** | PC 配合,netTask 运行 | | **耗时** | ~1.5–2 分钟(连接+网卡禁用后实测判死 18~31s,加恢复重连;芯片定时语义以实测为准,见 E11/E13) | @@ -1170,43 +1170,43 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 | 阶段 | TC ID | 优先级 | 类型 | 状态 | |------|-------|--------|------|------| -| TM-PHY-01 | TC-NET-101 | P0 | 功能测试 | ✅ | -| TM-PHY-01 | TC-NET-102 | P1 | 功能测试 | ✅ 由阶段 1 第 6 项覆盖(`test/board/ch395f_test_task.c` 的 `ch395f_get_glob_int_status_all()` 双读断言);原条目在 2026-08-22 重构中删除、本汇总行未同步 → 2026-10-21 核对补注 | -| TM-PHY-02 | TC-NET-201 | P0 | 功能测试 | ✅ | -| TM-PHY-02 | TC-NET-202 | P0 | 恢复测试 | ✅ | -| TM-PHY-02 | TC-NET-203 | P1 | 负向测试 | ✅ | -| TM-PHY-03 | TC-NET-301 | P0 | 功能测试 | ✅ | -| TM-PHY-03 | TC-NET-302 | P1 | 合规测试 | ✅ | -| TM-PHY-04 | TC-NET-401 | P0 | 功能测试 | ✅ | -| TM-PHY-04 | TC-NET-402 | P1 | 功能测试 | ✅ | -| TM-PHY-05 | TC-NET-501 | P0 | 功能测试 | ✅ | -| TM-PHY-05 | TC-NET-502 | P1 | 合规测试 | ✅ | -| TM-PHY-06 | TC-NET-601 | P0 | 功能测试 | ✅ | -| TM-PHY-06 | TC-NET-602 | P0 | 功能测试 | ✅ | -| TM-PHY-06 | TC-NET-603 | P0 | 边界测试 | ✅ | -| TM-PHY-06 | TC-NET-604 | P0 | 边界测试 | ✅ | -| TM-PHY-06 | TC-NET-605 | P0 | 恢复测试 | ✅ | -| TM-PHY-06 | TC-NET-606 | P1 | 恢复测试 | ✅ | -| TM-PHY-07 | TC-NET-701 | P0 | 功能测试 | ✅ | -| TM-PHY-07 | TC-NET-702 | P0 | 恢复测试 | ✅ | -| TM-PHY-07 | TC-NET-703 | P1 | 负向测试 | ✅ | -| TM-PHY-07 | TC-NET-704 | P0 | 并发测试 | ✅ | -| TM-PHY-08 | TC-NET-801 | P0 | 文件发送 | ✅ | -| TM-PHY-08 | TC-NET-802 | P0 | 文件接收 | 🟡 | -| TM-PHY-08 | TC-NET-803 | P1 | 回显/压力 | 🟡 | -| TM-PHY-09 | TC-NET-901 | P0 | 功能测试 | 🟡 | -| TM-PHY-09 | TC-NET-902 | P1 | 负向测试 | 🟡 | -| TM-PHY-10 | TC-NET-1001 | P0 | 边界测试 | 🟡 | -| TM-PHY-10 | TC-NET-1002 | P0 | 边界测试 | 🟡 | -| TM-PHY-10 | TC-NET-1003 | P0 | 边界测试 | 🟡 | -| TM-PHY-10 | TC-NET-1004 | P1 | 负向测试 | 🟡 | -| TM-PHY-10 | TC-NET-1005 | P1 | 压力测试 | 🟡 | -| TM-PHY-10 | TC-NET-1006 | P1 | 稳定性测试 | 🟡 | -| TM-PHY-11 | TC-NET-1101 | P1 | 功能测试 | 🟡 | -| TM-PHY-15 | TC-NET-1501 | P0 | 状态回读/二段关闭 | ✅ | -| TM-PHY-15 | TC-NET-1502 | P0 | 复位状态机 | ✅ | -| TM-PHY-15 | TC-NET-1502b | P1 | LISTEN 态复位 | ✅ | -| TM-PHY-15 | TC-NET-1503 | P1 | 链路缓存/事件护栏 | ✅ | +| 阶段 1 | TC-NET-101 | P0 | 功能测试 | ✅ | +| 阶段 1 | TC-NET-102 | P1 | 功能测试 | ✅ 由阶段 1 第 6 项覆盖(`test/board/ch395f_test_task.c` 的 `ch395f_get_glob_int_status_all()` 双读断言);原条目在 2026-08-22 重构中删除、本汇总行未同步 → 2026-10-21 核对补注 | +| 阶段 2 | TC-NET-201 | P0 | 功能测试 | ✅ | +| 阶段 2 | TC-NET-202 | P0 | 恢复测试 | ✅ | +| 阶段 2 | TC-NET-203 | P1 | 负向测试 | ✅ | +| 阶段 3 | TC-NET-301 | P0 | 功能测试 | ✅ | +| 阶段 3 | TC-NET-302 | P1 | 合规测试 | ✅ | +| 阶段 4 | TC-NET-401 | P0 | 功能测试 | ✅ | +| 阶段 4 | TC-NET-402 | P1 | 功能测试 | ✅ | +| 阶段 5 | TC-NET-501 | P0 | 功能测试 | ✅ | +| 阶段 5 | TC-NET-502 | P1 | 合规测试 | ✅ | +| 阶段 6 | TC-NET-601 | P0 | 功能测试 | ✅ | +| 阶段 6 | TC-NET-602 | P0 | 功能测试 | ✅ | +| 阶段 6 | TC-NET-603 | P0 | 边界测试 | ✅ | +| 阶段 6 | TC-NET-604 | P0 | 边界测试 | ✅ | +| 阶段 6 | TC-NET-605 | P0 | 恢复测试 | ✅ | +| 阶段 6 | TC-NET-606 | P1 | 恢复测试 | ✅ | +| 阶段 7 | TC-NET-701 | P0 | 功能测试 | ✅ | +| 阶段 7 | TC-NET-702 | P0 | 恢复测试 | ✅ | +| 阶段 7 | TC-NET-703 | P1 | 负向测试 | ✅ | +| 阶段 7 | TC-NET-704 | P0 | 并发测试 | ✅ | +| 阶段 8 | TC-NET-801 | P0 | 文件发送 | ✅ | +| 阶段 8 | TC-NET-802 | P0 | 文件接收 | 🟡 | +| 阶段 8 | TC-NET-803 | P1 | 回显/压力 | 🟡 | +| 阶段 9 | TC-NET-901 | P0 | 功能测试 | 🟡 | +| 阶段 9 | TC-NET-902 | P1 | 负向测试 | 🟡 | +| 阶段 10 | TC-NET-1001 | P0 | 边界测试 | 🟡 | +| 阶段 10 | TC-NET-1002 | P0 | 边界测试 | 🟡 | +| 阶段 10 | TC-NET-1003 | P0 | 边界测试 | 🟡 | +| 阶段 10 | TC-NET-1004 | P1 | 负向测试 | 🟡 | +| 阶段 10 | TC-NET-1005 | P1 | 压力测试 | 🟡 | +| 阶段 10 | TC-NET-1006 | P1 | 稳定性测试 | 🟡 | +| 阶段 11 | TC-NET-1101 | P1 | 功能测试 | 🟡 | +| 阶段 15 | TC-NET-1501 | P0 | 状态回读/二段关闭 | ✅ | +| 阶段 15 | TC-NET-1502 | P0 | 复位状态机 | ✅ | +| 阶段 15 | TC-NET-1502b | P1 | LISTEN 态复位 | ✅ | +| 阶段 15 | TC-NET-1503 | P1 | 链路缓存/事件护栏 | ✅ | ✅ = 已实现并验证 | 🟡 = 已实现待验证 | 🟠 = 待实现 @@ -1214,10 +1214,10 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 | 原则 | 覆盖用例 | |------|----------| -| DP-01:单 socket 生命周期 | TC-NET-202, 203, 601, 605, 606, 701, 702, 703, 704, 801, 802, 901, 902, 1001~1006 | -| DP-02:中断即读即清 | TC-NET-101, 102, 501, 502, 901 | -| DP-03:消息队列线程安全 | TC-NET-301, 302, 401 | -| DP-04:DMA 边界安全 | TC-NET-501, 502, 603, 604, 801, 802, 1001~1005 | +| 单 socket 生命周期 | TC-NET-202, 203, 601, 605, 606, 701, 702, 703, 704, 801, 802, 901, 902, 1001~1006 | +| 中断即读即清 | TC-NET-101, 102, 501, 502, 901 | +| 消息队列线程安全 | TC-NET-301, 302, 401 | +| DMA 边界安全 | TC-NET-501, 502, 603, 604, 801, 802, 1001~1005 | ## 附录 D:CH395F 边界条件速查 diff --git a/docs/测试说明/GD5F2GQ5UE_Test_Guide.md b/docs/测试说明/GD5F2GQ5UE_Test_Guide.md index 9ebe9bce..7db9f11d 100644 --- a/docs/测试说明/GD5F2GQ5UE_Test_Guide.md +++ b/docs/测试说明/GD5F2GQ5UE_Test_Guide.md @@ -4,7 +4,7 @@ > 全部公开/私有函数,并向上延伸到 FTL(`nand_ftl.c`)与 FatFS diskio。 > 详细芯片行为见 `docs/芯片手册/GD5F2GQ5UExxG.md`,已知陷阱见 `docs/问题记录/GD5F2GQ5UE_Trap_Records.md`。 > -> **2026-10-03 错误通道硬化**(E-10/E-42/E-53/E-54/E-56/E-57,见 `问题记录/GD5F2GQ5UE_Trap_Records.md` TRAP-GD5F-06): +> **2026-10-03 错误通道硬化**(见 `问题记录/GD5F2GQ5UE_Trap_Records.md` TRAP-GD5F-06): > `wait_busy` 区分"超时"(`GD5F_WAIT_EXPIRED`,旧名 `GD5F_BUSY_TIMEOUT`)与"真故障"(`GD5F_ERROR`)、`bbt_scan` 失败**放弃持久化**、 > init 全链路检查返回值 + **回读 B0h 校验 ECC 使能**、`read` 的 raw 契约文档化、公开 API 统一 `int32_t`。 > 套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`。 @@ -66,7 +66,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10` > **破坏性用例开关(2026-09-21 新增,默认关闭)**:`test_config.h` 的 `ENABLE_DESTRUCTIVE_TESTS` > 控制所有**会改变设备持久状态**的用例——写/擦 NAND、`nand_ftl_format`、FatFS 建文件写文件、 > 向 BBT 持久化坏块标记(永久减少可用容量)。关闭时为**只读模式**,可随时在在用设备上跑; -> 打开后才编译并运行 PHASE3~9,以及 PHASE2 的 TC-206/207、PHASE10 的 TC-1003/1004。 +> 打开后才编译并运行 PHASE3~9,以及 PHASE2 的 TC-GD5F-206/207、PHASE10 的 TC-GD5F-1003/1004。 > 依据:2026-09-21 实测一轮破坏性 GD5F 运行即让 dhara 多记一个坏块(容量 95248 → 95200; > dhara 把该计数持久化在 journal checkpoint 头里,普通恢复模式清不掉——见下文容量恢复说明), > 故破坏性用例只在**产品首次上电/产线首启**或明确需要时开启。 @@ -82,7 +82,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10` 执行顺序(由 `gd5f_test_run()` 按阶段串行,与函数依赖一致): -- **只读(默认)**:`init/read_id/reset` → `BBT(读取与 RAM 内标记)` → `DMA 守卫读方向(TC-704)` → `边界条件(越界 / size=0)` +- **只读(默认)**:`init/read_id/reset` → `BBT(读取与 RAM 内标记)` → `DMA 守卫读方向(TC-GD5F-704)` → `边界条件(越界 / size=0)` - **破坏性(开关打开时插入其间)**:`页读写` → `块擦除` → `ECC` → `SPI 原语` → `DMA 边界(701~703、705)` → `FTL(含 format)` → `FatFS` 开机横幅会明确打印本次模式:`mode: READ-ONLY (...)` 或 `mode: DESTRUCTIVE (...)`。 @@ -759,7 +759,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10` | TC-STORAGE-04 | BBT 重建 + 重新格式化(恢复) | P1 | 受 `ENABLE_STORAGE_RECOVERY_TESTS` 控制(默认关) | > 当前实测(2026-09-21,`ENABLE_DESTRUCTIVE_TESTS` 关闭 / 打开两种配置): -> 只读模式 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`(PHASE1/2/7 的 TC-704/10,无任何写操作, +> 只读模式 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`(PHASE1/2/7 那批只读阶段用例,含 704 那条只读守卫项;无任何写操作, > 卷内容与容量均不变);破坏性模式 `=== GD5F2GQ5UE Tests: 68/68 PASSED (failed=0) ===`(全部阶段)。 > `TEST_SUITE_STORAGE` 只读模式实测 `=== Storage Tests: 3/3 PASSED (failed=0) ===`(mount + TC-STORAGE-05, > 卷内容与容量不变);破坏性模式为全量集成用例(2026-09-20 实测 **20/20 PASSED**,含 TC-STORAGE-01/02/03/05/06)。 diff --git a/docs/测试说明/串口助手设计说明.md b/docs/测试说明/串口助手设计说明.md index 613b9b5a..f68d4b8f 100644 --- a/docs/测试说明/串口助手设计说明.md +++ b/docs/测试说明/串口助手设计说明.md @@ -2,7 +2,7 @@ > **版本**:v1.1(需求 + 设计 + 实施记录) > **日期**:2026-09-15 -> **状态**:**已实施**(S1–S6 完成;本机验收 A1–A5/A8 通过,A6/A7 正路径待 CH340 硬件接入后补验) +> **状态**:**已实施**(步骤 1–6 完成;本机验收 A1–A5/A8 通过,A6/A7 正路径待 CH340 硬件接入后补验) > **关联文档**:`串口助手使用指南.md`(命令用法/工作流/排障) · `自动化测试流程.md` · `serial-assistant` skill(命令速查/陷阱) · `.serialtest/config.json`(端口单一配置源,2026-10-10 由 `.opencode/serial_config.json` 迁入) > **取代**:`serial-mcp-server_串口调试.md`(实施时删除,工具历史并入本文 §12) @@ -237,12 +237,12 @@ flowchart LR | # | 步骤 | 产物 | 验证 | |---|---|---|---| -| S1 | `requirements.txt` 增 pyserial + `.venv` 安装 | requirements.txt | `pip show pyserial` 有版本 ✅ | -| S2 | daemon 核心:HTTP 服务 + 串口读线程 + 行日志写(含 clear/截头/DTR 策略) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(daemon 部分) | `start→status→send→stop` 手动走通 ✅ | -| S3 | CLI 全子命令 + 自动拉起 + 超时契约 + `selftest` | serial_tool.py(CLI 部分) | `selftest` 0=PASS + 命令走查 ✅ | -| S4 | 验收 A1–A6、A8 | 测试通过记录 | A1–A5/A8 PASS ✅(本机无 CH340,A6 待硬件) | -| S5 | @test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;**A7 正路径待 CH340 接入** | -| S6 | 文档/配置迁移(§7.2 全清单) | docs + 配置 | 全库 grep 无 serial-mcp-server 残留引用(本文 §12 工具历史除外) ✅ | +| 步骤 1 | `requirements.txt` 增 pyserial + `.venv` 安装 | requirements.txt | `pip show pyserial` 有版本 ✅ | +| 步骤 2 | daemon 核心:HTTP 服务 + 串口读线程 + 行日志写(含 clear/截头/DTR 策略) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(daemon 部分) | `start→status→send→stop` 手动走通 ✅ | +| 步骤 3 | CLI 全子命令 + 自动拉起 + 超时契约 + `selftest` | serial_tool.py(CLI 部分) | `selftest` 0=PASS + 命令走查 ✅ | +| 步骤 4 | 验收 A1–A6、A8 | 测试通过记录 | A1–A5/A8 PASS ✅(本机无 CH340,A6 待硬件) | +| 步骤 5 | @test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;**A7 正路径待 CH340 接入** | +| 步骤 6 | 文档/配置迁移(§7.2 全清单) | docs + 配置 | 全库 grep 无 serial-mcp-server 残留引用(本文 §12 工具历史除外) ✅ | 提交:全部完成后单提交(pre-commit 门禁;纯宿主侧 Python,不涉及 Keil 构建)。 diff --git a/docs/测试说明/存储维护与容量恢复.md b/docs/测试说明/存储维护与容量恢复.md index 063835f0..19071746 100644 --- a/docs/测试说明/存储维护与容量恢复.md +++ b/docs/测试说明/存储维护与容量恢复.md @@ -112,7 +112,7 @@ 保护下运行**,而且 `gd5f_check_ecc()` 在 ECC 关闭时恒读"无 ECC 错误",整条链不会再有任何信号。 - 现在(2026-10-23 改)关 ECC 失败会**立即中止**(此刻 ECC 仍开着,是安全侧;带着 ECC 去扫 spare 反而会把 - 厂标好块误判成坏块,"重建 BBT"变成制造坏块),重开失败则复用 init 的 **E-10** 做法**回读 B0h 校验 + 厂标好块误判成坏块,"重建 BBT"变成制造坏块),重开失败则复用 init 的做法**回读 B0h 校验 `ECC_EN` 位**,"写了但没生效"与写失败同罪,一律返回错误。 - **运维判据**:串口/`run.log` 里出现 `BBT rebuild: ECC restore FAILED` 或 `ECC not enabled (B0h=…)` → 本次恢复**不算完成**,设备处于无 ECC 保护态,必须**断电重启**让 init 重走一遍并使回读校验通过, diff --git a/docs/测试说明/实机测试实施指南.md b/docs/测试说明/实机测试实施指南.md index 3ecb795b..0f1f5ca6 100644 --- a/docs/测试说明/实机测试实施指南.md +++ b/docs/测试说明/实机测试实施指南.md @@ -23,10 +23,10 @@ |---|---|---|---| | **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 | | **步骤 2** | 产品 `@test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 | -| **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A / N-03) | ✅ 已闭环 | §4 | +| **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A) | ✅ 已闭环 | §4 | | **步骤 4** | 堆低水位告警实测(P1-B / S11) | ✅ 已闭环 | §5 | | **步骤 5** | GD5F 错误通道回归(P2-A / S9) | ✅ 已闭环 | §6 | -| **步骤 6** | 网络稳定性与基线重锁(P2-B / P1-8 + N-2/N-3) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 | +| **步骤 6** | 网络稳定性与基线重锁(P2-B / 日志静默观察项 + L5 队列往返基线待重验) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 | | **步骤 7** | 工程卫生(P3 / S5 + S4 + 待核实项) | ✅ 已闭环 | §8 | > **每步出口**:`@build` 0/0 → 一轮 `@test` PASS → ST-LINK 全片回读 `No difference found` → @@ -45,7 +45,7 @@ | A1 | 工作区干净 | `git status --short` | 无输出(或有明确待提交项) | | A2 | 产品构建 0/0 | `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py` | 末行 `BUILD_OK: 0 errors, 0 warnings` | | A3 | 构建产物为**产品态** | 检查 `test/board/test_config.h` L30-33 | 四个 `TEST_SUITE_*` **全部注释** | -| A4 | `.map` 非测试态 | 看 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` 的 `ER_IROM1` | 产品态基线随已接受的修复增长:262,100(初版)→ 262,836 B(10-02 round 1~6)→ 264,284 B(round 7~9)→ **264,936 B(当前,含 A2/A3/A4 补码)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 | +| A4 | `.map` 非测试态 | 看 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` 的 `ER_IROM1` | 产品态基线随已接受的修复增长:262,100(初版)→ 262,836 B(10-02 第 1~6 轮台架)→ 264,284 B(第 7~9 轮台架)→ **264,936 B(当前,含 A2/A3/A4 补码)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 | | A5 | 全量静态门禁 | `pre-commit run --all-files` | **17/17 Passed**(2026-10-03 实测;含新增 S5 `test-cppcheck`) | | A6 | ST-LINK 探针可见 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py list`(CLI 经 `DTU_STLINK_CLI` / PATH 解析) | 列出探针 SN `0B250D12A218303030303032` | | A7 | 串口端口正确 | 检查 `.serialtest/config.json` 的 `portName` | 端口单一配置源 = 此文件(当前 COM6,2026-09-21 台架重插后由 COM7 变更);变更后先杀旧 daemon 再 `open` | @@ -97,7 +97,7 @@ --- -## 4. 步骤 3:IWDG 故障注入与长跑(P1-A,N-03 收尾) · ✅ 已闭环 +## 4. 步骤 3:IWDG 故障注入与长跑(P1-A 收尾) · ✅ 已闭环 > **2026-10-03 台架结论**(实施细节与证据见 `子系统设计/看门狗设计说明.md` 变更记录): > V2/V4/V5 三用例全部通过——V2(任务饿死)声明 stall 后 ≤2.5s `reset cause: IWDG`;V4(关中断死循环)每 ~4.9s 稳定 IWDG 复位(IWDG 独立于中断);V5(30 min 真实负载)**120/120 次 FTP 传输全 PASS、零 IWDG 复位**。 @@ -186,11 +186,11 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L ## 6. 步骤 5:GD5F 错误通道统一(P2-A,S9 = 上游 C2) · ✅ 已闭环 -> **2026-10-03 台架结论**:四要素(分类/透传/分级/留痕)+ 返回类型统一 `int32_t` 已落地(E-10/E-42/E-53/E-54/E-56/E-57 全部销项;历史编号,属主文档已删除)。 +> **2026-10-03 台架结论**:四要素(分类/透传/分级/留痕)+ 返回类型统一 `int32_t` 已落地。 > 套件回归:`test/run_all_suites.py --suites gd5f` → **`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`**(只读模式,4 个破坏性用例按设计跳过);编排器 `[GD5F] -> PASS`。 > **顺带修复(编排器缺陷,非本次代码引入)**:`run_all_suites.py` 原对所有套件统一 `--forbidden "HardFault;assert;[ERR]"`,而 GD5F 的 `TC-GD5F-704`(CCM 非 DMA 可达缓冲)会**预期**打出 `[ERR][NAND] RX DMA buffer not DMA-reachable ... fallback poll`(用例 PASS)→ GD5F 套件**永远被判 FAIL**(自 `2055de0` 起)。按 `verify_log.py` 的说明改为**按套件覆盖 `--forbidden`**(GD5F = `HardFault;assert`),修复后 PASS。 -> **来源**:第二档 #1(E-10/E-42/E-53/E-54/E-56/E-57,历史编号); +> **来源**:第二档 #1; > `docs/产品需求/产品需求设计文档.md` §7.1(错误码通道四要素骨架,设计基线)。 **现状证据**:`ch395f.c`(1330 行) 与 `gd5f2gq5ue.c`(1496 行) 是同代驱动,但**只有 CH395F** @@ -198,13 +198,13 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L **统一错误通道四要素**(以 CH395F 为范式): -| 要素 | 内容 | 对应缺陷 | -|---|---|---| -| ① **分类** | `wait_busy` 区分「超时 / 真故障 / 参数错」——不得混为一个返回码 | E-53 | -| ② **透传** | 底层错误码逐层上报,不吞不猜(禁 `(void)ret`) | E-10 / E-42 | -| ③ **分级** | 可恢复(重试)/ 不可恢复(上报自检 + 降级)显式判定 | E-54 / E-56 | -| ④ **留痕** | 不可恢复项写 syslog selftest;可恢复项计数并在 UI/日志可见 | E-57 | -| — | 返回类型统一 `int32_t` | E-57 | +| 要素 | 内容 | +|---|---| +| ① **分类** | `wait_busy` 区分「超时 / 真故障 / 参数错」——不得混为一个返回码 | +| ② **透传** | 底层错误码逐层上报,不吞不猜(禁 `(void)ret`) | +| ③ **分级** | 可恢复(重试)/ 不可恢复(上报自检 + 降级)显式判定 | +| ④ **留痕** | 不可恢复项写 syslog selftest;可恢复项计数并在 UI/日志可见 | +| — | 返回类型统一 `int32_t` | **实施顺序**(数据安全优先): 1. 先建**分类函数**(`gd5f_wait_busy` 内检查三次 HAL 调用)——所有下游判断的基础; @@ -216,7 +216,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L 7. **RAM 预算**:新增错误分支**不得引入新的大缓冲**(先跑 `ram_report.py` 核算)。 **判据**: -- **E-10 / E-42 / E-53 / E-54 / E-56 / E-57 全部销项**; +- **错误通道审计项全部销项**; - **GD5F 套件回归 PASS**: ```bash # 套件回归(需实机) @@ -236,7 +236,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L ## 7. 步骤 6:网络稳定性与基线重锁(P2-B) · ◐ 部分闭环(7.1/7.2 已闭环;7.3 待 PC peer) -### 7.1 P1-8 网络重建失败恢复(A2;实施细节见 `网络/Net层阻塞模型架构设计.md`,状态见 §10) · ✅ 正常路径已闭环 +### 7.1 网络重建失败恢复(A2;实施细节见 `网络/Net层阻塞模型架构设计.md`,状态见 §10) · ✅ 正常路径已闭环 **判据**:拔插网线/改 IP 触发重建后—— - 失败路径**写 syslog 自检**(不再零日志); @@ -249,12 +249,12 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L - **正常路径 ✅**:V5 长跑(30 min,FTP 512 KB 上/下传)**120/120 PASS、零 IWDG 复位**, 网络重建后的 FTP/time_sync 服务据代次号重建监听、功能正常(证据 `/tmp/v5_soak_serial_20261003.log`)。 - **负向路径(拔线/芯片级 `net_init` 失败)**受**台架拓扑**限制(CH395↔交换机, - 拔交换机侧线缆链路仍 UP;芯片级失败需 SPI 故障注入)→ 已登记 `docs/任务规划/登记簿.md` **N-16** + 拔交换机侧线缆链路仍 UP;芯片级失败需 SPI 故障注入)→ 已登记 `docs/任务规划/登记簿.md` 条目「网络负向场景台架验证」 (① PHY 拔线负向 ② A2 `net_init` 失败分支 ③ A4 短写压测),需专项窗口。 -### 7.2 N-2:49s 全系统日志静默复现与定位 · ✅ 判据②已满足 / 判据①未复现 +### 7.2 日志静默观察项:49s 全系统日志静默复现与定位 · ✅ 判据②已满足 / 判据①未复现 -**现状证据**(登记簿 N-2):2026-09-22 台架实测,串口日志 **49s 无行** +**现状证据**(登记簿条目「日志静默观察项」):2026-09-22 台架实测,串口日志 **49s 无行** (testTask/netTask/[STACK] 行全缺),期间 net 栈照常处理外部连接 → **日志管线停滞**, 队列满后各任务日志行被丢;系统功能不受损。 @@ -268,15 +268,15 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L - **判据①(复现 49s 并定位卡点)→ 未复现**:对 V5 30 min 长跑日志(6031 条带时间戳行) 做间隔分析,**最大静默 41s**(发生在**开机/烧录边界**,非运行期),**运行期无 49s 静默**。 运行期 >10s 的静默共 24 处,**全部**为 ~28s,且**均落在 `e02_probe` 的 time_sync 30s - 挂起客户端窗口内**——与 **N-17** 同源(探针场景专属,非通用负载)。 + 挂起客户端窗口内**——与「探针窗口心跳缺口」同源(探针场景专属,非通用负载)。 - **结论**:49s 静默在当前固件 + V5 负载下**不复现**;可观测性缺口已由既有代码补齐。 - N-2 降级为**低风险观察项**(保留登记,若再现则借 `[ERR] CONSOLE dropped` 计数定位)。 + 日志静默观察项降级为**低风险**(保留登记,若再现则借 `[ERR] CONSOLE dropped` 计数定位)。 - **后续(2026-10-25,道F 批 2 合并评估)**:窗内静默的同源阻塞机制 (`drop_conn` 同步占用 netTask 400-600ms)已随「登记式拆链」修复 - (`../网络/网络自愈架构设计.md` §4.6);N-2 × S8 合并评估已完成,结论与「无源 vs + (`../网络/网络自愈架构设计.md` §4.6);日志静默观察项与日志单写者化的合并评估已完成,结论与「无源 vs 真停滞」判别口径落 `../子系统设计/控制台单写者日志设计说明.md` 文末附录—— 再现静默先按三本账(`dbg_console_dropped/_tx_fault/_tx_timeout`)+ syslog 丢弃 - 账对照定性,再决定是否开卡;销项条件 = N-17 修复版 V5 复跑(e02_probe + FTP + 账对照定性,再决定是否开卡;销项条件 = 探针窗口心跳缺口修复版 V5 复跑(e02_probe + FTP ≥30 min)运行期 `task stall` 与真停滞嫌疑静默段双零(2026-10-25 v1.1 起 `MISS start` 降级为诊断行不计销项——见下方判读工具与 `../子系统设计/看门狗设计说明.md` §6.1)。 @@ -299,7 +299,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L **执行步骤**:长跑(§4.2 V5)期间周期性打点 + 对照 `CH395F` 无关的 SD2506 侧日志判别卡点。 -### 7.3 N-3:L5 队列往返基线重验 · ⬜ 待 PC peer +### 7.3 L5 队列往返基线待重验 · ⬜ 待 PC peer **现状**:L5 基线(1223/988µs)属**分片时代**;L3 对该路径逐位不变 → 机制上无影响, 但严格重锁需跑一轮 Phase 13(**需 PC peer**)。 @@ -353,9 +353,10 @@ cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_base **判据**:一键列出双向差异清单;差异收敛后再考虑升为门禁。 **结论(✅ 已闭环,2026-10-03)**:`scripts/check_test_traceability.py` 已落地并跑通, -**默认仅告警(exit 0)、不阻断提交**。当前输出 **30 处差异**(26 处"文档有、代码无" + -4 处"代码有、文档无":`TC-GD5F-1100/1101/1102`、`TC-STORAGE-06`)。差异清单已收敛为 -可追踪项,后续按需补登记;**暂不升门禁**(与 §8.2 目标一致)。 +**默认仅告警(exit 0)、不阻断提交**。2026-10-26 重打:当前差异 **25 处**(文档有代码无 + 代码有文档无两向), +另有约 1,486 处属"代码有该号、号旁写着人话意图",按「代码即属主登记处」放过——**弱于文档的保证**, +对外举证仍需模块级说明。当年列出的孤儿号(GD5F 那三个 11xx、存储 06 号)已在 2026-10-26 清号批次 +里从代码侧删号收口。差异清单可追踪,后续按需补登记;**暂不升门禁**(与 §8.2 目标一致)。 ### 8.3 待核实项:`TEST_SUITE_SELECTED` 未含 `TEST_SUITE_RTC` · ✅ 已核实(非缺陷) @@ -397,7 +398,7 @@ CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创 但正常 `f_close` 就会走到 `CTRL_SYNC`(FatFs `sync_fs` → `disk_ioctl` → `ftl_flush_cache`), 所以**"写完即关"的路径(日志、配置保存)关文件即落盘,不必再"写另一个文件逼刷"**;会丢的 只有**写事务进行中**掉电/硬复位(含 IWDG 误复位)里最后那笔未跨页的写(≤512 B)—— - FTP STOR 传输途中掉电就落在这一档(`N-14` 已登记;登记簿 N-22 于 2026-10-22 裁定暂不做, + FTP STOR 传输途中掉电就落在这一档(已登记观察;「写回缓存空闲 flush」于 2026-10-22 裁定暂不做, 缺的是"空闲/掉电前周期 flush",且本机无掉电检测硬件,掉电时点无法补救)。 → 排查写入丢失时**先看开机日志的 head/tail/epoch**(`Map head=… tail=… epoch=…`, 纯内存字段、零 I/O):head 复位后倒退 = L1(resume 错);head 正常 = L2(缓存未刷)。 @@ -446,14 +447,14 @@ CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创 | 事项 | 状态 | 实施细节 / 证据所在 | |---|---|---| | **S7** 自检结果判定 | ✅ 已落地并投用 | `测试说明/自动化测试流程.md`;提交 `4a4cd29` | -| **A1** P0-1 `cfg_file` 堆分配改造 | ✅ 已闭环(含真机持久化) | `子系统设计/配置文件设计说明.md`(实施 v1.11 + 2026-10-03 真机验证记录 + 2026-10-04 独立复跑记录) | -| **A2** P1-8 网络重建失败恢复 | ✅ 已闭环(正常路径零回归;故障分支见 N-16) | `网络/Net层阻塞模型架构设计.md` | -| **A3** E-34 CH395F 字节命令超时 | ✅ 已闭环(正常路径零回归;故障分支待专项窗口) | —(历史编号,属主文档已于 2026-10-15 删除) | -| **A4** E-04 `remo_send` 返回值 | ✅ 已闭环(正常路径零回归;推流压测待专项窗口) | —(历史编号,属主文档已于 2026-10-15 删除) | +| **A1** `cfg_file` 堆分配改造 | ✅ 已闭环(含真机持久化) | `子系统设计/配置文件设计说明.md`(实施 v1.11 + 2026-10-03 真机验证记录 + 2026-10-04 独立复跑记录) | +| **A2** 网络重建失败恢复 | ✅ 已闭环(正常路径零回归;故障分支见「网络负向场景台架验证」) | `网络/Net层阻塞模型架构设计.md` | +| **A3** CH395F 字节命令超时 | ✅ 已闭环(正常路径零回归;故障分支待专项窗口) | — | +| **A4** `remo_send` 返回值 | ✅ 已闭环(正常路径零回归;推流压测待专项窗口) | — | | 2026-10-02 台架取证链(IWDG PR/RLR 错配 / dhara resume 回滚 / ORE 僵尸通道) | ✅ 已修 | `子系统设计/看门狗设计说明.md` 2026-10-02 变更记录 · `架构设计/STM32F4-Base存储架构说明.md` §5.4.1 · `子系统设计/控制台单写者日志设计说明.md` §12;提交 `930215f` | > **真机判定汇总**(2026-10-03,本机 MCU,新固件全闭环 `@test` PASS 7/7):A1 ✅ PASS(含配置持久化); -> A2/A3/A4 正常路径零回归;芯片级失败分支 / 短写 / 拔线负向场景记 `docs/任务规划/登记簿.md` N-16,待专项台架窗口。 +> A2/A3/A4 正常路径零回归;芯片级失败分支 / 短写 / 拔线负向场景记 `docs/任务规划/登记簿.md` 条目「网络负向场景台架验证」,待专项台架窗口。 --- @@ -464,14 +465,14 @@ CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创 | S7 自检判定 | —(原复审项,已闭环 2026-10-02) | `测试说明/自动化测试流程.md`(参数已落地,`4a4cd29`) | | V2/V4/V5 | `子系统设计/看门狗设计说明.md` §7 验证记录 | `子系统设计/看门狗设计说明.md` 变更记录(2026-10-03) | | S11 堆告警 | —(原复审项,已闭环 2026-10-03) | `内存管理/RAM使用分析与优化方案.md`(基线更新) | -| S9 GD5F | —(第二档 #1:历史编号 E-10/E-42/E-53/E-54/E-56/E-57,属主文档已删除) | `问题记录/GD5F2GQ5UE_Trap_Records.md`、`测试说明/GD5F2GQ5UE_Test_Guide.md` | -| N-2 日志静默 | `docs/任务规划/登记簿.md` N-2 | `网络/Net层阻塞模型架构设计.md`(E-02 头阻塞,历史编号) | -| P1-8 网络重建 | —(原台账项,已闭环 2026-10-02) | `网络/Net层阻塞模型架构设计.md` | +| S9 GD5F | —(第二档 #1) | `问题记录/GD5F2GQ5UE_Trap_Records.md`、`测试说明/GD5F2GQ5UE_Test_Guide.md` | +| 日志静默观察项 | `docs/任务规划/登记簿.md` | `网络/Net层阻塞模型架构设计.md`(头阻塞,历史编号) | +| 网络重建 | —(原台账项,已闭环 2026-10-02) | `网络/Net层阻塞模型架构设计.md` | | S5/S4 | —(原复审项,已闭环 2026-10-03) | `测试说明/测试开发标准流程.md` | | 模拟器差距 | `测试说明/模拟器差距清单.md` §3 决策规则 | 本文为"需回真机"项的执行面 | > **模拟器差距清单 §3 的六条"必须回真机"规则**决定"哪些改动**必须在本规划内验证—— -> 改动触及 `Drivers/BSP/` 寄存器时序 / H-1~H-7 硬件项 / L-2~L-4 / 网络重建并发 / 启动段 / 内存预算, +> 改动触及 `Drivers/BSP/` 寄存器时序 / 模拟器差距清单的硬件专属项 / 配置变更检测与回调 / 网络重建并发 / 启动段 / 内存预算, > 一律在本文件找到对应批次。 --- diff --git a/docs/测试说明/时间同步单元测试说明.md b/docs/测试说明/时间同步单元测试说明.md index b567bd0d..f40c92c7 100644 --- a/docs/测试说明/时间同步单元测试说明.md +++ b/docs/测试说明/时间同步单元测试说明.md @@ -12,8 +12,8 @@ > [`看门狗单元测试说明.md`](看门狗单元测试说明.md)(喂狗位图判定状态机)、 > [`卷锁单元测试说明.md`](卷锁单元测试说明.md)(同批落地的姊妹篇)、 > [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md)(`required_mask` / 抽样窗 / 长耗时白名单)、 -> [`../任务规划/README.md`](../任务规划/README.md)(**N-17** 登记条目)、 -> 历史审计 **E-02**(netTask 头阻塞;属主文档已于 2026-10-15 删除,现状见 [`../网络/Net层阻塞模型架构设计.md`](../网络/Net层阻塞模型架构设计.md)) +> [`../任务规划/README.md`](../任务规划/README.md)(登记条目「探针窗口心跳缺口」)、 +> 历史审计(netTask 头阻塞;属主文档已于 2026-10-15 删除,现状见 [`../网络/Net层阻塞模型架构设计.md`](../网络/Net层阻塞模型架构设计.md)) --- @@ -23,12 +23,12 @@ 本模块是**两件已知风险的当事方**,而两件风险的成因**全部是本模块的纯逻辑**: -1. **N-17(看门狗余量)**:登记簿 N-17 记录"time_sync 监听重建期间心跳缺口 800ms~1s, +1. **探针窗口心跳缺口(看门狗余量)**:登记簿条目记录"time_sync 监听重建期间心跳缺口 800ms~1s, 距 IWDG 最坏超时 1701ms 余量仅约 0.7s"。该缺口的每一段都由本模块产生—— `time_sync_delay_fed()` 的**分片喂狗节奏**、监听重试退避(500 / 2000ms)、 `accept` 轮询退避(200ms)、收尾延时(50ms),以及"就绪屏障等待态也要喂心跳"。 真机上只能靠"跑起来看复位不复位"间接观察;主机侧可**逐片断言**心跳次数与片长。 -2. **E-02(头阻塞)**:旧实现用单次 30s 阻塞 `net_recv` 等时间包,独占 `netTask`, +2. **头阻塞**:旧实现用单次 30s 阻塞 `net_recv` 等时间包,独占 `netTask`, 使其余任务的网络操作整体停摆。现改为"分片有界读 + 总预算",本单测钉死其**分片语义** (单次等待片长恒 ≤ 50ms、总预算 30s 不因分片而改变)。 @@ -73,7 +73,7 @@ test/host/time_sync/time_sync_test.c 看门狗抽样窗为 `WD_POLL_PERIOD_MS = 100ms`(`App/util/task_watchdog.c`)。若一次长等待 (如 2000ms 退避)期间不喂心跳,或心跳节奏恰好滑过两个连续抽样窗,该任务就会被判"卡死"。 故 `time_sync_delay_fed()` 把任意长等待切成 **≤ `TIME_SYNC_WAIT_SLICE_MS` = 50ms** 的短片, -**每片前**喂一次心跳——语义等价于一次长延时,仅把心跳节奏钉在窗内(`round 8b` 消抖)。 +**每片前**喂一次心跳——语义等价于一次长延时,仅把心跳节奏钉在窗内(第 8b 轮台架消抖)。 **(二)`net_recv_to()` 的四态返回。** `time_sync_handle_conn()` 的重组循环依赖该契约: @@ -118,7 +118,7 @@ test/host/time_sync/time_sync_test.c | **门禁层级** | **L0.5 层**(介于 L0 静态门禁与 L1 板端套件之间):主机侧、秒级、纯逻辑 | | **pre-commit 钩子** | `time-sync-unit-test`(本地钩子,`.pre-commit-config.yaml` §7g) | | **汇总行** | `=== Time Sync Tests: 80/80 PASSED (failed=0) ===` | -| **与板端测试的关系** | 互补:本单测保证**延时节奏与重组逻辑**符合设计;板端保证**真实 TCP 时序 + IWDG 不误复位**(N-17 的实际余量只能由板端长跑判定) | +| **与板端测试的关系** | 互补:本单测保证**延时节奏与重组逻辑**符合设计;板端保证**真实 TCP 时序 + IWDG 不误复位**(该心跳缺口的实际余量只能由板端长跑判定) | --- @@ -219,16 +219,16 @@ definition)规则静默合并为同一个对象**——于是 `task_wd_bind` CH395F 缓冲区边界。因此本单测能保证的是"**给定返回码序列,状态机反应正确**", **不能**保证真实链路上的时序行为——后者属板端与 PC 对端(`test/host/time_sync_server.py`)职责。 -### 4.4 N-17 的实际余量仍由板端判定 +### 4.4 探针窗口心跳缺口的实际余量仍由板端判定 -本单测把"每片喂心跳、片长 ≤ 50ms、退避值正确"这些**逻辑前提**钉死,但 N-17 关注的 +本单测把"每片喂心跳、片长 ≤ 50ms、退避值正确"这些**逻辑前提**钉死,但该缺口关注的 是**端到端心跳缺口 vs IWDG 超时**——这还取决于真实调度延迟、`netTask` 排队时长、 Flash 写耗时等主机侧无法建模的因素。故: - 本单测 ✅ 保证"逻辑上不会出现超过 50ms 的**无喂狗**窗口"; - 板端 ✅ 保证"实测缺口 + 调度抖动仍小于 1701ms"。 -两者都过,N-17 才能闭环。 +两者都过,这个心跳缺口才能闭环。 --- diff --git a/docs/测试说明/模拟器差距清单.md b/docs/测试说明/模拟器差距清单.md index 9b04883e..57a93129 100644 --- a/docs/测试说明/模拟器差距清单.md +++ b/docs/测试说明/模拟器差距清单.md @@ -38,35 +38,35 @@ ### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`) -| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | -|---|---|---|---|---|---| -| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 | -| N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) | -| N-3 | ~~`net_recv_to` 缺失~~(**已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 | -| N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` | -| N-5 | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 | -| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(N-1)仍不建模 | —— | +| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | +|---|---|---|---|---| +| **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | accept 轮询计时失真、lftpd 控制通道 errno 分类、timeSync errno 分级等在模拟器上无法复现 | `@test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 | +| **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) | +| **消息泵缺失**:~~`net_recv_to` 缺失~~(**已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 | +| **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` | +| **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 | +| **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(无 pending 机制)仍不建模 | —— | ### 2.2 日志与配置 -| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | -|---|---|---|---|---|---| -| L-1 | **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 | -| L-2 | **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 | -| L-3 | **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) | -| L-4 | **堆后端不同** | `cfg_store_alloc` = 宿主 `malloc`(堆余量以 MB 计) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 48 KB,实测最低水位 ~4.8 KB) | **分配失败路径在模拟器上永不触发**——P0-1 类"KB 级分配必然返回 NULL"缺陷模拟器照不出来(`AGENTS.md` 已列为铁律) | 内存预算门禁 + 真机 `[HEAP]` 水位告警(S11) | +| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | +|---|---|---|---|---| +| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 | +| **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 | +| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) | +| **堆后端不同** | `cfg_store_alloc` = 宿主 `malloc`(堆余量以 MB 计) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 48 KB,实测最低水位 ~4.8 KB) | **分配失败路径在模拟器上永不触发**——"KB 级分配必然返回 NULL"这类缺陷模拟器照不出来(`AGENTS.md` 已列为铁律) | 内存预算门禁 + 真机 `[HEAP]` 水位告警(S11) | ### 2.3 硬件专属(模拟器完全无对应) -| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | -|---|---|---|---|---|---| -| H-1 | **IWDG 硬件行为** | **不编译看门狗模块**(`simulator/` 无 `task_watchdog.c`,也无 IWDG 桩) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑也不在模拟器内跑——改由主机侧单测覆盖 | 真机 `@test`(复位原因日志 + 故障注入 V2/V4)+ 看门狗主机侧单测(L0.5,39 断言)
`看门狗设计说明.md` §4.7 / §7 | -| H-2 | **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `@test` | -| H-3 | **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@test` | -| H-4 | **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` L0.5(已覆盖算术面) | -| H-5 | **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) | -| H-6 | **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) | -| H-7 | **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6,S10 落地) | +| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | +|---|---|---|---|---| +| **IWDG 硬件行为** | **不编译看门狗模块**(`simulator/` 无 `task_watchdog.c`,也无 IWDG 桩) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑也不在模拟器内跑——改由主机侧单测覆盖 | 真机 `@test`(复位原因日志 + 故障注入 V2/V4)+ 看门狗主机侧单测(L0.5,39 断言)
`看门狗设计说明.md` §4.7 / §7 | +| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `@test` | +| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@test` | +| **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` L0.5(已覆盖算术面) | +| **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) | +| **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) | +| **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6 已落地) | ### 2.4 哪些差异是"有意的对齐"(非缺陷) @@ -86,11 +86,11 @@ 模拟器回归(L3)通过后,**满足以下任一条件即必须走真机**(`@test` / 板端套件): 1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码 -2. **改动触及 §2.3 的 H-1~H-7 任一子系统**的初始化或运行时行为 -3. **改动触及 §2.2 的 L-2/L-3/L-4**:变更检测令牌、段回调副作用、内存分配失败路径 -4. **涉及网络重建 / 多连接并发**(N-2、N-5) -5. **涉及启动段(`osKernelStart` 之前)**(H-7) -6. **涉及内存预算**:新增任务/缓冲/栈(L-4;模拟器堆不可作依据) +2. **改动触及 §2.3 硬件专属差距的任一子系统**的初始化或运行时行为 +3. **改动触及 §2.2 的 mtime 粒度 / 无应用回调 / 堆后端三条**:变更检测令牌、段回调副作用、内存分配失败路径 +4. **涉及网络重建 / 多连接并发**(代次号恒为 0、单连接 backlog) +5. **涉及启动段(`osKernelStart` 之前)**(启动段硬件初始化) +6. **涉及内存预算**:新增任务/缓冲/栈(堆后端不同;模拟器堆不可作依据) 反之,**纯 UI 场景/渲染/导航、配置解析算术、日志记录编解码格式**的改动,模拟器 + L0.5 可给出足够信心。 diff --git a/docs/测试说明/测试开发标准流程.md b/docs/测试说明/测试开发标准流程.md index 4b2cfaf7..b375d510 100644 --- a/docs/测试说明/测试开发标准流程.md +++ b/docs/测试说明/测试开发标准流程.md @@ -36,7 +36,7 @@ > [`看门狗单元测试说明.md`](看门狗单元测试说明.md)。适格对象:算法 / 状态机 / 协议编解码 / > 参数校验等**无副作用**的模块。 > -> **CI**:**L0 + L0.5 已上 CI**(Gitea Actions,`.gitea/workflows/ci.yml`,2026-10-01 T-6); +> **CI**:**L0 + L0.5 已上 CI**(Gitea Actions,`.gitea/workflows/ci.yml`,2026-10-01); > L1/L2/L3 需硬件,只在本地。本地等价命令 `scripts/ci_local.py`。详见 §9。 ### 1.1 门禁决策表(改动类型 → 必跑门禁) @@ -65,9 +65,9 @@ G0~G3 命令详见 §6 / §7;ui-verify 完整命令见 `UI_自动化测试流 | 对象 | 格式 | 现用示例 | |---|---|---| -| 阶段 | `TM-{MOD}-{NN}` | TM-PHY-01 | +| 阶段 | 直接写中文「阶段 N」(不再发号) | 阶段 6 | | 测试用例 | `TC-{MOD}-{NNN}` | `TC-NET-601`(CH395F)、`TC-GD5F-205`、`TC-STORAGE-03`、`TC-RTC-002` | -| 设计原则 | `DP-{NN}` | DP-01(单连接 socket) | +| 设计原则 | 写语义短语(不再发号) | 单连接 socket | 模块缩写 `{MOD}` 在新模块测试文档立文档时确定(2~4 个大写字母);现有:`NET`=CH395F 网络、`GD5F`、`STO`=存储、`RTC`、`ADC`、`RS485`。 @@ -114,7 +114,7 @@ flowchart TD 断言宏(语义统一,统计变量按套件独立,防跨套件串数): -**内核集中在 `test_common.h`**(2026-10-01 抽取,T-4)——4 套件的宏此前逐字重复,改一处要改四处: +**内核集中在 `test_common.h`**(2026-10-01 抽取)——4 套件的宏此前逐字重复,改一处要改四处: ```c /* test/board/test_common.h */ @@ -185,12 +185,12 @@ static void phaseN_run(void) { > 注意:`test/` 在 pre-commit 检查范围之外(工具代码豁免)——上表约定属**自律**要求,但 0 错 0 警的构建红线对测试代码同样生效。 -### 3.4 测试构建下的 netTask(2026-09-15 已修复 P1-6,未来测试设计必读) +### 3.4 测试构建下的 netTask(2026-09-15 已修复,未来测试设计必读) 代码事实(2026-09-15 修复后): 1. `netTask` = **基础线程**:定义于 `.ioc` → `Src/freertos.c` 的 **CubeMX 生成区无条件创建**(全构建唯一实例)。产品 / NET 层测试模式下正常运行:等存储就绪(≤10s)→ `net_stack_bring_up()`(CH395F 初始化)→ 置 `SYS_EVT_NET_READY` → `net_poll()` 轮询循环。 -2. ~~USER CODE 区在 `ENABLE_NET_LAYER_TESTS` 下另建一个 netTask(双 netTask 并存)~~ → **2026-09-15 已删除**(P1-6):NET 层测试构建不再重复创建。 +2. ~~USER CODE 区在 `ENABLE_NET_LAYER_TESTS` 下另建一个 netTask(双 netTask 并存)~~ → **2026-09-15 已删除**:NET 层测试构建不再重复创建。 3. **CH395F 硬件层模式下 `net_task_func` 入口自挂起**(2026-09-15 守卫,`ENABLE_HW_LAYER_TESTS`):不 bring_up、不轮询、不广播 NET_READY;testTask 自行 `net_stack_bring_up()` 后独占 CH395F 跑各阶段。任务仍被创建(基础线程策略:恒存在、该模式"不运行"),占 4KB 栈——彻底方案(`.ioc` 移除 + 条件创建)经评估未采用:网络线程是基础线程,仅测试模式"不运行"。 由此产生的设计约束: @@ -208,7 +208,7 @@ static void phaseN_run(void) { 6. 立 `docs/测试说明/<模块>_Test_Guide.md`(§2 模板); 7. `@test` 以套件标题行为 `-Required` 标记跑通回归。 -### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加 T-5) +### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加) 套件互斥(编译期 `#error`)→ 跑 4 个套件的回归本来要手工循环 4 轮 (改宏 → 构建 → 烧录 → 采集 → 判读)。本脚本编排成一条命令: @@ -217,7 +217,7 @@ static void phaseN_run(void) { & '.venv\Scripts\python.exe' test/run_all_suites.py # 全部套件 & '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F & '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划 -& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6) +& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录 ``` **核心保障:`try/finally` 无条件还原 `test_config.h`**(含异常 / `KeyboardInterrupt` @@ -227,7 +227,7 @@ static void phaseN_run(void) { > ⚠️ **完整回归不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起, > 且破坏性套件会改设备持久状态。仅回归窗口手工使用。 -#### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加 S6) +#### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加) **解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑, 但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件 @@ -242,11 +242,11 @@ static void phaseN_run(void) { | 做什么 | 逐套件:改 `test_config.h` 宏 → `@build` → 记 PASS/FAIL;结束后无条件还原 | | **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) | | 判定 | `@build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL | -| RAM 溢出 | P1-9 类"编译过但链接失败"会**自然暴露**为 `L6406E`——这正是想要的 | +| RAM 溢出 | "编译过但链接失败"这类问题会**自然暴露**为 `L6406E`——这正是想要的 | | 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 | > **2026-10-02 实测**:4 套件(CH395F / STORAGE / GD5F / RTC)**全部 `BUILD_OK: 0 errors, 0 warnings`** -> ——即 T-4 提到的"CH395F 套件不可编译"问题**已修复**,现已纳入可自检范围。 +> ——即前述"CH395F 套件不可编译"问题**已修复**,现已纳入可自检范围。 > ⚠️ **隐藏陷阱:`.map` 必须复位为产品态**(2026-10-02 实测踩到)。脚本跑完最后一轮留下的 > `STM32F407-Demo.map` 是**测试套件**的体积(RTC 套件下 ER_IROM1 达 266 KB), @@ -267,13 +267,13 @@ static void phaseN_run(void) { |---|---|---| | Python 串口助手(serial_tool) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(.venv + pyserial) | 常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle | | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | `test/host/` | 一键闭环:采集 → 烧录 → 提取日志 → 校验 | -| **`test/run_all_suites.py`** | `test/` | **多套件回归编排**(T-5)+ **`--check-build` 只编译不烧录**(S6,纯离机,验证 4 套件可编译性);见 §3.6 | -| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验(T-2) | -| **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **L0.5 通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置(T-3) | +| **`test/run_all_suites.py`** | `test/` | **多套件回归编排** + **`--check-build` 只编译不烧录**(纯离机,验证 4 套件可编译性);见 §3.6 | +| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验 | +| **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **L0.5 通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置 | | **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:声明 `TestBuild` 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 | -| **`test/host/sys_clock/run_unit_test.py`** | `test/host/sys_clock/` | **L0.5 主机侧单测**:穷举 `sys_clock` 时间换算 / 星期 / 惰性再基准化 / RTC 故障降级(100 断言,S1-a) | -| **`test/host/cfg_file/run_unit_test.py`** | `test/host/cfg_file/` | **L0.5 主机侧单测**:INI 解析 / 值编解码 / 段注册 / load-save-poll 契约,内存虚拟 FS 驱动(139 断言,S1-b) | -| **`test/host/nand_port/run_unit_test.py`** | `test/host/nand_port/` | **L0.5 主机侧单测**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 dhara,以 **RAM 假芯片**驱动,149 断言钉死 is_free 整页扫描(N-12)与六个入口的越界守卫(N-13) | +| **`test/host/sys_clock/run_unit_test.py`** | `test/host/sys_clock/` | **L0.5 主机侧单测**:穷举 `sys_clock` 时间换算 / 星期 / 惰性再基准化 / RTC 故障降级(100 断言,首批主机侧单测) | +| **`test/host/cfg_file/run_unit_test.py`** | `test/host/cfg_file/` | **L0.5 主机侧单测**:INI 解析 / 值编解码 / 段注册 / load-save-poll 契约,内存虚拟 FS 驱动(139 断言,首批主机侧单测) | +| **`test/host/nand_port/run_unit_test.py`** | `test/host/nand_port/` | **L0.5 主机侧单测**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 dhara,以 **RAM 假芯片**驱动,149 断言钉死 is_free 整页扫描与六个入口的越界守卫 | | **`scripts/ci_local.py`** | `scripts/` | **本地跑与 CI 等价的门禁**(L0 + L0.5):自动选用装了 pre-commit 的解释器;`.map` 自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。见 §9.3 | | `.serialtest/config.json` | 仓库 | **串口端口单一配置源**(当前 COM6 / 115200);换端口只改这个文件 | | `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` / `time_sync_server.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) | @@ -314,7 +314,7 @@ static void phaseN_run(void) { - 测试专用钩子(如静态态复位)用 `#ifdef <模块>_UNIT_TEST` 守卫,MCU 构建不编入(零符号); - 单测**不能替代**板端验证:真机寄存器 / 复位原因 / 时序仍须 L1/L2 台架闭环。 -#### 4.2.1 L0.5 适格模块台账(2026-10-02 加,S2) +#### 4.2.1 L0.5 适格模块台账(2026-10-02 加) **问题**:L0.5 起步阶段只有"看门狗"一个先例,**下一个该测谁**靠临时判断,易遗漏高价值模块。 @@ -324,22 +324,22 @@ static void phaseN_run(void) { | 模块 | 规模 | 状态 | 平台层形态 | 备注 | |---|---|---|---|---| | `App/util/task_watchdog.c` | — | ✅ 已覆盖(39 断言) | 内联 `__weak` 钩子 | 范式来源;`#ifdef TASK_WATCHDOG_UNIT_TEST` 复位钩子 | -| `App/util/sys_clock.c` | 479 行 | ✅ 已覆盖(100 断言,2026-10-02 S1-a) | 外部符号(`HAL_GetTick`/`sd2506_*`)+ 宏 | **零侵入**(无需 `#ifdef`);首测即抓出 Zeller 星期缺陷 | -| `App/cfg/cfg_file.c` | 1250 行 | ✅ 已覆盖(139 断言,2026-10-02 S1-b) | 后端抽象(`cfg_file_store.h`) | 内存虚拟 FS 桩;**首测即抓出外科手术保存死码 P0**(见 §5) | +| `App/util/sys_clock.c` | 479 行 | ✅ 已覆盖(100 断言,2026-10-02 首批) | 外部符号(`HAL_GetTick`/`sd2506_*`)+ 宏 | **零侵入**(无需 `#ifdef`);首测即抓出 Zeller 星期缺陷 | +| `App/cfg/cfg_file.c` | 1250 行 | ✅ 已覆盖(139 断言,2026-10-02 首批) | 后端抽象(`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` | — | ⛔ 已删除(2026-10-10) | — | 旧档 NCFG 迁移整体移除(旧档 `/cfg/netcfg.bin` 不读、不删、不迁移),测试候选随之撤销 | | `App/protocol/iec104/*`(104 协议栈) | — | ✅ 已覆盖(82 断言,2026-10-12 B4) | 数据源 vtable + tick 注入 + 发送回调注入 | APCI 重组/总召分片/COV/k-w-t1~t3/两段式全离线穷举;`iec104-unit-test` 钩子 | -| `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c`(dhara 移植层) | 573 行 | ✅ 已覆盖(149 断言,2026-10-15 N-21) | 11 个 SPI 原语(`gd5f2gq5ue.h`)→ **RAM 假芯片** | 移植层是纯地址算术 + 边界判定,换掉芯片即可离线跑;钉死 N-12(is_free 整页扫描)与 N-13(六入口越界守卫),并内联旧算法做对照证明断言有牙 | +| `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c`(dhara 移植层) | 573 行 | ✅ 已覆盖(149 断言,2026-10-15 起离线钉死 dhara 移植层两条硬约束) | 11 个 SPI 原语(`gd5f2gq5ue.h`)→ **RAM 假芯片** | 移植层是纯地址算术 + 边界判定,换掉芯片即可离线跑;钉死 is_free 整页扫描与六入口越界守卫,并内联旧算法做对照证明断言有牙 | | `App/util/rs485.c` | — | ⏸ 待功能实现 | — | 上游 B2;当前为空桩 | | `Drivers/BSP/TPAFE5160/*.c` | — | ❌ 不适格 | — | 寄存器序列为主,需真机时序 | | 其余驱动(CH395F/GD5F2GQ5UE 芯片层/SD2506) | — | ❌ 不适格 | — | 需硬件时序 / SPI / I2C 总线。⚠ **例外**:GD5F 之上的 `nand_ftl.c` 移植层**适格**(见上一行)——"驱动不适格"不等于"驱动之上的胶水层不适格" | -| `App/util/crc.c` · `ringbuf.c` | 120/154 行 | ⛔ 已删除(2026-10-02 S8) | — | 孤儿文件,零引用,已 `git rm` | +| `App/util/crc.c` · `ringbuf.c` | 120/154 行 | ⛔ 已删除(2026-10-02) | — | 孤儿文件,零引用,已 `git rm` | **使用方式**:新增/改动上述模块前,按表判断是否需补 L0.5;`⬜ 候选` 表示"适格但未做", 在性价比允许时应排入(判据:边界密集、零硬件、单次真机回归 30s~2min vs 单测 2~5s)。 -> ⭐ **两条实证**:`sys_clock`(S1-a)与 `cfg_file`(S1-b)**首次落地即各抓出一处 +> ⭐ **两条实证**:`sys_clock` 与 `cfg_file` **首次落地即各抓出一处 > 生产缺陷**(Zeller 星期公式错误 / 外科手术保存死码)。这印证了本台账的性价比判断—— > 高价值纯逻辑模块的 L0.5 覆盖,是**投入最小、产出最大**的测试改进方向。 @@ -413,7 +413,7 @@ flowchart TD | 开机全量(存储 / RTC / RS485 / ADC) | 默认 + `RS485 init: OK;NAND init:;ADC init:;SD2506 ID:` | | 测试套件构建 | 套件结尾汇总行(`=== Storage Tests:` / `=== GD5F2GQ5UE Tests:` 等,确切字符串见各模块 Test_Guide) | -### 6.2.1 套件汇总行校验(2026-10-01 加,T-2) +### 6.2.1 套件汇总行校验(2026-10-01 加) > ⚠️ **纯 Required 子串匹配有盲区**:套件启动横幅命中即判 PASS——**套件是否跑到汇总行、 > 断言数是否为 0 完全不可见**。套件中途挂死/复位、或 Phase 宏全关(没编入任何断言)时, @@ -458,7 +458,7 @@ flowchart TD | gitlint | commit message 标题 ≤100 字符 | | cppcheck | 静态分析(warning / performance / portability / style,含 suppress 清单) | | no-mojibake | 禁止 U+FFFD 乱码字节(保护中文注释) | -| coding-standard | 代码规范 V1.0 机械检查(S1~S19) | +| coding-standard | 代码规范 V1.0 机械检查(条款号 `CODE-RULE-nn`,规则表在 `scripts/check_coding_standard.py`) | | **test-suite-switch** | **测试套件开关=产品构建**(`test/board/test_config.h` 的 `TEST_SUITE_*` 须全注释;跑套件回归确需提交测试构建时用 `--allow-test-build` 显式放行) | 范围:仅项目自有代码受检;第三方与生成物(`Lib/`、`Middlewares/`、CMSIS/HAL、`lftpd`、`dhara`、`MDK-ARM/`、`test/`、CubeMX 生成的 `Src/` `Inc/` 等)被排除(详见 `pre-commit使用说明.md`)。 @@ -502,7 +502,7 @@ flowchart LR --- -## 9. CI 持续集成(Gitea Actions,2026-10-01 加 T-6) +## 9. CI 持续集成(Gitea Actions,2026-10-01 加) 远程仓库为 **Gitea 1.25.4**(`gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git`),支持 Gitea Actions (兼容 GitHub Actions 语法,workflow 置于 `.gitea/workflows/`)。落地文件 **`.gitea/workflows/ci.yml`**。 @@ -526,7 +526,7 @@ flowchart LR `.map` 时自动跳过该钩子**(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`—— 是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。 -> 🔴 **门禁盲区(S14,2026-10-02)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**—— +> 🔴 **门禁盲区(2026-10-02 记录)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**—— > 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示 > (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `@build`(0/0) > → `python scripts/check_mem_numbers.py --scan`。详见 diff --git a/docs/测试说明/测试文档模板规范.md b/docs/测试说明/测试文档模板规范.md index 38786f20..8100786b 100644 --- a/docs/测试说明/测试文档模板规范.md +++ b/docs/测试说明/测试文档模板规范.md @@ -24,7 +24,7 @@ ## 1. 概述 ### 1.1 套件与入口(TEST_SUITE_* 宏 / 入口函数 / 阶段宏 / 运行方式) -### 1.2 设计原则覆盖表(DP-xx → 描述 → 验证用例) +### 1.2 设计原则覆盖表(原则短语 → 描述 → 验证用例) ### 1.3 测试环境(拓扑、工具链、硬件版本、PC 端依赖) ### 1.4 启用方法(编译开关 + 运行命令) ### 1.5 回归接入(@test 标记 / PC 端脚本 / 门禁层级) @@ -73,7 +73,7 @@ | 字段名 | 填写说明 | |--------|----------| -| **阶段 ID** | `TM-模块缩写-两位数序号`,如 TM-PHY-01、TM-NAND-01 | +| **阶段** | 直接写中文「阶段 N」(如 阶段 1、阶段 6),不再发阶段号 | | **类型** | 独立运行 / 需 PC 配合 / 需物理操作 / 在 xxTask 中运行 | | **耗时** | 预估执行时间 | | **入口** | 代码入口函数或宏定义 | @@ -100,7 +100,7 @@ | **预期结果** | 系统应表现的行为 | | **通过标准** | 可量化的判定条件 | | **机器可判** | 串口标记 / TEST_CHECK / 像素参考 / 池水位 / L3 人工(见 §7.4) | -| **覆盖原则** | DP-xx(可选,关联设计原则) | +| **覆盖原则** | 设计原则短语(可选,关联设计原则) | ``` ### 5.1 类型枚举 @@ -127,14 +127,11 @@ ## 6. 命名规则 -### 6.1 阶段 ID +### 6.1 阶段 -``` -TM-{MOD}-{NN} -``` - -- `TM` — Test Module -- `{NN}` — 两位序号,从 01 开始 +- 正文、元信息表、汇总表一律写中文**「阶段 N」**(如 阶段 1、阶段 6),**不再发阶段号**; +- 用例号的百位已经带阶段信息(§6.3),无需另立阶段标识; +- 历史文档里出现过的旧阶段号冻结不回改,新文档按本条。 ### 6.2 模块缩写(以项目现用为准) @@ -160,16 +157,10 @@ TC-{MOD}-{NNN} - **无阶段划分套件**:全局顺序编号(如 `TC-STORAGE-03`、`TC-RTC-001`); - 编号不复用:废弃用例保留编号并注明"已废弃"。 -### 6.4 设计原则 ID +### 6.4 设计原则 -``` -DP-{NN} -``` - -- `DP` — Design Principle -- `{NN}` — 两位序号,从 01 开始 - -示例:`DP-01`(CH395F 单连接 socket)、`DP-02`(中断集中处理) +- 设计原则**写语义短语**,**不再发号**:如「单连接 socket」「中断集中处理」「线程安全」「DMA 边界安全」; +- 覆盖原则处、汇总表与代码注释**引用同一条短语**(同一事实只在一处详述)。 --- @@ -246,7 +237,7 @@ DP-{NN} | 原则 | 描述 | 验证用例 | |------|------|----------| -| DP-01 | ... | TC-XXX-xxx | +| 单连接 socket | ... | TC-NET-601 | ### 1.3 测试环境 @@ -268,18 +259,18 @@ DP-{NN} | 项 | 值 | |---|---| -| 阶段 ID | TM-XXX-01 | +| 阶段 | 阶段 1 | | 类型 | ... | | 耗时 | ... | | 入口 | ... | | 出口 | ... | | 阶段宏 | `ENABLE_XXX_PHASE1_TESTS` | -### TC-XXX-001: 用例标题 +### TC-<模块码>-<序号>: 用例标题 | 字段 | 值 | |------|-----| -| **ID** | TC-XXX-001 | +| **ID** | `TC-<模块码>-<序号>`(全局唯一,不复用) | | **优先级** | P0 | | **类型** | 功能测试 | | **标题** | ... | diff --git a/docs/测试说明/相量层单元测试说明.md b/docs/测试说明/相量层单元测试说明.md index 1e14f403..6ef578a8 100644 --- a/docs/测试说明/相量层单元测试说明.md +++ b/docs/测试说明/相量层单元测试说明.md @@ -11,7 +11,7 @@ > [`ADC输入层单元测试说明.md`](ADC输入层单元测试说明.md)(同批落地的 L1 姊妹篇)、 > [`../算法/傅氏算法详解.md`](../算法/傅氏算法详解.md)(**系数约定权威口径** §2.3)、 > [`../算法/继电保护功能复现规划.md`](../算法/继电保护功能复现规划.md)(§3.2 L2 设计点 / §10.2 术语表)、 -> [`../硬件平台/ADC通道映射说明.md`](../硬件平台/ADC通道映射说明.md)(L1 输入口径 / N-18 约束) +> [`../硬件平台/ADC通道映射说明.md`](../硬件平台/ADC通道映射说明.md)(L1 输入口径 / 「双转换器无共同采样触发」约束) --- @@ -184,7 +184,7 @@ test/host/phasor/phasor_test.c ### 4.2 主机侧合成波形**不代表真实硬件** `frame_at()` 给 9 路语义通道灌的是**同值**波形,仅为驱动 L2 本身。V0.1 上各通道**不同刻** -(N-18:TPAFE 与 STM32 无共同采样触发),跨通道相量运算在本平台上不成立——本单测**不**做 +(V0.1 硬件缺陷:TPAFE 与 STM32 无共同采样触发),跨通道相量运算在本平台上不成立——本单测**不**做 任何跨通道合成断言(L2-5 受限,见 `docs/算法/继电保护功能复现规划.md` §1.6)。 ### 4.3 实例数 4 → 5:规划表已对齐(2026-10-10) diff --git a/docs/测试说明/看门狗单元测试说明.md b/docs/测试说明/看门狗单元测试说明.md index 0ef70501..e4c3dbfa 100644 --- a/docs/测试说明/看门狗单元测试说明.md +++ b/docs/测试说明/看门狗单元测试说明.md @@ -71,7 +71,7 @@ test/host/watchdog/watchdog_test.c > `reg.exe`。运行器因此**直接拼 `INCLUDE`/`LIB` 环境变量**并用绝对 > 路径调 `cl.exe`(只需 MSVC 版本目录 + Windows SDK 的 include/lib),无副作用、可重复。 > -> 该工具链逻辑**不在本模块目录内**(2026-10-01 T-3 抽取):已沉淀为通用 harness +> 该工具链逻辑**不在本模块目录内**(2026-10-01 抽取):已沉淀为通用 harness > [`../../test/host/common/unit_test_harness.py`](../../test/host/common/unit_test_harness.py), > 本模块的 `run_unit_test.py` 只**声明** `TestBuild`(源文件 / 桩目录 / 汇总行标记)。 > 新增 L0.5 单测时直接复用 harness,见 [`../../test/host/common/README.md`](../../test/host/common/README.md)。 diff --git a/docs/测试说明/链路延迟测试说明.md b/docs/测试说明/链路延迟测试说明.md index 936bc138..dbfdd4cd 100644 --- a/docs/测试说明/链路延迟测试说明.md +++ b/docs/测试说明/链路延迟测试说明.md @@ -5,7 +5,7 @@ > 哪些缺陷已修/未修、新异常出现时查什么。 > **数据日期**:2026-09-22 台架(COM6 @115200;STM32F407ZGTx @168MHz + CH395F,SPI2 分频 8) > **数据文件**:`m2_data/`、`m3_data/`(JSON + 串口证据片段);本文所有数字均可回溯到这些文件 -> **代码状态**:D10 / E-02 / D17 / D12 / D14 / D15 / D16 已修复并台架复验(§8); +> **代码状态**:D10 / D17 / D12 / D14 / D15 / D16 已修复并台架复验(§8); > D1(重传周期 200ms)/ D6(per-recv 日志)2026-09-22 已实施并台架复验(P0-a/P0-b,§10.1); > D20(开机首个 FTP 会话第 2 个数据连接 425)已定位未修(§8.2) > **合并说明**:本文统一原《链路延迟测试说明》《链路时延实测数据分析》《远程显示延迟分析》 @@ -25,7 +25,7 @@ MCU 侧真实处理只占 6.7ms,任务拾取/调度约 2.3ms(2026-09-22 重 - **解法在客户端一侧(生产客户端已实装)**:Windows 连接时关延迟 ACK(帧耗时 **20.7ms**,3.3×)/ 非 Windows 用"入站轻推"兜底(**29.4ms**,2.3×);协议与固件都不需要改(§7.4)。 - **设备侧无可行解**(帧长 3209 为质数、TX FIFO = MSS、CH395F 无 Nagle 开关)→ 不必再投入。 -- **缺陷**:D10(断线重监听 588ms 冻结 netTask)、E-02(一条空闲 FTP 控制连接冻结全网 17.7s)、 +- **缺陷**:D10(断线重监听 588ms 冻结 netTask)、头阻塞(一条空闲 FTP 控制连接冻结全网 17.7s)、 D17(INIT 应答帧字节数字段恒 0)等已量化并**修复 + 台架复验**(§8); D1(重传周期 500→200ms)/ D6(per-recv 日志)2026-09-22 已实施(§10.1);D20 已定位未修(§8.2)。 @@ -114,12 +114,12 @@ sequenceDiagram | **L2** | SPI DMA | `ch395f_read_recv_buf(N)` 完整 DMA 事务(含 CS 切换),N ∈ {4, 64, 1024, 2048} | MCU,DWT µs,Phase 12,N=100/档 | 20 / 114 / **1611** / 3208µs(斜率 1.56µs/B + 固定 14µs) | SPI2/DMA/总线异常 | | **L3** | 发送分块握手 | `write_send_buf(1KB)` → `SENBUF_FREE` 的等待时长(= `net_send_locked` 每 chunk 的真实等待) | MCU(netTask 上下文),Phase 13 | **219µs**/chunk | 芯片发送路径 / 重传(D1) / 链路(D7) | | **L4** | 芯片→线上+PC | 数据交入芯片协议栈 → PC 首见字节 / 收齐全帧(peer 逐 `recv()` 批次打时间戳) | PC(peer),Phase 13/14 | 首批 8.6ms;**段间间隙 ≥200ms = 重传**(P0-a 后粒度;旧固件 500ms) | D1/D4/D7("网络延迟"直接证据) | -| **L5** | 队列往返 | 调用方视角 `net_recv(DONTWAIT)` / `net_send` 全程 = 入队 + netTask 调度 + 处理 + 通知 | MCU(调用方计时),Phase 13,**两档**:静默基线 + 压力档(并发 FTP 流量) | 静默:收 1223µs / 发 988µs(2026-09-22 重锁,P0-b 后;L3 对 DONTWAIT 路径逐位不变,重验留待 Phase 13 下次窗口) | D3(E-02 头阻塞,已两阶段修复) / D5 调度 | +| **L5** | 队列往返 | 调用方视角 `net_recv(DONTWAIT)` / `net_send` 全程 = 入队 + netTask 调度 + 处理 + 通知 | MCU(调用方计时),Phase 13,**两档**:静默基线 + 压力档(并发 FTP 流量) | 静默:收 1223µs / 发 988µs(2026-09-22 重锁,P0-b 后;L3 对 DONTWAIT 路径逐位不变,重验留待 Phase 13 下次窗口) | D3(头阻塞,已两阶段修复) / D5 调度 | | **L6** | 请求→首字节 | peer 发 10B 请求 → MCU 收齐并回显(服务端视角);与 PC 侧对照,差值 = 线路 + PC 栈 | MCU + PC 双侧,Phase 13 | MCU 986µs / PC 3.9ms | MCU 收路径/调度 | | **L7** | 整帧 3209B 往返 | LCDMEM 全帧:PC 发 → 收齐(含 L3+L4) | PC 为主,Phase 13 | 61.0ms(M2);**spike(≥500ms)=0** | D1+D2+D4 综合 | | **L8** | 生产路径帧周期 | **真实 uiTask + 真实 `ui_net_sink`**(:7003)完整帧周期:请求拾取(10ms 拍)+ 队列 + 发送 + L4 + peer 侧编码 | MCU 探针 + PC,Phase 14 | 真机 61.6ms / 模拟器 6.8ms(含 peer 100ms 节拍) | 全段(用户真实体感) | | **L9** | 连接/重监听 | connect→ESTABLISHED;close→relisten 成功 | MCU(net_poll 观测点),Phase 14 | relisten **89ms**(D10 修复后) | 握手/队列;**≥500ms = D10 回归** | -| **L10** | 日志税 | 一行 60 字符 `printf`(现 fputc 逐字节 @115200)耗时 | MCU,Phase 13 附带 | **4402µs/行** | D6(per-recv 日志已删 2026-09-22;S8 非阻塞日志仍待办) | +| **L10** | 日志税 | 一行 60 字符 `printf`(现 fputc 逐字节 @115200)耗时 | MCU,Phase 13 附带 | **4402µs/行** | D6(per-recv 日志已删 2026-09-22;非阻塞日志仍待办) | | **L11** | uiTask 节拍 | 生产循环实测周期(`osDelay(10)` 之外多花多少 = LVGL 渲染 + 渲染中排空) | MCU 探针,Phase 14 | **10986µs**(渲染仅 ~1ms/帧) | D5/D9(实测均非瓶颈) | | **Δ** | CH395F 净贡献 | `L8(真机) − L8(模拟器)`,同一 peer 交替跑 | PC,Phase 14 | **54.8ms**(阈值 ≤80ms) | 直接回答"网络延迟"占多少 | @@ -411,14 +411,14 @@ D10 修复后实测 89ms(= 30ms 重试间隔 + 约 60ms 芯片沉降),故 重连 17.1/22.4ms → pending 构建:帧周期 med **58.9ms**(−2.7ms)、尾批 **50.4ms**(−2.2ms)—— 执行器零占用使生产路径净缩短(更早的空闲窗口)。) -**压力档(`--ftp-stress`,E-02 复现;第 25 帧起 FTP 空闲登录保持)** +**压力档(`--ftp-stress`,头阻塞复现;第 25 帧起 FTP 空闲登录保持)** | 分桶 | n | med | p95 | max | 尖峰(≥500ms) | |---|---|---|---|---|---| | quiet(0~24 帧) | 25 | 58.4ms | 69.0ms | 69.8ms | 0 | | stress(25~49 帧,**挂起 recv 构建**) | 25 | **60.1ms** | 66.4ms | **69.3ms** | **0** | -(E-02 修复前 stress 档 med 17718ms、尖峰 1 次 = 17.7s 全网冻结(旧固件);阶段一 5ms 分片修复后 +(头阻塞修复前 stress 档 med 17718ms、尖峰 1 次 = 17.7s 全网冻结(旧固件);阶段一 5ms 分片修复后 med 65.0ms/max 133.5ms(133.5 = 压力启动瞬态单帧);**挂起 recv 化后 med 60.1ms/max 69.3ms** ——p99 尾部较分片档压缩近半(133.5→69.3),MCU 侧 `prod_cycle_mcu` 6706µs 在压力下纹丝不动 (min 6698/max 6720):头阻塞工况不再向 UI 帧周期传导 = 结构性收益。本档重锁 = @@ -444,7 +444,7 @@ med 65.0ms/max 133.5ms(133.5 = 压力启动瞬态单帧);**挂起 recv 化 ¹ L5 两行为 2026-09-22 上午分片时代(M2 B2 档)数值:L3 对 DONTWAIT 快速路径**逐位不变**(dispatch → `net_poll` → `get_recv_len` 读,无 readable 门控),pending tick 无 pending 时为纯指针读零 SPI -(µs 级)——机制上不影响 L5;M2 重验未在本轮执行(低风险,留待 Phase 13 下次窗口,见 `docs/任务规划/README.md` N-3)。 +(µs 级)——机制上不影响 L5;M2 重验未在本轮执行(低风险,留待 Phase 13 下次窗口,见 `docs/任务规划/README.md` 条目「L5 队列往返基线待重验」)。 数据来源:L1/L2 = §6.1;L3/L5/L6/L7 = M2 B2 档(200ms、per-recv 日志已删)串口 `[LAT]` 行 (`m2_data/m2_quiet_r200.json`);L8/L9/L11 = **挂起 recv 构建 2026-09-22 16:42/16:43 重锁** @@ -497,7 +497,7 @@ peer 侧 = `m3_data/m3_pending_*.json`(L3 重锁)与 `m3_data/m3_dev_*_r200. |---|---|---| | ① 同一客户端 → 模拟器 | tail = **0.0ms**,cycle 6.8ms | 客户端栈、Windows、请求格式全部相同 → 尾批**不是 PC 侧**造成 | | ② M2 `L3` 批量排空(16×4096B,全满块) | 65536B / **125.4ms**(≈2ms/KB) | 16 个满块若各带 50ms 尾巴应 ≥800ms → **满块无此惩罚** | -| ③ 压力档(E-02 头阻塞)下的 L7/生产帧 | tail 仍 43~62ms | 尾批**不受 netTask 拥塞影响** → 属芯片 TX 行为 | +| ③ 压力档(头阻塞)下的 L7/生产帧 | tail 仍 43~62ms | 尾批**不受 netTask 拥塞影响** → 属芯片 TX 行为 | ### 7.3 尾批 ~52ms:机理与带内验证(全部不需要抓包) @@ -604,12 +604,12 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 3. 寄存器级定位(如需):首会话复现窗口内打印 `CMD_GET_SOCKET_STATUS_SN`(2 字节)与 SINT 序列; 4. **现场规避**:客户端重试(425 属 RFC 959 瞬态 4xx,重试一次即可)——最省事的临时手段。 -### 8.3 关键实现要点(D10/E-02 修复,供后续维护) +### 8.3 关键实现要点(D10/头阻塞修复,供后续维护) 1. **D10 重试必须防饥饿**:调用方(accept/recv 路径每 10ms 一次)只登记一次重试,实际重试由 netTask 轮询到点执行;若让调用方每次立即重试并重置时刻,会永不重试。重试间隔 100ms→30ms 后恢复时间 147ms→89ms(芯片沉降约 60ms); -2. **(历史,阶段一)E-02 分片长度 = 其他任务的最坏等待**:100ms 片下受压帧仍 ~750ms(多片排队),收到 +2. **(历史,阶段一)头阻塞修复的分片长度 = 其他任务的最坏等待**:100ms 片下受压帧仍 ~750ms(多片排队),收到 **5ms** 片后才降到 ~75ms——该"分片即上限"模型已于 2026-09-22 由 **挂起 recv 化**取代 (`a45c0cb`):执行器零占用后,其他任务的最坏等待 = 一个主循环节拍(~1ms),与任何片长无关 (M3 压力档实测:med 60.1ms/max 69.3ms,p99 尾部较分片档 133.5ms 压缩近半); @@ -625,12 +625,12 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 维护注意:队列中长 SEND/CLOSE 消息处理期间,pending 完成顺延至下轮 tick(~1ms 级;长 SEND stall 期间最多顺延相应时长)——已知代价,勿"优化"回执行器内等待。 -### 8.4 相关既有缺陷(会掩盖/放大延迟;编号为历史审计编号,属主文档已删除,仅作指称) +### 8.4 相关既有缺陷(会掩盖/放大延迟) -| 编号 | 位置 | 与延迟的关系 | +| 缺陷 | 位置 | 与延迟的关系 | |---|---|---| -| **E-04** | `ui_net_sink.c` `remo_send` | 丢弃 `net_send` 返回值 → 弱网短写静默截断,客户端 CRC 不过 → 断连 → 观感"卡完直接黑屏/卡住" | -| **E-33/E-34** | `ch395f.c` SPI DMA/字节命令 | DMA 失败 void 化无错误通道;`HAL_MAX_DELAY` → 一次 SPI 故障可能让 netTask 永久挂死(无看门狗) | +| **`remo_send` 短写** | `ui_net_sink.c` `remo_send` | 丢弃 `net_send` 返回值 → 弱网短写静默截断,客户端 CRC 不过 → 断连 → 观感"卡完直接黑屏/卡住" | +| **SPI 错误通道** | `ch395f.c` SPI DMA/字节命令 | DMA 失败 void 化无错误通道;`HAL_MAX_DELAY` → 一次 SPI 故障可能让 netTask 永久挂死(无看门狗) | --- @@ -642,9 +642,9 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 |---|---|---| | 每帧都**稳定**慢 ≈150~300ms | 结构性基线(§10.1 的 D2/D5/D6/D8) | 跑 §5 的 Phase 13/14 对照基线 | | **间歇**卡,且 ≈200ms 的整数倍(旧固件 500ms) | **D1** 丢包重传 | Wireshark `tcp.analysis.retransmission`;查 PHY/网线/Wi-Fi | -| **间歇**整屏冻结数秒~数十秒 | **E-02** 头阻塞(已修)/ D10 重连(已修)/ 回归 | 查 FTP/对时会话是否挂着;核对 §8 修复在位 | -| 只在开 FTP / 对时后卡 | E-02 + 单连接 socket 争用 | 临时停 ftpTask/timeSyncTask 对照 | -| 换网口/换线/换 PC 就好 | D7 物理链路 | §10.2-S3 拓扑纪律 | +| **间歇**整屏冻结数秒~数十秒 | **头阻塞**(已修)/ D10 重连(已修)/ 回归 | 查 FTP/对时会话是否挂着;核对 §8 修复在位 | +| 只在开 FTP / 对时后卡 | 头阻塞 + 单连接 socket 争用 | 临时停 ftpTask/timeSyncTask 对照 | +| 换网口/换线/换 PC 就好 | D7 物理链路 | 拓扑纪律:核对开机日志 PHY 行 + 交换机端口强制全双工 + PC 有线直连 | ### 9.2 分段对照 @@ -653,7 +653,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 | L1/L2 超 crit | SPI 走线/PCB/SPI2 分频或 DMA 配置 | 对照 `docs/问题记录/CH395F_Trap_Records.md`;逻辑分析仪抓 SCK/CS | | L3 spike ≥200ms(连续台阶;旧固件 500ms) | **D1 重传 / D7 链路** | Wireshark `tcp.analysis.retransmission`;查 PHY 协商/网线/Wi-Fi | | L3 基线(非 spike)抬高 | 芯片发送路径 / 中断响应 | 对照 `docs/芯片手册/CH395F.md` §5.17/5.18(注意:状态寄存器**不被 SET 类命令更新**,`GET_CMD_STATUS` 恒读到 RESET 的 0x10 陈旧残留,2026-09-22 台架实证——不可用作 SET 值验收) | -| L5 压力档爆表 | **E-02 头阻塞(已修)→ 回归** | 定位触发源(阻塞 recv 的 FTP/对时);核对 挂起 recv 在位(`net_recv_locked` 阻塞分支 = 登记 pending 而非等待;`net_pending_tick` 在主循环) | +| L5 压力档爆表 | **头阻塞(已修)→ 回归** | 定位触发源(阻塞 recv 的 FTP/对时);核对 挂起 recv 在位(`net_recv_locked` 阻塞分支 = 登记 pending 而非等待;`net_pending_tick` 在主循环) | | L5/L6 静默档抬高 | D5 调度(任务抢占/节拍) | 查任务优先级与 osDelay;`stack_report.py` 核对栈水位 | | L6/L7 整体抬高且 L1~L5 正常 | 线路+PC 段(L4) | Wireshark 段间间隙;`Δ` 对照定位 MCU 侧净贡献 | | **L7/L8 尾批固定迟到 ~50ms** | 已定性(§7.3):次 MSS 尾段被 Nagle 扣住 + Windows 延迟 ACK | 客户端侧解法已实测(§7.4):`--ack-probe` / `--ack-fast` → tail 52→3.9ms;生产客户端已实装;**设备侧无解**,不必再投入 | @@ -662,15 +662,15 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 | **L7 一直超时 / peer 报 `l7_crc.fail`** | **D15**:帧格式不一致 / peer 超读 | 对照 `ui_net_sink.c` 的 `remo_send`/`remo_handle`(§8 D15) | | **会话跑完一段后无限循环、peer 空等 300s** | **D16**:Phase 13 状态机迁移目标错 | 核对顺序 L5→L3→L10→L6→L7→DONE(§8 D16) | | **`[LAT]` 段行缺失/截断、wait-idle 判不出空闲** | **D12-b**:热路径日志洪水 | 删除 net 热路径逐调用日志(§8 D12-b) | -| **开机日志连续丢行**(含必检标记 `net init: OK` 等)但系统健康 | **日志并发竞态**:多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行、UART 状态短暂卡住(2026-09-22 台架实证:boot 时 testTask 启动行与 netTask 日志并发 → 连续 4 行丢失、~2s 后自恢复) | 必现证据行采用"静默采集 + 延迟到安静窗口打印"模式(范例:P0-a 验收行曾按此模式实装,验收完成后探针已移除);根治 = S8 日志非阻塞/串行化(§10.2) | +| **开机日志连续丢行**(含必检标记 `net init: OK` 等)但系统健康 | **日志并发竞态**:多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行、UART 状态短暂卡住(2026-09-22 台架实证:boot 时 testTask 启动行与 netTask 日志并发 → 连续 4 行丢失、~2s 后自恢复) | 必现证据行采用"静默采集 + 延迟到安静窗口打印"模式(范例:P0-a 验收行曾按此模式实装,验收完成后探针已移除);根治 = 日志非阻塞/串行化(§10.2) | | **`relisten` 段 FAIL、断线固定 ~588ms** | **D10** 回归(退回 `osDelay(500)`) | 核对 `net_listener_request`/`net_listener_tick` 非阻塞重试在位(§8 D10) | -| **远端显示整帧卡住十几秒后自恢复** | **E-02** 回归 | 核对 挂起 recv 在位:`net_recv_locked` 阻塞分支登记 pending(哨兵 `NET_PENDING_REGISTERED`)+ `net_pending_tick` 在主循环 + 调用方剩余预算循环(lftpd/timeSync)(§8.3 要点 4) | +| **远端显示整帧卡住十几秒后自恢复** | **头阻塞回归** | 核对 挂起 recv 在位:`net_recv_locked` 阻塞分支登记 pending(哨兵 `NET_PENDING_REGISTERED`)+ `net_pending_tick` 在主循环 + 调用方剩余预算循环(lftpd/timeSync)(§8.3 要点 4) | | **INIT 应答帧字节数字段恒为 0** | **D17** 回归 | 核对 `remo_send_init` 发满 8B(§8 D17) | | **开机首个 FTP 会话第 2 个数据连接 425** | **D20**(已定位未修,§8.2) | 复现:`test/host/ftp_regression.py`(复位开边界);现场规避:客户端重试 425(瞬态 4xx);根治方向:数据 Socket 轮换 | | **`[LAT]` 多段数值雷同 / 全 0 却 verdict=OK** | **D18/D19**(测试工装) | 核对 `lat_probe_add_seg` 段名过滤 + `lat_time_init` 调用 + `med==0` 判无效 | | **连接建立后 15~30s 无流量即被 RST** | **D13**:芯片空闲超时(芯片行为) | 客户端须容忍并重连(peer `--wait` 重试是正常路径) | | **peer 重连后 MCU 状态滞留、新连接字节被吞** | **D14**:芯片静默回收 Socket | 对账⑤自动认领(手动 `net_resync_listen`/`net_relisten` 已退役,Phase 13 abort 期自愈亦交 net 层) | -| L8 超而 L7 正常 | 生产路径附加成本(uiTask 节拍/LVGL/日志) | 对照 L10/L11 与 §10.1(渲染已排除;per-recv 日志已删,S8 非阻塞日志仍待办) | +| L8 超而 L7 正常 | 生产路径附加成本(uiTask 节拍/LVGL/日志) | 对照 L10/L11 与 §10.1(渲染已排除;per-recv 日志已删,非阻塞日志仍待办) | | Δ 显著 >80ms | CH395F 栈净开销异常 | 回到 L1~L9 逐段排查 | --- @@ -682,12 +682,12 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 | # | 原因 | 实测验证(2026-09-22 台架) | 当前状态 | |---|---|---|---| | D1 | CH395F TCP 重传固定周期(非 RTT 自适应) | 台架链路(有线 100M 全双工)多轮零重传尖峰 → 台架无丢包;**弱网(Wi-Fi/双工不匹配)仍是秒级卡顿首要嫌疑**,200ms 粒度台阶 = 重传指纹(旧固件 500ms) | **已修(P0-a,2026-09-22)**:`NET_RETRANS_PERIOD_MS` 500→**200**(窗口 8×200=1.6s,原 4s)。台架验收说明:`GET_CMD_STATUS` 对 SET 类命令无判别力(500/2000ms 超限对照均读 0x10 陈旧残留,§9.2)→ 接受性以手册范围(≤1000ms)+ 全套件零回归(§6.2/§6.3)+ 与 500ms 行为同构为准。验收探针(3×20ms 状态轮询 + 证据行)验收完成后已从代码移除,结论保留于本文档 | -| D2 | 3209B × 1KB 发送 FIFO 停止等待(4 次串行分块握手,期间 netTask 全程占用) | `chunk_wait` 219µs/块 ≈ SPI 搬运成本的 1/7 → 握手本身**不是瓶颈**;每帧固定成本小(MCU 净 7.1ms) | 基线成本,可接受;根治 = 差量/RLE + 帧版本号(待办 S2) | -| D3 | netMsgQueue 头阻塞(= §8 E-02) | 已量化:一条空闲 FTP 控制连接冻结全网 17.7s | **两阶段闭环**:① 5ms 分片有界读 + 让出(f8d6e90/4a1605b,缓解)② **挂起 recv 化**(`a45c0cb`,结构性——执行器零占用,缺陷类消灭,§8.3 要点 4) | +| D2 | 3209B × 1KB 发送 FIFO 停止等待(4 次串行分块握手,期间 netTask 全程占用) | `chunk_wait` 219µs/块 ≈ SPI 搬运成本的 1/7 → 握手本身**不是瓶颈**;每帧固定成本小(MCU 净 7.1ms) | 基线成本,可接受;根治 = 差量/RLE + 帧版本号(待办) | +| D3 | netMsgQueue 头阻塞 | 已量化:一条空闲 FTP 控制连接冻结全网 17.7s | **两阶段闭环**:① 5ms 分片有界读 + 让出(f8d6e90/4a1605b,缓解)② **挂起 recv 化**(`a45c0cb`,结构性——执行器零占用,缺陷类消灭,§8.3 要点 4) | | D4 | PC 端延迟 ACK 与"每段等 ACK"停止等待交互 | `--ack-fast` 带内实验证实:关掉延迟 ACK 尾批自行消失(52→3.9ms) | **已客户端侧解决**(生产客户端关延迟 ACK/轻推兜底,§7.4) | | D5 | uiTask 节拍:10ms 循环 + 每拍一次 64B recv + step 在 poll 前 | `ui_loop` 10986µs ≈ 设计值(渲染 ~1ms);拾取调度 ~2.3ms(P0-b 删 per-recv 日志后,原 ~6.8ms) | 基线行为;可选优化 S7(poll 前置 + while 排空),低优先 | -| D6 | 阻塞式日志在关键路径(fputc 逐字节 @115200) | `logtax_line` 4402µs/行;per-recv 日志已删(2026-09-22):`queue_rt_recv` 5997→1223µs、帧周期 66.0→61.6ms、首字节 13.9→9.6ms | **部分已修(P0-b,2026-09-22)**;S8 非阻塞日志仍待办(同时是开机日志并发丢行 §9.2 的根治项) | -| D7 | 物理链路:强制 100M 全双工 vs 交换机自协商 → 双工不匹配静默丢包 | 台架 `PHY: 100M FULL` 成立、零尖峰 → 台架干净 | 现场排查项(S3):核对开机日志 PHY 行 + 交换机端口强制全双工 + 验证 PC 有线直连 | +| D6 | 阻塞式日志在关键路径(fputc 逐字节 @115200) | `logtax_line` 4402µs/行;per-recv 日志已删(2026-09-22):`queue_rt_recv` 5997→1223µs、帧周期 66.0→61.6ms、首字节 13.9→9.6ms | **部分已修(P0-b,2026-09-22)**;非阻塞日志仍待办(同时是开机日志并发丢行 §9.2 的根治项) | +| D7 | 物理链路:强制 100M 全双工 vs 交换机自协商 → 双工不匹配静默丢包 | 台架 `PHY: 100M FULL` 成立、零尖峰 → 台架干净 | 现场排查项:核对开机日志 PHY 行 + 交换机端口强制全双工 + 验证 PC 有线直连 | | D8 | 客户端固定成本:100ms 拉取节拍、按键后不立即取帧、1bpp→PNG→base64→WS 编码链 | 生产客户端优化后:帧耗时 20.7ms、显示帧率 ~9fps | 部分已优化(延迟确认抑制已上线);其余待办 S9(按键后立即取帧/不传 PNG/自适应节拍)——客户端仓库 | ### 10.2 待办优化项(按性价比排序,均未实施) @@ -698,7 +698,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 3. **~~P0-b~~ 已实施(2026-09-22)**:删除 `net_socket.c` per-recv `DBG_INFO`;实测 `queue_rt_recv` −80% (5997→1223µs)、生产帧周期 −4.4ms(66.0→61.6ms)、首字节 −4.3ms(13.9→9.6ms); 4. **S1** 超时参数分档:`NET_SEND/RECV_TIMEOUT_MS` 全局一刀切 → 按服务分离(远显短超时+非阻塞,FTP 保留长超时); -5. **S2** 差量/RLE + 帧版本号 + 由拉改推(**根治,收益最大**:典型变化 <100B → 单段一帧,延迟 ≈ 一个 RTT; +5. **差量/RLE + 帧版本号 + 由拉改推**(**根治,收益最大**:典型变化 <100B → 单段一帧,延迟 ≈ 一个 RTT; 稳态带宽 32KB/s → ≈0;D1 影响面 4 段 → 1 段;新命令走同一帧格式与 CRC,旧客户端兼容); 6. **S4** 发送块配额与 MSS:48 块按实际使用的 3 个 socket 重新分配(各 16 块),MSS → 1460 (3209B 从 4 段降到 1~2 段);**约束**:单次写长度必须 ≤ 发送缓冲(TRAP-NET-16 铁律), @@ -706,20 +706,20 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 **另**:MSS/块配额变更**必须**跑可信用例(`test/host/ch395f_socket_test.py tcp_client` 全序列 + 新连接独立 4096B 对照),判据 = **客户端侧内容完全匹配**(勿用带固件内读者的台架配方,其短收是读者吞数据不是故障); 7. **S5** 去掉一次 3200B 拷贝(`g_tx` 直接承接位图,省 ~3.2KB 常驻 RAM;改前跑 `scripts/ram_report.py`); 8. **S7** uiTask:`ui_net_sink_poll()` 前置到 `ui_port_step` 前 + while 排空(省按键一整拍); -9. **~~S8~~ 已实施·形态变更(生产者侧 2026-09-24 单写者;2026-10-25 N-2 合并评估收口)**: +9. **日志非阻塞化:已实施·形态变更(生产者侧 2026-09-24 单写者;2026-10-25 与日志静默观察项合并评估收口)**: `fputc` → 环形缓冲**已落地**(4KB 环 + 单写者 + 事件位;4402µs/行 → µs 级入环, 溢出 = 有界 200ms + dropped 账 + `[ERR]` 留痕;冲刷 = 有界裸发,见 `../子系统设计/控制台单写者日志设计说明.md` §4);**TX 中断/DMA 维持否决** - (该文档 D4),复议绑定 S-3 TX-only 根治后的 `tx_timeout` 实测——N-2 × S8 全文 + (该文档 D4),原绑定的复议条件(根治项落地后实测 `tx_timeout`)已随 2026-10-26 裁定不做而失效——日志静默观察项 × 日志单写者化合并评估全文 结论见同文档文末附录; 10. **S9** 客户端侧(DTU-RemtoeLCD):按键后立即取帧、不传 PNG(直接发 1bpp/差量 + canvas 绘制)、 自适应节拍(帧耗时补偿)、验证 PC 一律有线同交换机; 11. **M4**(可选)`[计划] test/run_latency_test.py` 一键编排(open/clear/flash/wait-idle/peer/collect/merge/verdict)——待排期; 12. **M5**(可选,在役设备)非独占采样器:事件触发、10s 旁路打点入 syslog——设计预留; 13. 协议补位(尾批退路,仅当客户端无法改动时;见 §7.4)。 -14. **日志并发保护(2026-09-22 新增,与 S8 同源、优先级上调)**:开机多任务并发 `printf` 丢整行 +14. **日志并发保护(2026-09-22 新增,与日志非阻塞化同源、优先级上调)**:开机多任务并发 `printf` 丢整行 (实测 §9.2:必检标记 `net init: OK` 曾被竞态吞掉 → 影响开机验收可靠性)。过渡:必现证据行按 - "静默采集 + 延迟到安静窗口打印"模式(范例:P0-a 验收行,验收完成后已移除);根治 = S8(串口 TX 环形缓冲 + "静默采集 + 延迟到安静窗口打印"模式(范例:P0-a 验收行,验收完成后已移除);根治 = 日志非阻塞化(串口 TX 环形缓冲 + 中断/DMA,溢出丢弃策略需定义)。 **现状(2026-10-25)**:根治已随单写者落地——行级原子 = 临界区整行入环、单写者发出, 交错/丢行结构性消除;「写者注册前」窗口为裸发例外,纪律 = `dbg_console_writer_ready()` @@ -754,10 +754,10 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 | `m2_data/m2_quiet_r500.json` | 2026-09-22 A/B 对照档 B1(500ms + per-recv 日志 = 旧基线条件;复跑与 run15 重合 ±0.2%) | | `m2_data/m2_quiet_r200.json` | **2026-09-22 M2 重锁基线** B2(200ms、per-recv 日志已删 = 当前代码) | | `m2_data/README.md` | M2 数据说明(逐帧尾批明细、方法学约束) | -| `m3_data/m3_dev_quiet.json` | 真机 65 帧生产周期(D10/E-02/D17 修复前基线) | +| `m3_data/m3_dev_quiet.json` | 真机 65 帧生产周期(D10/头阻塞/D17 修复前基线) | | `m3_data/m3_dev_quiet_fixed.json` | 真机 65 帧(修复后:帧周期 65.7ms、relisten OK、INIT framebytes=3200) | -| `m3_data/m3_dev_stress.json` | E-02 压力档(修复前:17.7s 尖峰) | -| `m3_data/m3_dev_stress_fixed.json` | E-02 压力档(修复后:med 74.7ms / max 106.8ms,零尖峰) | +| `m3_data/m3_dev_stress.json` | 头阻塞压力档(修复前:17.7s 尖峰) | +| `m3_data/m3_dev_stress_fixed.json` | 头阻塞压力档(修复后:med 74.7ms / max 106.8ms,零尖峰) | | `m3_data/m3_dev_quiet_r200.json` | **2026-09-22 M3 静默档重锁基线**(50 帧,P0-a/P0-b 代码:帧周期 61.6ms) | | `m3_data/m3_dev_stress_r200.json` | **2026-09-22 M3 压力档重锁基线**(45 帧,第 15 帧起 FTP stress:med 65.0ms / max 133.5ms,零尖峰)——分片时代数值,**已被 L3 档取代**(历史对照) | | `m3_data/m3_pending_quiet.json` | **2026-09-22 M3 静默档 L3 重锁基线(当前)**(50 帧,pending 构建 16:42:帧周期 58.9ms / 首字节 9.2ms / 尾批 50.4ms,零尖峰,CRC 50/0) | diff --git a/docs/环境与部署/并行任务工作树方案.md b/docs/环境与部署/并行任务工作树方案.md index 2c2360f7..fb2ebf1f 100644 --- a/docs/环境与部署/并行任务工作树方案.md +++ b/docs/环境与部署/并行任务工作树方案.md @@ -36,7 +36,7 @@ Python 环境、以及 `docs/任务规划/` 这类**全局唯一登记簿**都 2026-09-19 建、已过期),本方案沿用其「仓库同级 + `_` 前缀」命名,但把散落的目录收敛到统一的 `_wt/` 容器下。**两者已于 2026-10-12 清理**:`_v1pin-build` 干净直接移除;`_v2oui-build` 有 2 个 未提交改动(uvprojx / freertos.c),已按字节留档于 `_wt/.archive/2026-10-12-legacy-worktrees/` -后才移除(两提交由 `wip/trap22-fix` / `wip/v2-pending-recv` 持有,无提交丢失)。 +后才移除(两提交由两条 `wip/*` 保活分支持有,其一为 `wip/v2-pending-recv`;无提交丢失)。 --- @@ -209,8 +209,8 @@ D:/Code/DTU Code/ ### 7.3 并行道与写范围规则(规划层 ↔ 执行层映射) -规划层(任务卡在 `docs/任务规划/数据可信.md` 与 `docs/任务规划/采集保护收尾.md`, -五字段 = **DoD / 硬依赖 / 资源 / 写范围 / 状态**;归道见 `docs/任务规划/并行分道方案.md`) +规划层(当前任务卡在 `docs/任务规划/UI补齐.md`,后续事项在 `docs/任务规划/登记簿.md`; +五字段 = **DoD / 硬依赖 / 资源 / 写范围 / 状态**) 按**资源属性**把任务切进三条道,本方案(工作树 + 锁)是它的执行层: | 道 | 资源 | 并行性 | 锁 / 机制 | diff --git a/docs/环境与部署/项目部署环境要求.md b/docs/环境与部署/项目部署环境要求.md index 7f0d8b90..90d88598 100644 --- a/docs/环境与部署/项目部署环境要求.md +++ b/docs/环境与部署/项目部署环境要求.md @@ -408,7 +408,7 @@ pre-commit run --all-files > 获取渠道优先级:① `winget`(自动校验、免手动)→ ② 上表官方页 → ③ 网络受限时自行选可信镜像; > Keil / ST 官网访问受限时可走代理。 -### 2.11 CI runner 环境(Gitea Actions,2026-10-01 T-6) +### 2.11 CI runner 环境(Gitea Actions,2026-10-01 起) CI 跑 **L0 静态门禁 + L0.5 主机侧单测**(**不含 L1/L2/L3**——需真机 + ST-LINK,只在本地)。 workflow 见 `.gitea/workflows/ci.yml`;覆盖边界与判定细节见 [`../测试说明/测试开发标准流程.md`](../测试说明/测试开发标准流程.md) §9。 @@ -533,7 +533,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级 | 开关 | 位置 | 当前值 | 说明 | |---|---|---|---| -| 功能裁剪开关 | — | 无 | **当前没有任何可裁剪的编译开关**(2026-10-22):原 `APP_NET_ENABLE` / `APP_ADC_ENABLE` / `APP_RS485_ENABLE` 三个宏全库零消费点,已按报告 S-4 删除,`App/app/app_cfg.h` 只留决策占位说明。要不要真实门控取决于产品定义(`docs/任务规划/README.md` §2 阶段 B0);在此之前各模块一律编译进产品 | +| 功能裁剪开关 | — | 无 | **当前没有任何可裁剪的编译开关**(2026-10-22):原 `APP_NET_ENABLE` / `APP_ADC_ENABLE` / `APP_RS485_ENABLE` 三个宏全库零消费点,已于 2026-10-22 删除,`App/app/app_cfg.h` 只留决策占位说明。要不要真实门控取决于产品定义(开放问题见 `docs/任务规划/登记簿.md` §9);在此之前各模块一律编译进产品 | | `TEST_SUITE_*` | `test/board/test_config.h` | 全注释(产品构建) | 测试套件**互斥**:一次构建只开一个(CH395F / 存储 / GD5F / RTC);全注释 = 正常产品固件 | --- diff --git a/docs/算法/继电保护功能复现规划.md b/docs/算法/继电保护功能复现规划.md index 3973154e..157a6257 100644 --- a/docs/算法/继电保护功能复现规划.md +++ b/docs/算法/继电保护功能复现规划.md @@ -39,7 +39,7 @@ | 采样硬件 | TPAFE5160(16 位、8 通道**同步**、±5 V 双极性、并行 `GPIOG[15:0]`)+ STM32 内部 ADC1(12 位、9 通道**顺序**扫描、0~3.3 V 单极性) | | 采样软件 | `Drivers/BSP/TPAFE5160/tpafe5160.h` 驱动就绪(原子快照 API);`App/task/adc_task.c` **已实现真实消费者(B1,2026-10-10)**——`tpafe5160_snapshot()` 取数 + 帧统计;**采样节拍由 TIM3 更新事件(1 kHz)硬件触发**(2026-10-10;同一事件经 TRGO 触发 ADC1 + DMA);STM32 侧 **`App/algo/adc_input.c` 已实现语义映射 + ADC1 DMA 乒乓帧消费(L1-3/L1-4,v2.0)**,`HAL_ADC_Start_DMA` 已出现 | | 保护算法层 | **L2 相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `@test`);**L3 元件框架 + 过流/过负荷/过压/欠压/零序过压 + 定值模型软件侧完成**(`prot_elm.c` + `prot_set.c`,2026-10-11,57 项主机单测);**出口 / SOE 记录仍缺失**(P4) | -| 已知硬件缺陷 | **N-18**:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 | +| 已知硬件缺陷 | 登记簿条目「双转换器无共同采样触发」:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 | ### 1.3 复现范围界定 @@ -50,11 +50,11 @@ 3. 保护元件:**V0.1 测量有效子集**——过压/欠压(`UA`)、零序过压(`UN`)、过流(A 相,`IA`)、过负荷(A 相)、过频/低频;三相完整过流、零序过流、涌流制动、PT/CT 断线等**建接口不使能**,待 V0.2(见 §1.6)。 4. 出口与记录:跳闸/告警出口、SOE/动作记录、定值模型与 cfg_file 集成、自检闭锁。 -> ⚠️ **V0.1 硬件裁剪**(2026-10-10 定性):本清单受 **N-18**(跨转换器无同一时刻性)与**"STM32 侧无中点偏置"**两条约束,实际可闭环的保护元件见 **§1.6**——V0.1 上只有 TPAFE 三路(`UA`/`UN`/`IA`)测量有效。 +> ⚠️ **V0.1 硬件裁剪**(2026-10-10 定性):本清单受「双转换器无共同采样触发」(跨转换器无同一时刻性)与**"STM32 侧无中点偏置"**两条约束,实际可闭环的保护元件见 **§1.6**——V0.1 上只有 TPAFE 三路(`UA`/`UN`/`IA`)测量有效。 **本期不做(延后或依赖条件)** -- 方向元件 / 功率 / 功率因数 / 对称分量类运算——受 **N-18** 制约,V0.1 上取子板计量结果,或待 V0.2 硬件改版后实现。 +- 方向元件 / 功率 / 功率因数 / 对称分量类运算——受「双转换器无共同采样触发」制约,V0.1 上取子板计量结果,或待 V0.2 硬件改版后实现。 - 三相完整过流、零序过流、依赖三相的 PT/CT 断线——需 V0.2 硬件(8 路电气量全上 TPAFE + 中点偏置)后启用。 - 重合闸、馈线自动化(FA)等 FTU 专有功能——视产品定义(B0)。 - 双核 / DSP 拆分、VxWorks 特有机制、IEC 61850、完整故障录波——与单核 FreeRTOS 平台不匹配。 @@ -66,7 +66,7 @@ | --- | --- | --- | | **B0 产品定义** | 采集是否持续 / 采样率与通道 / 转发目标 / 保护元件清单——决定 B1 与保护层的**规模** | ✅ 已确认(2026-10-10,见 §1.5) | | **B1 ADC 采集链路** | 周期 CONVST + `adc_task` 消费 `tpafe5160_snapshot()` + 建任务 + 栈预算 | ✅ 已实现并 `@test` 实机闭环(2026-10-10;`adcTask` 512 words,台架 1000 Hz) | -| **N-18 硬件缺陷** | V0.1 跨转换器无同一时刻性 → 限定 V0.1 可实现的保护范围 | ⚠️ 硬件改版项(V0.2) | +| **双转换器无共同采样触发(硬件缺陷)** | V0.1 跨转换器无同一时刻性 → 限定 V0.1 可实现的保护范围 | ⚠️ 硬件改版项(V0.2) | | **栈预算** | 保护任务栈需入 `Src/freertos.c` 并经 `scripts/stack_report.py` 核算 | ✅ 两任务均已核算:`adcTask`(**L1-6 后:栈抬至 640 words,静态 1080 B / 台架实测峰值 952/2560 / 余 57%**);`protectTask`(B5,2026-10-12:**512 words,静态链 872 B / 余 1176 B / 57%**,台架水位待 `@test`) | ### 1.5 B0 产品定义(2026-10-10 确认) @@ -101,7 +101,7 @@ | `4014` | `1U0` 零序电压 | `UN`(外部专用零序电压通道) | | `4015~4017` | `1Ia`、`1Ib`、`1Ic` | `IA`/`IB`/`IC` | | `4018` | `1I0` 零序电流 | `IN`(外部专用零序电流通道) | -| `4019~401c` | `1P`/`1Q`/`1S`/`1Pf` | 计算量(受 **N-18** 制约,V0.1 不可做) | +| `4019~401c` | `1P`/`1Q`/`1S`/`1Pf` | 计算量(受「双转换器无共同采样触发」制约,V0.1 不可做) | | `401d` | `1f` 频率 | 软件测频 | | `401e/f` | `V1`/`V2` 直流量(点表注"FTU 无") | ⚠️ 无对应硬件通道 → **本期不做**(B0-a) | @@ -138,7 +138,7 @@ | # | 约束 | 出处 | 后果 | | --- | --- | --- | --- | -| N-18 | 8 路电气量分属 TPAFE5160 与 STM32 ADC1 **两片**,两片**无共同采样触发** | `ADC通道映射说明.md` §1、登记簿 N-18 | 跨转换器的相位 / 矢量 / 功率 / 功率因数 / 负序运算**不成立** | +| 双转换器无共同采样触发 | 8 路电气量分属 TPAFE5160 与 STM32 ADC1 **两片**,两片**无共同采样触发** | `ADC通道映射说明.md` §1、登记簿条目「双转换器无共同采样触发」 | 跨转换器的相位 / 矢量 / 功率 / 功率因数 / 负序运算**不成立** | | R5 | STM32 侧**无中点偏置**(1k 串联 + 1k 到 `*_GND`),0~3.3 V 单极性 | `ADC通道映射说明.md` §7、本文 §9 R5 | STM32 侧交流量负半周**削顶** → 该侧**算不出有效值 / 相量** | **(2)净效果:V0.1 只有 TPAFE 三路"测量有效"** @@ -158,8 +158,8 @@ | 过负荷(A 相) | `IA` | TPAFE | ✅ | 测量有效 | | 过频 / 低频 | `UA` / `IA` | TPAFE | ✅ | 单通道测频,有效 | | 过流(B / C 相) | `IB` / `IC` | STM32 | ❌ | 削顶,无有效值 | -| 零序过流 | `IN`(外部)/ 自产 `Ia+Ib+Ic` | STM32 / 跨转换器 | ❌ | 削顶 / 跨转换器(N-18) | -| 方向 / 功率 / 功率因数 / 负序 | 多路 | 跨转换器 | ❌ | N-18 | +| 零序过流 | `IN`(外部)/ 自产 `Ia+Ib+Ic` | STM32 / 跨转换器 | ❌ | 削顶 / 跨转换器(双转换器无共同采样触发) | +| 方向 / 功率 / 功率因数 / 负序 | 多路 | 跨转换器 | ❌ | 双转换器无共同采样触发 | | PT / CT 断线 | 三相判据 | 跨转换器 | ❌ | 需三相同时刻 | | 母线接地告警 | `3U0`(UN) + `3I0`(IN) | UN 可 / IN 不可 | ⚠️ 部分 | 仅电压侧可判 | @@ -168,7 +168,7 @@ 采用**"骨架全做 + 有效通道子集闭环"**: - **与通道无关的骨架**(采集触发、`adcTask`、语义映射、标定、全周傅氏、双区交换、统一保护元件接口、定值模型 + cfg_file、出口驱动、SOE / 记录、自检闭锁)——**本期全做**;这是 V0.2 改版后**不需要重构**的核心资产(§6.2 的接口设计启示)。 -- **保护元件**——本期只让**测量有效的通道**闭环动作:`UA`(过压/欠压、测频)、`UN`(零序过压)、`IA`(过流 A 相、过负荷)。其余元件**建接口 + 预置定值但不使能**,待 **V0.2**(N-18 修复 + 补中点偏置,8 路电气量全上 TPAFE)。 +- **保护元件**——本期只让**测量有效的通道**闭环动作:`UA`(过压/欠压、测频)、`UN`(零序过压)、`IA`(过流 A 相、过负荷)。其余元件**建接口 + 预置定值但不使能**,待 **V0.2**(修复「双转换器无共同采样触发」+ 补中点偏置,8 路电气量全上 TPAFE)。 - **控制类 / FTU 专有功能**(重合闸、小电流接地选线、后加速、检合环同期):均依赖三相 / 零序测量或出口硬件,**本期不做**,随 V0.2。 - 即:**本期交付"整条保护链路的软件实现" + "单相/中性线保护闭环验证"**;三相完整过流、零序过流、方向/功率类随 V0.2 硬件启用。 @@ -189,7 +189,7 @@ | **L4 出口与信号** | 跳闸/告警出口、信号灯 | `TripKO` / KO | `App/protect/` + BSP 出口驱动 | | **L5 记录与上送** | SOE/动作记录、UI 显示、遥测上送 | 记录模块 + 通信 | `App/log/` + `Drivers/BSP/UI/` + `App/protocol/` | -> **分层原则**:L1 只做"数"(码值→工程量),L2 只做"相量"(工程量→复相量),L3 只做"判据"(相量 + 定值→布尔),L4/L5 只做"动作与留痕"。**算法与 I/O 分离**,使 L2/L3 成为主机侧单元测试(L0.5)的天然适格对象(落实登记簿 **A-4**)。 +> **分层原则**:L1 只做"数"(码值→工程量),L2 只做"相量"(工程量→复相量),L3 只做"判据"(相量 + 定值→布尔),L4/L5 只做"动作与留痕"。**算法与 I/O 分离**,使 L2/L3 成为主机侧单元测试(L0.5)的天然适格对象(落实 `docs/任务规划/测试与架构专项.md`「采集链路测试可验证性约束」)。 ### 2.2 任务与中断模型(映射到 STM32F4 + FreeRTOS) @@ -248,7 +248,7 @@ | L1-8 | 零序通道接入(外部专用 / 自产切换) | 外部 `UN`/`IN`;自产 `3I0 = Ia+Ib+Ic`(V0.2 起) | 零序通道可切换来源,值正确 | ⬜ | | L1-9 | 二次谐波/五次谐波提取入口 | k=2(涌流)、k=5(选线) | 相量层可产出对应谐波相量 | ✅ 2026-10-11:`phasor.c` 实例表 `C04-IA-F2`(k=2)/ `C05-IA-F5`(k=5)随每拍重算产出,台架心跳日志 `phasor(hb) C04/C05` 行可见(`boot_log_data/20261011_prod_phasor_boot_log.txt`);谐波**抑制/提取正确性**已由主机单测覆盖(`test/host/phasor/`) | -> ⚠️ **N-18 约束**:L1-4 中 `UA/UN/IA` 在 TPAFE、`UB/UC/IB/IC/IN` 在 STM32 ADC。V0.1 上 L1 只做**逐通道独立监视**,不得假设同族量同刻(`UA` 与 `UB` 无共同采样触发)。 +> ⚠️ **「双转换器无共同采样触发」约束**:L1-4 中 `UA/UN/IA` 在 TPAFE、`UB/UC/IB/IC/IN` 在 STM32 ADC。V0.1 上 L1 只做**逐通道独立监视**,不得假设同族量同刻(`UA` 与 `UB` 无共同采样触发)。 ### 3.2 L2 相量计算 @@ -258,7 +258,7 @@ | L2-2 | 基波相量实例 | **5 个实例**(参考项目 4 个,见下方注):`C01-IA-F1` / `C02-UA-F1` / `C03-UN-F1` / `C04-IA-F2` / `C05-IA-F5` | 每实例输出 `{fRe, fIm, fAm, fAmSQ}` | ✅ 2026-10-11:`k_inst[]` 数据驱动 5 实例;台架开机即产出 `phasor(first)` 5 行(`boot_log_data/20261011_prod_phasor_boot_log.txt`) | | L2-3 | 相量有效值输出 | `fAmSQ = √(fRe²+fIm²)` 直接为**有效值**(系数已折算) | 标准源读数 = 设定有效值 | ✅ 2026-10-11(链路级):`fAmSQ` 口径实现 + 单测合成正弦幅值误差 ≤1%;**"标准源读数"判据在无信号源台架不可达**(同 §7.1),待 L1-5 标定后复核 | | L2-4 | 相位输出 | `φ = atan2(fIm, fRe)` | 相位误差 ≤ 目标值 | ✅ 2026-10-11(链路级):`fRe/fIm` 常驻双区、每拍产出,φ 由消费侧 `atan2f` 现算(不常驻结构体,见 `docs/算法/采集与算法层设计说明.md` §6);单测覆盖相位;**目标值待产品定义 + 标准源复核** | -| L2-5 | 线/负序/零序合成 | 线电压 `Uab=Ua−Ub` 等;负序 `3U2`/`3I2`;零序 `3U0`/`3I0` | 合成相量可复用于保护元件(**V0.1 仅同转换器内可合成**,见 §1.6) | ⬜ **V0.1 不适用**:跨转换器合成被 N-18 禁止;零序走**外部专用通道** `UN`/`IN`(§6.4),无需自产 → 随 V0.2 启用 | +| L2-5 | 线/负序/零序合成 | 线电压 `Uab=Ua−Ub` 等;负序 `3U2`/`3I2`;零序 `3U0`/`3I0` | 合成相量可复用于保护元件(**V0.1 仅同转换器内可合成**,见 §1.6) | ⬜ **V0.1 不适用**:跨转换器合成被「双转换器无共同采样触发」禁止;零序走**外部专用通道** `UN`/`IN`(§6.4),无需自产 → 随 V0.2 启用 | | L2-6 | 每 1 ms 滑动窗刷新 | 每来新点,窗滑动一格、20 点重算(暴力法) | 相量每拍更新 | ✅ 2026-10-11:`sample_ring` 40 点环 + `phasor_run()` 每拍整窗重算(仅 `r==1` 新帧压环,P0 修复);台架 `ring` 随 `frames` 同步递增、`idle=0` | | L2-7 | 双区指针原子交换 | 计算区/使用区切换 | 保护元件读使用区,无撕裂 | ✅ 2026-10-11:`s_zone[2][5]` + 单字节 `volatile` 切换(Cortex-M 原子);单测覆盖双区交换;为独立 L3 任务预留无锁读 | | L2-8 | 遥测谐波轨(可选) | k=1~9,方和根 `√(ΣHk²)` | 与基波轨并行,供 UI/上送 | ⬜ | @@ -267,7 +267,7 @@ > **L2-2 实例数 4 → 5 的来历**:参考项目(`APP/CLS100_Dsp/InstCalCell.c`)为 **4 个实例**——C01 基波 I/U、C02 零序基波、C03 二次谐波、C04 五次谐波。本项目按 V0.1 **有效通道**(`UA`/`UN`/`IA`,见 §1.6:`UB/UC/IB/IC/IN` 无中点偏置 → 削顶不可用)展开:C01 的"I/U"拆为两行(`C01-IA-F1` 电流 / `C02-UA-F1` 电压),其余顺延为 `C03-UN-F1` 零序、`C04-IA-F2` 二次、`C05-IA-F5` 五次。实现见 `App/algo/phasor.c` 的 `k_inst[]`;判据按 **5** 个实例断言(见 `docs/测试说明/相量层单元测试说明.md` §2.6)。 -> **本表状态列口径**(2026-10-11 更新):L2-1~L2-4 / L2-6 / L2-7 已**实现 + 主机单测(57 用例)+ 台架 `@test` 闭环**——闭环命令 `run_boot_test.py --required "...;phasor(first);phasor(hb)" --require-order "App init OK>phasor(first)>phasor(hb)"`,证据 `boot_log_data/20261011_prod_phasor_boot_log.txt`(`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`)。⚠ **幅值/相位精度判据(标准源)在无信号源台架上不可达**(同 §7.1 无示波器口径),且 L1-5 标定未实现 → 现读数为**原始码值**,只证明链路通;精度复核排在标定之后。L2-5 随 V0.2(N-18),L2-8 可选项未做。 +> **本表状态列口径**(2026-10-11 更新):L2-1~L2-4 / L2-6 / L2-7 已**实现 + 主机单测(57 用例)+ 台架 `@test` 闭环**——闭环命令 `run_boot_test.py --required "...;phasor(first);phasor(hb)" --require-order "App init OK>phasor(first)>phasor(hb)"`,证据 `boot_log_data/20261011_prod_phasor_boot_log.txt`(`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`)。⚠ **幅值/相位精度判据(标准源)在无信号源台架上不可达**(同 §7.1 无示波器口径),且 L1-5 标定未实现 → 现读数为**原始码值**,只证明链路通;精度复核排在标定之后。L2-5 随 V0.2(双转换器无共同采样触发),L2-8 可选项未做。 ### 3.3 L3 保护元件 @@ -278,11 +278,11 @@ | # | 元件 | 判据要点 | 关键定值 | 状态 | | --- | --- | --- | --- | --- | | L3-1 | 三段式相间过流 | 相电流基波 > 定值,经延时出口;返回系数滞回 | Ⅰ/Ⅱ/Ⅲ 段电流 + 延时 | 🔶 2026-10-11 主机实现:`App/algo/prot_elm.c` 通用"滞回+定时限"核 + `prot_set.c` 定值(Ⅰ/Ⅱ/Ⅲ 段,A 相 `IA`),单测 27 用例含 1.01× 启动/100 ms 到点/滞回保持/返回释放;**板端接线已落地(B5,2026-10-12:protectTask 20 ms 节拍,动作经 104 YX16~18 上送)**;出口(L4)待做 | -| L3-2 | 三段式零序过流 | 零序电流 > 定值,经延时出口 | Ⅰ/Ⅱ/Ⅲ 段零序电流 + 延时 | ⬜(N-18:`IN` 走 STM32,V0.2 使能;框架已就绪,加实例表一行即可) | +| L3-2 | 三段式零序过流 | 零序电流 > 定值,经延时出口 | Ⅰ/Ⅱ/Ⅲ 段零序电流 + 延时 | ⬜(双转换器无共同采样触发:`IN` 走 STM32,V0.2 使能;框架已就绪,加实例表一行即可) | | L3-3 | 过负荷 | 相电流 > 定值,告警/跳闸可整定 | 过负荷电流 + 延时 | 🔶 同 L3-1(`OL-IA` 实例,默认告警不跳闸;板端接线随 B5 落地,YX23 上送) | | L3-4 | 过压 / 欠压 | 相/线电压越限,经延时出口 | 过压定值、失压定值 + 延时 | 🔶 同 L3-1(`OV-UA`/`UV-UA`,欠量方向单测覆盖;沿用参考告警段 0x8027/0x8029) | | L3-5 | 过频 / 低频 | 频率越限,经延时出口 | 过频、低频定值 + 延时 | ✅ 软件侧完成(B8,2026-10-12):`freq` 层测频(自适应窗,45~55 Hz 扫频最坏 **0.0101 Hz** / 1% 噪声 0.0160 Hz)+ `OF/UF` 实例(51.0/49.0 Hz、dly 1000 ms、返回系数 0.99/1.01,本地定值 8 项);OF/UF 暂无独立遥信点,动作仅经 YX15 总聚合可见(独立分点 + 注入验收 = B10) | -| L3-6 | 零序过压 | 零序电压 > 定值 | 零序过压定值 + 延时 | 🔶 同 L3-1(`3U0-UN` 实例,外部零序电压通道,不受 N-18 限制) | +| L3-6 | 零序过压 | 零序电压 > 定值 | 零序过压定值 + 延时 | 🔶 同 L3-1(`3U0-UN` 实例,外部零序电压通道,不受双转换器无共同采样触发限制) | **扩展集(P2/P3)** @@ -295,7 +295,7 @@ | L3-11 | 故障启动(波形启动) | 突变量启动,触发录波/启动元件 | ⬜ | | L3-12 | 无压/无流闭锁 | 低电压或低电流时闭锁过流 | ⬜ | -**延后集(受 N-18 / 产品定义制约)** +**延后集(受双转换器无共同采样触发 / 产品定义制约)** | # | 元件 | 制约 | | --- | --- | --- | @@ -426,10 +426,10 @@ | --- | --- | --- | --- | | AD 器件 | 专用同步采样 AD | TPAFE5160(16 位、8 通道同步、±5 V)+ STM32 ADC1(12 位、顺序、0~3.3 V) | 统一到"工程量"层,屏蔽码值差异 | | 通道数 | 8 实通道(4U+4I,自产零序) | 8 路电气量(4U+4I)+ 温度 | 语义映射见 `docs/硬件平台/ADC通道映射说明.md` | -| 同一时刻性 | 单芯片同步 | **跨转换器无共同触发**(N-18) | V0.1 只做逐通道独立监视 | +| 同一时刻性 | 单芯片同步 | **跨转换器无共同触发**(登记簿条目「双转换器无共同采样触发」) | V0.1 只做逐通道独立监视 | | 量程/精度 | 一致 | TPAFE LSB ≈ 152.6 µV vs STM32 LSB ≈ 806 µV | 同族量不可统一算法处理 | -### 6.2 N-18 影响与降级口径 +### 6.2 双转换器无共同采样触发:影响与降级口径 - **V0.1 现板**:主板侧只做**逐通道独立监视**(有效值/越限/温度);**禁止**跨转换器的同周期相位、功率、功率因数、矢量类运算。 - **受影响功能**:方向元件、功率方向、差动类——延后(L3-13)。 @@ -449,7 +449,7 @@ - 参考:TLV13 选择映射表——外部零序(专用通道)或**自产零序**(`3I0 = Ia+Ib+Ic`)。 - 本项目 V0.1 实况(**2026-10-10 更正**,此前"V0.1 无专用零序通道"的表述作废): - **有外部零序专用通道**——`UN`(零序电压,TPAFE)与 `IN`(零序电流,STM32 ADC),对应点表 `4014` / `4018`; - - **自产零序在 V0.1 不可行**:`Ia` 在 TPAFE、`Ib`/`Ic` 在 STM32,求和属**跨转换器**(N-18)。 + - **自产零序在 V0.1 不可行**:`Ia` 在 TPAFE、`Ib`/`Ic` 在 STM32,求和属**跨转换器**(双转换器无共同采样触发)。 - 于是本期:**零序过压**(`UN`,TPAFE,测量有效)可做;**零序过流**(`IN`,STM32 削顶 → 无有效值)不可做。 - V0.2(8 路电气量全上 TPAFE)后可自产,外部/自产可切换(恢复 TLV13 语义)。 - 落点:L1-8。 @@ -477,7 +477,7 @@ | **P4** | 出口、记录与定值集成 | L4 出口与记录、§5 定值模型落 cfg_file、UI 显示、上送 | P1 | 动作出口 + SOE 记录 + UI 显示闭环(**定值持久化 + 软压板 + 竞态收口 = B7 完成 2026-10-12,台架腿已验 ✅**;**SOE/动作记录 = B9 完成 2026-10-12 ✅**:protectTask 边沿→SPSC 环→defaultTask 落盘 `/log/soe.log`,protectTask 静态链 872 B 不变,自发 YX 上送 M_SP_TB_1 带时标;出口继电器 L4-1~L4-3 仍 ⬜——V0.1 无出口硬件,跳闸维持否定确认 R7 口径) | | **P5** | 方向/功率类(延后) | L3-13 方向元件等 | V0.2 硬件 / 子板计量 | 跨转换器相位一致后实现 | -> ⚠️ **V0.1 硬件裁剪**(2026-10-10,详见 §1.6):V0.1 上只有 TPAFE 三路(`UA`/`UN`/`IA`)测量有效。故 **P0 骨架全做**(与通道无关),**P1/P2 只做有效通道子集**——A 相过流、A 相过负荷、过压/欠压、零序过压、过频/低频;**三相过流(B/C 相)、零序过流、方向/功率类、PT/CT 断线**依赖 V0.2 硬件(N-18 修复 + 中点偏置),本期**建接口不使能**。 +> ⚠️ **V0.1 硬件裁剪**(2026-10-10,详见 §1.6):V0.1 上只有 TPAFE 三路(`UA`/`UN`/`IA`)测量有效。故 **P0 骨架全做**(与通道无关),**P1/P2 只做有效通道子集**——A 相过流、A 相过负荷、过压/欠压、零序过压、过频/低频;**三相过流(B/C 相)、零序过流、方向/功率类、PT/CT 断线**依赖 V0.2 硬件(修复「双转换器无共同采样触发」+ 中点偏置),本期**建接口不使能**。 ### 7.2 每阶段交付物与验收判据 @@ -516,9 +516,9 @@ B0 产品定义 ✅(2026-10-10 已确认,见 §1.5) | L1 板端套件 | 采样/相量/保护元件 | `test/run_all_suites.py` 板端套件 | | L2/L3 台架 | 标准源注入、动作出口、时序 | 台架闭环(COM6 @115200) | -### 8.2 A-4 约束落实 +### 8.2 `docs/任务规划/测试与架构专项.md`「采集链路测试可验证性约束」落实 -登记簿 **A-4** 对采集链路提出三条设计约束,保护层须一并满足: +`docs/任务规划/测试与架构专项.md`「采集链路测试可验证性约束」对采集链路提出三条设计约束,保护层须一并满足: 1. **协议解析与硬件 I/O 分离**——保护判据函数只吃相量 + 定值,可单测; 2. **采集缓冲双缓冲 + 序号**——防撕裂(单测测不出时序撕裂,须设计上消除); @@ -538,8 +538,8 @@ B0 产品定义 ✅(2026-10-10 已确认,见 §1.5) | # | 风险 / 开放问题 | 影响 | 处置方向 | | --- | --- | --- | --- | -| R1 | **V0.1 硬件裁剪保护范围**(N-18 + 无中点偏置 → 仅 TPAFE 三路 `UA`/`UN`/`IA` 测量有效) | 三相过流 / 零序过流 / 方向功率类本期无法闭环 | 本期做有效通道子集(§1.6);其余随 V0.2 硬件启用 | -| R2 | **N-18 硬件缺陷** | 方向/功率/矢量类无法在 V0.1 实现 | V0.2 硬件改版;V0.1 取子板计量 | +| R1 | **V0.1 硬件裁剪保护范围**(双转换器无共同采样触发 + 无中点偏置 → 仅 TPAFE 三路 `UA`/`UN`/`IA` 测量有效) | 三相过流 / 零序过流 / 方向功率类本期无法闭环 | 本期做有效通道子集(§1.6);其余随 V0.2 硬件启用 | +| R2 | **双转换器无共同采样触发(硬件缺陷)** | 方向/功率/矢量类无法在 V0.1 实现 | V0.2 硬件改版;V0.1 取子板计量 | | R3 | **单核算力** | 1 ms 节拍内完成傅氏 + 全部保护元件 | 实测 CPU 余量;必要时裁剪遥测谐波轨或降低元件扫描频率 | | R4 | **等周波调频实现** | 频偏时精度不达标 | TIM 重装载调频;先固定 1000 Hz 跑通再上闭环 | | R5 | **STM32 侧无中点偏置** | 交流量负半周削顶,无法算 RMS | V0.1 只做监视;V0.2 补偏置 | diff --git a/docs/算法/采样有效性校验设计说明.md b/docs/算法/采样有效性校验设计说明.md index ef0eda8a..ec460ba6 100644 --- a/docs/算法/采样有效性校验设计说明.md +++ b/docs/算法/采样有效性校验设计说明.md @@ -92,7 +92,7 @@ 两条边界是刻意的,破坏它们的代价写在右边: -1. **ISR 侧只加计数,不在中断里打日志**——FRSTDATA 异常只累计(E-44),帧序号只自增(E-45)。 +1. **ISR 侧只加计数,不在中断里打日志**——FRSTDATA 异常只累计,帧序号只自增。 中断里做重活会直接推后下一拍采样,见 `docs/子系统设计/ADC采样节拍设计说明.md` §5。 2. **判定层不许出现 HAL / 日志 / 取数**——`test/host/adc_health/adc_health_test.c` 用 `#include` 原样纳入生产源文件、**零桩**跑全套。判定层里多一个 `DBG_ERROR`,主机侧就要为它写一份桩, @@ -283,7 +283,7 @@ else { 清电平位 } /* 只有 stall 类有 else 分支,SEQ_BACKWARD 没有 | 计数(快照字段) | 生产者 | 正常增量 | 出现增量意味着 | 事件位 | 锁存位 | 闭锁 | |---|---|---|---|---|---|---| -| `tpafe_frst_err` | TPAFE BUSY EXTI | 0 | 该帧 8 个通道不可信(FRSTDATA 无跳变,E-44) | `ADC_HE_FRSTDATA` | `ADC_HF_FRSTDATA` | 否 | +| `tpafe_frst_err` | TPAFE BUSY EXTI | 0 | 该帧 8 个通道不可信(FRSTDATA 无跳变) | `ADC_HE_FRSTDATA` | `ADC_HF_FRSTDATA` | 否 | | `stm32_errs` | L1 消费侧 | 0 | 连续 >=2 拍无新帧,或 DMA 挂载失败 | `ADC_HE_STM32_ERR` | `ADC_HF_STM32_ERR` | 否 | | `stm32_gaps` | L1 消费侧 | 少量 | 超拍丢帧(§6.2) | `ADC_HE_STM32_GAP` | 不入锁存 | 否 | | `stm32_hw_errs` | HAL ADC/DMA 错误回调 | 0 | 硬件层面报过错 | `ADC_HE_HW_ERR` | `ADC_HF_HW_ERR` | 否 | diff --git a/docs/算法/采集与算法层设计说明.md b/docs/算法/采集与算法层设计说明.md index 9d7fb6f9..b6459eae 100644 --- a/docs/算法/采集与算法层设计说明.md +++ b/docs/算法/采集与算法层设计说明.md @@ -39,7 +39,7 @@ ADC1)、不做出口动作。 - **这样分离换到什么**:`sample_ring` / `phasor` / `adc_health` / `prot_set` / `prot_elm` / `freq` **零平台依赖**(只用 `stdint.h` / `math.h` / `stddef.h` 与本目录头),因此可被主机侧 - 单测以 `#include` 原样纳入、离线全覆盖(落实登记簿 **A-4**),不需要真机就能钉死判据。 + 单测以 `#include` 原样纳入、离线全覆盖(落实 `docs/任务规划/测试与架构专项.md`「采集链路测试可验证性约束」),不需要真机就能钉死判据。 - **上下文约束**:`*_init()` 一律无 RTOS 依赖、幂等,由 `app_main_init()` 在**内核启动前**调用; `adc_input_poll()` / `phasor_run()` / `freq_run()` **仅任务上下文**(快照走临界区)。 - **消费者在 `App/task/`**:`adcTask` 采集并压环、`protectTask`(B5)跑 `prot_elm_run()`; @@ -85,7 +85,7 @@ flowchart TD ## 3. L1 语义取数 `adc_input.c` -**解决的问题**:V0.1 上 8 路电气量被两片转换器分片(硬件缺陷 **N-18**),且网名 `AINn` +**解决的问题**:V0.1 上 8 路电气量被两片转换器分片(硬件缺陷**双转换器无共同采样触发**),且网名 `AINn` ≠ 板级 `ADCn` ≠ 转换器通道号(17 路里 13 路置换)。消费侧若直接索引驱动缓冲,改版即全线错位。 本层把"哪一路"收进一张表 `k_adc_map[]`,上层**按语义取数、不写死下标**。 @@ -105,7 +105,7 @@ flowchart TD - **STM32 侧是顺序扫描**(Rank1..8 = CH0..CH7、Rank9 = CH14,与 `MX_ADC1_Init()` 严格一致), 9 个通道**不是同一时刻**(一轮 ≈67 µs);由 TIM3 TRGO 硬件触发 + DMA 自动搬运( `docs/子系统设计/ADC采样节拍设计说明.md` §4)。STM32 侧数据**固定滞后 1 拍(1 ms)**——与 TPAFE - 侧本来就没有同刻性(N-18),无新增误差。 + 侧本来就没有同刻性(双转换器无共同采样触发),无新增误差。 - **开机映射自检**挡住"映射到一个从不扫描的通道"这类静默错(启动日志 `ADC input layer init: OK`)。 - 本文件是本层**唯一**允许出现 HAL 调用的地方;新增模块若不需要外设,请保持 `sample_ring` / `phasor` 那种零依赖形态,才能免桩单测。 @@ -233,7 +233,7 @@ flowchart TD 无有效波形(浮空 / PT 断线时 rms ≈ 0,相位无意义)→ 停止更新、`valid` 清零、读数保持, 台架浮空因此**不发散**;信号刚消失的过渡段(≤20 ms 残窗)读数漂移有界 ≤0.3 Hz。 - **源优先级 UA(C02) → IA(C01)**,按名前缀解析、每拍动态回退(台架只注电流也能测频); - **源切换即废相位基准重建**——UA 与 IA 之间没有共同相位基准(N-18),跨源相位差无意义。 + **源切换即废相位基准重建**——UA 与 IA 之间没有共同相位基准(双转换器无共同采样触发),跨源相位差无意义。 ### 7.2 精度判据(J1~J6,与 `test/host/freq/freq_test.c` 文件头同口径) @@ -321,7 +321,7 @@ UA 无幅值落 IA → 0.0020 Hz。 - 相量类**任一未就绪 → 整拍跳过**(不给判据喂陈旧值); - 频率类**无有效读数 → 按方向填"必不启动"值**(过量填 `0`、欠量填 `1e30` → 滞回比较恒假 → 元件复位),**不整拍跳过**——频率源缺失不得饿死电流 / 电压类元件。 -- **V0.1 实例表**(受 N-18 约束):`OC1 / OC2 / OC3 / OL`(IA)、`OV / UV`(UA)、`3U0` +- **V0.1 实例表**(受双转换器无共同采样触发约束):`OC1 / OC2 / OC3 / OL`(IA)、`OV / UV`(UA)、`3U0` (UN 外部通道)、`OF / UF`(`freq` 测频,B8)。**零序过流(IN 走 STM32)故意不在表中** ——跨转换器不构成同刻矢量。 - **OF / UF 的出口可见性**:两元件**未单独映射 104 遥信点**(送检点表口径见 @@ -338,7 +338,7 @@ UA 无幅值落 IA → 0.0020 Hz。 1. **语义映射只有一处权威**:`docs/硬件平台/ADC通道映射说明.md`。取数一律经 `adc_input_raw()`, **禁止**在消费侧直接索引驱动缓冲或写死下标。 -2. **禁止跨转换器运算**(V0.1 硬件缺陷 **N-18**):`UA/UN/IA` 在 TPAFE、`UB/UC/IB/IC/IN` 在 +2. **禁止跨转换器运算**(V0.1 硬件缺陷**双转换器无共同采样触发**):`UA/UN/IA` 在 TPAFE、`UB/UC/IB/IC/IN` 在 STM32 ADC1,两片转换器没有共同采样触发 → 无同一时刻性、相位不确定。故本层**不做**跨转换器的 相位 / 功率 / 功率因数 / 负序零序类矢量运算;**8 路全接 TPAFE(V0.2 硬件)后方可解禁**。 3. **标定折算已落地、精度复核未做**(2026-10-25 `App/algo/adc_cal`;标准源两点注入见 diff --git a/docs/编号规范.md b/docs/编号规范.md index 87d64cc3..f31a3a44 100644 --- a/docs/编号规范.md +++ b/docs/编号规范.md @@ -1,604 +1,224 @@ -# 编号规范 +# 编号规范:默认**不**用编号 -> **适用对象**:本仓库全部 Markdown 文档、C/Python/脚本源码注释、提交信息中出现的**实体编号**(`E-44`、`N-21`、`TC-NET-601`、`FR-STO-1`、`Trap 07` 这类"字母 + 数字"的条目身份标识)。 -> **不适用**:C 语言标识符(`g_`/`s_`/`PS_*`/`TEST_SUITE_*`)、元器件位号(`U1`/`C12`/`R3`)、章节号(`§3.2`,归 [`docs/文档规范.md`](文档规范.md) §3.5 管)——见下文「划界」一节。 -> **强制程度**:§2~§5 为**强制**(由 `scripts/check_ids.py` 机械校验,§8 已落地 warn-only);§6~§10 为**约定 + 迁移计划**。 -> **相关文档**:[`docs/文档规范.md`](文档规范.md)(链接/章节/门禁总规范)、[`docs/任务规划/README.md`](任务规划/README.md) §0(本规范的登记簿摘要)、[`docs/测试说明/测试文档模板规范.md`](测试说明/测试文档模板规范.md) §6(测试侧编号细则)、[`docs/门禁说明/id-check.md`](门禁说明/id-check.md)(`id-check` 钩子逐项详解,含限制与盲区)。 -> **状态**:**§8 门禁已落地**(warn-only,含 `scripts/id_registry.json` / `scripts/id_aliases.json`,实测基线见 §8.1);§4 的目标前缀与 §7 的迁移映射在阶段 1 落地前**尚未生效**,落地进度见 §9。 +> **一句话:能把事实写清楚,就别发号。** 全项目允许使用「字母+数字」编号的族只有 **8 个**(§4.1), +> 它们的共同点只有一个——**有脚本按字面量匹配它**。除此之外,一律写语义短语(§3.3)。 +> +> 这条不是洁癖:规划 / 待办 / 审计 / 差距 / 阶段这些给人看的东西,历史上全发过号,也全都变成了 +> 没人认得的代号。2026-10-26 一天清了两批 + 本文瘦身,编号字面量 **10,956 → 8,230 处**(§8.1),清掉的号里 +> 绝大多数从来没被任何脚本读过一次。沿革与教训留在 §8.3 与 git log,正文不复述。 +> +> **机器面才是事实源**:族注册表 `scripts/id_registry.json`、旧号别名 `scripts/id_aliases.json`、 +> 门禁 `scripts/check_ids.py`(八项校验,详解 `docs/门禁说明/id-check.md`)。本文与机器面冲突时 +> **以机器面为准,并回来改本文**。 --- -## 1. 为什么要重写编号体系 +## 1. 决定要不要发号之前,先问三句 -### 1.1 病根:前缀按"来源"分配,不按"事物类别"分配 - -每来一次审计/复审/专项,就自造一套前缀,字母很快瓜分殆尽: - -| 来源 | 自造的前缀 | +| 问自己 | 答"否"的结论 | |---|---| -| 2026-09-21 错误码传播审计 | `E-xx`、缺陷模式 `P1~P9`、严重度 `P0~P2` | -| 2026-09-15/09-21 代码审计 | `P0-x` ~ `P3-x`(严重度直接进编号) | -| 2026-09 项目审查与优化建议报告(**2026-10-25 已删除**) | `N-xx`、`D-x`、`S-x`、`R-x`、`F-x`、`U1~U5`(六族属主文档已删,存量冻结作历史指称) | -| 台架与长期观察 | `N-x`、`S-x`、`T-x`、`gen`、`round`、`V2/V4/V5` | -| 2026-10-02 复审 | `S1~S14`、`A-1~A-7` | -| 任务规划 | `W`、`B\d`、`C\d`、`D\d`、`M\d` | -| 各模块陷阱/设计文档 | `Trap NN`、`陷阱 N`、`D1~D10`、`O1~O6`、`U1~U15`、`①~④` | +| 有脚本 / CI / 回归测试要按这个字面量做匹配吗? | **不发号**,写语义短语(§3.3) | +| 现有文档或登记表里已经能指代这件事吗? | 引用它,别再发第二个号指同一件事 | +| 一年后别人还能在属主登记表查到它的定义吗? | 查不到就别发——"先记着,以后再说"是编号失控的唯一来源 | -**后果**:同一字母被 3~5 个来源共用,编号本身不再能定位事物。 +三条硬规矩(对确实要发的号): -### 1.2 已确证的事故(全部有仓内证据,非假想) +- **号不承载语义**:严重度、优先级、模块名、轮次都不写进号里。事实一变,号就开始撒谎。 +- **号不回收、不复用**:销项 = 删登记行 + 清掉所有引用 + 别名表标 `void`(§5.5)。历史上"销掉的号被 + 重新取走"撞过两次。 +- **引用必须带语义**:号旁边要有人话,裸号不许独担信息(读者不该被迫查表才看懂一句话)。 -| 类型 | 实锤 | -|---|---| -| **同字母多义** | `P1` 五义(缺陷模式 / 严重度 / 审计条目 `P1-8` / 用例优先级 / `ch395f_test_task.c` 的 Phase1 测试项 `P1-01`);`L1` 四义(算法层 / 测试分层 / UI 四层 / CubeMX 三层);`U1` 四义;`O4` 四篇文档四义 | -| **同一文件内双义** | `实机测试实施指南.md` 的 `A2/A3/A4` 既是环境预检项(§2)又是已完成事项(§10) | -| **连字符/补零偷偷承载语义** | `R1`=风险、`R-2`=内存优化项;`D7`=验证窗口、`D-7`=文档门禁、`D1~D7`=设计决策;`N-2`(登记簿)与 `N-02`(报告)同号不同义 | -| **跨文档同号不同义** | `Trap 02` 在 CH395F 是"缓冲区重叠"、在 GD5F 是"测试缓冲越界";`B10` 在规划是"送检窗口"、在陷阱审计是"孤儿监听" | -| **语义进编号 → 事实一变编号就撒谎** | `P1-x` 把严重度写进号里,而审计中大量条目事后降级(`E-59` P0→P1、dhara `is_free` P1→P2):改号则引用全断,不改则号实不符 | -| **孤儿编号(代码有、文档无)** | `I-1`/`I-2`、`Task #32` 会话号、`P1-01~P1-09`、`TC-GD5F-1100/1101/1102`、`TC-STORAGE-06`、`TC-AH-*`、`TC-PE-*`(前两项项目已在 `docs/测试说明/实机测试实施指南.md` 自行承认) | -| **悬空引用(引用了不存在的号)** | `Trap 19`(裸形;**当时的 NET 域在册表里没有这一号**——别误读成"这个坑不存在",2026-10-13 全库重排后该域确有 `TRAP-NET-19` 这条真实条目,两回事)曾被 `docs/测试说明/CH395F_Test_Guide.md` 引用;`N-5/N-8/N-19/N-30/N-31/N-32` 销项删行后仍被引用(`N-32` = 监听生命周期模块化,被 `docs/网络/` 三篇与代码注释多处引用) | -| **同一事物多种字面** | `Trap 07` / `TRAP-07` / `陷阱 1`;`gen 181` / `gen-288` / `gen68`;`TC-NET-704` / `TC704` / `TC-203` | -| **规范自身就不自洽** | `docs/测试说明/测试文档模板规范.md` §6.3 同一行并列 `TC-STORAGE-03`(2 位)与 `TC-RTC-001`(3 位);`docs/测试说明/测试文档模板规范.md` §6.2 已废止 `PHY` 模块码,而阶段 ID 至今仍是 `TM-PHY-01` | -| **机器校验只覆盖一个族** | (2026-10-15 现状,2026-10-20 已由 §8.1 收口)当时 31 个 pre-commit 钩子中只有 `TC` 有 `scripts/check_test_traceability.py`,且**未挂钩子、warn-only、正则 `TC-[A-Za-z0-9]+-\d+` 匹配不到大写后缀形态**;`E/N/S/B/C/D/A/U/O/gen/Trap` 全部裸奔 | +### 1.2 这四类事故就是上面三条的由来 -> **可借鉴的先例**:`§N.M` 章节引用当年 1,578 处也全靠自觉,靠「紧邻写法 + 别名表 + 可判定比例 + 钩子」把可判定比例做到 58% 并转为拦提交([`docs/文档规范.md`](文档规范.md) §3.5)。编号走同一条路径即可收口。 +1. **同一字母多义**:`P1` 五义、`L1` 四义、`U1` 四义、`D7` 与元器件位号同形 → 前缀必须按**事物类别** + 分配(§2)。 +2. **属主文档一删,号全体成孤儿**:审计报告删除后,全仓几百处引用再也查不到定义 → 号越少,这种 + "集体失效"的爆炸半径越小(§4.5)。 +3. **语义塞进号里**:条目事后降级,改号则引用全断、不改则号实不符 → 号不承载语义(P-2)。 +4. **销项号被复用**:登记簿删行后仓库里毫无痕迹,下一个条目顺手取走同号 → 不回收、不复用, + 改用**语义标识**(短语自带唯一性)。 --- -## 2. 八条设计原则(强制) +## 2. 八条设计原则(一句话版) -| # | 原则 | 理由 | +**P-8 语义优先**压过其余七条:**给人看的条目一律不发号**。其余七条只在"确实要发号"时生效—— +P-1 前缀 = 事物类别(不是来源文档 / 审计轮次)· P-2 号不承载语义 · P-3 一个事实一个号 · +P-4 规范形态唯一(连字符与补零不承载语义,写法只许一种)· P-5 只增不复用,新增先取游标 · +P-6 号必须能反查到定义(属主登记处得存在)· P-7 编号不语义、**引用必语义**。 + +--- + +## 3. 三种指代方式,按这个顺序挑 + +| 优先 | 方式 | 用在 | |---|---|---| -| **P-1** | **前缀 = 事物类别,不是来源文档/审计轮次**。来源是登记簿的「出处」列,不进编号 | 否则每轮审计都造一套新字母,必然瓜分殆尽 | -| **P-2** | **编号不携带语义**:严重度、状态、日期、负责人、波次一律是表格列 | 降级只改列,编号永不变、引用永不断(`P1-x` 的历史教训) | -| **P-3** | **一个前缀只有一个属主登记处**;前缀一经分配**永不复用给别类**,号段用尽即扩位(`N-99`→`N-100`),绝不换字母 | 保证"看到前缀就知道去哪张表查" | -| **P-4** | **形态唯一**:`<前缀>-<序号>`;前缀 1~3 位大写字母;序号位数按类别固定并左补零;子项只允许一级 `-<小写字母>`。**禁止**裸形(`S3`)、空格形(`Trap 07`)、中文形(`陷阱 1`)、圈号(`①②③④`) | 连字符与补零不得承载语义;字面唯一才能被机器判定 | -| **P-5** | **只增不复用**;每个类别有「下一个可用号」游标(`scripts/id_registry.json`,§8.1 已落地),新增先取游标 | 并行任务与多文档同时新增时防撞号(《项目审查与优化建议报告》与登记簿并轨时 `N-16` 撞过号,就是没有游标——见 `docs/任务规划/README.md` §0) | -| **P-6** | **引用必须机器可判定**:跨文档引用用行内代码,首次出现紧邻属主文档名;文档内编号**必须**带文档名 | 与 `§N.M` 同一条路:能校验的约定才算存在 | -| **P-7** | **编号不语义,引用必语义**:编号本体保持无语义并冻结(同 P-2);但**正文引用必须带语义括注**——`TRAP-NET-18(异常释放后排空 RX 残渣)`、`docs/网络/网络自愈架构设计.md` §4.6(应用主动拆会话保监听);**禁止裸 ID 单独承担信息**。语义放括注,事实变了只改括注、编号不变 | 读者不该为看懂一句话去跳三篇文档。可读性由**引用方式**解决,不由**编号命名**解决——命名带语义会重蹈 `P1-x` 覆辙(§1.2)。落地见 §5.6 | -| **P-8** | **语义优先**:规划 / 待办类条目**优先用语义标识**(§3.3)——一行短语即标识,不分配字母+数字号;只有**需机器解析**或**作历史证词**的类别才发编号,且引用时强制带语义括注(P-7) | 编号的唯一性维护成本已实测超过收益:`N-31` 销项删行后按"现存最大值 +1"又被取走,与两处历史引用撞号(`docs/任务规划/README.md` §0)。短语自带唯一性(重名一眼可见),且读者不必跳表查号 | - ---- - -## 3. 主轴:编号 / 语义标识 / 章节引用 - -| 层 | 适用事物 | 形态 | 引用规则 | 门禁校验 | -|---|---|---|---|---| -| **全局编号** | 会跨文档流转:需求、缺陷、待办、决策、风险、测试用例、陷阱记录 | 全局唯一前缀 + 全局序号 + **唯一属主登记处** | 行内代码;跨文档首次出现紧邻属主名 | 全库唯一;必须能在属主表查到定义行 | -| **文档内编号** | 只在定义它的那篇文档内有意义:状态机组合、方案档位、专项风险、专项阶段 | 允许存在,前缀带类别码,**号段文档内独立** | **必须紧邻文档名**(同 `§N.M` 紧邻规则) | 校验"该文档内确有此编号" | -| **语义标识**(§3.3,**规划 / 待办类默认**) | 登记簿条目、任务卡、待确认、风险、决策、缺陷、工作项、差距、验证场景 | **一行短语即标识**,不分配字母+数字号;≤20 汉字 | 同文档内写短语;跨文档紧邻属主文档名 | 同表内去重(同义异写算撞名);引用查不到 = 悬空 | +| ① 最好 | **语义短语** | 规划 / 待办 / 缺陷 / 风险 / 决策 / 差距 / 文档内步骤与阶段——**默认形态** | +| ② 其次 | **文档名 + 章节号** | 指某篇文档的某节(`docs/网络/网络自愈架构设计.md` §4.10,紧邻写法) | +| ③ 最后 | **编号** | 只有 §4.1 那 8 个族,且必须能反查到定义 | ### 3.1 判定口诀 - -> **会不会在别的文档里被引用?会 → 全局编号;不会 → 文档内编号,且引用必须带文档名。** -> -> **前置一步(P-8)**:规划 / 待办类条目先问"能不能用一行短语说清是什么事"—— -> **能 → 用语义标识(§3.3),不发编号**;只有需机器解析(`TC`)或作历史证词(`TRAP`)才进上面的判定。 - -现状的病根正是把该全局的(发现、待办)做成了文档内编号(各文档自造 `O1~O4`、`A1~A6`),又把该文档内的(设计决策 `D1~D7`、状态机 `U1~U15`)做成了裸形全局编号。 +**机器读 → 发号;人读 → 写短语。** 拿不准就写短语:短语看不懂还能靠上下文猜,号看不懂只能去查表。 ### 3.2 编号 vs 章节引用 +章节引用不是编号,但同样要**紧邻文档名**——只写「`§` + 小节号」而不写文档名,会被 doc-links 当成本文自引用而静默放过, +这是它的已知盲区,别踩。 -| 条目特征 | 用什么 | -|---|---| -| 会增删、会重排、只是"读第几条" | **章节引用** `` `docs/xxx.md` §N.M ``(不造编号) | -| 需要稳定身份(要销项、要被代码引用、要跨文档追溯) | **编号** | +### 3.3 语义标识(默认形态,怎么写) -各设计文档的"本期不做 / 遗留可选项"清单(现状 `O1~O4` 四篇文档四义)**默认不造编号**,改用 §小节引用;确需逐条引用时才启用文档内编号 `OOS-`。 - -### 3.3 语义标识(新条目默认形态) - -> **立场**:编号是"确需跨文档长期引用"才付的成本(§5.1 第 0 步)。规划与待办类条目 -> 生命周期短、销项即删行,编号带来的唯一性维护成本已实测超过收益——`N-31` 销项删行后 -> 按"现存最大值 +1"又被取走,与两处历史引用撞号(详见 `docs/任务规划/README.md` §0)。 -> **这类条目改用语义标识:一行短语本身就是标识,不再分配字母+数字号。**(原则见 P-8) - -**形态规则**(强制): - -| 项 | 规则 | -|---|---| -| 构成 | 主语 + 症状/动作,直接说清是什么事:`49s 日志静默`、`USART1 TX-only 根治`、`dhara is_free/copy 判据` | -| 长度 | **≤20 个汉字**(或 ≤40 个 ASCII 字符),一行写完 | -| 禁用 | 不含 `\|`(表格安全)、不换行、不以 `-` 结尾、不带序号后缀(`日志静默-1` 非法) | -| 不含易变属性 | 严重度、状态、日期、负责人、波次一律写进表格列,不进标识(同 P-2) | -| 唯一性 | 同一属主登记处内唯一;**同义异写视为撞名**(`日志静默` 与 `49s 日志静默` 只能留一条) | -| 冻结 | 措辞一经登记即**冻结**;确需改写在原行加"别名"列,旧措辞保留可查(§5.7) | - -**适用分界**(强制): - -| 域 | 条目 | 标识形态 | -|---|---|---| -| **规划 / 待办**(转语义) | 登记簿条目(原 `N-x` / `S-x` / `T-x`)、排期任务卡(原 `B` / `C` / `D`)、待确认(`OQ`)、风险(`RSK`)、决策(`DR`)、缺陷(`DEF`)、工作项(`WI`)、模拟器差距(`GAP`)、验证场景(`SC`) | **语义标识** | -| **机器解析 / 历史证词**(保留编号) | `TC-*`、`TRAP-*`、`FR-*` / `NFR-*`、`RC-*` | **编号 + 强制语义括注**(P-7) | -| **历史冻结族**(§4.3) | `E-xx`、报告自有编号、`S1~S14`、`A-1~A-7`、`P0-x`~`P3-x` | 不回改,走别名表 | - -> ⚠ **为什么 `TC-*` 不能转语义**(硬约束,勿试):`scripts/check_test_traceability.py` -> 用 `TC-[A-Z]{1,4}-\d{3,4}` 做可追溯性比对;`test/host/ch395f_socket_test.py` 把 -> `TC-NET-602` 打进运行日志;板级 `test/board/ch395f_test_task.c` 直接把它编进 C 宏名 -> `PHASE2_TC203_TIMEOUT_MS`。语义短语进不了宏名,也做不了正则排序。 -> `TRAP-*` 同理不转:196 处文档 + 122 处代码引用,是历史证词的高频锚点。 - -**引用写法**: - -| 场景 | 写法 | -|---|---| -| 登记处表内 | `` `49s 日志静默` ``(`#` 列填短语本身,不再填号) | -| 跨文档 | `` `docs/任务规划/登记簿.md` 的 `49s 日志静默` ``(紧邻文档名,同 §N.M 规则) | -| 代码注释 | 允许,但**必须带属主文档名**(同 §5.3),如:(登记簿 `8 路电气量分属两片转换器`) | -| 销项 | 删行 + 清全库引用;短语**不复用**(同"号不回收",见 §5.7) | +- 一行短语就是标识:≤20 汉字、说清"是什么事"、**同一登记表内不撞名**(同义异写算撞名)。 +- 好的例子:`双转换器无共同采样触发`、`FTP 鉴权`、`写回缓存空闲 flush`、`L5 队列往返基线待重验`。 + 坏的例子:`优化项 3`、`遗留问题`、`重要缺陷`——换个上下文就指代不明,等于又造了个号。 +- 登记处就是那张表:表首格写短语,**不要再配一个字母号**;跨文档引用写成 + `` `docs/任务规划/登记簿.md` 「双转换器无共同采样触发」 ``,同篇后续直接用短语。 +- 文档内的实施步骤 / 测试阶段 / 设计原则条目写中文:「步骤 3」「阶段 7」「DMA 边界安全」, + **不写 `S3` / `TM-PHY-01` / `DP-03` 这种号**。 --- -## 4. 编号空间总表 +## 4. 编号空间 -### 4.1 在册保留(零改写,仅补规则) +### 4.1 允许用号的族(**穷举**——这张表以外一律不许发号) -| 前缀(正则) | 类别 | 属主登记处 | 游标/规则 | +| 族 | 形态 | 属主登记处(定义在哪查) | 为什么必须是号 | |---|---|---|---| -| `FR-[A-Z0-9]+-\d{2}` | 功能需求 | `docs/产品需求/产品需求设计文档.md` §5 | 各域独立递增;**新增域写进该文 §1.2 术语与缩写** | -| `NFR-[A-Z0-9]+-\d{2}` | 非功能需求(**新建**,现 §7 全是散文无号) | `docs/产品需求/产品需求设计文档.md` §7 | 从 `NFR-<域>-01` 起 | -| `N-\d{1,3}[a-z]?` | **登记簿条目**(技术债 / 长期观察 / 硬件改版项;含义由"网络项"重定义) | `docs/任务规划/登记簿.md` | **曾用**最大 `N-33`(`N-30`~`N-33` 均已销项、登记簿行已删,**现存至 `N-29`**)→ 游标 `N-34`(号不回收);原 `S-x`/`T-x` 并入本族(§7.1)。⚠ 实测另有报告侧补零带子项后缀的 `N-03a`/`N-03c` 与 §5.5 的扩位示例 `N-100` → 位数收到 3、允许小写子项后缀 | -| `E-\d{1,2}` | 错误码/返回值传播审计发现 | 无(原属主 `docs/问题记录/错误码传播审计.md` 已于 2026-10-15 删除) | **冻结**:`E-63` 为最大号,**不再新增**;新发现一律 `DEF`;现存引用仅作历史指称(§4.3)。⚠ 属主已删 → 该族**无法判定义存在与否**,门禁只拦越游标(§8.1) | -| `E\d+` | **自愈实验编号**(N34 台架实验,**无连字符**) | `docs/网络/网络自愈架构设计.md` §5.2 | ⚠ 规范原记「已封闭 `E0`~`E15`」,**实测该表已到 `E19`**(E16~E19 是其后追加)→ 游标按实测 `E19` 收录,封闭线待订。⚠ 与上一行靠**连字符**区分:带连字符 = 审计发现 | -| `TC-[A-Z][A-Z0-9]{0,4}-\d{1,4}[a-z]?` | 测试用例 | 各测试文档 | 3 位起;分阶段套件序号 = 阶段号×100 + 项号(阶段 ≥10 自然进位 4 位,`TC-NET-1001` 合规);后缀只允许 `-<小写字母>`。⚠ 模块码 `GD5F`/`I104` **含数字**,规范原写的 `[A-Z]{1,4}` 会整族漏掉 GD5F(实测 285 处)→ 已按实测放宽 | -| `F--` | **net / 驱动侧功能与改进项**(`F-NET-2~17`、`F-DRV-*`、`F-MS-*`) | `docs/网络/网络自愈架构设计.md` | 2026-10-20 补行:本族**早就在被代码注释大量引用**(实测 60+ 处)而总表里没有——正是 §5.1 第 1 步"表里没有的类别先改本规范"的适用对象;`id-check` 落地时按实测收编 | -| `P-` | **本规范的设计原则条目**(`P-1`~`P-8`) | 本文档 §2 | 2026-10-20 补行;⚠ 与档位词 `P0~P3`(§4.4)、审计条目 `P0-x`(§4.3)靠**连字符**区分,三者形态互斥 | -| `CODE-RULE-` | **嵌入式 C 代码规范条款号**(配原文章节号,如 `CODE-RULE-03 (3.4.1)`) | `scripts/check_coding_standard.py`(规则表 = 单一事实源) | 2026-10-20 批 1 新建:原名是裸形 `S1`~`S19`,与登记簿 `S-x`、UI 像素状态、2026-10-02 复审专项条目**四义撞形**(§1.2)→ 单词化改名,旧号进 `id_aliases.json` 的 **scoped** 条目。⚠ `S14` 历史上不存在,空洞不复用 → 游标 19 | -| `MENU-STATE-` | **UI 菜单像素回归状态号**(按键序列状态,共 8 态) | `simulator/tests/check_menu_rows.py` 的 `STATES` 表 | 2026-10-20 批 1 新建:原名裸形 `S0`~`S7`(同上撞形);**冻结在 07**,加状态须先改本族游标 | -| `TM-[A-Z]{1,4}-\d{2}` | 测试阶段 | 各测试文档 | 模块码取 §6 统一表(`TM-PHY-01` → `TM-NET-01`) | -| `DP-[A-Z]{1,4}-\d{2}` | 设计原则 | 各测试文档 | **加模块码**(现 `DP-01` 跨文档会撞) | -| `L\d` / `L\d-\d{1,2}` | 采集与保护分层、层内条目 | `docs/算法/继电保护功能复现规划.md` §3 | 保留(代码侧 141 处引用,是全项目最规范的一族,作样板);`L` 前缀**专属**此族 | -| `W\d+` | 波次 | `docs/任务规划/README.md` §2.0 | **2026-10-25 废止**:波次改用语义名,理由见该文档 §2.0;游标冻结 `W8`,存量仅作历史指称;子任务卡不再用 `W1-0` 形态(并入 `WI`) | +| `TC-*` | `TC-<模块码>-<序号>` | `docs/测试说明/*_Test_Guide.md`、`test/host/<模块>/*`(代码即登记处) | 用例↔断言双向追溯,要进 C 断言标签 | +| `TRAP-*` | `TRAP-<域>-` | `docs/问题记录/*_Trap_Records.md` | 踩坑证词的永久锚点,代码注释反查 | +| `L-` | 保护功能号 | `docs/算法/继电保护功能复现规划.md` | 全库唯一"定义与引用都齐"的样板族 | +| `F--` | 功能项 | `docs/网络/网络自愈架构设计.md` | 代码注释按号回指设计条目 | +| `FR-*` | 需求 | `docs/产品需求/产品需求设计文档.md` §5 | 需求↔测试追溯 | +| `CODE-RULE-` | 代码规范条款 | `scripts/check_coding_standard.py` | 门禁按条款号报错并统计存量 | +| `MENU-STATE-` | UI 状态机键 | `simulator/tests/check_menu_rows.py` | 像素回归脚本按它取状态行 | +| `P-` | **本文的设计原则** | 本文 §2 | 就是本文自己的条目 | -### 4.2 目标前缀(阶段 1 改名后启用) +- **新增族的唯一正当条件**:有脚本要按字面量匹配它。顺序固定:先改 `id_registry.json`(含属主登记处) + → 再写属主文档 → 最后才允许在别处引用。反过来做必然造出悬空号。 +- 模块码是既定缩写,事实源 = registry 的族 pattern 与属主文档标题行,**别自创**(§6)。 -> **2026-10-15 修订(依 P-8 / §3.3)**:本表中**规划 / 待办类**前缀在阶段 1 落地时改为 -> **语义标识**形态(不再分配序号)——`DEF` / `WI` / `OQ` / `RSK` / `DR` / `MS` / `GAP` / `SC`; -> **需机器解析或作历史证词**的保留编号——`TC` / `TM` / `DP` / `TRAP` / `FR` / `NFR` / -> `RC` / `GEN` / `RUN`,引用时强制带语义括注(P-7)。下表的"游标"列对转语义的前缀不再适用。 +### 4.1.1 已停发的族(别再指,更别再"复用"这些号) -| 新前缀 | 类别 | 收编的旧编号 | 新属主登记处 | 游标 | -|---|---|---|---|---| -| `DEF-` | 缺陷 / 问题发现 | `P0-x`~`P3-x`(在册项)、新发现 | `[计划] docs/问题记录/缺陷登记簿.md` | `DEF-001` | -| `WI-` | 工作项 / 任务卡 | `B\d`(功能主线)、`C\d`(结构债)、`D\d`(验证窗口)、`W1-0`、`S1~S14`、`A-1~A-7`、`P1-10`、报告 `R-x`(内存优化)、`F-x`、`U1~U5` 的未闭环项 | `docs/任务规划/` 波次与排期文件(类别做列) | `WI-001` | -| `DR-` | 决策记录 | `docs/网络/网络自愈架构设计.md` 的 `D1~D7` | `[计划] docs/架构设计/决策记录登记簿.md` | `DR-001` | -| `OQ-` | 待确认 / 开放问题 | `B0-a/b/c`、`待确认-N` | `docs/任务规划/基线与待确认.md` | `OQ-001` | -| `RSK-` | 风险(**文档内编号**) | 保护规划 `R1~R8`、看门狗 `R1~R7` | 各文档风险表(引用必须带文档名) | 各文档自 `RSK-01` | -| `TRAP--` | 陷阱记录 | `Trap 07`、`TRAP-07`、`陷阱 1` | `docs/问题记录/<模块>_Trap_Records.md` | 保留原号(`TRAP-NET-01~21`——NET 域 2026-10-13 全库迁移并重排连续,沿革见该文档、`TRAP-GD5F-01~06`、`TRAP-RTC-01~`) | -| `GAP-` | 模拟器差距 | `H-1~H-7`、`L-1~L-4`、差距清单 `N-1~N-6` | `docs/测试说明/模拟器差距清单.md` | `GAP-01` | -| `SC-` | 实机验证场景 | 实机预检 `A1~A8`、看门狗验证用例 `V2/V4/V5` | `docs/测试说明/实机测试实施指南.md` | `SC-01` | -| `T0/T0.5/T1/T2/T3` | **测试分层**(原 `L0/L0.5/L1/L2/L3`) | 测试分层五档 | `docs/测试说明/测试开发标准流程.md` §1 | 固定五档 | -| `G0/G0.5/G1/G2/G3` | **门禁**(必跑清单,与测试分层是两套,不合并) | 现门禁决策表 `G0~G3` | `docs/测试说明/测试开发标准流程.md` §1.1 | 固定 | -| `GEN-` | 台架日志代次 | `gen 181`、`gen-288`、`gen68` | `docs/测试说明/串口助手设计说明.md` | 数字不变,仅改字面 | -| `RUN--` | 台架轮次 | `round 1~9` | `docs/测试说明/实机测试实施指南.md` | 日期制 | -| `MS-` | 里程碑 | `M1~M3` | 波次文件 | `MS-01` | -| `RC-` | net 周期对账类型 | `①②③④⑤`(唯一被代码逻辑引用的圈号链) | `docs/网络/网络自愈架构设计.md` | `RC-01`~`RC-05` | -| `PAT-` | 缺陷模式(九类) | 原 `docs/问题记录/错误码传播审计.md` §1.1 的 `P1~P9`(该文档已删除,定义迁至本文档 §4.2.1) | 本文档 §4.2.1 | `PAT-01`~`PAT-09` | -| `ST-` | 专项内部实施阶段(**文档内编号**) | 保护规划 §7.1 的 `P0~P5`、方案阶段 `P0~P3` | 各专项文档 | 各文档自 `ST-0` | -| `UL-` | UI 标准化四层(**文档内编号**) | 报告 UI 四层 `L0~L3` | `docs/UI设计/UI_设计架构.md` | `UL-0` | -| `LAT-` | 链路延迟分段(**文档内编号**) | `docs/测试说明/链路延迟测试说明.md` 的 `L1~L11` | 同文 | `LAT-01` | -| `CS-` | CubeMX 定制存活三层 | `AGENTS.md` 的 `L1 写进 .ioc → L2 USER CODE → L3 审计` | `docs/子系统设计/看门狗设计说明.md` §10 | `CS-1`~`CS-3` | +| 停发族 | 现在怎么写 | 旧号去哪查 | +|---|---|---| +| 登记簿 `N-x` / `S-x` / `T-x`、专项 `A-x`、已删报告的 UI 簇 `U1~U5`(2026-10-26 方案 B) | 语义短语——**登记簿表首格本身就是短语**,如「双转换器无共同采样触发」「CI 远端闭环未验证」 | `scripts/id_aliases.json` 标 `retired-to-phrase` | +| `E-xx` 审计发现、`P0-x`~`P3-x` 审计条目、报告六族 `D/R/F/N/U/S`、复审残号 `S1~S14`/`A-x`/`B1~B10`/`T-x`、模拟器差距 `H-x`/`L-x`、`TM-*` 阶段号、`DP-*`、`gen`/`round`/会话临时号 | 写条目本身("原子快照取数"、"队头阻塞"、"台架日志代次 288"、"第 3 轮台架") | 别名表标 `void`;**部分死号故意不进别名表**,好让门禁继续咬残留引用 | +| 规范曾规划却从未启用的前缀族(`DEF`/`WI`/`DR`/`OQ`/`RSK`/`GAP`/`SC`/`GEN`/`MS`/`UL`/`LAT`/`CS` 等) | 同上,写事实 | 无定义,本就不该存在 | -#### 4.2.1 缺陷模式定义(`PAT-01`~`PAT-09`) +> 想知道某个号还活着吗:跑 `scripts/check_ids.py --top 0`,或直接查 `id_aliases.json`——命中 `void` / +> `retired-to-phrase` 就是死号。**看到查不到的号,正确动作是删号留事实,不是补一个定义。** -2026-10-15 由已删除的 `docs/问题记录/错误码传播审计.md` §1.1 迁入(原编号 `P1`~`P9`,与发现编号 `E-xx` 区分)。 -新发现归入 `DEF-` 时,"缺陷模式"列仍按本表取值: - -| 模式 | 名字 | 一句话 | 典型症状 | -|---|---|---|---| -| **PAT-01** | 值拷贝队列陷阱 | 消息体按值拷进队列,netTask 修改的是**副本**,调用方看不到 | 回传字段恒为初始值 | -| **PAT-02** | 共享全局错误变量竞态 | 全局 `errno` 被多任务交错覆写 | 报出别人的错误码 | -| **PAT-03** | 返回值与错误码混同 | "0 = 成功"其实是"命令已发出";成功路径不清 errno | 失败被当成功 | -| **PAT-04** | 返回值未检查 | `HAL_*` / 库函数返回被丢弃 | 失败静默继续 | -| **PAT-05** | 事件标志语义 | 状态位被当作命令位消费掉 | 等待者永不唤醒 | -| **PAT-06** | 通知值宽度与符号 | 32 位通知值装不下 / 符号位被当成数据 | 大长度变负数 | -| **PAT-07** | ISR 与跨上下文共享状态 | ISR 写、任务读,无同步 | 读到半包 / 撕裂 | -| **PAT-08** | 外设 / 库特定约定 | 该外设 / 该移植版特有的规则 | 照抄上游示例即错 | -| **PAT-09** | 超时与错误混同 | 超时用同一个码表达,调用方无法区分 | 无法复位 / 无法重试 | +### 4.2 目标前缀(作废) +2026-10-26 删除:那批"改名后才启用"的前缀族从未有属主登记处,留着只会把读者带到一张不存在的登记表前。 ### 4.3 冻结前缀(历史快照,正文不回改) +族还在注册表里、但属主登记处已删的:**只为让冻结历史行不报"未登记前缀"而保留登记**,游标一律冻结, +不得新增、不得复用。 -| 冻结对象 | 处置 | -|---|---| -| `E-` | 冻结于 `E-63`;引用照旧,**禁止新增**;门禁拒绝游标之外的新 E 号 | -| 《项目审查与优化建议报告》自造的 `N-xx`/`D-x`/`S-x`/`R-x`/`F-x`/`U1~U5`(**该报告 2026-10-25 已删除**) | 报告是历史基线,**不回改**;引用一律紧邻文档名;未闭环项已并轨进 `N-19~N-29`,出处列保留报告编号。六族在 `id_registry.json` 里 `owner_docs` 已清空、`check_dangling` 关闭(§4.3) | -| `docs/问题记录/CH395F_Trap_Records.md` 2026-10-11 审计的 `A1~A6`/`B1~B10` | 该表是审计快照(条目已修复或推翻),**冻结**;新发现进 `DEF`;引用紧邻文档名 | -| `P0-x`~`P3-x` | 在册项迁 `DEF`;已闭环项留在历史文档,走别名表 | -| `Task #32` 等会话临时号 | **禁止入册**;现存引用(`Src/main.c`、`Drivers/BSP/UI/ui_net_sink.c` 的 `I-1/I-2`)改写为可解析形式或就地展开成事实描述 | +### 4.4 这些**不是**编号——别顺手清,也别当门禁漏洞 +元器件位号 `U1`/`C12`/`R3`/`L5`/`D7`(硬件语料按目录整体排除)、数学与协议记号(`N-1`、测频的正序与 +负序分量 `S1`/`S2`、`SHA-256`、`RS-485`、`ADC1_IN14`、手册页码)、活表代号 `E0~E19`(网络自愈 §5.2, +**无连字符**,与死号 `E-nn` 只差一个连字符)、Socket 状态组合 `U1~U15`(网络自愈 §1.1)、档位词 `P0~P3`。 +判据只有一条:**带连字符、且能在属主登记表查到定义**才叫编号;查不到的就是普通文本,不是"漏登记的号"。 -### 4.4 划界:这些不是编号,不受本规范管 - -| 对象 | 例子 | 归属 | -|---|---|---| -| C 标识符 | `PS_OC1_EN`、`TEST_SUITE_STORAGE`、`ENABLE_PHASE14_TESTS`、`SYS_EVT_NET_READY`、`TASK_WD_ADC` | `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | -| 元器件位号 | `U1`、`C12`、`R3`、`L5`、`D7`(`docs/硬件平台/`、`docs/芯片手册/`) | **门禁与改写脚本一律排除**,这是最大误伤风险源 | -| 章节号 | `§3.2` | `docs/文档规范.md` §3.5 | -| **档位词** | `P0`/`P1`/`P2`/`P3`(严重度、用例优先级) | 允许继续使用,但**永不带 `-<数字>` 后缀**——`P1-8` 这类形态一律非法 | -| 图元 id | `README.md` 架构图 Mermaid 节点 `A1`/`B1`/`C1`/`D2` | 改描述性 id(`net`/`sto`/`ui`),避免与条目字面撞名 | -| 运行时计数 | 网络代次号 `net_get_generation()` | 代码概念;与台架 `GEN` 是两回事,文档里须写全"网络代次号" | - -### 4.5 报告自有编号族登记处(属主文档 2026-10-25 已删,本表是唯一登记处) - -> 《项目审查与优化建议报告》(原 `docs/问题记录/项目审查与优化建议报告.md`)已于 **2026-10-25 删除**, -> 但它自造的 6 个编号族(`N-xx` / `D-x` / `S-x` / `R-x` / `F-x` / `U1~U5`)在全仓仍有大量**历史指称**。 -> 本节承接原报告 §9 的登记职责,`scripts/id_registry.json` 里这 6 族的 `owner_docs` 就指向本节。 -> -> - 六族**一律冻结**:不再新增号,新条目用语义标识(§3.3)。 -> - 引用时仍**必须紧邻文档名或「报告」字样**(`原报告 N-14`、`报告 F-5`)——它们与登记簿的 -> `N-x` / `S-x` / `D-x` **同字母不同义**(原报告 N-16 = 日志逐条开关,登记簿 N-16 = A2/A4 负向台架)。 -> - 未闭环项早已并轨到 `docs/任务规划/登记簿.md` 的 `N-19`~`N-29` 与 U 簇,出处列保留报告编号。 -> -> **⚠ 本节的登记处身份只恢复「可判定比例」,不构成定义集。** 规范正文混着大量写法示例 -> (`N-99`、`S1`、`N-31`),若让本节进定义集会同时打穿两条判定:裸形 `S1` 被判成 `S-1` 漏连字符、 -> 本节表里的 `S-3` 与登记簿 S-3 被判重复定义。因此 `check_ids.py` 对 `snapshot_only` 族另设 -> `_def_owners`(剔除规范正文)——本表是**存档**,悬空/撞号/裸形一律不拿它当依据。 -> -> **⚠ 11 个已死号另有别名登记**(`scripts/id_aliases.json`,`status: void`):`N-03b` / `N-10` / `N-11` / -> `N-12` / `N-13` / `N-20` / `N-21` / `N-24` / `N-26` / `S-1` / `S-2`。它们被代码注释与文档广泛引用 -> (`nand_port_test.c`、`sys_log_mcu.c`、`nand_ftl.c`、`gd5f2gq5ue.h` 等约 110 处),原先是靠"报告里 -> 顺带提过一句"才算有定义——报告一删就集体悬空。别名表逐条写明它们现在详述于哪篇文档,这也正是 -> 原报告「去处表」的内容。其中 `N-21` / `S-1` / `S-2` **一号两义**(登记簿与原报告各有一个), -> 别名注记里已分别标明。 - - -| 号 | 事项 | 状态 | 现在详述于 | -|---|---|---|---| -| N-01 | 配置引擎整文件驻留 | ✅ 已修(流式列为后续项) | `docs/子系统设计/配置文件设计说明.md` §6.2/§8 | -| N-02 / N-03 / N-03a / N-03b / N-03c | 存储与日志取证类条目 | ✅ 已闭环 | `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 | -| N-06 | 注册白名单与 `--strict` | 🟡 残余随 B2 | 登记簿 N-23 残余 | -| N-07 / N-08 | 网络自愈与卷锁 | ✅ 已闭环 | `docs/网络/网络自愈架构设计.md` §4.10 | -| **N-09** | `NET_MAX_SOCKETS`/PHY/PASV 三项网络债 | ❌ 在策 | `docs/任务规划/登记簿.md` N-25 | -| N-10 ~ N-13 | 主机侧 nand/dhara 移植层单测动因 | ✅ 已建 | `test/host/nand_port/`(第 16 个单测钩子) | -| **N-14** | FTL 写回缓存无空闲/掉电强制 flush | ⏸ 暂缓 | `docs/任务规划/登记簿.md` N-22 | -| **N-15** | `nand_ftl` 三块 2 KB 页缓冲未合并 | 🟡 **观察项(2026-10-23 判定不做)** | `docs/内存管理/存储栈RAM优化调研.md` §5、登记簿 N-25 | -| N-16 / N-17 | 台架窗口项(task stall 归因等) | 🟡 待窗口 | 登记簿 N-16/N-17 | -| **N-18** | V0.1 双转换器无共同采样触发 | ❌ 待 V0.2 | `AGENTS.md` 硬件引脚节 | -| N-19 ~ N-21 | 时钟/对时与已销项条目 | ✅ 已闭环 | `git log`(该报告已删) | -| **D-1** | 外部资料与工程文档同仓同树 | ❌ 暂不处理 | `docs/任务规划/登记簿.md` N-29 | -| D-2 ~ D-7 | 文档治理条目(链接/同源/编号/BOM 等) | ✅ 多数已制度化为钩子 | `docs/文档规范.md` | -| D-9 | 文档面残余观察 | 🟡 观察 | 本节即定义位 | -| **S-1** | 分层反向依赖(BSP include App 的头) | 🟡 已止住恶化(存量 6 条基线待还,新增被 `layer-deps` 拦) | `docs/门禁说明/layer-deps.md`(存量 6 条基线) | -| **S-2** | 跨模块裸 `extern` | ✅ **已闭环 2026-10-22**(24 处手写全消;两类本就不算债见 §0.4) | `git log`;契约见 `App/task/task_handles.h`、`gd5f_spi1_notify_*`、`net_msg_queue_adopt` | -| **S-4** | `app_cfg.h` 未接线占位开关 | ✅ **已闭环 2026-10-22**(4 个零消费点宏删除) | `App/app/app_cfg.h` 修改记录 v1.2;口径同步见 `docs/环境与部署/项目部署环境要求.md` §3.2/§3.5、`docs/产品需求/产品需求设计文档.md`、`docs/任务规划/排期详述.md` C5 | -| **S-3** | `.ioc` 侧 USART1 配置 | ❌ 在策 | 登记簿 S-3 | -| **S-5** | 统一 `bsp_err_t` 错误码与超时命名约定 + **返回值不得裸丢弃** | 🟡 主体已落地(2026-10-23:契约 + `bsp-err` 四规则 + RS485/SD2506/TPAFE 收编 + GD5F 改名 + dhara 三类码 + `consume-result` 照出 37 条丢弃 + ECC 回读校验;基线 19 → 1 → 38) | `docs/代码规范/pre-commit使用说明.md` §2.27、`STM32F4-Base存储架构说明.md` §10.4、`Drivers/BSP/bsp_err.h` | -| R-1 ~ R-9 | 内存优化项(R-9 已落地、R-5 判定不可行、其余随簇暂缓) | 见 `docs/内存管理/RAM使用分析与优化方案.md` §4 | `docs/内存管理/RAM使用分析与优化方案.md` §4、`docs/内存管理/RAM预算台账.md` §3 | -| F-1 / F-2 / F-3 | LVGL 死代码裁剪,省 64,576 B | ✅ 已落地 2026-10-11 | `docs/内存管理/RAM使用分析与优化方案.md` §2.6.4 | -| **F-4 / F-5 / F-6** | Flash 双精度与死代码簇 | ✅/🟡/❌ 见 `docs/内存管理/RAM使用分析与优化方案.md` §2.6.4 | `App/algo/prot_set.h` 注释 | -| U1 ~ U5 | UI 标准化簇 | 见 `docs/UI设计/UI_设计架构.md` §1.4;排期见 `docs/任务规划/README.md` §2.0「UI 扩展」 | +### 4.5 已删报告的自有族 +《项目审查与优化建议报告》2026-10-25 已删除,它自造的六族随之失去定义,且与登记簿同字母不同义。 +看到"报告 N-16"这类写法一律按死号处理:删号留事实——各条目原本指向的设计文档就是现在的出处。 --- -## 5. 分配、引用、销项规则(强制) +## 5. 分配、引用、销项 ### 5.1 新增编号 +先过 §1 三问。确实要发号:① 在 `id_registry.json` 确认族与游标 ② 在属主登记处写定义行 ③ 才允许在 +别处引用。**没有属主登记处的号不许存在**——门禁的校验 3 就是照这句实现的。 -0. **先问"能不能不加号"**:能用 § 小节引用、表格列、语义标题表达清楚的,就不要分配编号—— - 编号是"确需跨文档长期引用"时才付的成本(P-1 / P-3 / P-7)。确需新增才往下走。 -1. 查 §4 总表确定类别与属主登记处;**表里没有的类别 → 先改本规范,不得自造前缀**。 -2. 到属主登记处取游标号(`scripts/id_registry.json` 记每类的游标与冻结状态),登记处新增一行。 -3. 严重度 / 状态 / 波次 / 出处写进表格列,**不进编号**。 - -### 5.2 引用写法 - -| 场景 | 写法 | -|---|---| -| 全局编号,跨文档首次出现 | `` `docs/任务规划/登记簿.md` N-21 ``(紧邻,同 `§N.M` 规则) | -| 全局编号,同段后续 | `` `N-21` `` | -| 文档内编号 | **必须** `` `docs/网络/网络自愈架构设计.md` DR-003 `` | -| 尚未创建的条目文件 | 加 `[计划] ` 前缀(`docs/文档规范.md` §3.3) | -| 引用报错样例/历史字面 | 该行加 `[示例]` 标记 | - -### 5.3 代码注释引用 - -- **允许引用**(长期有效):`FR-*`、`NFR-*`、`DEF-*`、`E-*`(冻结存量)、`N-*`、`TRAP-*`、`DR-*`、`TC-*`、`L-`。 -- **禁止引用**(易变):`WI-*`、`W`、`MS-*`、`OQ-*`、`ST-*`——代码是长期资产,波次与任务卡会变。 -- 首次出现带属主文档名(现状 90% 裸引,靠"全库唯一"这一未成文假设)。 - -### 5.4 提交信息 - -标题带主编号:`fix(net): N-32 监听重建阻塞导致心跳缺口`。一次提交只挂一个主编号,其余进正文。 +### 5.3 代码注释 +**优先写短语**(`FTP 鉴权`、`监听生命周期模块化`),别写字母号;确需引用活号时必须带属主文档名。 +**规划 / 待办类编号禁止出现在代码里**——代码的寿命比任何一张待办表都长,待办表改了行,注释就成了 +永久的假引用(本仓实测:196 处这样的死引用,全是这么攒出来的)。 ### 5.5 销项与归档 +删登记行 + **同步清掉全仓引用**(清引用是销项的一部分,不是"以后再说")+ 别名表标 `void`。 +冻结的历史(变更记录行、`[示例]` / `[计划]` 行)不回改,门禁对它们已豁免。 -- 销项 = 删登记行 + **清全库引用**(沿用 `docs/任务规划/README.md` §3 与 `AGENTS.md` 既有规则)+ 游标**不回退**。 -- 号段用尽 → 扩位(`N-99`→`N-100`),**不换字母**。 -- 历史文档(审计报告、评审报告、已闭环陷阱)不回改,走别名表。 - -### 5.6 编号密集文档的对照节(强制) - -一篇文档若引用 **≥3 个编号族**(如 `I*` + `E*` + `F-*` + `M*` + `U*` + `TRAP-*`),**必须**在正文之前 -加一节「导读:本文用到的编号与术语」——每个记号一行,给出「是什么 + 在哪查」,让读者不必跳出本文拼语义。 - -- **首次实践**:`docs/网络/网络自愈架构设计.md` 的 `## 导读:本文用到的编号与术语`。 -- 对**已封闭 / 已归档**的编号族尤其必要:属主登记表可能已删行(`N-30`~`N-33` 销项、`E0`~`E15` 随审计归档), - 此时该文档即其**唯一可查处**。 -- 与 P-7 配套:**正文引用带语义括注**(局部可读)+ **对照节给编号族总览**(全局可查),两者合起来才算看得懂。 - -### 5.7 语义标识的登记、冻结与别名 - -- **登记**:在属主登记处新增一行,`#` 列填短语本身;落笔前先 `grep` 确认同表内无同名 / - 同义条目(同义异写 = 撞名,见 §3.3 形态规则)。 -- **冻结**:短语即标识,**改措辞等于换 ID**。确需改写时在原行加「别名」列,新旧措辞并存, - 引用侧同步替换完成后再删旧名——**禁止原地改写且不留下别名**。 -- **历史编号迁移**:编号转语义标识时,原号进别名表(例:`N-18` → `8 路电气量分属两片转换器`); - 历史文档中的裸号**不回改**(§5.5 同款处置)。 -- **销项**:删行 + 清全库引用(沿用 `docs/任务规划/README.md` §3 与 `AGENTS.md` 既有规则); - 短语**不复用**——重名即视为同一条目,与"号不回收"同理(撞名风险高于撞号,故由门禁兜底)。 -- **代码注释**:语义短语可进代码注释(中文注释允许),但必须带属主文档名(同 §5.3); - 现状 `App/algo/` 下 `N-18` 等 6+ 处**裸编号引用违反 P-7**,迁移时一并补语义。 -- **门禁**(`scripts/check_ids.py`,§8,2026-10-20 已落地 warn-only):① 同一登记处内短语去重; - ② 引用了登记处不存在的短语 = 悬空,报错;③ 打印「语义标识可判定比例」,只许涨不许跌。 - ⚠ **当前落的是"编号"侧的 ②③**(编号族的悬空与可判定比例已机械化);**语义短语**侧的 - 同表去重与短语悬空仍由人工 + `grep` 执行——短语是自由文本,机械去重要先解决同义异写判定 - (`日志静默` vs `49s 日志静默`),那是另一个问题,别把它记成"已由门禁兜住"。 +### 5.7 语义标识的登记与别名 +短语就是登记处的行名,不需要别名表;**只有"号转短语"**时才在别名表留一条 `retired-to-phrase`, +`note` 写清短语,方便把残留引用找干净。 --- -## 6. 模块码表(`TC` / `TM` / `DP` / `TRAP` 共用) +## 6. 模块码表 +`TC` 与 `TRAP` 共用模块码。唯一事实源是 `scripts/id_registry.json` 的族 pattern 与属主文档标题行; +**本文故意不再抄一份**——抄一份就多一处会过期的地方(历史上这张表就长期与代码不同步)。 +要加模块码:先改 registry,再在属主文档出现,最后才在别处引用。 -现状是**板端 6 个码 + 主机侧 10 个码两套并存**,且 `docs/测试说明/ADC输入层单元测试说明.md` 明文豁免主机侧"与板端缩写表互不影响"。本规范合并为一张表,**现有码全部沿用(零改写)**,只规定新模块必须先登记: +## 7. 改号与迁移 -| 码 | 模块(源文件/目录) | 码 | 模块 | -|---|---|---|---| -| `NET` | `Drivers/BSP/CH395F` + `Drivers/BSP/NET` | `ADCIN` | `App/algo/adc_input` | -| `GD5F` | `Drivers/BSP/GD5F2GQ5UE` | `ADCHEALTH` | `App/algo/adc_health` | -| `STORAGE` | 存储栈(FTL + FatFs + `App/fs`) | `PHASOR` | `App/algo/phasor` + `sample_ring` | -| `RTC` | `Drivers/BSP/SD2506` | `PROTSET` | `App/algo/prot_set` | -| `ADC` | 采集节拍与通道映射(`Src/adc.c`、TIM3) | `PROTELEM` | `App/algo/prot_elm` | -| `RS485` | `Drivers/BSP/RS485` | `VOLLOCK` | `App/fs/vol_lock` | -| `UI` | `Drivers/BSP/UI` | `TIMESYNC` | `App/sync/time_sync` | -| `LOG` | `App/log` | `SYSCLK` | `App/util/sys_clock` | -| `CONFIG` | `App/cfg` + `Drivers/BSP/NETCFG` | `WATCHDOG` | `App/util/task_watchdog` | -| `SHA256` | `App/util/sha256` | `FTPAUTH` | `Drivers/BSP/NET/lftpd` 鉴权 | -| `IEC104` | `App/protocol/iec104` | `TOOL` | PC 端工具(串口助手等) | +### 7.1 映射规则(作废) +"按审计报告把某族整体迁到另一族"那套映射已作废。2026-10-26 的实测结论是:**绝大多数号该删不该迁**。 -> 废止码:`PHY`(→ `NET`)、`NAND`(→ `GD5F`)。历史文档不回改,新文档按本表。 +### 7.2 孤儿编号处置(已完成) +代码有号、文档无号那批已按同一口径处理完:能对上属主表的补模块码,对不上的删号留事实; +**绝不为了少报孤儿而凭空补定义行**(那等于把账做平、把问题藏起来)。 + +### 7.3 改号操作规程(要动号就照这个走,一步都别省) +1. 走 `scripts/rename_ids.py`:映射表驱动、**显式 scope**、默认干跑、自动写别名条目; + **绝不全局查找替换**——同一裸形字面量可能同属多个族(实测 `S3` 三义),全局替换等于造新撞号。 +2. 改完复核**所有识别面**:`check_ids.py` 与 `check_test_traceability.py` 的候选正则上界都要还能匹配新 + 形态;判据是"编号字面量总数有没有真的变小"(曾经因为正则上界太窄,改完号静默隐形 339 个)。 +3. **逐号做语义复核**,不能只看 dry-run 命中数:最阴的失效是"字面改对、意思改错"。 +4. 登记表 / 映射表类文件**不得当引用语料**,否则自家审计表里的旧形态会被判成新账。 +5. 收尾重打台账 `--prune-backlog`(只减不增),并确认新错误级为 0。 --- +## 8. 门禁:`id-check`(八项校验) -> ⚠ **2026-10-20 批 2**:上表 16 个有歧义的码已单词化(`AI`→`ADCIN`、`AH`→`ADCHEALTH`、`PS`→`PROTSET`、`PE`→`PROTELEM`、`PH`→`PHASOR`、`TS`→`TIMESYNC`、`VL`→`VOLLOCK`、`NP`→`NANDPORT`、`CFG`→`CONFIG`、`CLK`→`SYSCLK`、`WD`→`WATCHDOG`、`STO`→`STORAGE`、`SH`→`SHA256`、`FA`→`FTPAUTH`、`FR`→`FREQ`、`104`→`IEC104`),全库 1,146 处随 `scripts/rename_ids.py` 改写,旧号入 `scripts/id_aliases.json`。`NET`/`GD5F`/`RTC`/`SOE`/`UI`/`ADC`/`LOG`/`RS485`/`TOOL` 不动(已是单词、芯片名或业界通用词)。**连带修门禁**:模块码变长到 9 位(`ADCHEALTH`),候选正则与族正则的 `{0,4}` 上界必须放宽到 `{0,9}`,否则改名后的号会整批从门禁眼里消失(实测漏掉 950 处——改名类批次必须连带复核识别面,不能只改数据不改判据)。 -## 7. 迁移映射(legacy → 新号) +查什么、报错怎么读、盲区在哪——**全在 `docs/门禁说明/id-check.md`**,本文只留口径与基线。 +八项校验一句话版:① 未登记前缀 ② 形态不合规(裸形 / 空格 / 中文 / 补零)③ 悬空引用 ④ 重复定义 +⑤ 冻结族越游标 ⑥ 编号未紧邻文档名 ⑦ 孤儿条目(告警)⑧ 可判定比例 + 两根护栏。 -### 7.1 映射规则 - -| 旧族 | 新族 | 规则 | -|---|---|---| -| 登记簿 `N-2`…`N-33`(`N-30`~`N-33` 已销项,现存至 `N-29`) | `N-02`…`N-33` | 仅补零,数字不变;号不回收,游标仍 `N-34` | -| 登记簿 `S-3/S-4/S-5`、`T-7` | `N-34`… | 续号并入,别名表保留旧号 | -| 主线 `B0~B10`、结构债 `C1~C6`、验证窗口 `D2~D7`、`W1-0` | `WI-001`… | 按波次顺序连续重编,映射表逐条列出并落别名表 | -| 复审 `S1~S14`、`A-1~A-7`、报告 `R-x`/`F-x`/`U1~U5` 未闭环项 | `WI-*` | 同上;已闭环项留历史文档 | -| `P0-x`~`P3-x` 在册项 | `DEF-*` | 严重度转列 | -| 设计说明 `D1~D10` | `DR-001`~`DR-010` | 数字对齐 | -| `B0-a/b/c`、`待确认-N` | `OQ-001`… | — | -| `Trap NN` / `陷阱 N` | `TRAP--` | 数字不变,补模块码 —— **批 4 已做(2026-10-21)**:GD5F `01~07`、RTC `01`、NET `19`/`21`;`docs/硬件平台/` 属排除语料,里面的 `Trap05` 未动(见 §8.3 批 4 说明) | -| `H-1~H-7`、`L-1~L-4`、差距 `N-1~N-6` | `GAP-01`~`GAP-17` | 按文档内出现顺序 | -| 实机 `A1~A8`、看门狗 `V2/V4/V5` | `SC-01`… | — | -| 测试分层 `L0/L0.5/L1/L2/L3` | `T0/T0.5/T1/T2/T3` | 门禁 `G0~G3` 不动(两套不同尺度,不合并) | -| `gen 181`/`gen-288`/`gen68` | `GEN-181`/`GEN-288`/`GEN-068` | 数字不变 | -| `round 1~9` | `RUN-<日期>-` | 无日期的历史轮次留别名表 | -| `M1~M3` | `MS-01`~`MS-03` | — | -| 对账 `①~④` | `RC-01`~`RC-04` | 代码注释同步 | -| 审计模式 `P1~P9` | `PAT-01`~`PAT-09` | 正文冻结,新文档用新号 | -| 保护 `R1~R8`、看门狗 `R1~R7` | 各文档 `RSK-01`… | 文档内编号 + 引用带文档名 | -| 保护阶段 `P0~P5`、方案 `P0~P3` | 各文档 `ST-0`… | 与档位词 `P0` 解耦 | -| `TC-STORAGE-01~06` | `TC-STORAGE-001~006` | 补零 | -| `TC-ADCIN-01A`/`02A`/`02B`/`02C`、`TC-VOLLOCK-008b` | `TC-ADCIN-001a`… / `TC-VOLLOCK-008b` | 后缀统一小写字母,基号补零 | -| `TM-PHY-01~11` | `TM-NET-01~11` | 模块码 | -| `DP-01~04` | `DP-NET-01~04` | 加模块码 | - -### 7.2 孤儿编号处置(阶段 1 一并清) - -| 孤儿 | 处置 | -|---|---| -| `I-1`/`I-2`(`ui_net_sink.c`) | 就地展开成事实描述(原属主《网络Socket监听生命周期设计说明》已并入 `docs/网络/网络自愈架构设计.md` §9.3,不再补编号定义行) | -| `Task #32` 会话号及其小节引用 | 会话号,改写为可解析引用(结论已沉淀进 `docs/子系统设计/看门狗设计说明.md` §11) | -| `P1-01~P1-09`(`ch395f_test_task.c`) | 改指 `TC-NET-101`…,或补文档条目 | -| `TC-GD5F-1100/1101/1102`、`TC-STORAGE-06` | 补进 `docs/测试说明/GD5F2GQ5UE_Test_Guide.md` | -| `TC-AH-*`、`TC-PE-*`、iec104 `TC-014/015/024` | 补单测说明文档(缺文档 = 追溯断链) | -| `Trap 19` | 悬空引用,删除或改指实际条目 | - -### 7.3 改写操作规程(每批必走) - -```powershell -# 1) 预览:映射表驱动的 dry-run,人工通读 diff(重点看 docs/硬件平台/ 是否被误伤) -& '.venv\Scripts\python.exe' [计划] scripts/rename_ids.py --map [计划] scripts/id_aliases.json --dry-run - -# 2) 落盘后三重复核 -git grep -n '<旧字面量>' # 零残留 -& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all -& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 0 错误 0 警告 -``` - -**硬约束**:`docs/硬件平台/`、`docs/芯片手册/` 全程排除(元器件位号 `U1/C12/R3/L5/D7` 与编号字面同形);带 BOM 的文件(`Drivers/BSP/UI/*`、`App/task/ui_task.c`、`App/cfg/cfg_file.c`、`Lib/lv_conf.h`)改写后必须核对 BOM 未丢;**禁止**用 `Get-Content`/`Set-Content` 重写含中文文件。 - ---- - -## 8. 门禁:`check_ids.py` 与 `id-check` 钩子 - -配置两份:`scripts/id_registry.json`(前缀 → 属主文档 / 形态正则 / 是否冻结 / 游标)、`scripts/id_aliases.json`(旧号 → 新号,照搬 `scripts/section_aliases.json` 的成功范式)。 - -| # | 校验 | 级别 | -|---|---|---| -| 1 | 出现未登记前缀(自造编号) | 错误 | -| 2 | 形态不合规:裸形 `S3`、空格形 `Trap 07`、中文形 `陷阱 1`、补零错、`P1-8` 这类档位词带后缀 | 错误 | -| 3 | 引用查无定义且不在别名表(悬空引用) | 错误 | -| 4 | 同一字面量被两个属主重复定义(`N-16` 类事故) | 错误 | -| 5 | 冻结前缀出现游标之外的新号 | 错误 | -| 6 | 文档内编号引用未紧邻文档名 | 错误 | -| 7 | 定义了但全库无引用(孤儿条目) | 告警 | -| 8 | 打印**「编号引用可判定比例」** | 每次运行,只许涨不许跌 | - -- 排除:`Lib/`、`Middlewares/`、HAL/CMSIS、`docs/芯片手册/`、`docs/硬件平台/`(位号)、构建产物。 -- 上线节奏:**先 warn-only → 2026-10-21 关闸为【增量拦截】**(`--strict` + 债务台账,见 §8.4)。与 `doc-links` 的差别在于:存量太多(710 处),等清零会逼出 `--no-verify`,所以先关闸再还债。 -- 顺带收编 `scripts/check_test_traceability.py`:正则补齐大写后缀与无模块码形态,覆盖 7 篇「单元测试说明.md」,一并挂进 `id-check`。 - **2026-10-21 口径改写**(B 层之后):差异分三档——`DOC-ONLY` 文档声明而代码没有(真断链)、`CODE-ONLY` 代码有而母号不在登记处且号旁无意图(真未登记)、`IN-CODE` 靠「代码旁写着意图」放过(弱保证,单列示数、不算过关);小写子项后缀按母号算。 - -### 8.1 落地记录(2026-10-20) - -**已落地并挂上 `id-check` 钩子(warn-only)**。三项机械判据先定死,否则八项校验全是空话: - -| 判据 | 口径 | -|---|---| -| 「定义」存在 | 字面量出现在该族的**属主登记处文档**里(registry 的 `owner_docs` / `owner_docs_glob`)——不依赖表格行首等易漂移形态 | -| 「重复定义」 | 字面量出现在**定义位**(表格首格 / 标题行 / 列表项行首);正文里"提到过"不算定义,否则任何交叉引用都会被误判 | -| 「可判定」 | 字面量落进已登记前缀族**且该族属主登记处已存在**;未登记前缀、属主还是 `[计划]` 的都算不可判定 | - -**首轮基线(本仓库实测,`--update-baseline` 写进 `id_registry.json`,只许涨不许跌)**: +### 8.1 当前基线(2026-10-26 编号迁移批次后重打) | 项 | 数值 | |---|---| -| 扫描文件 | 536 个(`git ls-files` 减去 §8 排除清单) | -| 编号字面量出现 | 8271 处 | -| **可判定比例基线** | **84.0%**(8747 / 10410 = 84.02%,2026-10-25 删报告 + 补登 11 条已死号别名后重打;存进 `id_registry.json` 时**下取整到 0.1pp**——用 `round()` 会让 82.34 进位成 82.3 再反过来把自己判死,实测踩过;比较容差 0.1pp) | -| 校验 1 未登记前缀 | 0 处 | -| 校验 2 形态不合规 | 396 处(**批 1 已从 417 降到 396**:裸形 `S`/`T` 类 71 → 50。剩余大头:真无模块码 `TC-nnn` 181 处 / 5 文件、数字模块码 `TC-104-nnn` 23 处(§6 表里模块码是 `I104`,非数字)、`DP-01`~`DP-04` 22 处、`Task #nn` 33 处、`Trap 空格形`+`陷阱 中文形` 43 处、`gen/round` 36 处、大写后缀 31 处) | -| 校验 3 悬空引用 | 错误 626 / 告警 223(scoped 别名收编旧号后 −16)(错误集中在 `TC-PS-*`/`TC-PE-*`/`TC-AH-*`/`TC-FR-*`/`TC-SOE-*`——**主机侧单测有码无文档**,正是 §7.2 列的"缺文档 = 追溯断链") | -| 校验 4 重复定义 | 0 处(`§4.3` 已声明的同号不同义族对 443 个字面量走 `collides_with` 白名单,不计事故) | -| 校验 5 冻结越游标 | 0 处 | -| 校验 6 未紧邻文档名 | 269 处(报告自有编号 `D-x`/`R-x`/`F-x`/`P0-x` 被裸引,属 §9 阶段 1-③ 待办) | -| 校验 7 孤儿条目 | 88 处(告警) | -| 附带 TC 双向追溯 | 代码侧 556 / 文档侧 290 / 交集 260(差异 326,收编后含主机侧单测一侧,比原板级口径的 33 大一个量级——**原口径漏的正是 7 篇单测说明**) | +| 扫描文件 / 编号字面量 | 754 个 / **8,230 处**(两批清理前 10,956;本规范自己瘦身去掉 160 处 → 8,343,编号迁移批次再降到 8,230) | +| **护栏 1**:可判定比例下限 `decidable_ratio_baseline` | **86.0%**(实测 86.01%;容差 0.1pp,只许涨) | +| **护栏 2**:不可判定引用绝对数上限 `undecidable_total_ceiling` | **1,166 处**,只减不增(实跑 1,151 处) | +| 校验 1/2/3/4/5/6 错误级 | **0**;债务台账 0 条指纹 → `--strict` 实为**全量拦截** | +| 告警 | 悬空 2(`App/algo/freq.c` 的正序 / 负序记号,属 §4.4 误判面)+ 孤儿 50(陷阱条目这类本就允许无人引用) | -**有牙实测**([示例] 下列字面量是注入用的假编号,不是真实引用):临时注入 `Z-99` / 裸形 `S3` / `TC-NET-9999`(悬空)/ `E-70`(越 `E-63` 游标)/ 裸引 `D-7` / 在第二篇属主文档重复定义 `DP-01` → 六项分别被校验 1/2/3/5/6/4 点名,`--strict` 退出码 1;临时档删除后 `git status` 干净。 +> ⚠ **读这些数字的口径**:跨批次**不可直接相减**。方案 B 把"本可判定的引用"换成散文,分子分母同缩, +> 比例反而从 86.7% 掉到 85.98%,而**不可判定绝对数一处没涨**——所以同批加了护栏 2,并把比例基线 +> **人工下调**一次(工具按设计拒绝下调,必须留书面理由,见 `docs/门禁说明/id-check.md` §7.6);本规范瘦身后比例回到 86.0%, +> 护栏 2 也顺手从 1,192 收紧到 1,166。 +> 以后判断"编号有没有变坏"看**绝对数**,不看比值:号只许越清越少,新增没登记的号一定被护栏 2 拦住。 -**口径修正记录(2026-10-21,B 层)**——下列改动**改变了比例的分母/分子含义**,读历史数字必须知道: - -| 改动 | 动机 | 对比例的影响 | -|---|---|---| -| 主机侧单测建 **9 族 `TC-HOST-<模块码>`**,属主登记处 = `test/host/<目录>/*`(代码即登记处;**2026-10-23 起同一口径新增 4 族,现 13 族**,见 §8.4 变更记录) | 实测这些号在代码目录外**零消费者**(唯一"外部引用"是 `id_aliases.json` 自己),逐号补文档只会造出没人读的复述层 | 悬空错误 −508 中的 430(**口径修正,不是仓库变好**) | -| `subitem_ok`:小写子项后缀(`TC-TIMESYNC-003b`)母号在册即算已定义 | §4.1 明允许小写后缀,是合法形态;双向追溯仍按完整字面量报差异,可见性不丢 | 悬空 −74(同上,属口径) | -| **`snapshot_only`**:§4.3 冻结历史快照族(`N-RPT`/`D-RPT`/`S-RPT`/`R-RPT`/`F-RPT`/`P-AUDIT`/`A-AUDIT`/`B-AUDIT`)**不参与"定义位优先"** | 曾试过让定义位优先无差别生效:`N-21`(登记簿条目已销项)被审计报告里同号的另一码事"接管",门禁转而去报 ⑥"请写报告文档名"——**照做就制造 §1.2 的 `N-31` 撞号**;实测该做法使校验 6 从 266 暴涨到 1437、比例假崩到 68.7% | 校验 6 回到 269(与 B 层前持平) | -| **补属主**:`docs/任务规划/README.md` 加入 `N`/`S-LDG`/`T-LDG` 族的 `owner_docs` | 这是**我的配置漏项**:活登记处在 README(`N-21` 出现 2 次),我原先只登记了 `登记簿.md`(`N-21` = 0)→ 真条目被误判悬空 74 处 | 悬空 −74(**修 bug,不是口径**) | -| `excluded_paths` 加 `scripts/id_aliases.json`、`scripts/id_registry.json` | 别名表/注册表**自身不得制造引用面**——上批别名条目一度成为这些号的唯一"外部引用" | 极小 | -| **追溯脚本同口径对齐**(`check_test_traceability.py`):文档声明了代码没有 = DOC-ONLY 真断链(38);代码有、母号不在登记处**且号旁没写人话意图** = CODE-ONLY 真未登记(7);代码有但旁边写着意图 → 单列 `IN-CODE`(238),**弱于文档的保证,禁止当成已登记** | B 层认代码为属主后,老口径「每个号都必须出现在文档」与门禁自相矛盾——照它升 `--strict` 会拦死 173+ 处本不该写文档的号,逼出 `--no-verify`,门禁公信力就是这么没的 | 差异从 284 降到 45(38+7),另 238 处转入「看得见但不过关」的 `IN-CODE` 账 | -| ⚠ 同批改名要复核**所有**识别面:`TC_RE` 也停在 `{0,4}`,模块码单词化后代码侧集合从 556 悄悄缩到 217(339 个号隐形),已放宽到 `{0,8}` | 这是「改名必须连带复核判据面」教训的**第二次发生**,发生在另一支脚本上 | 修回后代码侧 578 | -| **追溯器补上「变更记录 / `[示例]` / `[计划]` 行」豁免**(`check_test_traceability.py`,与 `check_ids.py` / `rename_ids.py` / `doc-links` 同一条口径) | 追溯器是**唯一没这条豁免**的一支:它把冻结历史当活引用,凡变更记录里写过的旧用例号一律要求代码里也存在同号 (例:`\| 2026-10-10 \| 新增 TC-AI-070..078 \|` 写的是**当年**的名字,号在批 2 已改成 `TC-ADCIN-*`,按 §8.1 历史行不回改 → 拿它去代码里找同号必然落空) | `DOC-ONLY` 38 → **29**(−9 全是假阳性,**不是真变好**)。关键佐证:**交集 260 → 260 不变**——豁免没有吞掉任何一条真对应,只吐出了冻结行 | -| 已销项历史任务号 `T-2`/`T-3`/`T-4`/`T-6` 入别名表(`status: void`) | §5.5「销项 = 删行 + 清引用」只做了一半的欠账;本条只让门禁停止把它当自造号,并如实指路 | 悬空错误 −35 | - -**修正后实测(2026-10-21)**:错误级 **711**(B 层前 1318)= ②398 + ③44 + ⑥269;告警级 ③223 + ⑦100;可判定比例 **82.71%**(6883/8322),基线仍 82.6%。 -⚠ 拿 711 与 1318 直接比是**错的**——其中 504 处是口径修正,只有 74 处是我修的注册表 bug;真正"变好"的部分是 **0**。剩下的 711 处没有一条能靠调判据消掉,只能补文档、改写引用或按 §4.3 登记冻结。 - -**显式盲区(不是"检查通过",是记在案上的判定边界)**:裸形同字母多义(§1.2 的 P1 五义 / L1 四义 / U1 四义 / D7 三义)与元器件位号同形(§4.4)→ 裸形族**只在属主登记处文档内**判越游标,文档外降为告警;圈号编号与正常列举无法机械区分,不入判定。这两块的收口属 §9 阶段 1-②/1-③ 的迁移批次,收口后再扩判定范围。 - -### 8.2 落地时发现的规范 ↔ 仓库偏差(registry 按实测收录,规范行待订正) - -| 规范原文 | 实测 | 处置 | -|---|---|---| -| §4.1 `E\d+`「已封闭 `E0`~`E15`」 | `docs/网络/网络自愈架构设计.md` §5.2 实验表已到 **E19** | 游标按实测 E19 收录;封闭线待修订 | -| §4.1 `FR-[A-Z0-9]+-\d{2}` | 现存 `FR-STO-1` 等全是**未补零**的 1 位序号 | 正则放宽到 `\d{1,2}`,补零归 §9 阶段 1 | -| §4.1 `TC-[A-Z]{1,4}-\d{3,4}` | 模块码 `GD5F`/`I104` **含数字**,按原文正则会漏掉 GD5F 全族(实测 285 处) | 放宽为 `[A-Z][A-Z0-9]{0,4}`;序号暂收 2 位(`TC-STORAGE-06` 属 §7.2 孤儿) | -| §4.1/§4.2 总表无 `F--` | `F-NET-2~17`/`F-DRV-*`/`F-MS-*` 定义在 `docs/网络/网络自愈架构设计.md` 且被大量代码注释引用 | 先按实测收编成族(§5.1 第 1 步:表里没有的类别先改本规范) | -| §4.1 `N-\d{2}` | 报告用 `N-03a`/`N-03c` 子项后缀;§5.5 扩位示例 `N-100` | 放宽到 `\d{1,3}[a-z]?` | +### 8.3 改号与清号批次(只留要点) +2026-10-21 五批统一改名(陷阱号归范、主机侧单测认代码为属主登记处、追溯器三档口径对齐); +2026-10-26 两批清号(A:删死号约 1,557 处文本实删;B:登记簿系 20 个号转语义短语约 418 处)。 +逐批动机与影响只在 git log 与 `scripts/rename_maps/`,**本文不维护流水账**。要记住的只有两条教训: +门禁曾经把"政策上禁止回改的变更记录行"当活账拦提交;也曾经用比值当唯一护栏而**惩罚清号动作本身** +——两处都在 2026-10-26 修掉了,别再往回改。 +### 8.4 台账与 `--strict` +错误级问题按「文件 + 校验项 + 字面量 + 次数」记指纹进 `scripts/id_known_backlog.json`,`--strict` 只拦 +**台账之外**的新账,台账只减不增。本批台账已还清到 **0 条**——从现在起任何一条新错误级问题都会当场 +拦提交,**别指望还有豁免面**可钻。 --- -### 8.3 迁移批次记录(改名走 `scripts/rename_ids.py`) +## 9. 还剩什么(以及别再做什么) +剩下能被机器读到的号就是 §4.1 那 8 族。除此之外,**"编号说明"这件事本身应该继续变薄**:每多一个族, +就多一处需要有人记得它的地方;每多发一个号,就多一个将来没人敢删的字符串。要新增族之前,请把这张 +只有 8 行的表当作反对你的第一手证据。 -| 批 | 日期 | 族 | 旧 → 新 | 处数 / 文件 | 别名 | -|---|---|---|---|---|---| -| **批 5(口径)** | 2026-10-21 | 追溯器豁免 | **未改任何编号**:给 `check_test_traceability.py` 补与其它三支工具同源的「变更记录 / `[示例]` / `[计划]` 行不比对」口径 + `--verbose` 打印被豁免的 9 个号 | 判据改动;`DOC-ONLY` 38 → 29、交集 260 不变 | 无新增(`TC-AI-070` 等 8 个旧号**早在批 2 就已在别名表**,本批一度以为缺、写错层级后被 assert 拦下) | -| **批 4(Trap)** | 2026-10-21 | 陷阱号形态 | `Trap 01~07` → `TRAP-GD5F-01~07`、`陷阱 1` → `TRAP-RTC-01`、`Trap 21`/`Trap 19` → `TRAP-NET-21`/`TRAP-NET-19`、`见陷阱 01` → `TRAP-GD5F-01` | 23 处 / 11 文件(含 `Drivers/BSP/GD5F2GQ5UE/*` 注释 4 处、`test/board/*` 2 处) | 11 条全部带 scope;映射表即审计轨迹 `scripts/rename_maps/batch3-trap.json` | -| **批 1** | 2026-10-20 | 代码规范条款 | `S1`~`S19` → `CODE-RULE-01`~`19` | 78 处 / 2 文件 | 18 条(scoped:`scripts/check_coding_standard.py`、`docs/代码规范/pre-commit使用说明.md`) | -| **批 3(口径)** | 2026-10-21 | 主机侧单测归属 | 建 9 族 `TC-HOST-*`(代码即属主)+ `subitem_ok` + `snapshot_only` + 补 README 属主 + `T-x` 入别名 | 判据与注册表改动,未改任何存量号 | 见 §8.1 口径修正记录 | -| **批 2** | 2026-10-20 | 测试用例模块码 | 16 个码 → 单词化(见 §6) | 1,146 处 / 约 30 文件 | 全局别名(字面量已完全限定,无歧义) | -| **批 1** | 2026-10-20 | UI 像素回归状态 | `S0`~`S7` → `MENU-STATE-00`~`07` | 61 处 / 3 文件 | 8 条(scoped:`simulator/tests/check_menu_rows.py`、`docs/UI设计/UI_自动化测试流程.md`、`simulator/README.md`) | - -⚠ **别名必须带 scope**:同一裸形字面量可同时是多族的旧号(实测 `S3` 三义)。全局别名会把另一族的真悬空洗成「已解析」——等于用工具复制 §1.2 的 `N-31`/`N-32` 撞号事故。 - ---- - -**批 5 的经过值得记一笔:我的判断被自己的证据推翻了两次** - -1. 我据 `DOC-ONLY` 38 处判定「批 2 模块码单词化漏扫了 4 篇属主文档」(`TC-AI-070/080/090`、 - `TC-PH-001/030`、`TC-TS-001`、`TC-VL-001`),并写好了逐号点名的映射表准备改名。 -2. 改名工具干跑 **0 处命中** → 查下去发现这 8 处**全部位于变更记录行**(`| 2026-10-10 | … |`), - 按 §8.1 属冻结历史、**本就不该回改**;再查别名表,`TC-AI-070 → TC-ADCIN-070` 这批 - **早在批 2 就已登记**。也就是说:真要按原计划改,是在销毁历史记录,而收益是零。 -3. 我顺手补别名时把键写到了 JSON **顶层**(真容器是 `aliases`,515 条),条目完全无效—— - 是 `assert k not in d['aliases']` 把它拦下来的;删掉无效键后该文件字节与 HEAD 完全一致。 - -**教训写在这里而不只是提交信息里**:`DOC-ONLY` 这类"两侧字面比对"的差异,**先判它是不是冻结行/ -别名可解析**,再谈改数据;改名工具的 0 命中和 assert 失败都是**信号**,不是小故障。 - -**剩 29 处 `DOC-ONLY` 是真问题,但不是编号问题**(`--verbose` 可复现全清单): -- 4 处无模块码形态(`TC-1003`、`TC-206`、`TC-501`、`TC-704`)——属 ② 大批的文档侧镜像; -- `TC-NET-607` 一条是**历史指称**:原文写「原 TC-NET-607/608/609,现 701/702/703」, - 一条对三条,别名表是一对一模型,写不进别名; -- `TC-NET-302/401/402/501` 标题明写「(硬件层)」= 人工台架步骤,代码里没有实现载体**是预期**; -- 其余 20 处(`TC-NET-101/102/1101/601/606/701~704/801/802/901/902/1006/1501~1503` 与 - `TC-GD5F-503`)**全库无任何载体**(代码、台架脚本、模拟器都没有)——文档声明了用例而没有实现, - 这三类要不要各立口径(人工台架豁免 / 历史指称 void 别名 / 真未实现登记待办),**属产品裁决,不由门禁决定**。 - -**批 4 刻意不改的三处(写清楚,别被当成漏网)** - -- `docs/环境与部署/并行任务工作树方案.md` 里的 `wip/trap22-fix` 是 **git 分支名**,不是编号—— - 这类「非编号字面量」要靠**类型识别**处理(与位号 `T1`、IEEE 负序 `S2`、gitlint 规则 ID 同一批), - 不靠白名单硬豁免,也不许为它改分支名。 -- `docs/UI设计/菜单页面设计.md` 的变更记录行「陷阱 3」按 §8.1 冻结不改写;正文里指向本文自己小节的 - 那处(原「见 §8.2 陷阱 1」)**本来就不是 `TRAP-*` 族的号**,已改写成 - 「见本文 §8.2 第 1 条(`lv_text_get_width` 的 `length=0` 陷阱)」——引用要能当问题读, - 不必为两处文档内自指另立一族。 -- `docs/硬件平台/` 网表分析报告里 6 处 `Trap05`(语义 = `TRAP-NET-05`)留在排除语料里不动, - 盲区已登记进 `id_registry.json` 的 TRAP 族 `cursor_note`。 - -### 8.4 债务台账与增量拦截(2026-10-21 关闸) - -**为什么不照 §8 原节奏「清零后再 `--strict`」**:存量 710 处(② 形态迁移 398 + ⑥ 裸引写全属主 269 + ③ 真悬空 44), -靠人清完要数周到数月;这期间**新欠的债一条都不会被拦**——而新增恰恰是编号烂掉的主因(`N-31`/`N-32` 撞号 -就是新增时撞上的)。更糟的是「等清零」会让所有人习惯看到「门禁报红但照样提交」,真到关闸那天已经没人信它。 - -**机制**:`--strict` 不再「有任何错误就失败」,而是**只拦台账之外的新错误级问题**。 - -| 件 | 说明 | -|---|---| -| 台账 | `scripts/id_known_backlog.json`:340 条指纹,覆盖 710 处错误级问题(② 167 类 / ⑥ 135 类 / ③ 38 类) | -| 指纹 | `(文件, 校验号, 字面量) → 出现次数`。**故意不含行号**:行号每改必漂,拿它当指纹会让台账天天「假新增」,三天后没人敢看 strict 输出,台账等于失效——**台账失效比没有台账更糟** | -| 为什么带计数 | 同一文件里同一个号**多出现一次**也算新账:复制粘贴式扩散是编号烂掉的常见路径,只按「有没有出现过」判会放过它 | -| 只减不增 | `--prune-backlog` 收掉已还清条目;台账内条目**变多**会被 `--strict` 当场拦;确需新增一条必须写 `reason` 字段(默认禁止顺手加行,否则一周后台账变成第二个 backlog) | -| 初始化 | `--write-backlog` 全量快照(关闸那次用过;之后再跑等于豁免新债,须显式确认) | -| `--only` 与 `--strict` | **互斥**(局部扫描不能当门禁结论),同时给直接返回 2 | -| 缺台账 | `--strict` 直接返回 2 并提示先生成,**绝不退化成「一次性拦死全部存量」**(那样必然逼人 `--no-verify`) | -| 台账自身不得当语料 | 关闸第一次提交就被自己的台账咬了:`id_known_backlog.json` 里必然写着 `P1-3` / `Trap 05` 这些号,扫描器当成新引用 → 报 10 类新账、提交被拦。已把台账加入 `excluded_paths`(与 `id_aliases.json` / `id_registry.json` 同一条道理:**登记号的表不是引用号的文档**。第 4 次同样自循环:改名映射表 `scripts/rename_maps/batch3-trap.json` 里必然写着 `Trap 21` / `陷阱 1` 这些旧形态,关闸后第一次提交就被它自己拦下 6 类「新账」→ 整个 `scripts/rename_maps/` 目录一并入 `excluded_paths`(它是审计轨迹,不是引用语料) | - -**关闸验收(三例,2026-10-21 实测)**: - -| 情况 | 期望 | 实测 | -|---|---|---| -| A 全新未登记前缀(`Z-99`)[示例] | 拦 | exit 1,`+1 [校验1] docs/tmp_teeth_a.md Z-99` ✅ | -| B 同一文件里已登记的号**多出现一次**(计数 1→2) | 拦 | exit 1,`+1 [校验6] ui-verify/SKILL.md 内的 R-9` ✅ | -| C 干净态(710 处存量全在台账内) | 放行 | exit 0,`增量拦截通过` ✅ | - -临时注入文件与追加行当场还原(`git diff` 干净)。**C 例最关键**:放不住存量就会逼出 `--no-verify`, -那是把门禁从「闸」降级成「报表」——本仓已有 mem-budget / mem-numbers 走 `--no-verify` 的先例,别再开第二个口子。 - -## 9. 落地阶段与验收 - -| 阶段 | 内容 | 验收判据 | 风险 | -|---|---|---|---| -| **0 规范** | 本文件;`docs/任务规划/README.md` §0 改为指向本规范的摘要;`docs/README.md`、`AGENTS.md`、`docs/文档规范.md` §6 登记;两份 `id_*.json` 初版 | `check_doc_links --all` 通过、`@build` 0/0 | 零(纯新增 + 指针) | -| **0.5 语义标识** | §3.3 / §5.7 规范落地:登记簿、排期、波次的**存量条目转语义短语**(原号进别名表,`N-x`/`S-x`/`T-x`/`B`/`C`/`D`);代码注释裸编号(如 `N-18`)补语义;`DEF`/`WI`/`OQ`/`RSK`/`DR`/`GAP`/`SC` 按语义形态启用 | 每批:dry-run 通读 → 落盘 → `git grep` 零残留 → `check_doc_links --all` → `@build` 0/0 | 中(撞名与措辞漂移是主要风险) | -| **1-① 小族** | `RC`/`PAT`/`GEN`/`RUN`/`MS`/`GAP`/`SC`/`OQ`/`RSK`/`DR`/`TRAP`/`ST`/`UL`/`LAT`/`CS` + Mermaid 节点 id + 孤儿处置 | 每批:dry-run 通读 → 落盘 → `git grep` 零残留 → `check_doc_links --all` → `@build` 0/0 → `ci_local.py` | 中 | -| **1-② 大族** | `N` 补零 + `S`/`T` 并入;`WI` ← `B`/`C`/`D`/`W1-0`/`S1x`/`A-x`/`R-x`/`F-x`/`U-x`;测试分层 `L0~L3` → `T0~T3`(**最大机械批次**,`L0.5` 一词全库百余处) | 同上,逐族一个 commit | **高** | -| **1-③ 缺陷族** | `DEF` ← `P0-x`~`P3-x` 在册项;报告历史编号补齐紧邻文档名;档位词规则落地;别名表与游标定稿 | 同上 | 中 | -| **2 门禁** | ✅ **已落地(2026-10-20,warn-only)**:`scripts/check_ids.py` + `id-check` 钩子 + `scripts/id_registry.json` / `scripts/id_aliases.json`;`check_test_traceability.py` 已收编(正则补齐 + 纳入 7 篇「单元测试说明.md」与 `test/host/` 一侧) | 全库告警清零后 entry 加 `--strict` 转拦提交;可判定比例基线 **84.0%** 已入档(批 1 改名后由 82.3% 抬升,2026-10-25 删报告后由 82.6% 抬到 84.0%)(§8.1,写在 `id_registry.json` 里,跌破即失败) | 中(首轮确实暴露出 626 处真悬空 + 416 处形态违规 + 269 处未紧邻文档名,见 §8.1) | - -每阶段结束停下等指令再提交(`AGENTS.md` 边界:commit/push 须明确指示)。 - ---- - -## 10. 常见错误速查 - -| 现象 | 原因 | 修复 | -|---|---|---| -| `id-check` 报"未登记前缀" | 自造了一套编号 | 查 §4 总表归到既有类别;确属新类别先改本规范 | -| 同一编号在两篇文档各指一物 | 用了文档内编号却裸引 | 改全局编号,或引用补文档名(紧邻) | -| 引用的编号查不到定义 | 孤儿/悬空 | 补定义行,或删引用 | -| 降级后编号与事实不符 | 语义进了编号 | 违反 P-2;严重度改列,编号不动 | -| 硬件文档里 `U1`/`C12` 被改 | 改写脚本没排除位号目录 | 回滚该批,按 §7.3 排除清单重跑 | -| 改完中文变乱码 | 用了整文件重写 | 见 `AGENTS.md` 铁律:只用精确子串替换,BOM 按原样补回 | +## 10. 速查 +- 看到一个不认识的号 → 先查 `id_aliases.json` 与属主登记表;查不到就**删号留事实**,不要补定义。 +- 想给待办 / 缺陷 / 差距 / 风险发号 → 不许,写短语(§3.3)。 +- 想给文档内步骤或测试阶段发号 → 不许,写「步骤 3」「阶段 7」(§3.3)。 +- 想新增前缀族 → 先回答"哪个脚本要按字面量匹配它"(§4.1);答不出就别建。 +- 要改号 → `scripts/rename_ids.py` + §7.3 五步。 +- 门禁报错读不懂 → `docs/门禁说明/id-check.md`。 --- @@ -606,14 +226,4 @@ git grep -n '<旧字面量>' # 零残留 | 日期 | 变更 | 说明 | |---|---|---| -| 2026-10-23 | **主机侧单测族 +4(现共 13 族)** | 覆盖率口径 `docs/测试说明/主机单测覆盖率口径.md` §9 P1 第①批新建 `test/host/{net_cfg,ftp_cfg,prot_set_store,task_prof}` 四个模块,按 2026-10-21 批 3 的同一 B 层口径补建 `TC-HOST-{NETCFG,FTPCFG,PROTSTOR,TASKPROF}`(代码即属主登记处、`allow_orphans`、游标按实测最大号 056/014/016/020)。**验证了这条口径真的在拦东西**:建族前,文档里引用 `TC-NETCFG-028` 被判「台账之外的新账」而卡提交;建族后 `--strict` 通过 | -| 2026-10-14 | 新增 P-7 与引用可读性规则 | §2 加 **P-7「编号不语义,引用必语义」**(编号本体保持无语义并冻结,但正文引用必须带语义括注、禁裸 ID 独担信息)→ 六条原则变**七条**;§5.1 加前置步"先问能不能不加号";新增 **§5.6「编号密集文档的对照节(强制)」**(≥3 个编号族必须加「导读:编号与术语」);§4.1 补登记 **`E\d+`**(N34 自愈实验编号族,已封闭)并把 `N-\d{2}` 的在册范围订正为「曾用最大 `N-33`,现存至 `N-29`」;§1.2 悬空引用示例补 `N-32`;§7.1 同步订正。起因:`docs/网络/网络自愈架构设计.md` 合并与导读节实践暴露"裸编号引用不可读",用户裁定以后尽量用语义标识 | -| (规划稿) | 建立本文档 | 全库扫描 1,385 个不同编号 / 19,306 处出现后定稿的两层编号体系(全局编号 + 文档内编号)、六条原则、目标前缀总表、三批迁移映射与 `check_ids.py` 门禁设计;用户决策:`E-xx` 冻结保留、严重度档位词 `P0~P3` 不变、文档落 `docs/编号规范.md`、阶段 1 三批全做 | -| 2026-10-21 | **批 5:追溯器口径补齐(未改任何号)** | `check_test_traceability.py` 加入与 `check_ids.py`/`rename_ids.py`/`doc-links` 同源的「变更记录 / `[示例]` / `[计划]` 行不参与比对」豁免,并新增 `frozen_ids` 留痕(`--verbose` 可列);`DOC-ONLY` 38 → 29、总差异 45 → 36,**交集 260 → 260 不变**(证明没吞掉真对应)。批 5 原计划的 8 处文档侧改名**被证据否掉**(全在变更记录行、别名批 2 已登记),未落盘;经过与教训、以及剩 29 处的三类真问题都写进 §8.3 批 5 段 | -| 2026-10-21 | **批 4:陷阱号统一(② 清的第一族)** | 23 处 / 11 文件:`Trap 01~07` → `TRAP-GD5F-01~07`(属主文档 7 个标题行从此成为定义位)、`陷阱 1` → `TRAP-RTC-01`、`Trap 21`/`Trap 19` → `TRAP-NET-21`/`TRAP-NET-19`;UI 文档自指改写成平实语言。别名 11 条全部带 scope。改名后 `TRAP-GD5F-01/05/06` 三处历史悬空引用自动落地为已定义;三处刻意不改与 `docs/硬件平台/` 盲区写进 §8.3 批 4 | -| 2026-10-21 | **关闸:`id-check` 转增量拦截** | `check_ids.py` 新增 `--write-backlog` / `--prune-backlog` 与按台账比对的 `--strict`(指纹 = 文件 + 校验号 + 字面量 + 计数,**不含行号**);生成 `scripts/id_known_backlog.json`(340 条覆盖 710 处存量);钩子 `entry` 加 `--strict`。验收三例:新字面量拦、同号计数增长拦、干净态放行,全通过。新增 §8.4 说明机制与「为什么不照清零再升 strict 的原节奏」 | -| 2026-10-21 | **追溯脚本同口径对齐** | `check_test_traceability.py` 差异定义改为 DOC-ONLY / CODE-ONLY / IN-CODE 三档(真差异 45 = 38 + 7;弱保证 238 单列示数);顺带修 `TC_RE` 模块码上界 `{0,4}`→`{0,8}`——**同一个坑第二次踩**(代码侧一度从 556 缩到 217),已写进 §8.1 | -| 2026-10-21 | **B 层口径落地(批 3)** | 主机侧单测 118 号实测在代码目录外零消费者 → 建 9 族 `TC-HOST-*` 认代码为属主登记处(不逐号补文档);新增 `subitem_ok`(小写子项后缀母号在册即算已定义)、`snapshot_only`(§4.3 冻结快照族不参与定义位优先)、`allow_orphans` 旗标与 `excluded_paths` 补两项;补 `docs/任务规划/README.md` 为 `N`/`S-LDG`/`T-LDG` 属主(**我的配置漏项,曾误判 74 处真条目悬空**);`T-2`/`T-3`/`T-4`/`T-6` 入别名表标 `void`。错误级 1318 → 711(其中 504 处属口径修正、74 处属修 bug,**真正变好 0 处**)。新增 §8.1 口径修正记录表 | -| 2026-10-20 | **编号改名开禁(用户授权)+ 批 1 落地** | 新增 `scripts/rename_ids.py`(§7.3 规划脚本:映射表驱动、显式 scope、干跑/落盘两态、自动写 `id_aliases.json` 的 scoped 条目、跳过 `[示例]`/`[计划]` 行与变更记录表、按原字节读写保 BOM 与行尾)。**批 1**:代码规范条款 `S1~S19` → `CODE-RULE-nn`(78 处/2 文件)、UI 像素回归状态 `S0~S7` → `MENU-STATE-nn`(61 处/3 文件);`check_ids.py` 支持 **scoped 别名** 并登记两族(66 族);可判定比例 82.3% → **82.7%**、裸形判错 71 → 50、错误级 1312 → 1291。§4.1 补两行、新增 §8.3 迁移批次记录 | -| 2026-10-20 | **§8 门禁落地(warn-only)** | 新增 `scripts/check_ids.py`(八项校验 + 可判定比例基线)、`scripts/id_registry.json`(逐项来自 §4 总表 + 实测游标)、`scripts/id_aliases.json`;挂上 `id-check` 钩子(36→37);顺带收编 `check_test_traceability.py`(正则补齐大写后缀/无模块码形态,比对范围扩到 7 篇「单元测试说明.md」+ `test/host/` 一侧,另加 `--board-only` 回退原口径)。**新增 §8.1 落地记录**(基线 82.3%、backlog 分布、有牙实测、显式盲区)与 **§8.2 规范↔仓库偏差表**(`E19`、`FR` 未补零、`TC` 模块码含数字、`F--` 总表缺行、`N` 子项后缀/扩位);§4.1 相应行按实测订正并补 `F-` / `P-` 两行 | -| 2026-10-15 | 新增 P-8 与语义标识规范(§3.3 / §5.7) | §2 加 **P-8「语义优先」**(规划 / 待办类条目优先用语义标识,不发字母+数字号)→ 七条原则变**八条**;§3 主轴由"两层编号"扩为**编号 / 语义标识 / 章节引用**三种;新增 **§3.3「语义标识(新条目默认形态)」**(形态规则、适用分界、引用写法)与 **§5.7「语义标识的登记、冻结与别名」**;§4.2 加修订说明(规划类前缀改语义标识,`TC`/`TRAP`/`FR`/`NFR`/`RC` 等保留编号);§9 加「0.5 语义标识」阶段。起因:用户裁定去除字母数字编号(难理解),先定规范本体 | +| 2026-10-26 | **编号治理收口:清死号 → 停发活号 → 本文重写为短版(用户三次裁定)** | 全仓字母+数字编号 10,956 → 8,230 处。① **清死号**:属主登记处已删或从未启用的号一律就地删号留事实、禁补号(文本实删约 1,557 处)。② **停发活号**:登记簿 `N-x`/`S-x`/`T-x`、专项 `A-x`、已删报告的 UI 簇 `U1~U5` 共 20 个号转成语义短语(约 418 处)——登记簿表首格就是短语,旧号进 `scripts/id_aliases.json` 标 `retired-to-phrase`;已销项的死号**故意不进别名表**,让门禁继续咬残留引用。③ **本文瘦身**:619 行 → 229 行,删掉八原则长表、迁移映射表、模块码表与门禁细节(那些各有事实源:registry / 别名表 / `docs/门禁说明/id-check.md`),改成「先问三句再决定要不要发号」+ §4.1 允许用号的族**穷举 = 8 个**。④ **门禁配套**:`check_ids.py` 加变更记录行豁免与第二根护栏 `undecidable_total_ceiling`(比值会被清号动作稀释,绝对数不会),债务台账还清到 0 条 → `--strict` 实为全量拦截,比例基线重打为 86.0%。**本表按用户 2026-10-26 的明确指示只保留最近一条**,更早沿革看 `git log` 与 `docs/门禁说明/id-check.md` §7.5(含其后的方案 B 一节);这是**本文档的例外**,不改变 §5.5对其他文档「冻结历史不回改」的约束。 | diff --git a/docs/网络/BSD_Socket_API_使用指南.md b/docs/网络/BSD_Socket_API_使用指南.md index 99e1607c..c1f0143b 100644 --- a/docs/网络/BSD_Socket_API_使用指南.md +++ b/docs/网络/BSD_Socket_API_使用指南.md @@ -177,7 +177,7 @@ int net_close_sock(net_sock_t *p_sock); - **消息队列由 CubeMX 创建**:在 `MX_FREERTOS_Init()`(`osKernelInitialize()` 之后)中调用 `osMessageQueueNew(8, sizeof(net_msg_t), NULL)` 创建 - **队列句柄全局可见**:通过 `extern osMessageQueueId_t netMsgQueueHandle` 引用 - **`netTask` 不可向自身发消息**:`netTask` 中直接调 `net_send()` 会死锁(等待自己处理消息),必须在 `netTask` 中使用 `net_send_sock()` 等内部版本 -- **阻塞 recv 不独占 netTask(挂起 recv 模型)**:无数据时公共 `net_recv()` / `net_recv_to()` **登记挂起请求后返回**(每 fd 至多一个,禁止覆盖——覆盖即丢失前调用方通知 = 永久挂死);完成由 `net_pending_tick()` 按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方——等待期间调用方在自己上下文睡通知,netTask 零占用(消除 E-02 头阻塞)。副作用:`buf` 指针须保持有效至返回;close / 网络重建 / 陈旧重同步会**显式取消并通知**(`NET_ERR_CONNRESET` 等),调用方永不挂死 +- **阻塞 recv 不独占 netTask(挂起 recv 模型)**:无数据时公共 `net_recv()` / `net_recv_to()` **登记挂起请求后返回**(每 fd 至多一个,禁止覆盖——覆盖即丢失前调用方通知 = 永久挂死);完成由 `net_pending_tick()` 按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方——等待期间调用方在自己上下文睡通知,netTask 零占用(消除队头阻塞)。副作用:`buf` 指针须保持有效至返回;close / 网络重建 / 陈旧重同步会**显式取消并通知**(`NET_ERR_CONNRESET` 等),调用方永不挂死 - **recv 语义主流化**:对齐 POSIX/Python——**模式归使用者、超时归调用方、API 不强加默认预算**。`net_recv()` 默认**无限阻塞**(`NET_RECV_TIMEOUT_MS` 30s 默认已删),唤醒源 = 数据 / EOF / close / 网络重建(CONNRESET),会话死亡由对账① ~400ms 判死兜底;非阻塞具名入口 `net_recv_nb()`(原 `NET_MSG_DONTWAIT` 路径转正),模式属性 `net_setblocking(fd, on)` 挂 socket 控制块;超时首选 `net_select(...)`,`net_recv_to(...)` 保留为**显式预算**便捷入口。内部看门狗降级为 **tripwire**:挂起超 60s 只 `[ERR]` 留痕一条(完成逻辑缺位探针),不再伪造 TIMEDOUT 返回值。⚠ **文档契约(嵌入式特有)**:IWDG `required_mask` 内任务**禁止无限阻塞 recv**——必须 `net_select` 超时或 `net_recv_to` 显式预算("超时归使用者"对受监控任务是义务不是选项) - **阻塞 send 不独占 netTask(挂起 send 模型)**:`net_send()` 快路径**有界自旋**等发送缓冲腾出(预算 `NET_SEND_SPIN_MS` = 2ms;健康链路实测块间等待 med 219µs ≪ 2ms,故正常发送不会触发挂起),超预算即登记挂起 send 返回;推进由 `net_send_pending_tick()` 按主循环节拍分块完成(①断连 ②推进 ③写完 ④无进展超时),调用方睡任务通知——netTask 最坏停滞由 `NET_SEND_TIMEOUT_MS`(1s)压到 2ms。返回值恒为**已写入字节数**:`= len` 全部写入,`< len` 为**短写**,调用方**必须**检查并重试剩余(断连/超时/取消均以已发字节数结束,不会伪造全部成功)。停滞计时按**无进展**刷新(每次成功写块重置),大文件传输不会被误判超时 - **队列阻塞语义**:只有 `osMessageQueuePut` 失败(8 长度固定队列,极端情形)时公共 API 阻塞;recv / send 挂起等待**不计入**队列占用(完成前队列中无消息滞留) @@ -191,7 +191,7 @@ int net_close_sock(net_sock_t *p_sock); |---|---|---|---| | **`auto_relisten`** | 数据通道正常 EOF / DISCONNECT | 重开 + 重新 LISTEN,回 LISTENING;应用层无需 close/重 listen | `net_listen()` 置位;`net_close()` 清 | | **D10 非阻塞重监听重试** | 重开失败(TRAP-NET-09:芯片 close 后需沉降) | 单次尝试失败即登记待重试,每轮到点重试;超时限放弃并记 `relisten give up` | 重试间隔 30ms / 时限 2s(自首次尝试);同 fd 有在途挂起 recv / send 时跳过(挂起保险,防毁在途连接 / 截断在途传输) | -| **TRAP-NET-17 周期对账 + 监听重建(合一 `net_reconcile_tick`;对账①②⑤ + 原 pump③④/U11)** | 芯片 CONNECT/DISCONNECT 事件未收到(快速复连,读清消费竞态——E14 复核:非芯片侧丢失);静默回收 Socket(peer FIN,无 DISCONNECT 事件);监听型 fd 芯片监听失位 / 本层状态滞后 | 每 100ms 对在用 socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs),修正**五类**失同步 + 一类被动重同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理(只置事实);② 本层 LISTENING 而芯片已握手 → 补 accept(N-30 幽灵甄别);③ 本层 LISTENING 而芯片监听失位 → 重建(`net_listener_request`);④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(原手动 API `net_resync_listen` 的语义并入);U11 本层 CLOSED 而芯片**正在** LISTEN → 只做状态重同步、不发芯片命令 | 周期 100ms;**分级去抖**:②=1 轮、③④⑤=2 轮、①=4 轮(M3 让位窗 400ms,**勿改小**)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴;U11 被动重同步不去抖、不占冷却);其它位置只置事实、不做重建动作 | +| **TRAP-NET-17 周期对账 + 监听重建(合一 `net_reconcile_tick`;对账①②⑤ + 原 pump③④/U11)** | 芯片 CONNECT/DISCONNECT 事件未收到(快速复连,读清消费竞态——E14 复核:非芯片侧丢失);静默回收 Socket(peer FIN,无 DISCONNECT 事件);监听型 fd 芯片监听失位 / 本层状态滞后 | 每 100ms 对在用 socket 读**驱动状态缓存**(`ch395f_sock_state_cached`,唯一刷新点 = `net_poll` 每轮全量刷新,E2 实测单次回读 18.1µs),修正**五类**失同步 + 一类被动重同步:① 本层 ESTABLISHED 而芯片已断 → 按断开处理(只置事实);② 本层 LISTENING 而芯片已握手 → 补 accept(幽灵甄别,属「relisten 沉降段原语化受阻」);③ 本层 LISTENING 而芯片监听失位 → 重建(`net_listener_request`);④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建;⑤ 本层 ESTABLISHED 而芯片已静默回收回 LISTEN(0x05/0x01)→ RESYNC 语义只修本层、不发芯片命令(原手动 API `net_resync_listen` 的语义并入);U11 本层 CLOSED 而芯片**正在** LISTEN → 只做状态重同步、不发芯片命令 | 周期 100ms;**分级去抖**:②=1 轮、③④⑤=2 轮、①=4 轮(M3 让位窗 400ms,**勿改小**)+ 动手后 **1s 冷却**(防"刚 open 未生效"瞬态重建风暴;U11 被动重同步不去抖、不占冷却);其它位置只置事实、不做重建动作 | | **TRAP-NET-18 排渣隔离** | 异常释放(芯片 TIMEOUT,如客户端中止上传):在途数据落进 RX 缓冲,直接复监听 → 下条连接把 ~2048B 残渣当自己的数据读走(上传静默截断);正常 FIN 释放亦有残留 DISCONNECT 收尾问题 | 复监听前沉降/隔离:持续排空 RX 残渣 + 主动 close + 排空中断,等芯片回 CLOSED/TCP_CLOSED 才 open+listen;到期强制放行 | 沉降等级(`net_listener_settle`):异常释放 QUARANTINE **400ms** / 正常释放 GRACEFUL **100ms**(2026-09-24 起正常 FIN 亦需沉降);上限 **800ms** 强制放行(**必须 < 2s 时限**,否则监听被放弃) | **行为结论**:监听器恢复从"一次性事件"变为"登记 → 到点重试"过程: @@ -249,7 +249,7 @@ int main(void) - 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT) - 触发事件回调 - 处理数据接收 - - `net_process_messages()` 每轮先跑 4 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ 挂起 recv 完成检查(阻塞 recv 唤醒,E-02 修复)→ 挂起 send 推进(阻塞 send 唤醒)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,事件未收到/静默回收/监听失位自愈,§3.6) + - `net_process_messages()` 每轮先跑 4 个**非阻塞**检查再取队列:D10 重监听重试(断连后监听器恢复)→ 挂起 recv 完成检查(阻塞 recv 唤醒,队头阻塞修复)→ 挂起 send 推进(阻塞 send 唤醒)→ TRAP-NET-17 芯片/本层对账 + 监听重建合一 reconciler(100ms 周期,读驱动状态缓存,事件未收到/静默回收/监听失位自愈,§3.6) - **监听 Socket 建立**:由就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)经队列 API 建立(`net_socket` → `net_bind` → `net_listen`);断连后 net 层自动重监听(§3.6),服务层只需循环 `net_accept`,无需维护重监听逻辑 ```c @@ -962,7 +962,7 @@ int net_recv(int sockfd, void *buf, int len, int flags); > **语义主流化**:对齐 POSIX/Python——`net_recv()` 默认**无限阻塞**(旧 `NET_RECV_TIMEOUT_MS` 30s 默认预算已删,API 不再强加隐藏值)。唤醒源 = 数据 / EOF / close / 网络重建(`NET_ERR_CONNRESET`);会话死亡由对账①在 ~400ms 内判死兜底(§3.6),不存在"对端消失后永久挂死"窗口。需要"等但不无限等":首选 `net_select(r,w,e,timeout_ms)`(§7.1),或 `net_recv_to()` 显式预算。 > -> **挂起 recv 模型**:无数据时阻塞接收**不占 netTask**——登记挂起请求(每 fd 至多一个,禁止覆盖:覆盖会丢失前调用方通知 = 永久挂死)后**立即返回**;完成按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方。等待期间调用方在自己上下文睡通知,netTask 全程零占用(消除 E-02 头阻塞)。内部 tripwire(原看门狗降级):挂起超 60s 只 `[ERR]` 留痕一条("完成逻辑缺位"探针),**绝不伪造 TIMEDOUT 返回值**。 +> **挂起 recv 模型**:无数据时阻塞接收**不占 netTask**——登记挂起请求(每 fd 至多一个,禁止覆盖:覆盖会丢失前调用方通知 = 永久挂死)后**立即返回**;完成按主循环节拍检查(①数据 ②到点[仅显式预算] ③断连)并经任务通知唤醒调用方。等待期间调用方在自己上下文睡通知,netTask 全程零占用(消除队头阻塞)。内部 tripwire(原看门狗降级):挂起超 60s 只 `[ERR]` 留痕一条("完成逻辑缺位"探针),**绝不伪造 TIMEDOUT 返回值**。 > > ⚠ **文档契约(嵌入式特有,Python 没有)**:IWDG `required_mask` 内任务(当前产品掩码 `0x1F7`:default/net/ftp/ui/timeSync/adc/iec104/protect)**禁止无限阻塞 recv**——必须 `net_select` 超时或 `net_recv_to` 显式预算并周期喂心跳,否则"对端连上不发数据"这种完全正常的场景 = IWDG 复位。 @@ -1005,7 +1005,7 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms); | `timeout_ms` | 本片等待预算(ms,**必须 >0**;0 = `net_recv` 的无限等待语义,恰与本接口"有界"存在理由相反,直接拒 `NET_ERR_INVAL`——本接口是**显式预算**入口,没有"默认 30s"隐藏值) | | 返回值 | >0=字节数(允许部分读,TCP 分段);`0`=对端关闭(排空残留后);`-1`=真错误(`net_get_errno` 可查);**-2=本片预算内无数据**(**不是 errno**,不得依赖 errno 判超时——`s_net_errno` 是跨任务共享全局量) | -调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(E-02 头阻塞修复,完整例 §11.2);受 IWDG 监控任务的长等待必须走本接口(或 `net_select`)+ 每片喂心跳。 +调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(队头阻塞修复,完整例 §11.2);受 IWDG 监控任务的长等待必须走本接口(或 `net_select`)+ 每片喂心跳。 #### net_sendto - 发送 UDP 数据 @@ -1029,7 +1029,7 @@ int net_close(int sockfd); 释放 Socket 控制块,TCP 先断开连接。⚠ **单连接模型(MCU 端 CH395F)不得对监听 fd 调本 API**——连接与监听同一 fd,close 会连监听一起毁掉(§12.4);EOF 时 net 层已 `auto_relisten`,应用层**不得**在 EOF 时 close 该 fd。"放弃会话但保监听"用下面的 `net_drop_conn()`。 -#### net_drop_conn - 主动拆会话、保留监听(E-02 收口) +#### net_drop_conn - 主动拆会话、保留监听(队头阻塞收口) ```c int net_drop_conn(int sockfd); @@ -1042,7 +1042,7 @@ int net_drop_conn(int sockfd); - **EOF 路径不要调**:EOF 时 net 层 `auto_relisten` 已接管(§12.4),无需也不应再拆 - 经消息队列委托 netTask(同其它 `net_*` API,非 netTask 上下文调用);等待期 netTask 有界占用 ~400-600ms - 模拟器(`net_socket_sim.c`,数据 fd ≠ 监听 fd)实现为关数据连接、监听常驻 -- 台架定案(2026-10-13,E-02):CH395F close **无 RST 语义**——对端内核存活但不 close 时芯片停 FIN_WAIT_2 长尾,监听恢复受限于对端 close;对端配合则 TIME_WAIT 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。不配合场景兜底 = 芯片 TIMEOUT/keepalive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-07 +- 台架定案(2026-10-13):CH395F close **无 RST 语义**——对端内核存活但不 close 时芯片停 FIN_WAIT_2 长尾,监听恢复受限于对端 close;对端配合则 TIME_WAIT 过渡 ~150ms → reconciler 形态④ relisten → 新连接接入。不配合场景兜底 = 芯片 TIMEOUT/keepalive 拆僵尸(E13 定案 30~90s)。详见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-07 #### 自愈与查询 API @@ -1257,7 +1257,7 @@ void tcp_client_reconnect(void) ### 11.2 分片阻塞读(`net_recv_to`,timeSync 模式) -长等待 recv 曾独占 netTask(E-02 头阻塞)。挂起 recv 模型下:单次 `net_recv_to` 单次占用有界(≤ 本片预算)且等待期间 netTask 零占用(挂起模型);调用方累计总预算,收齐协议帧再返回: +长等待 recv 曾独占 netTask(队头阻塞)。挂起 recv 模型下:单次 `net_recv_to` 单次占用有界(≤ 本片预算)且等待期间 netTask 零占用(挂起模型);调用方累计总预算,收齐协议帧再返回: ```c #define FRAME_LEN 8 /* "TIME" + uint32 大端 本地时间 */ diff --git a/docs/网络/BSD_Socket_API_函数调用关系.md b/docs/网络/BSD_Socket_API_函数调用关系.md index 403629e5..318d6b2f 100644 --- a/docs/网络/BSD_Socket_API_函数调用关系.md +++ b/docs/网络/BSD_Socket_API_函数调用关系.md @@ -117,7 +117,7 @@ flowchart TB | 函数 | 设计作用 | 主要调用 | 意图 / 注意 | |---|---|---|---| -| `net_init(ip,mask,gw,mac)` | 芯片一次性配置:探测/复位 → 参数 → MAC → 协议栈初始化 → 8 Socket 缓冲布局 → 使能 PING | `net_pending_cancel_all` → `ch395f_check_exist` / `ch395f_reset` / `set_fun_para(0x08)` / `set_tcp_mss` / `set_retrans_count+period` / `set_keepalive_idle+intvl+cnt` → `set_mac_addr` → `parse_ip_string` + `set_ip_addr` / `set_gwip_addr` / `set_mask_addr`(或 `set_dhcp`)→ `ch395f_init` → `set_send_buf` / `set_recv_buf` ×8 → `ping_enable` | ① **重建前**先取消全部挂起 recv 并逐一通知(不通知 = 调用方永久挂死),且必须早于控制块清零与芯片复位;② 重传/KeepAlive/**MAC**/IP/GW/MASK 参数**必须**在 `ch395f_init` 之前下发(`docs/芯片手册/CH395F.md` §9.2.1:SET_MAC 为步骤①,INIT_CH395 加载 MAC——顺序反了本次上电不生效,P1-7);③ `fun_para=0x08`:bit3 SOCKET_CLOSE(DISCONNECT/TIMEOUT 后不清缓冲,软件排空后手动关);bit0/bit1 保持 0——8 个 Socket 由索引 `_SN` 寻址 + 缓冲显式分配实现,与 FUN_PARA 无关(bit0 是保留位,必须为 0);④ 缓冲布局每 Socket 6 块(收 4 + 发 2)×8 = 48 块恰好用尽,Socket 0~7 必须显式重分配(TRAP-NET-01/-02);⑤ 捕获 `xTaskGetCurrentTaskHandle` 存入 `s_net_task_handle`(防自通知死锁的判据) | +| `net_init(ip,mask,gw,mac)` | 芯片一次性配置:探测/复位 → 参数 → MAC → 协议栈初始化 → 8 Socket 缓冲布局 → 使能 PING | `net_pending_cancel_all` → `ch395f_check_exist` / `ch395f_reset` / `set_fun_para(0x08)` / `set_tcp_mss` / `set_retrans_count+period` / `set_keepalive_idle+intvl+cnt` → `set_mac_addr` → `parse_ip_string` + `set_ip_addr` / `set_gwip_addr` / `set_mask_addr`(或 `set_dhcp`)→ `ch395f_init` → `set_send_buf` / `set_recv_buf` ×8 → `ping_enable` | ① **重建前**先取消全部挂起 recv 并逐一通知(不通知 = 调用方永久挂死),且必须早于控制块清零与芯片复位;② 重传/KeepAlive/**MAC**/IP/GW/MASK 参数**必须**在 `ch395f_init` 之前下发(`docs/芯片手册/CH395F.md` §9.2.1:SET_MAC 为步骤①,INIT_CH395 加载 MAC——顺序反了本次上电不生效);③ `fun_para=0x08`:bit3 SOCKET_CLOSE(DISCONNECT/TIMEOUT 后不清缓冲,软件排空后手动关);bit0/bit1 保持 0——8 个 Socket 由索引 `_SN` 寻址 + 缓冲显式分配实现,与 FUN_PARA 无关(bit0 是保留位,必须为 0);④ 缓冲布局每 Socket 6 块(收 4 + 发 2)×8 = 48 块恰好用尽,Socket 0~7 必须显式重分配(TRAP-NET-01/-02);⑤ 捕获 `xTaskGetCurrentTaskHandle` 存入 `s_net_task_handle`(防自通知死锁的判据) | | `net_get_generation()` | 读网络代次号(纯 RAM) | 无 | FTP/UI/对时服务据此感知"网络已重建"→ 放弃旧监听并重建 | | `net_generation_inc()` | 代次号 +1 | 无 | 唯一合法递增点 = `net_stack_bring_up()` 末尾(PHY/链路就绪后)——太早递增会让服务在网络不可用时抢跑重建;模拟器端恒为 0(无重建概念,上层自愈逻辑空转) | @@ -140,9 +140,9 @@ flowchart TB | `net_send` | `NET_MSG_SEND` | `net_send_locked` | `ch395f_write_send_buf`(分块)+ 等 SENBUF_FREE(`net_poll` 轮询,**有界自旋 ≤ `NET_SEND_SPIN_MS`**);超预算登记挂起 send(`net_send_pending_register`) | 实际字节数 / -1 | 分块 ≤ `NET_SEND_CHUNK_MAX` = 1KB(**硬件发送 FIFO 硬上限**,TRAP-NET-16:写穿 FIFO = 数据错位);`send_ready` 门控用 `net_poll` 轮询而非 osDelay,**自旋超 `NET_SEND_SPIN_MS`(2ms)即转挂起**(netTask 最坏停滞由 1s 压到 2ms);短写**必须**告警——静默截断会让对端永久等待剩余字节(2026-09-21 实证:UI 推流应答截断 → 客户端失联) | | `net_recv` | `NET_MSG_RECV`(timeout=0→**无限等待**,无 30s 默认预算) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / CONNRESET;阻塞形态无 TIMEDOUT 一档) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞(`blocking` 属性=1 且未 DONTWAIT)→ **登记挂起后立即返回**(挂起登记,netTask 零占用),唤醒源=数据/EOF/close/网络重建;DONTWAIT / `blocking=0` + CLOSED/session_dead → EOF + 触发重监听;⚠ IWDG 受监控任务禁用无限形态(`net_socket.h` 文档契约) | | `net_recv_nb` | `NET_MSG_RECV`(flags=DONTWAIT) | `net_recv_locked` | 同上 | >0 / 0 / -1(WOULDBLOCK 等) | 原 `NET_MSG_DONTWAIT` 路径转正的具名入口;不改变 `blocking` 属性;受 IWDG 监控 / 有周期职责任务的推荐默认(ui_net_sink / iec104_serv 已迁) | -| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套 + **显式预算**入口(等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) | -| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣 | 0 / -1 | **先**结束本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,N-30) | -| `net_drop_conn` | `NET_MSG_DROP_CONN` | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_request` | 0(含幂等)/ -1 | **拆会话保监听**(E-02 收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 登记重监听、失败交 D10 + reconciler 形态④ | +| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | 队头阻塞修复配套 + **显式预算**入口(等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) | +| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣 | 0 / -1 | **先**结束本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,属「relisten 沉降段原语化受阻」) | +| `net_drop_conn` | `NET_MSG_DROP_CONN` | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_request` | 0(含幂等)/ -1 | **拆会话保监听**(队头阻塞收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 登记重监听、失败交 D10 + reconciler 形态④ | | ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 | | ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 | @@ -164,7 +164,7 @@ flowchart TB ### 4.5 挂起 recv 子系统(阻塞等待不独占 netTask) -**模型**:阻塞等待 = **挂起请求登记**(每 fd 至多一个,禁止覆盖)+ **主循环节拍完成检查** + **通知唤醒**(LwIP netconn 模型)。消除 E-02 头阻塞(旧实现:netTask 自旋等数据,一次 30s 空闲 recv 冻住全网 17.7s)。 +**模型**:阻塞等待 = **挂起请求登记**(每 fd 至多一个,禁止覆盖)+ **主循环节拍完成检查** + **通知唤醒**(LwIP netconn 模型)。消除队头阻塞(旧实现:netTask 自旋等数据,一次 30s 空闲 recv 冻住全网 17.7s)。 | 函数 | 设计作用 | 主要调用 | 意图 / 注意 | |---|---|---|---| @@ -387,7 +387,7 @@ sequenceDiagram |---|---|---| | 1 | **一切芯片访问收敛 netTask**(唯一执行者);跨任务调用走队列 | TRAP-NET-14:跨任务 SPI 竞争破坏芯片寄存器 → 连接必超时 | | 2 | **结果只经任务通知值回传**;消息按值入队,调用方看不到 netTask 对消息的写入;勿给消息加"回传用"字段 | TRAP-NET-10:6 个 API 永远返回 -1;"加 msg.err 字段"的修法曾致 `ui_net_sink` 每次空轮询误判真错误 → 连上即断 | -| 3 | **netTask 不为任何人自旋等待**:阻塞 recv = 挂起登记;重开失败 = 重试登记(D10);初次 listen/connect 的 open 重试环(`net_sock_reset_sync` 有界等待,GRACEFUL 100ms 底 / 600ms 封顶)是唯一豁免(启动期无竞争,原 3×500ms 盲等已换确定性排渣) | E-02:一次空闲 30s recv 冻住全网 17.7s;旧重监听 osDelay(500) → 恢复 588ms、期间重连被拒 | +| 3 | **netTask 不为任何人自旋等待**:阻塞 recv = 挂起登记;重开失败 = 重试登记(D10);初次 listen/connect 的 open 重试环(`net_sock_reset_sync` 有界等待,GRACEFUL 100ms 底 / 600ms 封顶)是唯一豁免(启动期无竞争,原 3×500ms 盲等已换确定性排渣) | 队头阻塞整改:一次空闲 30s recv 冻住全网 17.7s;旧重监听 osDelay(500) → 恢复 588ms、期间重连被拒 | | 4 | **netTask 不向自身发消息 / 不自我挂起**(挂起登记对执行任务自身发起直接拒绝) | 自等自通知 = 死锁 | | 5 | **基于状态读的重建动作必须去抖 + 冷却**:分级门槛 ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(100ms/轮)+ 冷却 1s | 2026-09-23:无去抖 → 监听反复重建直至 `relisten give up`,三服务口全死 | | 6 | **异常释放(TIMEOUT)后复监听前必须隔离**:排空 RX 残渣 + 等 CLOSED/TCP_CLOSED;隔离上限 < 重试放弃时限 | TRAP-NET-18:~2KB 残渣被下条连接当自己的数据 → 首笔上传截断 2048B 并误报 226 成功;上限=2s 时隔离耗尽重试预算 → 监听被弃 | @@ -406,7 +406,7 @@ sequenceDiagram | `ch395f_check_exist` / `ch395f_reset` | `net_init` | 上电探测与复位 | | `ch395f_set_fun_para(0x08)` | `net_init` | bit3 手动关缓冲(SOCKET_CLOSE);bit0/1=0,Socket 个数与 FUN_PARA 无关 | | `ch395f_set_tcp_mss` / `set_retrans_count` / `set_retrans_period` / `set_keepalive_idle+intvl+cnt` | `net_init`(**均须在 `ch395f_init` 之前**) | 值见 `net_config.h` | -| `ch395f_set_mac_addr` | `net_init`(**须在 `ch395f_init` 之前**) | 写芯片 EEPROM;INIT_CH395 加载 MAC(`docs/芯片手册/CH395F.md` §9.2.1 步骤①,P1-7) | +| `ch395f_set_mac_addr` | `net_init`(**须在 `ch395f_init` 之前**) | 写芯片 EEPROM;INIT_CH395 加载 MAC(`docs/芯片手册/CH395F.md` §9.2.1 步骤①) | | `ch395f_set_ip_addr` / `set_gwip_addr` / `set_mask_addr` / `set_dhcp` | `net_init`(**均须在 `ch395f_init` 之前**) | 固定 IP 或 DHCP | | `ch395f_init` / `ch395f_ping_enable` | `net_init` | 协议栈初始化(INIT_CH395)/ PING(须在 init 之后) | | `ch395f_set_send_buf` / `ch395f_set_recv_buf` | `net_init`(8 Socket 布局);`net_bind`(UDP)(Socket 4~7 兜底) | 每 Socket 收 4 块 + 发 2 块 | diff --git a/docs/网络/Net层阻塞模型架构设计.md b/docs/网络/Net层阻塞模型架构设计.md index ae820fbd..64beb464 100644 --- a/docs/网络/Net层阻塞模型架构设计.md +++ b/docs/网络/Net层阻塞模型架构设计.md @@ -52,7 +52,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线 | B 有界分片轮询(`net_recv_to` 分片) | ≤ `timeout_ms` | 分片间隙可用 | ⚠️ 止血:仍周期性占用,调用方须自管总预算 | | C **挂起登记(本模型)** | 微秒级(登记一次) | 照常服务整个队列 | ✅ 结构性消灭 | -三条路线的量化对照见历史审计 E-02(头阻塞;属主文档已于 2026-10-15 删除,编号仅作指称)。本模型选 C;路线 B 的入口 +三条路线的量化对照见历史审计的队头阻塞发现(属主文档已于 2026-10-15 删除)。本模型选 C;路线 B 的入口 `net_recv_to` **仍在 API 面上**,定位是**调用方显式预算**(§6.2、§8),与挂起登记互补。 > **send 是同一问题的第二个面**:`net_send` 写入 CH395F 发送 FIFO 后要等 `send_ready` diff --git a/docs/网络/网络自愈架构设计.md b/docs/网络/网络自愈架构设计.md index 3a7dc546..b34c3660 100644 --- a/docs/网络/网络自愈架构设计.md +++ b/docs/网络/网络自愈架构设计.md @@ -12,12 +12,12 @@ | 记号 | 是什么 | 在哪查 | |---|---|---| | `I1`~`I8` | 本架构的**不变量**(Invariant)——违反即有实测后果 | §2 | -| `E0`~`E19`、`E-02` | **台架实验**编号(E = Experiment):每个实验是「假设 → 方法 → 判据 → 门禁哪个决策」 | §5.2(原始记录已归档,见 §12) | +| `E0`~`E19` | **台架实验**编号(E = Experiment):每个实验是「假设 → 方法 → 判据 → 门禁哪个决策」 | §5.2(原始记录已归档,见 §12) | | `F-NET-nn` / `F-DRV-nn` / `F-MS-nn` | 审计的**编号发现**:NET 层 / 驱动层 / 状态镜像层 | §7(只保留仍被引用的 12 条) | | `M1`~`M5` | 实验固件里的**候选修改代号**:M1 沉降无条件化 · M2 门控换 `net_listener_armed` · M3 可疑门 · M4/M4-lite 删沉降段全局读即清 · M5 `sock_reset` quiescent 排空 | §4.1、§4.3 | | `形态①`~`形态⑤` | `net_reconcile_tick` 的五类**失同步判据** | §4.1 | -| `U1`~`U15` | 状态迁移的**"无人负责"组合**(架构欠债清单) | `docs/问题记录/CH395F_Trap_Records.md` B10 行 | -| `TRAP-NET-nn`、`B3`/`B10` | **陷阱记录**条目与 B 系列 | 同上 | +| `U1`~`U15` | 状态迁移的**"无人负责"组合**(架构欠债清单) | `docs/问题记录/CH395F_Trap_Records.md` 审计遗留章节 | +| `TRAP-NET-nn` | **陷阱记录**条目 | 同上 | | `N-nn` | `docs/任务规划/登记簿.md` 的**任务/发现编号**(⚠ 已销项的号不再可查,本文引用处即其最终事实描述) | 该登记簿 | | `D1`~`D7` | 本文**决策记录** | §10 | | `自愈收敛阶段 N` | 2026-10 的分阶段实施标签,**只存在于历史文档与变更记录**(代码注释已全部改为平实语言);勾选清单已归档(§12)——**勿用它叙述设计,也勿再写回注释** | §12 | @@ -29,11 +29,11 @@ > ⚠ **字母 `E` 有两套编号,靠连字符区分**: > - `E0`~`E19`(**无**连字符)= 本文的**自愈实验**编号,逐条见 §5.2; -> - `E-02`(**带**连字符)= **历史审计发现**编号(属主文档已于 2026-10-15 删除, +> - 带连字符的 `E-` = **历史审计发现**编号(属主文档已于 2026-10-15 删除, > 编号冻结于 `docs/编号规范.md` §4.3)—— > §5.2 末行引用它是为了记录该发现的**收口台架**,**不是**实验编号 -> (证据:`test/host/e02_probe.py` 文档串自述"历史审计 E-02 的 timeSync 触发形态"); -> - 该审计编号族冻结于 `E-63`,不再新增。 +> (证据:`test/host/e02_probe.py` 的文档串自述它是该审计发现的 timeSync 触发形态探针); +> - 该审计编号族已冻结、不再新增,引用一律改说人话。 ## 1. 为什么是这个架构 @@ -422,7 +422,7 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin 必须 `net_select` 超时或 `net_recv_to` 显式预算。 - 上层 32 轮盲收(`ui_net_sink` / `iec104_serv`)保留现状(E6 裁决,§4.8)。 -### 4.6 `net_drop_conn`——应用主动拆会话保监听(E-02 收口;N-17 异步收口 2026-10-25) +### 4.6 `net_drop_conn`——应用主动拆会话保监听(队头阻塞收口;异步收口 2026-10-25) - 语义(**返回值 = 拆除与重建已登记**,不代表芯片已拆完):`net_pending_settle_close` 先结束挂起 recv(回传结果 + 唤醒调用方)→ 监听型(`auto_relisten`)fd: @@ -431,7 +431,7 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin 按 30ms 节奏执行(与被动超时/断连释放同一台机器),失败交 D10 + 形态④; 非监听 client fd 无监听可保,仍同步 `net_sock_reset_sync`(600ms 封顶)。 端口恢复窗 ~0.4~1s,与被动断连恢复窗(E1 发现 A)同型。 -- **为什么监听型不得同步等待(N-17 定位,论证随结论一并留档)**:旧形态在 +- **为什么监听型不得同步等待(探针窗口心跳缺口定位时的论证,随结论一并留档)**:旧形态在 `NET_MSG_DROP_CONN` 处理器内同步跑 `net_sock_reset_sync(QUARANTINE)`(2ms 节奏、 400ms 底/600ms 顶,自家注释原记「等待期 netTask 有界占用 ~400-600ms」),netTask 的 1ms 主循环冻结在同一迭代内:NET 位心跳停摆 + 串行总线整窗停摆(UI 推流/FTP/ @@ -453,7 +453,7 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin `time_sync.c`(不消费返回值,accept 轮询天然容忍 WOULDBLOCK 窗)。 - **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调** (`auto_relisten` 已接管,再调属重复拆)。 -- 芯片物理边界(E-02 台架,异步收口逐字不变——只移动「等待发生在哪」): +- 芯片物理边界(台架定案,异步收口逐字不变——只移动「等待发生在哪」): `close` **无 RST 语义**——对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150ms → 形态④ relisten(恢复全程 ~500ms);对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close**,兜底 = 芯片 TIMEOUT/KeepAlive 拆 @@ -614,8 +614,8 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回) > ⚠ **这些编号是冻结标识**:`net_socket.c` / `net_events.c` / `net_listener.c` 注释、 > `test/host/e02_probe.py`、`test/host/e6_soak.py`、`docs/问题记录/CH395F_Trap_Records.md` > 都直接引用它们,**重排或改名会断链**。 -> ⚠ 末行 `E-02` **不是实验编号**——它是**历史审计发现**编号 -> (`E-` 族,属主文档已于 2026-10-15 删除,冻结于 `E-63`),列在此处只为记录该发现的**收口台架**。 +> ⚠ 末行 **不是实验编号**——它记录的是**历史审计发现** +> (带连字符的 `E-` 族,属主文档已于 2026-10-15 删除并冻结),列在此处只为记录该发现的**收口台架**。 > 区分规则:**带连字符 = 审计发现,无连字符 = 自愈实验**。 | 编号 | 验证了什么(方法) | 结论(现状口径) | 落点 | @@ -636,11 +636,11 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回) | **E13** | 产品固件 + PC 网卡抖动注入,追"残渣风暴态"根因 | 根因 = **僵尸会话**(对端静默死亡、芯片侧 ESTABLISHED 未清、单连接监听被占),而非跨烧录残渣;`RSTI←NRST` 推翻"跨烧录存活";自愈链端到端 30~90 s 有效 | §4.7;PASV 预清缺口由 `sock_reset` 收口 | | **E14** | 48 轮直读 SINT 矩阵复核"丢中断"说法(drain-int 现场抓取 + SINT 补读 ×3 + 超时归类) | **芯片侧事件丢失 = 0**;"丢失"实为 drain 读清消费 + 门控竞态 + 主机同步;幽灵态本轮 0/48 未复现 | §1 事实 2;生产 listen/relisten 排事件挪到 LISTEN 命令**之前**;TRAP-NET-17/-20 归因更正(机制保留不变) | | **E15** | 幽灵态归因复核(WCH 官网 + 手册出处) | `0x04` + 旧端口 = 芯片**如实报告**旧连接拆链进行中,不是谎报:bit3=1 是自家配置(手册 §5.52②);拆链走 RFC 9293 §3.3.2 状态机(FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT,2MSL 驻留);WCH 官方博客确认"断连即重连被拒"、官方方案 = 关闭完成后重开 | TRAP-NET-20 口径"谎报"→"如实报告";settle 等待 = 官方方案;幽灵甄别 = 护栏;I3 加强 | -| **E16** | 把 relisten 隔离段收口到驱动 `sock_reset` 原语(删 net 层自建序列与参数副本),同板 A/B 跑 `ftp_abort_probe` + `relisten_stress` + `n30_repro` | **收口失败、已回退**:失效面精确落在"中止后首笔复连";根因两条——残渣在 CLOSE **之后**才落进 RX(一次排空接不住);复位机"逼回 CLOSED"的目标与上层立即复用同一 Socket 冲突 | §8 延后项 1(三条前置语义);§4.4 警告;登记 **N-30** | +| **E16** | 把 relisten 隔离段收口到驱动 `sock_reset` 原语(删 net 层自建序列与参数副本),同板 A/B 跑 `ftp_abort_probe` + `relisten_stress` + `n30_repro` | **收口失败、已回退**:失效面精确落在"中止后首笔复连";根因两条——残渣在 CLOSE **之后**才落进 RX(一次排空接不住);复位机"逼回 CLOSED"的目标与上层立即复用同一 Socket 冲突 | §8 延后项 1(三条前置语义);§4.4 警告;登记「relisten 沉降段原语化受阻」 | | **E17** | **单一所有者收口**:`net_listener_try`(当场动作、**无状态门**,各调用点自带门)改为 `net_listener_request`(只登记意图)+ `net_listener_tick`(唯一执行者,门集含"芯片已在 LISTEN 即了结"),同板全量回归 | 全绿;**延后一轮(≤1 ms)执行不伤即时性**(恢复窗 p50 与当场动作持平);relisten 留痕减半 | §4.2 登记/执行分离;§9.2 门集;§11.1 判据 | | **E18** | **负向注入打靶 · 三轮**(注入代码不入库):① 吞掉 DISCONNECT(在 i104 的 200 ms accept 轮询窗内 RST);③ 绕过本层簿记直接 close 芯片空闲监听;⑤ 开盲区吞掉芯片自发的中断 + 等芯片真 CLOSED 再 open+listen | **①③⑤ 全部命中并救回**;①⑤ 的前提会被更快的消费者路径消解 → 常态 0 命中;⑤ 还要先越过芯片 `FIN_WAIT_2`(E15 的 2MSL 长尾,实测 ≈1 s) | §4.1.4 / §4.1.5:**三条均不可降级为只留痕**;⑤ 的**取消挂起 recv/send** 不可省 | -| **E19** | **`force open` 反事实 A/B**(本层已收口 CLOSED + 芯片停 `FIN_WAIT_2` `0x05/0x06` + **对端挂住不关** = E-02 场景;A = 现状放行,B = 只留痕不放行) | **放行与不放行的端口恢复时刻同一分布** → 动作在全部可产现场景内无可测收益;真正兜住"监听器永久失效"的是 **D10 预算 + 形态④ 重新登记**。两条实测坑:① 本层直接改 `state` **不会**进沉降窗(窗只由 `net_listener_settle` 开启);② `relisten retry budget exhausted` 在此被稳定复现 = 对端不配合拆链的签名,**非回归** | §4.3:800 ms 上限降级为**只留痕**(`keep waiting` 只留首末、隔离标志不清 → 继续排渣) | -| **E-02**(审计编号) | `test/host/e02_probe.py` 台架定案 `net_drop_conn` 收口后的时序 | 对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150 ms → 形态④ relisten,恢复全程 ~500 ms;对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close** | §4.6 | +| **E19** | **`force open` 反事实 A/B**(本层已收口 CLOSED + 芯片停 `FIN_WAIT_2` `0x05/0x06` + **对端挂住不关** = 队头阻塞场景;A = 现状放行,B = 只留痕不放行) | **放行与不放行的端口恢复时刻同一分布** → 动作在全部可产现场景内无可测收益;真正兜住"监听器永久失效"的是 **D10 预算 + 形态④ 重新登记**。两条实测坑:① 本层直接改 `state` **不会**进沉降窗(窗只由 `net_listener_settle` 开启);② `relisten retry budget exhausted` 在此被稳定复现 = 对端不配合拆链的签名,**非回归** | §4.3:800 ms 上限降级为**只留痕**(`keep waiting` 只留首末、隔离标志不清 → 继续排渣) | +| **队头阻塞审计**(非实验编号) | `test/host/e02_probe.py` 台架定案 `net_drop_conn` 收口后的时序 | 对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150 ms → 形态④ relisten,恢复全程 ~500 ms;对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close** | §4.6 | ## 6. 红线(明确不做的事) @@ -690,7 +690,7 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回) ③ **槽位可加入**:每 Socket 只有一个复位槽位,`ch395f_sock_reset_begin` 返 -1(已在运行) 不等于失败——同步包装必须**加入**已在跑的复位机,否则 `net_close` 走 "proceed anyway"、排渣被跳过。 - 补齐 ①②③ 并各自回归后再接;登记 **N-30**(`docs/任务规划/登记簿.md`)。 + 补齐 ①②③ 并各自回归后再接;登记 `docs/任务规划/登记簿.md` 条目「relisten 沉降段原语化受阻」。 2. **部分读→再查 recv_len 定向 TC**:旧"部分读归零"机理未证实(与手册 §5.34 及 TC-NET-201 相反),注释约束已降级为历史观察——以定向用例结案(陷阱记录「台架纪律」 节已挂该待办)。 diff --git a/docs/门禁说明/bsp-err.md b/docs/门禁说明/bsp-err.md index 05b0f7fe..0bce67e3 100644 --- a/docs/门禁说明/bsp-err.md +++ b/docs/门禁说明/bsp-err.md @@ -7,7 +7,7 @@ `language: system`、`pass_filenames: false`)。 - 规则脚本:`scripts/check_bsp_err.py`(纯标准库;文件头就是规则正文)。 - 约定正文:`Drivers/BSP/bsp_err.h`(**header-only,无 `.c`** → 不占体积、不需双端注册)。 -- 背景与整改史:S-5 条目的完整记录已随审查报告删除,现存依据 = 登记簿 **S-5** 行与 `Drivers/BSP/bsp_err.h`。 +- 背景与整改史:原审查报告里该条契约的完整记录已随报告删除,现存依据 = `Drivers/BSP/bsp_err.h` 与 `scripts/check_bsp_err.py` 的存量基线。 ## 1. 它在查什么 diff --git a/docs/门禁说明/cubemx-survival.md b/docs/门禁说明/cubemx-survival.md index b9f8fcec..810c8ba4 100644 --- a/docs/门禁说明/cubemx-survival.md +++ b/docs/门禁说明/cubemx-survival.md @@ -63,7 +63,7 @@ CubeMX 重生成存活审计.......Failed **`--write` 必须与"确认过的改动"写在同一个提交里并说明。** 6. 它不判断定制**是否仍然正确**,只判断"还在不在原来的位置"。 7. **基线漏项 = 静默不检**(2026-10-25 实证):`--check` 只比对基线里已登记的条目——一条定制 - 若从未进过基线,检查面再全也不会报它。`docs/任务规划/测试与架构专项.md` P1-10 返工时 + 若从未进过基线,检查面再全也不会报它。`docs/任务规划/测试与架构专项.md` 那次返工时 `--write` 顺带补出了原本缺失的 `net-msgq-adopt`(`Src/freertos.c` RTOS_QUEUES 区须含 `net_msg_queue_adopt`),该条目此前 处于"不存在于基线 → 永不检查"态。**新增标记区外定制时,登记必须与代码同提交**(同 §5 的 diff --git a/docs/门禁说明/host-unit-tests.md b/docs/门禁说明/host-unit-tests.md index c0da9e2c..41aa85eb 100644 --- a/docs/门禁说明/host-unit-tests.md +++ b/docs/门禁说明/host-unit-tests.md @@ -34,12 +34,12 @@ | 套件(登记出处) | 规模 | 为什么非要有 | |---|---|---| -| `sha256`(N-19) | 13 例 | FIPS 180-4 标准向量(空串 / `abc` / 56 B 补位跨界 / 1 MB)+ `hex_eq` 非法输入。哈希算错是"正确口令被拒 / 错误口令放行"的静默语义错误,板端只见 `530`/`230` 看不出根因 | -| `ftp-auth`(N-19) | 36 例 | 连续 5 败锁定 / 60 s 解锁 / 成功清零 / tick 32 位回绕。板端每验一条要真等 60 s、回绕要跑满 49.7 天;纯函数(tick 由参数注入)就是为了让这些分支能毫秒级穷举 | +| `sha256`(FTP 鉴权) | 13 例 | FIPS 180-4 标准向量(空串 / `abc` / 56 B 补位跨界 / 1 MB)+ `hex_eq` 非法输入。哈希算错是"正确口令被拒 / 错误口令放行"的静默语义错误,板端只见 `530`/`230` 看不出根因 | +| `ftp-auth`(FTP 鉴权) | 36 例 | 连续 5 败锁定 / 60 s 解锁 / 成功清零 / tick 32 位回绕。板端每验一条要真等 60 s、回绕要跑满 49.7 天;纯函数(tick 由参数注入)就是为了让这些分支能毫秒级穷举 | | `iec104`(B4) | 82 例 | CP56 往返 / APCI 半帧粘帧坏帧 / STARTDT-STOPDT / 总召分片 / COV 死区 / k=12 窗满 / w=8 触发 S / 三个超时定时器 / N(R) 倒退。板端复现要真主站 + 真等待(t1=15 s、窗满要发满 12 帧) | | `freq`(B8) | 46 断言 | 扫频 45~55 Hz 误差 ≤0.02 Hz(实测 0.0101)/ 1% 噪声 ≤0.10 Hz / 启动收敛 ≤40 ms / 阶跃 ≤120 ms。板端无标准源照不出来;定长窗的负序泄漏纹波(0.317 Hz)只有合成扫频能暴露 | | `soe`(B9) | 32 断言 | 64 B 定长记录编解码往返 / 截断与坏校验和拒绝 / IV 位 / seq 连续性 / 类型枚举。记录格式是**磁盘上的字节级契约**,错了要 FTP 下载再解码才看得见 | -| `nand-port`(N-21) | 149 断言 | 整页扫描的空闲判定(页首 64 B 全 0xFF 而第 1000 字节有数据等形态)+ **与旧算法对照**(旧实现只看页首,同一页答"空闲")。这类缺陷真机极难复现,发作后只表现为"某次复位后丢数据" | +| `nand-port`(dhara 移植层两条硬约束) | 149 断言 | 整页扫描的空闲判定(页首 64 B 全 0xFF 而第 1000 字节有数据等形态)+ **与旧算法对照**(旧实现只看页首,同一页答"空闲")。这类缺陷真机极难复现,发作后只表现为"某次复位后丢数据" | - 运行器统一用 MSVC `cl.exe`(远端 CI 的 L0.5 层);公共骨架是 `test/host/common/unit_test_harness.py` —— **改动这个骨架会同时触发 16 个钩子** @@ -50,7 +50,7 @@ - 状态机、编解码、换算这类逻辑,在真机上验证成本极高(要么构造故障、要么等复现); 在 PC 上毫秒级就能重跑,是"改完敢不敢提交"的第一层信心。 - 它们**已经在真实收口上起过作用**:`nand-port-unit-test` 与 `sys-clock-unit-test` 的桩里就 - 注入过 RTC 读失败等错误路径,用来证明失败分支的行为(本次 S-5 收编时同步改过这两个 runner + 注入过 RTC 读失败等错误路径,用来证明失败分支的行为(本次 BSP 错误码契约收编时同步改过这两个 runner 的 include 目录,见 §6 第 4 条)。 ## 3. 触发面 diff --git a/docs/门禁说明/id-check.md b/docs/门禁说明/id-check.md index 7deb6fa2..1ac0b86a 100644 --- a/docs/门禁说明/id-check.md +++ b/docs/门禁说明/id-check.md @@ -2,7 +2,7 @@ > 一句话:它管**"号"这个东西不许野生**——每个编号必须属于一个已登记的族、形态合法、 > 有定义处、有人引用、引用它的时候说清是哪篇文档的号。 -> 规范正文 = `docs/编号规范.md`(§4 前缀总表、§8 门禁),本门禁是那八项的机械执行。 +> 规范正文 = `docs/编号规范.md`(§1 先问三句、§4.1 允许用号的族〔穷举〕、§8 门禁与实测基线),本门禁是那八项的机械执行。 - 配置:`entry: python scripts/check_ids.py --strict`,`pass_filenames: false`, `files:` 覆盖 `.md/.c/.h/.py/.yaml/.json/.txt` 与配置文件本身。 @@ -27,7 +27,7 @@ ## 2. 为什么要它 -- 本仓同时存在 `N-xx`/`N-x`/`TC-*`/`E-xx`/`TRAP-*`/`D10`/`U12` 等几十族编号,历史上出过 +- 本仓曾同时存在几十族编号(`N-xx`/`TC-*`/`E-xx`/`TRAP-*`/`D10`/`U12`…),历史上出过 **真事故**:登记簿销项删行后号被复用(撞号)、引用指向已被删的号(悬空)、同一字面量两个 属主(重复定义)。规范 §1 把成因写得很直白:连字符与补零偷偷承载语义。 - **本次实测拦下过我自己两次**:一次是"字母加数字"自造号(校验 1),一次是引用某个跨文档 @@ -36,18 +36,18 @@ - **`owner_docs` ≠ 定义集**(2026-10-25 起):族可以"有登记处"却"没有定义位"。判可判定只看 `owner_docs` 是否解析出真实文件;而悬空(校验 3)/ 撞号(校验 4)/ 裸形(校验 2)/ 紧邻(校验 6) 一律只看 `_def_owners`。两者仅在一种情形下不同:**`snapshot_only` 族的登记处挂在规范正文** - (`docs/编号规范.md` §4.5 那种冻结存档表)。规范里混着大量写法示例(`N-99`、`S1`、`N-31`), + (`docs/编号规范.md` §4.3 说的冻结前缀:登记处只剩冻结存档,甚至只存在于规范正文)。规范正文里混着大量写法示例, 若拿它当定义集会立刻误伤——实测裸形 `S1` 被判成 `S-1` 漏连字符 238 条、表里 `S-3` 与登记簿 S-3 被判重复定义。所以这类族的 `_def_owners` 为空,等价于"属主已删、只有存档"。 代价要认清:这类族的号**不会再被校验 3 兜住**,新增引用只能靠别名表或人工核。 ## 3. 触发面 -- 扫描面以 git 清单为准。当前一次实跑:**696 个文件、编号字面量 10410 处**。 +- 扫描面以 git 清单为准。最近一次实跑(2026-10-26 编号迁移批次后):**754 个文件、编号字面量 8,230 处**(方案 A 之前 10,956 → 方案 A 之后 8,946 → 方案 B 之后 8,503 → 编号迁移批次后 8,230)。 - **排除**:`Lib/`、`Middlewares/`、HAL/CMSIS、`docs/芯片手册/`、`docs/硬件平台/`。 最后一个是关键:硬件文档里的**元器件位号 `U1`/`C12`/`R3`/`L5`/`D7`** 与我们的编号字面 同形,不整体排除就会大量误报(脚本里另有一套位号识别,但仍**没有解除这两个目录的语料排除**)。 -- 已声明的"同号不同义"族对(`docs/编号规范.md` §4.3):实跑 583 个字面量走 `collides_with` +- 已声明的"同号不同义"族对(`docs/编号规范.md` §4.3):实跑 876 个字面量走 `collides_with` 白名单,**不计入校验 4**。 ## 4. 报错怎么读 @@ -61,12 +61,12 @@ - 单独跑:`& '.venv\Scripts\python.exe' scripts/check_ids.py --strict --verbose`(`--verbose` 带修法,`--top 30` 多打,`--only 1,3,5` 只跑指定校验)。 - 常用旗标:`--strict`(钩子用的档位)、`--verbose`、`--write-backlog` / `--prune-backlog` (重建 / 收紧台账)、`--update-baseline`(把当前可判定比例写回 `id_registry.json`,只许涨)、`--no-trace`。 -- 存量在 `scripts/id_known_backlog.json`(按指纹豁免,实跑 **640 处**在册)。 +- 存量在 `scripts/id_known_backlog.json`(按指纹豁免,实跑 **0 处 / 0 类指纹**在册;HEAD 台账 303 条指纹 / 651 处)。 ## 5. 好处 -- **拦下过真事(2026-10-25 追加)**:`docs/任务规划/测试与架构专项.md` P1-10 的返工提交被 - **校验 6** 拦下 4 处——C 注释里裸写编号、未紧邻属主文档名;改成「条目属主 `docs/任务规划/测试与架构专项.md` P1-10」即过。 +- **拦下过真事(2026-10-25 追加)**:`docs/任务规划/测试与架构专项.md` 一条返工提交被 **校验 6** + 拦下 4 处——C 注释里裸写该文档的条目编号、未紧邻属主文档名;补齐文档名即过。 实证**紧邻写法在代码注释里同样是硬约束**,不是只约束 Markdown。 - 把"编号卫生"从人脑记忆变成机器判定:销项只做一半、引用写错文档、自造号,都会在提交时被点名。 @@ -74,13 +74,13 @@ ## 6. 限制与盲区(这一节请当正文读) -1. **绿灯不等于编号都合法**:门禁是【增量拦截】——存量 640 处按台账豁免。它保证的是 +1. **绿灯不等于编号都合法**:门禁是【增量拦截】——存量 0 处按台账豁免。它保证的是 "没变差",不是"干净"。 2. **`collides_with` 白名单挡事故、不挡可读性**。已声明同号不同义的族对不再报——于是 "新造一个和已有裸形撞车的代号"可以一路绿灯。**这正是本门禁今天照不出来的东西**: 我自己造的四个字母数字代号在被改名前,没有任何一条校验会响(同形多义已被声明为已知事实)。 结论:**编号可读性要人管**,`docs/门禁说明/bsp-err.md` §6 那条就是这个教训的落点。 -3. 实跑有 **867 处"裸形盲区"**:形态太像普通词/数字组合的字面量识别不出来,等于不在管辖内。 +3. 实跑有 **827 处"裸形盲区"**:形态太像普通词/数字组合的字面量识别不出来,等于不在管辖内。 4. 校验 7(孤儿)只告警不拦提交:定义完一直没人引用的号会长期存在而门禁不响。 5. "定义"的判据是"出现在属主登记处文档里",**不校验语义**:写进错的属主文档、或写成一句 否定("该号从未存在")也算定义——批量改名工具最阴的失效模式就是"字面改对、意思改错"。 @@ -88,7 +88,9 @@ `scripts/rename_maps/` 里必然写着旧的非法形态),否则第一次提交就被自家审计表当"新账"拦下。 7. 校验 8 只许涨不许跌:好处是防退化,代价是**顺手写一个未登记族的号就会红**,哪怕与本次改动无关。 8. 它不懂"这个号该不该存在"。新增一族编号是否合理,仍是 `docs/编号规范.md` 的人工判断。 -9. **二进制工件整体跳过**(2026-10-25 task-22 补):gz 串口存档 / png 等字节流没有编号语义, +9. **变更记录行豁免**(2026-10-26 加):首格是日期的表格行不参与判定——政策禁止回改历史, + 拦它等于逼人改历史或 `--no-verify`。代价:新写进变更记录行的旧形态编号照不出来。 +10. **二进制工件整体跳过**(2026-10-25 task-22 补):gz 串口存档 / png 等字节流没有编号语义, 按文本 `errors=replace` 解码后随机字节会拼出「字母-数字」形态的**伪编号**,把无关存量顶成 校验 1 的"台账外新账"(`boot_log_data` 落 f3rounds gz 时实测拦下提交——本节文字引用这类 噪声形态时同样会被扫,措辞必须只描述形态不举例)。判据 = gzip 魔数或头 2KB 含 NUL → 计入 @@ -109,7 +111,52 @@ - 存量:`scripts/id_known_backlog.json`(指纹级);**只减不增**,收紧用 `--prune-backlog`。 - 别名:`scripts/id_aliases.json`(旧字面量 → 现行字面量)——号改名走 `scripts/rename_ids.py`, 它会显式写别名条目,**绝不全局查找替换**。 -- 比例基线:`id_registry.json` 的 `decidable_ratio_baseline`(实跑当前 84.02%,基线 84.0%、容差 0.1pp)。 +- 比例基线:`id_registry.json` 的 `decidable_ratio_baseline`(实跑当前 86.01%,基线 86.0%、容差 0.1pp)。 + +## 7.5 2026-10-26 清号批次对门禁的影响 + +- **删的是引用,不是登记**:`E-xx`、`P0-x`~`P3-x`、报告六族(`D/R/F/N/U`)、复审残号(S 族 1~14 / A 族 / B 族)、 + 会话临时号、`TM-*` / `DP-*`、模拟器差距 `H-x`/`L-x` 等族**仍留在 `id_registry.json`**(`frozen` / `snapshot_only`), + 为的是历史行与 `[示例]` 不报错;但**活文档与代码注释里已不再引用它们**,新增引用会被校验 3 / 5 当场拦下。 +- **可判定比例为什么上涨**:这些族的属主登记处已删 → 字面量原本**进分母、不进分子**。删掉这类"判不了" + 的引用,分母缩小,比例自然上升。⚠ **这不等于"编号卫生变好了"**——真证据是**编号字面量绝对数下降** + (清理前 10,956 → 现在 8,230)**且**校验 2/3/6 的存量同时下降。只报比例不报绝对数,就是自欺。 +- **族不是越多越好**:`docs/编号规范.md` §4.1 现在只列「有属主登记处、且全仓真有引用」的族;原 `docs/编号规范.md` §4.2 那 15 个 + 「改名后才启用」的目标前缀已作废——**空族会骗人**:读者看到 `DR-001` 就会去找一张根本不存在的登记表。 +- 要加一族:先改 `docs/编号规范.md` §4.1,再改 `id_registry.json`,两处同一批提交(`docs/编号规范.md` §5.1 第 1 步)。 +- **本批顺带补了「变更记录行」豁免**(`CHANGELOG_ROW`:表格首格是日期、或「(规划稿)」): + `rename_ids.py` / `doc-links` / 追溯器早就是这条口径,唯独本门禁缺——于是把 `docs/编号规范.md` §5.5「历史不回改」 + 锁死的行当活账拦提交,实测 **36 处错误级 + 20 处告警全来自历史行**。豁免后这些行的编号 + **不再被判定**(新增时也判不到,这是这条豁免的代价,写进 §6 盲区);被跳过的字面量数打印在 + 扫描首行(`变更记录行 N 处`),不让它变成看不见的面分母缩水。 + +## 7.6 方案 B(同日):登记簿系编号退休为语义短语,与新增的第二根护栏 + +用户裁定「B 方案」:把**规划 / 待办类**在册编号也停发——登记簿 `N-x`/`S-x`/`T-x`、专项 `A-x`、已删报告的 UI 簇 `U1~U5`, +共 20 个号;引用侧同批改写约 418 处、**字面量净减 443**(8,946 → 8,503)——构成:在册活号 237 处 + 指向已销项号的死引用 196 处(FTP 鉴权 / 监听生命周期模块化 / 探针窗口心跳缺口 / 日志读写放大那批已销项号)+ UI 簇非位号约 45 处。 +对门禁的三件事: + +1. **族保留登记、只停发**(游标冻结不前进)。不为"看着干净"而删族:删族后历史行与别名表里的旧号会立刻 + 报「未登记前缀」,等于把清理变成制造新账(与 `docs/编号规范.md` §8.3 记的「快照族先补定义位」同一方向的教训)。 +2. **旧号进 `id_aliases.json` 标 `status: retired-to-phrase`**(不是 `void`)——区别在于条目还活着, + 只是不再用号指它。门禁两者都只当"可解析",**都不许再被活文档引用**。 + ⚠ 反过来,那批**故意不进别名表的死号**(FTP 鉴权那个旧号):进了就等于把残留引用永久洗白; + 让校验 3 继续报它们,才是"引用清干净了"的机器证据。本批实测:转换掉定义位后, + 那 12 处 `Drivers/BSP/**` 残留立刻从"静默"变成 12 条错误——这正是想要的效果。 +3. **新增第二根护栏:不可判定引用绝对数上限**(`id_registry.json` → `undecidable_total_ceiling`,只减不增, + `--update-baseline` 同时下调用它,且拒绝上调)。为什么必须加: + + > 清号批次把「本可判定的引用」换成散文,**分子分母同时缩小**——比例这类比值指标反而下跌。 + > 实测本批 86.4% vs 批次前 86.7%,而**不可判定引用绝对数一处没涨**(清号前 1,192 → 清号后仍 ≤ 该值)。 + > 也就是说比例的下跌 100% 是**稀释**,不是卫生退化;只拿比值当护栏,等于惩罚清号本身。 + + 所以同批把 `decidable_ratio_baseline` **下调**到本批实测值(工具按设计拒绝下调,需人工改 registry—— + 这是有意的摩擦:下调基线必须留下书面理由,理由就写在本节)。此后判断"编号有没有变坏"看两条: + **不可判定绝对数**(会不会新增没登记的号)+ **字面量总数**(号有没有变多),比值只作趋势参考。 + +顺带修的口径:`CHANGELOG_ROW` 原先只认「首格是日期」的变更记录行,**首格是版本号、次格是日期** +(`| v1.6 | 2026-10-25 | …`)那类表漏豁免——同一份冻结历史被两种表头拆成两种待遇,属工具缺陷, +现已补齐(要求版本号与日期两格连排,单独的版本号不算,避免普通表格被误免)。 ## 8. 改这个门禁本身 @@ -122,6 +169,8 @@ | 日期 | 变更 | |---|---| +| 2026-10-26 | **方案 B(同日续批)**:登记簿系 `N-x`/`S-x`/`T-x`/`A-x` 与 UI 簇 `U1~U5` 共 20 个号停发、约 418 处引用转语义短语(字面量净减 443:8,946 → 8,503);新增第二根护栏「不可判定引用绝对数上限」(`undecidable_total_ceiling`,只减不增),并把比例基线下调——理由与实测(比例跌、绝对数不涨)见 §7.6;`CHANGELOG_ROW` 补「版本号 + 日期」表头的变更记录行 | +| 2026-10-26 | **清号批次**(用户裁定:字母+数字编号太多、没人认得):全仓删除「属主已死 / 从未启用」的编号约 1,100 处,规范 §4.1 收成全活族 + §4.1.1 列已清除族,§4.2/§4.5/§7.1/§9 作废压缩。门禁口径不变(族仍登记,只拦新增引用);扫描面 / 字面量总数 / 台账 / 比例基线按本批实跑重打(见 §3、§7)。新增 §7.5 讲清"比例上升 ≠ 卫生变好"的读法 | | 2026-10-25 | 二进制工件跳过(§6 第 9 条):`iter_files` 按 gzip 魔数/头 2KB NUL 嗅探排除 gz/png 等,防乱码伪编号顶出校验 1 假新账(f3rounds gz 入库实测触发);602 处存量台账口径不变 | | 2026-10-25 | 补「`owner_docs` ≠ 定义集」口径(§2 末条):`snapshot_only` 族的登记处挂在规范正文时,`_def_owners` 为空 → 只恢复可判定、不参与悬空/撞号/裸形/紧邻。基线 82.6% → **84.0%**(删《项目审查与优化建议报告》+ 补登 11 条已死号别名后重打,实测 84.02%、696 文件 / 10410 字面量) | | 2026-10-23 | 首篇逐项文档(数据为当日实跑:551 文件 / 8868 字面量 / 台账 640 处 / 可判定 82.85%) | diff --git a/docs/门禁说明/layer-deps.md b/docs/门禁说明/layer-deps.md index f442c329..b04f0dc2 100644 --- a/docs/门禁说明/layer-deps.md +++ b/docs/门禁说明/layer-deps.md @@ -6,7 +6,7 @@ - 配置:`entry: python scripts/check_layer_deps.py`、`pass_filenames: false`、 `files: ^Drivers/BSP/|^scripts/check_layer_deps\.py$|^\.pre-commit-config\.yaml$`。 - 规范出处:`docs/架构设计/项目架构总览.md`(六层单向:业务 → UI/基础服务 → 中间件 → - BSP 驱动 → HAL/RTOS → 硬件)、`AGENTS.md` 分层边界;整改背景见报告 §0.4 的 S-1 行。 + BSP 驱动 → HAL/RTOS → 硬件)、`AGENTS.md` 分层边界。 ## 1. 它在查什么 diff --git a/docs/门禁说明/mem-budget.md b/docs/门禁说明/mem-budget.md index 431f469b..60ad24f0 100644 --- a/docs/门禁说明/mem-budget.md +++ b/docs/门禁说明/mem-budget.md @@ -5,7 +5,7 @@ - 配置:`entry: python scripts/ram_baseline.py --gate`、`pass_filenames: false`、 `files:` 覆盖 `App/`、`Drivers/BSP/`、`Src/`、`Inc/`、`Lib/lv_conf.h` 等会影响占用面。 -- 背景:登记簿 N-27 前半;三件套里的机械部分(阈值门禁 + 趋势台账 + 台账受管区)。 +- 背景:登记簿条目「缩堆迁 CCM 后半(暂缓)」的前半;三件套里的机械部分(阈值门禁 + 趋势台账 + 台账受管区)。 ## 1. 它在查什么 diff --git a/docs/门禁说明/registration-check.md b/docs/门禁说明/registration-check.md index bcec6d19..713552ec 100644 --- a/docs/门禁说明/registration-check.md +++ b/docs/门禁说明/registration-check.md @@ -4,7 +4,7 @@ > ——这一门专抓"写了却从不生效"的死代码,包括第四种更隐蔽的:页面描述符没接线。 - 配置:`entry: python scripts/check_registration.py --strict`、`pass_filenames: false`。 -- 背景:登记簿 N-23(出处审查报告 N-06 残余);UI 页面那类是 U4 门禁补强(2026-10-15)。 +- 背景:登记簿条目「rs485_task 白名单缺口」;UI 页面那类是「UI 字符集与像素回归门禁」补强(2026-10-15)。 ## 1. 它在查什么(四类缺口) diff --git a/docs/问题记录/CH395F_Trap_Records.md b/docs/问题记录/CH395F_Trap_Records.md index 24b8e809..6e54e6e6 100644 --- a/docs/问题记录/CH395F_Trap_Records.md +++ b/docs/问题记录/CH395F_Trap_Records.md @@ -154,7 +154,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修 2. **C 模式真因**:`FUN_PARA` bit3 `SOCKET_CLOSE=1`(§5.52 ②)下产生 DISCONNECT 后**芯片不关 Socket、不清缓冲**,长期停在 `05H/00H`;此时直接 `open_socket` 必返 `0x20` FAIL——**把 Socket 拉回 CLOSED 的动作是 `close`**; 3. **做法**:直接 `ch395f_close_socket()`;`disconnect` 纯属多余且会**制造** DISCONNECT 事件(bit3=1 下必须被消费/预清,否则污染下一条连接,TRAP-NET-12/23 实证); 4. **观测口径**:§5.22 第 1 字节只有 `00H/05H`,第 2 字节(TCP 态)**仅在 byte0=OPEN 时有意义**——关闭后轮询**第 1 字节**等 `00H` 再 open,别拿 byte1 当"卡住"判据。 -- **边界(E-02 台架)**:`close` 无 RST 的适用条件——对端配合(收到 FIN 后其内核 close):芯片走 `TIME_WAIT` ~150ms → relisten → 新连接接入;**对端内核存活但不 close**:芯片停在 **`FIN_WAIT_2`(byte0=05H/byte1=06H)长尾**——没有 RST 就拆不掉,**监听恢复受限于对端 close**。不配合场景的兜底 = 芯片自身 TIMEOUT/KeepAlive 拆僵尸会话(判死窗 30~90s,`docs/网络/网络自愈架构设计.md` §5.2 E13)——**不是**应用可加速的路径。 +- **边界(台架定案)**:`close` 无 RST 的适用条件——对端配合(收到 FIN 后其内核 close):芯片走 `TIME_WAIT` ~150ms → relisten → 新连接接入;**对端内核存活但不 close**:芯片停在 **`FIN_WAIT_2`(byte0=05H/byte1=06H)长尾**——没有 RST 就拆不掉,**监听恢复受限于对端 close**。不配合场景的兜底 = 芯片自身 TIMEOUT/KeepAlive 拆僵尸会话(判死窗 30~90s,`docs/网络/网络自愈架构设计.md` §5.2 E13)——**不是**应用可加速的路径。 - **应用侧出口 = `net_drop_conn(fd)`**:netTask 侧走驱动 `sock_reset` QUARANTINE(FIN+排渣+排空事件+等 byte0=00H,400ms 底)+ 本层收口 CLOSED + `auto_relisten` 登记重监听(≤1ms 后 `net_listener_tick` 执行);**保监听不毁**(单连接模型不得 `net_close` 监听 fd)。对端不 close 时复位机等在 `net_poll` 步进里(`ch395f_sock_reset_poll`),对端 FIN 到达即自动走完——**不搁浅**。 ## TRAP-NET-09 · 沉降窗:PHY link up 后 / close 后不能立即 open @@ -189,7 +189,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修 1. **异常释放(TIMEOUT/RST)隔离**:`handle_timeout_event` 置 per-fd 隔离标记;`net_relisten_locked` 隔离期内持续排空芯片 RX(`net_drain_chip_rx()`)+ 主动 close + 排空该 socket 中断 + 等 `CLOSED/TCP_CLOSED`,满 `NET_RELISTEN_QUARANTINE_MS`(400ms) 才 open+listen;上限 `NET_RELISTEN_SETTLE_MS`(800ms) 强制放行——**必须小于** `NET_RELISTEN_DEADLINE_MS`(2000ms):取 2000ms 会耗尽重试预算 → give up → **监听器死亡**; 2. **正常释放(FIN)沉降**:`handle_disconnect_event` 登记(仅 `recv_bytes != 0` 时——纯连接即关与只发不收免沉降保复连时序),relisten 前最短等 `NET_RELISTEN_GRACEFUL_MS`(**100ms**,实测悬崖:0/30ms 无效、100ms 稳定通过);日志区分 `relisten settle`/`relisten quarantine`; 3. **应用侧硬化**:`net_sock_t.released_abnormal` 标记(TIMEOUT 置 1/DISCONNECT 置 0,新 fd 与新监听清零——新连接不得继承上一条的释放原因);`net_pending_tick` 断连结束时按标记以 `NET_ERR_CONNRESET` 或 EOF(0) 回传;`lftpd receive_file` 错误路径 **unlink 截断文件** + STOR 回 **450**。 -- **收口尝试与边界(2026-10-14,E16)**:曾把上面修复 1 的排渣序列收口进驱动复合复位原语 `ch395f_sock_reset_*`(去重复实现与参数副本),**实测回退**:`ftp_abort_probe` RST **0/10**、FIN **0/5**(首笔复连仍被截断到 ≤2048B),并新增 `sock_reset_sync timeout (600ms)` / `close: sock_reset not done` 两条 `[ERR]`;同场 net 层自建序列 **10/10 + 5/5**(快复连 50/50、背靠背 30/30 两版都过 → 失效面精确落在"中止后首笔复连")。结论 = 原语缺**隔离窗内持续排空/关闭**与**复用让位**两条语义,补齐前 **net 层保留自建序列**,勿再直接把监听重建接进原语。前置语义与 A/B 数据 → `docs/网络/网络自愈架构设计.md` §8 延后项 1、§5.2 E16;登记 **N-30**。 +- **收口尝试与边界(2026-10-14,E16)**:曾把上面修复 1 的排渣序列收口进驱动复合复位原语 `ch395f_sock_reset_*`(去重复实现与参数副本),**实测回退**:`ftp_abort_probe` RST **0/10**、FIN **0/5**(首笔复连仍被截断到 ≤2048B),并新增 `sock_reset_sync timeout (600ms)` / `close: sock_reset not done` 两条 `[ERR]`;同场 net 层自建序列 **10/10 + 5/5**(快复连 50/50、背靠背 30/30 两版都过 → 失效面精确落在"中止后首笔复连")。结论 = 原语缺**隔离窗内持续排空/关闭**与**复用让位**两条语义,补齐前 **net 层保留自建序列**,勿再直接把监听重建接进原语。前置语义与 A/B 数据 → `docs/网络/网络自愈架构设计.md` §8 延后项 1、§5.2 E16;登记条目「relisten 沉降段原语化受阻」。 - **协议边界(不可判定,留痕)**:FTP STOR 无长度声明,"在途 FIN 关闭"与"正常完成"**逐字节不可区分** → 优雅中止仍回 226;仅 RST/掉链(芯片 TIMEOUT)可识别 → 450。FIN 边界由客户端长度校验(SIZE/RETR 往返)兜底。 - **回归基线**:`--check-abort` RST **5/5**(450)+ FIN **5/5**(226 边界不变);followup 全过;正常往返 freeze SHA-256 全 MATCH;`:7003` sink 复用 RST×4/FIN×4 全过;`:8888` 真实收包写 RTC 成功。 - **与 TC-NET-604 的关系**:本条是独立、已可信复现并已修复的真实缺陷;TC-NET-604 已结案为非缺陷(判读教训见第六章台架纪律)。现场"上传失败/文件不完整/像卡死"类报告优先用本条判据复核。 @@ -295,7 +295,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修 ## 主机侧测试工具过期 -- **`test/host/ftp_abort_probe.py` 已修**:原用匿名登录,N-19 后匿名被 530 拒 → TRAP-NET-18 两条腿**假 0/5**(round 1 `PASV failed`,且半认证会话占死 FTP 唯一会话,后续轮全变 `ConnectionRefused`)。改 `admin/admin` 后 RST 5/5、FIN 5/5。**教训:跑既有回归腿前先确认凭据仍有效,否则会把工具过期读成固件回归。** +- **`test/host/ftp_abort_probe.py` 已修**:原用匿名登录,FTP 鉴权落地后匿名被 530 拒 → TRAP-NET-18 两条腿**假 0/5**(第 1 轮 `PASV failed`,且半认证会话占死 FTP 唯一会话,后续轮全变 `ConnectionRefused`)。改 `admin/admin` 后 RST 5/5、FIN 5/5。**教训:跑既有回归腿前先确认凭据仍有效,否则会把工具过期读成固件回归。** - **同族未修**:`test/host/ftp_up_dl_test.py`、`ftp_speed_test.py`、`ftp_test.py` 仍用匿名登录 → 对当前固件必挂登录步;`docs/测试说明/FTP_Test_Guide.md` 推荐的这几个入口因此已失效(现行可用入口是 `test/host/ftp_regression.py`,默认 `admin/admin`)。 ## 文档侧待修清单 diff --git a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md index 512b410d..216898b3 100644 --- a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md +++ b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md @@ -155,7 +155,7 @@ if (ok) { 1. 探针文件名改 `/fschk.tmp`(严格 8.3:名≤8、扩展≤3、不以点开头); 2. `FR_INVALID_NAME` 移出重格式化触发集合(名字语法错误与卷健康无关)。 -**验证**(gen 45~48,@test 连续复位): +**验证**(台架日志代次 45~48,@test 连续复位): `Map resumed: 197→198→199→200 sectors used`(卷随 boot 正常增量持久)、 无 reformat 行、`[CFG] load: OK (523B)`(config.ini 跨复位存活)、VERDICT PASS。 @@ -199,7 +199,7 @@ if (ok) { ## TRAP-GD5F-06 — 轮询路径"伪完成":HAL 失败被伪装成操作完成(已修复,2026-10-03) -**发现时间**:2026-10-03(错误码传播审计 E-10/E-42/E-53/E-54/E-56/E-57 复核 + 台架套件回归) +**发现时间**:2026-10-03(错误码传播审计六条复核 + 台架套件回归) **现象/机制**: `gd5f_wait_busy` 内 3 次 HAL 调用(`HAL_SPI_Transmit` / `HAL_SPI_Receive`)返回值**全部丢弃**, @@ -216,7 +216,7 @@ if (ok) { - 公开 API 返回类型 `int` / `int32_t` 混用(码值均为小负数,无功能影响)。 **修复(2026-10-03)**: -- `gd5f_wait_busy` 检查三次 HAL 调用,失败即返 `GD5F_ERROR`(与 `GD5F_WAIT_EXPIRED` 区分;后者旧名 `GD5F_BUSY_TIMEOUT`,2026-10-23 按 S-5 R2 改名——返回码不许叫 `*_TIMEOUT`,因为同名形状里还有"毫秒数"和"自旋圈数"两种东西)。 +- `gd5f_wait_busy` 检查三次 HAL 调用,失败即返 `GD5F_ERROR`(与 `GD5F_WAIT_EXPIRED` 区分;后者旧名 `GD5F_BUSY_TIMEOUT`,2026-10-23 按 BSP 错误码契约改名——返回码不许叫 `*_TIMEOUT`,因为同名形状里还有"毫秒数"和"自旋圈数"两种东西)。 - `gd5f_bbt_scan` 返回错误码、每轮 `spare` 清零、读失败中止;`bbt_rebuild` 扫描失败 **放弃持久化**(`gd5f_bbt_load()` 回滚 RAM 后直接返回,不 `bbt_save`)。 - init 全链路检查返回值;init 后**回读 B0h 校验 ECC 使能(bit4)**,未使能即报错。 @@ -234,7 +234,7 @@ if (ok) { ## TRAP-GD5F-07 — dhara 移植层:`is_free` 只看页首 + HAL 无几何校验(已修复,2026-10-15) -**发现时间**:2026-10-15(原审查报告 N-12 / N-13 复核,登记簿原 N-21;该报告 2026-10-25 已删除) +**发现时间**:2026-10-15(原审查报告两条移植层缺陷复核,登记簿曾登记、现已销项;该报告 2026-10-25 已删除) **现象 / 机制**(两个独立缺陷,同一层): @@ -263,7 +263,7 @@ if (ok) { - **错误码选择是刻意的**:越界返回 `DHARA_E_CORRUPT_MAP`。**不能**用 `DHARA_E_BAD_BLOCK` ——`journal.c:904 recover_from` 只对该码执行"标坏块 + 跳块",会把无辜好块标掉; **不能**用 `DHARA_E_RECOVER`——`map.c:275 try_recover` 只对该码启动回收。其余错误一律 - 原样上抛,不会静默清 map(E-31 教训)。 + 原样上抛,不会静默清 map(教训:宁报错不销毁数据)。 - 无错误通道的两个函数取**保守值**:`is_free` 越界返回 0(判非空,最多少用一页)、 `is_bad` 越界返回 1(判坏,绝不读写)、`mark_bad` 越界直接返回(绝不标无辜好块)。 - `is_free` 内 `gd5f_page_read` / `gd5f_read_from_cache` 任一失败 → 返回 0 @@ -289,7 +289,7 @@ if (ok) { 1. **`is_free` 读失败返 0 的方向性代价**:比"误判空闲 → 再编程"安全(dhara 会去读 checkpage, 被 magic / CRC 门挡住),但一次**瞬态 SPI 毛刺**就可能让整次 mount 失败——旧实现反而会被 "页首恰好读到 0xFF"的快路径掩盖过去。这是**刻意选择**:宁可挂载失败,不可误判空闲 - (E-31 纪律:绝不拿销毁数据换可用性)。排障时记住"挂载失败 + 有 SPI 错误"先看这里。 + (纪律:绝不拿销毁数据换可用性)。排障时记住"挂载失败 + 有 SPI 错误"先看这里。 2. **空白卷首启成本**:dhara resume 扫到的每个检查点页都是全 0xFF → 每页要 32 次列读 (旧实现 1 次)。量级仍小(O(log N) × ppc),但**首次挂载(新格式化卷)会明显变慢**。 台架若观察到该现象,根因在此,**不是回归**。 @@ -300,4 +300,4 @@ if (ok) { **单一真源**(`disk_initialize` 与守卫同取此宏,杜绝两处各写一遍再各自漂移), 再加两条负数组编译期守卫 `FTL_PAGE_SIZE == (1 << FTL_LOG2_PAGE_SIZE)` / `GD5F_PAGES_PER_BLOCK == (1 << FTL_LOG2_PPB)`——改 GD5F 常量而不同步几何即编译错。 - AC5 不支持 C11 `_Static_assert`,负数组是本项目惯用法(同本目录 `gd5f2gq5ue.h` 的 N-26 守卫)。 + AC5 不支持 C11 `_Static_assert`,负数组是本项目惯用法(同本目录 `gd5f2gq5ue.h` 的 DMA 可达性三层防线守卫)。 diff --git a/docs/问题记录/采集与数据处理短板评测.md b/docs/问题记录/采集与数据处理短板评测.md index d1c644cb..c9c24de4 100644 --- a/docs/问题记录/采集与数据处理短板评测.md +++ b/docs/问题记录/采集与数据处理短板评测.md @@ -3,7 +3,7 @@ > **定位**:以参考项目 CLS100(`Scatter_P_HV03.01_SV01.01`)的"采样 → 预处理 → 相量 → 遥测 → 录波 → 上送"全链路为对照,评测本项目在**电力采集与数据处理**上的完成度,指出下一步该补的短板与顺序。 > **不是待办登记簿**:排序权威仍是 `docs/任务规划/README.md` §2;本文只出评测结论与建议,条目落地时按语义标识登记进 `docs/任务规划/登记簿.md`。 > **对照口径**:项目内以 `docs/算法/继电保护功能复现规划.md` §3 的 L1~L5 清单为准;参考侧以参考项目 `项目分析文档/04-算法与采样专题/` 三篇(《ADC采集至数据使用全流程详解》《项目详细分析-交流采样与有效值计算》《傅氏算法详解》)与源码为准。 -> **硬件前提**:结论只针对 **V0.1 现板**;受 N-18(8 路电气量分属两片转换器)制约的项单独标注,V0.2 改版后解禁。 +> **硬件前提**:结论只针对 **V0.1 现板**;受「双转换器无共同采样触发」(8 路电气量分属两片转换器)制约的项单独标注,V0.2 改版后解禁。 --- @@ -206,7 +206,7 @@ ### 5.3 派生量(L2-5) - **现状**:只有 3 个基波实例 + 2 个谐波实例,无线电压、无负序/正序/零序合成、无差分、无直流分量。 -- **为什么现在做**:V0.1 受 N-18 限制,跨转换器合成**不成立**(`UA` 与 `UB` 无同一时刻性);但**代码与单测可以先就绪**,V0.2 只改映射表就能使能。参考侧 `CalfLineVec`/`CalfNegVec`/`CalfPosVec`/`CalfSeqVec`/`CalfZeroVec`/`CalfAnaDC`/`CalfAnaDif` 是七个小模块,逻辑清晰、天然可主机单测。 +- **为什么现在做**:V0.1 受双转换器无共同采样触发限制,跨转换器合成**不成立**(`UA` 与 `UB` 无同一时刻性);但**代码与单测可以先就绪**,V0.2 只改映射表就能使能。参考侧 `CalfLineVec`/`CalfNegVec`/`CalfPosVec`/`CalfSeqVec`/`CalfZeroVec`/`CalfAnaDC`/`CalfAnaDif` 是七个小模块,逻辑清晰、天然可主机单测。 - **建议**:按"吃实例下标、出合成相量"的接口建 `derived.c/h`,V0.1 标注"不使能",单测用合成正弦验证相位关系。 - **规模**:M。 @@ -220,7 +220,7 @@ ### 5.5 功率与电度 - **现状**:零。点表 `4019~401c`(P/Q/S/PF)与电度上送缺失。 -- **关键判断**:V0.1 上**算不出来**(N-18 禁止跨转换器功率运算),产品口径是"取子板计量模块结果"(见 `docs/硬件平台/ADC通道映射说明.md` §1)。所以这不是"补算法",而是**先定数据来源**: +- **关键判断**:V0.1 上**算不出来**(双转换器无共同采样触发 → 禁止跨转换器功率运算),产品口径是"取子板计量模块结果"(见 `docs/硬件平台/ADC通道映射说明.md` §1)。所以这不是"补算法",而是**先定数据来源**: - 若子板计量有数字接口(RS-485/SPI)→ 这是 B2(RS-485 采集,已暂缓)的一部分; - 若没有 → 等 V0.2 三相全上 TPAFE 后自算。 - **建议**:**先拍板来源,再写代码**。现在写功率算法在 V0.1 上无法验证,属于无效工作。 @@ -318,7 +318,7 @@ | 同期合闸、小电流接地选线 | 依赖三相/零序电流与出口,V0.1 均不具备;参考侧同期组件本身也被注释未启用 | | IEC 61850 / GOOSE | 与单核 FreeRTOS 平台、当前产品定义都不匹配 | | 双核拆分 / DSP 移植 | 平台不匹配;单核 168 MHz + FPU 目前余量够 | -| 在 V0.1 上写功率算法 | N-18 使相位关系不成立,写出来无法验证(见 5.5) | +| 在 V0.1 上写功率算法 | 双转换器无共同采样触发使相位关系不成立,写出来无法验证(见 5.5) | | 先改 4 kHz 节拍再想录波 | 节拍一改,保护窗、CPU、RAM 全变;应先定"录波要不要 80 点"再动 | --- diff --git a/scripts/README.md b/scripts/README.md index 642799b3..c51cd4a0 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -11,14 +11,14 @@ |---|---|---| | `check_coding_standard.py` | `C coding standard` | C 代码规范机械检查(命名前缀/常量右置/头文件保护宏等) | | `check_mojibake.py` | `Forbid U+FFFD mojibake` | 扫描 `U+FFFD` 乱码(`EF BF BD` 字节) | -| `check_doc_links.py` | `doc-links` | **文档链接存在性** + 命名规范(D-3/D-7)+ **`§N.M` 章节引用**(口径见 `docs/文档规范.md` §3.5)。钩子已带 `--sections-strict`(自引用失效也拦);`--section-todo` 列出机器判不了目标的 `§`(带改写建议)。每次跑打印「可判定比例」行,防覆盖率回落 | +| `check_doc_links.py` | `doc-links` | **文档链接存在性** + 命名规范 + **`§N.M` 章节引用**(口径见 `docs/文档规范.md` §3.5)。钩子已带 `--sections-strict`(自引用失效也拦);`--section-todo` 列出机器判不了目标的 `§`(带改写建议)。每次跑打印「可判定比例」行,防覆盖率回落 | | `section_aliases.json` | (被 `check_doc_links.py` 读) | 文档**昵称别名表**:「内存方案」→ `docs/内存管理/RAM使用分析与优化方案.md`。只登记无歧义昵称;登记一次即把该昵称的引用纳入硬门禁 | | `check_ids.py` | `id-check` | **编号门禁**:`docs/编号规范.md` §8 的八项校验(未登记前缀 / 形态 / 悬空 / 重复定义 / 越游标 / 未紧邻文档名 / 孤儿 / 可判定比例)。**已关闸为增量拦截**:钩子带 `--strict`,只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题(存量按指纹豁免、台账只减不增);可判定比例跌破基线任何模式都失败。详表(含限制与盲区)→ `docs/门禁说明/id-check.md` | | `id_registry.json` | (被 `check_ids.py` 读) | 前缀族**注册表**:属主登记处 / 形态正则 / 冻结 / 游标,逐项来自 `docs/编号规范.md` §4;`decidable_ratio_baseline` = 校验 8 的基线(只许涨)。与 `section_aliases.json` 同一套"别名表 + 可判定比例"范式 | -| `id_aliases.json` | (被 `check_ids.py` 读) | 编号**别名/作废表**:旧字面量 → 现行字面量或 `status: void`(如 `N-32` 销项后仍被引用、`T-4` 随销项永久作废、模块码 `PHY→NET`) | +| `id_aliases.json` | (被 `check_ids.py` 读) | 编号**别名/作废表**:旧字面量 → 现行字面量或 `status: void`(如已销项的旧号仍被引用 → `status: void`、模块码 `PHY→NET`) | | `check_test_traceability.py` | (被 `id-check` 顺带调用) | **TC 用例双向追溯**:文档 ↔ 代码两侧的 TC 编号集合比对;2026-10-20 起范围扩到 `test/host/` 与 7 篇「单元测试说明.md」(`--board-only` 回退旧板级口径)。此前该脚本**未挂任何钩子** | -| `gen_task_table.py` | `task-table` | FreeRTOS 任务表以 `Src/freertos.c` 为**唯一事实源**生成/校验(D-2/D-7) | -| `check_mem_numbers.py` | `mem-numbers` | 内存/Flash 数字以 `.map` 为唯一事实源校验(D-7 体积档) | +| `gen_task_table.py` | `task-table` | FreeRTOS 任务表以 `Src/freertos.c` 为**唯一事实源**生成/校验 | +| `check_mem_numbers.py` | `mem-numbers` | 内存/Flash 数字以 `.map` 为唯一事实源校验(体积档) | | `check_test_suite.py` | `test-suite-switch` | 测试套件开关=产品构建(防测试固件静默提交) | ### 报告生成(非门禁) @@ -37,12 +37,12 @@ > `check_doc_links.py` 除链接存在性外,还校验 **`§N.M` 章节引用**是否指向目标文档真实存在的编号标题(口径见 `docs/文档规范.md` §3.5)。 -### CI 与本地等价(2026-10-01 T-6) +### CI 与本地等价(2026-10-01) | 脚本 / 文件 | 作用 | |---|---| | `ci_local.py` | **本地跑与 CI 等价的门禁**(L0 pre-commit + L0b 提交信息 + **L0c 体积门禁** + L0.5 主机侧单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers` / `mem-budget`(⚠ 那是 SKIP 不是通过,缺口由 L0c 兜住);退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/测试开发标准流程.md` §9 | -| `check_size_gate.py` | **体积门禁的强制路径(A-5 残余)**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的 L0c 步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** | +| `check_size_gate.py` | **体积门禁的强制路径**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的 L0c 步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** | | `size_attestation.json` | (被 `check_size_gate.py` 读写)体积同码凭证:受管源码指纹(`git ls-files` 范围内逐文件 sha256 聚合)+ 测量数字与占比 + `.map` 出处。改固件源码后不重测 → 指纹对不上 → L0c 与 CI 同时拦红 | | `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 16 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;**不在 pre-commit 里**,入口是 `ci_local.py --coverage`(口径见 `docs/测试说明/主机单测覆盖率口径.md`) | | `ci_gitlint_range.ps1` | CI 侧**提交信息门禁**:逐条校验本次 push/PR 引入的提交(`--all-files` 跑不到 `commit-msg` stage,故必须单独守) | @@ -63,7 +63,7 @@ & '.venv\Scripts\python.exe' scripts/ci_local.py & '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0 & '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5 -& '.venv\Scripts\python.exe' scripts/ci_local.py --size-only # 只跑 L0c 体积门禁(A-5 强制路径) +& '.venv\Scripts\python.exe' scripts/ci_local.py --size-only # 只跑 L0c 体积门禁(强制路径) & '.venv\Scripts\python.exe' scripts/check_size_gate.py --show # 体积同码凭证现状(0.3 s,无需 Keil) & '.venv\Scripts\python.exe' scripts/check_size_gate.py --stamp # 用新鲜 .map 重新盖戳(--update 已自动带做) diff --git a/scripts/check_bsp_err.py b/scripts/check_bsp_err.py index b42e3ac6..515bc682 100644 --- a/scripts/check_bsp_err.py +++ b/scripts/check_bsp_err.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""BSP 错误码与超时命名约定检查(报告 S-5 的机械检查那一半)。 +"""BSP 错误码与超时命名约定检查(机械检查那一半)。 为什么要有这个脚本:约定写在 docs 里不会自己生效。同一层的驱动今天并存六套返回约定, 而"能不能重试"只写在个别注释里;叫 *_TIMEOUT 的东西里既有真毫秒(单位靠不统一的 _MS 后缀表达)、 diff --git a/scripts/check_cubemx_survival.py b/scripts/check_cubemx_survival.py index fac30cd1..47e9c785 100644 --- a/scripts/check_cubemx_survival.py +++ b/scripts/check_cubemx_survival.py @@ -142,7 +142,7 @@ CHECKS = [ hint="崩溃现场报告实现(打印 PC/LR/xPSR/CFSR/HFSR/BFAR/MMFAR)" "须在 USER CODE BEGIN 0 区内(生成区内的标准保护位)。", ), - # N-20(2026-10-12):Error_Handler 统一降级。函数体本身在 CubeMX 的 + # Error_Handler 统一降级(2026-10-12;详述见看门狗设计说明.md §4.4)。函数体本身在 CubeMX 的 # `USER CODE BEGIN/END Error_Handler_Debug` 区内(L2 受保护),重生成不丢; # 本项是 L3 兜底:被打回模板默认(__disable_irq+while(1) 静默挂死)**不报编译错**, # 只在真出外设初始化故障时暴露——整机只剩 IWDG 兜底且无自检日志。 @@ -592,7 +592,7 @@ def main(): print(" → 若是本次有意改动,确认无误后跑 `--write` 重建基线;") if args.write: - with open(BASELINE, "w", encoding="utf-8") as f: + with open(BASELINE, "w", encoding="utf-8", newline="\n") as f: json.dump( {"snapshot": snap, "_note": "CubeMX 存活审计基线;改动用 --write 重建"}, f, diff --git a/scripts/check_doc_links.py b/scripts/check_doc_links.py index 6ab01ee4..1abc1e7a 100644 --- a/scripts/check_doc_links.py +++ b/scripts/check_doc_links.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -文档路径一致性 + 命名规范检查(D-3/D-6/D-7)。 +文档路径一致性 + 命名规范检查。 由 pre-commit 调用:python scripts/check_doc_links.py ,发现问题退出码 1。 检查三类: diff --git a/scripts/check_font_charset.py b/scripts/check_font_charset.py index 81667819..c9d6dab6 100644 --- a/scripts/check_font_charset.py +++ b/scripts/check_font_charset.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""check_font_charset.py - UI 字符集差集门禁(防"缺字显示白框",报告 §6.5 U4 残余)。 +"""check_font_charset.py - UI 字符集差集门禁(防"缺字显示白框",原报告 §6.5 的 UI 字符集与像素回归门禁残余项)。 背景: 本工程唯一中文字体 `Drivers/BSP/UI/ui_font_cn_12.c` 是**子集**字体:只包含 @@ -9,7 +9,7 @@ `make_font.py` 的 `verify_coverage()` 只在**生成期**校验"txt ⊆ 字体 cmap"; 若开发者改了 UI 字符串却忘了重跑 extract_cn / make_font,提交期**无任何门禁** 能拦住——缺字上机渲染为空白(白框),无日志无报错,只能靠肉眼截图发现。 - 本钩子把这条约定机械化(U4 三钩子之二,golden 已建、registration-check 见 §2.18)。 + 本钩子把这条约定机械化(UI 字符集与像素回归门禁三钩子之二,golden 已建、registration-check 见 §2.18)。 判定口径(两段链条,缺一环即白框): A. 必需字符集(源码在用 ∪ KEEP_EXTRA 保留字) vs ui_cn_chars.txt @@ -51,7 +51,7 @@ 1 = 失败(存在白框方向的缺口:必需字不在 txt(含 KEEP_EXTRA 保留字被剔),或 txt 字不在字体;--strict 时未知冗余也算) 2 = 环境错误(字符集文件 / 字体文件 / 生成器脚本缺失) -创建日期:2026-10-15(U4 残余,报告 §6.5 / §7 交付物清单"字符集差集检查"行) +创建日期:2026-10-15(UI 字符集与像素回归门禁残余项,报告 §6.5 / §7 交付物清单"字符集差集检查"行) """ import argparse diff --git a/scripts/check_ids.py b/scripts/check_ids.py index 8ac5e391..41794ffa 100644 --- a/scripts/check_ids.py +++ b/scripts/check_ids.py @@ -67,6 +67,18 @@ CAND_MOD = re.compile(r"(? ceil_old: + print("[ERR] 拒绝上调绝对数上限:本次不可判定 %d > 上限 %d" + % (und_now, ceil_old), file=sys.stderr) + return 1 + reg["undecidable_total_ceiling"] = und_now REGISTRY_FILE.write_text(json.dumps(reg, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n") print("[OK] 基线已写进 scripts/id_registry.json:%.1f%%(本次实测 %.2f%%)" @@ -803,6 +837,14 @@ def main(): print("\n[FAIL] 校验 8:可判定比例跌破基线 %.2f%% < %.2f%%(只许涨不许跌,容差 %.1fpp)" % (ratio, base, RATIO_TOL)) return 1 + # 绝对数护栏:与比值护栏同时生效,任一越线即失败(§8.1)。 + und_now = ck.stats["refs_total"] - ck.stats["decidable"] + ceil_now = reg.get("undecidable_total_ceiling") + if ceil_now is not None and und_now > ceil_now and not args.update_baseline: + print("\n[FAIL] 校验 8:不可判定引用绝对数 %d > 上限 %d(只许减不许增,§8.1)\n" + " → 要么把新增的号登记进属主文档 / 别名表,要么别加这个号" + % (und_now, ceil_now)) + return 1 if err_total: if args.strict: led = load_backlog() diff --git a/scripts/check_layer_deps.py b/scripts/check_layer_deps.py index a3d8eab9..70661d56 100644 --- a/scripts/check_layer_deps.py +++ b/scripts/check_layer_deps.py @@ -44,7 +44,7 @@ ALLOW = { } # 存量基线(2026-10-22 清点 6 条,只减不增)。每条 = (引用方, 被引用的 App 头)。 -# 修法是"注入/传参",不是把上层服务搬进 BSP——详见报告 §0.4 的 S-1 行。 +# 修法是"注入/传参",不是把上层服务搬进 BSP。 # 2026-10-25 道B(task/UI-rec-reader)还清 UI 记录 3 条:ui_w_records 改注入式 # ui_rec_reader_t(契约 UI_数据供给设计说明.md §7.2),runlog/selftest/records.h # 不再 include sys_log.h,绑定在 App/task/ui_task.c 与 simulator/main.c。 diff --git a/scripts/check_lv_conf_sync.py b/scripts/check_lv_conf_sync.py index bd2cb19e..95ccbf02 100644 --- a/scripts/check_lv_conf_sync.py +++ b/scripts/check_lv_conf_sync.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""两份 lv_conf.h 特性子集一致性门禁(2026-10-11 加,随 F-1/F-2/F-3 落地)。 +"""两份 lv_conf.h 特性子集一致性门禁(2026-10-11 加)。 背景:`Lib/lv_conf.h`(MCU)与 `simulator/lv_conf.h`(PC)是**两份独立文件**, 此前长期漂移——模拟器早已 I1-only,MCU 端却让 11 个 `LV_DRAW_SW_SUPPORT_*`、 diff --git a/scripts/check_mem_numbers.py b/scripts/check_mem_numbers.py index 154bc6a7..34291e8a 100644 --- a/scripts/check_mem_numbers.py +++ b/scripts/check_mem_numbers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -内存/Flash 数字一致性门禁(D-7 第三档:体积)。 +内存/Flash 数字一致性门禁(体积档)。 问题 ---- @@ -324,7 +324,7 @@ def scan_prose(text: str, stale, errors, path: pathlib.Path): def main(argv): - ap = argparse.ArgumentParser(description="内存/Flash 数字一致性门禁(D-7 体积档)") + ap = argparse.ArgumentParser(description="内存/Flash 数字一致性门禁(体积档)") g = ap.add_mutually_exclusive_group() g.add_argument("--check", action="store_true", help="检查模式(默认)") g.add_argument("--write", action="store_true", help="同步受管快照区") diff --git a/scripts/check_registration.py b/scripts/check_registration.py index 886a3f2a..32206fd5 100644 --- a/scripts/check_registration.py +++ b/scripts/check_registration.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """check_registration.py - 双端注册反向校验门禁(纯标准库)。 -背景(登记簿 N-23,出处审查报告 N-06 残余): +背景(登记簿条目「rs485_task 白名单缺口」): 本工程 Keil 工程与 PC 模拟器都用**显式文件列表**(无通配扫描)。新 `.c` 必须手动注册两处才会被编译: ① `MDK-ARM/STM32F407-Demo.uvprojx` 的 `` 条目(MCU 固件构建) @@ -16,7 +16,7 @@ ② 存在但未进 CMakeLists -> 模拟器不编译(**仅当该文件本应进模拟器时**才是缺口; MCU-only 文件不进模拟器是**正确**的,见下方豁免) ③ 注册了但文件不存在(反向)-> 幽灵注册(改名/删除后残留的注册条目) - 另对 UI 页面模块(U4 门禁补强,2026-10-15,报告 §6.5): + 另对 UI 页面模块(UI 字符集与像素回归门禁补强,2026-10-15,报告 §6.5): ④ `Drivers/BSP/UI/ui_page_*.c` 定义了 `ui_page__desc`,但该符号在 UI 目录内 除自身 .c/.h 外无任何引用 -> 页面未接入 `ui_content_page.c` 的 `s_defs[]` 描述符表:两端**编译 @@ -29,12 +29,12 @@ 模拟器只建模 **UI + 网络配置共享核心**;其余模块(MCU 驱动 / 固件任务 / 纯算法 / CubeMX 生成层)**不进模拟器是设计事实**,模拟器另有 `_sim` 替身或根本无对应。 豁免用**显式清单 + 每行理由注释**(命名模式 `*_mcu.c` + 个别点名), - **不做整目录一刀切**(N-23 教训正是"目录级盲区")。唯一例外是 `Src/`: + **不做整目录一刀切**(这一项的教训正是"目录级盲区")。唯一例外是 `Src/`: CubeMX 生成的外设初始化/中断/main,按构造即 MCU 专属、不可能有模拟器替身, 故整目录豁免(任务口径明确认可"Src/ 全部")。 已知缺口过渡(存量不拦、只对新缺口 FAIL): - `KNOWN_GAPS` 登记存量缺口(N-23 存量,Lead 处置中):命中 -> 打印 `[WARN]` 不拦; + `KNOWN_GAPS` 登记存量缺口(白名单存量,Lead 处置中):命中 -> 打印 `[WARN]` 不拦; 新出现的缺口 -> 打印 `[GAP]` 并 FAIL。`--strict` 关闭白名单(存量也 FAIL),供审计。 退出码: @@ -46,7 +46,7 @@ & '.venv\\Scripts\\python.exe' scripts/check_registration.py & '.venv\\Scripts\\python.exe' scripts/check_registration.py --strict -设计依据:docs/任务规划/登记簿.md N-23;.dsh/skills/new-source-file +设计依据:docs/任务规划/登记簿.md 条目「rs485_task 白名单缺口」;.dsh/skills/new-source-file 创建日期:2026-10-12 """ @@ -138,10 +138,10 @@ SIM_EXEMPT = [ ("Drivers/BSP/UI/ui_port.c", "MCU UI 端口层(显示/触摸硬件),模拟器另有 sim/sim_port.c"), ] -# --- 已知缺口白名单(存量,N-23,Lead 已处置)--- +# --- 已知缺口白名单(存量,Lead 已处置)--- # 命中 -> WARN 不拦;新缺口 -> GAP FAIL。仅针对"存在但未注册"方向。 KNOWN_GAPS = { - "App/task/rs485_task.c": "E-59 门禁刻意未接线(B2 消费者就绪时随任务立项注册)", + "App/task/rs485_task.c": "RS-485 接线门禁(驱动契约未修完,随 B2 暂缓)", } @@ -250,7 +250,7 @@ def parse_cmake(): return sources -# --- UI 页面模块 → 描述符表接线(④,U4 门禁补强)--- +# --- UI 页面模块 → 描述符表接线(④,UI 字符集与像素回归门禁补强)--- UI_DIR_REL = "Drivers/BSP/UI" @@ -261,7 +261,7 @@ def check_ui_page_wiring(): (`ui_content_page.h` 枚举 + `ui_content_page.c` 的 `s_defs[]` 表)。漏了后者的页面 两端照常编译链接(desc 无引用 → 链接器剥离),①②③ 全绿但菜单打不开这一页。 判定:页面模块 = `ui_page_*.c` 中定义 `_desc =` 的文件(不定义 desc 的 - `ui_page_*.c` 不是页面模块——未来 U3 生成的 `ui_page_registry.c` 承载表引用, + `ui_page_*.c` 不是页面模块——未来 UI 页面原型化生成的 `ui_page_registry.c` 承载表引用, 本规则依旧成立且不会把它误判为页面);引用面 = `Drivers/BSP/UI/` 下全部 .c/.h, 排除该页自身 .c/.h(自身头文件里的 extern 声明不算接线)。 """ @@ -363,7 +363,7 @@ def main(): if not os.path.isfile(os.path.join(REPO_ROOT, f.replace("/", os.sep))): gaps.append("[GAP] %s | 幽灵注册(CMakeLists)" % f) - # ④ UI 页面模块未接入描述符表(框架注册,U4;①②③ 照不出的盲区) + # ④ UI 页面模块未接入描述符表(框架注册,UI 字符集与像素回归门禁;①②③ 照不出的盲区) ui_gaps, ui_pages = check_ui_page_wiring() gaps.extend(ui_gaps) @@ -380,7 +380,7 @@ def main(): print("结果: FAIL(%d 个新缺口/幽灵注册/页面未接线,%d 个存量 WARN)" % (len(gaps), len(warns))) return 1 if warns: - print("结果: PASS(无新缺口;%d 个存量缺口已白名单 WARN,N-23 Lead 处置中)" % len(warns)) + print("结果: PASS(无新缺口;%d 个存量缺口已白名单 WARN,白名单缺口由 Lead 处置中)" % len(warns)) return 0 print("结果: PASS(双端注册一致)") return 0 diff --git a/scripts/check_size_gate.py b/scripts/check_size_gate.py index 8b1b3ecf..7c96123e 100644 --- a/scripts/check_size_gate.py +++ b/scripts/check_size_gate.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""体积门禁强制路径(A-5 残余,2026-10-25)。 +"""体积门禁强制路径(2026-10-25)。 要消除的盲区 ------------ @@ -61,7 +61,7 @@ python scripts/check_size_gate.py --ci [--warn-only] python scripts/check_size_gate.py --show -设计依据:docs/任务规划/测试与架构专项.md(A-5 残余)、docs/CI/README.md。 +设计依据:docs/任务规划/测试与架构专项.md、docs/CI/README.md。 创建日期:2026-10-25 """ @@ -325,7 +325,7 @@ def _report(args, ci_mode, lines): """失败输出:CI 事件加 GHA 标注;--warn-only 降级为 0(非主干不拦)。""" if ci_mode: tag = "::warning::" if args.warn_only else "::error::" - print(tag + " 体积门禁未通过(A-5 强制路径)", file=sys.stderr) + print(tag + " 体积门禁未通过(强制路径)", file=sys.stderr) for ln in lines: print(ln, file=sys.stderr) if args.warn_only: @@ -439,7 +439,7 @@ def main(argv=None): except Exception: # noqa: BLE001 pass ap = argparse.ArgumentParser( - description="体积门禁强制路径(A-5 残余:同码凭证 + CI 红线复核)") + description="体积门禁强制路径(同码凭证 + CI 红线复核)") g = ap.add_mutually_exclusive_group() g.add_argument("--stamp", action="store_true", help="用新鲜 .map 盖同码戳(@build 后)") g.add_argument("--local", action="store_true", help="本地/ci_local 自查:工作树须与测量同码") diff --git a/scripts/check_test_coverage.py b/scripts/check_test_coverage.py index 3a379bc3..c221cd7a 100644 --- a/scripts/check_test_coverage.py +++ b/scripts/check_test_coverage.py @@ -17,7 +17,7 @@ r""" 为什么要它(2026-10-23,W5 验证常态): 原评审报告 §4.8 记的两处空白之一(该报告 2026-10-25 已删除):无覆盖率口径—— L0.5 只回答“跑过哪些 TC”,回答不了“生产源码里哪些行根本没进过”。用例数与 TC 追溯率 - 会随时间上升,但覆盖率照的是另一类真相:**新增分支一行没测**(N-12/N-13 那类“读代码 + 会随时间上升,但覆盖率照的是另一类真相:**新增分支一行没测**(那类“读代码 才发现”的缺陷,覆盖率是最便宜的机器照法)。 工具链现实(2026-10-23 本机实测,换机必读): diff --git a/scripts/check_test_suite.py b/scripts/check_test_suite.py index 51078094..efaf1b4a 100644 --- a/scripts/check_test_suite.py +++ b/scripts/check_test_suite.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """check_test_suite.py - 测试套件开关门禁:防止测试固件被静默提交(纯标准库)。 -背景(2026-10-01 测试架构评审 T-1,P0): +背景(2026-10-01 测试架构评审,P0): `test/board/test_config.h` 的 `TEST_SUITE_*` 宏决定固件里编入哪个测试套件; **全部注释 = 产品构建**。但该开关此前**没有任何门禁检查**——实测已提交的 HEAD 里 `TEST_SUITE_CH395F` 就是开启状态,`.map` 含 34 处 test 代码, @@ -37,7 +37,7 @@ & '.venv\\Scripts\\python.exe' scripts/check_test_suite.py --allow-test-build & '.venv\\Scripts\\python.exe' scripts/check_test_suite.py --file <路径> -设计依据:docs/任务规划/登记簿.md T-1;docs/测试说明/测试开发标准流程.md §3.1 +设计依据:docs/任务规划/登记簿.md;docs/测试说明/测试开发标准流程.md §3.1 创建日期:2026-10-01 """ diff --git a/scripts/check_test_traceability.py b/scripts/check_test_traceability.py index 0078caaa..1e4e17a8 100644 --- a/scripts/check_test_traceability.py +++ b/scripts/check_test_traceability.py @@ -26,7 +26,7 @@ ① 文档声明了、代码没有 → DOC-ONLY; ② 代码有、母号不在文档、**且号旁边没写人话意图** → CODE-ONLY(真未登记); ③ 代码有、靠"旁边有意图"放过 → 单独打印为 IN-CODE,**弱于文档**,不算已登记。 - 小写子项后缀(`TC-XXX-003b`)按母号算(`subitem_ok`),与编号门禁同口径。 + 小写子项后缀(`TC-<模块码>-003b` 这类小写尾号)按母号算(`subitem_ok`),与编号门禁同口径。 **批 6 再分两档**:硬件层人工用例(标题/章节含「(硬件层)」)记为 `BENCH`、文档已标未实现/待补/暂缓/已取消的记为 `NOTIMPL`,两者**单列示数**不进差异——但也不是「已覆盖」,只是「不该在这里期望代码」。 **2026-10-21 第二次口径修正(批 5)**:变更记录行与 `[示例]`/`[计划]` 行**不参与比对** (冻结历史按编号规范 §8.1 不回改,旧号靠 `id_aliases.json` 解析;这条豁免另三支脚本早有,本脚本是最后缺它的一个,`DOC-ONLY` 因此 38 → 29、交集不变);被豁免的号存进 `frozen_ids`,`--verbose` 全量列出——**豁免不等于已对应**,别拿它当收敛。 @@ -48,6 +48,13 @@ import argparse import re import sys + +# 控制台编码兜底:pre-commit / CI 之外的裸跑环境代码页常是 936(GBK), +# print 中文或 ⚠ 会抛 UnicodeEncodeError 让脚本带 traceback 以非零码死掉 +# (同 check_ids.py / check_doc_links.py 的做法;2026-10-26 补齐,此前裸跑在第 245 行崩)。 +for _stream in (sys.stdout, sys.stderr): + if hasattr(_stream, "reconfigure"): + _stream.reconfigure(encoding="utf-8", errors="replace") from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent @@ -58,7 +65,7 @@ DOC_DIR = REPO_ROOT / "docs" / "测试说明" # 2026-10-20 收编进 id-check 门禁时补的三条(docs/编号规范.md §8「顺带收编」): # ① 规范 §4.1 的合规形态 `TC-[A-Z]{1,4}-\d{3,4}[a-z]?`(带小写后缀,TC-NET-1502b); # ② 现存的大写后缀形态(TC-AI-01A [示例],§7.1 待统一小写)也要能抓出来,否则整条漏扫; -# ③ 无模块码形态(iec104 的 TC-014/TC-104,§7.2 列为孤儿)纳入比对而不是静默忽略。 +# ③ 无模块码形态(iec104 文档里缺模块码的那批用例号,§7.2 列为孤儿)纳入比对而不是静默忽略。 # ⚠ 模块码上界必须跟着 §6 模块码表走:批 2 单词化后码长可达 9 位(ADCHEALTH), # 这里曾停在 {0,4},导致代码侧集合从 556 悄悄缩到 217——**同一批改名要复核所有 # 识别面,不止 check_ids.py**(2026-10-21 实测踩到)。 diff --git a/scripts/check_ui_golden.py b/scripts/check_ui_golden.py index 5843a0c8..f865023f 100644 --- a/scripts/check_ui_golden.py +++ b/scripts/check_ui_golden.py @@ -10,7 +10,7 @@ 作者:王建锋 创建日期:2026-10-20 修改记录: - 2026-10-20 王建锋 创建(W6 U4 收尾:golden 入提交门禁) + 2026-10-20 王建锋 创建(W6 UI 字符集与像素回归门禁收尾:golden 入提交门禁) """ import os diff --git a/scripts/ci_local.py b/scripts/ci_local.py index 71623d91..08b81b56 100644 --- a/scripts/ci_local.py +++ b/scripts/ci_local.py @@ -2,14 +2,14 @@ # -*- coding: utf-8 -*- """ci_local.py - 本地一键跑"与 CI 等价"的门禁(纯标准库)。 -为什么需要它(2026-10-01 T-6): +为什么需要它(2026-10-01): CI(`.gitea/workflows/ci.yml`)只在 push / PR 时跑,且需要 Gitea Actions + Windows runner 就绪。**提交前想自查、或 runner 还没配好时**,用本脚本在本地 跑同一套判定,命令与退出码与 CI 一一对应。 覆盖范围(与 CI 完全一致): L0 静态门禁 = `pre-commit run --all-files` + 提交信息门禁(gitlint) - L0c 体积门禁(A-5 残余的强制路径,2026-10-25)= `scripts/check_size_gate.py --local` + L0c 体积门禁(强制路径,2026-10-25)= `scripts/check_size_gate.py --local` **fail-closed**:没有体积同码凭证、或代码与测量不同码即红——不再"没跑=通过"。 CI 侧的「体积门禁」步跑的是同一条实现(那条无需 Keil,见 .gitea/workflows/ci.yml)。 L0.5 主机侧纯逻辑单测 = `test/host/**/run_unit_test.py` @@ -36,7 +36,7 @@ 1 = 有门禁/单测失败 2 = 环境错误(缺 pre-commit / python 模块等) -汇总行(S3,2026-10-02,对齐 CI 的 ::notice::): +汇总行(2026-10-02 起,对齐 CI 的 ::notice::): [SUMMARY] L0: pre-commit hooks -- passed=N failed=M skipped=K [SUMMARY] gitlint: N commit message(s) checked, M failed [SUMMARY] L0.5: K runner(s), A/B assertions passed, fail=F @@ -169,7 +169,7 @@ def _run(cmd, env=None): def _run_capture(cmd, env=None): """跑子进程,输出**既直通终端又捕获**(对齐 CI 的 Tee-Object)。 - 返回 (退出码, 全部输出文本)。用于 L0.5 解析各模块汇总行(S3 汇总)。 + 返回 (退出码, 全部输出文本)。用于 L0.5 解析各模块汇总行。 """ print("$ " + " ".join(str(c) for c in cmd)) cp = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env, @@ -291,11 +291,11 @@ def run_gitlint(count): def run_size_gate(): - """L0c:体积门禁(A-5 残余的强制路径)。返回退出码(0/1/2)。 + """L0c:体积门禁(强制路径)。返回退出码(0/1/2)。 为什么必须单列一步(2026-10-25):`mem-numbers` / `mem-budget` 的事实源是 Keil `.map`,无 `.map` 时两者**自适应 SKIP 并返回 0**——"改了代码但根本没编译" - 这条最常见的漏测路径因此在本地也是绿的,CI 更是整根钩子都不跑。A-5 说的正是 + 这条最常见的漏测路径因此在本地也是绿的,CI 更是整根钩子都不跑。它要拦的正是 这个**"没跑 = 通过"**的盲区。 本步跑 `scripts/check_size_gate.py --local`,不需要 Keil:它校验**体积同码凭证** @@ -305,7 +305,7 @@ def run_size_gate(): """ print("") print("=" * 64) - print(" L0c 体积门禁(A-5 残余:同码凭证 + 92% 红线,无需 Keil)") + print(" L0c 体积门禁(同码凭证 + 92% 红线,无需 Keil)") print("=" * 64) script = REPO_ROOT / "scripts" / "check_size_gate.py" if not script.is_file(): @@ -409,7 +409,7 @@ def main(argv=None): g.add_argument("--l0-only", action="store_true", help="只跑 L0 静态门禁") g.add_argument("--l05-only", action="store_true", help="只跑 L0.5 主机侧单测") g.add_argument("--size-only", action="store_true", - help="只跑 L0c 体积门禁(A-5 强制路径;约 0.3 s,无需 Keil)") + help="只跑 L0c 体积门禁(强制路径;约 0.3 s,无需 Keil)") ap.add_argument("--coverage", action="store_true", help="额外跑主机单测覆盖率(约 25 s;需 VS 代码覆盖率组件;默认不跑,CI 也不跑)") ap.add_argument("--keep-going", action="store_true", @@ -441,7 +441,7 @@ def main(argv=None): return 2 rc = 0 - # 实际跑过哪些段——收尾那行只报这些。理由与 A-5 同源且是同一类错误: + # 实际跑过哪些段——收尾那行只报这些。理由与那个盲区同源且是同一类错误: # "--l05-only 却宣布 L0/L0b/L0c 全部通过"就是把没跑的说成跑过了(SKIP 冒充 PASS # 的镜像版)。2026-10-25 做 task-10 时从 --l05-only 的输出里发现,当场改掉。 ran = [] @@ -456,7 +456,7 @@ def main(argv=None): rc_msg = run_gitlint(args.commits) ran.append("L0b") # L0c 体积门禁同样**无条件照跑**:它回答的是"这份代码测过体积没有", - # 与 L0/L0b 的结论正交,且只要 0.3 s——少跑一次就是 A-5 那个盲区。 + # 与 L0/L0b 的结论正交,且只要 0.3 s——少跑一次就是那个盲区。 rc_size = run_size_gate() ran.append("L0c") if rc != 0 or rc_msg != 0 or rc_size != 0: diff --git a/scripts/gen_task_table.py b/scripts/gen_task_table.py index e7674438..8c1899ca 100644 --- a/scripts/gen_task_table.py +++ b/scripts/gen_task_table.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -FreeRTOS 任务表一致性工具(D-2 / D-7)。 +FreeRTOS 任务表一致性工具。 背景 ---- diff --git a/scripts/id_aliases.json b/scripts/id_aliases.json index 84e43faf..0b947535 100644 --- a/scripts/id_aliases.json +++ b/scripts/id_aliases.json @@ -3,6 +3,158 @@ "generated_from": "docs/编号规范.md §7 迁移映射 + §4.3 冻结前缀;本表只登记**仓库里已实际出现**的旧字面量 → 现行字面量,不做尚未落地的批量改写(§7.2 阶段 1 一律保留存量)", "note": "旧号引用不因本表而改写:本表只让门禁把「旧号引用」判成可解析(校验 3 悬空引用 / 校验 4 重复定义)。批量改写属 §9 阶段 1,须逐批 dry-run + 三重复核(§7.3)。 带 scope 的条目只在所列文件内生效(同一裸形字面量可能属多族,全局别名会把真撞号洗成已解析)。", "aliases": { + "N-3": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B(用户裁定:规划/待办类编号一律转语义标识):登记簿行保留、# 列改短语「L5 队列往返基线待重验」。旧号此后不得再指,引用侧已全部改写" + }, + "N-16": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「网络负向场景台架验证」;旧号不得再指" + }, + "N-18": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「双转换器无共同采样触发」(AGENTS.md 与采集链文档同批改写);旧号不得再指" + }, + "N-23": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿「N-23 残余」行改短语「rs485_task 白名单缺口」;旧号不得再指" + }, + "N-25": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「网络性能资源建议簇」;旧号不得再指" + }, + "N-27": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「缩堆迁 CCM 后半(暂缓)」;mem-budget 钩子说明同批改写;旧号不得再指" + }, + "N-28": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「uvprojx 空 TU 观察」;旧号不得再指" + }, + "N-29": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「仓库体积膨胀观察」;旧号不得再指" + }, + "N-30": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「relisten 沉降段原语化受阻」;原 resolved-in-place 条目由本条取代;旧号不得再指" + }, + "S-3": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B 曾把登记簿该行的 # 列改成语义短语;同日晚些时候用户裁定**不做这项改造**、登记行已删除。旧号不得再指(与 MENU-STATE S3 旧号、代码规范条款旧号无关,那些条目带各自 scope)" + }, + "S-4": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「dhara 重试自旋残留」;旧号不得再指" + }, + "S-5": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「可选全序列卷锁」;旧号不得再指" + }, + "T-7": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:登记簿 # 列改短语「CI 远端闭环未验证」;旧号不得再指" + }, + "A-4": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:docs/任务规划/测试与架构专项.md 条目改短语「采集链路测试可验证性约束」;旧号不得再指" + }, + "A-5": { + "to": null, + "status": "retired-to-phrase", + "note": "2026-10-26 方案 B:体积门禁 CI 强制路径条目改短语「体积门禁 CI 强制路径」;旧号不得再指" + }, + "U1": { + "to": null, + "status": "retired-to-phrase", + "scope": [ + "Drivers/BSP/UI/*", + "docs/UI设计/*", + "docs/任务规划/*", + "docs/子系统设计/看门狗设计说明.md", + "docs/代码规范/pre-commit使用说明.md", + "docs/门禁说明/*", + ".pre-commit-config.yaml", + "docs/README.md", + "docs/产品需求/*", + "docs/网络/*", + "docs/测试说明/*", + "docs/架构设计/*", + "docs/内存管理/*", + "docs/CI/*", + "docs/子系统设计/*" + ], + "note": "2026-10-26 方案 B:已删报告的 UI 标准化簇号 U1 → 短语「UI 设计令牌层」。**scope 限定**:同字面量在 docs/硬件平台 与 tools/netlist_semantics 是元器件位号,不受本条影响" + }, + "U2": { + "to": null, + "status": "retired-to-phrase", + "scope": [ + "Drivers/BSP/UI/*", + "docs/UI设计/*", + "docs/任务规划/*", + "docs/子系统设计/*", + "docs/代码规范/*", + "docs/门禁说明/*", + "docs/测试说明/*", + "test/*" + ], + "note": "2026-10-26 方案 B:UI 标准化簇 U2 → 短语「记录页共享组件合并」;同字面量在硬件网表语境是位号" + }, + "U3": { + "to": null, + "status": "retired-to-phrase", + "scope": [ + "Drivers/BSP/UI/*", + "docs/UI设计/*", + "docs/任务规划/*", + "docs/子系统设计/*", + "docs/门禁说明/*", + "docs/测试说明/*" + ], + "note": "2026-10-26 方案 B:UI 标准化簇 U3 → 短语「UI 页面原型化」;同字面量 `U3.14` 一类是位号(点号后缀),不在本条范围" + }, + "U4": { + "to": null, + "status": "retired-to-phrase", + "scope": [ + "Drivers/BSP/UI/*", + "docs/UI设计/*", + "docs/任务规划/*", + "docs/子系统设计/*", + "docs/代码规范/*", + "docs/门禁说明/*", + ".pre-commit-config.yaml" + ], + "note": "2026-10-26 方案 B:UI 标准化簇 U4 → 短语「UI 字符集与像素回归门禁」" + }, + "U5": { + "to": null, + "status": "retired-to-phrase", + "scope": [ + "Drivers/BSP/UI/*", + "docs/UI设计/*", + "docs/任务规划/*", + "docs/子系统设计/*", + "docs/门禁说明/*", + "docs/README.md" + ], + "note": "2026-10-26 方案 B:UI 标准化簇 U5 → 短语「UI 剩余 21 叶页面落地」" + }, "N-31": { "to": null, "status": "void", diff --git a/scripts/id_known_backlog.json b/scripts/id_known_backlog.json index 8eecdcbf..ba872b4c 100644 --- a/scripts/id_known_backlog.json +++ b/scripts/id_known_backlog.json @@ -2,1847 +2,7 @@ "schema": 1, "note": "编号债务台账(docs/编号规范.md §8.4):--strict 只对**台账之外**的新错误级问题拦提交。台账只许减不许增——还清的用 --prune-backlog 收掉;确需新增一条必须写 reason 字段说明这笔债为什么该存在,默认禁止顺手加行(否则一周后台账就变成第二个 backlog)。指纹 = 文件 + 校验号 + 字面量,不含行号(行号会漂,用它台账天天假新增)。", "generated_by": "scripts/check_ids.py --write-backlog", - "total_error_findings": 651, - "decidable_ratio_at_generation": 82.94, - "entries": [ - { - "file": ".dsh/skills/serial-assistant/scripts/serial_tool.py", - "code": 2, - "literal": "T1", - "count": 1 - }, - { - "file": ".dsh/skills/ui-verify/SKILL.md", - "code": 6, - "literal": "R-9", - "count": 1 - }, - { - "file": ".gitea/workflows/ci.yml", - "code": 2, - "literal": "S3", - "count": 3 - }, - { - "file": ".pre-commit-config.yaml", - "code": 6, - "literal": "D-2", - "count": 2 - }, - { - "file": ".pre-commit-config.yaml", - "code": 6, - "literal": "D-3", - "count": 1 - }, - { - "file": ".pre-commit-config.yaml", - "code": 6, - "literal": "D-7", - "count": 3 - }, - { - "file": ".pre-commit-config.yaml", - "code": 6, - "literal": "F-1", - "count": 1 - }, - { - "file": "App/algo/freq.c", - "code": 2, - "literal": "S2", - "count": 2 - }, - { - "file": "App/app/app_cfg.h", - "code": 6, - "literal": "P2-12", - "count": 1 - }, - { - "file": "App/cfg/cfg_file.c", - "code": 6, - "literal": "P0-1", - "count": 1 - }, - { - "file": "App/cfg/cfg_file.c", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "App/cfg/cfg_file.c", - "code": 6, - "literal": "P2-1", - "count": 6 - }, - { - "file": "App/cfg/cfg_file.c", - "code": 6, - "literal": "P3-1", - "count": 1 - }, - { - "file": "App/cfg/cfg_file.h", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "App/cfg/cfg_file_store_mcu.c", - "code": 6, - "literal": "P0-1", - "count": 2 - }, - { - "file": "App/cfg/cfg_file_store_mcu.c", - "code": 6, - "literal": "P3-2", - "count": 3 - }, - { - "file": "App/log/console_log.c", - "code": 2, - "literal": "gen-288", - "count": 3 - }, - { - "file": "App/log/sys_log_mcu.c", - "code": 6, - "literal": "P2-10", - "count": 3 - }, - { - "file": "App/log/sys_log_mcu.c", - "code": 6, - "literal": "P2-9", - "count": 4 - }, - { - "file": "App/task/adc_task.c", - "code": 6, - "literal": "P3-11", - "count": 1 - }, - { - "file": "App/task/default_task.c", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "App/task/default_task.c", - "code": 6, - "literal": "P2-9", - "count": 1 - }, - { - "file": "App/task/iec104_serv.c", - "code": 2, - "literal": "I-1", - "count": 1 - }, - { - "file": "App/task/net_task.c", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "App/task/net_task.c", - "code": 6, - "literal": "P1-6", - "count": 1 - }, - { - "file": "App/task/net_task.c", - "code": 6, - "literal": "P1-8", - "count": 5 - }, - { - "file": "App/task/net_task.c", - "code": 6, - "literal": "P2-11", - "count": 1 - }, - { - "file": "App/task/net_task.h", - "code": 6, - "literal": "P1-6", - "count": 1 - }, - { - "file": "App/task/rs485_task.c", - "code": 6, - "literal": "P3-11", - "count": 1 - }, - { - "file": "Drivers/BSP/CH395F/ch395f.c", - "code": 6, - "literal": "P2-2", - "count": 5 - }, - { - "file": "Drivers/BSP/CH395F/ch395f.h", - "code": 6, - "literal": "P1-06", - "count": 1 - }, - { - "file": "Drivers/BSP/CH395F/ch395f.h", - "code": 6, - "literal": "P2-2", - "count": 1 - }, - { - "file": "Drivers/BSP/NET/net_select.c", - "code": 6, - "literal": "P2-7", - "count": 2 - }, - { - "file": "Drivers/BSP/NET/net_socket.c", - "code": 6, - "literal": "P1-7", - "count": 1 - }, - { - "file": "Drivers/BSP/NETCFG/net_cfg.c", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "Drivers/BSP/NETCFG/net_cfg.h", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "Drivers/BSP/RS485/rs485.c", - "code": 6, - "literal": "P3-4", - "count": 1 - }, - { - "file": "Drivers/BSP/UI/ui_net_sink.c", - "code": 2, - "literal": "I-1", - "count": 2 - }, - { - "file": "Drivers/BSP/UI/ui_net_sink.c", - "code": 2, - "literal": "I-2", - "count": 3 - }, - { - "file": "Src/freertos.c", - "code": 6, - "literal": "P1-6", - "count": 1 - }, - { - "file": "Src/freertos.c", - "code": 6, - "literal": "P2-13", - "count": 1 - }, - { - "file": "docs/CI/远程CI完整指南.md", - "code": 2, - "literal": "S3", - "count": 2 - }, - { - "file": "docs/CI/远程CI完整指南.md", - "code": 6, - "literal": "D-3", - "count": 1 - }, - { - "file": "docs/README.md", - "code": 2, - "literal": "T1", - "count": 1 - }, - { - "file": "docs/README.md", - "code": 2, - "literal": "Task #32", - "count": 1 - }, - { - "file": "docs/README.md", - "code": 6, - "literal": "D-3", - "count": 1 - }, - { - "file": "docs/README.md", - "code": 6, - "literal": "D-6", - "count": 4 - }, - { - "file": "docs/README.md", - "code": 6, - "literal": "D-7", - "count": 3 - }, - { - "file": "docs/UI设计/UI_自动化测试流程.md", - "code": 6, - "literal": "F-1", - "count": 1 - }, - { - "file": "docs/UI设计/UI_设计架构.md", - "code": 6, - "literal": "F-1", - "count": 1 - }, - { - "file": "docs/UI设计/内容页架构.md", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "docs/UI设计/菜单页面设计.md", - "code": 2, - "literal": "陷阱 3", - "count": 1 - }, - { - "file": "docs/产品需求/产品需求设计文档.md", - "code": 6, - "literal": "P0-1", - "count": 1 - }, - { - "file": "docs/代码规范/pre-commit使用说明.md", - "code": 2, - "literal": "T1", - "count": 1 - }, - { - "file": "docs/代码规范/pre-commit使用说明.md", - "code": 2, - "literal": "W1-0", - "count": 1 - }, - { - "file": "docs/代码规范/pre-commit使用说明.md", - "code": 6, - "literal": "D-2", - "count": 3 - }, - { - "file": "docs/代码规范/pre-commit使用说明.md", - "code": 6, - "literal": "D-3", - "count": 3 - }, - { - "file": "docs/代码规范/pre-commit使用说明.md", - "code": 6, - "literal": "D-7", - "count": 3 - }, - { - "file": "docs/任务规划/README.md", - "code": 2, - "literal": "W1-0", - "count": 2 - }, - { - "file": "docs/任务规划/README.md", - "code": 6, - "literal": "R-2", - "count": 3 - }, - { - "file": "docs/任务规划/README.md", - "code": 6, - "literal": "R-3", - "count": 3 - }, - { - "file": "docs/任务规划/README.md", - "code": 6, - "literal": "R-4", - "count": 3 - }, - { - "file": "docs/任务规划/README.md", - "code": 6, - "literal": "R-5", - "count": 1 - }, - { - "file": "docs/任务规划/README.md", - "code": 6, - "literal": "R-6", - "count": 3 - }, - { - "file": "docs/任务规划/README.md", - "code": 6, - "literal": "R-9", - "count": 3 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 2, - "literal": "gen 181", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 2, - "literal": "gen-288", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 6, - "literal": "F-1", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 6, - "literal": "R-2", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 6, - "literal": "R-3", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 6, - "literal": "R-4", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 6, - "literal": "R-6", - "count": 1 - }, - { - "file": "docs/任务规划/登记簿.md", - "code": 6, - "literal": "R-9", - "count": 2 - }, - { - "file": "docs/内存管理/RAM使用分析与优化方案.md", - "code": 2, - "literal": "S2", - "count": 1 - }, - { - "file": "docs/内存管理/RAM使用分析与优化方案.md", - "code": 2, - "literal": "S3", - "count": 2 - }, - { - "file": "docs/内存管理/RAM使用分析与优化方案.md", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "docs/内存管理/RAM预算台账.md", - "code": 2, - "literal": "W1-0", - "count": 2 - }, - { - "file": "docs/内存管理/RAM预算台账.md", - "code": 6, - "literal": "R-2", - "count": 5 - }, - { - "file": "docs/内存管理/RAM预算台账.md", - "code": 6, - "literal": "R-9", - "count": 3 - }, - { - "file": "docs/内存管理/内存内容分配策略.md", - "code": 2, - "literal": "gen68", - "count": 2 - }, - { - "file": "docs/内存管理/内存内容分配策略.md", - "code": 6, - "literal": "R-4", - "count": 1 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P0-1", - "count": 3 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P0-2", - "count": 7 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P0-3", - "count": 3 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P1-4", - "count": 6 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P1-5", - "count": 3 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P2-6", - "count": 1 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P2-7", - "count": 1 - }, - { - "file": "docs/内存管理/存储栈RAM优化调研.md", - "code": 6, - "literal": "P2-8", - "count": 1 - }, - { - "file": "docs/子系统设计/IEC104上行设计说明.md", - "code": 2, - "literal": "I-1", - "count": 1 - }, - { - "file": "docs/子系统设计/控制台单写者日志设计说明.md", - "code": 2, - "literal": "gen-288", - "count": 2 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 2, - "literal": "P1-4a", - "count": 1 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 2, - "literal": "Task #32", - "count": 2 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 2, - "literal": "gen-288", - "count": 1 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 2, - "literal": "round 3", - "count": 1 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 2, - "literal": "round 7", - "count": 2 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 2, - "literal": "round 8", - "count": 1 - }, - { - "file": "docs/子系统设计/看门狗设计说明.md", - "code": 3, - "literal": "N-03", - "count": 2 - }, - { - "file": "docs/子系统设计/运行与自检记录设计说明.md", - "code": 2, - "literal": "S2", - "count": 1 - }, - { - "file": "docs/子系统设计/运行与自检记录设计说明.md", - "code": 2, - "literal": "S3", - "count": 1 - }, - { - "file": "docs/子系统设计/运行与自检记录设计说明.md", - "code": 6, - "literal": "P2-10", - "count": 1 - }, - { - "file": "docs/子系统设计/运行与自检记录设计说明.md", - "code": 6, - "literal": "R-1", - "count": 1 - }, - { - "file": "docs/子系统设计/运行与自检记录设计说明.md", - "code": 6, - "literal": "R-2", - "count": 1 - }, - { - "file": "docs/子系统设计/配置文件设计说明.md", - "code": 6, - "literal": "P0-1", - "count": 7 - }, - { - "file": "docs/子系统设计/配置文件设计说明.md", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "docs/子系统设计/配置文件设计说明.md", - "code": 6, - "literal": "P1-5", - "count": 1 - }, - { - "file": "docs/文档规范.md", - "code": 6, - "literal": "D-6", - "count": 3 - }, - { - "file": "docs/文档规范.md", - "code": 6, - "literal": "D-7", - "count": 4 - }, - { - "file": "docs/架构设计/项目架构总览.md", - "code": 6, - "literal": "P2-13", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-01A", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-02A", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-02B", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-02C", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-03A", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-04A", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-ADCIN-04B", - "count": 1 - }, - { - "file": "docs/测试说明/ADC输入层单元测试说明.md", - "code": 2, - "literal": "TC-AI-03A", - "count": 1 - }, - { - "file": "docs/测试说明/CH395F_Test_Guide.md", - "code": 2, - "literal": "DP-01", - "count": 16 - }, - { - "file": "docs/测试说明/CH395F_Test_Guide.md", - "code": 2, - "literal": "DP-02", - "count": 5 - }, - { - "file": "docs/测试说明/CH395F_Test_Guide.md", - "code": 2, - "literal": "DP-03", - "count": 7 - }, - { - "file": "docs/测试说明/CH395F_Test_Guide.md", - "code": 2, - "literal": "DP-04", - "count": 13 - }, - { - "file": "docs/测试说明/CH395F_Test_Guide.md", - "code": 2, - "literal": "TC-302", - "count": 1 - }, - { - "file": "docs/测试说明/CH395F_Test_Guide.md", - "code": 2, - "literal": "TC-501", - "count": 1 - }, - { - "file": "docs/测试说明/GD5F2GQ5UE_Test_Guide.md", - "code": 2, - "literal": "TC-1003", - "count": 1 - }, - { - "file": "docs/测试说明/GD5F2GQ5UE_Test_Guide.md", - "code": 2, - "literal": "TC-206", - "count": 1 - }, - { - "file": "docs/测试说明/GD5F2GQ5UE_Test_Guide.md", - "code": 2, - "literal": "TC-704", - "count": 2 - }, - { - "file": "docs/测试说明/串口助手设计说明.md", - "code": 2, - "literal": "S2", - "count": 1 - }, - { - "file": "docs/测试说明/串口助手设计说明.md", - "code": 2, - "literal": "S3", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 2, - "literal": "round 1", - "count": 3 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 2, - "literal": "round 3", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 2, - "literal": "round 7", - "count": 2 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 3, - "literal": "N-03", - "count": 2 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 6, - "literal": "H-1", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 6, - "literal": "H-7", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 6, - "literal": "L-2", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 6, - "literal": "L-4", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 6, - "literal": "P0-1", - "count": 1 - }, - { - "file": "docs/测试说明/实机测试实施指南.md", - "code": 6, - "literal": "P1-8", - "count": 5 - }, - { - "file": "docs/测试说明/模拟器差距清单.md", - "code": 6, - "literal": "P0-1", - "count": 1 - }, - { - "file": "docs/测试说明/测试开发标准流程.md", - "code": 2, - "literal": "DP-01", - "count": 1 - }, - { - "file": "docs/测试说明/测试开发标准流程.md", - "code": 2, - "literal": "S2", - "count": 2 - }, - { - "file": "docs/测试说明/测试开发标准流程.md", - "code": 2, - "literal": "S3", - "count": 2 - }, - { - "file": "docs/测试说明/测试开发标准流程.md", - "code": 6, - "literal": "P1-6", - "count": 4 - }, - { - "file": "docs/测试说明/测试开发标准流程.md", - "code": 6, - "literal": "P1-9", - "count": 2 - }, - { - "file": "docs/测试说明/测试文档模板规范.md", - "code": 2, - "literal": "DP-01", - "count": 2 - }, - { - "file": "docs/测试说明/测试文档模板规范.md", - "code": 2, - "literal": "DP-02", - "count": 1 - }, - { - "file": "docs/测试说明/测试文档模板规范.md", - "code": 3, - "literal": "TC-XXX-001", - "count": 2 - }, - { - "file": "docs/测试说明/测试文档模板规范.md", - "code": 3, - "literal": "TM-NAND-01", - "count": 1 - }, - { - "file": "docs/测试说明/测试文档模板规范.md", - "code": 3, - "literal": "TM-XXX-01", - "count": 1 - }, - { - "file": "docs/测试说明/链路延迟测试说明.md", - "code": 2, - "literal": "S2", - "count": 2 - }, - { - "file": "docs/测试说明/链路延迟测试说明.md", - "code": 2, - "literal": "S3", - "count": 1 - }, - { - "file": "docs/环境与部署/并行任务工作树方案.md", - "code": 2, - "literal": "trap22", - "count": 1 - }, - { - "file": "docs/环境与部署/项目部署环境要求.md", - "code": 6, - "literal": "P2-12", - "count": 1 - }, - { - "file": "docs/硬件平台/ADC通道映射说明.md", - "code": 6, - "literal": "P1-5", - "count": 2, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "A-10", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "A-11", - "count": 2, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-1", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-10", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-2", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-3", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-4", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-5", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-6", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-7", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-8", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "C-9", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 1, - "literal": "FOC-817", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 3, - "literal": "A-2", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 3, - "literal": "A-3", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 3, - "literal": "A-6", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 3, - "literal": "A-8", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 3, - "literal": "A-9", - "count": 2, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 5, - "literal": "A-8", - "count": 1, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/硬件平台/V0.0_网表详细分析报告.md", - "code": 5, - "literal": "A-9", - "count": 2, - "reason": "硬件语料(docs/硬件平台/)2026-10-22 首次纳入扫描暴露的**先前不可见存量**,非本批新引入。引脚名/总线名/网名(PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3 一类)已由 check_ids.py 的位号规则挡掉且不进比例分母;本条是**真编号**:报告自有条目号(C-x / A-x)或需紧邻文档名,待逐条归位(§4.3 快照族登记或补文档名)。器件料号 FOC-817 也留在此记账——不给它做形状规则,因为 [A-Z]{3}-\\d{3} 会与真家族 DEF-* 撞形,宁可留一条债也不制造盲区。" - }, - { - "file": "docs/网络/BSD_Socket_API_使用指南.md", - "code": 6, - "literal": "P1-3", - "count": 1 - }, - { - "file": "docs/网络/BSD_Socket_API_函数调用关系.md", - "code": 2, - "literal": "T1", - "count": 4 - }, - { - "file": "docs/网络/BSD_Socket_API_函数调用关系.md", - "code": 6, - "literal": "P1-7", - "count": 2 - }, - { - "file": "docs/网络/网络自愈架构设计.md", - "code": 2, - "literal": "T1", - "count": 1 - }, - { - "file": "docs/问题记录/CH395F_Trap_Records.md", - "code": 2, - "literal": "round 1", - "count": 1 - }, - { - "file": "docs/问题记录/GD5F2GQ5UE_Trap_Records.md", - "code": 2, - "literal": "gen 45", - "count": 1 - }, - { - "file": "scripts/README.md", - "code": 6, - "literal": "D-2", - "count": 1 - }, - { - "file": "scripts/README.md", - "code": 6, - "literal": "D-3", - "count": 1 - }, - { - "file": "scripts/README.md", - "code": 6, - "literal": "D-7", - "count": 1 - }, - { - "file": "scripts/check_doc_links.py", - "code": 6, - "literal": "D-3", - "count": 1 - }, - { - "file": "scripts/check_lv_conf_sync.py", - "code": 6, - "literal": "F-1", - "count": 1 - }, - { - "file": "scripts/check_mem_numbers.py", - "code": 6, - "literal": "D-7", - "count": 2 - }, - { - "file": "scripts/check_test_traceability.py", - "code": 2, - "literal": "TC-014", - "count": 1 - }, - { - "file": "scripts/check_test_traceability.py", - "code": 3, - "literal": "TC-XXX-003b", - "count": 1 - }, - { - "file": "scripts/ci_local.py", - "code": 2, - "literal": "S3", - "count": 2 - }, - { - "file": "scripts/gen_task_table.py", - "code": 6, - "literal": "D-2", - "count": 1 - }, - { - "file": "scripts/gen_task_table.py", - "code": 6, - "literal": "D-7", - "count": 1 - }, - { - "file": "scripts/ram_baseline.py", - "code": 2, - "literal": "W1-0", - "count": 1 - }, - { - "file": "scripts/ram_baseline.py", - "code": 6, - "literal": "R-9", - "count": 1 - }, - { - "file": "scripts/rename_ids.py", - "code": 2, - "literal": "S3", - "count": 2 - }, - { - "file": "scripts/rename_ids.py", - "code": 3, - "literal": "TC-PROTSET-11", - "count": 1 - }, - { - "file": "simulator/lv_conf.h", - "code": 6, - "literal": "F-2", - "count": 1 - }, - { - "file": "simulator/sim/cfg_file_store_sim.c", - "code": 6, - "literal": "P0-1", - "count": 1 - }, - { - "file": "simulator/tests/check_content_pages.py", - "code": 6, - "literal": "F-2", - "count": 1 - }, - { - "file": "simulator/tests/check_menu_rows.py", - "code": 6, - "literal": "F-2", - "count": 1 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 2, - "literal": "DP-04", - "count": 3 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 2, - "literal": "TC-301", - "count": 2 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 2, - "literal": "TC-302", - "count": 2 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-01", - "count": 5 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-02", - "count": 3 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-03", - "count": 5 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-04", - "count": 3 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-05", - "count": 3 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-06", - "count": 4 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-07", - "count": 3 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-08", - "count": 3 - }, - { - "file": "test/board/ch395f_test_task.c", - "code": 6, - "literal": "P1-09", - "count": 5 - }, - { - "file": "test/board/gd5f_test_task.c", - "code": 3, - "literal": "TC-GD5F-1101", - "count": 3 - }, - { - "file": "test/board/gd5f_test_task.c", - "code": 3, - "literal": "TC-GD5F-1102", - "count": 1 - }, - { - "file": "test/board/net_latency_test.c", - "code": 6, - "literal": "P1-06", - "count": 1 - }, - { - "file": "test/board/net_test_task.c", - "code": 6, - "literal": "P1-9", - "count": 1 - }, - { - "file": "test/board/test_config.h", - "code": 6, - "literal": "P2-13", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-003B", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-003C", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-003D", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-005B", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-01A", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-02A", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-02B", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-02C", - "count": 2 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-03A", - "count": 2 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-03B", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-03C", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-03D", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-03E", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-04A", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-04B", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-074B", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 2, - "literal": "TC-ADCIN-09A", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-031", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-032", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-036", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-037", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-038", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-071", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-072", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-073", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-074", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-075", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-076", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-078", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-081", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-082", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-083", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-084", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-085", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-087", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-088", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-089", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-091", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-092", - "count": 1 - }, - { - "file": "test/host/adc_input/adc_input_test.c", - "code": 3, - "literal": "TC-ADCIN-093", - "count": 1 - }, - { - "file": "test/host/cfg_file/cfg_file_test.c", - "code": 2, - "literal": "TC-010", - "count": 1 - }, - { - "file": "test/host/cfg_file/cfg_file_test.c", - "code": 2, - "literal": "TC-014", - "count": 2 - }, - { - "file": "test/host/cfg_file/cfg_file_test.c", - "code": 3, - "literal": "TC-CONFIG-024", - "count": 2 - }, - { - "file": "test/host/cfg_file/cfg_file_test.c", - "code": 3, - "literal": "TC-CONFIG-044", - "count": 2 - }, - { - "file": "test/host/cfg_file/cfg_file_test.c", - "code": 3, - "literal": "TC-CONFIG-052", - "count": 1 - }, - { - "file": "test/host/cfg_file/cfg_file_test.c", - "code": 6, - "literal": "P2-1", - "count": 2 - }, - { - "file": "test/host/ch395f_socket_test.py", - "code": 2, - "literal": "TC-301", - "count": 9 - }, - { - "file": "test/host/ch395f_socket_test.py", - "code": 2, - "literal": "TC-302", - "count": 9 - }, - { - "file": "test/host/ch395f_socket_test.py", - "code": 2, - "literal": "TC-602", - "count": 2 - }, - { - "file": "test/host/e11_keepalive_probe.py", - "code": 2, - "literal": "T1", - "count": 2 - }, - { - "file": "test/host/ftp_auth_bench.py", - "code": 2, - "literal": "T1", - "count": 3 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-001", - "count": 4 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-002", - "count": 3 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-003", - "count": 5 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-004", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-005", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-006", - "count": 11 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-007", - "count": 7 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-008", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-009", - "count": 3 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-010", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-011", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-012", - "count": 5 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-013", - "count": 5 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-014", - "count": 7 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-015", - "count": 4 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-016", - "count": 3 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-017", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-018", - "count": 3 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-019", - "count": 1 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-020", - "count": 7 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-021", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-022", - "count": 3 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-023", - "count": 2 - }, - { - "file": "test/host/iec104/iec104_test.c", - "code": 2, - "literal": "TC-024", - "count": 11 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-001", - "count": 9 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-002", - "count": 4 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-003", - "count": 6 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-004", - "count": 3 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-005", - "count": 4 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-006", - "count": 4 - }, - { - "file": "test/host/soe/soe_test.c", - "code": 2, - "literal": "TC-007", - "count": 2 - }, - { - "file": "test/run_all_suites.py", - "code": 6, - "literal": "P1-10", - "count": 1 - }, - { - "file": "test/run_all_suites.py", - "code": 6, - "literal": "P1-9", - "count": 1 - }, - { - "file": "tools/netlist_semantics/gen_kb.py", - "code": 2, - "literal": "T1", - "count": 7 - }, - { - "file": "tools/netlist_semantics/netlist_sem.py", - "code": 2, - "literal": "T1", - "count": 1 - }, - { - "file": "tools/netlist_semantics/pinout_kb.json", - "code": 2, - "literal": "T1", - "count": 7 - } - ] + "total_error_findings": 0, + "decidable_ratio_at_generation": 85.98, + "entries": [] } diff --git a/scripts/id_registry.json b/scripts/id_registry.json index 9b1ffce3..84cef3b4 100644 --- a/scripts/id_registry.json +++ b/scripts/id_registry.json @@ -1,7 +1,7 @@ { "schema": 1, "generated_from": "docs/编号规范.md §4 编号空间总表(§4.1 在册 / §4.2 目标 / §4.3 冻结 / §4.4 划界)+ §6 模块码表 + §7 迁移映射。游标取「规范记载值」与「全库实测最大号」的较大者,实测偏差一律写进 cursor_note,不凭印象编。", - "decidable_ratio_baseline": 84.0, + "decidable_ratio_baseline": 86.0, "spec_docs": [ "docs/编号规范.md" ], @@ -58,7 +58,8 @@ ".dsh/n31_neg_inject_gen217.log", ".dsh/n31_neg_inject_gen221_form5.log", "scripts/id_known_backlog.json", - "scripts/rename_maps/" + "scripts/rename_maps/", + "docs/硬件平台/" ], "excluded_suffixes": [ ".svg", @@ -215,10 +216,11 @@ "frozen": false, "cursor": "N-34", "check_dangling": true, - "cursor_note": "规范 §4.1 记游标 N-34;实测登记簿现存至 N-30(N-31 曾于 2026-10-14 被复用撞号,见 docs/任务规划/README.md §0)→ 新条目一律改语义标识(P-8),实需编号才取 N-34", + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", "adjacent_cues": [ "登记簿" - ] + ], + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "S-LDG", @@ -233,10 +235,11 @@ "frozen": true, "cursor": "S-5", "check_dangling": true, - "cursor_note": "现存 S-3/S-4/S-5;§7.1 之后不再新增 S 号", + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", "adjacent_cues": [ "登记簿" - ] + ], + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "T-LDG", @@ -252,11 +255,12 @@ "frozen": true, "cursor": "T-7", "check_dangling": true, - "cursor_note": "现存 T-7(复审 T-1~T-5 见 docs/任务规划/测试与架构专项.md 卷首)", + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", "adjacent_cues": [ "登记簿", "专项" - ] + ], + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "E-AUD", @@ -338,7 +342,7 @@ "title": "待确认 / 开放问题(§4.2 规划类 → 语义标识)", "pattern": "\\bOQ-[0-9]{1,3}\\b", "owner_docs": [ - "docs/任务规划/基线与待确认.md" + "docs/任务规划/登记簿.md" ], "scope": "global", "frozen": true, @@ -351,7 +355,7 @@ "title": "里程碑目标形态 MS-(§4.2;存量 M1~M3)", "pattern": "\\bMS-[0-9]{1,2}\\b", "owner_docs": [ - "docs/任务规划/采集保护收尾.md" + "docs/任务规划/登记簿.md" ], "scope": "global", "frozen": false, @@ -688,8 +692,7 @@ "pattern": "\\bB[0-9]{1,2}\\b", "owner_docs": [ "docs/任务规划/README.md", - "docs/任务规划/采集保护收尾.md", - "docs/任务规划/排期详述.md" + "docs/任务规划/登记簿.md" ], "scope": "global", "frozen": true, @@ -713,8 +716,7 @@ "title": "结构债排期条目 C1~C6(§4.1 **2026-10-25 废止**,改语义名;存量仅作历史指称)", "pattern": "\\bC[1-9]\\b", "owner_docs": [ - "docs/任务规划/README.md", - "docs/任务规划/排期详述.md" + "docs/任务规划/README.md" ], "scope": "global", "frozen": true, @@ -735,8 +737,7 @@ "title": "验证窗口排期条目 D2~D7(§4.1 **2026-10-25 废止**,改语义名;存量仅作历史指称)", "pattern": "\\bD[1-9]\\b", "owner_docs": [ - "docs/任务规划/README.md", - "docs/任务规划/排期详述.md" + "docs/任务规划/README.md" ], "scope": "global", "frozen": true, @@ -757,7 +758,7 @@ "title": "里程碑 M1~M3(§4.2 目标 MS-;存量冻结)", "pattern": "\\bM[1-3]\\b", "owner_docs": [ - "docs/任务规划/采集保护收尾.md", + "docs/任务规划/登记簿.md", "docs/任务规划/README.md" ], "scope": "global", @@ -1073,11 +1074,12 @@ "cursor_owner_only": true, "check_dangling": true, "dangling_outside_owner": "warn", - "cursor_note": "「编号不随条目删除而回收」(专项文档卷首)→ 已销项的号再被引用即悬空;⚠ 与登记簿 S-3/S-4/S-5 同号不同义(§1.2),也与 CI 步骤号 S3、代码规范条款号 S3 同形 → 文档外只告警", + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", "adjacent_cues": [ "复审", "专项" - ] + ], + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "A-REVIEW", @@ -1091,11 +1093,12 @@ "frozen": true, "cursor": "A-7", "check_dangling": true, - "cursor_note": "现存 A-4/A-5 行,其余已销项(编号不回收)", + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", "adjacent_cues": [ "复审", "专项" - ] + ], + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "T-REVIEW", @@ -1113,7 +1116,9 @@ "复审", "专项" ], - "collides_with": "N" + "collides_with": "N", + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "P-AUDIT", @@ -1123,8 +1128,7 @@ "owner_docs": [ "docs/任务规划/README.md", "docs/任务规划/测试与架构专项.md", - "docs/任务规划/登记簿.md", - "docs/任务规划/排期详述.md" + "docs/任务规划/登记簿.md" ], "scope": "global", "frozen": true, @@ -1227,8 +1231,9 @@ "cursor_owner_only": true, "check_dangling": false, "dangling_note": "与 UI 状态机 U1~U15、位号 U17 三方同形(§4.4 位号目录已排除)→ 只拦越游标", - "cursor_note": "U1~U5 原见《项目审查与优化建议报告》§6.5(该报告 2026-10-25 已删除);现行排期见 docs/任务规划/README.md §2.0「UI 扩展」与 登记簿 U 簇行", - "owner_note": "属主文档 docs/问题记录/项目审查与优化建议报告.md 已于 2026-10-25 删除(用户裁定:内容大部分已闭环);登记职责与 26 行「号 → 现在详述于」对照表迁到 docs/编号规范.md §4.5,故 owner_docs 指向该节——它只用于**恢复可判定比例**(§4.5 是冻结存档登记处),定义集仍为空(snapshot_only 族剔除规范正文,见 check_ids.py 的 _def_owners),因此不参与悬空/撞号/裸形判定。未闭环项早已并轨到登记簿 N-19~N-29 + U 簇" + "cursor_note": "2026-10-26 方案 B 停发:存量号已全部转语义短语(docs/编号规范.md §4.1.1),游标冻结不前进,旧号在 id_aliases.json 标 retired-to-phrase;本族登记只为冻结历史行不报未登记前缀", + "owner_note": "属主文档 docs/问题记录/项目审查与优化建议报告.md 已于 2026-10-25 删除(用户裁定:内容大部分已闭环);登记职责与 26 行「号 → 现在详述于」对照表迁到 docs/编号规范.md §4.5,故 owner_docs 指向该节——它只用于**恢复可判定比例**(§4.5 是冻结存档登记处),定义集仍为空(snapshot_only 族剔除规范正文,见 check_ids.py 的 _def_owners),因此不参与悬空/撞号/裸形判定。未闭环项早已并轨到登记簿 N-19~N-29 + U 簇", + "retired": "2026-10-26 方案 B 转语义短语" }, { "name": "U-STATE", @@ -1818,7 +1823,6 @@ "owner_note": "B 层口径(用户裁定 2026-10-21,同 TC-HOST-MEAS/TC-HOST-ADCCAL 先例;2026-10-25 道D 卡 10 建族):属主登记处 = 单测代码目录,不逐号补文档;路由顺序证据链在 TC-IEC104-029(test/host/iec104)", "cursor_note": "游标按建族时实测最大号收录(2026-10-25):越界即视为未登记的冒号牌" }, - { "name": "TC-HOST-IEC104", "class": "TC", @@ -1930,7 +1934,7 @@ "cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌" }, { - "name": "TC-HOST-TC-HOST-LFTPIO", + "name": "TC-HOST-LFTPIO", "class": "TC", "title": "主机侧单测用例号:FTP 文件 IO 层与路径规范化(test/host/lftpd_io)", "pattern": "\\bTC-LFTPIO-[0-9]{1,4}[a-z]?\\b", @@ -1952,7 +1956,7 @@ "cursor_note": "游标按实测最大号收录(2026-10-23):越界即视为未登记的冒号牌" }, { - "name": "TC-HOST-TC-HOST-LFTSTR", + "name": "TC-HOST-LFTSTR", "class": "TC", "title": "主机侧单测用例号:FTP 命令行字符串工具(同模块内)(test/host/lftpd_io)", "pattern": "\\bTC-LFTSTR-[0-9]{1,4}[a-z]?\\b", @@ -2146,5 +2150,66 @@ "owner_docs_exclude": [ "docs/测试说明/测试文档模板规范.md" ], - "designator_rule": "硬件语料位号识别:docs/硬件平台/ 与 docs/芯片手册/ 里匹配 ^[RCDJXUT][0-9]{1,3}$(无连字符)的字面量判为**位号**,不产生任何编号结论;不含 B(V0.0 网表报告的 B1 是真专项条目),带连字符的 D-7/N-18/TC-206 不受影响。机制在 check_ids.py 的 add() 里落地(统计键 designator_skipped)。**本目录仍未纳入扫描语料**:纳入前必须先把位号从可判定比例的 refs_total 里减掉,否则约 4200 处位号会把比例分母灌水、掩盖真实变化。 【批 9 扩展】同一规则再覆盖**引脚名/总线名/网名/区间**:^[A-Z]{2,3}[0-9]{1,2}(-[0-9]{1,2})?(如 PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3、AIN1),同样不进 refs_total。带连字符的一字母形态(C-10 / A-9 / N-18 / D-7)与 TC-206 / TC1003 一律不受影响,仍照旧判。器件料号(FOC-817SC-F 被切成 FOC-817)**不做形状规则**:[A-Z]{3}-\\d{3} 与真家族 DEF-* 撞形,代价远大于收益,故留在台账 reason 里记账。" + "designator_rule": "硬件语料位号识别:docs/硬件平台/ 与 docs/芯片手册/ 里匹配 ^[RCDJXUT][0-9]{1,3}$(无连字符)的字面量判为**位号**,不产生任何编号结论;不含 B(V0.0 网表报告的 B1 是真专项条目),带连字符的 D-7/N-18/TC-206 不受影响。机制在 check_ids.py 的 add() 里落地(统计键 designator_skipped)。**本目录仍未纳入扫描语料**:纳入前必须先把位号从可判定比例的 refs_total 里减掉,否则约 4200 处位号会把比例分母灌水、掩盖真实变化。 【批 9 扩展】同一规则再覆盖**引脚名/总线名/网名/区间**:^[A-Z]{2,3}[0-9]{1,2}(-[0-9]{1,2})?(如 PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3、AIN1),同样不进 refs_total。带连字符的一字母形态(C-10 / A-9 / N-18 / D-7)与 TC-206 / TC1003 一律不受影响,仍照旧判。器件料号(FOC-817SC-F 被切成 FOC-817)**不做形状规则**:[A-Z]{3}-\\d{3} 与真家族 DEF-* 撞形,代价远大于收益,故留在台账 reason 里记账。", + "excluded_paths_note": [ + "docs/硬件平台/ :2026-10-26 补齐——规范 §8 一直写着排除,registry 却没写进去,导致位号与网表复核条目被扫成编号(实测报 28 处错误,其中校验 1 把器件型号 FOC-817、网表复核条目 C-1~C-10/A-10/A-11 全部当自造号)。位号识别仍保留作为第二道保险" + ], + "cleared_2026_10_26": { + "note": "2026-10-26 清号批次:这些族的字面量已从活文档与代码注释中删除(属主登记处已删除或从未启用)。族定义保留在本文件里只有一个目的:让历史行、[示例]/[计划] 行与别名表不触发校验 1。不得新增引用;新增会被校验 3/5 拦下。可清单位:全仓零引用后可连族带别名一起删除。", + "families": [ + "E-AUD", + "P-AUDIT", + "N-RPT", + "D-RPT", + "S-RPT", + "R-RPT", + "F-RPT", + "U-RPT", + "S-REVIEW", + "A-REVIEW", + "B-AUDIT", + "T-LDG", + "S-LDG", + "I-ORPHAN", + "WAVE-TASK", + "TM", + "DP-LEGACY", + "DP", + "H-GAP", + "L-GAP", + "GAP", + "DR", + "RC", + "MS-TGT", + "GEN", + "WI", + "OQ", + "RSK", + "SC", + "ST", + "CS", + "UL", + "LAT", + "DEF", + "PAT", + "TRAP-NOMOD", + "TC-NOMOD", + "TC-UPPER" + ] + }, + "retired_to_phrase_2026_10_26": { + "note": "用户裁定方案 B:规划/待办类编号(登记簿 N-x/S-x/T-x、专项 A-x、已删报告 UI 簇 U1~U5)一律转语义短语。族保留登记、游标冻结;引用侧同批改写约 418 处、字面量净减 443(8,946 → 8,503):活号 237 + 已销项死引用 196 + U 簇非位号约 45。短语表见 docs/编号规范.md §4.1.1。", + "families": [ + "N", + "S-LDG", + "T-LDG", + "A-REVIEW", + "S-REVIEW", + "T-REVIEW", + "U-RPT" + ] + }, + "baseline_note": "2026-10-26 方案 B 曾把比例基线由 86.6% 下调到 85.9%(转短语消掉的全是「本可判定」引用,比值被稀释而不可判定绝对数未增;理由见 docs/门禁说明/id-check.md §7.6 与 docs/编号规范.md §8.1);同批新增第二根护栏 undecidable_total_ceiling 后,本文档又瘦身了一半,删掉的多是不可判定的历史号引用,两根护栏已按 --update-baseline 重打为比例 86.0% / 绝对数 1166(都只朝好的方向动)。", + "undecidable_total_ceiling": 1166, + "undecidable_ceiling_note": "不可判定引用绝对数上限(refs_total - decidable),只许减不许增;--update-baseline 同步下调且拒绝上调。加它的原因:比值会被清号动作稀释,绝对数不会。" } diff --git a/scripts/ram_baseline.py b/scripts/ram_baseline.py index b868e0c0..4a44996a 100644 --- a/scripts/ram_baseline.py +++ b/scripts/ram_baseline.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -RAM 预算门禁 + 趋势台账(登记簿 N-27 前半 / 审查报告 §5.5 三件套,2026-10-12)。 +RAM 预算门禁 + 趋势台账(登记簿条目「缩堆迁 CCM 后半(暂缓)」的台账/门禁那半 / 审查报告 §5.5 三件套,2026-10-12)。 三件套的机械部分收在本脚本(事实源 = Keil `.map`,与 `check_mem_numbers.py` 同口径): 1. **92% 阈值门禁**(`--gate`,pre-commit `mem-budget` 钩子入口): RW_IRAM1 / RW_CCM 任一区占比 > 92% → FAIL,并附 Top-10 RW+ZI 对象表; 无 `.map`(未做 Keil 构建)→ 自适应跳过(与 `mem-numbers` 同口径, - CI 盲区登记在 `docs/任务规划/测试与架构专项.md` A-5 残余)。 + CI 盲区登记在 `docs/任务规划/测试与架构专项.md`)。 `--gate` 同时校验台账受管区与 `.map` 一致(防漂移,同 `mem-numbers` 思路)。 2. **趋势基线** `scripts/ram_baseline.tsv`(`--update` 每次 @build 后追加一行; `--trend` 看趋势并高亮 RW+ZI 较上一行增长 >1 KB 的对象——方案 §4.0.3 @@ -18,7 +18,7 @@ RAM 预算门禁 + 趋势台账(登记簿 N-27 前半 / 审查报告 §5.5 三 子系统配额(报告 §5.5 第 3 条的规划值;RTOS 堆 2026-10-12 B5 48→56 KB): 网络 12 KB / 存储 12 KB / UI 16 KB / RTOS 堆 56 KB / 应用 8 KB - ——UI 配额是 R-9/R-2 落地后的**目标态**,现状超配 = W3 债务,台账如实展示; + ——UI 配额是缩池与迁 CCM 落地后的**目标态**,现状超配 = W3 债务,台账如实展示; 配额改动 = 先登记 `docs/内存管理/RAM预算台账.md` 变更记录,再改本脚本 `QUOTAS`。 用法(仓库根): @@ -28,7 +28,7 @@ RAM 预算门禁 + 趋势台账(登记簿 N-27 前半 / 审查报告 §5.5 三 python scripts/ram_baseline.py --trend [-n 10] 退出码:0 通过/跳过;1 门禁失败或受管区漂移;2 事实源解析失败。 -创建日期:2026-10-12(W1-0,登记簿 N-27 前半) +创建日期:2026-10-12(登记簿条目「缩堆迁 CCM 后半(暂缓)」的台账/门禁那半) """ import argparse import pathlib @@ -175,7 +175,7 @@ def cmd_gate(args): if map_path is None: print("[SKIP] 无 .map(未做 Keil 构建)→ RAM 预算门禁不适用") print(" ⚠ **SKIP 不等于 PASS**:本钩子这一趟什么都没检查。") - print(" A-5 残余的强制兜底(无需 Keil,改代码后必然拦红):") + print(" 强制兜底(无需 Keil,改代码后必然拦红):") print(" 本地 scripts/ci_local.py 的 L0c 步 / CI 的「体积门禁」步,") print(" 都跑 scripts/check_size_gate.py(同码凭证 + 92% 红线复核)。") print(" 改过代码请先 @build,再 python scripts/ram_baseline.py --update。") @@ -331,7 +331,7 @@ def cmd_update(args): f"({region_pct(reg['RW_IRAM1']):.1f}%) / RW_CCM {reg['RW_CCM']['size']:,} B " f"({region_pct(reg['RW_CCM']):.1f}%) → {TSV_PATH.relative_to(REPO_ROOT)}") - # A-5 残余(2026-10-25):同一次「真实测量」顺带刷新**体积同码凭证** + # 强制路径(2026-10-25):同一次「真实测量」顺带刷新**体积同码凭证** # scripts/size_attestation.json——这样「每次 @build 后跑 --update」这条**既有 # 纪律**同时喂饱那条无需 Keil 也能判的强制门禁(check_size_gate --ci / # --local,即 ci_local 的 L0c 步与 CI 的体积门禁步),不新增人工步骤。 @@ -411,7 +411,7 @@ def main(argv=None): except Exception: pass ap = argparse.ArgumentParser( - description="RAM 预算门禁 + 趋势台账(N-27 前半 / 报告 §5.5 三件套)") + description="RAM 预算门禁 + 趋势台账(缩堆迁 CCM 暂缓项的台账/门禁 / 报告 §5.5 三件套)") g = ap.add_mutually_exclusive_group() g.add_argument("--gate", action="store_true", help="92% 阈值门禁(pre-commit 入口)") g.add_argument("--write", action="store_true", help="同步台账受管区") diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index a2d5e286..76bd9fa1 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -8,3 +8,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-10-23 213676 18860 1188 131500 232536 22.2 75264 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 2026-10-24 213780 18860 1204 131852 232640 22.2 75632 57.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736 2026-10-25 224860 24640 1332 135100 249500 23.8 79008 60.3 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736 +2026-10-26 224860 24640 1332 135100 249500 23.8 79008 60.3 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6770;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4128;sys_log_mcu.o=3944;default_task.o=2736 diff --git a/scripts/rename_ids.py b/scripts/rename_ids.py index a07e223d..1dba21e7 100644 --- a/scripts/rename_ids.py +++ b/scripts/rename_ids.py @@ -8,9 +8,9 @@ 2. 改完必须自动登记 `scripts/id_aliases.json`(旧号 → 新号),否则历史提交正文与 未改写的文档里的旧号会集体变成"悬空引用"(§8 校验 3)——别名表是改名的**前置条件**, 不是可选项。 - 3. 同一个裸形字面量在不同文件里可能是**不同族**(实测 `S3` 既是代码规范条款、又是 + 3. 同一个裸形字面量在不同文件里可能是**不同族**(实测同一裸形既是代码规范条款、又是 UI 像素回归状态、又是 2026-10-02 复审专项条目)→ **改名与别名都必须限定作用域**, - 全局替换会制造新的撞号(正是 §1.2 记录的 N-31/N-32 类事故)。 + 全局替换会制造新的撞号(正是 §1.2 记录的「销项号被重新取走」那类事故)。 用法: & '.venv\\Scripts\\python.exe' scripts/rename_ids.py --map .dsh/rename_batch1.json # 干跑(默认) @@ -89,7 +89,7 @@ def match_files(patterns, candidates): def render(template, groups): """按模板渲染新号。**只有模板带格式说明符时**才把数字组转 int—— - 否则 `TC-PROTSET-011` 会被渲染成 `TC-PROTSET-11`(丢前导零,实测差点毁掉整批补零形态)。""" + 否则 `TC-PROTSET-011` 会被渲染成丢前导零的形态(实测差点毁掉整批补零)。""" vals = {} for k, v in groups.items(): if v is None: @@ -153,7 +153,7 @@ def merge_aliases(new_aliases, batch): """把本批 `旧号 → 新号` 并进 id_aliases.json,带 scope(同字面量多义的关键)。""" doc = json.loads(ALIASES_FILE.read_text(encoding="utf-8")) table = doc.setdefault("aliases", {}) - # 已**完全限定**的字面量(如 `TC-PROTSET-011`)可以全局认别名;裸形(`S3`)必须带 scope + # 已**完全限定**的字面量(如 `TC-PROTSET-011`)可以全局认别名;裸形必须带 scope scope = [] if batch.get("global_alias") else [norm(x) for x in batch.get("files", [])] added = 0 for old, news in sorted(new_aliases.items()): diff --git a/scripts/size_attestation.json b/scripts/size_attestation.json index fa1b8f36..46fbb33a 100644 --- a/scripts/size_attestation.json +++ b/scripts/size_attestation.json @@ -1,15 +1,15 @@ { - "built_at": "2026-10-25T18:46:17+08:00", - "fingerprint": "101b6500c00ec55bba062c4141f5942479e329bba1b839be5d25be287751c228", + "built_at": "2026-10-26T12:21:03+08:00", + "fingerprint": "27086d0cd632d2443ff06d519183e4b9f748d08582c7e4f61b26b3d81ee0d312", "fingerprint_files": 1422, "fingerprint_source": "git-index", "gated_regions": [ "RW_IRAM1", "RW_CCM" ], - "git_head": "9d90e00d52bba74834c80f8fef483dc00e6f7507", + "git_head": "7635480e06f853fd27f1e48e059d71e59dca2165", "map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map", - "map_mtime": 1792925151, + "map_mtime": 1792982281, "pct": { "ER_IROM1": 23.79, "RW_CCM": 87.89, diff --git a/scripts/v5_log_gap_report.py b/scripts/v5_log_gap_report.py index 93c1569b..a630d1bd 100644 --- a/scripts/v5_log_gap_report.py +++ b/scripts/v5_log_gap_report.py @@ -5,7 +5,7 @@ 判据口径出处(两处;改口径先改文档、再同步本脚本,自检断言会拦漂移): - 「无源 vs 真停滞」判别:docs/子系统设计/控制台单写者日志设计说明.md 文末附录 - (N-2 × S8 合并评估,2026-10-25)——静默段贴边(前一行 / 结束后一窗)命中 + (日志静默观察项与单写者化的合并评估,2026-10-25)——静默段贴边(前一行 / 结束后一窗)命中 「丢弃账 / 故障账」留痕行 = 真停滞嫌疑;零命中 = 无源(系统安静,正常形态)。 - 开机良性豁免:docs/测试说明/实机测试实施指南.md §9 第 8 条——开机屏障期 (reset cause / armed / netTask: started 等锚点后一窗)偶见的单抽样 MISS start diff --git a/simulator/lv_conf.h b/simulator/lv_conf.h index a1b5f60c..648bc782 100644 --- a/simulator/lv_conf.h +++ b/simulator/lv_conf.h @@ -229,10 +229,10 @@ #define LV_USE_FFMPEG 0 #define LV_USE_GIF 0 /* 注:9.3 无 LV_USE_GLTF / LV_USE_GSTREAMER / LV_USE_ARCLABEL(更新版本残留),已删 */ -/* 2026-10-11(F-2/F-3 对齐 MCU 端 Lib/lv_conf.h):本项目 UI 全部自绘, +/* 2026-10-11(对齐 MCU 端 Lib/lv_conf.h):本项目 UI 全部自绘, * 三处代码(Drivers/BSP/UI、App、simulator)零控件构造函数、零 flex/grid 调用。 * 关掉后模拟器与固件跑的是同一套特性子集,golden 回归才能真正验证 MCU 配置。 - * 若将来引入控件型页面(U2 原型库),两端同步逐项打开并复跑 ui-verify。 */ + * 若将来引入控件型原型页面,两端同步逐项打开并复跑 ui-verify。 */ #define LV_USE_IMAGE 0 #define LV_USE_IMAGEBUTTON 0 #define LV_USE_IME_PINYIN 0 diff --git a/simulator/main.c b/simulator/main.c index fc894a9b..04184e34 100644 --- a/simulator/main.c +++ b/simulator/main.c @@ -499,7 +499,7 @@ static int run_cfgtest_scenarios(void) { } printf("[cfgtest] OK surgical save (comment kept, value intact)\n"); - /* E-01 场景 1:伪造超长文件(> CFG_FILE_MAX_LEN)→ poll 必须判损坏 + /* 场景 1:伪造超长文件(> CFG_FILE_MAX_LEN)→ poll 必须判损坏 * (与 load/MCU 同一契约):RAM 回工厂默认 + 文件重建为出厂模板, * 而非截断解析残留 10.0.0.1(修复前死码走的是截断分支) */ { @@ -531,7 +531,7 @@ static int run_cfgtest_scenarios(void) { } printf("[cfgtest] OK oversized file rebuilt to factory template\n"); - /* E-01 场景 2:save 拒写"雷"文件——伪造合法界内纯注释文件(4000B,无 + /* 场景 2:save 拒写"雷"文件——伪造合法界内纯注释文件(4000B,无 * [network] 段),外科手术保存文末补写受管段 → 输出必然超 * CFG_FILE_MAX_LEN:必须拒绝(net_cfg_save 返 -1)且原文件不动; * 修复前此处会成功写出 ~4200B 超长文件 = 下次开机被销毁的雷 */ @@ -565,7 +565,7 @@ static int run_cfgtest_scenarios(void) { * 函数功能:配置自检外壳——保证不在工作目录留下伪造的配置档 * 入口参数:无 * 返回值:run_cfgtest_scenarios 的判定 int - * 说明:E-01 场景 2 会写一个 4000 B 纯注释"雷"文件,并断言"拒写后原文件不动" + * 说明:场景 2 会写一个 4000 B 纯注释"雷"文件,并断言"拒写后原文件不动" * (size == 4000)——**因此连正常通过路径也会把它留在原地**。该文件是 * gitignore 的本地产物,但之后每一次 UI 回归都会加载它:外科手术保存要在 * 4000 B 注释后补写受管段,输出必然超 CFG_FILE_MAX_LEN → 引擎按设计拒写 diff --git a/simulator/sim/cfg_file_store_sim.c b/simulator/sim/cfg_file_store_sim.c index 8771b9b7..1d145044 100644 --- a/simulator/sim/cfg_file_store_sim.c +++ b/simulator/sim/cfg_file_store_sim.c @@ -9,7 +9,7 @@ * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本 * v1.1 2026-09-18 王建锋 增 cfg_store_alloc / cfg_store_free / cfg_store_heap_free - * (宿主 CRT 堆实现,对齐 MCU 端 FreeRTOS 堆语义,P0-1) + * (宿主 CRT 堆实现,对齐 MCU 端 FreeRTOS 堆语义) */ /* 头文件包含区 */ diff --git a/simulator/sim/net_socket_sim.c b/simulator/sim/net_socket_sim.c index f48d712e..906a8e6b 100644 --- a/simulator/sim/net_socket_sim.c +++ b/simulator/sim/net_socket_sim.c @@ -719,7 +719,7 @@ int net_close(int sockfd) { } /* - * 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——E-02 收口) + * 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——头阻塞收口) * 入口参数:sockfd - socket 描述符 * 出口参数:无 * 返回值:0 成功(含幂等:无活动会话),-1 失败 diff --git a/simulator/tests/check_content_pages.py b/simulator/tests/check_content_pages.py index 57f5c9ca..88999999 100644 --- a/simulator/tests/check_content_pages.py +++ b/simulator/tests/check_content_pages.py @@ -287,7 +287,7 @@ STRIDE = W // 8 # I1:20 字节/行 # 页面结构变更导致漂移时,先确认新值合理再更新此常量(内存回归门禁)。 # 2026-09-14: 4864 → 5520(+656B)= run_headless 新增 ui_standby_init():待机模块 # 常驻屏 s_scr + 容器 s_cont 两个 lv_obj,随菜单基线同移(见 check_menu_rows.py)。 -# 2026-10-11: 5520 → 5448(−72B)= 裁剪 LVGL 控件类(F-2),随菜单基线 5440→5368 同移; +# 2026-10-11: 5520 → 5448(−72B)= 裁剪 LVGL 控件类,随菜单基线 5440→5368 同移; # **一次性残留不变,仍是 80 B**(5448 − 5368),说明泄漏特征未变,只是每对象样式表更小。 POOL_AFTER_ROUNDTRIP = 5448 @@ -296,7 +296,7 @@ def run_sim(seq, out, cwd): """在指定工作目录里跑一次无头截图。 模拟器按**工作目录**读写 `sim_config.ini`:目录里残留一份配置(尤其 - `--cfgtest` E-01 场景 2 伪造的 4000 B 纯注释"雷"文件)会让引擎按设计 + `--cfgtest` 场景 2 伪造的 4000 B 纯注释"雷"文件)会让引擎按设计 拒绝保存 → 网口设置页保存行显示 ERR → 与"工厂默认态"的黄金参考对不上。 因此一律在空临时目录里跑,回到"无配置文件 = 工厂默认"这一确定态。 """ diff --git a/simulator/tests/check_menu_rows.py b/simulator/tests/check_menu_rows.py index cc118137..6aea74ea 100644 --- a/simulator/tests/check_menu_rows.py +++ b/simulator/tests/check_menu_rows.py @@ -57,7 +57,7 @@ ROOT_X0, ROOT_Y0, ROOT_W, ROOT_H = 0, 44, 66, 107 # 先确认新值合理再更新此常量(内存回归门禁,见 docs/UI设计/UI_自动化测试流程.md) # 2026-09-14: 4784 → 5440(+656B)= run_headless 新增 ui_standby_init():待机模块 # 常驻屏 s_scr + 容器 s_cont 两个 lv_obj(先于池测量创建),属有意结构变更。 -# 2026-10-11: 5440 → 5368(−72B)= 裁剪 LVGL 控件类(F-2:label/button/buttonmatrix/ +# 2026-10-11: 5440 → 5368(−72B)= 裁剪 LVGL 控件类(label/button/buttonmatrix/ # bar/textarea 全关,simulator/lv_conf.h 与 Lib/lv_conf.h 对齐为同一特性子集)。 # 像素逐字节未变(11 页黄金参考在**裁剪前**生成、裁剪后全部一致);差值来自 # lv_theme_apply 不再为已不存在的控件类登记样式条目 → 每对象样式表更小。更优。 diff --git a/test/README.md b/test/README.md index 0a71f238..dc9f63de 100644 --- a/test/README.md +++ b/test/README.md @@ -26,7 +26,7 @@ & '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F & '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划 -# 只编译不烧录(S6;不需要 MCU / 串口 / ST-LINK,验证 4 套件可编译性) +# 只编译不烧录(不需要 MCU / 串口 / ST-LINK,验证 4 套件可编译性) & '.venv\Scripts\python.exe' test/run_all_suites.py --check-build ``` diff --git a/test/board/README.md b/test/board/README.md index f861bd43..223d186e 100644 --- a/test/board/README.md +++ b/test/board/README.md @@ -8,7 +8,7 @@ | 文件 | 测试对象 / 作用 | |---|---| | `test_config.c/h` | **测试任务配置**:`TEST_SUITE_*` 套件选择开关(互斥)+ `ENABLE_DESTRUCTIVE_TESTS` | -| `test_common.h` | **公共断言内核**:`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`;各套件用一行宏绑定自身统计变量(T-4) | +| `test_common.h` | **公共断言内核**:`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`;各套件用一行宏绑定自身统计变量 | | `ch395f_test_task.c/h` + `ch395f_test.h` | CH395F 以太网(硬件层 Phase 1~5 / NET 层 Phase 6~11) | | `gd5f_test_task.c/h` + `gd5f_test.h` | GD5F2GQ5UE SPI NAND 存储 | | `sd2506_test_task.c/h` + `sd2506_test.h` | SD2506 RTC | diff --git a/test/board/ch395f_reuse_bench.c b/test/board/ch395f_reuse_bench.c index 0ca418ec..972e226b 100644 --- a/test/board/ch395f_reuse_bench.c +++ b/test/board/ch395f_reuse_bench.c @@ -1,6 +1,6 @@ /** * @file ch395f_reuse_bench.c - * @brief N-30 芯片级 Socket 复用时序台架(裸机,内核前运行,OS/net/FTP 全剥离) + * @brief relisten 沉降段原语化受阻的芯片级 Socket 复用时序台架(裸机,内核前运行,OS/net/FTP 全剥离) * @details 直接调用 ch395f_* 命令,循环执行受控时序矩阵(延迟/关闭次序/载荷/Socket 号), * 每步打印芯片状态与中断寄存器,定位"关→开→监听→连接"复用时序中 * 芯片行为的确切规律。配套主机脚本 test/host/ch395f_reuse_host.py。 @@ -10,7 +10,7 @@ * 运行方法:在实验分支(如 n30-chip-bench)把本 .c 注册进 uvprojx Test 组、 * Src/main.c 的 USER CODE 2 区在 app_main_init() 之前调 ch395f_reuse_bench() * (内核前裸机循环,永不返回,断电退出),烧录后跑主机脚本对照芯片日志。 - * 旧 N-30 定案矩阵(对照 vs "CONNECT 事件未收到捕获自 accept"修复组)保留在 s_vars[]/ + * 早先那轮定案矩阵(对照 vs "CONNECT 事件未收到捕获自 accept"修复组)保留在 s_vars[]/ * bench_round(),由 s_bench_legacy=0 停泊不运行。 * * 驱动原语前置实验:同一固件合并跑 E2/E3/E4—— @@ -395,7 +395,7 @@ static void bench_round(unsigned idx) { * 驱动原语前置实验区:E2 / E3 / E4 一次固件合并跑 * ===================================================================== */ -/* 旧 N-30 定案矩阵停泊开关:0 = 不运行(保留代码备查,勿删) */ +/* 早先那轮定案矩阵的停泊开关:0 = 不运行(保留代码备查,勿删) */ static const int s_bench_legacy = 0; /* --- DWT 周期计数器:168 MHz,1 cycle ~= 5.95 ns(不依赖 HAL 时基/中断) --- */ @@ -825,7 +825,7 @@ void ch395f_reuse_bench(void) { if (s_bench_legacy != 0) { /* 丢中断复核实验(2026-10-14):固定 8 周期 × 6 变体后返回正常启动 * (主机脚本 ch395f_reuse_host.py --mode legacy --cycles 8 同步收工) */ - DBG_INFO("BENCH legacy N-30 matrix: 8 cycles x %u variants", (unsigned)BENCH_N_VARS); + DBG_INFO("BENCH legacy matrix: 8 cycles x %u variants", (unsigned)BENCH_N_VARS); unsigned idx = 0; for (unsigned cyc = 0U; cyc < 8U; cyc++) { for (unsigned iv = 0U; iv < (unsigned)BENCH_N_VARS; iv++) { diff --git a/test/board/ch395f_reuse_bench.h b/test/board/ch395f_reuse_bench.h index 09694d65..811338fa 100644 --- a/test/board/ch395f_reuse_bench.h +++ b/test/board/ch395f_reuse_bench.h @@ -1,5 +1,5 @@ /* - * ch395f_reuse_bench.h - N-30 芯片级 Socket 复用时序台架(裸机实验,临时分支专用) + * ch395f_reuse_bench.h - relisten 沉降段原语化受阻的芯片级 Socket 复用时序台架(裸机实验,临时分支专用) */ #ifndef CH395F_REUSE_BENCH_H diff --git a/test/board/ch395f_test.h b/test/board/ch395f_test.h index dd47dd71..4b82e177 100644 --- a/test/board/ch395f_test.h +++ b/test/board/ch395f_test.h @@ -86,7 +86,7 @@ extern "C" { * test/run_all_suites.py 的 _rewrite_config 只改 TEST_SUITE_* 行(那些行没有行尾 * 跨行注释)所以一直没炸;本文件是"人手工切阶段"的入口,必须自身安全。 * - * 链接余量构建实测(条目属主 docs/任务规划/测试与架构专项.md P1-10;2026-10-25, + * 链接余量构建实测(条目属主 docs/任务规划/测试与架构专项.md;2026-10-25, * RW_IRAM1 = 128 KB 的 .sct 区,92% 红线 = 120,586 B): * 配置(TEST_SUITE_CH395F + 阶段) Code ZI RW_IRAM1 距红线余量 * 产品基线(阶段全关) 214,104 131,852 75,632 57.7% 44,954 B diff --git a/test/board/ch395f_test_task.c b/test/board/ch395f_test_task.c index 037af6bf..385f397e 100644 --- a/test/board/ch395f_test_task.c +++ b/test/board/ch395f_test_task.c @@ -46,7 +46,7 @@ test_stats_t g_test_stats; * 测试目的:确认 CH395F 与 MCU 之间的 SPI 通信链路正常,所有底层命令 * 能够正确发送并得到预期回复。这是其他所有 Phase 的前提条件。 * - * 共 9 项测试(P1-01 ~ P1-09),逐项执行,汇总报告。 + * 共 9 项测试,逐项执行,汇总报告。 * =================================================================== */ /* 解析点分十进制 IP 字符串为数组(避免引入 sscanf 库)。 @@ -79,12 +79,12 @@ static void phase1_run(void) { /* 全局中断寄存器读即清,唯一读者 = net_poll(F-NET-7;用户裁定 2026-10-13: * 测试代码同样禁自行读取)。net_stack_bring_up() 的 set_phy 强制重协商会 - * 锁存 GINT_STAT_PHY_CHANGE(0x0004)——由 P1-06 内部双读自清,不在此预清。 */ + * 锁存 GINT_STAT_PHY_CHANGE(0x0004)——由全局中断状态项内部双读自清,不在此预清。 */ DBG_INFO("=== CH395F Phase 1 Tests ==="); - DBG_INFO("--- 9 test items (P1-01 ~ P1-09) ---"); + DBG_INFO("--- 9 test items ---"); - /* ---- P1-01: ch395f_check_exist() ---- + /* ---- ch395f_check_exist() ---- * 原理:SPI 写入命令 0x06 (CMD_CHECK_EXIST) 后跟测试字节 0x57。 * CH395F 硬件将测试字节按位取反后返回,期望收到 ~0x57 = 0xA8。 * 通过:函数返回 CH395F_ERR_SUCCESS(0x00)= SPI 回显取反匹配。 @@ -95,15 +95,15 @@ static void phase1_run(void) { */ { ch395f_status_t ret = ch395f_check_exist(); - DBG_INFO(" [P1-01] ch395f_check_exist()"); + DBG_INFO(" ch395f_check_exist()"); DBG_INFO(" SPI write: CMD=0x06, DATA=0x57"); DBG_INFO(" expect: ~0x57 = 0xA8"); DBG_INFO(" actual: 0x%02X", (uint8_t)ret); TEST_CHECK(ret == CH395F_ERR_SUCCESS, - "P1-01 check_exist = 0x%02X (expect 0x00=~0x57=0xA8 match)", (uint8_t)ret); + "check_exist = 0x%02X (expect 0x00=~0x57=0xA8 match)", (uint8_t)ret); } - /* ---- P1-02: ch395f_get_version() ---- + /* ---- ch395f_get_version() ---- * 原理:SPI 写入命令 0x27 (CMD_GET_IC_VER),读取芯片版本字节。 * 通过:版本值 == 0x4A(CH395F 硬件版本)。 * 失败:版本值 ≠ 0x4A(芯片型号不匹配、未正确初始化或损坏)。 @@ -111,14 +111,14 @@ static void phase1_run(void) { */ { uint8_t ver = (uint8_t)ch395f_get_version(); - DBG_INFO(" [P1-02] ch395f_get_version()"); + DBG_INFO(" ch395f_get_version()"); DBG_INFO(" expect: 0x4A"); DBG_INFO(" actual: 0x%02X", ver); TEST_CHECK(ver == 0x4A, - "P1-02 version = 0x%02X (expect 0x4A)", ver); + "version = 0x%02X (expect 0x4A)", ver); } - /* ---- P1-03: 设置 IP → 回读验证 ---- + /* ---- 设置 IP → 回读验证 ---- * 原理:ch395f_set_ip_addr() 将 IP 写入 CH395F 内部寄存器, * 然后通过 ch395f_get_ip_inf() 回读完整网络信息(IP+GW+MASK)。 * 前 4 字节为 IP 地址。 @@ -132,15 +132,15 @@ static void phase1_run(void) { ch395f_get_ip_inf(ip_info); int32_t match = (ip_info[0] == 192 && ip_info[1] == 168 && ip_info[2] == 1 && ip_info[3] == 100); - DBG_INFO(" [P1-03] ch395f_set_ip_addr(192.168.1.100)"); + DBG_INFO(" ch395f_set_ip_addr(192.168.1.100)"); DBG_INFO(" expect: IP[0..3] = 192.168.1.100"); DBG_INFO(" actual: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); - TEST_CHECK(match, "P1-03 IP = %d.%d.%d.%d (expect 192.168.1.100)", + TEST_CHECK(match, "IP = %d.%d.%d.%d (expect 192.168.1.100)", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); } - /* ---- P1-04: 设置网关 → 回读验证 ---- - * 原理:同 P1-03,通过 ch395f_set_gwip_addr() 写入,ch395f_get_ip_inf() + /* ---- 设置网关 → 回读验证 ---- + * 原理:同「设置 IP → 回读」项,通过 ch395f_set_gwip_addr() 写入,ch395f_get_ip_inf() * 回读的第 5-8 字节为网关地址。 * 通过:回读 GW[0..3] == 192.168.1.1。 * 失败:任意字节不匹配。 @@ -152,15 +152,15 @@ static void phase1_run(void) { ch395f_get_ip_inf(ip_info); int32_t match = (ip_info[4] == 192 && ip_info[5] == 168 && ip_info[6] == 1 && ip_info[7] == 1); - DBG_INFO(" [P1-04] ch395f_set_gwip_addr(192.168.1.1)"); + DBG_INFO(" ch395f_set_gwip_addr(192.168.1.1)"); DBG_INFO(" expect: GW[0..3] = 192.168.1.1"); DBG_INFO(" actual: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); - TEST_CHECK(match, "P1-04 GW = %d.%d.%d.%d (expect 192.168.1.1)", + TEST_CHECK(match, "GW = %d.%d.%d.%d (expect 192.168.1.1)", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); } - /* ---- P1-05: 设置掩码 → 回读验证 ---- - * 原理:同 P1-03,ch395f_set_mask_addr() 写入,ch395f_get_ip_inf() + /* ---- 设置掩码 → 回读验证 ---- + * 原理:同「设置 IP → 回读」项,ch395f_set_mask_addr() 写入,ch395f_get_ip_inf() * 回读的第 9-12 字节为子网掩码。 * 通过:回读 MASK[0..3] == 255.255.255.0。 * 失败:任意字节不匹配。 @@ -172,14 +172,14 @@ static void phase1_run(void) { ch395f_get_ip_inf(ip_info); int32_t match = (ip_info[8] == 255 && ip_info[9] == 255 && ip_info[10] == 255 && ip_info[11] == 0); - DBG_INFO(" [P1-05] ch395f_set_mask_addr(255.255.255.0)"); + DBG_INFO(" ch395f_set_mask_addr(255.255.255.0)"); DBG_INFO(" expect: MASK[0..3] = 255.255.255.0"); DBG_INFO(" actual: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); - TEST_CHECK(match, "P1-05 MASK = %d.%d.%d.%d (expect 255.255.255.0)", + TEST_CHECK(match, "MASK = %d.%d.%d.%d (expect 255.255.255.0)", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); } - /* ---- P1-06: ch395f_get_glob_int_status_all()(16 位版) ---- + /* ---- ch395f_get_glob_int_status_all()(16 位版) ---- * 全库唯一允许 net_poll 之外读全局寄存器的位置——被测对象就是该 API 本身 * (硬件层模式独占上下文,无并发消费者)。读即清语义 → 先读一次自清 * (吸收 bring_up 锁存的 PHY_CHANGE),短暂延时后第二次读为断言值。 @@ -194,14 +194,14 @@ static void phase1_run(void) { (void)ch395f_get_glob_int_status_all(); /* 自清读(被测 API 自身,见上) */ HAL_Delay(10); uint16_t gint_all = ch395f_get_glob_int_status_all(); - DBG_INFO(" [P1-06] ch395f_get_glob_int_status_all()"); + DBG_INFO(" ch395f_get_glob_int_status_all()"); DBG_INFO(" expect: 0x0000"); DBG_INFO(" actual: 0x%04X", gint_all); TEST_CHECK(gint_all == 0x0000, - "P1-06 glob_int_status_all = 0x%04X (expect 0x0000)", gint_all); + "glob_int_status_all = 0x%04X (expect 0x0000)", gint_all); } - /* ---- P1-07: 打开→关闭 Socket 0 → 验证 CMD_STATUS ---- + /* ---- 打开→关闭 Socket 0 → 验证 CMD_STATUS ---- * 原理:连续执行 open_socket(0) 和 close_socket(0),然后读取 * 命令状态寄存器,确认 Socket 状态机正常流转。 * 通过:CMD_STATUS == 0x00(CH395F_ERR_SUCCESS),表示两次命令均成功执行。 @@ -211,16 +211,16 @@ static void phase1_run(void) { uint8_t open_ret = ch395f_open_socket(0); uint8_t close_ret = ch395f_close_socket(0); uint8_t cmd_st = ch395f_get_cmd_status(); - DBG_INFO(" [P1-07] ch395f_open_socket(0) -> ch395f_close_socket(0)"); + DBG_INFO(" ch395f_open_socket(0) -> ch395f_close_socket(0)"); DBG_INFO(" expect: CMD_STATUS == 0x00 (CH395F_ERR_SUCCESS)"); DBG_INFO(" actual: open=%d, close=%d, CMD_STATUS=0x%02X", open_ret, close_ret, cmd_st); TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, - "P1-07 open=%d close=%d cmd=0x%02X (expect 0x00)", + "open=%d close=%d cmd=0x%02X (expect 0x00)", open_ret, close_ret, cmd_st); } - /* ---- P1-08: 设置 ARP 参数 ---- + /* ---- 设置 ARP 参数 ---- * 原理:ch395f_set_arp(period, retry) 配置 ARP 请求周期和重试次数。 * period = 10(×100ms = 1s)、retry = 5(最多 5 次重试)。 * 通过:CMD_STATUS == 0x00(参数写入成功)。 @@ -229,14 +229,14 @@ static void phase1_run(void) { { ch395f_set_arp(10, 5); uint8_t cmd_st = ch395f_get_cmd_status(); - DBG_INFO(" [P1-08] ch395f_set_arp(period=10, retry=5)"); + DBG_INFO(" ch395f_set_arp(period=10, retry=5)"); DBG_INFO(" expect: CMD_STATUS == 0x00"); DBG_INFO(" actual: CMD_STATUS = 0x%02X", cmd_st); TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, - "P1-08 set_arp CMD_STATUS = 0x%02X (expect 0x00)", cmd_st); + "set_arp CMD_STATUS = 0x%02X (expect 0x00)", cmd_st); } - /* ---- P1-09: 设置 TTL ---- + /* ---- 设置 TTL ---- * 原理:ch395f_set_ttl(sock, ttl) 设置指定 Socket 的 IP 报 TTL 值。 * sock = 0(Socket 0)、ttl = 64(默认值)。 * 通过:CMD_STATUS == 0x00。 @@ -245,11 +245,11 @@ static void phase1_run(void) { { ch395f_set_ttl(0, 64); uint8_t cmd_st = ch395f_get_cmd_status(); - DBG_INFO(" [P1-09] ch395f_set_ttl(sock=0, ttl=64)"); + DBG_INFO(" ch395f_set_ttl(sock=0, ttl=64)"); DBG_INFO(" expect: CMD_STATUS == 0x00"); DBG_INFO(" actual: CMD_STATUS = 0x%02X", cmd_st); TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, - "P1-09 set_ttl CMD_STATUS = 0x%02X (expect 0x00)", cmd_st); + "set_ttl CMD_STATUS = 0x%02X (expect 0x00)", cmd_st); } TEST_REPORT("Phase 1"); @@ -1029,7 +1029,7 @@ static void phase2_run(void) * * 前置条件:PC 运行 udp_client 发包到 192.168.1.100:60000 * 会话结构:累计 PHASE3_TOTAL_ROUNDS(60) 个有效数据包后关闭 Socket - * 第 1~30 轮 = TC-301(PC 发 64B);第 31~60 轮 = TC-302(1B/64B/200B) + * 第 1~30 轮 = TC-NET-301(PC 发 64B);第 31~60 轮 = TC-NET-302(1B/64B/200B) * "HELLO"(5B) 为 PC 就绪探针:照常回显但不计入轮次 * 通过:60/60 有效轮回显成功。 * 失败:任意轮回显超时或解析错误。 @@ -1037,7 +1037,7 @@ static void phase2_run(void) #ifdef ENABLE_PHASE3_TESTS -/* UDP 有效回显轮数:1~30 = TC-301(64B),31~60 = TC-302(变长)。 +/* UDP 有效回显轮数:1~30 = TC-NET-301(64B),31~60 = TC-NET-302(变长)。 * "HELLO" 探针只回显不计数,与 test/ch395f_socket_test.py udp_client 约定 */ #ifndef PHASE3_TOTAL_ROUNDS #define PHASE3_TOTAL_ROUNDS 60 @@ -1329,7 +1329,7 @@ phase4_end: #define PHASE5_TOTAL_BYTES (256*1024) /* 回传总量 */ #endif #ifndef PHASE5_CHUNK_SIZE -#define PHASE5_CHUNK_SIZE 4093 /* 单块 = socket 默认发送缓冲,DP-04 边界 */ +#define PHASE5_CHUNK_SIZE 4093 /* 单块 = socket 默认发送缓冲,DMA 边界安全 */ #endif #ifndef PHASE5_CONN_TIMEOUT_MS #define PHASE5_CONN_TIMEOUT_MS 15000 @@ -1452,7 +1452,7 @@ static void phase5_run(void) { /* ---- 发送缓冲空闲:生成并写入一块 ---- */ phase5_gen_block(tx, (uint32_t)blk_tx); /* 确定性生成第 blk_tx 块模式(TX/RX 可复现) */ sum_tx += phase5_sum32(tx, PHASE5_CHUNK_SIZE); /* 先累加发送侧总账(与 RX 侧对账用) */ - ch395f_write_send_buf(sock, tx, PHASE5_CHUNK_SIZE); /* 4096B 单次写入(= 发送缓冲上限,DP-04 边界) */ + ch395f_write_send_buf(sock, tx, PHASE5_CHUNK_SIZE); /* 4096B 单次写入(= 发送缓冲上限,DMA 边界安全) */ blk_tx++; /* 该块已交出 */ can_write = 0; /* 进入等待 SENBUF_FREE 状态 */ tx_sw = HAL_GetTick(); /* 记录等待起点(供超时判定) */ @@ -1520,7 +1520,7 @@ static void phase5_run(void) { break; /* 缓冲空,跳出排空循环 */ } if (rlen > PHASE5_CHUNK_SIZE) { - rlen = PHASE5_CHUNK_SIZE; /* 单次读取上限 = 块大小(DP-04 4KB DMA 边界) */ + rlen = PHASE5_CHUNK_SIZE; /* 单次读取上限 = 块大小(DMA 边界安全,4KB) */ } ch395f_read_recv_buf(sock, rx, rlen); /* 一次 SPI 事务读走 rlen 字节 */ { diff --git a/test/board/gd5f_test_task.c b/test/board/gd5f_test_task.c index d23a2290..be8de5e8 100644 --- a/test/board/gd5f_test_task.c +++ b/test/board/gd5f_test_task.c @@ -822,7 +822,7 @@ static void gd5f_phase11(void) blk = gd5f_find_run(1); if (blk >= GD5F_TOTAL_BLOCKS) { - GD5F_TEST_CHECK(0, "TC-GD5F-1100 no usable block for repro"); + GD5F_TEST_CHECK(0, "no usable block for repro"); GD5F_TEST_REPORT("GD5F Phase 11"); return; } @@ -873,7 +873,7 @@ static void gd5f_phase11(void) round_bad = 1; if (first_bad < 0) { first_bad = round; - DBG_INFO("TC-GD5F-1101 r=%d p=%u IMM mismatch head=%02X %02X %02X %02X " + DBG_INFO("r=%d p=%u IMM mismatch head=%02X %02X %02X %02X " "tail=%02X %02X %02X %02X", round, (unsigned)s_p11_probe[pi], s_rbuf[0], s_rbuf[1], s_rbuf[2], s_rbuf[3], @@ -913,18 +913,18 @@ static void gd5f_phase11(void) imm_bad++; } if (round == 1 || (round & 3) == 0) { - DBG_INFO("TC-GD5F-1101 progress r=%d/%d storm_fail=%d imm_fail=%d ctl_fail=%d", + DBG_INFO("progress r=%d/%d storm_fail=%d imm_fail=%d ctl_fail=%d", round, GD5F_P11_ROUNDS, storm_fail, imm_fail, ctl_fail); } } GD5F_TEST_CHECK(storm_fail == 0, - "TC-GD5F-1100 storm ops: erase/program all OK (fail=%d)", storm_fail); + "storm ops: erase/program all OK (fail=%d)", storm_fail); GD5F_TEST_CHECK(imm_bad == 0, - "TC-GD5F-1101 immediate read after storm: %d/%d bad rounds (pages=%d first=%d)", + "immediate read after storm: %d/%d bad rounds (pages=%d first=%d)", imm_bad, GD5F_P11_ROUNDS, imm_fail, first_bad); GD5F_TEST_CHECK(ctl_fail == 0, - "TC-GD5F-1102 delayed(50ms) read: %d/%d bad rounds (pages=%d)", + "delayed(50ms) read: %d/%d bad rounds (pages=%d)", ctl_bad, GD5F_P11_ROUNDS, ctl_fail); GD5F_TEST_REPORT("GD5F Phase 11"); } diff --git a/test/board/net_latency_test.c b/test/board/net_latency_test.c index 7bc730bd..eea7fb57 100644 --- a/test/board/net_latency_test.c +++ b/test/board/net_latency_test.c @@ -447,7 +447,7 @@ void net_latency_phase12_run(void) (void)ch395f_check_exist(); break; default: - /* 例外(同 P1-06):被测对象就是本 API 的 SPI 时延——硬件层 + /* 例外(同 Phase 1 全局中断状态用例):被测对象就是本 API 的 SPI 时延——硬件层 * 独占上下文、无并发消费者;除此之外测试代码禁读全局寄存器 * (读即清,唯一读者 = net_poll,F-NET-7) */ (void)ch395f_get_glob_int_status_all(); @@ -515,7 +515,7 @@ void net_latency_phase12_run(void) * 段定义(说明文档 §1): * L5 queue_rt_recv / queue_rt_send —— 调用方视角 net_recv/net_send * 全程(入队 + netTask 调度 + 处理 + 通知),静默基线档; - * 压力档 = peer --ftp-stress(lftpd 阻塞读占住 netTask → E-02 复现) + * 压力档 = peer --ftp-stress(lftpd 阻塞读占住 netTask → 头阻塞复现) * L6 req2first_mcu —— 请求收齐 → 应答发送完成(MCU 侧,含队列+1 chunk) * L7 fullframe_mcu —— 9B LCDMEM 请求 → 3209B 全帧发送完成 * (netTask 内 4 次分块停止等待,= 生产路径发送段) diff --git a/test/board/net_test_task.c b/test/board/net_test_task.c index 8fc044df..8833305c 100644 --- a/test/board/net_test_task.c +++ b/test/board/net_test_task.c @@ -40,7 +40,7 @@ /* * 缓冲区声明前置于全部 Phase 代码:声明必须早于任何使用点(Phase 6 - * 即首次使用 s_rx_buf)。P1-9 修复:原声明位于文件后段(Phase 8 注释块 + * 即首次使用 s_rx_buf)。历史修复:原声明位于文件后段(Phase 8 注释块 * 之后),phase6_run 使用先于声明 → ARMCC #20 identifier undefined, * 全部 NET 层测试配置不可编译。声明条件按各 Phase 实际用缓冲精确门控: * s_rx_buf —— Phase 6(Echo)/9(Select) 恒用, diff --git a/test/board/storage_test_task.c b/test/board/storage_test_task.c index dc15ba2f..5ec4a2ad 100644 --- a/test/board/storage_test_task.c +++ b/test/board/storage_test_task.c @@ -329,7 +329,7 @@ static void storage_test_name_validation(void) } #ifdef ENABLE_DESTRUCTIVE_TESTS -/* TC-STORAGE-06: 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱) +/* 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱) * 本移植 FA_OPEN_APPEND 不含 FA_WRITE → 不带写位打开 f_write 必返 FR_DENIED; * 追加写必须 FA_WRITE|FA_OPEN_APPEND。另 f_write 簇耗尽返 FR_OK+短写(无错误 * 码),判成败必须校验写回字节数。锁定这两条移植专有语义防回归。 @@ -339,17 +339,17 @@ static void storage_test_open_modes(void) UINT bw; FRESULT r; - DBG_INFO("=== Storage Test: TC-STORAGE-06 Open-mode semantics ==="); + DBG_INFO("=== Storage Test: Open-mode semantics ==="); if (f_mount(&fs, "", 1) != FR_OK) { - STORAGE_TEST_CHECK(0, "TC-STORAGE-06 mount"); + STORAGE_TEST_CHECK(0, "mount"); return; } (void)f_unlink("mode.tmp"); /* 1) 建一个含初始内容的文件 */ r = f_open(&fil, "mode.tmp", FA_CREATE_ALWAYS | FA_WRITE); - STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 create base file"); + STORAGE_TEST_CHECK(r == FR_OK, "create base file"); if (r == FR_OK) { f_write(&fil, "abc", 3, &bw); f_close(&fil); @@ -357,36 +357,36 @@ static void storage_test_open_modes(void) /* 2) FA_OPEN_APPEND(无 FA_WRITE)→ 写必返 FR_DENIED */ r = f_open(&fil, "mode.tmp", FA_OPEN_APPEND); - STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 open append(no write)"); + STORAGE_TEST_CHECK(r == FR_OK, "open append(no write)"); if (r == FR_OK) { FRESULT wr = f_write(&fil, "xyz", 3, &bw); STORAGE_TEST_CHECK(wr == FR_DENIED, - "TC-STORAGE-06 append-no-write must DENY (fr=%d)", (int)wr); + "append-no-write must DENY (fr=%d)", (int)wr); f_close(&fil); } /* 3) FA_WRITE|FA_OPEN_APPEND → 追加成功且字节数正确 */ r = f_open(&fil, "mode.tmp", FA_WRITE | FA_OPEN_APPEND); - STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 open write|append"); + STORAGE_TEST_CHECK(r == FR_OK, "open write|append"); if (r == FR_OK) { (void)f_write(&fil, "xyz", 3, &bw); - STORAGE_TEST_CHECK(bw == 3, "TC-STORAGE-06 append byte-count (%u)", bw); + STORAGE_TEST_CHECK(bw == 3, "append byte-count (%u)", bw); f_close(&fil); } /* 4) 只读打开后写 → 必返 FR_DENIED(读句柄不可写) */ r = f_open(&fil, "mode.tmp", FA_READ); - STORAGE_TEST_CHECK(r == FR_OK, "TC-STORAGE-06 open read-only"); + STORAGE_TEST_CHECK(r == FR_OK, "open read-only"); if (r == FR_OK) { FRESULT wr = f_write(&fil, "q", 1, &bw); STORAGE_TEST_CHECK(wr == FR_DENIED, - "TC-STORAGE-06 read-only write must DENY (fr=%d)", (int)wr); + "read-only write must DENY (fr=%d)", (int)wr); f_close(&fil); } (void)f_unlink("mode.tmp"); } -#endif /* ENABLE_DESTRUCTIVE_TESTS: TC-STORAGE-06 */ +#endif /* ENABLE_DESTRUCTIVE_TESTS: 打开模式语义 */ #endif /* !ENABLE_STORAGE_RECOVERY_TESTS: 共享缓冲 + 正常测试函数 */ #ifdef ENABLE_STORAGE_RECOVERY_TESTS diff --git a/test/board/test_common.h b/test/board/test_common.h index f4e22360..0fde9eb7 100644 --- a/test/board/test_common.h +++ b/test/board/test_common.h @@ -2,7 +2,7 @@ * 模块名称:板端测试套件公共断言宏 * 模块功能:为 test/board/ 下的各测试套件提供**语义统一**的统计结构体与断言/ * 报告宏内核,消除 4 套件(CH395F / STORAGE / GD5F / RTC)之间逐字重复的 - * 宏定义(2026-10-01 测试架构评审 T-4)。 + * 宏定义(2026-10-01 测试架构评审)。 * * 设计取舍:**统计变量仍按套件独立**(各套件持有自己的 `g_xxx_stats`), * 公共宏只提供"内核",由各套件用一行宏把它绑定到自己的统计变量。 diff --git a/test/board/test_config.h b/test/board/test_config.h index d2123a9c..58649008 100644 --- a/test/board/test_config.h +++ b/test/board/test_config.h @@ -47,7 +47,7 @@ extern "C" { #endif /* 派生标记:任一套件被选中时置位。**2026-10-04 起有唯一消费点**——`Src/freertos.c` - * 的 MX_FREERTOS_Init() 用它门控 testTask 的回收(P2-13):产品构建下 CubeMX 生成区 + * 的 MX_FREERTOS_Init() 用它门控 testTask 的回收:产品构建下 CubeMX 生成区 * 无条件创建 testTask,随后本标记未定义 → 立即 vTaskDelete 回收其 8KB CCM 栈 + TCB, * 避免空转占堆。testTask 的调度见 test/board/test_config.c 的 start_test_task(), * 后者**直接按 TEST_SUITE_RTC 等宏分支**,与本标记无关——故"RTC 构建下 testTask 不被 diff --git a/test/host/README.md b/test/host/README.md index a502c9fb..d47e7c83 100644 --- a/test/host/README.md +++ b/test/host/README.md @@ -9,8 +9,8 @@ | 脚本 | 作用 | |---|---| | `run_boot_test.py` | **`@test` 一键闭环**:开串口+清日志 → 烧录 → wait-idle → `verify_log` 校验(退出码 0=PASS) | -| `verify_log.py` | **日志标记校验**:Required / Forbidden 标记 → PASS / FAIL;可选 `--require-suite-summary` 校验套件汇总行(`=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`),抓"没跑到汇总行/断言数为 0"的盲区(T-2) | -| `e02_probe.py` | E-02 专项探针(历史审计编号) | +| `verify_log.py` | **日志标记校验**:Required / Forbidden 标记 → PASS / FAIL;可选 `--require-suite-summary` 校验套件汇总行(`=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`),抓"没跑到汇总行/断言数为 0"的盲区 | +| `e02_probe.py` | 头阻塞专项探针 | ### 网络 / FTP @@ -30,7 +30,7 @@ | `e6_soak.py` | E6 24h 挂机驱动(常态 + 每小时/每 4h E1 压测注入;判据读串口 `U15 probe` 计数) | | `pasv_probe.py` | PASV 数据通道时序探针 | | `ch395f_reuse_host.py` | Socket 复用/中止形态 PC 对端(bench 配套) | -| `ftp_auth_bench.py` | FTP 鉴权/锁定台架腿(N-19) | +| `ftp_auth_bench.py` | FTP 鉴权/锁定台架腿 | | `iec104_master.py` | **IEC 60870-5-104 主站客户端(B6/B7)**:连设备 :2404 跑 STARTDT → 总召(actcon/四组背景扫描/actterm)→ 经 C_SE 两段写临时压低 OC1 定值确定性触发 YX16 变位(COT=3)→ 恢复 → YK11 两段式 → C_RD 读回;**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、定值与压板态跨复位保持(含非默认 7.5 往返);纯标准库,输出纯 ASCII 可直接重定向归档为台架证据(判据 exit 0/1/2) | ### 时间 / 时延 @@ -62,11 +62,11 @@ | `vol_lock/run_unit_test.py` | **卷锁单测**:离线编译真实 `App/fs/vol_lock.c`,33 断言点覆盖创建 / 获取 / 释放契约(**fail-open 不变量**:创建失败仍返回 1 且零阻塞)/ 幂等 / 超时透传 | | `vol_lock/vol_lock_test.c` | 测试单元本体(`#include` 生产源码 + CMSIS 互斥桩) | | `vol_lock/stub/` | 桩头文件(`cmsis_os.h`) | -| `time_sync/run_unit_test.py` | **时间同步单测**:离线编译真实 `App/sync/time_sync.c`,80 断言点覆盖分片喂狗延时 / 监听退避 / 时间包重组(半包·粘连·EOF·真错·预算耗尽·tick 回绕)/ 任务主循环(**N-17 的全部逻辑贡献者**) | +| `time_sync/run_unit_test.py` | **时间同步单测**:离线编译真实 `App/sync/time_sync.c`,80 断言点覆盖分片喂狗延时 / 监听退避 / 时间包重组(半包·粘连·EOF·真错·预算耗尽·tick 回绕)/ 任务主循环(**探针窗口心跳缺口的全部逻辑贡献者**) | | `time_sync/time_sync_test.c` | 测试单元本体(`#include` 生产源码 + 网络 / 事件 / RTOS 可脚本化桩 + `longjmp` 跳出 `for(;;)` 任务循环) | | `time_sync/stub/` | 桩头文件(`FreeRTOS.h` / `task.h` / `cmsis_os2.h` / `net_socket.h` / `sys_clock.h` / `sys_events.h` / `dbg_log.h`) | | `iec104/run_unit_test.py` | **IEC 60870-5-104 协议栈单测(B4)**:离线编译真实 `App/protocol/iec104/` 三模块 + 数据源(prot_set/prot_elm/phasor/sample_ring),**82** 用例覆盖 CP56 往返 / APCI 半帧·粘帧·坏帧 / STARTDT 握手 / 总召分片+RBT / COV 死区 / k=12 窗满 / w=8 触发 S / t1·t2·t3 / N(R) 倒退判故障 / 遥控·定值两段式与 PN / C_CS / 未知类型·地址错误应答 | -| `nand_port/run_unit_test.py` | **dhara NAND 移植层单测(N-21)**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 `Lib/dhara/{journal,map}.c`,以 **RAM 假芯片**驱动,**149** 断言覆盖 is_free 整页扫描(N-12)/ 六个 HAL 入口的越界守卫(N-13)/ 故障注入 / dhara map"写入 → 重新 resume"场景 | +| `nand_port/run_unit_test.py` | **dhara NAND 移植层单测**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 `Lib/dhara/{journal,map}.c`,以 **RAM 假芯片**驱动,**149** 断言覆盖 is_free 整页扫描 / 六个 HAL 入口的越界守卫 / 故障注入 / dhara map"写入 → 重新 resume"场景 | | `nand_port/nand_port_test.c` | 测试单元本体(`#include` 生产源码 + RAM 假芯片;内联一份"只看页首 64 B"的**旧算法对照**,证明断言能咬人) | | `nand_port/stub/` | 桩头文件(`gd5f2gq5ue.h` 芯片几何与 SPI 原语声明 / `dbg_log.h` 日志宏计数) | @@ -86,11 +86,11 @@ 原语打交道——把它们换成一块 RAM 假芯片,整层即可离线跑。而它要防的两类缺陷恰恰是 板端**拿不到观测面**的: -- **N-12**:`is_free` 旧实现只看页首 64 B,"页首 0xFF、后段有数据"的已编程页被误判 +- `is_free` 旧实现只看页首 64 B,"页首 0xFF、后段有数据"的已编程页被误判 空闲 → dhara 往已编程页再编程 = 静默数据丢失。真机要恰好撞上那种页形态才发作, 发作后只表现为"某次复位后丢数据"。假芯片把**双编程做成可数事件**(违规计数), 测试还内联一份旧算法做对照:同一页旧算法答"空闲"、新实现答"非空"。 -- **N-13**:越界页/块号(含 `DHARA_PAGE_NONE` 哨兵)旧会被地址位截断成**真实页** +- 越界页/块号(含 `DHARA_PAGE_NONE` 哨兵)旧会被地址位截断成**真实页** → 静默读写/搬走无辜数据。真机上没有任何路径会主动喂进越界号。假芯片记录**全部 访问次数**,于是"越界 → 返回 -1 + `DHARA_E_CORRUPT_MAP` 且零芯片访问、零标坏块" 可机械断言。 @@ -101,7 +101,7 @@ # @test 一键闭环(推荐,需真机) & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py -# 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0(T-2) +# 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0 & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py ` --required "=== Storage Tests:" --forbidden "ERROR;HardFault;[ERR]" ` --require-suite-summary "Storage Tests" @@ -157,5 +157,5 @@ - `docs/测试说明/ADC输入层单元测试说明.md` — ADC 输入层单测(逐 Rank 钉死映射) - `docs/测试说明/相量层单元测试说明.md` — 相量层单测(傅氏系数 / 双区交换) - `docs/测试说明/卷锁单元测试说明.md` — 卷锁单测(fail-open 不变量) -- `docs/测试说明/时间同步单元测试说明.md` — 时间同步单测(N-17 逻辑贡献者) +- `docs/测试说明/时间同步单元测试说明.md` — 时间同步单测(探针窗口心跳缺口逻辑贡献者) - `docs/测试说明/CH395F_Test_Guide.md`、`FTP_Test_Guide.md` diff --git a/test/host/adc_input/adc_input_test.c b/test/host/adc_input/adc_input_test.c index 8a1a9944..d4d890c6 100644 --- a/test/host/adc_input/adc_input_test.c +++ b/test/host/adc_input/adc_input_test.c @@ -34,7 +34,7 @@ * TC-ADCIN-021..025 取到新帧:返回 0 / 状态码 / 不计失败 / DMA 只挂一次 / seq=1 * TC-ADCIN-026..02B **Rank -> 通道号映射**:UB->CH14(rank9) / UC->CH3(rank4) / * IB->CH6(rank7) / IC->CH1(rank2) / IN->CH2(rank3) / TEMP->CH5(rank6) - * TC-ADCIN-02C stm32_take 不置 TPAFE 有效标志(两来源独立) + * TC-ADCIN-02c stm32_take 不置 TPAFE 有效标志(两来源独立) * TC-ADCIN-080..089 **乒乓双缓冲**:消费侧读"刚完成的半区",即使另一半正被 DMA * 改写也不得读到(撕裂帧防线);两拍交替半区、seq 推进; * 超拍未消费 -> 记一次丢帧(gap),与停摆(err)分开计数 @@ -42,7 +42,7 @@ * (别的句柄不得污染本层 seq / hw_errs) * TC-ADCIN-030..034 HAL_ADC_Start_DMA 失败:init 返回 -2 / 状态码 RC_START / 计数 +1 * TC-ADCIN-035..039 本拍无新帧:返回 RC_STALL / **单拍不计故障**(相位伪影)/ seq 不前进 - * TC-ADCIN-03A..03E 连续两拍无新帧才计停摆;停摆与丢帧分开计数;恢复拍自愈 + * TC-ADCIN-03a..03e 连续两拍无新帧才计停摆;停摆与丢帧分开计数;恢复拍自愈 * TC-ADCIN-070..078 **停摆拍不得沿用上一拍旧值**(2026-10-10 补,真机实测发现的 * 正确性缺陷;DMA 版改由"无新帧"触发同一路径):先成功一拍让 * STM32 侧进入有效态,再注入无新帧拍 -> adc_input_raw() 必须返 -2; @@ -220,10 +220,10 @@ static void tc_init(void) { CHECK(adc_input_init() == 0, "TC-ADCIN-003 adc_input_init returns 0 (mapping self-check passed)"); CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN, "TC-ADCIN-004 after init the STM32 status code = NOT_RUN"); - CHECK(s_start_dma_calls == 1, "TC-ADCIN-003B init arms DMA exactly once"); - CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-ADCIN-003C DMA length = 2 x 9 (ping-pong double buffer)"); - CHECK(s_dma_buf != NULL, "TC-ADCIN-003D the DMA destination buffer address was handed down"); - CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-005B after init the DMA completion counter = 0"); + CHECK(s_start_dma_calls == 1, "TC-ADCIN-003b init arms DMA exactly once"); + CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-ADCIN-003c DMA length = 2 x 9 (ping-pong double buffer)"); + CHECK(s_dma_buf != NULL, "TC-ADCIN-003d the DMA destination buffer address was handed down"); + CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-005b after init the DMA completion counter = 0"); CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-005 after init the STM32 failure counter is zeroed"); CHECK((adc_input_raw(ADC_SEM_UA, &v) == -2) && (adc_input_raw(ADC_SEM_UB, &v) == -2), "TC-ADCIN-006 after init neither TPAFE nor STM32 side holds valid data"); @@ -263,7 +263,7 @@ static void tc_desc(void) { "TC-ADCIN-018 TEMP -> STM32 CH5"); CHECK(adc_input_desc(ADC_SEM_COUNT) == NULL, "TC-ADCIN-019 out of range (ADC_SEM_COUNT) -> NULL"); - CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01A negative index -> NULL"); + CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01a negative index -> NULL"); } /* ===================== TC-ADCIN-020:map_check ===================== */ @@ -296,10 +296,10 @@ static void tc_take_ok(void) { CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-ADCIN-027 UC reads CH3(rank4)=103"); CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-ADCIN-028 IB reads CH6(rank7)=106"); CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-ADCIN-029 IC reads CH1(rank2)=101"); - CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-ADCIN-02A IN reads CH2(rank3)=102"); - CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-ADCIN-02B TEMP reads CH5(rank6)=105"); + CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-ADCIN-02a IN reads CH2(rank3)=102"); + CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-ADCIN-02b TEMP reads CH5(rank6)=105"); - CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-02C stm32_take does not set the TPAFE valid flag"); + CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-02c stm32_take does not set the TPAFE valid flag"); } /* ============ TC-ADCIN-080..085:乒乓双缓冲(读刚完成半区,不读正在写的半区) ============ @@ -316,25 +316,25 @@ static void tc_dma_pingpong(void) { dma_fill(1u, 900u); /* 模拟 DMA 正在写后半区(值 900 系) */ CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-080 first round fetch succeeded"); CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), - "TC-ADCIN-081 reads the just completed half (108), not the half being written"); + "reads the just completed half (108), not the half being written"); dma_beat(200); /* 第 2 帧 -> 后半区(传输完成回调) */ dma_fill(0u, 800u); /* 模拟 DMA 正在写前半区(值 800 系) */ - CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-082 second round fetch succeeded"); + CHECK(stm32_take() == ADC_INPUT_RC_OK, "second round fetch succeeded"); CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), - "TC-ADCIN-083 second round reads the later half (208)"); - CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-084 DMA completion counter = 2"); + "second round reads the later half (208)"); + CHECK(adc_input_stm32_seq() == 2u, "DMA completion counter = 2"); /* 半区奇偶口径:seq=1 -> 前半区,seq=2 -> 后半区(与 dma_beat 独立推导,互相证伪) */ - CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "TC-ADCIN-085 UC stays consistent within the frame (203)"); + CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "UC stays consistent within the frame (203)"); /* 消费者超拍:两拍都没取 -> 中间那帧已被乒乓缓冲覆盖 -> 记 1 点丢帧 */ dma_beat(300); dma_beat(400); CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-086 after skipping rounds the newest frame is still fetched"); - CHECK(adc_input_stm32_gaps() == 1u, "TC-ADCIN-087 seq advanced by 2 -> records 1 dropped frame"); - CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "TC-ADCIN-088 what came back is the newest frame (408)"); - CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-089 a drop is not counted as a stall (two fault classes stay apart)"); + CHECK(adc_input_stm32_gaps() == 1u, "seq advanced by 2 -> records 1 dropped frame"); + CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "what came back is the newest frame (408)"); + CHECK(adc_input_stm32_errs() == 0u, "a drop is not counted as a stall (two fault classes stay apart)"); /* 第二档:上面只跨了 1 帧(前进 2 记 1 点),"次数"与"点数"数值相同、钉不出单位。 * 这里连投三帧不消费 = 一次跨 2 帧,必须记 2 点(累计 1+2=3),把 2026-10-21 改的 @@ -358,15 +358,15 @@ static void tc_hw_errs(void) { CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-090 error callback total = 2"); HAL_ADC_ErrorCallback(&hadc_other); - CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-091 other ADC handles must not count (hadc guard)"); + CHECK(adc_input_stm32_hw_errs() == 2u, "other ADC handles must not count (hadc guard)"); HAL_ADC_ConvHalfCpltCallback(&hadc_other); HAL_ADC_ConvCpltCallback(&hadc_other); - CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-092 a completion callback from another handle must not advance seq"); + CHECK(adc_input_stm32_seq() == 0u, "a completion callback from another handle must not advance seq"); HAL_ADC_ErrorCallback(&hadc1); - CHECK(adc_input_stm32_hw_errs() == 3u, "TC-ADCIN-093 hw_errs and errs count separately and never interfere"); - CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-09A hw_errs does not pollute errs"); + CHECK(adc_input_stm32_hw_errs() == 3u, "hw_errs and errs count separately and never interfere"); + CHECK(adc_input_stm32_errs() == 0u, "hw_errs does not pollute errs"); } /* ===================== TC-ADCIN-030..034:DMA 挂载失败 ===================== */ @@ -378,8 +378,8 @@ static void tc_init_dma_fail(void) { s_start_dma_rc = HAL_ERROR; CHECK(adc_input_init() == -2, "TC-ADCIN-030 HAL_ADC_Start_DMA failure -> init returns -2"); - CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-ADCIN-031 status code = RC_START"); - CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-032 failure counter +1"); + CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "status code = RC_START"); + CHECK(adc_input_stm32_errs() == 1u, "failure counter +1"); CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-034 after an arming failure the STM32 side holds no valid data"); } @@ -393,20 +393,20 @@ static void tc_take_stall(void) { /* 没有任何 DMA 完成回调 = TIM3 未启动 / ADC 停摆 / DMA 出错 */ CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-035 no new frame -> RC_STALL(-2)"); - CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-036 status code = RC_STALL"); - CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-037 a single round without a new frame is not a fault (phase artefact)"); - CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-038 seq does not advance while stalled"); + CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "status code = RC_STALL"); + CHECK(adc_input_stm32_errs() == 0u, "a single round without a new frame is not a fault (phase artefact)"); + CHECK(adc_input_stm32_seq() == 0u, "seq does not advance while stalled"); CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-039 a stalled round leaves the STM32 side without valid data"); (void)stm32_take(); /* 连续第二拍无新帧 = 真停摆 */ - CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-03A two rounds in a row with no new frame -> stall count = 1"); + CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-03a two rounds in a row with no new frame -> stall count = 1"); (void)stm32_take(); /* 第三拍仍无新帧 */ - CHECK(adc_input_stm32_errs() == 2u, "TC-ADCIN-03B an ongoing stall accumulates every round = 2"); - CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03C a stall is not counted as a drop (two fault classes stay apart)"); + CHECK(adc_input_stm32_errs() == 2u, "an ongoing stall accumulates every round = 2"); + CHECK(adc_input_stm32_gaps() == 0u, "a stall is not counted as a drop (two fault classes stay apart)"); dma_beat(100); /* 恢复 */ - CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-03D after recovery the fetch succeeds"); - CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03E the recovery round advances by 1 -> not a drop"); + CHECK(stm32_take() == ADC_INPUT_RC_OK, "after recovery the fetch succeeds"); + CHECK(adc_input_stm32_gaps() == 0u, "the recovery round advances by 1 -> not a drop"); } /* ============ TC-ADCIN-070..078:停摆拍不得沿用上一拍旧值(陈旧帧) ============ @@ -425,23 +425,23 @@ static void tc_take_stale(void) { (void)adc_input_init(); dma_beat(100); CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-070 precondition: first round succeeded"); - CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-071 precondition: UB reads 108 (valid state)"); + CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "precondition: UB reads 108 (valid state)"); /* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */ - CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-072 second round has no new frame"); - CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-073 a stalled round must not reuse the previous value -> -2"); - CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "TC-ADCIN-074 a stalled round must not emit any other STM32 channel either"); - CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "TC-ADCIN-074B same as above (TEMP also -2)"); + CHECK(stm32_take() == ADC_INPUT_RC_STALL, "second round has no new frame"); + CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "a stalled round must not reuse the previous value -> -2"); + CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "a stalled round must not emit any other STM32 channel either"); + CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "a stalled round must not emit TEMP either -> -2"); dma_beat(200); - CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-075 third round recovers successfully"); - CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "TC-ADCIN-076 the recovery round can be read again (self-healing)"); + CHECK(stm32_take() == ADC_INPUT_RC_OK, "third round recovers successfully"); + CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "the recovery round can be read again (self-healing)"); /* --- 挂载失败支路:同样不得沿用(init 失败后整层无有效数据) --- */ fakes_reset(); s_start_dma_rc = HAL_ERROR; (void)adc_input_init(); - CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-078 after an arming failure no value may be read -> -2"); + CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "after an arming failure no value may be read -> -2"); } /* ===================== TC-ADCIN-040..045:raw 参数守卫与无数据 ===================== */ @@ -482,9 +482,9 @@ static void tc_raw_tpafe(void) { CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-ADCIN-047 snapshot requests 8 channels"); CHECK(s_snapshot_dst != NULL, "TC-ADCIN-048 the snapshot destination buffer is not NULL"); CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 3006), "TC-ADCIN-049 UA -> buf[6]=3006"); - CHECK((adc_input_raw(ADC_SEM_UN, &v) == 0) && (v == 2005), "TC-ADCIN-04A UN -> buf[5]=2005"); + CHECK((adc_input_raw(ADC_SEM_UN, &v) == 0) && (v == 2005), "TC-ADCIN-04a UN -> buf[5]=2005"); CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == -1234), - "TC-ADCIN-04B IA -> buf[2]=-1234 (two's-complement negative kept as is)"); + "TC-ADCIN-04b IA -> buf[2]=-1234 (two's-complement negative kept as is)"); } /* ===================== TC-ADCIN-050..058:poll 契约 ===================== */ diff --git a/test/host/adc_input/stub/tpafe5160.h b/test/host/adc_input/stub/tpafe5160.h index 69a9516f..066b58e3 100644 --- a/test/host/adc_input/stub/tpafe5160.h +++ b/test/host/adc_input/stub/tpafe5160.h @@ -5,7 +5,7 @@ * * ⚠ 仅供 test/host/adc_input/ 使用,不参与 MCU 构建。 * - * 被测代码只消费 tpafe5160_snapshot()(E-58 原子快照),故桩只保留该声明; + * 被测代码只消费 tpafe5160_snapshot()(原子快照),故桩只保留该声明; * 其返回值语义(1=新帧 / 0=无新帧 / -1=参数错)与真实驱动一致,由测试单元 * 给强符号桩并按用例注入。 * diff --git a/test/host/cfg_file/cfg_file_test.c b/test/host/cfg_file/cfg_file_test.c index 6fd300df..6f73b2fb 100644 --- a/test/host/cfg_file/cfg_file_test.c +++ b/test/host/cfg_file/cfg_file_test.c @@ -39,7 +39,7 @@ * TC-CONFIG-021 非法键定义拒绝(UINT len 非 1/2/4、MAC len≠6、STR len>79) * TC-CONFIG-022 段名超长拒绝(≥ CFG_NAME_MAX) * TC-CONFIG-023 挂回调段 data_size > CFG_SNAP_MAX 拒绝 - * TC-CONFIG-024 键名超长拒绝(≥ CFG_NAME_MAX);恰好 15 字符(= CFG_FILE_MAX_NAME_LEN)接受 + * 键名超长拒绝(≥ CFG_NAME_MAX);恰好 15 字符(= CFG_FILE_MAX_NAME_LEN)接受 * —— load 契约(cfg_file_load)—— * TC-CONFIG-030 文件缺失 -> 生成出厂模板写盘 + 返回 0 * TC-CONFIG-031 文件存在 -> 解析有效键进 RAM 副本 @@ -51,12 +51,12 @@ * TC-CONFIG-041 外科手术:保留注释/未知段/未知键,受管键原位改写 * TC-CONFIG-042 缺失受管段/键补写 * TC-CONFIG-043 输出超长 -> 拒绝保存(-1) 且原文件不动 - * TC-CONFIG-044 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动(P2-1); - * 非受管键的超长行照旧放过(原样保留) + * 受管键行超 CFG_LINE_TEST_MAX -> 拒绝保存(-1) 原文件不动; + * 非受管键的超长行照旧放过(原样保留) * —— poll 契约(cfg_file_poll)—— * TC-CONFIG-050 首轮建立基线(不重载) * TC-CONFIG-051 沉降去抖:连续 2 次稳定才重载 - * TC-CONFIG-052 与基线一致不重载 + * 与基线一致不重载 * TC-CONFIG-053 变更后重载 + 挂回调段差异触发回调 * TC-CONFIG-054 溢出到文件超长 -> 工厂默认 + 重建模板 + 自检留痕(不触发回调) * @@ -549,10 +549,10 @@ static void tc_parse(void) { "baud = 9600\r\n"); CHECK(cfg_file_load() == 0, "load returns 0"); - CHECK(s_net_data.u8 == 42u, "TC-010/013: comment skipped, key parsed, ws trimmed"); + CHECK(s_net_data.u8 == 42u, "TC-CONFIG-010/013: comment skipped, key parsed, ws trimmed"); CHECK(s_net_data.ip[0] == 10u && s_net_data.ip[3] == 1u, "ip parsed"); - CHECK(s_net_data.u16 == s_net_def.u16, "TC-014: absent key keeps default (u16)"); - CHECK(s_net_data.name[0] == 'd', "TC-014: unknown key leaves default (name)"); + CHECK(s_net_data.u16 == s_net_def.u16, "TC-CONFIG-014: absent key keeps default (u16)"); + CHECK(s_net_data.name[0] == 'd', "TC-CONFIG-014: unknown key leaves default (name)"); CHECK(s_sys_data.baud == 9600u, "multi-section: sys parsed"); } @@ -689,7 +689,7 @@ static void tc_register(void) { CHECK(cfg_file_register(&r) == 0, "same section without callback accepted"); } - printf("[TC-CONFIG-024] key name length limit\n"); + printf("key name length limit\n"); { cfg_section_reg_t r; static const cfg_key_def_t long_key[] = { @@ -838,7 +838,7 @@ static void tc_save(void) { } } - printf("[TC-CONFIG-044] over-long managed key line -> reject (P2-1)\n"); + printf("over-long managed key line -> reject\n"); { char buf[512]; size_t n; diff --git a/test/host/cfg_file_store_mcu/cfg_file_store_mcu_test.c b/test/host/cfg_file_store_mcu/cfg_file_store_mcu_test.c index 5259ae0f..b9ebe2a9 100644 --- a/test/host/cfg_file_store_mcu/cfg_file_store_mcu_test.c +++ b/test/host/cfg_file_store_mcu/cfg_file_store_mcu_test.c @@ -3,7 +3,7 @@ * 模块功能:在 PC 上离线验证 App/cfg/cfg_file_store_mcu.c 的五个卷操作 + 三个堆操作: * cfg_store_read / cfg_store_write_atomic / cfg_store_stat / * cfg_store_remove / 两个路径 getter / alloc-free-heap_free - * 重心放在**原子写**与它的 v1.2 修复(E-01)上:本 FatFs 移植的 f_rename + * 重心放在**原子写**与它的 v1.2 修复上:本 FatFs 移植的 f_rename * 对已存在目标返回 FR_EXIST、**不覆盖**,所以"覆盖写"必须先 f_unlink 目标 * 再 f_rename——少了这一步,一切非首启保存(UI 改配置、模板重建)静默失败, * 而模拟器后端(POSIX rename 覆盖语义)照不出来。 @@ -11,12 +11,12 @@ * 被测对象:真实源文件 App/cfg/cfg_file_store_mcu.c(#include 原样纳入)。 * 假件与桩: * test/host/common/fatfs_fake.{h,c} —— 假 FatFs 内存卷(真实 ff.h 声明; - * 它的 f_rename **如实返回 FR_EXIST**,正是 E-01 的那颗雷) + * 它的 f_rename **如实返回 FR_EXIST**,正是原子写那颗雷) * stub/FreeRTOS.h —— 三个分配器原型(签名逐条对齐 portable.h:163/164/166) * 假卷锁 / 假堆(可注入分配失败)写在本文件里 * * 为什么要主机侧单测(而不是只靠台架): - * 1. **E-01 原子写**:unlink→rename 这个非原子窗口是"掉电时配置暂失、开机按首启 + * 1. **原子写**:unlink→rename 这个非原子窗口是"掉电时配置暂失、开机按首启 * 重建工厂模板"的有意取舍。任何一次"顺手优化"(比如给 rename 加覆盖、或把 * unlink 挪到 rename 之后)都会让配置保存静默失败或留下脏 TMP,而这在 POSIX * 后端上完全看不出来。→ 假卷把 FR_EXIST 原样喂进来。 @@ -44,7 +44,7 @@ * —— 原子写 —— * TC-CFGFST-010 首启写:TMP 建 -> 写 -> unlink(目标,FR_NO_FILE 容忍) -> rename * TC-CFGFST-011 **覆盖写必须先 unlink 目标**:假卷 rename 对已存在目标返 FR_EXIST, - * 少了 unlink 就是 -1 且旧内容原封不动(E-01 的直接复现) + * 少了 unlink 就是 -1 且旧内容原封不动(原子写缺陷的直接复现) * TC-CFGFST-012 rename 失败 -> -1 + TMP 清理 + 目标内容保持不动 * TC-CFGFST-013 TMP 打开失败 -> -1、零 write、release * TC-CFGFST-014 写短(FR_OK + 字节数不足)-> -1 + TMP 被删 + 目标原内容不动 @@ -326,7 +326,7 @@ static void tc_write_atomic(void) size_t n; int ok; - printf("-- TC-CFGFST-010..020 atomic write (core of E-01) --\n"); + printf("-- TC-CFGFST-010..020 atomic write (core regression) --\n"); /* 010 首启写(目标不存在) */ reset_all(); @@ -339,7 +339,7 @@ static void tc_write_atomic(void) && (s_lock_releases == 1) && (s_lock_seq_err == 0), "TC-CFGFST-010 first write: TMP -> rename -> target 2 bytes, no TMP left behind"); - /* 011 覆盖写:假卷的 f_rename 对已存在目标返回 FR_EXIST —— 这就是 E-01 */ + /* 011 覆盖写:假卷的 f_rename 对已存在目标返回 FR_EXIST —— 这就是原子写的雷 */ reset_all(); ok = (cfg_store_write_atomic(P_CFG, (const uint8_t *)"old-content", 11u) == 0); { @@ -349,7 +349,7 @@ static void tc_write_atomic(void) && (ff_fake_fetch(P_CFG, buf, 0u, sizeof(buf)) == 3u) && (memcmp(buf, "new", 3u) == 0) && (g_ff_obs.calls[FF_FAKE_OP_UNLINK] == unlinks_before + 1), - "TC-CFGFST-011 overwrite: unlink target exactly once then rename -> new content lands (E-01)"); + "TC-CFGFST-011 overwrite: unlink target exactly once then rename -> new content lands (atomic-write trap)"); } /* 同一条的"反面":直接 rename 到已存在目标必败(证明上一步的 unlink 不是多余的) */ { diff --git a/test/host/cfg_file_store_mcu/run_unit_test.py b/test/host/cfg_file_store_mcu/run_unit_test.py index f9d2b382..703c34a9 100644 --- a/test/host/cfg_file_store_mcu/run_unit_test.py +++ b/test/host/cfg_file_store_mcu/run_unit_test.py @@ -5,12 +5,12 @@ r"""配置存储后端(cfg_file_store_mcu)的主机侧单元测试运行器 做什么: 用本机 MSVC 编译 test/host/cfg_file_store_mcu/cfg_file_store_mcu_test.c(以 #include 纳入**生产源** App/cfg/cfg_file_store_mcu.c,并纳入共享假卷 - test/host/common/fatfs_fake.c),在 PC 上离线跑读、原子写(E-01 的 + test/host/common/fatfs_fake.c),在 PC 上离线跑读、原子写( unlink->rename 全链)、状态令牌、删除、路径 8.3 合规、假堆分配,失败即非 0 退出。 工具链与判定委托 test/host/common/unit_test_harness.py。 为什么要主机侧单测(而不是只用台架): - 1. **E-01 原子写**:本仓 FatFs 移植的 f_rename 对**已存在目标返回 FR_EXIST、不 + 1. **原子写**:本仓 FatFs 移植的 f_rename 对**已存在目标返回 FR_EXIST、不 覆盖**,所以覆盖写必须先 unlink 目标。模拟器后端(POSIX rename 覆盖语义) 永远照不出这一条 → 曾经"一切非首启保存静默失败"。假卷按真实 ff.c 返回 FR_EXIST,把这条做成必然失败的断言。 diff --git a/test/host/ch395f_reuse_host.py b/test/host/ch395f_reuse_host.py index a030bcbd..825f2da2 100644 --- a/test/host/ch395f_reuse_host.py +++ b/test/host/ch395f_reuse_host.py @@ -1,4 +1,4 @@ -# N-30 芯片级复用时序台架:主机端(与 test/board/ch395f_reuse_bench.c 共用变体表) +# relisten 沉降段原语化受阻:芯片级复用时序台架主机端(与 test/board/ch395f_reuse_bench.c 共用变体表) # 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] [--mode legacy|e4] # legacy: 按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。 # e4: E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接—— @@ -130,7 +130,7 @@ def main(): ap.add_argument("--mode", choices=["legacy", "e4"], default="legacy") ap.add_argument("--rounds", type=int, default=10, help="e4: rounds per variant") ap.add_argument("--first-budget", type=int, default=300, - help="e4: connect budget for round 0 (bench runs E2/E3 first)") + help="e4: connect budget for the first round (bench runs E2/E3 first)") args = ap.parse_args() if args.mode == "e4": diff --git a/test/host/ch395f_socket_test.py b/test/host/ch395f_socket_test.py index 8f3616bf..095140bd 100644 --- a/test/host/ch395f_socket_test.py +++ b/test/host/ch395f_socket_test.py @@ -513,12 +513,12 @@ def udp_client_test(args): 会话结构与固件 phase3_run() 约定: - "HELLO" 探针:回显即就绪,不计入固件轮次 - - TC-301:30 轮 64B 基础回显(--size 非默认值时覆盖单轮载荷大小) - - TC-302:1B/64B/200B 各 --count 轮变长合规(默认 10) + - TC-NET-301:30 轮 64B 基础回显(--size 非默认值时覆盖单轮载荷大小) + - TC-NET-302:1B/64B/200B 各 --count 轮变长合规(默认 10) 任一轮 mismatch/timeout 立即终止;结束输出汇总,失败退出码 1。 """ log_info("=" * 60) - log_info("UDP Echo 测试(TC-301 基础回显 + TC-302 变长合规)") + log_info("UDP Echo 测试(TC-NET-301 基础回显 + TC-NET-302 变长合规)") log_info("=" * 60) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -546,11 +546,11 @@ def udp_client_test(args): sock.settimeout(5) - # Phase B: TC-301 基础回显 30 轮 x 64B + # Phase B: TC-NET-301 基础回显 30 轮 x 64B size_b = 64 if args.size == 1024 else min(args.size, 200) rounds_b = 30 log_info("-" * 40) - log_info(f"Phase B: TC-301 基础回显({rounds_b} 轮 x {size_b}B)") + log_info(f"Phase B: TC-NET-301 基础回显({rounds_b} 轮 x {size_b}B)") ok_b = 0 for i in range(rounds_b): td = generate_data(size_b) @@ -558,23 +558,23 @@ def udp_client_test(args): sock.sendto(td, dest) data, _ = sock.recvfrom(size_b + 16) if data != td: - log_error(f"[TC-301] #{i+1} mismatch: {len(data)}B != {len(td)}B") + log_error(f"[TC-NET-301] #{i+1} mismatch: {len(data)}B != {len(td)}B") break except socket.timeout: - log_error(f"[TC-301] #{i+1} echo timeout") + log_error(f"[TC-NET-301] #{i+1} echo timeout") break ok_b += 1 if ok_b % 10 == 0: - log_info(f"[TC-301] 进度 {ok_b}/{rounds_b}") + log_info(f"[TC-NET-301] 进度 {ok_b}/{rounds_b}") time.sleep(0.02) - # Phase C: TC-302 变长合规(仅 Phase B 全过后继续,否则后续必超时) + # Phase C: TC-NET-302 变长合规(仅 Phase B 全过后继续,否则后续必超时) sizes_c = [1, 64, 200] total_c = len(sizes_c) * args.count ok_c = 0 if ok_b == rounds_b: log_info("-" * 40) - log_info(f"Phase C: TC-302 变长合规({sizes_c}B 各 {args.count} 轮)") + log_info(f"Phase C: TC-NET-302 变长合规({sizes_c}B 各 {args.count} 轮)") for sz in sizes_c: ok_sz = 0 for i in range(args.count): @@ -583,14 +583,14 @@ def udp_client_test(args): sock.sendto(td, dest) data, _ = sock.recvfrom(sz + 16) if data != td: - log_error(f"[TC-302] {sz}B #{i+1} mismatch: {len(data)}B != {sz}B") + log_error(f"[TC-NET-302] {sz}B #{i+1} mismatch: {len(data)}B != {sz}B") break except socket.timeout: - log_error(f"[TC-302] {sz}B #{i+1} echo timeout") + log_error(f"[TC-NET-302] {sz}B #{i+1} echo timeout") break ok_sz += 1 time.sleep(0.02) - log_info(f"[TC-302] {sz}B: {ok_sz}/{args.count}") + log_info(f"[TC-NET-302] {sz}B: {ok_sz}/{args.count}") ok_c += ok_sz if ok_sz < args.count: break @@ -598,9 +598,9 @@ def udp_client_test(args): # 汇总与退出码 log_info("=" * 60) if ok_b == rounds_b and ok_c == total_c: - log_success(f"PASS: TC-301 {ok_b}/{rounds_b}, TC-302 {ok_c}/{total_c}") + log_success(f"PASS: TC-NET-301 {ok_b}/{rounds_b}, TC-NET-302 {ok_c}/{total_c}") else: - log_error(f"FAIL: TC-301 {ok_b}/{rounds_b}, TC-302 {ok_c}/{total_c}") + log_error(f"FAIL: TC-NET-301 {ok_b}/{rounds_b}, TC-NET-302 {ok_c}/{total_c}") sys.exit(1) except Exception as e: @@ -874,7 +874,7 @@ def _build_dhcp_pkt(op, xid, client_mac, yiaddr, siaddr, options): def _announce_listener(results): - """后台监听 MCU 的 4B IP 广播宣告(UDP :60001,TC-602 步1 的 PC 侧观测)""" + """后台监听 MCU 的 4B IP 广播宣告(UDP :60001,TC-NET-402 步1 的 PC 侧观测)""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: @@ -1026,7 +1026,7 @@ def dhcp_server_test(args): print() - # 汇总宣告监听结果(TC-602 步 1 PC 侧观测;join 延迟由固件 30s HELLO 窗覆盖) + # 汇总宣告监听结果(TC-NET-402 步 1 PC 侧观测;join 延迟由固件 30s HELLO 窗覆盖) listener.join(timeout=10) if results.get('announce'): if results['announce'] != offer_ip: diff --git a/test/host/common/README.md b/test/host/common/README.md index 9d06edac..fee8a66f 100644 --- a/test/host/common/README.md +++ b/test/host/common/README.md @@ -9,7 +9,7 @@ |---|---| | `unit_test_harness.py` | **通用单测 harness**(纯标准库):探测本机 MSVC → 拼 `INCLUDE`/`LIB` → 编译测试单元 → 运行 → 判定退出码 | -## 为什么要有这层(2026-10-01 抽取,T-3) +## 为什么要有这层(2026-10-01 抽取) 看门狗单测最早的运行器(`test/host/watchdog/run_unit_test.py`)把工具链探测、 `INCLUDE`/`LIB` 拼接、GBK 解码、`/wd4819` 抑制、退出码契约**全写死在模块目录内**。 diff --git a/test/host/common/fatfs_fake.c b/test/host/common/fatfs_fake.c index 46472a93..5b1173ab 100644 --- a/test/host/common/fatfs_fake.c +++ b/test/host/common/fatfs_fake.c @@ -1040,7 +1040,7 @@ FRESULT f_rename(const TCHAR *path_old, const TCHAR *path_new) } dup = s_find(nnew); if (dup >= 0) { - /* E-01 台架实证的根因:本移植(与上游同语义)"new name already in use" + /* 台架实证的根因:本移植(与上游同语义)"new name already in use" * 一律 FR_EXIST、不覆盖。原子写因此必须先 unlink 目标再 rename。 */ return FR_EXIST; } diff --git a/test/host/common/fatfs_fake.h b/test/host/common/fatfs_fake.h index 3d561fd8..50c85e0f 100644 --- a/test/host/common/fatfs_fake.h +++ b/test/host/common/fatfs_fake.h @@ -41,7 +41,7 @@ * - f_stat:父目录在而对象不在 → FR_NO_FILE;父目录也不在 → FR_NO_PATH; * - f_unlink 不存在 → FR_NO_FILE;f_mkdir 已存在 → FR_EXIST; * - **f_rename 撞已存在的新名 → FR_EXIST、不覆盖**(ff.c「new name already in - * use」,2026-09-22 台架 E-01 暴露的那条:缺了它,一切「目标存在的覆盖写」 + * use」,2026-09-22 台架暴露的那条:缺了它,一切「目标存在的覆盖写」 * 必然失败,而模拟器端 POSIX rename 的覆盖语义照不出来)。不复刻本条 = * cfg_store_write_atomic 里「先 unlink 再 rename」那一步永远测不出必要性; * - f_lseek 越过文件尾:只读句柄**钳到 objsize 且不报错**(ff.c 正常寻址分支), diff --git a/test/host/common/unit_test_harness.py b/test/host/common/unit_test_harness.py index c05827d5..cc9802e1 100644 --- a/test/host/common/unit_test_harness.py +++ b/test/host/common/unit_test_harness.py @@ -7,7 +7,7 @@ 被测模块的差异(源文件、桩目录、额外 include、断言汇总行特征)全部由调用方 (各模块自己的 `run_unit_test.py`)传入。 -为什么要 harness(2026-10-01 测试架构评审 T-3): +为什么要 harness(2026-10-01 测试架构评审): 看门狗单测的运行器(`test/host/watchdog/run_unit_test.py`,170 行)把工具链 探测、`INCLUDE`/`LIB` 拼接、GBK 解码、`/wd4819` 抑制、退出码契约**全部写死在 模块目录内**。第二个 L0.5 单测出现时须整段复制——工具链细节一旦修正(新增 VS @@ -36,7 +36,7 @@ summary_marker="=== Task Watchdog Tests:", ))) -设计依据:docs/任务规划/登记簿.md T-3;docs/测试说明/测试开发标准流程.md §4.2 +设计依据:docs/测试说明/测试开发标准流程.md §4.2 创建日期:2026-10-01 """ diff --git a/test/host/console_log/console_log_test.c b/test/host/console_log/console_log_test.c index 47c75589..9637f392 100644 --- a/test/host/console_log/console_log_test.c +++ b/test/host/console_log/console_log_test.c @@ -35,7 +35,7 @@ * 读后清、300ms 冷却两端、tick==0 也进冷却(哨兵撞值修好后为正向断言)、 * 半块 / 已上线仍判失败 * 090..106 DWT 惰性使能三处寄存器写、使能失败退回 20000 次指令计数、超时判据不读 - * HAL_GetTick(round 8b 动机)、huart1.Instance == NULL 短路两路 + + * HAL_GetTick(第 8b 轮台架动机)、huart1.Instance == NULL 短路两路 + * "未初始化绝不 reinit"(误自愈修好后为正向断言)+ 证据不蒸发 * 110..117 fputc 重定向(未注册直发 / 注册逐字节入环 / ISR 上下文裸发 / 环满时 ISR * 仍不等待) @@ -864,7 +864,7 @@ static void tc_dwt(void) CHECK(con_dwt_reads == 3U, "TC-CONLOG-097 the counted branch never reads CYCCNT (independent of DWT, hence of any time source)"); - printf("-- TC-CONLOG-098..099 the timeout criterion does not use HAL_GetTick (round 8b motivation) --\n"); + printf("-- TC-CONLOG-098..099 the timeout criterion does not use HAL_GetTick --\n"); reset_all(); con_uart_txe_stuck = 1; /* tick 冻结在 0,模拟 TIM7 停摆 */ con_dwt_step = SystemCoreClock / 10U; /* 一次判据正好 = 100ms 上界 */ diff --git a/test/host/console_log/run_unit_test.py b/test/host/console_log/run_unit_test.py index f919e9a1..f864dc96 100644 --- a/test/host/console_log/run_unit_test.py +++ b/test/host/console_log/run_unit_test.py @@ -13,7 +13,7 @@ 为什么要主机侧单测(而不是只用台架): 本模块的坑全是"异常时刻 + 节奏":ORE 僵尸通道在软复位后毫秒级重锁、TIM7 停摆让 - HAL 的 tick 超时永不成立(round 8a 台架:唯一喂狗者死等 TXE -> IWDG 误复位)、 + HAL 的 tick 超时永不成立(第 8a 轮台架:唯一喂狗者死等 TXE -> IWDG 误复位)、 背靠背块超时把主循环拉到 450~500ms 饿死喂狗(台架实测的停喂误复位)。这些在台架上 要么一年一遇、要么现场已被复位冲掉;把 100/250/300ms 三条时间线做成显式量之后, 299ms 与 300ms 的差别就是一行断言。详见 diff --git a/test/host/e02_probe.py b/test/host/e02_probe.py index ed01b73f..34780b7e 100644 --- a/test/host/e02_probe.py +++ b/test/host/e02_probe.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""E-02 头阻塞专项台架探针(timeSync 侧,A/B 对照用)。 +"""头阻塞专项台架探针(timeSync 侧,A/B 对照用)。 -场景(历史审计 E-02 的 timeSync 触发形态,属主文档已于 2026-10-15 删除): +场景(历史审计的头阻塞触发形态,属主文档已于 2026-10-15 删除): 1. 一个"挂起"的对时客户端连上 :8888 后不发任何数据(空闲/故障客户端, 常见工况)——旧固件在此触发 30s 阻塞 net_recv 独占 netTask, 全体消费者的队列消息停摆最长 30s; diff --git a/test/host/ftp_abort_probe.py b/test/host/ftp_abort_probe.py index 83790823..d7ce1fa5 100644 --- a/test/host/ftp_abort_probe.py +++ b/test/host/ftp_abort_probe.py @@ -31,7 +31,7 @@ def rd(sock): def raw_login(ip, port, timeout=10.0): s = socket.create_connection((ip, port), timeout=timeout) rd(s) # 220 banner - # N-19: anonymous login is rejected (530) -> use the factory default account + # FTP auth is mandatory: anonymous login is rejected (530) -> use the factory default account s.sendall(b"USER admin\r\n"); rd(s) s.sendall(b"PASS admin\r\n"); rd(s) s.sendall(b"TYPE I\r\n"); rd(s) diff --git a/test/host/ftp_auth_bench.py b/test/host/ftp_auth_bench.py index 0ae78557..d777d506 100644 --- a/test/host/ftp_auth_bench.py +++ b/test/host/ftp_auth_bench.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""FTP 鉴权台架回归(N-19,需真机在线) +"""FTP 鉴权台架回归(需真机在线) 验证固件侧 lftpd 鉴权集成的完整行为矩阵(主机单测 test/host/ftp_auth 覆盖 不到的"协议接线"部分:命令门、530/331/230/550 响应码、跨会话锁定): @@ -65,7 +65,7 @@ def _try_login(host, port, timeout, user, pwd): def main(argv=None): - ap = argparse.ArgumentParser(description="FTP auth bench matrix (N-19)") + ap = argparse.ArgumentParser(description="FTP auth bench matrix") ap.add_argument("--host", default="192.168.1.100") ap.add_argument("--port", type=int, default=21) ap.add_argument("--timeout", type=int, default=15) diff --git a/test/host/ftp_cfg/ftp_cfg_test.c b/test/host/ftp_cfg/ftp_cfg_test.c index 0e4277bc..88c7c922 100644 --- a/test/host/ftp_cfg/ftp_cfg_test.c +++ b/test/host/ftp_cfg/ftp_cfg_test.c @@ -9,7 +9,7 @@ * 包含 dbg_log.h,也没有别的平台依赖。 * * 为什么要主机侧单测(而不是只靠台架): - * 1. 这个模块是 **FTP 的门禁**(N-19):pwd_hash 存的是 SHA-256(user‖password), + * 1. 这个模块是 **FTP 鉴权的门禁**:pwd_hash 存的是 SHA-256(user‖password), * 判定写成大小写敏感就会在"用户手输大写 hex"时把正确口令当错误;写成敏感于 * 默认值大小写则会误报"已改口令",让出厂默认口令的启动告警**静默失效**—— * 现场表现是"以为改了口令"。 diff --git a/test/host/ftp_cfg/run_unit_test.py b/test/host/ftp_cfg/run_unit_test.py index 0764852f..8788b429 100644 --- a/test/host/ftp_cfg/run_unit_test.py +++ b/test/host/ftp_cfg/run_unit_test.py @@ -9,7 +9,7 @@ 工具链探测与判定全部委托给 test/host/common/unit_test_harness.py。 为什么要主机侧单测(而不是只用台架): - 本模块是 FTP 的门禁(N-19)。判定写成大小写敏感 → 用户手输大写 hex 时正确口令被拒; + 本模块是 FTP 鉴权的门禁。判定写成大小写敏感 → 用户手输大写 hex 时正确口令被拒; 反过来对出厂值敏感 → "已改口令"误报,出厂默认口令的启动告警**静默失效**(现场以为 改了口令)。[ftp] 段刻意不挂变更回调(口令下次登录才生效),有人"顺手补上"就会在 poll 重载时去重建网络。write_en 出厂 = 1 也是刻意的(FTP 推 config.ini 是既有维护 diff --git a/test/host/ftp_regression.py b/test/host/ftp_regression.py index 4dd4657a..3a710f11 100644 --- a/test/host/ftp_regression.py +++ b/test/host/ftp_regression.py @@ -30,7 +30,7 @@ def main(argv=None): ap.add_argument("--host", default="192.168.1.100", help="device IP") ap.add_argument("--port", type=int, default=21, help="FTP control port") ap.add_argument("--timeout", type=int, default=20, help="socket timeout (s)") - # N-19(2026-10-11):FTP 已强制鉴权,匿名登录被 530 拒;默认口令 admin/admin + # 2026-10-11 起 FTP 强制鉴权:匿名登录被 530 拒;默认口令 admin/admin # (出厂值,见 ftp_cfg.h),改过 [ftp] pwd_hash 后用这两个参数覆盖 ap.add_argument("--user", default="admin", help="FTP user ([ftp] user)") ap.add_argument("--pwd", default="admin", help="FTP password") diff --git a/test/host/iec104/iec104_test.c b/test/host/iec104/iec104_test.c index 447ad2b1..1ff24131 100644 --- a/test/host/iec104/iec104_test.c +++ b/test/host/iec104/iec104_test.c @@ -36,7 +36,7 @@ * TC-IEC104-023 k 窗满 COV 不丢事件(pend 暂存,窗开后补发) * —— 以下为「数据可信」卡 9/卡 11(道D,2026-10-25;板端默认源 = 真实 L2 链 ring→phasor→freq→meas,非桩)—— - * TC-IEC104-025 板端源读快照(024 号被存量 B7 用例的裸标签占用):未发布 + * TC-IEC104-025 板端源读快照(024 号已归 B7 用例):未发布 * IV|NT / 值=基波有效值 / 削顶 CLIPPED * →线上 IV 清零 / 干净路品质 0 / 频率未新鲜 IV * TC-IEC104-026 0x401D 绑频:收敛后 ±0.02 Hz / freq_valid==0 → IV 清零 @@ -440,17 +440,17 @@ static void tc_cp56(void) iec104_epoch_ms_to_cp56(e1, b); rc = iec104_cp56_to_epoch_ms(b, &back); - CHECK((rc == 0) && (back == e1), "TC-001 CP56 round trip on 2000-01-01"); + CHECK((rc == 0) && (back == e1), "TC-IEC104-001 CP56 round trip on 2000-01-01"); iec104_epoch_ms_to_cp56(e2, b); back = 0u; rc = iec104_cp56_to_epoch_ms(b, &back); - CHECK((rc == 0) && (back == e2), "TC-001 CP56 round trip on the leap day 2024-02-29"); + CHECK((rc == 0) && (back == e2), "TC-IEC104-001 CP56 round trip on the leap day 2024-02-29"); b[2] |= 0x80u; - CHECK(iec104_cp56_to_epoch_ms(b, &back) == -2, "TC-001 IV -> -2"); + CHECK(iec104_cp56_to_epoch_ms(b, &back) == -2, "TC-IEC104-001 IV -> -2"); iec104_epoch_ms_to_cp56(0u, b); /* 早于 2000 → 钳位 */ back = 0u; (void)iec104_cp56_to_epoch_ms(b, &back); - CHECK(back == e1, "TC-001 a value before 2000 is clamped"); + CHECK(back == e1, "TC-IEC104-001 a value before 2000 is clamped"); } static void tc_ioa_float(void) @@ -459,12 +459,12 @@ static void tc_ioa_float(void) iec104_put_ioa(b, 0x123456u); CHECK((iec104_get_ioa(b) == 0x123456u) && (b[0] == 0x56u) && (b[1] == 0x34u) - && (b[2] == 0x12u), "TC-002 IOA is 3 bytes little endian"); + && (b[2] == 0x12u), "TC-IEC104-002 IOA is 3 bytes little endian"); iec104_put_float(b, 1.0f); CHECK((b[0] == 0x00u) && (b[1] == 0x00u) && (b[2] == 0x80u) - && (b[3] == 0x3Fu), "TC-002 float in IEEE754 little endian"); + && (b[3] == 0x3Fu), "TC-IEC104-002 float in IEEE754 little endian"); iec104_put_float(b, -12.5f); - CHECK(iec104_get_float(b) == -12.5f, "TC-002 float round trip"); + CHECK(iec104_get_float(b) == -12.5f, "TC-IEC104-002 float round trip"); } static void tc_apci(void) @@ -476,9 +476,9 @@ static void tc_apci(void) static const uint8_t b[6] = { 0x68u, 4u, 0x07u, 0u, 0u, 0u }; iec104_link_on_rx(&g_lk, b, 3u, now); - CHECK(cap_u_count(0x0Bu) == 0, "TC-003 a half frame triggers nothing"); + CHECK(cap_u_count(0x0Bu) == 0, "TC-IEC104-003 a half frame triggers nothing"); iec104_link_on_rx(&g_lk, &b[3], 3u, now); - CHECK(cap_u_count(0x0Bu) == 1, "TC-003 a half frame is reassembled then con"); + CHECK(cap_u_count(0x0Bu) == 1, "TC-IEC104-003 a half frame is reassembled then con"); } { static const uint8_t b[12] = { @@ -489,21 +489,21 @@ static void tc_apci(void) iec104_link_on_rx(&g_lk, b, 12u, now); CHECK((cap_u_count(0x83u) == 1) && (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), - "TC-003 a coalesced frame is handled one by one and STOPDT takes effect"); + "TC-IEC104-003 a coalesced frame is handled one by one and STOPDT takes effect"); } reset_all(now); { static const uint8_t b[6] = { 0x69u, 4u, 0x07u, 0u, 0u, 0u }; iec104_link_on_rx(&g_lk, b, 6u, now); - CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 a bad start byte -> IDLE"); + CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-003 a bad start byte -> IDLE"); } reset_all(now); { static const uint8_t b[6] = { 0x68u, 3u, 0x07u, 0u, 0u, 0u }; iec104_link_on_rx(&g_lk, b, 6u, now); - CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 a bad length -> IDLE"); + CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-003 a bad length -> IDLE"); } } @@ -512,10 +512,10 @@ static void tc_startdt(void) uint32_t now = 1000u; reset_all(now); - CHECK(iec104_link_state(&g_lk) == IEC104_ST_OPEN, "TC-004 open -> OPEN"); + CHECK(iec104_link_state(&g_lk) == IEC104_ST_OPEN, "TC-IEC104-004 open -> OPEN"); handshake(now); CHECK((cap_u_count(0x0Bu) == 1) - && (iec104_link_state(&g_lk) == IEC104_ST_RUN), "TC-004 STARTDT -> RUN"); + && (iec104_link_state(&g_lk) == IEC104_ST_RUN), "TC-IEC104-004 STARTDT -> RUN"); } static void tc_open_ignore(void) @@ -528,11 +528,11 @@ static void tc_open_ignore(void) n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u); feed_i(a, n, now); CHECK((cap_i_count() == 0) && (iec104_link_state(&g_lk) == IEC104_ST_OPEN), - "TC-005 an I frame in OPEN state is ignored"); + "TC-IEC104-005 an I frame in OPEN state is ignored"); handshake(now); m_ns = 0u; /* OPEN 忽略不消费序号 → 主站仍从 0 起 */ feed_i(a, n, now); /* ns=0 仍有效(序号未被消费) */ - CHECK(cap_i_count() == 1, "TC-005 after RUN the same ns is answered normally"); + CHECK(cap_i_count() == 1, "TC-IEC104-005 after RUN the same ns is answered normally"); } static void tc_interrogation(void) @@ -551,34 +551,34 @@ static void tc_interrogation(void) n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u); feed_i(a, n, now); CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 100u) - && (c.cot == IEC104_COT_ACTCON), "TC-006 actcon is sent right away, COT7"); + && (c.cot == IEC104_COT_ACTCON), "TC-IEC104-006 actcon is sent right away, COT7"); iec104_link_poll(&g_lk, now); - CHECK(cap_i_count() == 5, "TC-006 the first poll uses the whole budget of 4 frames"); + CHECK(cap_i_count() == 5, "TC-IEC104-006 the first poll uses the whole budget of 4 frames"); CHECK((cap_i(1, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT) && (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 4u)) && (c.ioa == 1u), - "TC-006 YX double-point group, first call COT=4"); + "TC-IEC104-006 YX double-point group, first call COT=4"); CHECK((cap_i(2, &c) == 0) && (c.type == 1u) && (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 29u)) && (c.ioa == 5u), - "TC-006 YX single-point group with 29 points"); + "TC-IEC104-006 YX single-point group with 29 points"); CHECK((cap_i(3, &c) == 0) && (c.type == 13u) && (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 8u)) && (c.ioa == 0x4011u) - && (c.obj_len == 40u), "TC-006 YC group of 8 points, 40 bytes"); + && (c.obj_len == 40u), "TC-IEC104-006 YC group of 8 points, 40 bytes"); if (cap_i(3, &c) == 0) { CHECK((iec104_get_float(c.obj) == 111.0f) && (c.obj[4] == 0u), - "TC-006 YC value 111.0 with QDS=0"); - CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 an invalid channel sets QDS=IV"); + "TC-IEC104-006 YC value 111.0 with QDS=0"); + CHECK(c.obj[9] == IEC104_QDS_IV, "TC-IEC104-006 an invalid channel sets QDS=IV"); } CHECK((cap_i(4, &c) == 0) && (c.ioa == 0x401Du) && (c.vsq == 1u), - "TC-006 frequency as a single point with SQ=0"); + "TC-IEC104-006 frequency as a single point with SQ=0"); iec104_link_poll(&g_lk, now + 100u); CHECK((cap_i_count() == 6) && (cap_i(5, &c) == 0) && (c.type == 100u) - && (c.cot == IEC104_COT_ACTTERM), "TC-006 actterm closes the sequence"); + && (c.cot == IEC104_COT_ACTTERM), "TC-IEC104-006 actterm closes the sequence"); /* 二召:init 已清 → COT=2 */ n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u); feed_i(a, n, now + 200u); iec104_link_poll(&g_lk, now + 200u); CHECK((cap_i(7, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_BG_SCAN), - "TC-006 second call COT=2"); + "TC-IEC104-006 second call COT=2"); /* RBT 位 → COT=4(actcon3 是第 12 个未确认帧 → 先 S 确认窗才开) */ n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, IEC104_COT_BIT_RBT, 1u, 0u, body, 1u); @@ -586,7 +586,7 @@ static void tc_interrogation(void) feed_s(12u, now + 300u); iec104_link_poll(&g_lk, now + 300u); CHECK((cap_i(12, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT), - "TC-006 RBT -> COT=4"); + "TC-IEC104-006 RBT -> COT=4"); } static void tc_cov_yx(void) @@ -598,33 +598,33 @@ static void tc_cov_yx(void) reset_all(now); handshake(now); iec104_link_poll(&g_lk, now); /* 建快照 */ - CHECK(cap_i_count() == 0, "TC-007 the first poll sends nothing spontaneous"); + CHECK(cap_i_count() == 0, "TC-IEC104-007 the first poll sends nothing spontaneous"); s_f_bits |= (1uLL << 15); /* YX16(idx15)动作 */ iec104_link_poll(&g_lk, now + 100u); /* B9:单点 YX 自发 = M_SP_TB_1(30)带 CP56Time2a 时标(SOE 上送路径) */ CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 30u) && (c.cot == IEC104_COT_SPONT) && (c.ioa == 16u) - && (c.obj_len == 8u) && (c.obj[0] == 1u), "TC-007 a YX16 change of state sends COT3 with TB"); + && (c.obj_len == 8u) && (c.obj[0] == 1u), "TC-IEC104-007 a YX16 change of state sends COT3 with TB"); if ((cap_i(0, &c) == 0) && (c.obj_len == 8u)) { CHECK(iec104_cp56_to_epoch_ms(&c.obj[1], &back) == 0, - "TC-007 the CP56 time tag decodes"); - CHECK(back == s_epoch_val, "TC-007 the time tag equals the epoch_ms source"); + "TC-IEC104-007 the CP56 time tag decodes"); + CHECK(back == s_epoch_val, "TC-IEC104-007 the time tag equals the epoch_ms source"); } s_f_bits &= ~(1uLL << 15); /* 复归 */ iec104_link_poll(&g_lk, now + 200u); CHECK((cap_i_count() == 2) && (cap_i(1, &c) == 0) && (c.type == 30u) - && (c.ioa == 16u) && (c.obj[0] == 0u), "TC-007 YX16 is reset and changes again"); + && (c.ioa == 16u) && (c.obj[0] == 0u), "TC-IEC104-007 YX16 is reset and changes again"); /* B9 IV 路径:时标源无效 → CP56 IV 位如实置位 */ s_epoch_val = UINT64_MAX; s_f_bits |= (1uLL << 16); /* YX17(idx16)动作 */ iec104_link_poll(&g_lk, now + 300u); CHECK((cap_i_count() == 3) && (cap_i(2, &c) == 0) && (c.type == 30u) && (c.ioa == 17u) && ((c.obj[3] & 0x80u) != 0u), - "TC-007 an invalid clock sets the CP56 IV bit"); + "TC-IEC104-007 an invalid clock sets the CP56 IV bit"); s_epoch_val = 1760234567000ULL; s_f_bits |= (1uLL << 0); /* idx0 无效位 → 不触发 */ iec104_link_poll(&g_lk, now + 400u); - CHECK(cap_i_count() == 3, "TC-007 a change while the IV bit is set triggers nothing"); + CHECK(cap_i_count() == 3, "TC-IEC104-007 a change while the IV bit is set triggers nothing"); } static void tc_cov_yc(void) @@ -639,7 +639,7 @@ static void tc_cov_yc(void) s_f_yc[0] = 100.4f; /* 死区 0.5 V 内(卡 6 二次值口径:0.5 %×100 V, * 旧拟定码值死区 10 已由「数据可信」卡 6 重定) */ iec104_link_poll(&g_lk, now + 100u); - CHECK(cap_i_count() == 0, "TC-008 inside the deadband nothing is uploaded"); + CHECK(cap_i_count() == 0, "TC-IEC104-008 inside the deadband nothing is uploaded"); s_f_yc[0] = 101.25f; /* 101.25-100=1.25 > 0.5,第 1 越点 */ iec104_link_poll(&g_lk, now + 200u); CHECK(cap_i_count() == 0, "TC-IEC104-008 first deadband crossing only arms (2-point confirm)"); @@ -669,12 +669,12 @@ static void tc_k_window(void) for (i = 0; i < 12; i++) { feed_rd(now); } - CHECK(cap_i_count() == 12, "TC-009 the k=12 window caps the outstanding frames"); + CHECK(cap_i_count() == 12, "TC-IEC104-009 the k=12 window caps the outstanding frames"); feed_rd(now); - CHECK(cap_i_count() == 12, "TC-009 the 13th frame is suppressed by the k window"); + CHECK(cap_i_count() == 12, "TC-IEC104-009 the 13th frame is suppressed by the k window"); feed_s(12u, now); /* 全确认 → 窗开 */ feed_rd(now); - CHECK(cap_i_count() == 13, "TC-009 sending resumes after the S acknowledgement"); + CHECK(cap_i_count() == 13, "TC-IEC104-009 sending resumes after the S acknowledgement"); } static void tc_w_thresh(void) @@ -688,9 +688,9 @@ static void tc_w_thresh(void) for (i = 0; i < 7; i++) { feed_rd(now); } - CHECK(cap_has_s(&nr) == 0, "TC-010 after 7 received frames no S has been sent yet"); + CHECK(cap_has_s(&nr) == 0, "TC-IEC104-010 after 7 received frames no S has been sent yet"); feed_rd(now); /* 第 8 个 → w 阈 */ - CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-010 w=8 -> S with nr=8"); + CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-IEC104-010 w=8 -> S with nr=8"); } static void tc_t2(void) @@ -702,9 +702,9 @@ static void tc_t2(void) handshake(now); feed_rd(now); /* rx_unacked=1,t2 起爆 */ iec104_link_poll(&g_lk, now + 9999u); - CHECK(cap_has_s(&nr) == 0, "TC-011 no S before t2 expires"); + CHECK(cap_has_s(&nr) == 0, "TC-IEC104-011 no S before t2 expires"); iec104_link_poll(&g_lk, now + 10001u); - CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 S is sent once t2 expires"); + CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-IEC104-011 S is sent once t2 expires"); } static void tc_t3_t1(void) @@ -714,19 +714,19 @@ static void tc_t3_t1(void) reset_all(now); handshake(now); iec104_link_poll(&g_lk, now + 19999u); - CHECK(cap_u_count(0x43u) == 0, "TC-012 no TESTFR before t3 expires"); + CHECK(cap_u_count(0x43u) == 0, "TC-IEC104-012 no TESTFR before t3 expires"); iec104_link_poll(&g_lk, now + 20001u); - CHECK(cap_u_count(0x43u) == 1, "TC-012 TESTFR act is sent once t3 expires"); + CHECK(cap_u_count(0x43u) == 1, "TC-IEC104-012 TESTFR act is sent once t3 expires"); iec104_link_poll(&g_lk, now + 20001u + 15001u); - CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-012 an unanswered TESTFR hits t1 -> IDLE"); + CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-012 an unanswered TESTFR hits t1 -> IDLE"); /* con 清挂起 → 不误杀 */ reset_all(now); handshake(now); iec104_link_poll(&g_lk, now + 20001u); - CHECK(cap_u_count(0x43u) == 1, "TC-012 after reconnect t3 fires again"); + CHECK(cap_u_count(0x43u) == 1, "TC-IEC104-012 after reconnect t3 fires again"); feed_u(0x83u, now + 20100u); /* TESTFR con */ iec104_link_poll(&g_lk, now + 35000u); - CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-012 once con clears the pending frame t1 does not kill it by mistake"); + CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-IEC104-012 once con clears the pending frame t1 does not kill it by mistake"); } static void tc_dc(void) @@ -743,13 +743,13 @@ static void tc_dc(void) n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u); feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.type == 52u) && (c.cot == IEC104_COT_ACTCON) - && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-013 the select phase answers actcon"); - CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 the select phase forwards the source"); + && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-IEC104-013 the select phase answers actcon"); + CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-IEC104-013 the select phase forwards the source"); body[0] = 0x02u; /* 执行 ON */ n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u); feed_i(a, n, now); CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u) - && (s_f_yk_sel == 0u), "TC-013 the execute phase answers actcon"); + && (s_f_yk_sel == 0u), "TC-IEC104-013 the execute phase answers actcon"); /* 失配:选 ON 执 OFF */ body[0] = 0x82u; n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u); @@ -758,16 +758,16 @@ static void tc_dc(void) n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u); feed_i(a, n, now); CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u), - "TC-013 a mismatching execute phase returns PN"); + "TC-IEC104-013 a mismatching execute phase returns PN"); /* 拒绝类(YK1):选择段即 PN */ body[0] = 0x82u; n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x6000u, body, 1u); feed_i(a, n, now); CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u), - "TC-013 a YK without output hardware returns PN in the select phase"); + "TC-IEC104-013 a YK without output hardware returns PN in the select phase"); } -/* TC-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */ +/* TC-IEC104-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */ static void tc_dc_yk12(void) { uint32_t now = 1000u; @@ -778,21 +778,21 @@ static void tc_dc_yk12(void) reset_all(now); handshake(now); - CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 soft plates are engaged by default"); + CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-IEC104-024 soft plates are engaged by default"); body[0] = 0x81u; /* S=1, DPC=OFF:选择段 */ n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u); feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.cot == IEC104_COT_ACTCON) - && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-024 the YK12 select phase answers actcon"); - CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the select phase performs no action"); + && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-IEC104-024 the YK12 select phase answers actcon"); + CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-IEC104-024 the select phase performs no action"); body[0] = 0x01u; /* 执行 OFF */ n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u); feed_i(a, n, now); CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), - "TC-024 the YK12 execute phase answers actcon"); - CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-024 the execute phase drives the soft plate to the released state"); - CHECK(prot_set_selfcheck() == 0, "TC-024 set_sealed seals atomically and selfcheck passes"); - CHECK(prot_set_dirty_peek() == 1u, "TC-024 the dirty flag is set for persistence"); + "TC-IEC104-024 the YK12 execute phase answers actcon"); + CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-IEC104-024 the execute phase drives the soft plate to the released state"); + CHECK(prot_set_selfcheck() == 0, "TC-IEC104-024 set_sealed seals atomically and selfcheck passes"); + CHECK(prot_set_dirty_peek() == 1u, "TC-IEC104-024 the dirty flag is set for persistence"); body[0] = 0x82u; /* 再投入:选择 ON */ n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u); feed_i(a, n, now); @@ -800,13 +800,13 @@ static void tc_dc_yk12(void) n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u); feed_i(a, n, now); CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), - "TC-024 the engage command executes with actcon"); - CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 the soft plate reaches the engaged state"); + "TC-IEC104-024 the engage command executes with actcon"); + CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-IEC104-024 the soft plate reaches the engaged state"); body[0] = 0x80u; /* DPC=00 非法:选择段即 PN */ n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u); feed_i(a, n, now); CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u), - "TC-024 an invalid DPC returns PN in the select phase"); + "TC-IEC104-024 an invalid DPC returns PN in the select phase"); } static void tc_se(void) @@ -820,27 +820,27 @@ static void tc_se(void) reset_all(now); handshake(now); - CHECK(addr == 0x8858u, "TC-014 the OC1 current setting communication address"); + CHECK(addr == 0x8858u, "TC-IEC104-014 the OC1 current setting communication address"); iec104_put_float(body, 5.0f); body[4] = IEC104_QDC_S; /* 选择段 */ n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u); feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.type == 50u) - && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-014 selecting a setting answers actcon"); + && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-IEC104-014 selecting a setting answers actcon"); body[4] = 0u; /* 执行段 */ n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u); feed_i(a, n, now); CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), - "TC-014 executing a setting answers actcon"); - CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-014 the setting really was written (real prot_set)"); - CHECK(prot_set_selfcheck() == 0, "TC-014 sealing after execute keeps selfcheck passing"); - CHECK(prot_set_dirty_peek() == 1u, "TC-014 the execute phase sets dirty (B7, waiting to be persisted)"); + "TC-IEC104-014 executing a setting answers actcon"); + CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-IEC104-014 the setting really was written (real prot_set)"); + CHECK(prot_set_selfcheck() == 0, "TC-IEC104-014 sealing after execute keeps selfcheck passing"); + CHECK(prot_set_dirty_peek() == 1u, "TC-IEC104-014 the execute phase sets dirty (B7, waiting to be persisted)"); iec104_put_float(body, 100000.0f); /* 越上限 */ body[4] = IEC104_QDC_S; n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u); feed_i(a, n, now); CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u), - "TC-014 an out-of-range select phase returns PN"); + "TC-IEC104-014 an out-of-range select phase returns PN"); } static void tc_rd(void) @@ -856,20 +856,20 @@ static void tc_rd(void) n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u); feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_REQ) - && (iec104_get_float(c.obj) == 222.0f), "TC-015 C_RD hits a YC group"); + && (iec104_get_float(c.obj) == 222.0f), "TC-IEC104-015 C_RD hits a YC group"); n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x8858u, NULL, 0u); feed_i(a, n, now); CHECK((cap_i(1, &c) == 0) && (c.type == 13u) && (c.ioa == 0x8858u), - "TC-015 C_RD hits a setting"); + "TC-IEC104-015 C_RD hits a setting"); n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x9999u, NULL, 0u); feed_i(a, n, now); CHECK((cap_i(2, &c) == 0) && (c.type == 102u) && (c.cot == IEC104_COT_UNK_IOA) && ((c.oa & 0x80u) != 0u), - "TC-015 an unknown IOA answers COT14 plus EVI"); + "TC-IEC104-015 an unknown IOA answers COT14 plus EVI"); n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0u, NULL, 0u); feed_i(a, n, now); CHECK((cap_i(3, &c) == 0) && (c.cot == IEC104_COT_UNK_IOA), - "TC-015 a local setting item (comm_addr=0) is not addressable (B7)"); + "TC-IEC104-015 a local setting item (comm_addr=0) is not addressable (B7)"); } static void tc_cs(void) @@ -888,18 +888,18 @@ static void tc_cs(void) feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.type == 103u) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u) && (s_clock_ms == e), - "TC-016 C_CS syncs the clock with an exact epoch"); + "TC-IEC104-016 C_CS syncs the clock with an exact epoch"); cp[2] |= 0x80u; /* IV */ n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u); feed_i(a, n, now); CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u), - "TC-016 CP56 IV -> PN"); + "TC-IEC104-016 CP56 IV -> PN"); (void)iec104_epoch_ms_to_cp56(e, cp); s_clock_rc = -1; /* 系统侧拒绝 */ n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u); feed_i(a, n, now); CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u), - "TC-016 a clock_sync failure -> PN"); + "TC-IEC104-016 a clock_sync failure -> PN"); } static void tc_err_resp(void) @@ -914,13 +914,13 @@ static void tc_err_resp(void) n = mk_asdu(a, 200u, 1u, IEC104_COT_ACT, 0u, 1u, 0u, NULL, 0u); feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.type == 200u) && (c.cot == IEC104_COT_UNK_COT) - && ((c.oa & 0x80u) != 0u), "TC-017 an unknown type answers COT12 plus EVI"); + && ((c.oa & 0x80u) != 0u), "TC-IEC104-017 an unknown type answers COT12 plus EVI"); m = iec104_asdu_put_hdr(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 2u); a[m] = IEC104_RQT_GROUP1; m++; feed_i(a, m, now); /* ca=2 ≠ 1 */ CHECK((cap_i(1, &c) == 0) && (c.cot == IEC104_COT_UNK_CA) - && ((c.oa & 0x80u) != 0u), "TC-017 a wrong common address answers COT13 plus EVI"); + && ((c.oa & 0x80u) != 0u), "TC-IEC104-017 a wrong common address answers COT13 plus EVI"); } static void tc_stopdt(void) @@ -934,13 +934,13 @@ static void tc_stopdt(void) handshake(now); feed_u(0x13u, now); /* STOPDT act */ CHECK((cap_u_count(0x23u) == 1) - && (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), "TC-018 STOPDT con"); + && (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), "TC-IEC104-018 STOPDT con"); body[0] = IEC104_RQT_GROUP1; n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u); feed_i(a, n, now); - CHECK(cap_i_count() == 0, "TC-018 in STOPPED state the data service is off"); + CHECK(cap_i_count() == 0, "TC-IEC104-018 in STOPPED state the data service is off"); feed_u(0x43u, now); - CHECK(cap_u_count(0x83u) == 1, "TC-018 in STOPPED state TESTFR is still answered"); + CHECK(cap_u_count(0x83u) == 1, "TC-IEC104-018 in STOPPED state TESTFR is still answered"); } static void tc_seq_err(void) @@ -954,7 +954,7 @@ static void tc_seq_err(void) m_ns = 5u; /* 期望 0,投 5 */ n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u); feed_i(a, n, now); - CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-019 a sequence error -> IDLE"); + CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-IEC104-019 a sequence error -> IDLE"); } static void tc_pts(void) @@ -965,23 +965,23 @@ static void tc_pts(void) reset_all(1000u); CHECK((iec104_pts_yc_count() == 9u) && (iec104_pts_yx_count() == 33u) - && (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-020 table size and IOA layout"); + && (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-IEC104-020 table size and IOA layout"); s_f_yc[0] = 7.5f; CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (v == 7.5f) && (q == 0u), - "TC-020 a valid YC"); + "TC-IEC104-020 a valid YC"); s_f_yc_rc[1] = -1; (void)iec104_pts_yc_read(1u, &v, &q); - CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-020 an invalid channel gives IV"); + CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-IEC104-020 an invalid channel gives IV"); s_f_yc_rc[2] = -2; (void)iec104_pts_yc_read(2u, &v, &q); - CHECK(q == IEC104_QDS_NT, "TC-020 not ready gives NT"); + CHECK(q == IEC104_QDS_NT, "TC-IEC104-020 not ready gives NT"); iec104_pts_yx_read(0u, &d); - CHECK(d == IEC104_DIQ_IV, "TC-020 an invalid telemetry point gives IV"); + CHECK(d == IEC104_DIQ_IV, "TC-IEC104-020 an invalid telemetry point gives IV"); CHECK((iec104_pts_yx_is_dp(0u) == 1u) && (iec104_pts_yx_is_dp(4u) == 0u), - "TC-020 the is_dp boundary"); + "TC-IEC104-020 the is_dp boundary"); s_f_bits = (1uLL << 15); iec104_pts_yx_read(15u, &d); - CHECK(d == 1u, "TC-020 a valid telemetry point gives ON"); + CHECK(d == 1u, "TC-IEC104-020 a valid telemetry point gives ON"); } static void tc_ts_rp(void) @@ -998,11 +998,11 @@ static void tc_ts_rp(void) n = mk_asdu(a, IEC104_T_C_TS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u); feed_i(a, n, now); CHECK((cap_i(0, &c) == 0) && (c.type == 104u) && (c.cot == IEC104_COT_ACTCON), - "TC-021 C_TS actcon"); + "TC-IEC104-021 C_TS actcon"); n = mk_asdu(a, IEC104_T_C_RP_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u); feed_i(a, n, now); CHECK((cap_i(1, &c) == 0) && (c.type == 105u) && (c.cot == IEC104_COT_ACTCON), - "TC-021 C_RP actcon"); + "TC-IEC104-021 C_RP actcon"); } static void tc_periodic(void) @@ -1013,10 +1013,10 @@ static void tc_periodic(void) reset_all(now); handshake(now); iec104_link_poll(&g_lk, now + 60001u); - CHECK(cap_i_count() == 2, "TC-022 the cyclic upload takes 2 frames"); + CHECK(cap_i_count() == 2, "TC-IEC104-022 the cyclic upload takes 2 frames"); CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_PERIODIC) - && (c.ioa == 0x4011u), "TC-022 the cyclic YC group uses COT=1"); - CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 the cyclic frequency point"); + && (c.ioa == 0x4011u), "TC-IEC104-022 the cyclic YC group uses COT=1"); + CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-IEC104-022 the cyclic frequency point"); } static void tc_cov_kfull(void) @@ -1033,11 +1033,11 @@ static void tc_cov_kfull(void) } s_f_bits |= (1uLL << 14); /* YX15 变位 */ iec104_link_poll(&g_lk, now + 100u); - CHECK(cap_i_count() == 12, "TC-023 with k full nothing spontaneous can go out"); + CHECK(cap_i_count() == 12, "TC-IEC104-023 with k full nothing spontaneous can go out"); feed_s(12u, now + 200u); /* 窗开 */ iec104_link_poll(&g_lk, now + 300u); CHECK((cap_i_count() == 13) && (cap_i(12, &c) == 0) && (c.ioa == 15u) - && (c.cot == IEC104_COT_SPONT), "TC-023 once the window opens the backlogged event is sent, none lost"); + && (c.cot == IEC104_COT_SPONT), "TC-IEC104-023 once the window opens the backlogged event is sent, none lost"); } /* ========== 卡 9/卡 11:板端默认源(真实 L2 链:ring→phasor→freq→meas) ========== */ diff --git a/test/host/iec104_master.py b/test/host/iec104_master.py index cd8020ed..a8dcdf0e 100644 --- a/test/host/iec104_master.py +++ b/test/host/iec104_master.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """B6 bench leg: IEC 60870-5-104 master client (GI + COV round-trip). -Task card: docs/任务规划/采集保护收尾.md (B6 DoD); protocol baseline: +Historical bench leg; current acceptance index: docs/任务规划/登记簿.md §7. docs/子系统设计/IEC104上行设计说明.md §3/§4. Constants mirror App/protocol/iec104/iec104_asdu.h / iec104_link.h. @@ -33,8 +33,8 @@ Usage: python iec104_master.py [--ip 192.168.1.100] [--port 2404] [--addr 0x8858] [--low 0.05] [--restore 10.0] -B7 bench leg modes (setpoint persistence + global soft plate, task card -docs/任务规划/采集保护收尾.md B7; firmware side App/cfg/prot_set_store.c): +B7 bench leg modes (setpoint persistence + global soft plate; historical +bench leg, firmware side App/cfg/prot_set_store.c): --b7-pre : before reset: mark setpoint 7.5 (C_SE+C_RD), YK12 OFF (two-step) -> low setpoint 0.05 must NOT trip YX16 (plate OUT disables protection); YK12 ON -> YX16 trips (linkage); restore 10.0; diff --git a/test/host/latency_peer.py b/test/host/latency_peer.py index dc255412..f8c812cf 100644 --- a/test/host/latency_peer.py +++ b/test/host/latency_peer.py @@ -19,7 +19,7 @@ Both the serial log and this JSON carry HOST timestamps -> direct correlation. Usage: python latency_peer.py --host 192.168.1.100 --port 8080 --json m2_net.json - python latency_peer.py --mode net --ftp-stress ... (E-02 head-of-line repro) + python latency_peer.py --mode net --ftp-stress ... (head-of-line repro) Pure stdlib (socket/json/time/argparse/threading). ASCII output only. """ @@ -61,7 +61,7 @@ REMO_CMD_KEY = 2 REMO_CMD_LCDMEM = 3 REMO_MEMSIZE = 3200 # 160x160 1bpp REMO_LCDMEM_FRAME = 4 + 4 + REMO_MEMSIZE + 1 # 3209 (header + offset + bitmap + CRC) -REMO_CYCLE_DEADLINE_S = 45.0 # per-cycle recv deadline (E-02 stress: up to 30s) +REMO_CYCLE_DEADLINE_S = 45.0 # per-cycle recv deadline (head-of-line stress: up to 30s) def now_ns(): @@ -307,13 +307,13 @@ class RemoPeer: "payload_len": len(f["data"])} return ms - # --- FTP head-of-line stress (E-02) --- + # --- FTP head-of-line stress --- def ftp_stress_worker(self, start_evt, stop_evt, hold_s): """Wait for the stress window to open, then hold an idle FTP login: lftpd read_line -> blocking net_recv(0) inside netTask (old 30s default budget NET_RECV_TIMEOUT_MS, removed in self-heal phase 1.5; today the wait is an L3 pending recv -> this scenario must NOT freeze the queue anymore) - -> every queued message (incl. the LCDMEM response) waits behind it (E-02).""" + -> every queued message (incl. the LCDMEM response) waits behind it.""" if not start_evt.wait(120.0): sys.stdout.write("[peer] ftp stress: window never opened\n") return @@ -362,7 +362,7 @@ class RemoPeer: for i in range(self.samples): if self.stress_from is not None and i == self.stress_from: - print("[peer] === FTP stress ON (E-02 head-of-line) at cycle %d ===" % i) + print("[peer] === FTP stress ON (head-of-line) at cycle %d ===" % i) stress_start.set() time.sleep(0.5) # let the FTP half-login block netTask @@ -568,7 +568,7 @@ class Peer: def ftp_stress_worker(self, start_evt, stop_evt): """Hold an idle FTP login: lftpd read_line -> blocking net_recv(0) in - netTask (30s timeout) -> all queued messages wait behind it (E-02).""" + netTask (30s timeout) -> all queued messages wait behind it.""" try: s = socket.create_connection((self.host, 21), timeout=10.0) s.settimeout(10.0) @@ -790,7 +790,7 @@ def main(argv=None): help="max seconds to retry connect (device boots after flash)") ap.add_argument("--json", default=None, help="write JSON report to this path") ap.add_argument("--ftp-stress", action="store_true", - help="hold idle FTP login (E-02 head-of-line repro)") + help="hold idle FTP login (head-of-line repro)") ap.add_argument("--samples", type=int, default=50, help="remo mode: LCDMEM frame cycles to measure (default 50)") ap.add_argument("--cadence-ms", type=int, default=100, diff --git a/test/host/n30_repro.py b/test/host/n30_repro.py index a82db06e..6fcac6f2 100644 --- a/test/host/n30_repro.py +++ b/test/host/n30_repro.py @@ -1,4 +1,4 @@ -# N-30 确定性复现:连续快速 STOR(无间隔),统计 425/超时失败次数 +# relisten 沉降段原语化受阻的确定性复现:连续快速 STOR(无间隔),统计 425/超时失败次数 # 用法: python n30_repro.py [--host IP] [--user U --pwd P] [--n 10] [--size 1024] # 退出码: 0=全部成功 / 1=有失败 / 2=连接错误 import argparse diff --git a/test/host/nand_port/nand_port_test.c b/test/host/nand_port/nand_port_test.c index 93e08593..a7dbd821 100644 --- a/test/host/nand_port/nand_port_test.c +++ b/test/host/nand_port/nand_port_test.c @@ -7,13 +7,13 @@ * 为什么要主机侧单测(而不是只用板端套件): * 移植层(dhara HAL 六个入口)是**纯地址算术 + 边界判定**,与 SPI 时序无关; * 而它要防的两类缺陷恰恰是真机**极难复现**的: - * 1. N-12:is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 + * 1. is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 * → dhara 往已编程页再编程 = 静默数据丢失。真机上要恰好撞上那种页形态 * 才发作,且发作后只表现为"某次复位后丢数据",无从定位。 - * 2. N-13:越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 + * 2. 越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 * → 静默读写/搬走无辜数据。真机上没有任何一条路径会主动喂进越界号, * 只有被污染的 meta 或将来改动才会触发——主机侧一次构造即钉死。 - * 假芯片把"双编程"做成**可数事件**(N-12 探针),并记录全部访问次数, + * 假芯片把"双编程"做成**可数事件**(is_free 精度回归探针),并记录全部访问次数, * 于是"越界不产生任何芯片访问""越界标坏块次数为 0"这类判据可机械断言 * ——这是板端永远拿不到的观测面。 * @@ -22,10 +22,10 @@ * * 用例前缀:TC-NP-NNN(NP = NAND Port);分组: * TC-NANDPORT-000 假芯片自洽(几何 / 上电态) - * TC-NANDPORT-100..108 dhara_nand_is_free 整页扫描(N-12) - * TC-NANDPORT-110..112 dhara_nand_is_free 越界守卫(N-13) + * TC-NANDPORT-100..108 dhara_nand_is_free 整页扫描 + * TC-NANDPORT-110..112 dhara_nand_is_free 越界守卫 * TC-NANDPORT-200..207 dhara_nand_prog(越界 / NULL / 往返 / 双编程探针 / 失败注入) - * TC-NANDPORT-300..307 dhara_nand_read(越界 / 越窗 offset+length / 部分读 / ECC / E-42) + * TC-NANDPORT-300..307 dhara_nand_read(越界 / 越窗 offset+length / 部分读 / ECC / 读失败保守判据) * TC-NANDPORT-400..405 dhara_nand_copy(src 与 dst 双端守卫 / 正常搬页) * TC-NANDPORT-500..504 dhara_nand_erase(越界块零标坏块 / 整块回 0xFF / 失败注入) * TC-NANDPORT-600..605 dhara_nand_is_bad / dhara_nand_mark_bad(越界保守判坏、BBT 不变) @@ -61,7 +61,7 @@ int g_nandtest_debug_count; * - 写:gd5f_program_load 只写"数据寄存器",gd5f_program_exec 才落到阵列, * 且落盘是 **NOR 叠加**(new = old & written)——真实 NAND 的位语义; * - 严格探针:exec 时若目标页已有非 0xFF 字节,记一次**双编程违规** - * (g_fake_double_program_cnt)。这是抓 N-12 回归的核心:任何 + * (g_fake_double_program_cnt)。这是抓 is_free 整页精度回归的核心:任何 * "把已编程页判成空闲"的实现都会让它立刻非 0; * - 擦除:整块回 0xFF;坏块表是 RAM 位图,mark_bad 记录次数与块号序列。 */ @@ -93,7 +93,7 @@ static unsigned long g_fake_is_bad_cnt; static unsigned long g_fake_mark_bad_cnt; static unsigned long g_fake_isfree_scan_cnt; /* 64 B 列读次数(is_free 指纹) */ -/* N-12 探针:往已编程页再编程的次数与页号 */ +/* 双编程探针:往已编程页再编程的次数与页号 */ static unsigned long g_fake_double_program_cnt; static uint32_t g_fake_last_double_program_page; @@ -191,7 +191,7 @@ int32_t gd5f_program_exec(uint32_t page_addr) { return GD5F_PROGRAM_FAIL; } - /* 严格语义探针:目标页已有非 0xFF 字节 = 双编程(N-12 的现场证据) */ + /* 严格语义探针:目标页已有非 0xFF 字节 = 双编程(is_free 误判的现场证据) */ for (i = 0; i < FAKE_PAGE_SIZE; i++) { if (s_chip[page_addr][i] != 0xFFu) { g_fake_double_program_cnt++; @@ -423,9 +423,9 @@ static struct dhara_nand s_ut_nand; #define UT_OOB_PAGE (UT_TOTAL_PAGES) /* 第一个越界页 */ #define UT_OOB_BLOCK ((dhara_block_t)s_ut_nand.num_blocks) -/* ===================== 旧算法对照(N-12 的"牙齿") ===================== +/* ===================== 旧算法对照(is_free 误判的"牙齿") ===================== * - * N-12 修复**之前**的 dhara_nand_is_free:只读页首 64 B 就回答"空闲"。 + * 整页精度修复**之前**的 dhara_nand_is_free:只读页首 64 B 就回答"空闲"。 * 本函数按当时的形状内联一份(同样经假芯片的两段式读),用来证明本套件**有牙**: * 对同一页,旧算法答 1(误判空闲)、新实现答 0。将来有人把 is_free 改回只看 * 页首,TC-NANDPORT-105 立刻变红——而不是"测试跟着实现一起改回去"。 @@ -451,7 +451,7 @@ static int old_is_free_first64(const struct dhara_nand *n, dhara_page_t p) { /* ===================== TC-NANDPORT-100..108:dhara_nand_is_free(整页扫描) ===================== * - * N-12:旧实现只看页首 64 B,会把"页首 0xFF、后段有数据"的已编程页误判空闲 + * 旧实现只看页首 64 B,会把"页首 0xFF、后段有数据"的已编程页误判空闲 * -> dhara 往已编程页再编程 = 数据丢失。本组把"必须扫完整页"钉死, * 并用 TC-NANDPORT-105 的旧算法对照证明这组断言真的能咬人。 */ @@ -465,7 +465,7 @@ static void tc_is_free(void) { CHECK(dhara_nand_is_free(&s_ut_nand, 0u) == 1, "erased page -> is_free = 1"); CHECK(g_fake_page_read_cnt == 1u, "one page_read only (no per-byte commands)"); CHECK(g_fake_read_cache_cnt == (FAKE_PAGE_SIZE / 64u), - "erased page scans the whole page = 32 column reads (N-12 evidence)"); + "erased page scans the whole page = 32 column reads (whole-page scan evidence)"); CHECK(g_fake_isfree_scan_cnt == (FAKE_PAGE_SIZE / 64u), "64B column-read fingerprint calibrated (proves scenario really calls is_free)"); @@ -479,16 +479,16 @@ static void tc_is_free(void) { CHECK(g_fake_read_cache_cnt == 1u, "early exit on first chunk = fast path (same cost as the old impl)"); - /* TC-NANDPORT-102:N-12 回归用例 + /* TC-NANDPORT-102:is_free 整页精度回归用例 * 页首 64 B 全 0xFF、第 1000 字节 = 0x00 -> 必须答"非空"。 * 旧实现只看页首 64 B 会误判空闲,dhara 遂往这一页再编程。 */ - printf("[TC-NANDPORT-102] N-12 regression: hole in first 64B, data at byte 1000\n"); + printf("[TC-NANDPORT-102] is_free regression: hole in first 64B, data at byte 1000\n"); fake_chip_reset(); (void)memset(page_buf, 0xFF, sizeof(page_buf)); page_buf[1000] = 0x00u; fake_poke_page(2u, page_buf, FAKE_PAGE_SIZE); CHECK(dhara_nand_is_free(&s_ut_nand, 2u) == 0, - "first 64B all 0xFF, byte 1000 = 0x00 -> is_free = 0 (N-12)"); + "first 64B all 0xFF, byte 1000 = 0x00 -> is_free = 0 (whole-page scan)"); CHECK(g_fake_read_cache_cnt > 1u, "scan continues past the first 64B (old impl stopped here = root cause)"); @@ -525,7 +525,7 @@ static void tc_is_free(void) { CHECK(dhara_nand_is_free(&s_ut_nand, 5u) == 0, "new implementation answers not-free -> 0 (opposite verdict = teeth)"); - /* TC-NANDPORT-106:读失败保守判非空(E-42 同族) */ + /* TC-NANDPORT-106:读失败保守判非空 */ printf("[TC-NANDPORT-106] read failure is answered conservatively\n"); fake_chip_reset(); s_fail_page_read = 1; @@ -534,7 +534,7 @@ static void tc_is_free(void) { fake_chip_reset(); s_fail_read_cache = 1; CHECK(dhara_nand_is_free(&s_ut_nand, 7u) == 0, - "read_from_cache failure -> not free (E-42 family)"); + "read_from_cache failure -> not free (conservative)"); /* TC-NANDPORT-107:NULL / num_blocks = 0 的几何守卫 */ printf("[TC-NANDPORT-107] NULL geometry / zero blocks guard\n"); @@ -557,9 +557,9 @@ static void tc_is_free(void) { CHECK(dhara_nand_is_free(&s_ut_nand, 8u) == 1, "after erase -> free (fake chip sane)"); } -/* ===================== TC-NANDPORT-110..112:is_free 越界守卫(N-13) ===================== */ +/* ===================== TC-NANDPORT-110..112:is_free 越界守卫 ===================== */ static void tc_is_free_oob(void) { - printf("[TC-NANDPORT-110] is_free out-of-range pages (N-13)\n"); + printf("[TC-NANDPORT-110] is_free out-of-range pages\n"); fake_chip_reset(); CHECK(dhara_nand_is_free(&s_ut_nand, DHARA_PAGE_NONE) == 0, @@ -605,7 +605,7 @@ static void tc_prog(void) { /* TC-NANDPORT-201:DHARA_PAGE_NONE -> -1 + CORRUPT_MAP + 零写入 * 旧实现会把 0xffffffff 加偏移送进 SPI 命令,被地址位截断成某个真实页 * -> 静默写坏无辜数据;现在必须在守卫处就挡住。 */ - printf("[TC-NANDPORT-201] prog DHARA_PAGE_NONE -> -1, zero write (N-13)\n"); + printf("[TC-NANDPORT-201] prog DHARA_PAGE_NONE -> -1, zero write\n"); fake_chip_reset(); err = DHARA_E_NONE; CHECK(dhara_nand_prog(&s_ut_nand, DHARA_PAGE_NONE, page_buf, &err) == -1, @@ -746,8 +746,8 @@ static void tc_read(void) { "check_ecc failure -> -1"); CHECK(err == DHARA_E_ECC, "check_ecc failure -> DHARA_E_ECC"); - /* TC-NANDPORT-307:E-42 同族——read_from_cache 失败必须 -1(旧实现返回 0 交垃圾) */ - printf("[TC-NANDPORT-307] E-42: read_from_cache failure must not report success\n"); + /* TC-NANDPORT-307:同一保守判据——read_from_cache 失败必须 -1(旧实现返回 0 交垃圾) */ + printf("[TC-NANDPORT-307] read_from_cache failure must not report success\n"); prep_content_page(page_buf, 21u); s_fail_read_cache = 1; err = DHARA_E_NONE; @@ -933,7 +933,7 @@ static void tc_bad_block(void) { * 用真实 dhara_map_* 跑一轮"写入 -> sync -> 重新 resume -> 读回",断言: * - 数据往返一致; * - **全程双编程计数为 0**:is_free 一旦退回"只看页首 64 B",journal 会把 - * 已编程的检查点页当空闲再编程一次,这个计数立刻非 0(N-12 的场景级证据)。 + * 已编程的检查点页当空闲再编程一次,这个计数立刻非 0(场景级证据)。 */ static void tc_dhara_scenario(void) { static uint8_t wr[FAKE_PAGE_SIZE]; @@ -978,7 +978,7 @@ static void tc_dhara_scenario(void) { CHECK(rc == 0, "read sector 1 after resume -> 0"); CHECK(memcmp(rd, wr, FAKE_PAGE_SIZE) == 0, "sector 1 data survives the resume"); CHECK(g_fake_double_program_cnt == 0u, - "write + resume scenario: zero double-program violations (N-12 scenario guard)"); + "write + resume scenario: zero double-program violations (scenario guard)"); } /* ===================== main ===================== */ @@ -999,10 +999,10 @@ int main(void) { CHECK(UT_TOTAL_PAGES == ((dhara_page_t)FAKE_USABLE_BLOCKS * FAKE_PAGES_PER_BLOCK), "TC-NANDPORT-000c guard bound = num_blocks << log2_ppb"); - printf("-- dhara_nand_is_free: whole-page scan (N-12) --\n"); + printf("-- dhara_nand_is_free: whole-page scan --\n"); tc_is_free(); - printf("-- dhara_nand_is_free: out-of-range guard (N-13) --\n"); + printf("-- dhara_nand_is_free: out-of-range guard --\n"); tc_is_free_oob(); printf("-- dhara_nand_prog --\n"); diff --git a/test/host/nand_port/run_unit_test.py b/test/host/nand_port/run_unit_test.py index cefa25eb..25166aef 100644 --- a/test/host/nand_port/run_unit_test.py +++ b/test/host/nand_port/run_unit_test.py @@ -14,9 +14,9 @@ 为什么要主机侧单测(而不是只用板端套件): 移植层是**纯地址算术 + 边界判定**,芯片侧只经 11 个 SPI 原语打交道——把它换成 一块 RAM 假芯片,整层就完全可在 PC 上跑。而它要防的两类缺陷恰恰是真机极难复现的: - - N-12:is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 → + - is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲 → dhara 往已编程页再编程 = 静默数据丢失(真机要恰好撞上那种页形态才发作); - - N-13:越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 → + - 越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页 → 静默读写/搬走无辜数据(真机上没有任何路径会主动喂进越界号)。 假芯片把"双编程"做成可数事件、并记录全部访问次数,于是"越界不产生任何芯片 访问""越界标坏块次数为 0"这类判据可机械断言——这是板端永远拿不到的观测面。 @@ -27,7 +27,7 @@ 退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。 -设计依据:原评审报告 N-12/N-13(该报告 2026-10-25 已删除;报告 R-3 建议的 +设计依据:dhara 移植层两条硬约束(原评审报告 2026-10-25 已删除;其建议的 `test/host/dhara/` 移植层单测,落位为 test/host/nand_port/); 创建日期:2026-10-15 """ @@ -50,7 +50,7 @@ BUILD = TestBuild( src=os.path.join(HERE, "nand_port_test.c"), include_dirs=[ os.path.join(HERE, "stub"), - os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(nand_ftl.c 依赖,2026-10-23 S-5) + os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(nand_ftl.c 依赖,2026-10-23) os.path.join(ROOT, "Drivers", "BSP", "GD5F2GQ5UE"), os.path.join(ROOT, "Lib", "dhara"), os.path.join(ROOT, "Lib", "FatFs"), diff --git a/test/host/net_cfg/net_cfg_test.c b/test/host/net_cfg/net_cfg_test.c index 5143bd91..42741e9f 100644 --- a/test/host/net_cfg/net_cfg_test.c +++ b/test/host/net_cfg/net_cfg_test.c @@ -38,7 +38,7 @@ * TC-NETCFG-004 未 load 时 mac = 全零(哨兵 = 不覆盖芯片内置 MAC) * —— 注册与 load(TC-NETCFG-005..011)—— * TC-NETCFG-005 首次 load 注册 [network] 段恰好一次(且 load 返回 0) - * TC-NETCFG-006 首次 load 一并注册 [ftp] 段(N-19:所有受管段先注册、后 load) + * TC-NETCFG-006 首次 load 一并注册 [ftp] 段(FTP 鉴权:所有受管段先注册、后 load) * TC-NETCFG-007 重复 load 不重复注册(幂等;否则引擎按"段名重复"拒登 → 静默失配) * TC-NETCFG-008 注册描述符内容:段名/尺寸/键数/默认值指针/变更回调/数据指针 = RAM 副本 * TC-NETCFG-009 load 成功:返回 0 且留一条 INFO、不误报 ERROR diff --git a/test/host/net_select/net_select_test.c b/test/host/net_select/net_select_test.c index 81b14b89..0cbc6391 100644 --- a/test/host/net_select/net_select_test.c +++ b/test/host/net_select/net_select_test.c @@ -205,7 +205,7 @@ static void tc_timeout(void) NET_FD_SET(0, &r); rc = net_select(1, &r, NULL, NULL, NULL); /* timeout = NULL:无限等 */ CHECK((rc == 1) && (g_delay_calls == 3) && NET_FD_ISSET(0, &r), - "TC-NETSEL-006 infinite wait wakes on a peer event (round 3)"); + "TC-NETSEL-006 infinite wait wakes on a peer event"); reset_all(); make_sock(0, NET_SOCK_STATE_ESTABLISHED, 0u, 0u); diff --git a/test/host/net_socket/net_socket_test.c b/test/host/net_socket/net_socket_test.c index a16ccbf9..1d695a0b 100644 --- a/test/host/net_socket/net_socket_test.c +++ b/test/host/net_socket/net_socket_test.c @@ -8,14 +8,14 @@ * alloc_socket/net_get_sock/net_sock_raw 的边界、net_socket() 的"新 fd 不得继承"、 * net_bind 的动态端口与回绕、getsockname/getpeername、setblocking/set_event_cb/ * sock_state、net_poll 未初始化短路。 - * 分片 2 每任务错误码槽(E-03)+ 队列句柄绑定(adopt 幂等):内核未跑→[0] 槽退化、 + * 分片 2 每任务错误码槽 + 队列句柄绑定(adopt 幂等):内核未跑→[0] 槽退化、 * 不同任务不同槽位互不覆写、槽位用尽回退 [0]、临界区成对且不嵌套泄漏。 * 分片 3 跨上下文消息面:八个 msg.type 全分派 + 结果经**任务通知值**回传 + * "登记挂起即不发即时回执"(do_notify=0)+ 失败时把 netTask 自己槽位的精确 * 错误码搭通知值带回来 + 调用方在自己的槽位里恢复 errno。 * 分片 4 状态机与闸门:监听门控 net_listener_armed 三支放行/其余挡下、net_can_read、 * net_poll 的"全局位命中才读本 Socket SINT、永不读全局"(B3/F-NET-7)、 - * 事件计数、每轮全量刷新驱动缓存 + sock_reset 步进(I1/E-02)。 + * 事件计数、每轮全量刷新驱动缓存 + sock_reset 步进(I1/头阻塞)。 * 分片 5 读写错误分支:send 分块上限/短写/挂起登记/末块不登记(NET_PENDING 拒收边界)、 * recv 快路径与 EOF vs WOULDBLOCK、connect 命令被拒的回滚、accept 的 * closed-with-readable 排空接受、close 的先结算再关芯片。 @@ -291,7 +291,7 @@ static void tc_104_socket_no_inherit(void) /* 三条"新 fd 不得继承"的定案,逐条钉(都是真机上静默出错的形态) */ CHECK(p->blocking == 1U, "TC-NETSOCK-104e blocking must default to explicitly 1 (BSD semantics)"); CHECK(p->released_abnormal == 0U, "TC-NETSOCK-104f must not inherit the previous release reason (TRAP-NET-18)"); - CHECK(p->session_dead == 0U, "TC-NETSOCK-104g must not inherit the previous session-dead level (N-32 P1)"); + CHECK(p->session_dead == 0U, "TC-NETSOCK-104g must not inherit the previous session-dead level (listener lifecycle P1)"); CHECK(g_peers.listener_clear_calls == 1 && g_peers.pending_clear_calls == 1 && g_peers.send_clear_calls == 1, "TC-NETSOCK-104h new fd clears listener retry and both pending tables (once each)"); @@ -581,7 +581,7 @@ static void tc_110_queue_adopt(void) reset_all(); } -/* ===================== 分片 2 每任务错误码槽(E-03) ===================== */ +/* ===================== 分片 2 每任务错误码槽 ===================== */ static void tc_120_errno_slot_per_task(void) { @@ -605,7 +605,7 @@ static void tc_120_errno_slot_per_task(void) (void)net_set_errno(NET_ERR_TIMEDOUT); g_ns_cur_task = (TaskHandle_t)&g_ns_task_net; CHECK(net_get_errno() == NET_OK, - "TC-NETSOCK-120c another task cannot read the caller code (the positive wording of E-03)"); + "TC-NETSOCK-120c another task cannot read the caller code (positive wording)"); (void)net_set_errno(NET_ERR_WOULDBLOCK); CHECK(net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-120d a task reads its own code"); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; diff --git a/test/host/net_socket/ns_s3_msg.c b/test/host/net_socket/ns_s3_msg.c index 68394dbc..8362c1f1 100644 --- a/test/host/net_socket/ns_s3_msg.c +++ b/test/host/net_socket/ns_s3_msg.c @@ -224,7 +224,7 @@ static void tc_213_send_error_code_roundtrip(void) rc = net_send(6, buf, 4, 0); /* 6 未分配 → 分派侧 BADF → 通知值 -2 */ CHECK(rc == -1, "TC-NETSOCK-213c BSD semantics kept for callers: any failure is -1"); CHECK(net_get_errno() == NET_ERR_BADF, - "TC-NETSOCK-213d caller reads the exact code from its own errno slot (E-03 plus notify value together)"); + "TC-NETSOCK-213d caller reads the exact code from its own errno slot (errno slot plus notify value together)"); reset_all(); } @@ -313,7 +313,7 @@ static void tc_222_recv_eof_relisten(void) g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT); CHECK(rc == 0, - "TC-NETSOCK-222d still EOF after relisten moved it back to LISTENING (N-32 P1 level, else EOF vanishes forever)"); + "TC-NETSOCK-222d still EOF after relisten moved it back to LISTENING (listener lifecycle P1 level, else EOF vanishes forever)"); /* auto_relisten=0(客户端 fd):EOF 不登记重建,交调用方决定重连 */ reset_all(); @@ -553,7 +553,7 @@ static void tc_240_drop_conn_branches(void) CHECK(g_peers.listener_settle_calls == 1 && g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE && g_fxchip.rst_begin_calls == 0, - "TC-NETSOCK-240f listener teardown is REGISTERED for the tick machine (QUARANTINE settle + REQ_DROP) - the handler starts no reset machine and never waits (N-17)"); + "TC-NETSOCK-240f listener teardown is REGISTERED for the tick machine (QUARANTINE settle + REQ_DROP) - the handler starts no reset machine and never waits (heartbeat gap fix)"); CHECK(g_peers.listener_req_calls == 1 && g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP, "TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)"); diff --git a/test/host/net_socket/ns_s4_state.c b/test/host/net_socket/ns_s4_state.c index fd2211a2..8654a3f8 100644 --- a/test/host/net_socket/ns_s4_state.c +++ b/test/host/net_socket/ns_s4_state.c @@ -275,7 +275,7 @@ static void tc_320_close_two_paths(void) } /* TC-NETSOCK-321 drop_conn:监听型 = 登记式(settle QUARANTINE + REQ_DROP, - * N-17 起 netTask 零占用);非监听 client fd 保留同步排渣;两者都不动监听簿记 */ + * 探针窗口心跳缺口修复起 netTask 零占用);非监听 client fd 保留同步排渣;两者都不动监听簿记 */ static void tc_321_drop_conn_settle(void) { int fd; @@ -292,9 +292,9 @@ static void tc_321_drop_conn_settle(void) g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE, "TC-NETSOCK-321b listener fd: abnormal-release isolation REGISTERED for the tick machine (TRAP-NET-18 QUARANTINE semantics survive, executed by net_listener_tick)"); CHECK(g_fxchip.rst_begin_calls == 0 && g_ns_error_count == 0, - "TC-NETSOCK-321b2 N-17: the handler starts no synchronous reset and logs nothing - netTask keeps its ~1ms beat"); + "TC-NETSOCK-321b2 heartbeat gap fix: the handler starts no synchronous reset and logs nothing - netTask keeps its ~1ms beat"); CHECK(s_net_socks[fd].lost_conn_seen == 0U, - "TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (N-30)"); + "TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (relisten settle)"); CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U && s_net_socks[fd].in_use == 1U, "TC-NETSOCK-321d port, listener attribute and fd all survive: this is drop-the-connection-not-the-listener"); @@ -305,7 +305,7 @@ static void tc_321_drop_conn_settle(void) "TC-NETSOCK-321f session bookkeeping closes out: clear readable and move to CLOSED"); reset_all(); - /* 非监听 client fd:无监听可保,同步排渣形态原样保留(N-17 只改监听型) */ + /* 非监听 client fd:无监听可保,同步排渣形态原样保留(心跳缺口修复只改监听型) */ bring_up(); fd = mk_established(NET_SOCK_STREAM); g_fake.cached_open[fd] = CH395F_SOCKET_OPEN; diff --git a/test/host/net_socket/ns_s5_io.c b/test/host/net_socket/ns_s5_io.c index ceec34ac..3390e8a7 100644 --- a/test/host/net_socket/ns_s5_io.c +++ b/test/host/net_socket/ns_s5_io.c @@ -255,7 +255,7 @@ static void tc_420_accept_shapes(void) g_peers.lost_probe_hit = 1; addrlen = 0; CHECK(net_accept(fd, NULL, NULL) == fd, - "TC-NETSOCK-420d the probe moved the listener to ESTABLISHED -> accept succeeds at once (N-30 root fix)"); + "TC-NETSOCK-420d the probe moved the listener to ESTABLISHED -> accept succeeds at once (relisten settle root fix)"); CHECK(g_peers.lost_probe_calls == 1, "TC-NETSOCK-420e only LISTENING calls the probe (once per accept round, with debounce)"); @@ -281,7 +281,7 @@ static void tc_420_accept_shapes(void) ch395f_fake_push_recv_len((uint8_t)fd, 1027U); addrlen = (int)sizeof(in); CHECK(net_accept(fd, sa, &addrlen) == fd, - "TC-NETSOCK-420h closed-with-readable: accept this connection to drain it (N-30, reversed yields 425)"); + "TC-NETSOCK-420h closed-with-readable: accept this connection to drain it (relisten settle, reversed yields 425)"); CHECK(g_peers.listener_req_calls == 0, "TC-NETSOCK-420i with leftover data present, do NOT register a rebuild (relisting first destroys arrived data)"); CHECK(g_chip.get_status_calls == 0, @@ -299,7 +299,7 @@ static void tc_420_accept_shapes(void) CHECK(g_peers.listener_req_calls == 1 && g_peers.last_req_reason == (int)NET_LISTENER_REQ_ACCEPT && g_peers.last_req_fd == fd, - "TC-NETSOCK-420l only a consumer hitting empty registers an immediate rebuild (N-32 P2: removing it widens the CLOSED window)"); + "TC-NETSOCK-420l only a consumer hitting empty registers an immediate rebuild (listener lifecycle P2: removing it widens the CLOSED window)"); CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0, "TC-NETSOCK-420m the registration path costs zero chip transactions (rebuild lives only in net_listener_tick)"); @@ -554,7 +554,7 @@ static void tc_460_reset_sync_timeout_bound(void) "TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)"); reset_all(); - /* N-17:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回 + /* 探针窗口心跳缺口修复:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回 * CLOSED 也与本调用无关(排渣与等待归 net_listener_tick 的隔离机) */ bring_up(); fd = mk_established(NET_SOCK_STREAM); @@ -564,7 +564,7 @@ static void tc_460_reset_sync_timeout_bound(void) t_before = g_ns_tick; CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 20U && g_ns_error_count == 0 && g_peers.listener_settle_calls == 1, - "TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (N-17)"); + "TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (heartbeat gap fix)"); reset_all(); /* 非监听 client fd 保留旧超时线(同步、600ms 封顶、留痕) */ diff --git a/test/host/net_socket/run_unit_test.py b/test/host/net_socket/run_unit_test.py index 24fba3e6..454f0c4a 100644 --- a/test/host/net_socket/run_unit_test.py +++ b/test/host/net_socket/run_unit_test.py @@ -6,7 +6,7 @@ 用本机 MSVC 编译 test/host/net_socket/net_socket_test.c(以 #include 纳入生产源 Drivers/BSP/NET/net_socket.c + 共享假芯片 test/host/common/ch395f_fake.c + 本目录的假内核 ns_rtos.c 与兄弟模块桩 ns_peers.c),在 PC 上离线验证: - 跨上下文消息全分派、结果经任务通知值回传、每任务错误码槽(E-03)、 + 跨上下文消息全分派、结果经任务通知值回传、每任务错误码槽、 监听门控与 closed-with-readable、net_poll 的中断分派与"只读自己 SINT", 以及读写路径的短写 / 挂起登记 / EOF / 命令被拒回滚。 工具链与判定委托 test/host/common/unit_test_harness.py。 @@ -20,7 +20,7 @@ (net_msg_t 是按值入队的,netTask 改自己那份副本,调用方永远看不到——AGENTS 模块约定明写"勿给 net_msg_t 加回传字段")。这条链的正确性取决于两个任务的 交错时序,真机上"通知丢了"表现为永久挂死并被看门狗复位,日志里只剩一条 - [ERR];而错误码本身还按任务隔离(E-03),跨任务覆写只会让排障读到别人的码。 + [ERR];而错误码本身还按任务隔离,跨任务覆写只会让排障读到别人的码。 把队列/通知/时基做成显式量后,"登记了挂起却仍发即时回执"这种缺陷在 PC 上 一次就能钉死。 diff --git a/test/host/sha256/run_unit_test.py b/test/host/sha256/run_unit_test.py index 1ccb432b..53c71982 100644 --- a/test/host/sha256/run_unit_test.py +++ b/test/host/sha256/run_unit_test.py @@ -11,7 +11,7 @@ `test/host/common/unit_test_harness.py`(本文件只做声明)。 为什么要主机侧单测(而不是只用板端): - sha256 是 FTP pwd_hash 校验(N-19)的地基——哈希算错是"正确口令被拒 / + sha256 是 FTP 鉴权的 pwd_hash 校验地基——哈希算错是"正确口令被拒 / 错误口令放行"的静默语义错误,板端只见 530/230 看不出根因;标准向量 (空串/abc/56B 补位跨界/1MB 长度进第二分组)主机侧一次钉死。 本模块零平台依赖,无需任何桩。 diff --git a/test/host/sha256/sha256_test.c b/test/host/sha256/sha256_test.c index d28a64bd..acafb017 100644 --- a/test/host/sha256/sha256_test.c +++ b/test/host/sha256/sha256_test.c @@ -7,7 +7,7 @@ * 保证测的是生产代码而非副本;本模块零平台依赖,无需任何桩)。 * * 为什么要主机侧单测(而不是只靠台架): - * sha256 是 FTP pwd_hash 校验(N-19)的地基——哈希算错的后果是 + * sha256 是 FTP 鉴权的 pwd_hash 校验地基——哈希算错的后果是 * "正确口令被拒 / 错误口令放行"这类静默语义错误,板端只看到 530/230, * 看不出哪一步坏;标准向量在主机侧毫秒级钉死。 * diff --git a/test/host/soe/soe_test.c b/test/host/soe/soe_test.c index c94b1540..ffc4a32f 100644 --- a/test/host/soe/soe_test.c +++ b/test/host/soe/soe_test.c @@ -47,15 +47,15 @@ static void tc_roundtrip(void) soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 2u, 0u, 12345u, 1760234567u, 0xDEADBEEFu); rc = soe_rec_parse(rec, &out); - CHECK(rc == 0, "TC-001 parse ok"); - CHECK(out.ver == SOE_REC_VER, "TC-001 ver"); - CHECK(out.type == (uint8_t)SOE_T_ELM_ASSERT, "TC-001 type"); - CHECK(out.elm_id == 2u, "TC-001 elm_id"); - CHECK(out.flags == 0u, "TC-001 flags"); - CHECK(out.seq == 12345u, "TC-001 seq"); - CHECK(out.epoch_s == 1760234567u, "TC-001 epoch"); - CHECK(out.value == 0xDEADBEEFu, "TC-001 value"); - CHECK(out.cp56[6] == (uint8_t)((2025u - 2000u) & 0x7Fu), "TC-001 cp56 year=2025"); + CHECK(rc == 0, "TC-SOE-001 parse ok"); + CHECK(out.ver == SOE_REC_VER, "TC-SOE-001 ver"); + CHECK(out.type == (uint8_t)SOE_T_ELM_ASSERT, "TC-SOE-001 type"); + CHECK(out.elm_id == 2u, "TC-SOE-001 elm_id"); + CHECK(out.flags == 0u, "TC-SOE-001 flags"); + CHECK(out.seq == 12345u, "TC-SOE-001 seq"); + CHECK(out.epoch_s == 1760234567u, "TC-SOE-001 epoch"); + CHECK(out.value == 0xDEADBEEFu, "TC-SOE-001 value"); + CHECK(out.cp56[6] == (uint8_t)((2025u - 2000u) & 0x7Fu), "TC-SOE-001 cp56 year=2025"); } /* ---------------- TC-SOE-002 校验和/版本硬门槛 ---------------- */ @@ -76,14 +76,14 @@ static void tc_gates(void) } rec[i] = save; } - CHECK(bad == 0, "TC-002 any byte flip rejected"); - CHECK(soe_rec_parse(rec, &out) == 0, "TC-002 intact parses ok"); + CHECK(bad == 0, "TC-SOE-002 any byte flip rejected"); + CHECK(soe_rec_parse(rec, &out) == 0, "TC-SOE-002 intact parses ok"); rec[SOE_OFF_VER] = (uint8_t)(SOE_REC_VER + 1u); /* 版本篡改:sum 仍按新 * 字节算会自洽,但版本 * 门槛先拒 */ - CHECK(soe_rec_parse(rec, &out) == -2, "TC-002 bad version -> -2"); - CHECK(soe_rec_parse(NULL, &out) == -1, "TC-002 null args -> -1"); + CHECK(soe_rec_parse(rec, &out) == -2, "TC-SOE-002 bad version -> -2"); + CHECK(soe_rec_parse(NULL, &out) == -1, "TC-SOE-002 null args -> -1"); } /* ---------------- TC-SOE-003 CP56 正确性 + 交叉往返 ---------------- */ @@ -101,24 +101,24 @@ static void tc_cp56(void) (void)soe_rec_parse(rec, &out); /* 显式字段:12:34:56 → ms 域 = 56*1000(秒域时基如实编码,亚秒 0) */ CHECK(((uint32_t)out.cp56[0] | ((uint32_t)out.cp56[1] << 8)) == 56000u, - "TC-003 ms field = sec*1000 (56000)"); + "TC-SOE-003 ms field = sec*1000 (56000)"); rc = iec104_cp56_to_epoch_ms(out.cp56, &back); - CHECK(rc == 0, "TC-003 cross decode ok"); - CHECK(back == 1791808496000ULL, "TC-003 cross roundtrip exact"); + CHECK(rc == 0, "TC-SOE-003 cross decode ok"); + CHECK(back == 1791808496000ULL, "TC-SOE-003 cross roundtrip exact"); /* 闰日 2024-02-29 00:00:00 UTC = 1709164800 */ soe_rec_build(rec, (uint8_t)SOE_T_SAMPLE_BLOCK, 0u, 0u, 2u, 1709164800u, 0u); (void)soe_rec_parse(rec, &out); CHECK(out.cp56[4] == 29u && out.cp56[5] == 2u && out.cp56[6] == 24u, - "TC-003 leap day fields"); + "TC-SOE-003 leap day fields"); rc = iec104_cp56_to_epoch_ms(out.cp56, &back); - CHECK((rc == 0) && (back == 1709164800000ULL), "TC-003 leap roundtrip"); + CHECK((rc == 0) && (back == 1709164800000ULL), "TC-SOE-003 leap roundtrip"); /* 2000-01-01 00:00:00 边界 */ soe_rec_build(rec, (uint8_t)SOE_T_PLATE_IN, 0u, 0u, 3u, 946684800u, 0u); (void)soe_rec_parse(rec, &out); rc = iec104_cp56_to_epoch_ms(out.cp56, &back); - CHECK((rc == 0) && (back == 946684800000ULL), "TC-003 year-2000 boundary"); + CHECK((rc == 0) && (back == 946684800000ULL), "TC-SOE-003 year-2000 boundary"); } /* ---------------- TC-SOE-004 IV 语义 ---------------- */ @@ -130,10 +130,10 @@ static void tc_iv(void) soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 1u, SOE_FLG_IV, 9u, 0u, 1u); (void)soe_rec_parse(rec, &out); - CHECK((out.cp56[2] & 0x80u) != 0u, "TC-004 cp56 IV bit set"); - CHECK(out.epoch_s == 0u, "TC-004 epoch field zeroed"); + CHECK((out.cp56[2] & 0x80u) != 0u, "TC-SOE-004 cp56 IV bit set"); + CHECK(out.epoch_s == 0u, "TC-SOE-004 epoch field zeroed"); CHECK(iec104_cp56_to_epoch_ms(out.cp56, &back) == -2, - "TC-004 cross decoder rejects IV"); + "TC-SOE-004 cross decoder rejects IV"); } /* ---------------- TC-SOE-005 钳位 ---------------- */ @@ -146,17 +146,17 @@ static void tc_clamp(void) soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 4u, 100u, 0u); (void)soe_rec_parse(rec, &out); CHECK((out.cp56[4] == 1u) && (out.cp56[5] == 1u) && (out.cp56[6] == 0u), - "TC-005 clamp low -> 2000-01-01"); + "TC-SOE-005 clamp low -> 2000-01-01"); (void)iec104_cp56_to_epoch_ms(out.cp56, &back); - CHECK(back == 946684800000ULL, "TC-005 clamp low exact"); + CHECK(back == 946684800000ULL, "TC-SOE-005 clamp low exact"); /* epoch_s 为 uint32 → 最大 2106-02-07(0xFFFFFFFF),恒在 CP56 域内: * 上界钳位是防御性死分支(epoch 域扩展时生效),此处断言精确往返 */ soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 5u, 0xFFFFFFFFu, 0u); (void)soe_rec_parse(rec, &out); - CHECK(out.cp56[6] == 106u, "TC-005 uint32 max -> year 2106 (no clamp needed)"); + CHECK(out.cp56[6] == 106u, "TC-SOE-005 uint32 max -> year 2106 (no clamp needed)"); (void)iec104_cp56_to_epoch_ms(out.cp56, &back); - CHECK(back == 4294967295000ULL, "TC-005 uint32 max exact roundtrip"); + CHECK(back == 4294967295000ULL, "TC-SOE-005 uint32 max exact roundtrip"); } /* ---------------- TC-SOE-006 结构不变量 ---------------- */ @@ -167,7 +167,7 @@ static void tc_layout(void) uint8_t s = 0u; int zero_ok = 1; - CHECK(SOE_REC_SIZE == 64u, "TC-006 record size 64"); + CHECK(SOE_REC_SIZE == 64u, "TC-SOE-006 record size 64"); soe_rec_build(rec, (uint8_t)SOE_T_SAMPLE_RESUME, SOE_ELM_NONE, 0u, 0u, 946684800u, 0u); for (i = 23u; i <= 62u; i++) { @@ -175,12 +175,12 @@ static void tc_layout(void) zero_ok = 0; } } - CHECK(zero_ok, "TC-006 reserved [23..62] zero"); + CHECK(zero_ok, "TC-SOE-006 reserved [23..62] zero"); for (i = 0u; i < SOE_OFF_SUM; i++) { s = (uint8_t)(s + rec[i]); } - CHECK(rec[SOE_OFF_SUM] == s, "TC-006 sum8 covers [0..62]"); - CHECK(rec[SOE_OFF_ELM] == SOE_ELM_NONE, "TC-006 elm none"); + CHECK(rec[SOE_OFF_SUM] == s, "TC-SOE-006 sum8 covers [0..62]"); + CHECK(rec[SOE_OFF_ELM] == SOE_ELM_NONE, "TC-SOE-006 elm none"); } /* ---------------- TC-SOE-007 序号边界 ---------------- */ @@ -190,11 +190,11 @@ static void tc_seq_edges(void) soe_rec_t out; soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 0u, 946684800u, 0u); - CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0u), "TC-007 seq 0"); + CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0u), "TC-SOE-007 seq 0"); soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 0xFFFFFFFFu, 946684800u, 0u); CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0xFFFFFFFFu), - "TC-007 seq 0xFFFFFFFF"); + "TC-SOE-007 seq 0xFFFFFFFF"); } int main(void) diff --git a/test/host/soe_store/soe_store_test.c b/test/host/soe_store/soe_store_test.c index d204d794..48bf858a 100644 --- a/test/host/soe_store/soe_store_test.c +++ b/test/host/soe_store/soe_store_test.c @@ -44,7 +44,7 @@ * TC-SOESTORE-011 打开模式必须是 FA_WRITE|FA_OPEN_APPEND;对照保真:只给 * FA_OPEN_APPEND 的句柄 f_write 恒返 FR_DENIED(陷阱一,本仓实证根因) * TC-SOESTORE-012 多条落盘:一条一次 open/write/close(本模块**没有**批量合并—— - * 与 sys_log 的 N-24 不同),FIFO 顺序、seq 递增 + * 与 sys_log 的日志读写放大收口不同),FIFO 顺序、seq 递增 * TC-SOESTORE-013 open 与 close 严格配对(脏句柄不跨调用残留) * —— drain 侧:故障注入 —— * TC-SOESTORE-014 短写 63B → 判失败 → unlink 重建 → 重试成功 → 最终恰好 64 字节 diff --git a/test/host/sys_clock/run_unit_test.py b/test/host/sys_clock/run_unit_test.py index 261427d4..8cab78ef 100644 --- a/test/host/sys_clock/run_unit_test.py +++ b/test/host/sys_clock/run_unit_test.py @@ -46,7 +46,7 @@ BUILD = TestBuild( include_dirs=[ os.path.join(HERE, "stub"), os.path.join(ROOT, "App", "util"), - os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(sd2506.h 依赖,2026-10-23 S-5) + os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(sd2506.h 依赖,2026-10-23) os.path.join(ROOT, "Drivers", "BSP", "SD2506"), HERE, ], diff --git a/test/host/sys_log_mcu/run_unit_test.py b/test/host/sys_log_mcu/run_unit_test.py index 3154b9d7..78443819 100644 --- a/test/host/sys_log_mcu/run_unit_test.py +++ b/test/host/sys_log_mcu/run_unit_test.py @@ -6,7 +6,7 @@ r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运 用本机 MSVC 编译 test/host/sys_log_mcu/sys_log_mcu_test.c(以 #include 纳入**生产源** App/log/sys_log_mcu.c,并纳入共享假卷 test/host/common/fatfs_fake.c),在 PC 上离线 跑 64B 定长记录格式、单条/批量两条写路径、syslog_drain 的 preboot+队列排空与 - "失败回投不丢条目"、N-24 的 8 条滑动读窗口 + 写者代数失效、挂载幂等与 + "失败回投不丢条目"、日志读写放大收口的 8 条滑动读窗口 + 写者代数失效、挂载幂等与 上电/重启分类、sys_log_evidence 的 wdev.log 取证通道,失败即非 0 退出。 工具链与判定委托 test/host/common/unit_test_harness.py。 @@ -20,7 +20,7 @@ r"""运行/自检记录落盘后端(sys_log_mcu)的主机侧单元测试运 3. **挂载幂等**:syslog_mcu_mounted 曾在建 /log 失败后永久 no-op(运行期日志与自检 全部滞留),改成幂等 + 重试后又必须防"每 2s 刷一条同样错误" → 要注入 mkdir 失败才测得到,且必须能数出"到底留痕几次"。 - 4. **读窗口 try 语义 / N-24 读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面, + 4. **读窗口 try 语义 / 日志读写放大收口的读侧**:窗口"命中即零 I/O"与"写者一落地就失效"是一枚硬币两面, 只做前者会把陈旧读当正确、只做后者会退回逐条 open/seek/close 的旧实现。 桩的边界:ff.h / sys_log.h / soe_store.h / sys_clock.h / vol_lock.h 用**真实头**; diff --git a/test/host/sys_log_mcu/sys_log_mcu_test.c b/test/host/sys_log_mcu/sys_log_mcu_test.c index 050514fe..3f3cb20d 100644 --- a/test/host/sys_log_mcu/sys_log_mcu_test.c +++ b/test/host/sys_log_mcu/sys_log_mcu_test.c @@ -2,11 +2,11 @@ * 模块名称:运行/自检记录落盘后端(sys_log_mcu)主机侧单元测试(L0.5) * 模块功能:在 PC 上离线验证 App/log/sys_log_mcu.c 的四族契约—— * A 64B 定长记录格式(时间戳 / epoch ASCII / 描述三段 + 截断 + 占位) - * B 写侧(单条 s_append_rec、批量 s_append_many、N-24 的"一批一次 + * B 写侧(单条 s_append_rec、批量 s_append_many、日志读写放大收口的"一批一次 * open/write/close"、容量轮转、L1 自愈、L2 留痕去重、卷锁 try) * C 排空(syslog_drain 的 preboot + 队列两条来源、换文件分批、 * 失败回投不丢条目、-3 退回慢路径) - * D 读侧(N-24 的 8 条滑动窗口 + 写者代数失效 + 越界/忙/短读处置) + * D 读侧(日志读写放大收口的 8 条滑动窗口 + 写者代数失效 + 越界/忙/短读处置) * 外加 syslog_mcu_mounted 的 挂载幂等与上电/重启分类、sys_log_evidence * 的 wdev.log 取证通道(8KB 护栏 + 第二条写必须在释放卷锁之后)。 * @@ -25,7 +25,7 @@ * 2. **f_write 簇分配失败 = FR_OK + 短写**:只判 fr != FR_OK 就等于把丢记录当成功。 * 3. **挂载幂等**:syslog_mcu_mounted 曾在建 /log 失败后永久 no-op(运行期日志与自检 * 全部滞留),且 2s 重试会刷屏 → 幂等 + 留痕去重都要注入 mkdir 失败才测得到。 - * 4. **读窗口 try 语义 / N-24 读侧**:读窗口"命中即零 I/O"与"写者一落地就必须失效重填"是同一 + * 4. **读窗口 try 语义 / 日志读写放大收口的读侧**:读窗口"命中即零 I/O"与"写者一落地就必须失效重填"是同一 * 枚硬币的两面——只做前者会把陈旧读当正确,只做后者会退化成逐条 open/seek/close * 的旧实现(UI 翻 20 条 = 20 次 FAT 往返)。两者都靠假卷的调用计数才断言得了。 * @@ -36,7 +36,7 @@ * TC-SYSLOG-002 描述超长截断到 SYSLOG_DESC_MAX,且不越 64B 边界 * TC-SYSLOG-003 墙钟未建立:get_str 返 NULL -> 占位 "1970-01-01 08:00:00"; * get() 返 UINT32_MAX -> epoch 字段落 "0000000000" - * TC-SYSLOG-004 单条路径与批量路径产出的同内容记录**逐字节相同**(N-24 抽出 + * TC-SYSLOG-004 单条路径与批量路径产出的同内容记录**逐字节相同**(日志读写放大收口抽出 * s_format_rec 的全部理由:UI 按定长字段偏移解析) * —— B 写侧 —— * TC-SYSLOG-010 单条落盘成功 + 打开模式必须 FA_WRITE|FA_OPEN_APPEND;close 配对 @@ -175,7 +175,7 @@ * - 真跨任务并发:读窗口用"对齐 32 位读写在 ARM 上原子"免锁,主机 x64 + 单线程 * 编不出真竞态;本套只断言单写者语义与代数失效逻辑本身。 * - f_close 内部 sync_fs -> disk_ioctl(CTRL_SYNC) -> ftl_flush_cache() 的掉电落盘 - * 链:属 nand_port/GD5F 层,假卷里没有 FTL,"关文件即落盘"要靠真机 + N-22 台账。 + * 链:属 nand_port/GD5F 层,假卷里没有 FTL,"关文件即落盘"要靠真机 + 写回缓存空闲 flush 台账。 * - snprintf 失败(n<=0)分支:宿主 snprintf 不会失败,且假件不提供注入—— * 为了刷覆盖率去伪造返回值等于把断言写在假件规则上,宁可留空。 * - DBG 文本内容:桩只计数(真实宏展开成 snprintf + 192B 行缓冲)。 @@ -741,7 +741,7 @@ static void tc_write_many(void) uint32_t i; int ok; - printf("-- TC-SYSLOG-020..024 batch write path (N-24) --\n"); + printf("-- TC-SYSLOG-020..024 batch write path (log I/O amplification wrap-up) --\n"); /* 020 8 条一次 I/O */ reset_all(); @@ -1139,7 +1139,7 @@ static void tc_read(void) uint32_t i; int ok; - printf("-- TC-SYSLOG-050..058 read side (N-24 sliding window) --\n"); + printf("-- TC-SYSLOG-050..058 read side (log I/O amplification wrap-up sliding window) --\n"); /* 050 count */ reset_all(); diff --git a/test/host/time_sync/run_unit_test.py b/test/host/time_sync/run_unit_test.py index 664871ce..91fb8046 100644 --- a/test/host/time_sync/run_unit_test.py +++ b/test/host/time_sync/run_unit_test.py @@ -13,7 +13,7 @@ `test/host/common/unit_test_harness.py`(本文件只做声明)。 为什么要主机侧单测(而不是只用板端套件): - 本模块是登记簿 N-17 的当事方——"监听重建期间心跳缺口 800ms~1s,距 IWDG 最坏 + 本模块是探针窗口心跳缺口那件事的当事方——"监听重建期间心跳缺口 800ms~1s,距 IWDG 最坏 超时 1701ms 余量仅 ~0.7s"。缺口成因全是本模块的**纯逻辑**(分片延时的喂狗 节奏、500/2000ms 监听退避、200ms accept 退避、50ms 收尾、屏障等待态喂心跳)。 真机上只能"跑起来看复位不复位",主机侧可**逐片断言**。时间包重组状态机 @@ -25,7 +25,7 @@ 退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。 -设计依据:App/sync/time_sync.c 文件头注释 + docs/任务规划/登记簿.md N-17; +设计依据:App/sync/time_sync.c 文件头注释 + docs/网络/网络自愈架构设计.md §4.6; 创建日期:2026-10-10 """ import os diff --git a/test/host/time_sync/time_sync_test.c b/test/host/time_sync/time_sync_test.c index e4abe860..0a5902ba 100644 --- a/test/host/time_sync/time_sync_test.c +++ b/test/host/time_sync/time_sync_test.c @@ -15,7 +15,7 @@ * - dbg_log.h -> DBG_ERROR/DBG_INFO 计数 * * 为什么要主机侧单测(而不是只用板端套件): - * 1. **本模块是 N-17 的当事方**:登记簿 N-17 记录"time_sync 监听重建期间心跳缺口 + * 1. **本模块是探针窗口心跳缺口的当事方**:当时的记录写着"time_sync 监听重建期间心跳缺口 * 800ms~1s,距 IWDG 最坏超时 1701ms 余量仅 ~0.7s"。缺口的成因全部是本模块的 * **纯逻辑**——分片延时(delay_fed)的心跳节奏、监听重试退避(500/2000ms)、 * accept 轮询退避(200ms)、收尾延时(50ms)、以及"屏障等待态也要喂心跳"。 @@ -235,7 +235,7 @@ int net_close(int sockfd) { return 0; } -/* ---- net_drop_conn(E-02 收口):计数 + 记录 fd ---- */ +/* ---- net_drop_conn(头阻塞收口):计数 + 记录 fd ---- */ static int s_drop_calls; static int s_drop_socks[32]; @@ -605,7 +605,7 @@ static void tc_handle_conn(void) { CHECK(s_recv_calls == 1, "TC-TIMESYNC-027 no more reads once the budget is exhausted (only 1 read)"); CHECK(g_tstest_info_count == 1, "TC-TIMESYNC-027b leaves the no TIME packet in 30s, drop conn trace"); CHECK(s_close_calls == 0, "TC-TIMESYNC-027c budget exhausted still does not close the connection"); - CHECK(s_drop_calls == 1, "TC-TIMESYNC-027d budget exhausted drops the session on purpose (E-02 wrap-up: net_drop_conn kills the connection, keeps the listener)"); + CHECK(s_drop_calls == 1, "TC-TIMESYNC-027d budget exhausted drops the session on purpose (head-of-line wrap-up: net_drop_conn kills the connection, keeps the listener)"); /* 9) 剩余预算不足一片 */ fakes_reset(); @@ -653,7 +653,7 @@ static void tc_task(void) { CHECK(s_close_calls == 1, "TC-TIMESYNC-040e the connection is closed once"); CHECK(s_delay_calls == 9, "TC-TIMESYNC-040f delay slice count = 4+4+1 (200+200+50 ms)"); CHECK(delays_all(50U, 9), "TC-TIMESYNC-040g every delay is sliced at 50 ms"); - CHECK(s_beat_calls == 13, "TC-TIMESYNC-040h beat count = main loop 1 + backoff 8 + accepted connection 1 + wrap-up 1 + next round 1"); + CHECK(s_beat_calls == 13, "TC-TIMESYNC-040h beat count = main loop 1 + backoff 8 + accepted connection 1 + wrap-up 1 + next cycle 1"); CHECK(s_beat_id_bad == 0, "TC-TIMESYNC-040i every beat sets the TIMESYNC bit"); /* B) 就绪屏障:2 次未就绪 + 1 次就绪 */ diff --git a/test/host/watchdog/run_unit_test.py b/test/host/watchdog/run_unit_test.py index 0fb5e4bf..cbd8c334 100644 --- a/test/host/watchdog/run_unit_test.py +++ b/test/host/watchdog/run_unit_test.py @@ -8,7 +8,7 @@ 失败即非 0 退出。零硬件、零烧录、毫秒级。 工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness - `test/host/common/unit_test_harness.py`(本文件只做声明,2026-10-01 T-3 抽取)。 + `test/host/common/unit_test_harness.py`(本文件只做声明,2026-10-01 抽取)。 为什么要主机侧单测(而不是只用板端套件): task_watchdog.c 的判定逻辑(位图取走清零 / 掩码 / miss_streak 累计 / 节流) @@ -22,7 +22,7 @@ 退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。 -设计依据:docs/测试说明/看门狗单元测试说明.md;docs/任务规划/登记簿.md T-3 +设计依据:docs/测试说明/看门狗单元测试说明.md 创建日期:2026-10-01 """ import os diff --git a/test/run_all_suites.py b/test/run_all_suites.py index 913a782c..ed9669d0 100644 --- a/test/run_all_suites.py +++ b/test/run_all_suites.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """run_all_suites.py - 多套件回归编排(纯标准库)。 -做什么(2026-10-01 测试架构评审 T-5): +做什么(2026-10-01 测试架构评审): 套件互斥(`test_config.h` 的 `#error`)+ 编译期开关 → 跑 4 个套件的回归 本来要手工循环 4 轮(改宏 → 构建 → 烧录 → 采集 → 判读)。本脚本把它编排成一条命令: @@ -10,12 +10,12 @@ 1. 改 test_config.h 启用该套件(其它全注释) 2. @build(0 错 0 警,否则跳过该套件) 3. 烧录(stlink-flash skill:stlink_flash.py flash,Reset & Run) - 4. @test 采集 + 判读(.dsh/skills/serial-boot-test 的 verify_log.py,含套件汇总行校验 T-2) + 4. @test 采集 + 判读(.dsh/skills/serial-boot-test 的 verify_log.py,含套件汇总行校验) 结束后: 5. **无条件还原 test_config.h**(try/finally,含异常/KeyboardInterrupt 路径) 为什么要还原(这是本脚本存在的核心理由): - 跑完一轮忘了还原 = **测试固件进库**(与 T-1 同源风险)。T-1 是"提交期门禁"兜底, + 跑完一轮忘了还原 = **测试固件进库**(与提交期门禁同源风险)。提交期门禁是兜底, 本脚本是"流程自动化"——两条防线各自独立。 ⚠️ 代价提醒: @@ -31,16 +31,16 @@ & '.venv\\Scripts\\python.exe' test/run_all_suites.py & '.venv\\Scripts\\python.exe' test/run_all_suites.py --suites STORAGE,GD5F & '.venv\\Scripts\\python.exe' test/run_all_suites.py --dry-run # 只打印计划 - & '.venv\\Scripts\\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6,无需 MCU) + & '.venv\\Scripts\\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(无需 MCU) & '.venv\\Scripts\\python.exe' test/run_all_suites.py --keep-config # 不还原(调试用,慎) ---check-build(S6 / 上游 P1-10): +--check-build: 只做"改宏 + @build",不烧录不采集,故**不需要 MCU / 串口 / ST-LINK**。 用途:① 改动测试固件或 test_config.h 后自检 4 套件仍可编译; ② 防止"某套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。 - 注意 RAM 溢出(P1-9)会自然暴露为链接错误 L6406E——这正是想要的。 + 注意 RAM 溢出会自然暴露为链接错误 L6406E——这正是想要的。 -设计依据:docs/任务规划/登记簿.md T-5;docs/测试说明/测试开发标准流程.md §3.1 +设计依据:docs/测试说明/测试开发标准流程.md §3.1 创建日期:2026-10-01 """ @@ -144,14 +144,14 @@ def main(argv=None): except Exception: pass - ap = argparse.ArgumentParser(description="多套件回归编排(T-5,纯标准库)") + ap = argparse.ArgumentParser(description="多套件回归编排(纯标准库)") ap.add_argument("--suites", default=",".join(SUITES.keys()), help="要跑的套件,逗号分隔(默认全部:{})".format(",".join(SUITES))) ap.add_argument("--build-timeout", type=int, default=300) ap.add_argument("--flash-timeout", type=int, default=300) ap.add_argument("--dry-run", action="store_true", help="只打印计划,不改配置不构建不烧录") ap.add_argument("--check-build", action="store_true", - help="只编译不烧录(S6/P1-10):逐套件改宏 + @build,验证 4 套件可编译性;" + help="只编译不烧录:逐套件改宏 + @build,验证 4 套件可编译性;" "不需要 MCU / 串口 / ST-LINK,可用于 CI 或改动测试固件后自检") ap.add_argument("--keep-config", action="store_true", help="结束后不还原 test_config.h(调试用;⚠️ 会留下测试固件配置)") @@ -212,7 +212,7 @@ def main(argv=None): results[suite] = "BUILD_FAIL" continue - # --check-build:编译通过即达标,不烧录不采集(S6) + # --check-build:编译通过即达标,不烧录不采集 if args.check_build: results[suite] = "PASS" print("[{}] BUILD_OK -> PASS(--check-build,未烧录)".format(suite)) @@ -227,7 +227,7 @@ def main(argv=None): # 烧录失败不直接判死:板子可能已复位,交由采集+判读决定(与 @test 一致) print("[WARN] 烧录 rc={}(继续:板子可能已复位)".format(frc)) - # 3) 采集 + 判读(含套件汇总行校验 T-2) + # 3) 采集 + 判读(含套件汇总行校验) # 烧录统一走 stlink-flash skill(与 .serialtest/config.json 的 flash_cmd 同源) flash_cmd = '"{}" "{}" flash'.format(py, STLINK_FLASH) cmd = [py, str(RUN_BOOT), "--flash-cmd", flash_cmd] diff --git a/tools/arch_diagram/gen_adc_health_flow_svg.py b/tools/arch_diagram/gen_adc_health_flow_svg.py index 1cd78cb9..e2c5e5eb 100644 --- a/tools/arch_diagram/gen_adc_health_flow_svg.py +++ b/tools/arch_diagram/gen_adc_health_flow_svg.py @@ -65,7 +65,7 @@ LANES = [ cells=[ dict(title="tpafe5160.c 的 BUSY EXTI", lines=["s_frame_seq++:每读完一帧 +1", - "s_frstdata_err++:无跳变帧累计(E-44)", + "s_frstdata_err++:无跳变帧累计计数", "ISR 内只加计数,不打日志"] ), dict(title="adcTask 每拍 poll 之后取值", lines=["tpafe_seq", diff --git a/tools/arch_diagram/gen_memory_svg.py b/tools/arch_diagram/gen_memory_svg.py index 61a57079..9d38279b 100644 --- a/tools/arch_diagram/gen_memory_svg.py +++ b/tools/arch_diagram/gen_memory_svg.py @@ -73,7 +73,7 @@ FLASH_CODE_SUB = [ ("FatFS", 8468, "#9C27B0"), ("C 库 (microlib)", 5486, "#795548"), ("startup", 468, "#B0BEC5"), - ("其他 (长尾)", 7585, "#E0E0E0"), # remainder(2026-10-23 S-5 C3/C1/C2 的零散增量) + ("其他 (长尾)", 7585, "#E0E0E0"), # remainder(2026-10-23 的零散增量) ] # ── Task stack budgets (2026-10-22 @build + 同日真机 [STACK] 实测; data/stack_report_20261022.md) ── diff --git a/tools/arch_diagram/gen_net_instability_svg.py b/tools/arch_diagram/gen_net_instability_svg.py index 6b0b04a4..4405ebea 100644 --- a/tools/arch_diagram/gen_net_instability_svg.py +++ b/tools/arch_diagram/gen_net_instability_svg.py @@ -74,7 +74,7 @@ RINGS = [ ["重建收敛到唯一所有者(net_listener + reconciler),其余位置只置事实不动手", "不变量 II:唯一周期重建决策者(消费者即时重建三处保留,台架实测)"]), ("⑤ 传达员被按在椅子上", "队头阻塞", - ["症状:FTP banner 迟到 25~30 s(E-02 实测),netTask 频繁冻结半秒", + ["症状:FTP banner 迟到 25~30 s(台架实测),netTask 频繁冻结半秒", "机制:30 s 阻塞收 + 500 ms 盲等忙等,而 netTask 是全楼唯一传达员"], "传达员永不离开传达室", ["忙等统一为 net_sock_reset_sync 有界排渣(600 ms 硬上限),阻塞消息化", @@ -86,7 +86,7 @@ RINGS = [ ["keepalive 60/5/3(E11 实测 ≈30 s 判死)+ 芯片 TIMEOUT → QUARANTINE → 重建", "自愈 30~90 s,不需要冷启动(E13 台架收口:用户判断正确)"]), ("⑦ 只会礼貌送客不会轰人", "close 无 RST(手册定案)", - ["症状:FIN_WAIT_2 长尾,监听恢复受限于对端 close(E-02 定案)", + ["症状:FIN_WAIT_2 长尾,监听恢复受限于对端 close(台架定案)", "机制:芯片 close 只发 FIN;对端内核存活但应用挂死 = 回 ACK 却不起身"], "知道他不轰人,就别指望他轰人", ["定案写入 TRAP-NET-07:兜底靠查岗(keepalive/TIMEOUT),不靠 RST", diff --git a/tools/menu_gen/gen_menu.py b/tools/menu_gen/gen_menu.py index e6e6085e..fc5e25bb 100644 --- a/tools/menu_gen/gen_menu.py +++ b/tools/menu_gen/gen_menu.py @@ -199,7 +199,7 @@ def emit(nodes): " * 入口参数:node - 节点下标 uint8_t 0 - 表长-1", " * 返回值:true - 有子项(显示为菜单,带 ▶);false - 无子项(叶子,可进内容页)", " * 限定条件:纯查询,无副作用;node 越界返回 false", - " * 函数说明:这是**谓词**(bool 合法),不是成败码——S-5 的 err-type 检查里白名单放行的形态。", + " * 函数说明:这是**谓词**(bool 合法),不是成败码——bsp-err 的 err-type 检查里白名单放行的形态。", " */", "bool ui_menu_tab_has_children(uint8_t node);", "", diff --git a/ui_menu_tree.txt b/ui_menu_tree.txt index 3a7436ea..203230db 100644 --- a/ui_menu_tree.txt +++ b/ui_menu_tree.txt @@ -2,7 +2,7 @@ # 规则:缩进即层级,每级 2 空格;`#` 开头为注释;空行忽略。 # 行尾可选 ` #P` = 该叶子需密码(密码页按所输密码授权:查看密码→只读 / 修改密码→可修改);无尾注 = 不需密码页(直接进,只读)。 # 密码值:Drivers/BSP/UI/ui_pwd_def.h 编译期配置(查看/修改各一个,改值 = 重新编译)。 -# 行尾可选 ` #page=<模块名>` = 该叶子绑定一个内容页实例(U3 页面注册表): +# 行尾可选 ` #page=<模块名>` = 该叶子绑定一个内容页实例(UI 页面原型化的页面注册表): # 模块名 = Drivers/BSP/UI/ui_page_<模块名>.c,其描述符符号须为 ui_page_<模块名>_desc。 # 声明了 #page 但实例文件缺失/符号不符 → 生成器报错(不产出半成品表)。 # 未被任何叶子 #page 引用的 ui_page_*.c 同样报错(写了却从不生效的死代码)。