Files
STM32F4-Base/docs/架构设计/项目架构总览.md
T
wandering1 4c74cfa825 refactor(编号·批次二): 文档侧 194 处保护功能号改成功能名,定义表 7 张首格改语义标识
批次二文档半(道 gate-docs 执行)。先纠三个数的口径账,别再拿任何旧数当准:**234 处是准数** —— 卡片写
52(Lead 用了门禁 Checker 的引用集合,它**刻意排除属主文档内部的定义位**)、道里先报 265(裸正则把
`SDL2-2.32.10` 里的 `L2-2` 当成功能号,误报 10 处)。最终边界口径 = 带左边界 + 跳过反引号代码段。

· 19 只文件 HEAD 共 234 处 → 现 40 处,**40 处 100% 落在变更记录行**(冻结历史,按 Q3 一条没动),
  非变更记录残留 **0**;改写 163 行 / 16 只文档。站点最多:规划文档 110→37、相量层单测说明 25→1、
  采集与数据处理短板评测 21→0、App/algo/README 26→0。
  (`App/algo/README.md` 由上一笔代码提交带上车,路径归属已在该笔正文注明,内容是本批改写)
· **定义表首列由编号改成语义标识**(编号规范 §3.3「表首格就是短语」的完全形态):首列原为编号的表
  共 **7 张**(不是 5 张),删掉与之重复的「语义名」列;表头标签按域词区分——三张 L3 保护**元件**表
  保留 `| 元件 |`,四张 L1/L2 表用 `| 功能名 |`(把「元件」统一洗成功能名会丢掉领域信息)。
  七张表逐张核:表头竖线数 == 分隔行 == 首条数据行 == 列数+1;与 HEAD 逐块对拍**净新增不一致块 0**,
  并且正是这个对拍抓到第一版「三张表数据行已删列、表头没删」的破表。
· 7 处删号留下的残词/重复解释括号人工收口(如「L1-5(通道标定)」→「通道标定(一次系数 + 零漂)」),
  复扫待收口站点 0;措辞逐字取自映射表,无第二种说法。
· tools/arch_diagram 三只生成器 4 处(3 处 docstring + 1 处图形文案)与产物同批:重跑后**只有位图那张
  变 2 个 text 节点的换行位置**,画布 1580x740 与总行数不变,另两张逐字节未变;改前先干跑对照证明三张
  svg 原本与生成器一致(不在 stale 基线里)——`check_generated_fresh --only docs-svg` PASS。

本笔另含 **F-* 半批的文档面**(同一轮 `git add docs tools/arch_diagram AGENTS.md` 一并收进
,非误伤):BSD
函数调用关系里 9 处反引号紧邻指针、属主文档 §7 的停发句与导读行(**12 条定义行原样保留、不删登记族**)、
以及 `gen_net_instability_svg.py` 与它的产物 `网络不稳根因总览.svg`(「唯一读者红线」那条的框宽自适应
526 → 551,右缘 1411 未越界,画布 1560x1468 不变)——该半批的**代码注释面**在下一笔。

⚠ 因 `docs/任务规划/登记簿.md` 与 `docs/任务规划/UI补齐.md` 正被另一张卡(UI 叶数复核)改动,本笔**不含
那两只文件**,批次二的 §9 存量收尾与它们同批提交。
2026-10-27 09:05:24 +08:00

32 KiB
Raw Blame History

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. 全景分层架构

STM32F4-Base 项目分层架构

