Files
STM32F4-Base/AGENTS.md
T

32 KiB
Raw Blame History

STM32F4-Base

STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);仅 Keil MDK-ARM v5 (ARMCC, C99) 工具链;FreeRTOS V10.3.1 / CMSIS-RTOS2;192KB SRAM / 1MB Flash。

任何代码修改后必须先 @build 验证(0 错误 0 警告)再提交;硬件闭环用 @test。

铁律:源码编辑与编码

  • 改代码只用 Edit 工具(精确子串替换);批量替换用 Edit 的 replaceAll。绝不用 Get-Content/Set-Content/cmd 重写含中文的文件——整文件重写会按系统默认编码重解码 → U+FFFD 乱码且不可逆
  • 字符串字面量必须纯 ASCII(2026-09-15 实测):本机 ARMCC 对无 BOM 源文件按 CP936 解读,字符串内含奇数个汉字时末尾高字节吞掉结束引号 → 编译错(偶数个汉字侥幸通过);中文只写注释(注释不参与解析)。带 BOM 的文件(Drivers/BSP/UI/*、App/task/ui_task.c、App/cfg/cfg_file.c、Lib/lv_conf.h)经 BOM 按 UTF-8 解码,不受此条限制
  • 确需脚本级重写:工程源码为 UTF-8(多数文件无 BOM,UI 系带 BOM),按原样同编码回写、保持 BOM 状态;拿不准就别碰
  • 发现乱码:先统计 EF BF BD 字节数确认真损坏(勿信终端/Read 渲染);不要急著 git checkout(会丢弃全部未提交工作)——先用 Edit 只修坏行
  • Edit 工具会剥掉文件原有的 UTF-8 BOM(2026-09-18 实测):带 BOM 文件(如 App/cfg/cfg_file.c)被 Edit 后 BOM 静默消失 → ARMCC 改按 CP936 解码 → 中文字符串字面量报 #870-D: invalid multibyte character sequence(只有重编译该文件才暴露,历史"0 警告"不代表安全)。改完带中文字面量的文件须核对 BOM 并按原样补回(只在文件头前缀 3 字节 EF BB BF,勿整文件重写)
  • 行尾策略已写死进 .gitattributes(2026-10-09):* text=auto eol=lf(仓库 blob 与工作区统一 LF)+ *.bat/*.cmd 强制 CRLF(cmd.exe 对 LF-only 的 goto/标签不可靠)+ PNG/TTF/DLL/LIB 等显式 binary + *.jpg 走 LFS 且 -text(该条必须排在 * 之后才能覆盖)。不要再依赖 core.autocrlf,也不要把工作区文件"改回 CRLF"——CubeMX 重生成写的就是 LF;此前每次重生成后 40+ 个第三方文件在 git status 里显示"已修改"(内容归一化后一致),根因就是行尾由每台机器各自的 autocrlf 决定。第三方/生成文件的"文件末尾空行"已一次性对齐工具输出(53bd9af),勿再用改写型钩子去修剪(已在 trailing-whitespace/end-of-file-fixer 豁免)

构建与验证

  • @build = & "MDK-ARM\build.bat"(仓库根目录用相对路径);退出码 0 = 0 错误 0 警告,1 = 有警告或错误(查 MDK-ARM/build_log.txt),2 = DTU_KEIL_UV4 未设/无效(换机必设,见 docs/环境与部署/本地工具路径环境变量方案.md);UV4 路径经 DTU_KEIL_UV4 环境变量解析(不再硬编码;显式设置但无效会立即报错、不静默回退)
  • @test / test/host/run_boot_test.py 一键闭环(2026-09-20 起为纯 Python 标准库实现,替代 PowerShell 版;& '.venv\Scripts\python.exe' test/host/run_boot_test.py):开串口+清日志 → 烧录(MDK-ARM/download.bat,下载后 Reset&Run)→ wait-idle(等 RX 静默 8s,超时 30s)→ test/host/verify_log.py 标记校验;退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法/预检错误
  • 默认校验标记:Required App init OK / Network Init / net init: OK;Forbidden FAIL / ERROR / HardFault / [ERR](四个 Fault 的崩溃日志都以 [ERR] 开头 → 一律判 FAIL,这是刻意的:出现 Fault 即本次运行不通过);按模块定制标记(网络/存储/RTC/ADC/RS485/UI)见 docs/测试说明/自动化测试流程.md
  • ⚠️ 旧绝对路径 D:\Code\DTU 程序\... 出现即失效(仓库已搬迁),一律改用仓库相对路径
  • 下载/烧录铁律(2026-09-20 实测):走 & "MDK-ARM\reset.bat" flash [文件](= ST-LINK_CLI -c SWD -P <文件> -Rst -Run),烧后 -Rst -Run 不可省略——裸 -P 会报 Programming Complete,但目标核处于死循环/异常状态时写入并不落地(芯片内容不变;这就是 09/19~09/20 "反复重下载、芯片一直是旧镜像、二分法失效" 的根因);.hex/.bin 均可直烧(1MB ≈ 6.5s,快于 UV4 路径);只重启不烧录用 reset.bat 无参(= -c SWD -Rst -Run,本机 3 线接线唯一复位手段:nRST 未接、-HardRst/RM= 不可用)
  • 烧录后必须回读校验:ST-LINK_CLI -c SWD -CmpFile <文件> 0x08000000(全片逐字节比对,No difference found 才算真正写入);CLI 经 DTU_STLINK_CLI 环境变量解析(未设时回退 PATH;本机已入 PATH,未设也能工作)(v3.5.0.0,探针 SN 0B250D12A218303030303032)→ 完整用法/复位语义/故障排查 docs/测试说明/ST-LINK_CLI_复位使用说明.md

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,重建后 L0 门禁直接可跑;DTU_PRECOMMIT_PYTHON 可指向装了 pre-commit 的其他解释器)(.venv/ 已 gitignore)
  • requirements.txt 必须纯 ASCII(Windows 下 pip 按 GBK 代码页解码,中文注释会 UnicodeDecodeError)

提交门禁(pre-commit)

  • 任一失败即拦截:cppcheck(warning/performance/portability/style)+ C 代码规范机械检查 + U+FFFD 乱码检查 + gitlint;范围仅项目代码(第三方 Lib//Middlewares//CMSIS/HAL/lftpd/dhara/MDK-ARM/test/ 排除;规范检查另排除 CubeMX 生成的 Src//Inc/)
  • 改写型钩子(trailing-whitespace + end-of-file-fixer)豁免 CubeMX/Keil 生成文件(2026-10-09 定;配置里 YAML 锚点 &cubemx-generated 两处共用):Src/*.{c,h}、Inc/*.{c,h}、Drivers/CMSIS/、Drivers/STM32F4xx_HAL_Driver/、Middlewares/、MDK-ARM/*.{uvprojx,uvoptx,sct,map}、.mxproject、*.ioc——这类文件每次重生成按工具自己的风格写回(实测每次 10+ 个文件各被补 1 行末尾空行),改写只会制造提交抖动。⚠ 只读检查不受影响:cppcheck / 乱码 / cubemx-survival / task-table / mem-numbers 仍覆盖 Src/、Inc/;App/、Drivers/BSP/ 自研代码仍受改写型钩子检查
  • 提交信息:单行中文标题 <100 字符(gitlint),UTF-8 无 BOM + git commit -F <文件>(防终端转码破坏)
  • 代码规范 V1.0:小写下划线命名(g_/s_/p_/a_ 前缀)、头文件保护宏 XXX_H、常量在右、文件头模块注释块 → docs/代码规范/
  • 全库扫描 pre-commit run --all-files;详见 docs/代码规范/pre-commit使用说明.md
  • CI(Gitea Actions,2026-10-01 T-6):.gitea/workflows/ci.yml 在 push/PR 跑 L0 静态门禁 + L0.5 主机侧单测(Windows runner);不含 L1/L2/L3(需真机+ST-LINK)、刻意不跑 @build(需 Keil 授权)、无 .map 时自适应跳过 mem-numbers。本地等价自查:& '.venv\Scripts\python.exe' scripts/ci_local.py(runner 未就绪时这是 L0 的唯一保障,不能因"已配 CI"放宽本地门禁)。详见 docs/测试说明/测试开发标准流程.md §9

平台约束

  • Inc//Src/ 中 CubeMX 生成文件带 USER CODE BEGIN/END 标记:自定义代码只写标记区之间;Drivers/BSP/ 为纯手工代码
  • CubeMX 重生成会覆盖标记区之外的一切(.ioc 的 KeepUserCode=true 只保护 USER CODE 区;2026-09-29 两次重生成各覆盖 4~5 处定制,其中 P1 两处不报编译错、只在真出故障时暴露)。防止定制丢失按三层优先级:L1 写进 .ioc(凡 CubeMX 界面能表达的配置,如任务栈/优先级/队列/事件组/堆大小/IWDG 参数——权限最高,重生成只会照写)→ L2 搬进 USER CODE 区(只信 CubeMX 当前模板真实生成的区名,优先文件头 USER CODE BEGIN 0 这类从未变过的标准位)→ L3 审计兜底。每次重生成后立刻跑 python scripts/check_cubemx_survival.py(已挂 pre-commit cubemx-survival 钩子;改动 .ioc/Src//Inc//task_watchdog/.uvprojx 触发)——清单 21 项 + 哈希基线漂移检测,直接列出"被覆盖了哪几处"。2026-10-09 第三次重生成(启用 ADC1)补充:Inc/FreeRTOSConfig.h 的自造 FREE_RTOS_CONFIG_HARDENING 区第二次被抹 → 已改走 L1:configCHECK_FOR_STACK_OVERFLOW=2 / configUSE_MALLOC_FAILED_HOOK=1 在 GUI 的 FreeRTOS → Config parameters → Hook function related definitions 里设(写进 .ioc 由模板生成,永不再丢;教训:别凭印象断言"界面里没有该项",先翻标签页或查 db/mcu/IP/*_Modes.xml)。⚠️ 启用后 CubeMX 会在 Src/freertos.c 生成 USER CODE BEGIN 4 / 5 区(默认内容是空桩)——钩子实现必须整体写进这两个区(原先在 USER CODE Application 区的实现要搬走,否则同名函数两处定义 = 编译错;配套 helper hook_ptr_ok() 须在钩子之前 → 放 USER CODE BEGIN PD 区)。审计项 stack-overflow-hook-impl / malloc-failed-hook-impl / stack-overflow-hook-helper 分别校验 4 / 5 / PD 三个区。⚠️ 不要依赖"自造标记区":2026-09-29 实测 CubeMX 换了一代中断函数模板(旧 HardFault_Handler/HardFault_Report 自造区 → 新函数体内 HardFault_IRQn 0/W1_HardFault_IRQn 0),旧区连同内容一起消失(看起来仍像"遵守了规则")。生成区内无标记位可用的改动只能 L3 兜底(如 Src/iwdg.c 的 HAL_IWDG_Init() 调用、4 个 Fault Handler 本体——本代模板整函数重写),一律加锚点注释(IWDG-DEFER-START-ANCHOR / HARD-FAULT-ANCHOR / MEMFAULT-ANCHOR / BUSFAULT-ANCHOR / USAGEFAULT-ANCHOR)供审计检出。方法论详见 docs/子系统设计/看门狗设计说明.md §10.1/§10.2
  • 4 个 Cortex-M4 Fault 全部采集现场(2026-09-29):HardFault / MemManage / BusFault / UsageFault 均为手工 __asm Handler(取 MSP/PSP 帧指针)→ B 到 per-fault C 入口 fault_entry_{hard,mem,bus,usage}(各自带故障名)→ hard_fault_report(tag, frame) 打 [ERR] <TAG> PC/LR/xPSR/R0-R3/CFSR/HFSR[/BFAR][/MMFAR] 后 NVIC_SystemReset()。后三者默认禁用(复位后 SHCSR 只开 HardFault),不使能就会被升级成 HardFault、日志看不出根因 → fault_handler_enable()(Src/stm32f4xx_it.c 的 USER CODE BEGIN 1 区)在 app_main_init() 第一行调用。刻意不启用 CCR.UNALIGN_TRP / CCR.DIV_0_TRP(会把普通 bug 升级成死机)。⚠️ ARMCC __asm 取不到 C 的数据符号(ADR r1, xxx_tag 报 A1516E: Bad symbol,去 static 也无效)且无法传字符串参数 → 故障名必须由 C 侧 per-fault 入口带上,不要在 __asm 里取 C 变量
  • SHCSR 的 Fault 使能位无法在 CubeMX 配置(2026-10-01 查模板源码证实):CubeMX 对 System Exception 只生成优先级(且仅当优先级 ≠ 0),从不生成使能代码——tpl_main_c.ftl 的 MX_NVIC_Init() 用 [#if vector.systemHandler=="false"] 挡住 HAL_NVIC_EnableIRQ,而 NVIC-STM32F417_Modes.xml 里 MemoryManagement/BusFault/UsageFault_IRQn 都标 Y(=systemHandler);全部模板中 SHCSR 只出现 1 次且是 MPU 清理的 &= ~MEMFAULTENA(禁用),无任何 |= 置位;MPU 路径也堵死(.ioc 无 MPU,且 HAL_MPU_Enable 只置 MEMFAULTENA 一位)。⚠️ .ioc 的 NVIC.MemoryManagement_IRQn=true 不代表已使能(第 1 段只是"向量在 NVIC 表里可见")——别误以为勾了 Enabled 就等于开了故障。结论:只能手工写(现放 USER CODE BEGIN 1 区,属 L2,重生成不丢)+ 审计项 fault-handler-enable 兜底
  • MDK-ARM/*.uvprojx 每次 @build 后被 Keil UV4 自动改写,改动一律不入库(2026-09-29 实测):UV4 构建时会把工程 XML 归一化成它本机风格的旧式写法(<Tag /> → <Tag></Tag>、XML 声明加 standalone="no"),并把 PackID 降到本机装的 DFP 版本(本机实测 3.1.1 → 3.0.0,属降级)。这些差异是环境相关的,提交后会把别人的 Pack 版本也一起改掉。判断方法:git diff MDK-ARM/STM32F407-Demo.uvprojx 里若只见 XML 格式/PackID 差异而无源文件挂载增删 → git checkout -- MDK-ARM/STM32F407-Demo.uvprojx 丢弃。只有真的新增/删除源文件(grep 文件名 计数变化)才提交
  • ARMCC 内联汇编语法(2026-09-29 踩坑,共 3 坑):① __asm volatile ("MRS %0, lr" : "=r" (x)) 是 GCC 语法,ARMCC 报 #18: expected a ")";② __asm { MRS x, lr } 块内联会把寄存器名当 C 标识符报 #20: identifier "lr" is undefined;③ __asm 块里取不到 C 的数据符号,ADR r1, xxx_tag 报 A1516E: Bad symbol 'xxx_tag'(去掉 static 也无效——汇编器只认自己作用域内的符号)。结论:整个函数用 __asm void f(void) { ... };要传"字符串/表格"这类 C 侧数据,就让 __asm 只取寄存器参数然后 B 到一个普通 C 函数,由 C 侧补上。若要覆盖 CubeMX 生成的同名函数,用 #define f f_Gen(在生成实体之前)把生成实体改名——链接器会把未被引用的 f_Gen 自动移除(.map 可见 Removing ... (N bytes)),零代码开销且不删生成区任何一行
  • FreeRTOS V10.3.1(CMSIS-RTOS V2);HAL 时基 = TIM7(非 SysTick),tick = 1ms;NVIC 4 位优先级分组,外设中断优先级 ≥ 5
  • 内核启动(osKernelStart)前禁止任何 FreeRTOS/CMSIS-RTOS API 调用(2026-09-20 启动失败实证根因,修复 82411cf):osMessageQueueNew/osMessageQueuePut/osMutexNew/osSemaphoreNew 等在内核启动前调用会杀死启动(09/19~09/20 "每次必死" 的代码层根因 = syslog_mcu_init 在内核启动前调用 osMessageQueueNew)。内核启动前代码的标准模式:纯寄存器/HAL 轮询操作 + 无锁静态缓冲(如 s_preboot[8] 暂存内核前日志)+ 内核启动后惰性创建真队列(任务上下文首次使用时,见 syslog_drain);凡在内核启动前执行的初始化函数里不得创建任何 CMSIS 对象
  • CH395F SPI:每次事务须 ch395f_spi_begin()/end() 包裹;分频系数必须为 8(PCB 约束,勿"优化")
  • 就绪屏障位必须 osFlagsNoClear(2026-09-21 实证):osEventFlagsWait 默认消费所等位,而 SYS_EVT_NET_READY/SYS_EVT_STORAGE_READY 是状态位——被 netTask/uiTask/timeSync 先消费掉后,ftpTask 的 WaitAll(两位) 永不成立 → FTP 21 端口静默不监听(无日志、无报错,纯靠可观测性才揪出来)。就绪位一律 osFlagsWaitAny|osFlagsNoClear / osFlagsWaitAll|osFlagsNoClear;命令位(SYS_EVT_CFG_SAVE/SYS_EVT_NET_RECONFIG)保持消费型等待
  • 内存预算紧张:新增任何任务/缓冲/栈前必须先核算 RAM 余量(docs/内存管理/、待处理内容.md);栈数据来源:& '.venv\Scripts\python.exe' scripts/stack_report.py -s tools\serial_tool\state\serial.log(Keil 静态调用图 → 每任务最坏链,+ 串口 [STACK] 运行时水位)与 scripts/ram_report.py(.map 静态占用);改栈或加大局部变量前后各跑一次
  • 自研代码禁用 C 库堆分配(2026-09-18,P0-1):禁止 malloc/calloc/realloc/strdup/free——本机工具链 MicroLIB 的 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 限定为 512 B,KB 级分配必然返回 NULL 且模拟器(宿主 CRT 堆)完全正常、照不出来。需要动态内存只用 pvPortMalloc/vPortFree(FreeRTOS 堆 configTOTAL_HEAP_SIZE),或像配置引擎那样把分配收进平台后端(cfg_store_alloc/free/heap_free:MCU=FreeRTOS 堆、模拟器=宿主堆,共享核心零分配符号);分配失败必须有 FAIL 日志 + 自检上报,暂态峰值要写编译期预算
  • FatFs 打开模式陷阱(2026-09-20 台架实测,Lib/FatFs 移植版):FA_OPEN_APPEND(0x30) = FA_OPEN_ALWAYS|FA_SEEKEND,不含 FA_WRITE(0x02)——不带写位打开的文件 f_write 恒返回 FR_DENIED(7)(/log 写失败真根因);追加写必须用 FA_WRITE|FA_OPEN_APPEND。该移植的 f_write 簇分配失败时返回 FR_OK+短写(无错误码),判失败必须校验写回字节数。标志位为旧式编号(FA_READ=0x01 / FA_WRITE=0x02 / FA_CREATE_NEW=0x04 / FA_CREATE_ALWAYS=0x08 / FA_OPEN_ALWAYS=0x10 / FA_OPEN_APPEND=0x30 / FA_SEEKEND=0x20(ff.c 内部定义)),与上游 FatFs R0.14 不同,勿照抄上游代码示例
  • FatFs 文件名约束(2026-09-20 台架实证,.fschk 无限重格式化事故根因):本移植 Lib/FatFs/ffconf.h 为 FF_USE_LFN=0,只支持严格 8.3 短名——主名≤8 字符、扩展≤3 字符、不得以点开头(.fschk 会被 ff.c create_short 把点后当扩展域解析超限 → f_open 恒返 FR_INVALID_NAME(6),纯 RAM 名字判定、不碰盘)。ff.c create_short 全部拒绝点已加 DEBUG 打印:[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符,非法名字调用看开机日志即可定位。自愈类逻辑严禁把名字/参数类错误码(FR_INVALID_NAME 等)归入卷损坏触发集(整卷重格式化不可逆)
  • DBG_* 每条日志约 90 B Flash + 192 B 栈(2026-10-02 S10 实测):dbg_log.h 的 DBG_INFO/ERROR/DEBUG 在每个调用点内联 char __con_line[DBG_CON_LINE_MAX](console_log.h 定义 192 B)+ 完整 snprintf 调用序列 → 单条约 90 B Flash(字符串常量另计)。给新模块接入日志时按"条数 × 90 B"估算 Flash 代价(实测 sd2506_init 加 6 条 = +576 B;app_main 加 3 条 = +180 B);深调用链模块另需核算栈(每条日志在栈上多占 192 B,会进 stack_report.py 的最坏链)。故日志不是免费的——已覆盖的步骤不要再加"进入前/完成后"重复锚点,只在真正黑洞(原先静默返回)处补
Task 栈 (words) 优先级
defaultTask 1024 Normal
netTask 960 Normal
ftpTask 1024 Normal
testTask 2048 BelowNormal
uiTask 4096 BelowNormal
timeSyncTask 512 Normal

(实际值以 Src/freertos.c 为准;testTask 仅在启用测试套件时创建。改动栈/缓冲前先核对此表与实测。2026-09-21 上调:defaultTask 512→1024(扛 mount/mkfs/FAT探针/cfg/syslog 深层 FatFS I/O,旧值经 configCHECK_FOR_STACK_OVERFLOW=2 钩子实测溢出)、testTask 1024→2048(存储套件 deep call chain + 局部缓冲溢出)。FreeRTOS 堆已迁 CCM,任务栈合计已不占 SRAM;2026-09-21 可观测性落地后实测:ftpTask 峰值曾达 3480B/4096B(余量 15%,低于 1024B 底线)→ 已按 docs/内存管理/RAM使用分析与优化方案.md §1.4 把 lftpd 9 处大局部缓冲静态化(BSS +4.6KB),复测峰值 1184B/4096B(余量 71%),6 任务全 PASS)

架构要点

Src/main.c(CubeMX 初始化)→ app_main_init → osKernelStart → 6 任务(osKernelStart 之前全是内核启动前代码:禁调任何 FreeRTOS/CMSIS-RTOS API,见「平台约束」)

  • app_main_init(时间子系统优先,使后续日志带时间戳):sd2506_init → sys_clock_init(墙钟;RTC 故障时降级默认时间基准并返回 -1 触发"时钟芯片异常"自检;sys_clock_get 每 30 天惰性再基准化防 49.7 天 tick 回绕)→ rs485_init → gd5f2gq5ue_init → tpafe5160_init → SD2506 诊断 → tpafe5160_start_conv_irq
  • 就绪屏障(App/app/sys_events.h Event Flags,取代魔法延时):SYS_EVT_NET_READY(netTask 置位)/ STORAGE_READY(defaultTask:卷挂载 + 网络配置加载完成)/ FTP_READY / TIMESYNC_READY;消费者用 osEventFlagsWait 阻塞等待
  • netTask:net_stack_bring_up(配置已由 defaultTask 在屏障前加载,本任务只读 RAM 副本 → net_init + PHY 强制 100M 全双工 → 递增网络代次号)→ 置 NET_READY → 循环 net_poll + net_process_messages;消费 SYS_EVT_NET_RECONFIG → 立即重建网络(新 IP 免重启生效,各 TCP 服务据代次号重建监听)
  • defaultTask:f_mount forced(无卷自动 f_mkfs FAT32;挂载后卷健康探针:小写两击确认,FAT 区陈旧/擦除时自动重格式化自愈,/config.ini 回落工厂默认——2026-09-20 /log 写失败修复)→ 网络配置加载(net_cfg_load,首启含旧档迁移)→ STORAGE_READY → LED 闪烁;同时是 syslog(/log/run.log·selftest.log,定长 64B 记录)的唯一写者与配置引擎属主(cfg_file 静态态单任务收敛于此:UI 保存请求执行 net_cfg_save_run + 2s cfg_file_poll,经 SYS_EVT_CFG_SAVE 事件唤醒;netTask/uiTask 不得直接触碰配置引擎,见 P1-3)
  • 全景 docs/架构设计/项目架构总览.md(六层分层 / 启动时序 / 存储·网络·UI 栈 / 目录组织)

硬件引脚

外设 接口 引脚
CH395F 以太网 SPI2 PB12 CS, PB13 SCK, PB14 SDO, PB15 SDI
GD5F2GQ5UE NAND SPI1 PE0 CS, PB3 SCK, PB4 MISO, PB5 MOSI, PB8 WP, PE1 HOLD
TPAFE5160 ADC 并行16位 PG0-PG15 数据, PC8 RD, PC9 CONVST, PC7 BUSY(V0.1;FRSTDATA=PC6,OS0/OS1/OS2=PA12/PA11/PA8)
ADC 通道语义(V0.1,17 路 = 8×TPAFE + 9×STM32) 见映射表 网名 AINn ≠ 板级 ADCn ≠ 转换器通道号(13/17 路置换);语义 UA/UB/UC/UN=AIN1~4、IA/IB/IC/IN=AIN8~11、V_ZL2=温度、其余 8 路备用——取数必须查 docs/硬件平台/ADC通道映射说明.md(TPAFE buf[2]=IA/buf[5]=UN/buf[6]=UA;网名 ADC17=PC4 用 ADC_CHANNEL_14,不是 ADC_CHANNEL_12)
USART1 调试串口 UART PA9 TX, PA10 RX (115200)
RS-485 UART4 PC10 TX, PC11 RX, PA15 DE(半双工)
6x LED GPIO PC5, PB0, PB1, PB2, PF11, PF12(低电平点亮)

模块约定

  • CH395F 单连接 socket:监听与数据通道复用同一 Socket(CONNECT 后转数据通道,DISCONNECT/TIMEOUT 后重开 → LISTEN);所有 CH395F 中断在 net_poll() 内经 GET_GLOB_INT_STATUS 集中读取并清除,不得在 ISR 或其他地方单独处理
  • 所有 net_* socket 操作经 netMsgQueue 消息队列在 netTask 串行执行(线程安全);非 netTask 任务直接调 net_* API,勿直接碰 CH395F 驱动
  • net_* 公开 API 的错误码经"任务通知值"回传(2026-09-21 实证):net_msg_t 是按值拷入队列的,netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 xTaskNotify() 通知值回来(失败=负的具体错误码如 NET_ERR_WOULDBLOCK、成功=字节数/描述符),公开 API 恢复 errno 后对外统一返回 -1。勿给 net_msg_t 加"回传用"字段(调用方只会读到栈垃圾;曾据此"修"错误码,导致 ui_net_sink 每次空轮询都误判为真错误 → 连上即 net_close)
  • 单连接 socket 的断开语义:监听 Socket 在 CONNECT 后自身即数据通道(g_client == g_listen),EOF 时 net 层已 auto_relisten;应用层不得在 EOF 时 net_close 该 fd(会连监听一起毁掉 → 端口"连接被拒"),仅当数据 fd ≠ 监听 fd(模拟器多连接后端)时才关
  • 网口配置:/config.ini [network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;旧档 /cfg/netcfg.bin 首启自动迁移)→ docs/子系统设计/网络配置说明.md
  • 新 .c 文件必须手动注册到两处才能双端编译:MDK-ARM/STM32F407-Demo.uvprojx + simulator/CMakeLists.txt(用 new-source-file skill)
  • MDK-ARM/*.uvprojx 每次 @build 后被 Keil UV4 自动改写,改动一律不入库(2026-09-29 实测):UV4 构建时会把工程 XML 归一化成它本机风格的旧式写法(<Tag /> → <Tag></Tag>、XML 声明加 standalone="no"),并把 PackID 降到本机装的 DFP 版本(本机实测 3.1.1 → 3.0.0,属降级)。这些差异是环境相关的,提交后会把别人的 Pack 版本也一起改掉。判断方法:git diff MDK-ARM/STM32F407-Demo.uvprojx 里若只见 XML 格式/PackID 差异而无源文件挂载增删 → git checkout -- MDK-ARM/STM32F407-Demo.uvprojx 丢弃。只有真的新增/删除源文件(grep 文件名 计数变化)才提交
  • 测试构建:test/board/test_config.h 的 TEST_SUITE_CH395F/STORAGE/GD5F/RTC 互斥(一次只能启用一个,#error);全部注释 = 产品构建;该开关受 pre-commit 钩子 test-suite-switch 门禁(默认要求产品构建,跑完套件回归须改回;确需提交测试构建用 ALLOW_TEST_BUILD=1 显式放行——2026-10-01 实测已提交 HEAD 里 TEST_SUITE_CH395F 长期开启,纯靠人记得改,故补此门禁);破坏性用例默认关闭:同文件 ENABLE_DESTRUCTIVE_TESTS(默认注释)控制写/擦 NAND、格式化、坏块注入与 FatFS 写类用例——关闭时套件为只读模式(开机横幅 mode: READ-ONLY,可随时在在用设备上跑、不动卷内容与容量),只在产品首次上电/产线首启或明确需要时打开(mode: DESTRUCTIVE);2026-09-21 实测一轮破坏性 GD5F 运行即让 dhara 多记一个坏块(容量 95248 → 95200;该计数持久化在 dhara journal checkpoint 头里,普通恢复模式清不掉,需全片擦除——见 docs/测试说明/存储维护与容量恢复.md);CH395F 套件 test/board/ch395f_test.h:硬件层 Phase 15 与 NET 层 Phase 611 互斥;CH395F 硬件层模式下 netTask(基础线程:无条件创建、全构建唯一实例)入口自挂起(不运行),testTask 独占 CH395F
  • 改驱动前先读对应 docs/问题记录/*_Trap_Records.md(CH395F / GD5F2GQ5UE / SD2506);各模块测试指南见 docs/测试说明/;存储维护/容量恢复(全片擦除恢复 dhara 坏块记账)见 docs/测试说明/存储维护与容量恢复.md
  • LVGL I1 绘制缓冲含 palette 头(2026-09-21 实证):索引色格式 draw buffer 布局 = [palette 8B][pixel data stride×h](lv_draw_buf_create 加 8B、lv_draw_buf_goto_xy/lv_refr get_max_row 都按此跳/扣),但 lv_display_set_buffers/lv_draw_buf_init 的容量校验只看 stride×h——只给 3200B 会被静默接受:末 8 个像素字节写到缓冲外(曾打脏相邻 s_frame_box[0] 指针 → HardFault),且 flush 收到的首地址含 palette(整帧错位 8 字节)。ui_port.c 按 stride×h + palette 供给并在 flush 时 +palette 取像素;详见 docs/UI设计/UI_设计架构.md §1.4 第 8 条
  • MCU 端必须显式应用 dark mono 主题(2026-09-21 实证,远端"纯白屏"根因):lv_display_create() 会按 lv_theme_mono_init(disp, false)(白底黑字)初始化单例主题,且 lv_display_set_theme() 只把主题重应用到 screens[0](底层)→ 活动屏幕仍是 LVGL 类默认浅灰 F5F5F5;而 UI 内容恒用 lv_color_white() 绘制 → 白底白字不可见、远端整帧 99% 为亮(客户端显示纯白)。修法(ui_port.c):lv_theme_mono_init(disp, true, ...) + lv_display_set_theme() + 对活动屏幕再 lv_theme_apply()(实测 F5F5F5 → 000000);同时显式写 I1 调色板头 8B([0]=黑 [1]=白,LVGL 不替调用方初始化);详见 docs/UI设计/UI_设计架构.md §1.4 第 9 条

串口助手 serial_tool

  • 入口:& '.venv\Scripts\python.exe' tools/serial_tool/serial_tool.py <命令>(daemon 自动拉起;全部命令 5s 超时、JSON 输出、exit 0/1/2/3)
  • 全量历史 = 日志文件 tools/serial_tool/state/serial.log(RX/TX/SYS 时间戳行);agent 直接 Read/Grep 文件,无采集窗口仪式;20MB 上限自动截头保尾 10MB
  • 常用:open(幂等)/ send <文本>(或 --hex 0D0A)/ log --tail N --grep P / clear(代际+1)/ wait-idle --settle-ms 8000 --max-ms 30000 / status / close
  • 串口端口单一配置源:.opencode/serial_config.json(当前 COM6,2026-09-21 台架重插后由 COM7 变更);端口变化只改这个文件
  • 命令/工作流/故障排查 docs/测试说明/串口助手使用指南.md;契约/daemon 行为/陷阱 docs/测试说明/串口助手设计说明.md
  • 抓开机日志铁律(2026-09-20 实测):① 顺序 open → clear → 烧录/复位 → wait-idle → 读日志校验标记(采集必须先于烧录,否则错过开机头几行);② wait-idle 必须带 --timeout <秒>(CLI→daemon 默认 5s 硬超时会掐断长等待:wait-idle --timeout 90 --settle-ms 8000 --max-ms 45000);③ 标记校验直接 Read/Grep/Select-String state/serial.log 文件最可靠(log --grep 子命令实测不可信,勿作判定依据)
  • open 报"拒绝访问/端口被占用"时:先查残留旧 daemon(上一会话遗留的 serial_tool.py --serve python 进程——口句柄已死但进程仍占着串口与 HTTP 口 18765)→ taskkill /F /PID <旧PID> → 重新 open(自动拉起新 daemon,系统自动释放口);改 serial_config.json 后同样必须先杀旧 daemon 再 open(daemon 启动时缓存配置)

LVGL PC 模拟器

  • simulator/(CMake + SDL2 预编译库)与 MCU 端共用 UI 源码(ui_menu.c、ui_font_cn_12.c),免烧录开发 UI:& "simulator\build_sim.bat"(按本机 VS 自动选生成器:16=2019 / 17=2022 / 18=2026,勿把生成器版本写死;build/ 缓存生成器与本机 VS 不符时自动删除重建)→ 运行 simulator\build\Debug\lvgl_sim.exe
  • 自检:--cfgtest(配置闭环)/ --logtest(日志引擎)/ --standby shot.bin(待机屏截图)/ --idle <秒>
  • UI 改动一律走 ui-verify skill 五步门禁;含新汉字的 UI 文案提交前先跑 font-regen skill 重生成中文字体

文档与图形

  • 配置文件 docs/子系统设计/配置文件设计说明.md;墙钟 docs/子系统设计/系统墙钟时间维护说明.md;控制台单写者日志 docs/子系统设计/控制台单写者日志设计说明.md;代码规范 docs/代码规范/;测试说明 docs/测试说明/;审计发现与待办 docs/待处理内容.md(统一登记簿:P-x 审计缺陷 / N·S·T-x 观察 / S1x·A-x 测试架构专项 / 阶段排序 / 诊断基线);引用关系图 docs/架构设计/引用关系图说明.md
  • 登记簿类文档「完成即销项」(2026-09-26 定规):docs/待处理内容.md 这类跨文档待办登记簿,事项修复/关闭后直接从表中删除该行(不保留"已消化"行、不建归档区)——结论与证据应已沉淀进对应模块文档/问题记录,登记簿只留未办项。销项时必须:① 先核对目标文档确已含完整记录(删了不丢信息)② 同步清理其他文档对该编号的引用(改为指向目标文档,或注明"已销项")③ 改完跑 check_doc_links.py --all + pre-commit
  • 图形走 draw-diagram skill:精确布局图(协议帧/分层/屏障)用 tools/arch_diagram/gen_*.py 生成器(产物放 docs/images/,脚本与 .svg 一并提交);拓扑图(流程/时序/状态机)用 Mermaid;勿手绘 ASCII 框图
  • 文件引用关系图走 tools/refgraph:& 'tools\refgraph\gen_refgraph.bat' 生成文件级 + 模块级引用图(产物 docs/images/引用关系图.*,脚本与 .dot/.svg/.json 一并提交,PNG 本地预览不提交);项目文件变化后重跑更新;渲染依赖 Graphviz(winget 可装)