规则:在**掩码后**的文本上匹配 malloc/calloc/realloc/strdup/strndup/ free/aligned_alloc 调用。三条判据都是踩过坑之后定的—— - 大小写敏感:pvPortMalloc / vPortFree(唯一允许的配对)不会误报; - 词边界 \b:dhara_nand_is_free(、buf_alloc( 这类同形后缀不误报; - 用掩码:注释与字符串字面量里写 malloc 不报(规范示例就踩过这种)。 覆盖名比 AGENTS 列的五枚多带上 strndup / aligned_alloc——同族同类, 漏一个就等于留后门。 出处不在规范 V1.0,在平台约束:MicroLIB 的 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 只有 512 B,KB 级分配必返 NULL,而模拟器(宿主 CRT 堆)完全正常、**照不出来**——配置引擎 2026-09-18 那次堆分配纠偏与 lftpd 的 malloc 隐患都是**先上线才发现**的那类,靠评审等于没管。 落地前按 门禁说明/coding-standard.md §8 的纪律先全库普查:钩子范围内 C/H 共 151 个文件,命中**只有** simulator/sim/cfg_file_store_sim.c 两行, 即 cfg_store_alloc/cfg_store_free = **平台分配后端的宿主侧实现**(设计依据 写在 docs/子系统设计/配置文件设计说明.md 的"MCU 端 = pvPortMalloc、 模拟器端 = 宿主 CRT")。所以零基线、直接硬拦截;豁免用脚本里的 HEAP_ALLOC_ALLOWLIST(**文件路径 + 函数名**双条件),刻意不做目录级豁免: 下次谁要碰 C 堆就得往这张表加一行,评审看得见。 自证:临时样例文件里 malloc/calloc/free 各报一处,pvPortMalloc/vPortFree/ buf_alloc(、注释、字符串五种陷阱形态全部不误报; pre-commit run coding-standard --all-files → **Passed**(= 全库零存量)。 顺带修三处口径不一致: - 规范 V1.0 §6.1 的示例自己写着 malloc/free(通用 C 模板遗留)→ 已在示例 下加"本仓例外,别照抄"批注,事实源指向 AGENTS.md 平台约束; - docs/门禁说明/mem-budget.md 里"这条纪律只靠 AGENTS 铁律与评审"已过期 → 更正为由 CODE-RULE-20 机器拦截; - AGENTS.md 平台约束条加"已机器拦截 + 唯一豁免"一句;scripts/README.md 补登 check_test_coverage.py(上一个提交漏登记)。 另:本次一并修好上一处编辑事故——报告 §4.8 曾被我的脚本调用截成半句 (只落第一行、丢掉 malloc 那半句),已重写为"两处空白均已闭合"。 自查:pre-commit run --files(9 个改动文件)适用项全 Passed; check_doc_links.py --all = 35 处,与主干同数(本分支贡献 0); check_ids.py --strict 通过(裸编号 P0-1 改平实说法,避免记成新账); 9 个文件 BOM/CR/U+FFFD/行尾与 HEAD 逐项一致。本轮零 .c/.h 改动,未跑 @build、未占 build/bench 锁;分支已 rebase 到 v2-on-ui,覆盖率脚本全量 回归仍 16/16、line=2552/2893 不变。
31 KiB
STM32F4-Base
工业级嵌入式 DTU 平台固件:STM32F407ZGTx @168MHz,集成以太网(CH395F)、SPI NAND 存储(GD5F)、电气量采集(TPAFE5160 + 片内 ADC)、RS-485、FTP 文件服务,自带远程显示(UI 推流)、墙钟对时、看门狗自恢复、运行/自检记录——既是可独立部署的现场设备,也是可复用的平台底座(保护测控等后续产品在此底座上补齐)。产品基线 → docs/产品需求/产品需求设计文档.md §2.1。
仅 Keil MDK-ARM v5 (ARMCC, C99) 工具链;FreeRTOS V10.3.1 / CMSIS-RTOS2;192KB SRAM / 1MB Flash。
任何代码修改后必须先 @build 验证(0 错误 0 警告)再提交;硬件闭环用 @test。
本文件 = agent 入口:宏观概况 + 每次都在场的铁律在这里,细节在
docs/与 skills(文末索引)。同一事实只在一处详述(docs/文档规范.md同此要求)。
项目概况
现状一句话:平台能力(网络 / 存储 / 时间 / UI / 日志 / 看门狗 / FTP / 配置)已实现并台架验收;采集主线已闭环到业务消费者(L1→L2 相量→L3 保护判据 protectTask→104 遥信,B5);RS-485 采集尚未接线(B2 按用户决策冻结,见 docs/任务规划/暂缓-RS485.md)——当前最大功能缺口,接线节奏受 E-59 门禁约束(见「平台约束」)。
数据主线(能力视角;分层图与跨层依赖表 → docs/架构设计/项目架构总览.md):
- 采集:TPAFE5160(计量级 8 路,并行总线)+ 片内 ADC1(9 路,DMA)→ TIM3 1 kHz 节拍 + 原子快照 → adcTask → L1 有效性校验 → L2 相量 → L3 保护(protectTask 20 ms 判据节拍,B5)→ 104 遥信上送
- 存储:GD5F SPI NAND → dhara FTL → FatFs →
/log运行与自检记录、/config.ini配置、FTP 根目录 - 网络:CH395F → netTask 串行 socket 栈 → FTP(21) / 对时(8888) / RemoDispBus 远程显示(7003) / IEC 60870-5-104 上行(2404)
- 人机:LVGL 160×160 单色菜单(uiTask),与 PC 模拟器同一份 UI 源码,远端可看图 + 注键
目录地图(只列顶层——稳定边界;细结构自己 ls,别背路径):
| 目录 | 职责 | 可改性 |
|---|---|---|
Src/ Inc/ |
CubeMX 生成(外设初始化 / 中断向量 / freertos.c 任务表) |
只写 USER CODE 区 |
App/ |
应用层:app/ 启动与就绪事件 · task/ 任务编排 · algo/ 采集与算法 L1~L3 · cfg/ 配置引擎 · log/ 日志 · fs/ 卷锁 · sync/ 对时 · util/ 工具 |
手写 |
Drivers/BSP/ |
手写芯片驱动与服务:TPAFE5160 CH395F NET(socket 栈+lftpd) NETCFG GD5F2GQ5UE SD2506 RS485 UI(LVGL 界面) |
手写 |
Drivers/STM32F4xx_HAL_Driver/ Drivers/CMSIS/ Middlewares/ |
ST HAL / CMSIS / FreeRTOS | 第三方,只读 |
Lib/ |
第三方库:lvgl FatFs dhara + lv_conf.h(MCU 端) |
只读(本地补丁须有文档登记,如 LVGL I1 补丁) |
MDK-ARM/ |
Keil 工程(.uvprojx、scatter、下载脚本) |
工程配置手改;UV4 抖动不入库 |
simulator/ |
LVGL PC 模拟器(CMake + SDL2),与固件同标准受检 | 手写 → lvgl-simulator skill |
test/ |
board/ 板级套件 · host/ 主机侧单测(提交门禁实跑) |
手写 |
scripts/ tools/ |
门禁与报告脚本(check_* / gen_* / ci_local.py)· 配图/字体/引用图生成器 |
手写 |
docs/ |
全量设计 / 测试 / 规范文档(人类索引 docs/README.md) |
手写,doc-links 门禁 |
.dsh/skills/ .gitea/ .serialtest/ |
agent skills(子域知识入口)· CI 工作流 · 串口/烧录配置 | 手写 |
分层边界:上层只依赖相邻下层(业务 → UI/基础服务 → 中间件 → BSP 驱动 → HAL/RTOS → 硬件,单向);新算法模块进 App/algo/,勿塞 App/task/(任务只做消费/编排);新 .c 必须双端注册(new-source-file skill)。
架构要点
Src/main.c(CubeMX 初始化)→ app_main_init → osKernelStart → 9 任务(osKernelStart 之前全是内核启动前代码:禁调任何内核 API,见「平台约束」)
app_main_init(时间子系统优先,使后续日志带时间戳):sd2506_init→sys_clock_init→rs485_init→gd5f2gq5ue_init→tpafe5160_init→adc_input_init(L1 采集层)→ SD2506 诊断 → 启动 TIM3 1 kHz 采样节拍- 就绪屏障(
App/app/sys_events.hEvent Flags,取代魔法延时):SYS_EVT_NET_READY/STORAGE_READY/FTP_READY/TIMESYNC_READY;消费者用osEventFlagsWait阻塞等待(就绪位osFlagsNoClear) - netTask:
net_stack_bring_up(配置已由 defaultTask 在屏障前加载,本任务只读 RAM 副本)→ 置 NET_READY → 循环net_poll+net_process_messages;消费SYS_EVT_NET_RECONFIG→ 按网络代次号重建监听(新 IP 免重启生效) - defaultTask:
f_mountforced(无卷自动f_mkfs;挂载后卷健康探针,FAT 区陈旧时自愈重格式化)→ 加载网络配置 → STORAGE_READY → LED 闪烁;同时是 syslog(/log/run.log·selftest.log,定长 64B 记录)的唯一写者与配置引擎属主(netTask/uiTask 不得直接触碰配置引擎) - adcTask:纯消费者——每拍
adc_input_poll()(TPAFE 原子快照 + 消费一帧 STM32 ADC1 DMA 双缓冲),只取数不做保护运算;节拍与唤醒同源(TIM3 ISR 内触发 CONVST + 同一更新事件经 TRGO 触发 ADC1,并调adc_tick_irq_notify()置位,任务osThreadFlagsWait等唤醒)。⚠ ISR 侧只能走adc_tick_irq_notify()(内含"消费者就绪"门,TIM3 比调度器早 ≈100 ms 启动);手写于Src/freertos.c的USER CODE区,纳入 IWDGrequired_mask(TASK_WD_ADC) - 采集与算法层(L1/L2/L3)落在
App/algo/:adc_input/adc_health= L1,sample_ring/phasor= L2,freq= L2.5 测频(B8,自适应窗),prot_set/prot_elm= L3。板端消费者 = protectTask(B5,2026-10-12):20 ms 节拍prot_elm_run()+ L5-4 采样故障闭锁 + L5-1 定值校验和巡检;跨上下文复归(104 YK11)经prot_elm_request_reset()落请求。纳入 IWDGrequired_mask(TASK_WD_PROTECT,产品0x1F7)。→docs/算法/采集与算法层设计说明.md(设计与判据)、App/algo/README.md(目录索引) - 全景 →
docs/架构设计/项目架构总览.md;启动时序与内核前模式 →docs/架构设计/项目启动顺序管理.md
| Task | 栈 (words) | 优先级 |
|---|---|---|
| defaultTask | 1280 | Normal |
| netTask | 960 | Normal |
| ftpTask | 1024 | Normal |
| testTask | 2048 | BelowNormal |
| uiTask | 4096 | BelowNormal |
| timeSyncTask | 640 | Normal |
| adcTask | 640 | AboveNormal |
| iec104Task | 640 | Normal |
| protectTask | 512 | Normal |
(实际值以 Src/freertos.c 为准,本表由 scripts/gen_task_table.py 生成、task-table 钩子校验;testTask 仅在启用测试套件时创建。FreeRTOS 堆已迁 CCM,任务栈不占 SRAM;每次抬栈的实测最坏链与余量台账 → docs/内存管理/RAM使用分析与优化方案.md §2.3(任务栈配置与实测峰值)与 §4.0.6(静态最坏链与判定)、docs/内存管理/内存内容分配策略.md §6(容量台账))
铁律:源码编辑与编码
- 改代码只用 Edit 工具(精确子串替换 / replaceAll);绝不用
Get-Content/Set-Content/cmd重写含中文的文件——整文件重写按系统默认编码重解码 →U+FFFD乱码且不可逆 - Edit 会静默剥掉文件原有的 UTF-8 BOM:带 BOM 的文件(
Drivers/BSP/UI/*、App/task/ui_task.c、App/cfg/cfg_file.c、Lib/lv_conf.h)被 Edit 后丢 BOM → ARMCC 改按 CP936 解码 → 中文字面量报#870-D: invalid multibyte character sequence(只有重编译该文件才暴露,历史"0 警告"不代表安全)。改完须核对 BOM 并按原样补回(只前缀 3 字节EF BB BF,勿整文件重写) - 字符串字面量必须纯 ASCII:无 BOM 文件里奇数个汉字会吞掉结束引号(偶数侥幸通过)→ 中文只写注释
- 怀疑乱码先统计
EF BF BD字节数(勿信终端/Read 渲染);不要急著git checkout(会丢弃全部未提交工作)——用 Edit 只修坏行 - 行尾由
.gitattributes写死(* text=auto eol=lf;*.bat/*.cmd强制 CRLF):不依赖core.autocrlf、不要把工作区文件改回 CRLF → 权威口径docs/文档规范.md§2 - 代码规范 V1.0:小写下划线命名(
g_/s_/p_/a_前缀)、头文件保护宏XXX_H(禁__前缀)、常量在右、文件头模块注释块 →docs/代码规范/
构建与验证
- 门禁时机(用户裁定 2026-10-13):
@build/scripts/ci_local.py/check_doc_links.py只在准备提交时跑(用户让 commit 后统一跑一遍),不是每次中间改动后都跑——多步改动期间直接改即可,验证集中在提交门禁;@test硬件闭环仍按任务需要跑 @build=& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py(仓库根执行)。退出码 0=PASS / 1=构建失败 / 2=环境错误(UV4/工程/档案)/ 3=用法 / 4=build 锁被占用;日志MDK-ARM/build_log.txt;换机必设DTU_KEIL_UV4→ 细节keil-buildskill +docs/环境与部署/本地工具路径环境变量方案.md@test=& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\run_boot_test.py:开串口+清日志 → 烧录(固件身份戳保证烧进去的就是这份构建、回读校验)→ wait-idle → 采日志 → 标记校验(含启动锚点计数)。退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法;戳提取规则、锚点计数判据与按模块定制标记见docs/测试说明/自动化测试流程.md- Forbidden 标记含
[ERR]:四个 Fault 的崩溃日志都以[ERR]开头 → 出现 Fault 即本次不通过(刻意) - 烧录必须
-Rst -Run(stlink-flashskill 的flash):裸-P会报Programming Complete,但目标核处于死循环/异常时写入并不落地("反复重下载、芯片一直是旧镜像、二分法失效"的根因)。烧后必须回读校验(stlink_flash.py cmp=-CmpFile,No difference found才算真正写入);⚠-CmpFile连接时会复位目标 → 必须排在采日志之后 →docs/测试说明/ST-LINK_CLI_复位使用说明.md - ⚠️ 旧绝对路径
D:\Code\DTU 程序\...出现即失效(仓库已搬迁),一律改用仓库相对路径
Python 环境
- 统一入口
& '.venv\Scripts\python.exe'(3.12,fontTools/Pillow 已装);PATH 上的python是 msys64 的,不要用它跑依赖第三方库的脚本;纯标准库脚本任意 python3 均可 - 重建/换机:
<本机任一 python ≥3.10> -m venv .venv+pip install -r requirements.txt(含pre-commit;DTU_PRECOMMIT_PYTHON可指向别的装了 pre-commit 的解释器);requirements.txt必须纯 ASCII(Windows 下 pip 按 GBK 代码页解码)
提交门禁(pre-commit)
- 任一失败即拦截,共 39 个钩子(每种门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径)→
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+ 主机侧单测 16 个(-unit-test后缀)+test-suite-switch+registration-check(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+mem-budget(RAM 92% 预算门禁,N-27)+font-charset(UI 字符集差集检查,防缺字白框,U4)+ui-golden(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)+layer-deps(分层方向:Drivers/BSP/**不得 includeApp/**的头;存量 6 条基线只减不增,报告 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) - 范围 = 项目代码:第三方(
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/ - 提交信息:标题 ≤100 字符、正文每行 ≤80(
.gitlint+ gitlint 默认)→ 长说明一律用短标题 +git commit -F <UTF-8 无 BOM 文件>多行正文(中文长标题极易超限,且-m会被终端代码页转码破坏) - CI(
.gitea/workflows/ci.yml)只跑 L0 静态门禁 + L0.5 主机侧单测,刻意不跑@build(需 Keil 授权)、不含 L1/L2/L3(需真机)。远程 CI 的全部资料(服务端开关 / runner 部署与保活 / workflow / 排障 / 实测坑)集中在docs/CI/README.md,不放本节——它只跑在本机一台 runnerwin-stm32-ci上,其他机器与项目都不涉及。日常改代码的等价自查是& '.venv\Scripts\python.exe' scripts/ci_local.py(L0 + L0b + L0.5)——runner 不在线也不影响本地门禁,不能因"已配 CI"放宽本地要求
平台约束
CubeMX 与 Fault
Inc//Src/是 CubeMX 生成文件(带USER CODE BEGIN/END标记):自定义代码只写标记区之间;Drivers/BSP/为纯手工代码- 重生成会覆盖标记区之外的一切(
.ioc的KeepUserCode=true只保护 USER CODE 区;实测两次重生成各覆盖 4~5 处定制,其中两处不报编译错、只在真出故障时暴露)。防丢失按三层优先级 L1 写进.ioc→ L2 搬进 USER CODE 区 → L3 审计兜底;每次重生成后立刻跑python scripts/check_cubemx_survival.py(已挂cubemx-survival钩子)。方法论与全部实测坑(自造标记区会被模板换代抹掉、宏改名#define f f_Gen让位、FreeRTOS 钩子必须写进USER CODE BEGIN 4/5区…)→docs/子系统设计/看门狗设计说明.md§10 - ARMCC
__asm只有一种合法形态:整函数__asm void f(void) { ... };GCC 内联语法报#18、块内联把寄存器名当 C 标识符报#20、取不到 C 的数据符号(A1516E: Bad symbol)→ 三条坑与可用写法docs/子系统设计/看门狗设计说明.md§10.4 - 4 个 Cortex-M4 Fault 全部采集现场(手工
__asmHandler 取 MSP/PSP →B到 per-fault C 入口fault_entry_{hard,mem,bus,usage}→hard_fault_report打[ERR] <TAG> …后NVIC_SystemReset());MemManage/BusFault/UsageFault 默认禁用,不使能就被升级成 HardFault、日志看不出根因 →fault_handler_enable()必须在app_main_init()第一行;SHCSR的使能位无法在 CubeMX 配置(模板只生成优先级)→ 看门狗设计说明 §10
RTOS 与内存
osKernelStart之前禁止任何 FreeRTOS/CMSIS-RTOS 创建类 API(osMessageQueueNew等会杀死启动,2026-09-20 实证根因)→ 内核前标准模式(纯寄存器/HAL 轮询 + 无锁静态缓冲 + 内核启动后惰性创建真队列)见docs/架构设计/项目启动顺序管理.md- 就绪屏障位必须
osFlagsNoClear:osEventFlagsWait默认消费所等位,就绪位被先消费掉 →ftpTask的WaitAll永不成立 → FTP 21 端口静默不监听(无日志无报错)。命令位(SYS_EVT_CFG_SAVE/NET_RECONFIG)保持消费型 - 自研代码禁用 C 库堆分配(
malloc/calloc/realloc/strdup/free):2026-10-23 起由提交门禁机器拦截(coding-standard的 CODE-RULE-20,唯一豁免 = 平台分配后端的宿主侧实现,见docs/门禁说明/coding-standard.md§7):MicroLIB 的 C 堆由startup_stm32f407xx.s的Heap_Size EQU 0x200限定为 512 B,KB 级分配必然返回 NULL,而模拟器(宿主 CRT 堆)完全正常、照不出来 → 只用pvPortMalloc/vPortFree,或把分配收进平台后端 →docs/内存管理/RAM使用分析与优化方案.md - 内存预算紧张:新增任何任务/缓冲/栈前先核算 RAM 余量;改栈或加大局部变量前后各跑一次
scripts/stack_report.py(静态最坏链 + 串口[STACK]水位)与scripts/ram_report.py(.map 静态占用);总量红线已机械化:mem-budget钩子拦 RW_IRAM1/RW_CCM >92%,子系统配额 / CCM 台账 / 趋势见docs/内存管理/RAM预算台账.md(每次@build后跑scripts/ram_baseline.py --update)→docs/内存管理/ - FreeRTOS V10.3.1(CMSIS-RTOS V2);HAL 时基 = TIM7(非 SysTick),tick = 1ms;NVIC 4 位优先级分组,外设中断优先级 ≥ 5
采集链(ADC / RS-485)
- 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
存储与网络外设
- FatFs(
Lib/FatFs移植版)两条台架实证陷阱:① 追加写必须FA_WRITE|FA_OPEN_APPEND——该移植FA_OPEN_APPEND不含FA_WRITE,不带写位打开的文件f_write恒返FR_DENIED;且f_write簇分配失败时返回FR_OK+短写(无错误码)→ 判失败必须校验写回字节数。②FF_USE_LFN=0→ 只支持严格 8.3 短名(不得以点开头),.fschk恒返FR_INVALID_NAME;自愈类逻辑严禁把名字/参数类错误码归入卷损坏触发集 → 完整口径与标志位编号表docs/架构设计/STM32F4-Base存储架构说明.md§10.1 - CH395F SPI:每次事务须
ch395f_spi_begin()/end()包裹;分频系数必须为 8(PCB 约束,勿"优化")
日志与工具链
DBG_*每条日志约 90 B Flash + 216 B 栈(每个调用点内联行缓冲 + 时间戳缓冲)→ 日志不是免费的,已覆盖的步骤别加重复锚点,只在原先静默返回的黑洞处补;深调用链模块在#include "dbg_log.h"之前#define DBG_LINE_BUF_MAX 128U(取值必须 ≥ 该模块最长日志行,否则该行被截断)MDK-ARM/*.uvprojx每次@build后被 UV4 自动改写,改动一律不入库(XML 归一化 +PackID降到本机 DFP 版本);判据与例外(真的增删源文件才提交)→keil-buildskill
硬件引脚
- 完整引脚表(含 SD2506 I2C1、SWD、调试口)→
docs/产品需求/产品需求设计文档.md§4.2(权威,V0.1 口径;勿在本文重复抄表) - ⚠ 网名
AINn≠ 板级ADCn≠ 转换器通道号(17 路里 13 路置换)→ 取数必须查docs/硬件平台/ADC通道映射说明.md(TPAFEbuf[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)
模块约定
- CH395F 单连接 socket:监听与数据通道复用同一 Socket(CONNECT 后转数据通道,DISCONNECT/TIMEOUT 后重开 → LISTEN);所有 CH395F 中断在
net_poll()内经GET_GLOB_INT_STATUS集中读取并清除,不得在 ISR 或其他地方单独处理。⚠ 全局寄存器整体读即清、芯片无按位清除手段,而 net_poll 只在全局位为 1 时才读该 Socket 的 SINT → "顺手全量清一遍"等于把别的在用 Socket 的门焊死(B3 实证)。现存读清点全量审计 + 新增一处读清的三条准入判据 →docs/网络/网络自愈架构设计.md§4.10 - 所有
net_*socket 操作经netMsgQueue在 netTask 串行执行;非 netTask 任务直接调net_*API,勿直接碰 CH395F 驱动 net_*公开 API 的错误码经"任务通知值"回传:net_msg_t是按值拷入队列的,netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭xTaskNotify()通知值回来。勿给net_msg_t加"回传用"字段(调用方只会读到栈垃圾)- 单连接 socket 的断开语义:EOF 时 net 层已
auto_relisten,应用层不得在 EOF 时net_close该 fd(会连监听一起毁掉 → 端口"连接被拒");仅当数据 fd ≠ 监听 fd(模拟器多连接后端)时才关 - 网口配置:
/config.ini[network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;旧档/cfg/netcfg.bin迁移已于 2026-10-10 删除,旧档存在则被忽略);同文件 [ftp] 段 = FTP 鉴权(user/pwd_hash= SHA-256(user‖password) /write,出厂默认 admin/admin,默认口令在用有启动告警;连续 5 次失败锁 60 s)→docs/子系统设计/网络配置说明.md - 新 .c 文件必须手动注册到两处才能双端编译:
MDK-ARM/STM32F407-Demo.uvprojx+simulator/CMakeLists.txt(用new-source-fileskill);⚠ 新文件落在还没有的新目录,还要把该目录加进uvprojx的<IncludePath>——否则报错落在消费者 TU 上(cannot open source input file),极易误判 - 测试构建:
test/board/test_config.h的TEST_SUITE_CH395F/STORAGE/GD5F/RTC互斥(一次只能一个,#error);全部注释 = 产品构建,受test-suite-switch钩子门禁;ENABLE_DESTRUCTIVE_TESTS默认关闭(关闭时套件只读模式,可随时在在用设备上跑)——⚠ 跑一轮破坏性 GD5F 即让 dhara 多记一个坏块且该计数持久化,普通恢复清不掉 →docs/测试说明/存储维护与容量恢复.md;板级套件运行用board-test-suiteskill - 改驱动前先读对应
docs/问题记录/*_Trap_Records.md(CH395F / GD5F2GQ5UE / SD2506);各模块测试指南见docs/测试说明/ - 改 UI / 绘制缓冲前读两条实证坑(I1 palette 头越界、白底白字整屏纯白)→
docs/UI设计/UI_设计架构.md§1.4 第 8/9 条,ui-verifyskill 亦载
文档与图形
- 登记簿类文档「完成即销项」:
docs/任务规划/README.md这类待办登记簿,事项修复/关闭后直接从表中删除该行(不保留"已消化"行)——结论与证据应已沉淀进对应模块文档/问题记录。销项时必须:① 先核对目标文档确已含完整记录 ② 同步清理其他文档对该编号的引用 ③ 改完跑check_doc_links.py --all+ pre-commit。新条目标识(用户裁定 2026-10-14):#列填语义短语(如沉降窗 800ms 上限只留痕),不再新增字母+数字编号——"编号不回收"只写规则却不留痕迹就不可执行(销项删行后仓库无痕迹,实测N-31于 2026-10-14 被复用撞号),语义标识自带唯一性;存量编号冻结沿用、已随销项消失的号永久作废 →docs/任务规划/README.md§0 - 图形走
draw-diagramskill:精确布局图用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<FilePath>/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
边界(Ask first / Never)
Ask first(须用户明确指示才做,均不是任何任务的隐含步骤):git commit / git push(构建与测试通过后停下等指令)· 开 ENABLE_DESTRUCTIVE_TESTS 跑破坏性套件(持久代价 → 模块约定)· CubeMX 重生成(→ cubemx-regen skill)· 登记簿删行销项(→ 文档与图形)· 建任务/抬栈/加大缓冲(先跑预算脚本 → 平台约束)
Never(任何任务):用 Get-Content/Set-Content/cmd 重写含中文文件、用 git checkout/reset --hard 丢弃未提交工作(→ 铁律)· 在 USER CODE 区之外写定制代码、自研代码写 C 库堆分配(→ 平台约束)· 非 defaultTask 直接触碰配置引擎、非 netTask 直接碰 CH395F 驱动(→ 模块约定)· 提交 UV4 抖动的 *.uvprojx、提交 .mnemon/(记忆工具本地状态,已 .gitignore)
按主题查哪份文档
| 主题 | 去处 |
|---|---|
| 全量索引 / 推荐阅读顺序 | docs/README.md |
| 架构全景 / 启动时序与内核前模式 | docs/架构设计/项目架构总览.md、docs/架构设计/项目启动顺序管理.md |
| 产品需求基线(做到什么程度) | docs/产品需求/产品需求设计文档.md |
看门狗 + CubeMX 重生成存活方法论 + ARMCC __asm |
docs/子系统设计/看门狗设计说明.md §10 |
| ADC 采样节拍 / 通道映射 / V0.1 硬件缺陷 | docs/子系统设计/ADC采样节拍设计说明.md、docs/硬件平台/ADC通道映射说明.md |
| 存储栈(FatFs / dhara / lftpd / 两条 FatFs 陷阱) | docs/架构设计/STM32F4-Base存储架构说明.md |
| RAM / 栈预算 / 容量台账 | docs/内存管理/RAM使用分析与优化方案.md、内存内容分配策略.md |
| 每个提交门禁一篇详解(查什么 / 拦下过什么真事 / 限制与盲区 / 基线口径) | docs/门禁说明/README.md(39 个钩子 → 23 篇索引) |
实体编号怎么写 / 前缀总表 / id-check 八项校验 |
docs/编号规范.md(§4 总表、§8 门禁与实测基线) |
| 提交门禁通用机制(阶段 / stash 陷阱 / 触发面 / 增量拦截三段式) | docs/代码规范/pre-commit使用说明.md §2 |
| 代码规范 V1.0 / cppcheck 口径 | docs/代码规范/嵌入式C语言代码规范_V1.0.md、cppcheck使用说明.md |
| 自动化测试 / 实机测试 / 板级套件 | docs/测试说明/自动化测试流程.md、实机测试实施指南.md、board-test-suite skill |
| 烧录与复位语义 | docs/测试说明/ST-LINK_CLI_复位使用说明.md |
| 串口助手 | serial-assistant skill(命令/判据/陷阱,用到自动加载)、docs/测试说明/串口助手使用指南.md、串口助手设计说明.md |
| UI 架构 / 模拟器 / UI 自动化回归 | lvgl-simulator skill(构建/自检/报错速查,用到自动加载)、docs/UI设计/UI_设计架构.md、LVGL模拟器部署与使用说明.md、UI_自动化测试流程.md;UI 改动回归走 ui-verify skill |
| 待办与审计发现登记簿 | docs/任务规划/README.md |
| 多任务并行开发(开空间 / 合并回收 / 台架与构建排队) | task-worktree skill(@task,用到自动加载)、docs/环境与部署/并行任务工作树方案.md |
| 换机 / 工具路径环境变量 | docs/环境与部署/ |
| 远程 CI(Gitea Actions,只跑本机一台 runner) | docs/CI/README.md |