上图由 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/** 不得 include App/** 的头,存量 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/*(L1~L3) 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/*(L1→L2→L3) → adc_task / protect_task 采集与算法层为纯计算:adcTask 消费 L1/L2(TIM3 1kHz 节拍)、protectTask 消费 L3(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 双缓冲)→ L1/L2 消费,不取数不做保护运算 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 内核创建。所有任务栈从 FreeRTOS heap_4 堆分配(已迁 CCM,2026-10-12 扩容至 56 KB,见 docs/内存管理/RAM使用分析与优化方案.md §4.1.2/§2.3)。 栈实测与预算(2026-10-1014,工具 scripts/stack_report.py = Keil 静态调用图 + 串口 [STACK] 水位;详见 docs/内存管理/RAM使用分析与优化方案.md §4.0.6/§2.3): defaultTask 实测峰值 2152B/5120B(余 41%)、静态最坏链 3016B;netTask 实测峰 **11161164B/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)。

2.2 就绪屏障(Event Flags,取代魔法延时)

就绪屏障(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()(L1 语义映射 + ADC1 DMA)→ phasor_init() → freq_init()<br/>→ prot_set_init() + prot_elm_init()(L3 内核前封存)
    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_test 20 轮 × 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 子集,122 字/217 字形)。

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/               采集与算法层 L1~L3: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
采集与算法层 L1~L3(设计与判据) 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/代码规范/

10. 变更记录

日期 变更
2026-09-10 初版:六层全景架构 + 任务/就绪屏障 + 启动时序 + 存储栈 + 网络栈 + UI 分层 + 目录组织
2026-09-10 §1 全景分层架构重绘:由"36 节点 + 跨层虚线"改为分层色带(每层一框)+ 职责表 + 跨层依赖表,消除连线交叉、提升可读性
2026-09-10 §1 再改为手绘 SVG(tools/arch_diagram/gen_arch_svg.py → 项目架构总览.svg:分层色带 + 模块芯片 + 单向下箭头,布局确定);PNG 预览本地渲染、不入库
2026-09-10 net_cfg 分层定位修正:按项目规划归入 ① 应用层(原列在 ③ 服务/中间件);SVG 与 §1.1 表同步(模块物理路径仍在 Drivers/BSP/NETCFG/)
2026-09-10 §1 布局修正:应用层与 UI 层同级并列(同一行并排,均向下依赖);生成器数据由"每层一行"改为 ROWS(行内可放多个并列层),顶层两框各引一条向下箭头
2026-09-10 §1 层级重定(按项目规划):① 业务层(改名,原"应用层")为顶层,含 net_cfg(被 UI 与基础服务共用)+ 业务编排;② UI 层与 ③ 基础服务层同级并列其下;time_sync 归基础服务层
2026-09-10 §1 定稿(严格版):① 业务层顶层仅 net_cfg 一块;app_main/sys_clock/sys_events/app_cfg/task/*/util/* 并入 ③ 基础服务层(任务编排 + 网络/存储/图形);② UI 层与 ③ 同级并列
2026-09-10 §1 新增 ④ 中间件层(通用库 / 协议栈:net_socket·net_select·lftpd·FatFs·nand_ftl·LVGL·util),位于 ③ 基础服务层与驱动层之间;③ 基础服务层收敛为应用侧服务与编排(app_main·sys_clock·sys_events·app_cfg·task/*·time_sync);层号顺延至 ⑦ 硬件
2026-09-10 lftpd 由 ④ 中间件层移入 ③ 基础服务层(FTP 视为应用侧服务);中间件层余 net_socket·net_select·FatFs·nand_ftl·LVGL·util
2026-09-10 §2.2 就绪屏障图由 Mermaid 改为手绘 SVG(tools/arch_diagram/gen_barrier_svg.py → 就绪屏障.svg):三栏(生产者 / 事件组 / 消费者)+ 水平对齐 + All 汇合点,消除原图 9 条边的交叉;PNG 预览本地渲染、不入库
2026-09-13 sys_clock 由 ③基础服务层改归 ④中间件层(通用平台时间服务,日志/文件/对时共用;文件移入 App/util/ 与 crc/ringbuf 同目录,include 路径不变):FatFs → sys_clock 成为层内引用,工程反向依赖消除;同日新增的 §1.2 "反向依赖例外" 条目撤销
2026-09-14 通用配置文件(P3):网口配置存储由 /cfg/netcfg.bin(NCFG 二进制)迁移至 /config.ini(INI [network] 段,App/cfg/cfg_file 引擎 + cfg_file_store 双端后端);net_cfg 对外 API 不变;旧档首启自动迁移;cfg_file 列为 MCU/模拟器共享源码
2026-09-14 运行/自检记录:新增 App/log/sys_log(定长 64B 文本记录引擎,MCU 后端 /log/run.log·selftest.log,defaultTask 唯一写者)+ UI 页面插件 ui_page_runlog/ui_page_selftest(动态列表)+ ui_standby 待机屏(空闲超时切入);密码页错误提示;字体 111→122 字(217 字形);详见 docs/子系统设计/运行与自检记录设计说明.md
2026-09-21 §2.2 屏障语义修正 + FTP 永不监听缺陷修复:osEventFlagsWait 默认消费所等位,就绪位(NET_READY/STORAGE_READY)被 netTask/uiTask/timeSync 先消费后,ftpTask 的 WaitAll(两位) 永不成立 → 任务永久阻塞、21 端口从未监听(静默);5 处就绪位等待统一加 osFlagsNoClear(命令位 CFG_SAVE/NET_RECONFIG 保持消费型)。同轮新增栈可观测性:Src/freertos.c 的 stack_probe_poll(串口 [STACK] 水位)+ scripts/stack_report.py(Keil 静态调用图 × 运行时水位,判定取两者更坏者);§2.1 任务表补齐实测栈数据并修正过期的 defaultTask/testTask 栈配置,§3 时序图补 NoClear 与 FTP 启动日志,§5 补 FTP 启动门/单客户端实测注意
2026-09-21 栈宽整改落地(内存方案 §1.4:lftpd 大局部缓冲静态化):Drivers/BSP/NET/lftpd/lftpd.c + lftpd_log.c 共 9 处大局部数组(message[256]/response[512]/name[256]×2/buffer[1024]×2/read_buffer[512]/arg_buf[512]/client 会话上下文/日志 buffer[256])改为函数级 static(lftpd 单客户端、全部在 ftpTask 内串行);ftpTask 实测峰值 3480 → 1184 B(余量 15% → 71%,判定 PASS),静态调用图 2528 → 472 B,代价静态 BSS +4,616 B(RW_IRAM1 82,048 / 131,072 B,余量仍 ~48 KB);§2.1 栈实测数据同步,原"涨栈到 1600 words"方案取消;FTP 功能回归 10/10 + 128KB 逐字节一致
2026-09-24 → 09-25 控制台单写者日志(09-24 实证驱动:多任务并发日志字符级交错 + HardFault 脏指针直达 UART):4KB 环形缓冲 + defaultTask 唯一发送(行级原子 + 指针可控)+ 内核前/ISR/异常钩子有界直发回退 + fputc 收敛到单写者路径;09-24 初版实施于 Src/usart.c USER CODE 区,09-25 迁出至 App/log/console_log.{h,c}(HAL 生成文件不含应用代码),dbg_log.h 改 #include "console_log.h"(调用点零改动),Src/usart.c 恢复纯 CubeMX 形态;@build 0/0 + @test PASS;详见 docs/子系统设计/控制台单写者日志设计说明.md
2026-09-25 defaultTask 任务体文件拆分:Src/freertos.c 的 StartDefaultTask 任务体(存储 bring-up / 配置引擎属主 / 控制台单写者 attach+drain / syslog 落盘 / LED / 栈水位探针 stack_probe_poll)整体迁至 App/task/default_task.c(default_task_func(),与其余任务文件同址);freertos.c 恢复"CubeMX 接线 + 崩溃钩子"形态(719→361 行),@build 0/0 复验;栈配置属性仍定义于 freertos.c(stack_report 数据源不变)
2026-10-10 删除旧档 NCFG 迁移(栈整改):Drivers/BSP/NETCFG/net_cfg_legacy.{c,h} 整体移除、cfg_store_legacy_path() 撤销,net_cfg_load 不再调用 net_cfg_legacy_migrate();旧档 /cfg/netcfg.bin 若存在则被忽略(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 +600 B,本仓库无携带旧档的设备。同轮栈整改(未抬栈):defaultTask 静态最坏链 5312 → 2776 B / 实测峰值 3392 → 2152 B、timeSyncTask 1736 → 1024 B,6 任务全 PASS;代价静态 BSS +4,128 B
2026-10-14 任务面与 NET 内部模块同步现状:§1.1 补 ④ 中间件层 net_events/net_pending/net_listener 与 ③ 层 algo/*(L1L3)、⑦ 硬件行去重(原两行重复编号且 UART5/TIM7 过期 → UART4/TIM3/ADC1);§1.2 增 NET 三内部模块与 algo→adcTask/protectTask 两条跨层依赖;§2.1 任务表 6 → 9 行(补 adcTask/iec104Task/protectTask,栈值按 freertos.c 校准:defaultTask 5120、netTask 3840、 timeSyncTask 2560、adcTask 2560、iec104Task 2560、protectTask 2048 B),栈实测更新至 2026-10-1014 并注明堆已迁 CCM 扩容 56KB;§3 时序图补 adc_input/phasor/freq/prot_set/prot_elm 内核前初始化与 TIM3 节拍启动、新增 iec104Task/protectTask/adcTask 参与者;§5 网络栈图补三个 NET 内部模块与 iec104_serv 消费者,单客户端恢复窗更新为实测 p50 208 ms,登记 SEND pending 二期与 2026-10-14 FTP 实测;§7 目录树补 algo//fs/、iec104_serv/protect_task、NET 三新文件;§9 补 Net 阻塞模型/网络自愈/algo/ADC 节拍/IEC104 五条专项文档索引