2525c2d948
用户裁定两条:① 剩余裸写端口的地方加「工厂默认」限定词,不改固件行为; ② 生效语义按需求口径「保存即生效」定,现实现(端口下次重 bind 才生效)是 要改的一侧 → 建任务登记,本笔不动代码行为。 限定词(24 处 / 17 文件):AGENTS 数据主线、App/protocol/README、根 README 文档索引、Src/freertos.c 三处任务注释、docs/README、项目启动顺序管理、存储 架构说明、软件架构与运行时规格(2)、RAM 使用分析、产品需求设计文档(5)、 项目架构总览(3)、test/host/README、iec104_master 流程注释、IEC104 上行设 计说明、继电保护功能复现规划。配图两张同批重跑:gen_arch_svg.py 与 gen_memory_svg.py(脚本与产物同一笔)。 两处陈旧计数(都做过双源核证): - 测试开发标准流程 像素层「60 内容页」→ 内容页黄金参考 66 张(64 页); 双源 = simulator/tests/ref/shot_*.bin 实数 66 与 check_content_pages.py 的 PAGES 66 条(含两个 _p2)。 - .pre-commit-config.yaml 的 uinav-selftest 注释「18 张黄金参考」→ 写成 「接线当时 18 张、现 66 张」:git log -S 定到 6a904c1b(2026-10-25),那 天 ref 目录确实 18 张,写时为真,但它描述的是钩子现有行为,属当前态。 口径修正与登记: - 页面扩充详细方案「通讯设置 = 无配置模型(端口与 CA 都是 #define)」→ 标 「当时」,并写明现已在 /config.ini 的 [comm] 段可配、UI 通讯设置页可改。 - 产品需求 §通讯配置:删掉「改后需重启生效」这句把两件事混为一谈的话,改成 需求要求「保存即生效」+ 现实现(公共地址重启、端口下次重 bind)+ 指向登记簿。 - docs/任务规划/登记簿.md §1 新增「104 端口改后立刻重 bind」:含隐性耦合 —— .serialtest/config.json 的必检锚点写死 listening on 2404,端口一改串口 回归必假失败;改行为时要么锚点只查 listening on,要么由脚本注入端口。 复扫(旧形态归零判据)剩 20 处未带限定词,逐条定性为刻意不改:默认值定义处、 出厂模板与断言、usage 与 argparse default、UI 黄金参考显示值、台架主站地址 (同数不同义)、串口锚点、叙事句、引用关系图坐标。逐条理由见一次性台账。 门禁:check_doc_links --sections-strict 对 16 篇改动 .md 逐个 rc=0; check_mermaid --all 31 文件 62 围栏新发 0;check_nested_comment --all 449 文件新发 0;check_bom_drift --all 99 个名单内文件无漂移; check_generated_fresh PASS(25 生成器 / 21 一致 / 4 只减不增基线);字节体检 无新增 BOM / CR / U+FFFD / 行尾空格(三连空行与 HEAD 逐项相同 = 存量)。 /keil-build 0 错 0 警,Program Size Code=246720 RO=29836 RW=2052 ZI=149708 与凭证同码:本笔只碰注释与配图文字,无固件行为变更。
163 lines
12 KiB
Markdown
163 lines
12 KiB
Markdown
# STM32F4-Base 软件架构与运行时规格
|
||
|
||
> **文档定位**:全仓**系统运行时架构(软件侧)**与**工具链 / 工程约束**的权威表档。
|
||
> 来源链:`docs/产品需求/产品需求设计文档.md` v1.9 原 §3/§4 → 《系统架构与硬件规格》
|
||
> →(2026-10-30 按"软件 / 硬件不混排"拆分)本档 = 软件侧;硬件侧见
|
||
> [`硬件规格与能力边界.md`](硬件规格与能力边界.md)。需求与验收口径仍在需求档。
|
||
> 与 [`docs/架构设计/项目架构总览.md`](项目架构总览.md) 的分工:总览是**叙述性全景**,
|
||
> 本档是**完整口径表**——就绪屏障位表(bit0~8)、线程间通信机制表、启动时序图以
|
||
> **本档为准**,两处冲突以本档为准并回改总览。
|
||
> **版本**:v1.0 **日期**:2026-10-30
|
||
|
||
## 1. 六层分层架构
|
||
|
||

|
||
|
||
> 上图由 `tools/arch_diagram/gen_arch_svg.py` 生成(手绘 SVG,零连线交叉);改架构后编辑脚本顶部
|
||
> `ROWS` 数据重跑即可,源数据与下表一致。
|
||
|
||
分层原则:**上层只依赖相邻下层(单向)**;**① 业务层为顶层**(仅 `net_cfg`,被 UI 与基础服务共用),
|
||
**② UI 层与 ③ 基础服务层同级并列**。
|
||
|
||
| 层 | 目录 | 职责 | 关键模块 |
|
||
|---|---|---|---|
|
||
| ① 业务层 | `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/*`(语义取数与有效性~保护判据)`protocol/iec104/*` `log/soe_store` `lftpd` `time_sync` |
|
||
| ④ 中间件层 | `Drivers/BSP/NET/`、`Lib/`、`App/util/` | 通用库 / 协议栈 | `net_socket` `net_select` `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 / DMA / GPIO / TIM7 |
|
||
|
||
> 表中硬件行只到"层"口径;**器件与引脚级规格见 [`硬件规格与能力边界.md`](硬件规格与能力边界.md)**。
|
||
|
||
## 2. 运行时任务模型
|
||
|
||
**九个** FreeRTOS 任务(`Src/freertos.c` 创建,任务体在 `App/task/`、`App/sync/`):
|
||
|
||
| 任务 | 栈(words / B) | 优先级 | 职责 |
|
||
|---|---|---|---|
|
||
| `defaultTask` | 1280 / 5120 | Normal | FatFS 挂载(无卷 `f_mkfs`)→ 卷健康探针/自愈 → `net_cfg_load` + 定值/运行参数/通讯参数装载(**配置引擎属主**)→ 置 `STORAGE_READY` → 日志落盘(唯一写者)+ 2 s 配置轮询 + 各模块脏位落盘(`prot_set` / `run_par` / `comm`)+ SOE 排空 + LED 闪烁 + 栈水位观测 + **看门狗唯一喂狗者** |
|
||
| `netTask` | 960 / 3840 | Normal | 等存储 → `net_stack_bring_up()` → 置 `NET_READY` → `net_poll` 轮询 + CH395F 操作串行化(消息队列执行者) |
|
||
| `ftpTask` | 1024 / 4096 | Normal | 等网络**且**存储(`WaitAll`)→ `lftpd_start("/", 21)` → 单客户端会话循环 |
|
||
| `testTask` | 2048 / 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**(未选套件时立即 `vTaskDelete` 回收其 8 KB CCM 栈) |
|
||
| `uiTask` | 4096 / 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60 s 切待机屏)/`ui_net_sink` → 10 ms 刷新 + 数据注入 |
|
||
| `timeSyncTask` | 640 / 2560 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC |
|
||
| `adcTask` | 640 / 2560 | AboveNormal | **采集主线唯一消费者**:每拍 `adc_input_poll()`(TPAFE 快照 + ADC1 DMA 帧)→ `adc_health` 有效性 → `phasor_run()` 相量 → `freq_run()` 测频 → 每 200 ms `meas_tick()` 发布遥测快照 |
|
||
| `iec104Task` | 640 / 2560 | Normal | 等网络 → TCP 工厂默认 :2404 监听 → 104 链路状态机(总召/变位/周期/遥控/定值/对时)+ 断链重听 + 网络代次自愈 |
|
||
| `protectTask` | 512 / 2048 | Normal | **20 ms 判据节拍**:采样故障闭锁判定 → 定值校验和巡检 → `prot_elm_run()` 元件判据 → 动作边沿投递 SOE(**不做 NAND I/O**) |
|
||
|
||
> `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 **56 KB** `heap_4` 堆
|
||
> (CCM)分配;CCM 水位 87.9%(<92% 门禁)。
|
||
> 任务表与 `Src/freertos.c` 的 `osThreadAttr_t` 一致,由 `scripts/gen_task_table.py` 生成并门禁校验;
|
||
> **栈配置与实测余量**见 `../产品需求/产品需求设计文档.md` §7.3,改栈前后必须重跑 `scripts/stack_report.py`。
|
||
>
|
||
> ⚠ **节拍同源**:TIM3 更新事件(1 kHz)同时触发 TPAFE `CONVST` 与(经 TRGO)ADC1 一轮 9 通道扫描,
|
||
> 并在 ISR 内唤醒 `adcTask`——采集节拍与任务唤醒**不允许**各自计时。
|
||
> ⚠ **ADC1 必须在挂 DMA 的瞬间处于关闭态**:`app_main_init()` 步骤 4b-pre 的 `adc1_disable()` 不可删、不可后移,
|
||
> 否则 DMA 乒乓缓冲整体旋转 2 格(表象与"映射表写错"一致)。证据见
|
||
> [`docs/子系统设计/ADC采样节拍设计说明.md`](../子系统设计/ADC采样节拍设计说明.md) §4.5。
|
||
|
||
## 3. 就绪屏障(Event Flags)
|
||
|
||

|
||
|
||
用 CMSIS-RTOS2 Event Flags(`sys_evtHandle`)表达"子系统就绪",取代魔法延时与全局标志轮询。
|
||
生产者与消费者互不知晓,只经事件组解耦;新增子系统只需定义新 bit + 各自 wait/set。
|
||
|
||
| 位 | 名称 | 类型 | 语义 |
|
||
|---|---|---|---|
|
||
| bit0 | `SYS_EVT_NET_READY` | 状态位 | netTask:`net_init` + PHY 稳定完成 |
|
||
| bit1 | `SYS_EVT_STORAGE_READY` | 状态位 | defaultTask:FTL/FatFS 挂载 + 配置就绪 |
|
||
| bit2 | `SYS_EVT_FTP_READY` | 状态位 | ftpTask:`lftpd_start` 完成 |
|
||
| bit3 | `SYS_EVT_TIMESYNC_READY` | 状态位 | timeSyncTask:首次时间同步完成 |
|
||
| bit4 | `SYS_EVT_NET_RECONFIG` | 命令位 | UI:请求立即应用新网口配置 |
|
||
| bit5 | `SYS_EVT_CFG_SAVE` | 命令位 | UI:网口配置保存请求(defaultTask 消费并写盘) |
|
||
| bit6 | `SYS_EVT_CONSOLE_TX` | 命令位 | 控制台单写者:生产者入环形缓冲后置位 |
|
||
| bit7 | `SYS_EVT_PROT_SET_SAVE` | 命令位 | 保护定值落盘请求:104 检测到定值脏后置位,defaultTask 原子写 `/prot_set.bin` |
|
||
| bit8 | `SYS_EVT_RUN_PAR_SAVE` | 命令位 | 运行参数段落盘请求:104 写运行参数后置位,defaultTask 整档外科保存 |
|
||
|
||
> ⚠️ **铁律**:状态位(bit0~3)表达**持久状态**,任何消费者都**不许消费**——必须
|
||
> `osFlagsWaitAny|osFlagsNoClear`。命令位(bit4~8)表达"请做一件事",必须**恰好被消费一次**(消费型等待)。
|
||
> 历史事故:就绪位被先到的等待者消费,导致 `ftpTask` 的 `WaitAll(bit0|bit1)` 永不成立、21 端口从未监听(静默)。
|
||
|
||
## 4. 启动时序
|
||
|
||
```mermaid
|
||
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 S as adcTask / protectTask
|
||
participant F as ftpTask
|
||
participant U as uiTask
|
||
participant I as iec104Task / timeSyncTask
|
||
|
||
M->>M: HAL_Init + CubeMX 外设初始化<br/>(SPI1/SPI2/I2C1/UART4/USART1/DMA/GPIO/TIM3/TIM7)
|
||
M->>A: app_main_init()
|
||
Note over A: 时间子系统优先,使后续日志带时间戳
|
||
A->>A: sd2506_init() → sys_clock_init()(墙钟基准)
|
||
A->>A: rs485_init() → gd5f2gq5ue_init()(BBT 扫描)
|
||
A->>A: tpafe5160_init() → adc_input_init()(语义映射自检)
|
||
A->>A: 配置段注册(adc_cal / run_par / prot_set / comm)→ 定值/元件初始化
|
||
A->>A: adc1_disable() → ADC1 挂 DMA(4b-pre,顺序不可变)→ 启动 TIM3 1 kHz 节拍
|
||
A-->>M: "App init OK"
|
||
M->>K: osKernelStart()
|
||
K->>D: 调度
|
||
D->>D: f_mount(无卷则 f_mkfs FAT32)→ 卷探针/自愈 → 配置装载<br/>→ 定值整表原子装载(损坏则保出厂默认 + 自检留痕)
|
||
D-->>N: set SYS_EVT_STORAGE_READY
|
||
S->>S: adcTask:等 TIM3 节拍 → 采集 → 相量/测频 → 200 ms 遥测快照
|
||
S->>S: protectTask:20 ms 判据(未标定/采样故障则停走)
|
||
N->>N: wait STORAGE_READY(Any|NoClear)→ net_init(ip,mask,gw,mac)<br/>→ PHY 强制 100M 全双工 → 等链路 ≤3s
|
||
N-->>F: set SYS_EVT_NET_READY
|
||
F->>F: wait NET_READY|STORAGE_READY(All|NoClear)→ lftpd_start("/", 21)
|
||
U->>U: wait NET_READY(Any|NoClear)→ ui_port_init → ui_menu_init → ui_net_sink_start(7003)
|
||
I->>I: wait NET_READY(Any|NoClear)→ TCP listen :2404(默认)/ :8888
|
||
```
|
||
|
||
> **顺序铁律**:① 配置段注册必须**先于** `cfg_file_load`(否则 FTP 上传的段不生效);
|
||
> ② `adc1_disable()` 必须在挂 DMA 之前;③ `osKernelStart` 之前禁调任何内核 API(含 `osDelay` / 队列)。
|
||
|
||
## 5. 线程间通信
|
||
|
||
| 机制 | 对象 | 用途 |
|
||
|---|---|---|
|
||
| Event Flags | `sys_evtHandle` | 启动就绪屏障 + 命令位(§3) |
|
||
| Message Queue | `netMsgQueue`(深度 8,`net_msg_t`) | 所有 CH395F 操作经队列**串行化**到 netTask 执行,保证 SPI 单线程访问 |
|
||
| 双区 + 序号(无锁) | `phasor` 相量区、`meas` 遥测快照 | 写侧填满计算区后单字节翻转活跃索引;读者先取索引再整份拷贝,`seq` 判"是否换帧" |
|
||
| SPSC 环(无锁) | `soe_store`(深度 8) | protectTask 只投递动作边沿,defaultTask 排空落盘——**保护路径零 NAND I/O** |
|
||
| 脏位 + 事件位 | `prot_set` / `run_par` / `comm` | 写入方置脏;104 侧 peek 后投递事件位,配置属主(defaultTask)落盘,失败保留脏位下轮重试 |
|
||
| 单字请求标志 | `prot_elm_request_reset()` | 跨上下文复归请求(104 遥控 YK11 在 iec104Task 落请求,protectTask 下一拍消费) |
|
||
|
||
## 6. 工具链与工程约束
|
||
|
||
> 本节收**构建与运行环境事实**。SDIO 无能力属硬件事实,见
|
||
> [`硬件规格与能力边界.md`](硬件规格与能力边界.md) §5 的 H5。
|
||
|
||
### 6.1 运行时与工具链
|
||
|
||
| 项 | 值 | 说明 |
|
||
|---|---|---|
|
||
| RTOS | FreeRTOS V10.3.1(CMSIS-RTOS2) | HAL 时基 TIM7,tick = 1 ms;NVIC 4 位抢占,外设中断优先级 ≥5 |
|
||
| 工具链 | Keil MDK-ARM v5(ARMCC v5) | 工程锁 `uAC6=0`,**不支持 armclang** |
|
||
| CubeMX | 6.17.0 + STM32Cube FW_F4 V1.28.3 | 与 `.ioc` 记录一致,再生成须用此组合 |
|
||
|
||
### 6.2 工程约束
|
||
|
||
| 约束 | 内容 |
|
||
|---|---|
|
||
| **CubeMX 生成文件** | `Inc/`、`Src/` 中带 `USER CODE BEGIN/END` 标记;自定义代码**只能**写标记区内 |
|
||
| **生成区外的定制** | 仅限 `Src/iwdg.c` 延后启动、4 个 Fault Handler 实体(`scripts/check_cubemx_survival.py` 审计兜底) |
|
||
| **`osKernelStart` 前** | 禁止任何 FreeRTOS / CMSIS-RTOS API 调用 |
|
||
| **BOM / 编码** | 部分源文件带 BOM(`Drivers/BSP/UI/*`、`App/task/ui_task.c`、`App/cfg/cfg_file.c`、`Lib/lv_conf.h`),被工具改写丢 BOM 会导致 ARMCC 按 CP936 解码、中文字面量编译失败 |
|
||
| **新源文件** | 必须双端注册(Keil `.uvprojx` + `simulator/CMakeLists.txt`),否则不参与编译 |
|
||
|
||
---
|
||
|
||
> **编号换算(原《系统架构与硬件规格》合档 → 本档)**:旧档号记裸号、不带 § 以免与本档编号混淆——
|
||
> 1.1→§1、1.2→§2、1.3→§3、1.4→§4、1.5→§5;旧 2.1 的 RTOS / 工具链 / CubeMX 三行与旧 2.5 五条工程约束
|
||
> (除 SDIO 行)并入 §6。全仓旧号引用已按本表重定向;换算只留此注,正文不再出现旧号。
|