数据面(App/algo,纯逻辑,双端编译) - wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask 唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃 兜底——下一次真故障无波可录比丢一段更贵 - wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带 锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度 - 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式 - 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样 纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉 - test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条 自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合 - 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定) 接线(任务层) - adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图), 段头按最低有效位精确记账,不经浮点回算 - adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实; 台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行 - defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到 关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms 补一条告警并记历史峰值(喂狗余量可核) - Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留 超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的, 半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进 - Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到 上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数; 段文件字节布局零改动,三份合成段哈希逐字节不变 - protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体 零改动 人机(160×160 单色屏) - 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器 得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30 - 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与 垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、 单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据 - ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、 手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口 - 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有 60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形 - 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变 - 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏 22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、 仪器用已在跑的单轮工作耗时探针,不新加埋点 台账与文档 - 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936 字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名; 散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建 重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置) - 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据); §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径 风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」 - 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项 - 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划 索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动) - 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件 形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记 「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场 - 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行 227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退 为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与 体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账 同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道 编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串 生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
24 KiB
STM32F4-Base 项目架构总览
版本:v1.1 日期:2026-10-14 平台:STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2)+ FreeRTOS V10.3.1(CMSIS-RTOS2) 用途:一页看清项目的分层、任务、数据流与目录组织。细节以各模块专项文档为准(见 §9)。
1. 全景分层架构
上图由
tools/arch_diagram/gen_arch_svg.py生成(手绘 SVG,布局确定、零连线交叉);改架构后编辑脚本顶部ROWS数据重跑即可,源数据与 §1.1 表一致。 需要 PNG 预览(PPT/聊天分享):用 Edge 无头渲染docs/images/项目架构总览.svg即可(PNG 不入库)。
分层原则:上层只依赖相邻下层(单向)——2026-10-22 起这条方向已有机械检查(
layer-deps钩子 /scripts/check_layer_deps.py:Drivers/BSP/**不得 includeApp/**的头,存量 6 条反向边钉成基线、只减不增,三个横切叶子头console_log.h/sys_clock.h/sha256.h显式豁免);① 业务层为顶层(仅net_cfg,被 UI 与基础服务共用),② UI 层与 ③ 基础服务层同级并列。跨层的具体调用关系见 §1.2 表。
1.1 各层职责与关键模块
① 业务层(顶层,仅
net_cfg);② UI 层与 ③ 基础服务层同级并列(均使用net_cfg);④ 中间件层 → ⑤ 驱动层 → ⑥ 内核/HAL → ⑦ 硬件 依次向下。
| 层 | 目录 | 职责 | 关键模块 |
|---|---|---|---|
| ① 业务层 | Drivers/BSP/NETCFG/ |
顶层共享配置(被 UI 与基础服务共用) | net_cfg |
| ② UI 层 | Drivers/BSP/UI/ |
LVGL 界面(与 PC 模拟器共用源码) | ui_port ui_menu ui_menu_view ui_menu_tab ui_content_page ui_page_* ui_net_sink ui_font_cn_12 ui_deco |
| ③ 基础服务层 | App/、Drivers/BSP/NET/lftpd/ |
应用侧服务与编排 | app_main sys_events app_cfg task/* algo/*(语义取数与有效性~保护判据) lftpd time_sync |
| ④ 中间件层 | Drivers/BSP/NET/、Lib/、App/util/ |
通用库 / 协议栈 | net_socket net_select net_events net_pending net_listener FatFs nand_ftl(dhara) LVGL sys_clock |
| ⑤ 驱动层 | Drivers/BSP/{CH395F,GD5F2GQ5UE,TPAFE5160,SD2506,RS485}/ |
芯片寄存器 / 时序 / 中断 | ch395f gd5f2gq5ue tpafe5160 sd2506 rs485 |
| ⑥ 内核 / HAL | Middlewares/、Drivers/STM32F4xx_HAL_Driver/、Drivers/CMSIS/ |
任务调度、外设 HAL、启动 | FreeRTOS V10.3.1 · STM32 HAL · CMSIS |
| ⑦ 硬件 | — | STM32F407ZGTx @168MHz | SPI1 / SPI2 / I2C1 / UART4 / USART1 / ADC1 / TIM3 / DMA / GPIO |
1.2 关键跨层依赖(对应上图未画出的连线)
| 依赖 | 说明 |
|---|---|
ui_page_net_cfg → net_cfg |
网口设置页显示/编辑 RAM 副本(不直接触 CH395F SPI) |
netTask → net_cfg → net_socket |
上电只读 RAM 副本(defaultTask 已在屏障前 net_cfg_load)→ net_init(ip,mask,gw,mac);配置保存后经 SYS_EVT_NET_RECONFIG 按网络代次号重建(新 IP 免重启生效) |
net_socket → net_events / net_listener / net_pending |
socket 宿主调三个内部模块:芯片边沿事件 + 100ms 周期对账 / 监听生命周期(重建请求登记 + 唯一执行)/ 阻塞 recv·send 挂起请求表 |
App/algo/*(语义取数与有效性→全周傅氏相量→保护判据) → adc_task / protect_task |
采集与算法层为纯计算:adcTask 消费 语义取数与有效性/全周傅氏相量(TIM3 1kHz 节拍)、protectTask 消费 保护判据(20ms 判据节拍) |
lftpd / net_cfg / 测试 → FatFs → nand_ftl → gd5f2gq5ue |
文件与配置落 NAND |
ui_net_sink / time_sync → net_socket |
远程显示 :7003 / 对时 :8888 共用同一 TCP 栈 |
ui_menu → ui_content_page → ui_page_* |
框架 + 页面插件(描述符解耦) |
time_sync / sys_clock → sd2506 |
墙钟基准与写回 RTC |
ui_port / ui_page_* → LVGL |
UI 全部绘制走 LVGL |
*rs485_task.c任务体已实现,但当前未在freertos.c创建(B2 暂缓,见docs/任务规划/暂缓-RS485.md);adc_task.c已于 2026-10-10 接入创建(adcTask)。
2. 运行时任务与就绪屏障
2.1 任务一览(Src/freertos.c 创建)
| 任务 | 栈 (字节) | 优先级 | 职责 | 入口 |
|---|---|---|---|---|
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 栈(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 双缓冲)→ 语义取数与有效性/全周傅氏相量消费,不取数不做保护运算 |
adc_task_func |
iec104Task |
2560 | Normal | 等网络 → IEC 60870-5-104 上行(:2404 监听 + link 状态机,遥信/遥测/定值区,B4) | iec104_serv_task |
protectTask |
2048 | Normal | 保护判据 20ms 节拍:prot_elm_run() + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检(B5);不依赖网络,纳入 IWDG required_mask |
protect_task |
idle(128 words)/timer(256 words)由 FreeRTOS 内核创建。所有任务栈从 FreeRTOSheap_4堆分配(已迁 CCM,2026-10-12 扩容至 56 KB,见docs/内存管理/RAM使用分析与优化方案.md§4.1.2/§2.3)。 栈实测与预算(2026-10-1014,工具1164B/3840B**(开机~FTP 传输期,2026-10-14)、静态链 1912B;ftpTask 实测 740B/4096B、静态 1216B;timeSyncTask 实测 616B/2560B、静态 1264B;uiTask 实测 4016B/16384B;adcTask 实测 952B/2560B、静态 1320B;iec104Task 静态 1456B(未上板);protectTask 静态 872B(未上板);testTask 静态 496B(仅测试构建)。 ⚠ 改栈或加大栈内局部变量前后必须重跑该脚本——静态分析低估含递归/间接调用的链(优化前 ftpTask:静态 2528B vs 实测 3480B)。scripts/stack_report.py= Keil 静态调用图 + 串口[STACK]水位;详见docs/内存管理/RAM使用分析与优化方案.md§4.0.6/§2.3): defaultTask 实测峰值 2152B/5120B(余 41%)、静态最坏链 3016B;netTask 实测峰 **1116
2.2 就绪屏障(Event Flags,取代魔法延时)
三栏式:左 = 生产者(set)· 中 = 事件组
sys_evtHandle· 右 = 消费者(wait)。 任务卡与事件位对齐同一行,箭头 =set/wait关系;bit1 → ftpTask的额外依赖用 All 汇合点表达(osFlagsWaitAll)。 源码tools/arch_diagram/gen_barrier_svg.py(改数据重跑即可);PNG 预览本地渲染、不入库。
设计原则:生产者与消费者互不知晓,只经事件组解耦;新增子系统只需定义新 bit + 各自 wait/set,不改已有任务。
(详见 docs/架构设计/项目启动顺序管理.md)
⚠ 就绪位必须 osFlagsNoClear(2026-09-21 实证,FTP 永不监听事故):osEventFlagsWait 默认消费所等位(CMSIS 包装 → xEventGroupWaitBits(xClearOnExit=pdTRUE),cmsis_os2.c:1243-1249),而就绪位表达的是持久状态——多人等待时必须人人可读。修前 netTask 等 STORAGE_READY 时清掉 bit1、uiTask/timeSyncTask 等 NET_READY 时清掉 bit0,导致 ftpTask 的 WaitAll(bit0|bit1) 两位永不同时为 1 → 任务永久阻塞、零日志零报错、21 端口从未监听(靠栈水位异常才定位到)。
| 位类型 | 例子 | 等待方式 |
|---|---|---|
| 状态位("环境已就绪") | SYS_EVT_NET_READY / SYS_EVT_STORAGE_READY |
osFlagsWaitAny|osFlagsNoClear、osFlagsWaitAll|osFlagsNoClear——任何消费者都不许消费它 |
| 命令位("请做一件事") | SYS_EVT_CFG_SAVE / SYS_EVT_NET_RECONFIG |
消费型等待(默认,不加 NoClear)——必须恰好被消费一次,否则会反复写盘/重建网络 |
本节图中 ftpTask ← bit0 + bit1(All 汇合点)依赖的正是"状态位常驻"这一前提。
2.3 线程间通信
| 机制 | 对象 | 用途 |
|---|---|---|
| Event Flags | sys_evtHandle |
启动就绪屏障(§2.2) |
| Message Queue | netMsgQueue(深度 8,net_msg_t) |
所有 CH395F 操作经队列串行化到 netTask 执行,保证 SPI 单线程访问 |
3. 启动时序
sequenceDiagram
autonumber
participant M as main
participant A as app_main_init
participant K as osKernelStart
participant D as defaultTask
participant N as netTask
participant F as ftpTask
participant U as uiTask
participant T as timeSyncTask
participant I as iec104Task
participant P as protectTask
participant C as adcTask
M->>M: HAL_Init + CubeMX 外设初始化<br/>(SPI1/SPI2/I2C1/UART4/USART1/ADC1/TIM3/DMA/GPIO/IWDG)
M->>A: app_main_init()
Note over A: 时间子系统优先,使后续日志带时间戳
A->>A: sd2506_init() → sys_clock_init()(墙钟基准)
A->>A: rs485_init() → gd5f2gq5ue_init()(BBT 扫描)→ tpafe5160_init()
A->>A: adc_input_init()(语义取数与有效性层的语义映射 + ADC1 DMA)→ phasor_init() → freq_init()<br/>→ prot_set_init() + prot_elm_init()(保护判据内核前封存)
A->>A: SD2506 诊断(ID/时间/温度/电池)→ HAL_TIM_Base_Start_IT(&htim3)(1kHz 节拍)
A-->>M: "App init OK"
M->>K: osKernelStart()
Note over K: MX_FREERTOS_Init 已创建 9 任务 + 事件组 + 队列<br/>(产品构建 testTask 创建即回收,实跑 8 任务)
K->>D: 调度
D->>D: f_mount(无卷则 f_mkfs FAT32)→ 卷健康探针/自愈 → net_cfg_load()(配置引擎属主)
D-->>N: set SYS_EVT_STORAGE_READY
D->>D: 进入 LED / syslog / 配置 poll 循环(含 stack_probe_poll 栈水位采样)
N->>N: wait STORAGE_READY(Any|NoClear,≤10s,超时回退工厂默认)
N->>N: 读 RAM 配置副本 → net_init(ip,mask,gw,mac)<br/>→ PHY 强制 100M 全双工 → 等链路 ≤3s → MAC 回写影子
N-->>F: set SYS_EVT_NET_READY
N->>N: 循环 net_poll() + net_process_messages()<br/>(消费 NET_RECONFIG → 按代次号重建监听)
F->>F: wait NET_READY|STORAGE_READY(All|NoClear)→ lftpd_start("/", 21)<br/>→ 串口输出:waiting for connection...
U->>U: wait NET_READY(Any|NoClear)→ ui_port_init → ui_menu_init → ui_net_sink_start(7003)
T->>T: wait NET_READY(Any|NoClear)→ TCP listen :8888
I->>I: wait NET_READY(Any|NoClear)→ IEC104 listen :2404
P->>P: 20ms 判据节拍 prot_elm_run()(保护判据,不依赖网络)
C->>C: wait TIM3 1kHz 唤醒(ISR adc_tick_irq_notify)→ adc_input_poll() 纯消费
Note over D,N,F,U,T,I: 就绪位一律 osFlagsNoClear(状态常驻、可被多任务重复读取);<br/>仅命令位 CFG_SAVE / NET_RECONFIG 用消费型等待
4. 存储栈
flowchart LR
FS["FatFs (Lib/FatFs)<br/>f_mount / f_open / f_mkfs"]
DIO["diskio.c<br/>BSP 适配层"]
FTL["nand_ftl.c<br/>dhara FTL(磨损均衡 + 逻辑块映射)"]
NAND["gd5f2gq5ue.c<br/>页读写 / ECC / BBT 坏块表"]
CHIP[("GD5F2GQ5UE<br/>NAND 2Gbit")]
subgraph USERS["使用方"]
NC["net_cfg<br/>/config.ini(INI [network] 段)"]
SLG["sys_log<br/>/log/run.log · selftest.log"]
LF["lftpd<br/>FTP 文件读写"]
ST["测试套件<br/>STORAGE/GD5F"]
end
NC --> FS
SLG --> FS
LF --> FS
ST --> FS
FS --> DIO --> FTL --> NAND --> CHIP
subgraph RTC["独立时间源"]
SD["SD2506 RTC (I2C1)"] --> SC["sys_clock<br/>墙钟 epoch"]
end
- 掉电持久化:
net_cfg配置(通用配置文件/config.ini,INI[network]段,cfg_file 引擎),保存后下次上电生效、或立即生效(网络重建)、外部变更 4s 沉降后自动重载;旧版 NCFG 二进制(/cfg/netcfg.bin)已不再读取——存在则忽略(不读、不删、不迁移),配置回落工厂默认(自动迁移已于 2026-10-10 删除)。 - 日志记录:
sys_log引擎写定长 64B 文本记录到/log/run.log(运行记录)与/log/selftest.log(自检记录),defaultTask 唯一写者,FTP 可直下(详见docs/子系统设计/运行与自检记录设计说明.md);实时调试流 = 控制台单写者日志(USART1,4KB 环形缓冲 + defaultTask 唯一发送,详见docs/子系统设计/控制台单写者日志设计说明.md)。 - NAND 坏块表(BBT)持久化在保留块池,不交给 dhara 管理。
5. 网络栈与 TCP 消费者
flowchart TB
CHIP[("CH395F<br/>MAC+PHY · SPI2 + DMA")] --> DRV["ch395f.c<br/>寄存器/命令 + 中断集中读取"]
DRV --> NS["net_socket.c<br/>BSD Socket API(单连接模式)"]
NS --> EVT["net_events.c<br/>边沿事件处理 + 100ms 周期对账"]
NS --> LIS["net_listener.c<br/>监听生命周期(请求登记 + 唯一执行)"]
NS --> PND["net_pending.c<br/>阻塞 recv/send 挂起请求表"]
DRV --> NSEL["net_select.c<br/>select/poll 多路复用"]
NS --> Q["netMsgQueue<br/>(串行化到 netTask)"]
Q --> NT["netTask<br/>net_poll() + net_process_messages()"]
NS --> C1["lftpd<br/>FTP :21"]
NS --> C2["time_sync<br/>对时 :8888"]
NS --> C3["ui_net_sink<br/>RemoDispBus :7003"]
NS --> C4["iec104_serv<br/>104 上行 :2404"]
C3 -.->|"回送 1bpp 帧 + 接收远端按键"| CLIENT["DTU-RemoteLCD 客户端"]
NC["net_cfg<br/>IP/掩码/网关/MAC"] --> NS
style NT fill:#efe,stroke:#484
单连接约束:CH395F 配置为 8 Socket(索引寻址)+ TCP Server 单连接(set_fun_para(0x08),bit3 SOCKET_CLOSE;bit0 保留必须为 0),Socket 0 复用监听与数据通道;FTP / 对时 / 远程显示 / IEC104 四个 TCP 服务串行占用连接,连接断开后自动回到 LISTEN。
FTP 启动门(2026-09-21 修复):
ftpTask必须等NET_READY且STORAGE_READY(两位均NoClear)才lftpd_start——此前就绪位被其它等待者消费,FTP 静默不监听(详见 §2.2 与docs/测试说明/FTP_Test_Guide.md§8.6)。 单客户端实测注意:同一时刻只服务 1 个会话,旧会话异常断开后经net_listener重建监听(实测恢复窗 p50 208 ms,n=30;口径见docs/网络/网络自愈架构设计.md)。测试脚本必须内置连接重试(test/host/ftp_up_dl_test.py、test/host/ftp_regression.py已内置),且不要用裸 TCP 工具探测 21 端口(探测会占掉唯一会话,见docs/测试说明/FTP_Test_Guide.md§8.7)。 阻塞模型(2026-10-14 SEND 二期落地):阻塞 recv/send 经net_pending挂起请求表续作,调用方铁律sent < len(末块写完即返回、不得登记挂起);架构与五路语义见docs/网络/Net层阻塞模型架构设计.md,监听重建/沉降隔离/周期对账见docs/网络/网络自愈架构设计.md。 2026-10-14 实测:FTP 回归连续 19 次 11/11;ftp_integrity_test20 轮 × 64 KB STOR/RETR SHA-256 20/20 一致,吞吐稳定 ≈80 KB/s。
6. UI 层架构(框架 + 页面插件)
flowchart TB
subgraph LOGIC["逻辑层"]
UM["ui_menu.c<br/>级联框状态机 + 按键分发"]
MT["ui_menu_tab.c<br/>菜单树生成物(ui_menu_tree.txt)"]
end
subgraph VIEW["显示层"]
MV["ui_menu_view.c<br/>框体/行/装饰自绘"]
DECO["ui_deco.c<br/>浅 V 装饰"]
end
subgraph FRAME["内容页框架"]
CP["ui_content_page.c<br/>屏生命周期 + 描述符驱动绘制/按键"]
end
subgraph PLUGIN["页面插件(每页一文件)"]
P1["ui_page_device_info"]
P2["ui_page_ac"]
P3["ui_page_dc"]
P4["ui_page_password"]
P5["ui_page_net_cfg"]
P6["ui_page_runlog<br/>运行记录(动态列表)"]
P7["ui_page_selftest<br/>自检记录(动态列表)"]
end
subgraph PORT["显示端口"]
UP["ui_port.c<br/>160x160 1bpp 缓冲"]
SINK["ui_net_sink.c<br/>远程推流"]
end
UM --> MT
UM --> MV
UM --> CP
UM --> STBY["ui_standby.c<br/>待机屏(空闲超时切入)"]
MV --> DECO
CP --> P1 & P2 & P3 & P4 & P5 & P6 & P7
MV --> UP
SINK --> UP
P5 -.->|"net_cfg RAM 副本"| CFG["net_cfg"]
P4 -.->|"ui_pwd_def.h 编译期密码"| PWD["查看 000 / 修改 123"]
P6 & P7 -.->|"/log/run.log · selftest.log"| SLG["App/log/sys_log<br/>定长 64B 记录引擎(defaultTask 落盘)"]
- 内容页为独立全屏屏,由框架一次性自绘(零 LVGL 子对象),ESC 返回菜单屏。
- 菜单受保护叶子(
ui_menu_tree.txt行尾#P):先弹密码页,授权后menu_password_ok先关页再续进入动作。 - 待机屏:空闲超时(默认 60s)自动切入,仅确认键回菜单;数据源同实时量页。
- 字体
ui_font_cn_12.c由tools/font_gen/生成(宋体 12px 1bpp 子集,224 字/319 字形,2026-10-29 实测;字形数 = 字符数 + ASCII 95 个)。
7. 目录组织
STM32F4-Base/
├── Src/ Inc/ CubeMX 生成:main.c / freertos.c / HAL 外设 / 中断 / 时基
│ ├── freertos.c 9 任务 + netMsgQueue + sys_evt 创建(CubeMX 接线 + 崩溃钩子;任务体在 App/task/)
│ └── main.c 入口 → app_main_init → osKernelStart
├── App/ 应用层(业务编排,非 CubeMX)
│ ├── app/ 启动编排与全局契约:app_main.c(时间子系统优先)、app_cfg.h、sys_events.h
│ ├── algo/ 采集与算法层 语义取数与有效性~保护判据:adc_input / phasor / freq / prot_set / prot_elm
│ ├── task/ default_task / net_task / ui_task / adc_task / iec104_serv / protect_task / rs485_task*(*未创建,B2 暂缓)
│ ├── log/ sys_log 记录引擎(定长 64B;MCU 后端落盘 /log/*.log)
│ ├── cfg/ 配置引擎:cfg_file(静态态单任务收敛于 defaultTask)、store(MCU=FreeRTOS 堆)
│ ├── fs/ 卷锁 vol_lock(syslog / cfg / lftpd 三条路径 try 语义)
│ ├── sync/ time_sync(TCP 对时服务)
│ ├── protocol/ 协议编解码(MODBUS 等)与帧解析
│ └── util/ sys_clock(墙钟)、task_watchdog(IWDG 判定状态机)
├── Drivers/BSP/ 纯手工驱动/服务(不受 CubeMX 保护)
│ ├── CH395F/ 以太网 MAC+PHY
│ ├── GD5F2GQ5UE/ NAND + dhara FTL
│ ├── TPAFE5160/ ADC
│ ├── SD2506/ RTC
│ ├── RS485/ 半双工串口
│ ├── NET/ net_socket / net_select / net_events / net_listener / net_pending / lftpd
│ ├── NETCFG/ 网口配置(INI [network] 段)
│ ├── UI/ LVGL UI(与模拟器共用)
│ └── dbg_log.h/dbg_cfg.h 分级日志
├── Lib/ 第三方:LVGL v9.2、FatFs、FreeRTOS、CMSIS
├── Middlewares/ ST 官方中间件
├── MDK-ARM/ Keil 工程(uvprojx + scatter + build/download 脚本)
├── simulator/ PC 模拟器(CMake + SDL2,共用 UI 源码 + 无头回归测试)
├── test/ 测试套件(test_config.h 互斥选择,产品构建空转)
│ ├── board/ 板端套件(.c/.h:CH395F / 存储 / RTC / ADC / RS485 / UI)
│ └── host/ 主机侧脚本(.py:烧录闭环、日志校验、UI 回归)
├── tools/ font_gen(字体生成)/ menu_gen(菜单表生成)
├── scripts/ 代码规范/乱码检查、RAM 报告(ram_report.py)、栈报告(stack_report.py)
└── docs/ 设计/测试/问题记录文档
8. 构建与双端
flowchart LR
SRC["共享源码<br/>Drivers/BSP/UI/*<br/>Drivers/BSP/NETCFG/net_cfg.c<br/>App/cfg/cfg_file.c"]
SRC --> MCU["MCU 端<br/>Keil MDK-ARM (ARMCC v5)<br/>@build → BUILD_OK"]
SRC --> SIM["PC 模拟器<br/>CMake + SDL2 (MSVC)<br/>lvgl_sim.exe --keys"]
MCU --> TEST["@test:串口采集 + 烧录 + 日志校验"]
SIM --> REG["UI 回归:<br/>check_menu_rows.py<br/>check_content_pages.py"]
| 目标 | 工具链 | 验证 |
|---|---|---|
| MCU 固件 | Keil MDK-ARM v5(@build = keil-build skill) |
@build 0 错 0 警 → @test 串口闭环 |
| PC 模拟器 | CMake + SDL2 + MSVC(simulator/build/) |
check_menu_rows.py + check_content_pages.py(文本断言 + 像素黄金参考) |
功能编译开关:当前不存在——App/app/app_cfg.h 自 2026-10-22 起不含任何宏(原 APP_NET/ADC/RS485_ENABLE 三个零消费点占位宏已删,接回方法记在该文件 v1.2),各模块一律编译进产品;裁剪测试套件的唯一开关是 test/board/test_config.h 互斥选择(产品构建全注释)。
9. 相关专项文档
| 主题 | 文档 |
|---|---|
| 启动顺序 / 就绪屏障 | docs/架构设计/项目启动顺序管理.md |
| 存储架构(NAND/FTL/FatFs) | docs/架构设计/STM32F4-Base存储架构说明.md |
| NET 阻塞模型(挂起请求表五路语义) | docs/网络/Net层阻塞模型架构设计.md |
| 网络自愈(监听重建 / 沉降隔离 / 周期对账) | docs/网络/网络自愈架构设计.md |
| 采集与算法层 语义取数与有效性~保护判据(设计与判据) | docs/算法/采集与算法层设计说明.md |
| 采集与算法层目录索引(文件清单 / 构建测试) | App/algo/README.md |
| ADC 采样节拍(TIM3 1kHz 同源驱动) | docs/子系统设计/ADC采样节拍设计说明.md |
| IEC 60870-5-104 上行(:2404) | docs/子系统设计/IEC104上行设计说明.md |
| 墙钟时间维护 | docs/子系统设计/系统墙钟时间维护说明.md |
| 控制台单写者日志(USART1 调试流) | docs/子系统设计/控制台单写者日志设计说明.md |
| RAM 预算与优化 | docs/内存管理/RAM使用分析与优化方案.md |
| 栈/内存观测工具 | scripts/stack_report.py(Keil 静态调用图 × 串口 [STACK] 水位,判定取两者更坏者)· scripts/ram_report.py(.map 静态占用)—— 用法与基线见内存方案 §4.0.6 |
| FTP 功能与陷阱 | docs/测试说明/FTP_Test_Guide.md §8.6(启动门)/ §8.7(单客户端)· 脚本 test/host/ftp_up_dl_test.py、test/host/ftp_regression.py、test/host/ftp_integrity_test.py |
| UI 设计架构 / 内容页 / 菜单 | docs/UI设计/UI_设计架构.md · 内容页架构.md · 菜单页面设计.md |
| UI 自动化测试 | docs/UI设计/UI_自动化测试流程.md |
| 驱动已知陷阱 | docs/问题记录/{CH395F,GD5F2GQ5UE,SD2506}_Trap_Records.md |
| 各模块测试指南 | docs/测试说明/ |
| 代码规范 | docs/代码规范/ |