通用配置文件 P1–P3:设计文档、FS 并发 A+ 定案、FatFs 墙钟接入、cfg_file 双端引擎与 net_cfg 集成(旧档 NCFG 首启迁移)

This commit is contained in:
2026-09-14 02:15:49 +08:00
parent cf2597f998
commit 97661415ca
29 changed files with 3076 additions and 359 deletions
+68
View File
@@ -0,0 +1,68 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="952" viewBox="0 0 1200 952" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
<rect x="0" y="0" width="1200" height="952" fill="#ffffff"/>
<text x="26" y="40" font-size="20" font-weight="700" fill="#0f172a">通用配置文件分层架构(/config.ini)</text>
<text x="26" y="62" font-size="13" fill="#64748b">INI 文本文件 · FTP 上传/下载 · 段键表引擎 · MCU/模拟器双端共享</text>
<rect x="26" y="78" width="1148" height="92" rx="12" fill="#f1f5f9" stroke="#475569" stroke-opacity="0.45"/>
<text x="44" y="104" font-size="17" font-weight="700" fill="#475569">PC 端(外部)</text>
<text x="171" y="104" font-size="12" fill="#64748b">文本编辑器 + FTP 客户端</text>
<rect x="44" y="116" width="362" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="225" y="139" font-size="13" fill="#1e293b" text-anchor="middle">文本编辑器:改 /config.ini(注释 / 加段 / 改值)</text>
<rect x="416" y="116" width="338" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="586" y="139" font-size="13" fill="#1e293b" text-anchor="middle">FTP 客户端:RETR 下载 / STOR 上传(端口 21)</text>
<rect x="26" y="214" width="1148" height="92" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.45"/>
<text x="44" y="240" font-size="17" font-weight="700" fill="#7c3aed">lftpd FTP 服务(端口 21)</text>
<text x="286" y="240" font-size="12" fill="#64748b">ftpTask · 单连接 · root = /(= 卷根)</text>
<rect x="44" y="252" width="388" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
<text x="238" y="275" font-size="13" fill="#1e293b" text-anchor="middle">RETR / STOR / LIST / DELE / CWD(lftpd 现状能力)</text>
<rect x="442" y="252" width="258" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
<text x="572" y="275" font-size="13" fill="#1e293b" text-anchor="middle">STOR = 原地覆盖(无 tmp+rename)</text>
<rect x="711" y="252" width="179" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
<text x="801" y="275" font-size="13" fill="#1e293b" text-anchor="middle">路径规范化封死 .. 穿越</text>
<rect x="26" y="350" width="1148" height="92" rx="12" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.45"/>
<text x="44" y="376" font-size="17" font-weight="700" fill="#0d9488">/config.ini(通用配置文件)</text>
<text x="300" y="376" font-size="12" fill="#64748b">FatFS 卷根(NAND GD5F2GQ5UE · dhara FTL)</text>
<rect x="44" y="388" width="283" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="185" y="411" font-size="13" fill="#1e293b" text-anchor="middle">INI 文本:[段] / key=value / ; 注释</text>
<rect x="337" y="388" width="257" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="466" y="411" font-size="13" fill="#1e293b" text-anchor="middle">CRLF · UTF-8(容忍 BOM)· ≤ 4KB</text>
<rect x="604" y="388" width="394" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="802" y="411" font-size="13" fill="#1e293b" text-anchor="middle">设备写:tmp + rename(原子);变更识别:size + mtime</text>
<rect x="26" y="486" width="1148" height="140" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.45"/>
<text x="44" y="512" font-size="17" font-weight="700" fill="#2563eb">cfg_file(配置文件引擎)</text>
<text x="271" y="512" font-size="12" fill="#64748b">App/cfg_file.c · 双端共享</text>
<rect x="44" y="524" width="325" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="206" y="547" font-size="13" fill="#1e293b" text-anchor="middle">INI 解析 / 序列化(逐键校验 · 容错 · 自愈)</text>
<rect x="379" y="524" width="337" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="547" y="547" font-size="13" fill="#1e293b" text-anchor="middle">外科手术式保存(保留注释 / 未知段 → 降级安全)</text>
<rect x="726" y="524" width="257" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="855" y="547" font-size="13" fill="#1e293b" text-anchor="middle">2s 变更轮询(f_stat)+ 段变更回调</text>
<rect x="44" y="572" width="254" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="171" y="595" font-size="13" fill="#1e293b" text-anchor="middle">出厂模板生成 · 旧 NCFG blob 迁移</text>
<rect x="26" y="670" width="1148" height="92" rx="12" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
<text x="44" y="696" font-size="17" font-weight="700" fill="#16a34a">段适配(键表)</text>
<text x="175" y="696" font-size="12" fill="#64748b">段 RAM 副本 = 模块静态结构体(稳态 0 增量)</text>
<rect x="44" y="708" width="361" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="224" y="731" font-size="13" fill="#1e293b" text-anchor="middle">net_cfg [network]:ip / mask / gateway / mac</text>
<rect x="415" y="708" width="352" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="591" y="731" font-size="13" fill="#1e293b" text-anchor="middle">未来:[adc] · [rs485] · [ftp] ...(三步接入)</text>
<rect x="26" y="806" width="1148" height="92" rx="12" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
<text x="44" y="832" font-size="17" font-weight="700" fill="#e11d48">消费者</text>
<text x="107" y="832" font-size="12" fill="#64748b">读 / 写段 RAM 副本 + 应用</text>
<rect x="44" y="844" width="263" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="175" y="867" font-size="13" fill="#1e293b" text-anchor="middle">netTask:组网 / 重建(代次号自愈)</text>
<rect x="317" y="844" width="320" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="477" y="867" font-size="13" fill="#1e293b" text-anchor="middle">UI 网口设置页:显示 / 编辑 / 保存(零改动)</text>
<rect x="648" y="844" width="329" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="813" y="867" font-size="13" fill="#1e293b" text-anchor="middle">未来模块:cfg_file_section_data() 取段数据</text>
<line x1="600.0" y1="176.0" x2="600.0" y2="199.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 600.0 206.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="600.0" y1="312.0" x2="600.0" y2="335.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 600.0 342.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="600.0" y1="448.0" x2="600.0" y2="471.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 600.0 478.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="600.0" y1="632.0" x2="600.0" y2="655.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 600.0 662.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="600.0" y1="768.0" x2="600.0" y2="791.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 600.0 798.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<text x="26" y="934" font-size="12" fill="#94a3b8">稳态 RAM 零增量(段数据 = 模块既有结构体);暂态堆缓冲 ≤9KB(仅 load/save 时);FF_FS_REENTRANT=1 卷锁串行化多任务 FS 访问;模拟器端 = 宿主文件 sim_config.ini(同核心)</text>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

+14 -14
View File
@@ -31,18 +31,16 @@
<text x="935" y="240" font-size="12" fill="#64748b">应用侧服务 / 编排</text>
<rect x="811" y="252" width="170" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="896" y="275" font-size="13" fill="#1e293b" text-anchor="middle">app_main(启动编排)</text>
<rect x="991" y="252" width="177" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1080" y="275" font-size="13" fill="#1e293b" text-anchor="middle">sys_clock(墙钟基准)</text>
<rect x="1179" y="252" width="185" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1271" y="275" font-size="13" fill="#1e293b" text-anchor="middle">sys_events(就绪屏障)</text>
<rect x="811" y="300" width="162" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="892" y="323" font-size="13" fill="#1e293b" text-anchor="middle">app_cfg(编译开关)</text>
<rect x="983" y="300" width="276" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1122" y="323" font-size="13" fill="#1e293b" text-anchor="middle">task/(net · ui · adc* · rs485*)</text>
<rect x="1270" y="300" width="148" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1344" y="323" font-size="13" fill="#1e293b" text-anchor="middle">lftpd(FTP :21)</text>
<rect x="811" y="348" width="227" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="924" y="371" font-size="13" fill="#1e293b" text-anchor="middle">time_sync(TCP 对时 :8888)</text>
<rect x="991" y="252" width="185" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1084" y="275" font-size="13" fill="#1e293b" text-anchor="middle">sys_events(就绪屏障)</text>
<rect x="1186" y="252" width="162" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1268" y="275" font-size="13" fill="#1e293b" text-anchor="middle">app_cfg(编译开关)</text>
<rect x="811" y="300" width="276" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="949" y="323" font-size="13" fill="#1e293b" text-anchor="middle">task/(net · ui · adc* · rs485*)</text>
<rect x="1097" y="300" width="148" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1171" y="323" font-size="13" fill="#1e293b" text-anchor="middle">lftpd(FTP :21)</text>
<rect x="1256" y="300" width="227" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1369" y="323" font-size="13" fill="#1e293b" text-anchor="middle">time_sync(TCP 对时 :8888)</text>
<rect x="26" y="446" width="1508" height="92" rx="12" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
<text x="44" y="472" font-size="17" font-weight="700" fill="#16a34a">④ 中间件层</text>
<text x="151" y="472" font-size="12" fill="#64748b">通用库 / 协议栈</text>
@@ -54,8 +52,10 @@
<text x="588" y="507" font-size="13" fill="#1e293b" text-anchor="middle">nand_ftl(dhara FTL)</text>
<rect x="692" y="484" width="164" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="774" y="507" font-size="13" fill="#1e293b" text-anchor="middle">LVGL v9.2(图形库)</text>
<rect x="866" y="484" width="186" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="960" y="507" font-size="13" fill="#1e293b" text-anchor="middle">util(crc · ringbuf)</text>
<rect x="866" y="484" width="177" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="955" y="507" font-size="13" fill="#1e293b" text-anchor="middle">sys_clock(墙钟基准)</text>
<rect x="1054" y="484" width="186" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1147" y="507" font-size="13" fill="#1e293b" text-anchor="middle">util(crc · ringbuf)</text>
<rect x="26" y="582" width="1508" height="92" rx="12" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.45"/>
<text x="44" y="608" font-size="17" font-weight="700" fill="#d97706">⑤ 驱动层</text>
<text x="134" y="608" font-size="12" fill="#64748b">Drivers/BSP · 芯片时序/中断</text>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

+7 -7
View File
@@ -1,6 +1,6 @@
# 系统墙钟时间维护 设计说明
> 模块:`App/sys_clock.c` / `App/sys_clock.h`
> 模块:`App/util/sys_clock.c` / `App/util/sys_clock.h`
> 依赖:SD2506API-G RTC 驱动(`Drivers/BSP/SD2506/`)、HAL(`HAL_GetTick()`)
> 适用平台:STM32F407ZGTx(Cortex-M4,168MHz),I2C1 接 SD2506
> 作者:王建锋 创建日期:2026-07-19
@@ -65,7 +65,7 @@ sys_clock_init(); /* 读取 RTC → 建立墙钟基准 */
## 4. 内部数据结构
`App/sys_clock.c` 中定义两个静态变量作为墙钟基准:
`App/util/sys_clock.c` 中定义两个静态变量作为墙钟基准:
```c
static volatile uint32_t s_base_tick = 0; /* HAL_GetTick() 基准(建立基准时的 Tick 值) */
@@ -76,7 +76,7 @@ static volatile uint32_t s_base_unix_secs = UINT32_MAX; /* 基准对应的 Unix
- `s_base_unix_secs`:与 `s_base_tick` 同一时刻的 Unix 秒数;`sys_clock_get()` 以它为锚推算当前值。
- 二者均带 `volatile`,因为时间是跨任务共享的状态(虽更新在 nets/默认任务中,但读取可能发生在任意任务上下文)。
> 注:当前工程仅 `main.c` 调用了 `sys_clock_init()`;`sys_clock_get()` / `sys_clock_get_str()` / `sys_clock_set()` 是供应用层(日志、文件时间、校时协议等)调用的 API,尚未被其他模块引用。
> 注:`app_main.c`(app_main_init)调用 `sys_clock_init()`;使用方含 `dbg_log`(日志时间戳)、`ffsystem`(文件时间戳)、`time_sync`(对时)等。
---
@@ -266,10 +266,10 @@ uint32_t now = sys_clock_get(); /* Unix 秒,可用于 FatFS 文件时间、
| 路径 | 说明 |
|------|------|
| `App/sys_clock.c` | 墙钟维护实现(基准法、换算、校时宏) |
| `App/sys_clock.h` | 对外 API 声明 |
| `App/util/sys_clock.c` | 墙钟维护实现(基准法、换算、校时宏) |
| `App/util/sys_clock.h` | 对外 API 声明 |
| `Drivers/BSP/SD2506/sd2506.c` / `.h` | SD2506 RTC I2C 驱动(`sd2506_get_time` / `sd2506_set_time`) |
| `Src/main.c` | 初始化调用顺序:`sd2506_init()` → `sys_clock_init()` |
| `App/app_main.c` | 初始化调用顺序:`sd2506_init()` → `sys_clock_init()` |
| `App/time_sync.c` / `time_sync.h` | 时间同步实现(TCP Server,`timeSyncTask`) |
| `test/time_sync_server.py` | PC 端时间推送客户端(TCP) |
@@ -353,4 +353,4 @@ PC 端 `python test/time_sync_server.py` 每 `interval` 秒推送一次,epoch
---
*文档依据 `App/sys_clock.c` / `App/time_sync.c` 实现与《嵌入式C语言代码规范(V1.0)》整理。*
*文档依据 `App/util/sys_clock.c` / `App/time_sync.c` 实现与《嵌入式C语言代码规范(V1.0)》整理。*
+55 -31
View File
@@ -1,6 +1,6 @@
# 网口设置页配置说明
> 版本:v2.0 日期:2026-09-10
> 版本:v2.1 日期:2026-09-14
> 适用工程:STM32F4-Base(STM32F407ZGTx + CH395F 以太网控制器)
> 范围:UI「网口设置」页可配置的网口参数(IP 地址 / 子网掩码 / 默认网关 / MAC 地址)——
> 参数与默认值、持久化、生效链路、页面操作与权限。
@@ -13,7 +13,7 @@
|---|---|
| **可配置** | IP 地址、子网掩码、默认网关、MAC 地址 |
| **入口** | 屏幕:`通讯参数 → 网口设置`(修改密码 `123` 可编辑;查看密码 `000` 只读) |
| **存储** | NAND Flash 文件 `/cfg/netcfg.bin`(掉电不丢) |
| **存储** | NAND Flash 文件 `/config.ini`(INI `[network]` 段,通用配置文件,掉电不丢);旧版 `/cfg/netcfg.bin` 首启自动迁移 |
| **生效** | 按「保存」后**立即生效**(网络重建约 1~3s,既有 TCP 连接会断开,无需重启) |
| **工厂默认** | `192.168.1.100` / `255.255.255.0` / `192.168.1.1` / 芯片内置 MAC |
@@ -37,7 +37,7 @@
| 默认网关 | `gw[4]` | **192.168.1.1** | |
| MAC 地址 | `mac[6]` | **全零** | 全零 = 不覆盖芯片内置 MAC(见 §2) |
工厂默认值是**单一来源**:`net_cfg.c` 顶部 `NET_CFG_DEF_*` 宏,同时用于 `s_cfg` 静态初始化与 `s_cfg_defaults()`,避免两处漂移。
工厂默认值是**单一来源**:`net_cfg.c` 顶部 `NET_CFG_DEF_*` 宏,同时用于 `s_cfg` 静态初始化与 `s_defaults`(引擎注册的 `p_defaults` 目标),避免两处漂移。
> **RAM 副本自启动即有效**:`s_cfg` 以工厂默认值**静态初始化**(落 `.data`),不依赖 `net_cfg_load()` 是否被调用——即便存储异常,页面与网络拿到的也是有效配置而非全零。
@@ -57,34 +57,54 @@
## 3. 持久化
### 3.1 磁盘格式(`/cfg/netcfg.bin`)
### 3.1 配置文件(INI `/config.ini`)
`net_cfg.c` 的 `net_cfg_blob_t`(两端布局一致,小端):
网口参数是**通用配置文件** `/config.ini` 的一个段(NAND 卷根,UTF-8/CRLF,文本编辑器可直接改):
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | `magic` | 4 | `0x4647434E`(ASCII `'NCFG'`) |
| 4 | `ver` | 2 | 格式版本 = 1 |
| 6 | `crc` | 2 | **CRC-16/MODBUS**,覆盖 ip..mac 共 18B |
| 8 | `ip[4]` | 4 | |
| 12 | `mask[4]` | 4 | |
| 16 | `gw[4]` | 4 | |
| 20 | `mac[6]` | 6 | |
| — | 对齐填充 | 2 | 结构体 `sizeof = 28B` |
```ini
; ============================================================
; STM32F4-Base 通用配置文件
; 格式: INI (UTF-8 / CRLF),本文件为配置唯一来源
; - 以 ';' 或 '#' 开头的行为注释,保存时保留
; - 未知段/键保存时保留;缺失段/键保存时写回
; - 编辑后经 FTP 上传或设备端保存,设备 4s 沉降后自动重载
; ============================================================
- **加载校验**:magic / ver / CRC 任一不符 → 回退工厂默认并打印 `DBG_ERROR`(坏配置不带崩网络)。
- **保存**:先建 `/cfg` 目录(`f_mkdir`,已存在不影响),再 `FA_CREATE_ALWAYS` 覆盖写。
[network]
ip=192.168.1.100
mask=255.255.255.0
gw=192.168.1.1
mac=00:00:00:00:00:00
```
### 3.2 存储后端(双端)
- **加载校验(逐键容错)**:单个键值非法(越界/格式错)不影响其他键——非法键回工厂默认并留痕(`parse: [network] <键> value invalid`);文件缺失 → 自动生成本模板;文件 > 4KB = 损坏 → 工厂默认 + 重建模板(坏配置不带崩网络)。
- **保存(外科手术式)**:注释 / 未知段 / 未知键逐行保留,受管键按 RAM 副本原位改写、缺失键补写;写盘 = 临时文件 `/config.TMP` + `f_rename` 原子替换(掉电不损坏目标文件)。
- **外部变更自动重载**(P4 阶段接入):2s 轮询检测 (size, mtime) 变化、4s 沉降(连续 2 次轮询稳定)后重载 `[network]` 段并重建网络——FTP 上传编辑后的文件无需重启即生效。
`net_cfg_store.h` 定义接口,两平台各一份实现(互斥编译):
完整设计(引擎 / 双端 / 并发模型 D15)见 `docs/配置文件设计说明.md`。
### 3.2 旧版档迁移(首启)
旧版格式 `/cfg/netcfg.bin`(28B:magic `'NCFG'` + ver + CRC-16/MODBUS + 18B 字段,格式拥有者 `net_cfg_legacy.c`):
| `/config.ini` 缺失 | `/cfg/netcfg.bin` | 行为 |
|---|---|---|
| 是 | 有效(magic/ver/CRC 通过) | 4 字段转 INI 行 → 写 `/config.ini`(带旧值)→ **删旧档** |
| 是 | 缺失 / 无效 | 生成出厂模板;**保留旧档**(留人工检查) |
| 否 | — | 正常加载现有文件(不迁移) |
迁移单向:旧档转换后删除(避免双数据源);新文件存在时恒跳过。
### 3.3 存储后端(双端)
`App/cfg_file_store.h` 定义接口(带路径的文件操作),两平台各一份实现(互斥编译):
| 平台 | 文件 | 位置 |
|---|---|---|
| MCU | `net_cfg_store_mcu.c` | NAND 上 FatFS 文件 `/cfg/netcfg.bin` |
| 模拟器 | `net_cfg_store_sim.c` | 工作目录本地文件 `sim_netcfg.bin`(`fopen`) |
| MCU | `App/cfg_file_store_mcu.c` | NAND 上 FatFS `/config.ini`(`/config.TMP` + `f_rename` 原子写) |
| 模拟器 | `simulator/sim/cfg_file_store_sim.c` | 工作目录本地文件 `sim_config.ini`(`fopen`/`rename`) |
磁盘格式两端一致,文件可互读。
INI 格式两端一致,文件可互读;mtime 变更令牌:MCU = FAT 打包时间(`fdate<<16\|ftime`,分钟级粒度),模拟器 = 宿主文件 mtime(秒级)。
---
@@ -105,7 +125,7 @@ sequenceDiagram
DT-->>NT: 置 SYS_EVT_STORAGE_READY(就绪屏障)
NT->>NT: 等 SYS_EVT_STORAGE_READY(≤10s;超时用工厂默认继续)
NT->>CFG: net_cfg_load()
Note over CFG: 读 /cfg/netcfg.bin;缺失/损坏 → 工厂默认
Note over CFG: 注册 [network] 段 → 首启自动迁旧档 → 读 /config.ini;缺失/非法键 → 工厂默认
CFG-->>NT: RAM 副本(有效)
NT->>CFG: net_cfg_get() → net_cfg_fmt_ipv4()
CFG-->>NT: "192.168.001.100"
@@ -191,10 +211,12 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| 文件 | 职责 |
|---|---|
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 配置核心:RAM 副本 + 工厂默认 + 加载/保存编排 + MAC 影子 + IPv4 格式化 |
| `Drivers/BSP/NETCFG/net_cfg_store.h` | 存储后端接口(读/写) |
| `Drivers/BSP/NETCFG/net_cfg_store_mcu.c` | MCU 后端:FatFS `/cfg/netcfg.bin` |
| `simulator/sim/net_cfg_store_sim.c` | 模拟器后端:本地 `sim_netcfg.bin` |
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 配置核心:RAM 副本 + 工厂默认 + 加载/保存编排 + MAC 影子 + IPv4 格式化([network] 段注册) |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | 旧版 NCFG 二进制格式拥有者:首启一次性迁移(校验 → 物化 → 删旧档) |
| `App/cfg_file.h/.c` | 通用配置文件引擎(双端共享核心:解析/校验/外科保存/模板/沉降轮询) |
| `App/cfg_file_store.h` | 存储后端接口(带路径的文件操作) |
| `App/cfg_file_store_mcu.c` | MCU 后端:FatFS `/config.ini`(`/config.TMP` + `f_rename` 原子写) |
| `simulator/sim/cfg_file_store_sim.c` | 模拟器后端:本地 `sim_config.ini` |
| `Drivers/BSP/UI/ui_page_net_cfg.c` | 网口设置内容页(显示 / 编辑 / 保存) |
| `Drivers/BSP/UI/ui_pwd_def.h` | 编译期密码(查看 000 / 修改 123) |
| `App/task/net_task.c` | 上电加载配置 + 消费"立即应用"请求重建网络 |
@@ -204,8 +226,8 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| API | 说明 |
|---|---|
| `net_cfg_load()` | 从存储加载到 RAM 副本;文件缺失/损坏回退工厂默认(恒返回 0) |
| `net_cfg_save()` | 组装磁盘格式写盘;0 成功 / -1 失败 |
| `net_cfg_load()` | 注册 `[network]` 段 + 旧档迁移 + 从 `/config.ini` 加载到 RAM 副本;缺失/非法键回退工厂默认(恒返回 0) |
| `net_cfg_save()` | 经 cfg_file 引擎写 `/config.ini`(外科手术式保存);0 成功 / -1 失败 |
| `net_cfg_request_apply()` | 请求立即应用(页面保存成功后调用) |
| `net_cfg_set_apply_cb()` | 注册"立即应用"平台回调 |
| `net_cfg_get()` | 取 RAM 副本只读指针(恒非空) |
@@ -221,8 +243,9 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
1. **保存即生效,但连接会断**:保存后重建网络(约 1~3s),既有 TCP 连接(FTP / 对时 / 远程显示)会断开并自动重建监听——IP 变更的必然结果,非缺陷。
2. **MAC 全零是哨兵**,不是"MAC 为 0":要恢复芯片内置 MAC,把 MAC 字段改回全零并保存。
3. **配置存在 NAND 上**:存储挂载失败时 `net_cfg_load` 回退工厂默认,网络仍能起,但保存会失败(页面显示 `ERR`)。
4. **改配置需"修改密码"**:查看密码 `000` 进页面是只读,ENTER 无效(防误改)。
5. **新增 TCP 服务需处理重建**:须在其监听循环中检测 `net_get_generation()` 变化并重建监听,否则网络重建后不再可达(见 §4.2)。
4. **变更令牌是分钟级**:FAT 时间为分钟粒度——同分钟内重写同长度文件,轮询检测不到变化(罕见;下次保存/改长度即恢复检测)。
5. **改配置需"修改密码"**:查看密码 `000` 进页面是只读,ENTER 无效(防误改)。
6. **新增 TCP 服务需处理重建**:须在其监听循环中检测 `net_get_generation()` 变化并重建监听,否则网络重建后不再可达(见 §4.2)。
---
@@ -242,4 +265,5 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| 日期 | 变更 |
|---|---|
| 2026-09-14 | v2.1:存储迁移至**通用配置文件** `/config.ini`(INI `[network]` 段,cfg_file 引擎:逐键校验 / 外科手术式保存 / 原子写 / 旧档首启迁移 / 沉降轮询);`net_cfg_store.*` 弃用(由 `cfg_file_store.*` 取代);对外 API 不变 |
| 2026-09-10 | MAC 显示改为常识格式 **`A4:94:B6:12:34:56`**(冒号分隔,17 字符);页面布局左移(名 x=4 / 值 x=54)以容纳 102px 宽度,编辑光标段偏移随之为 3i |
+571
View File
@@ -0,0 +1,571 @@
# 配置文件设计说明
> **版本**:v1.0(设计与实施规划)
> **日期**:2026-09-13
> **状态**:设计完成,按 §10 分阶段实施
> **关联文档**:`网络配置说明.md`(使用手册)· `项目架构总览.md` · `BSD_Socket_API_使用指南.md` · `docs/内存管理/`(RAM 依据)
## 1. 背景与目标
### 1.1 需求
| # | 需求 | 说明 |
|---|---|---|
| 1 | 网络参数配置写入一个文件 | IP / 掩码 / 网关 / MAC 持久化到文件 |
| 2 | 文件可经 FTP 下载 / 上传 | PC 端编辑后上传,设备自动检测并生效 |
| 3 | 通用配置文件 | 以后所有模块配置(ADC / RS-485 / 端口等)都进这个文件,按"段"组织 |
| 4 | 易于文本编辑器编辑 | 格式为纯文本、带注释、自解释 |
### 1.2 现状与差距分析(2026-09-13 调研)
**已有资产**(直接复用):
- 网络配置已是 FatFS 上的文件:`/cfg/netcfg.bin`(28 字节二进制 blob:magic 'NCFG' + ver + CRC-16/MODBUS + 18B 字段)——**但为二进制,不可编辑**
- FTP 通道现成:lftpd root = `/`(= FatFS 卷根),端口 21,支持 RETR / STOR / LIST / DELE
- 双端后端抽象模式现成:`net_cfg_store.h`(MCU = FatFS / 模拟器 = 宿主文件,互斥编译,格式两端一致)
- "立即应用"骨架现成:`net_cfg_set_apply_cb` / `net_cfg_request_apply` → `SYS_EVT_NET_RECONFIG` → netTask 重建网络 → 代次号 → TCP 服务自愈
**差距**(本设计的解决项):
| # | 差距 | 后果 | 设计解法 |
|---|---|---|---|
| ① | 二进制格式 | 编辑器无法编辑 | INI 文本格式(§3) |
| ② | 无通用机制 | 仅网络模块有配置 | 段键表引擎(§4.3) |
| ③ | FatFS 零并发保护(`FF_FS_REENTRANT=0`,应用层无锁) | ftpTask(FTP 传输)/ netTask(配置读+轮询)/ uiTask(保存)并发触卷 → 卷损坏 | `FF_FS_REENTRANT=1` + ffsystem.c 互斥钩子(§5.4)+ 三条一致性协议(D15:tmp+rename / 沉降去抖 / 单写者) |
| ④ | 无变更检测 | FTP 上传后不生效(需重启) | netTask 每 2s `f_stat` 轮询(size + mtime)(§5.3) |
| ⑤ | `FF_FS_NORTC=1` → 时间戳恒为固定值 | mtime 不变 → 无法检测"同尺寸编辑"(如 IP 尾段 100→200) | `NORTC=0` + `get_fattime`(复用调试日志墙钟源,运行期零 I2C)(§5.5) |
| ⑥ | 固件升级无迁移 | 现场设备升级后用户配置丢失(回工厂默认) | NCFG blob → INI 自动迁移(§9) |
| ⑦ | RAM 静态区仅余 5.8 KB(占用 95.4%) | 不能新增大静态缓冲 | 稳态零增量 + 暂态堆缓冲(§8) |
### 1.3 非目标(本期不做)
- 其他模块(ADC / RS-485 / 密码 / 端口等)的配置页与配置项——机制预留,逐项后续接入
- FTP 鉴权 / 权限(维持现状:无鉴权)
- DHCP 等网络模式扩展(仅 4 参数)
- 内存优化专项(LVGL 池迁 CCM 等,独立规划;本设计不依赖它)
## 2. 设计决策摘要
| # | 决策点 | 选择 | 理由(含否决项) |
|---|---|---|---|
| D1 | 文件格式 | **INI 文本**(`[section]` / `key=value` / 注释行) | 否决 JSON:无现成解析器(需引入,RAM 开销大)、编辑器不友好(无注释、尾逗号等);INI 零依赖、原生可编辑 |
| D2 | 文件位置 | **`/config.ini`(卷根)** | FTP 顶层直接可见;8.3 文件名兼容(`FF_USE_LFN=0` 约束);否决 `/cfg/config.ini`(藏子目录,多一级) |
| D3 | 编码 / 行尾 | UTF-8(内容实际 ASCII);设备写 **CRLF**;解析容忍 LF / BOM | 编辑器友好;解析为字节定向,不依赖代码页 |
| D4 | 保存策略 | **外科手术式行替换**(受管键替换,其余原样保留) | 否决整文件重生成:用户注释 / 未知段会被抹掉;**降级场景**(新文件被旧固件读)会抹掉未来版本段落;外科保留 = 降级安全 |
| D5 | RAM 模型 | **段键表引擎**:段 RAM 副本 = 各模块既有静态结构体;键表(const)映射 键 → 结构体偏移 | 稳态 RAM 零增量;未来模块"注册一个段"即接入 |
| D6 | 文件变更检测 | netTask 每 2s `f_stat`,比对 (size, mtime) | 否决内容哈希轮询(每 2s 全文件读,NAND 读流量浪费);mtime 检测依赖真实时间戳(见 D8) |
| D7 | 线程安全 | **`FF_FS_REENTRANT=1` + `Lib/FatFs/ffsystem.c`**(`ff_mutex_*` → osMutex,超时 1s) | FatFs 官方可重入方案;每次 f_* 调用加锁/放锁(逐次粒度,不长期阻塞 FTP 传输);`FR_TIMEOUT` 防死锁 |
| D8 | 时间戳 | **`FF_FS_NORTC=0` + `get_fattime`(复用 `sys_clock` 墙钟,与调试日志时间戳同源)** | mtime 为真实时间、与日志时间戳一致;墙钟 = 开机 RTC 基准 + tick 推进,运行期零 I2C;墙钟无效时回退固定值(有开机告警) |
| D9 | 写原子性 | **一切写入 = 临时文件 + `f_rename`**(设备保存 / FTP STOR / 出厂模板统一:先写 `<名>.TMP`(8.3 兼容),关文件成功后 rename 目标) | 同目录 rename = 单目录项写入,卷锁下原子 → 并发读者只见"旧完整文件或新完整文件",半截窗口结构消除;STOR 中途掉电仅残留 TMP(无害);逐键容错 + 下次保存自愈为次级兜底 |
| D10 | 旧档迁移 | 新固件首启:NCFG blob → INI,**迁移后删除旧文件** | 防现场升级丢配置;否决"保留旧文件"(双数据源、陈旧值混乱) |
| D11 | 出厂默认 | 文件缺失时**自动生成带注释模板**并落盘 | 首启 / 恢复出厂场景;文件自身即"格式手册" |
| D12 | 恢复出厂 | **FTP `DELE /config.ini`**(下次上电重新生成) | 本期不做 UI 入口(开放问题 O1) |
| D13 | 双端 | 共享 `cfg_file` 核心 + 平台 store 后端(沿用 net_cfg_store 模式) | MCU = FatFS `/config.ini`;模拟器 = 宿主文件 `sim_config.ini` |
| D14 | 网络配置 API 兼容 | **`net_cfg_*` 签名与语义不变** | UI 页零改动;load/save 内部改走 cfg_file |
| D15 | FS 并发模型 | **A+ = REENTRANT 互斥(D7)+ 三条一致性协议**:① 写 tmp+rename(D9 扩至 FTP STOR)② 轮询沉降去抖((size,mtime) 连续 2 次稳定才重载)③ 配置文件单写者收敛 netTask(UI 保存 = 消息至 netTask,落盘 + 重建由其执行) | 互斥保证单次调用原子(G1–G4:卷锁全程持有 / 1s 有界等待 / 优先级继承;锁固定 2 把,不随任务/文件数增长);三协议结构性消除"半截可见 / 重载抖动 / 多写者"缺口;**否决 actor 方案(单所有者队列 + 全代理层)**:经完整评估——代理 ~400–600 行、FILE 句柄表、符号改名/影子头、RAM +4KB、所有者自死锁纪律、双端适配,其两项结构优势(整操作原子 / 单所有者)可由上述三协议以 ~40 行等价中性化;实际并发仅 3 消费任务、写偶发 → 机制成本与收益不匹配;D13 store 后端抽象预留将来换入 actor 的空间(MCU 端后端实现) |
## 3. 文件格式规范
### 3.1 文件标识
| 端 | 路径 | 说明 |
|---|---|---|
| MCU | `/config.ini` | FatFS 卷根(NAND GD5F2GQ5UE,经 dhara FTL) |
| FTP | `/config.ini` | lftpd root = `/`,即卷根 |
| 模拟器 | `sim_config.ini` | 进程工作目录(宿主文件) |
### 3.2 语法规则
| 元素 | 规则 |
|---|---|
| 参数行 | `key=value`;key 为 `[a-z0-9_]+`(大小写敏感);值 = 首个 `=` 后至行尾,首尾空白去除 |
| 段头行 | `[section]`;段名 `[a-z0-9_]+`(大小写敏感) |
| 注释行 | `;` 或 `#` 开头(允许行首空白) |
| 空行 | 允许,保存时原样保留 |
| 段外键 | 加载时忽略(不设默认段),保存时原样保留 |
| 重复键 | 后者生效 |
| 行长 | > 127 字符 → 该行视为无效(键忽略,原文保留) |
| 文件大小 | > 4096 B → 整文件视为损坏 → 工厂默认 + 下次保存重生成 |
| 编码 | UTF-8;容忍文件头 BOM |
| 行尾 | CRLF(设备写出)/ LF 均接受 |
| 行内注释 | v1 不支持(防止值中 `;` 被误解析);注释仅占整行 |
### 3.3 出厂模板(首启自动生成)
```ini
; ==========================================================
; DTU 通用配置文件
; 语法: [段名] 段头 / key=value 参数
; 以 ';' 或 '#' 开头的行为注释
; 编辑后经 FTP 上传 (端口 21, 路径 /config.ini)
; 编辑后数秒内自动检测并生效 (2~6s, 含沉降去抖)
; (网络段变化将重建网络, 既有连接会断开)
; ==========================================================
[network]
; IP 地址
ip=192.168.1.100
; 子网掩码
mask=255.255.255.0
; 默认网关
gateway=192.168.1.1
; MAC 地址 (全零 = 使用芯片内置 MAC)
mac=00:00:00:00:00:00
```
### 3.4 值类型
| 类型 | 语法 | 示例 | 解析失败处理 |
|---|---|---|---|
| `CFG_VAL_IP4` | `a.b.c.d`(兼容前导零) | `192.168.1.100` | 该键取工厂默认 + `DBG_ERROR` |
| `CFG_VAL_MAC` | `aa:bb:cc:dd:ee:ff`(十六进制) | `a4:94:b6:12:34:56` | 同上 |
| `CFG_VAL_UINT` | 十进制(兼容 `0x` 前缀) | `7003` | 同上 |
| `CFG_VAL_STR` | 原始字符串(≤31 字符) | `hello` | 同上 |
### 3.5 校验与容错(逐键粒度)
- **逐键解析**:单个键值非法不影响其他键;非法键取工厂默认值(RAM 结构体以工厂默认静态初始化)
- **自愈**:下次保存时该行被写回合法当前值
- **未知内容保留**:未知段 / 键 / 注释 / 空行在保存时原样保留(外科保留)
| 场景 | 加载行为 | 保存行为 |
|---|---|---|
| 文件不存在 | 工厂默认;自动生成模板(有旧 blob 时优先迁移,见 §9) | 写当前值 |
| 段缺失 / 键缺失 | 该键取工厂默认 | 补写缺失键(段缺失则补整段) |
| 键值非法 | 该键取工厂默认 + 日志 | 重写为合法值(自愈) |
| 文件 > 4KB / 严重损坏 | 全部工厂默认 + `DBG_ERROR` | 整文件重生成(已知数据保留,未知内容丢失) |
| 未来版本多出的段(降级) | 忽略 | **原样保留**(降级安全的核心) |
| FTP 上传半截(掉电) | 逐行解析:完整行生效,尾部残行忽略 | 下次保存自愈 |
## 4. 总体架构
### 4.1 分层
![通用配置文件分层架构](images/配置文件分层架构.svg)
### 4.2 模块清单
| 文件 | 动作 | 端 | 职责 |
|---|---|---|---|
| `App/cfg_file.h` / `cfg_file.c` | **新增** | 双端 | 配置文件引擎:段注册 / 解析 / 外科保存 / 变更轮询 |
| `App/cfg_file_store.h` | **新增** | 双端 | 平台文件后端接口 |
| `App/cfg_file_store_mcu.c` | **新增** | MCU | FatFS 后端(`/config.ini`:读 / stat / 原子写 / 删) |
| `simulator/sim/cfg_file_store_sim.c` | **新增** | 模拟器 | 宿主文件后端(`sim_config.ini`) |
| `Lib/FatFs/ffsystem.c` | **新增** | MCU | REENTRANT 卷互斥钩子(osMutex)+ `get_fattime`(直引用墙钟,与日志时间戳同源) |
| `Lib/FatFs/ffconf.h` | 修改 | MCU | `FF_FS_REENTRANT` 0→1;`FF_FS_NORTC` 1→0 |
| `App/util/sys_clock.c/.h` | 修改 | MCU | 新增 `sys_clock_get_time()`(日历时间获取器);改归 ④中间件层,自 `App/` 移入 `App/util/` |
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 重构 | 双端 | `net_cfg_*` API 不变;load/save 走 cfg_file;注册 `[network]` 段 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | **新增** | 双端 | 旧 NCFG blob 解析 + 迁移 |
| `Drivers/BSP/NETCFG/net_cfg_store.h` / `net_cfg_store_mcu.c` | **删除** | — | 由 `cfg_file_store.*` 取代 |
| `simulator/sim/net_cfg_store_sim.c` | **删除** | 模拟器 | 由 `cfg_file_store_sim.c` 取代 |
| `App/task/net_task.c` | 修改 | MCU | 主循环每 2s `cfg_file_poll()` |
| `simulator/main.c` | 修改 | 模拟器 | 主循环每 2s 轮询 |
| `simulator/CMakeLists.txt` | 修改 | 模拟器 | 源文件列表 |
| `MDK-ARM/STM32F407-Demo.uvprojx` | 修改 | MCU | 新文件注册 |
### 4.3 关键机制
**(1) 段键表引擎** —— "以后所有配置进这个文件"的落地形式:
```c
/* 键定义:键名 + 类型 + 段结构体内的偏移/长度 */
typedef struct {
const char *name; /* 键名 */
cfg_val_type_t type; /* 值类型(UINT / IP4 / MAC / STR) */
uint16_t offset; /* 值在段 RAM 结构体内的偏移 */
uint16_t len; /* 值在结构体内的字节数 */
} cfg_key_def_t;
/* 段注册:段名 + 模块自有 RAM 副本 + 键表 */
typedef struct {
const char *name; /* 段名,如 "network" */
void *data; /* 段 RAM 副本(模块自有静态变量) */
const cfg_key_def_t *keys; /* 键表(const,Flash) */
uint16_t key_count;
} cfg_section_reg_t;
```
- 加载:逐行解析文件 → 受管键(键表命中)按偏移写入对应模块结构体
- 保存:按各段结构体当前值生成受管键行
- 未来模块接入三步:① 定义段结构体(静态,工厂默认初始化)② 写键表 ③ `cfg_file_register()`
**(2) 外科手术式保存** —— 降级安全的核心:
```mermaid
flowchart TD
A["cfg_file_save()"] --> B{"读 /config.ini"}
B -->|"不存在"| C["生成出厂模板(全部已注册段 + 注释)"]
B -->|"存在且 ≤ 4KB"| D["逐行扫描"]
B -->|"> 4KB(按损坏处理)"| E["用 RAM 数据整文件重生成"]
D --> F{"行类型"}
F -->|"受管键(已注册 key)"| G["替换为当前 RAM 值,标记 seen"]
F -->|"注释 / 空行 / 未知行"| H["原样保留"]
G --> I{"有键未写入?"}
H --> I
I -->|"是"| J["段末补写缺失键;段缺失则补整段"]
I -->|"否"| K["写 /config.ini.tmp"]
C --> K
E --> K
J --> K
K --> L["f_rename → /config.ini(原子替换)"]
L --> M["f_stat 更新变更基线"]
```
**(3) 变更检测** —— `cfg_file_poll()`(netTask / 模拟器主循环,2s 节拍):`f_stat` 取 (size, mtime-epoch) 与基线比对 → 变化则重载 → 段变化则触发该段回调。
**(4) 段变更回调** —— 未来模块的接入点:
```c
void cfg_file_register_cb(const char *p_name, cfg_section_cb_t p_cb);
/* 网络模块注册:[network] 段变化 → 置 SYS_EVT_NET_RECONFIG(复用既有重建路径) */
```
## 5. 运行时机制
### 5.1 上电加载
```mermaid
sequenceDiagram
participant DT as defaultTask
participant NT as netTask
participant FS as FatFS(NAND卷)
participant NS as 网络层
DT->>FS: f_mount(无卷自动 f_mkfs)
NT->>NT: 等 SYS_EVT_STORAGE_READY(≤10s)
NT->>NT: net_cfg_load()(注册 [network] 段)
alt /config.ini 不存在
alt 存在旧 /cfg/netcfg.bin
NT->>FS: 读旧 blob 校验 → 转写 /config.ini → 删旧文件
else 无旧文件
NT->>FS: 生成出厂模板 → 写 /config.ini
end
end
NT->>FS: 读 /config.ini → 解析入 net_cfg RAM 副本
NT->>NS: net_init(ip, mask, gw, mac)
NT->>NT: 置 SYS_EVT_NET_READY
```
- 存储异常(挂载超时 / 失败):回退工厂默认继续组网(与现状一致,不阻塞网络)
### 5.2 UI 保存
```mermaid
sequenceDiagram
participant UI as uiTask(网口设置页)
participant NC as net_cfg
participant CF as cfg_file
participant FS as FatFS
participant NT as netTask
UI->>NC: ↑/↓ 调整 → net_cfg_set(实时入 RAM,不写盘)
UI->>NC: ENTER 保存行 → net_cfg_save()
NC->>CF: cfg_file_save()
CF->>FS: 读旧文件 → 外科式行替换 → 写 /config.ini.tmp
CF->>FS: f_rename → /config.ini(原子)
CF->>FS: f_stat 更新变更基线
UI->>NC: net_cfg_request_apply()
NC->>NT: 置 SYS_EVT_NET_RECONFIG
NT->>NT: 下一节拍 net_stack_bring_up()(重建网络,代次 +1)
```
- UI 页代码**零改动**(`net_cfg_*` API 语义不变)
- 保存失败(存储异常)→ 页面显示 ERR(维持现状)
### 5.3 FTP 上传自动生效
```mermaid
sequenceDiagram
participant PC as PC(编辑器+FTP客户端)
participant FT as ftpTask(lftpd)
participant FS as FatFS
participant NT as netTask
PC->>FT: RETR /config.ini(下载)
FT->>FS: f_open/f_read(逐次加锁即放)
Note over PC: 编辑器修改(如改 IP)
PC->>FT: STOR /config.ini(原地覆盖)
FT->>FS: f_write/f_close(mtime 更新)
loop 每 2s
NT->>FS: f_stat(size + mtime)
end
NT->>NT: 与基线不同 → 重新加载
alt [network] 段有变化
NT->>NT: 置 SYS_EVT_NET_RECONFIG → 重建网络(代次 +1,服务自愈)
else 仅其他段变化
NT->>NT: 仅更新 RAM,触发对应段回调
end
```
- 检测粒度 2s(轮询周期);网络重建含等链路 ≤3s,总生效时间约 2~5s
- 下载天然支持(RETR 即文件读)
- **沉降去抖(D15-②)**:(size, mtime) 相对基线变化且**连续 2 次轮询(4s)稳定**才重载——防 FTP STOR 进行中的中间态触发多次重载(STOR 先写 `.TMP`,目标文件 (size, mtime) 在 rename 前不变,rename 单次原子更新 → 至多一次重载)
- **单写者(D15-③)**:`/config.ini` 的直接写者收敛为 netTask(UI 保存 = 消息至 netTask,由其落盘(tmp+rename)+ 网络重建,复用既有 `SYS_EVT_NET_RECONFIG` 路径);唯一例外 = FTP STOR 路径(lftpd,tmp+rename,与 netTask 写路径经卷锁互斥)
### 5.4 线程安全(FatFS 可重入)
- `FF_FS_REENTRANT=1`:FatFs 为每卷建互斥锁(系统锁 + 卷锁),**每次 f_\* 调用入口加锁、出口放锁**(逐次粒度)
- `Lib/FatFs/ffsystem.c` 实现四个钩子:`ff_mutex_create/delete/take/give` → `osMutexNew/Delete/Acquire/Release`(CMSIS-RTOS V2)
- 锁等待超时 = `FF_FS_TIMEOUT` = 1000ms → 超时返回 `FR_TIMEOUT`(操作优雅失败,下节拍重试,无死锁)
- 并发矩阵(启用后均由卷锁串行化):
| 任务 | FatFS 访问 | 典型持锁时长 |
|---|---|---|
| defaultTask | f_mount / f_mkfs(仅启动期) | 一次性 |
| netTask | 配置文件读 / stat 轮询 / 重建时写 | 毫秒级 |
| uiTask | (D15:保存 = 消息至 netTask,不直接触卷) | — |
| ftpTask | RETR / STOR / LIST / DELE | STOR 秒级(多次短锁) |
| testTask | 存储测试套件(测试构建) | 测试场景 |
- nand_ftl 页缓存在卷锁范围内调用 → 天然受保护
### 5.5 时间戳(变更检测基准)
- `FF_FS_NORTC=0`:写后关文件时 FatFs 以 `get_fattime()` 的返回值记录 mtime
- `get_fattime`(`Lib/FatFs/ffsystem.c` 内——FatFs 指定的 OS 胶水文件;ff.h 将其声明为"用户方提供"):**直引用 `sys_clock` 墙钟**,与调试日志时间戳同源
```c
#include "sys_clock.h" /* 层内中间件引用(sys_clock 归 ④中间件层) */
DWORD get_fattime(void) {
if (sys_clock_get_time(&t) != 0) { return FF_TIME_FALLBACK; } /* 日历时间获取器(纯时间 API) */
return ((t.year-1980)<<25 | t.month<<21 | t.day<<16 | t.hour<<11 | t.minute<<5 | t.second/2);
}
```
- **为何直引用而非回调注册**:`get_fattime` 唯一消费者是同库 `ff.c`,唯一外部依赖是时间源;直引用即 C 库平台钩子的标准模式,无注册步骤、无"漏注册 → 时间戳静默退化"失效模式;代价为零——`sys_clock` 是通用平台时间服务(日志/文件/对时共用),**改归 ④中间件层**(实体移入 `App/util/`,与 crc/ringbuf 同目录),二者为**层内引用**,工程无反向依赖
- 墙钟由 `sys_clock_init()` 开机建立(读一次 RTC,无效时按 `RTC_DEFAULT_INIT_*` 默认值自动校时),运行期纯 tick 推进——**零 I2C 流量**,与日志时间戳同一机制
- mtime 为**真实时间**,与调试日志时间戳完全一致(如 `2026-09-13 14:32:05`);FTP LIST 亦显示真实时间
- 墙钟无效(开机 RTC 读取失败且默认校时也失败的极罕见情形)→ 返回固定值(`FF_NORTC` 约定);检测退化为仅 size,既有开机告警链有效
- 比较语义:"相对上次是否变化"(非单调性判断);timeSync 校时可能使墙钟跳变 → 至多多一次重载(无害)
## 6. 接口设计
### 6.1 cfg_file 核心 API(双端共享)
```c
/* App/cfg_file.h */
/* 段变更回调(重载后在触发重载的任务上下文调用;可空) */
typedef void (*cfg_section_cb_t)(void);
/* 段注册描述符:name / p_data(段 RAM 副本)/ p_defaults(工厂默认,
* 与 p_data 同布局,load 先复位再解析)/ data_size / p_keys(键表)/
* key_count / p_cb(变更回调;注册段 data_size 须 ≤ 64 方可挂回调) */
typedef struct { ... } cfg_section_reg_t;
int cfg_file_register(const cfg_section_reg_t *p_sec); /* 注册段(首次 load 前;变更回调随段注册) */
int cfg_file_load(void); /* 0 = 正常(含回退默认)/ -1 = 严重错误 */
int cfg_file_save(void); /* 0 = 成功 / -1 = 失败(原文件保持不动) */
void cfg_file_poll(void); /* 变更检测 + 沉降去抖(netTask / 模拟器主循环,2s 节拍) */
void *cfg_file_section_data(const char *p_name); /* 取段 RAM 副本(未注册 = NULL) */
```
### 6.2 cfg_file_store 后端接口(每端一份实现,互斥编译)
```c
/* App/cfg_file_store.h —— 带路径的文件操作(路径由后端持有,核心不感知平台布局) */
typedef struct {
uint32_t size; /* 文件长度(字节) */
uint32_t mtime; /* 变更令牌(仅等值比较;MCU=FAT 时间打包 / 模拟器=宿主时间) */
} cfg_file_stat_t;
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread); /* 至多读 cap 字节 */
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len); /* 临时文件 + rename(原子) */
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st); /* 0 = 存在 / -1 = 缺失/失败 */
int cfg_store_remove(const char *p_path);
/* 路径由后端提供(MCU: /config.ini、/config.TMP、/cfg/netcfg.bin;
* 模拟器: sim_config.ini、sim_config.ini.tmp、sim_netcfg.bin) */
const char *cfg_store_config_path(void);
const char *cfg_store_tmp_path(void);
const char *cfg_store_legacy_path(void);
```
### 6.3 未来模块接入模板
```c
/* 示例:未来 [adc] 段 */
typedef struct {
uint16_t sample_ms; /* 采样周期 */
char channel[16]; /* 通道名 */
} adc_cfg_t;
static adc_cfg_t s_adc_cfg = { 100u, "ch1" }; /* 工厂默认静态初始化 */
static const adc_cfg_t s_adc_defaults = { 100u, "ch1" }; /* 引擎 p_defaults 目标 */
static const cfg_key_def_t s_adc_keys[] = {
{ "sample_ms", CFG_VAL_UINT, offsetof(adc_cfg_t, sample_ms), sizeof(uint16_t) },
{ "channel", CFG_VAL_STR, offsetof(adc_cfg_t, channel), sizeof(s_adc_cfg.channel) },
};
void adc_cfg_init(void)
{
static const cfg_section_reg_t s_sec = {
.name = "adc",
.p_data = &s_adc_cfg,
.p_defaults = &s_adc_defaults,
.data_size = sizeof(adc_cfg_t),
.p_keys = s_adc_keys,
.key_count = 2u,
.p_cb = adc_on_config_changed, /* 可选:变更回调 */
};
(void)cfg_file_register(&s_sec);
}
```
### 6.4 net_cfg 兼容承诺
- `net_cfg_load/save/get/set/get_mac/set_active_mac/fmt_ipv4/set_apply_cb/request_apply` **签名与语义全部不变**
- 内部:`net_cfg_load` = 段注册 + 旧档迁移(`net_cfg_legacy_migrate`)+ `cfg_file_load`;`net_cfg_save` = `cfg_file_save`
- UI 页(`ui_page_net_cfg.c`)、启动流程(`net_task.c`)、模拟器入口(`main.c`)的**调用点无需改动**
## 7. 双端支持
| 项 | MCU | 模拟器 |
|---|---|---|
| 后端 | `cfg_file_store_mcu.c`(FatFS) | `cfg_file_store_sim.c`(宿主文件) |
| 文件路径 | `/config.ini`(NAND 卷根) | `sim_config.ini`(工作目录) |
| mtime 来源 | FAT 打包时间(`fdate<<16\|ftime`,分钟级粒度) | 宿主文件系统(秒级) |
| 旧档迁移 | `/cfg/netcfg.bin`(NCFG blob) | `sim_netcfg.bin`(同格式) |
| 变更轮询 | netTask 2s | 主循环 2s(编辑器改动 2s 内反映) |
| 弃用 | — | `net_cfg_store_sim.c` 删除 |
模拟器行为:首跑无 `sim_config.ini` 而有 `sim_netcfg.bin` → 自动迁移并删旧文件;运行中用编辑器改 `sim_config.ini` → 2s 内自动重载(模拟器无网络重建概念,[network] 段"应用" = 刷新 RAM 副本)。
## 8. 内存预算
依据:最新 map(2026-09-13):静态区(128 KB)已用 125,104 B(95.4%),**余 5,968 B**;FreeRTOS 堆 48 KB,稳态占用 35.4 KB,**空闲 ≈13.4 KB**。
| 项 | 大小 | 性质 | 说明 |
|---|---|---|---|
| 段 RAM 副本 | **0 增量** | 稳态 | 复用 `net_cfg` 既有 `s_cfg`(22 B,.data) |
| 段表 / 键表 | ~150 B | 稳态(Flash) | const,进 .constdata |
| 段注册表(上限 8 段) | ~350 B | 稳态(.bss) | 含键行号表 256 B / 变更快照 64 B / 基线 16 B(实施实测) |
| REENTRANT 互斥锁 | ~100 B | 稳态(堆) | 2 个 osMutex |
| 读缓冲 | ≤ 4 KB | 暂态(堆) | `cfg_file_load`/`save` 期间(输入复用读缓冲) |
| 写出缓冲 | ≤ 5 KB | 暂态(堆) | `cfg_file_save` 外科手术输出;溢出 = 放弃保存(原文件不动) |
| **堆峰值(save 期间)** | **≈ 9 KB** | 暂态 | < 空闲 13.4 KB ✓ |
| **静态 .bss 增量** | **≈ 0.7 KB(实测 706 B)** | 稳态 | 段表 + 行号表 + 快照 + 基线 << 静态余量 5.9 KB ✓ |
**结论:无需内存优化专项**(LVGL 池迁 CCM 等);实施后以 map 复核两个余量数字。
## 9. 兼容与迁移
### 9.1 旧文件迁移(新固件首启)
```
/config.ini 缺失?
├─ 是 → /cfg/netcfg.bin 存在且有效(magic/ver/CRC)?
│ ├─ 是 → 4 字段转 INI 行 → 原子写 /config.ini → 删旧文件
│ │ 日志: "cfg file: legacy NCFG migrated"
│ └─ 否 → 生成出厂模板
└─ 否 → 正常加载现有文件
```
- 迁移单向:旧文件转换后删除(避免双数据源)
- 旧 blob 损坏:按不存在处理 → 出厂模板(旧文件保留,可人工查看)
### 9.2 现场升级 / 降级场景
| 场景 | 行为 |
|---|---|
| 旧设备(有 netcfg.bin)升级新固件 | 自动迁移,用户配置保留 |
| 新设备 / 格式化卷 | 出厂模板 |
| 新文件被**旧固件**读(降级) | 旧固件只读 `/cfg/netcfg.bin`(已删)→ 工厂默认;**降级需人工恢复网口配置**(已知限制,O2) |
| 含未来段的新文件被旧固件读 | 旧固件不读该文件 → 文件不受影响 |
### 9.3 恢复出厂
- **FTP `DELE /config.ini`** → 下次上电重新生成出厂模板(全段工厂默认)
- 本期无 UI 入口(开放问题 O1)
## 10. 实施计划
| 阶段 | 内容 | 涉及文件 | 验收标准 |
|---|---|---|---|
| **P1** | 设计文档 + 架构图 | 本文档 + `gen_cfg_arch_svg.py` + SVG | 用户确认 §2 决策 |
| **P2** | FS 线程安全 + 时间戳 | `ffconf.h`、`Lib/FatFs/ffsystem.c`(新)、`App/util/sys_clock.c`(sys_clock_get_time;移入中间件层)、`uvprojx`、架构总览 §1.1(sys_clock 改归 ④) | `@build` 0/0;`@test` 启动 PASS(无死锁) |
| **P3** ✅ 2026-09-14 | cfg_file 引擎(双端)+ net_cfg 集成 + 旧档迁移 | `App/cfg_file.{h,c}`、`App/cfg_file_store.h`、`App/cfg_file_store_mcu.c`(新)、`simulator/sim/cfg_file_store_sim.c`(新)、`net_cfg.{h,c}`(段注册 + 引擎化 load/save)、`net_cfg_legacy.c`(新,NCFG 迁移)、删 `net_cfg_store*`、`uvprojx`、`CMakeLists.txt` | Keil `@build` 0/0;模拟器生成 `sim_config.ini` 且编辑回读一致;旧档 NCFG 自动迁移三态正确 —— **已实测通过**(工厂模板 / 编辑回读 / 非法键回退 / 迁移三态 / 双端 0 警告;`cfg_file.c` 含中文注释块 → UTF-8 **带 BOM**) |
| **P4** | 轮询 + D15 协议 + 测试标记 | `net_task.c`(沉降去抖轮询 + 落盘收敛 + 串口标记)、`lftpd.c`(STOR tmp+rename) | `@build`;`@test` 启动新标记(`cfg file: loaded OK` 等);FTP 上传无半截可见(rename 原子) |
| **P5** | 模拟器迁移 | `simulator/sim/cfg_file_store_sim.c`(新)、删 `net_cfg_store_sim.c`、`main.c`(轮询)、`CMakeLists.txt` | sim 构建 + 运行;`sim_netcfg.bin` 自动迁移 |
| **P6** | 文档同步 | `网络配置说明.md`(§0/3/6/7 重写)、`AGENTS.md`、`README.md`、`项目架构总览.md` + `gen_arch_svg.py`、`内容页架构.md`、BSD 指南、`simulator/README.md`、`ui_page_net_cfg.c` 头注释、`docs/测试说明/` | pre-commit 全绿;全库无"Flash"旧表述残留 |
| **P7** | FTP 闭环脚本 | `test/ftp_config_test.py`(新)+ 测试说明 | 真机闭环 PASS(§11.2) |
| **P8** | 总回归 + 提交 | `@build` + `@test` + 模拟器 ui-verify + `pre-commit --all-files` | 0 错误 0 警告;全 PASS |
**建议提交边界**:C1 = P2+P3(FS 线程安全 + 配置引擎)/ C2 = P4+P5(网络配置迁移 + 模拟器)/ C3 = P6+P7(文档 + 测试脚本);P8 发现的收尾问题并入最近一次提交。
## 11. 测试计划
### 11.1 自动回归
| 串口标记 | 场景 | 属性 |
|---|---|---|
| `cfg file: loaded OK` | 上电加载成功 | Required(P4 起纳入 `@test`) |
| `cfg file: factory template written` | 首启 / 删文件 | 条件(手工) |
| `cfg file: legacy NCFG migrated` | 旧文件迁移 | 条件(真机首次升级) |
| `cfg file: saved OK` | UI 保存 | 条件(手工) |
| `cfg file: change detected` | FTP 上传 | 条件(P7 闭环) |
| `cfg file: ... FAILED` | 任何失败 | Forbidden |
既有标记不变:`App init OK` / `Network Init` / `net init: OK`;Forbidden:`FAIL` / `ERROR` / `HardFault`。
### 11.2 FTP 闭环脚本(`test/ftp_config_test.py`,`.venv` python,stdlib ftplib)
```
1. 登录(按 FTP 测试说明的约定)
2. RETR /config.ini → 解析 → 校验格式合法
3. 注入一条测试注释行 + 修改 IP(尾段 +1)
4. STOR /config.ini
5. 等待 ≤10s → 校验串口日志:"cfg file: change detected" + 网络重建标记
6. 再次 RETR → 校验:新 IP 生效 且 测试注释保留(外科保留验证)
7. (可选)STOR 损坏文件 → 校验回退默认 + 自愈
退出码:0 = PASS / 1 = FAIL
```
### 11.3 手工用例
| # | 用例 | 步骤 | 预期 |
|---|---|---|---|
| M1 | 编辑器往返 | 下载 → Notepad 改 IP + 加注释 → 上传 | 2~5s 内生效;注释保留 |
| M2 | 降级安全 | 文件加 `[future]` 段 → 设备端保存 → 下载 | `[future]` 段原样 |
| M3 | 恢复出厂 | FTP DELE /config.ini → 重启 | 重新生成模板,工厂 IP |
| M4 | 损坏容错 | STOR 一个含非法值 + 残行的文件 | 非法键回退默认,其余键生效,有日志 |
| M5 | 并发 | FTP 下载大文件同时 UI 保存 | 无卷损坏、无死锁(FR_TIMEOUT 重试) |
| M6 | 超长文件 | STOR > 4KB 文件 | 回退默认 + 下次保存重生成 |
## 12. 风险与开放问题
### 12.1 风险
| 风险 | 等级 | 缓解 |
|---|---|---|
| FTP STOR 半截写(掉电 / 断链) | 中 | 逐键容错 + 工厂回退;0 字节 STOR 自动删文件(lftpd 现状)→ 下次上电重新生成模板;下次保存自愈 |
| 墙钟无效(开机 RTC 读取失败且默认校时失败) | 低 | mtime 退化为固定值(检测退化为 size);既有开机告警链 + 默认校时(RTC_DEFAULT_INIT)兜底,实际极罕见 |
| mtime 秒边界碰撞(同秒两次写) | 低 | (size, mtime) 双比对;2s 轮询通常跨秒;误报 = 多一次重载(无害) |
| save 堆峰值与 LVGL / lftpd 争用 | 低 | 事件触发(罕见);FR_TIMEOUT 兜底;实施后 map 复核堆空闲 |
| 8.3 文件名约束 | 无 | `config.ini` 合规;段名 / 键名属文件内容,不受限 |
| 卷重新格式化丢配置 | 中(现状一致) | 与现状相同(f_mkfs 清空卷);文档提示 |
### 12.2 开放问题
| # | 问题 | 默认策略(可推翻) |
|---|---|---|
| O1 | UI 是否加"恢复出厂"入口 | 本期不做,用 FTP 删除 |
| O2 | 降级时配置同步策略 | 接受"降级 = 工厂默认" + 文档说明 |
| O3 | 其他模块接入顺序 | 按需(ADC / RS-485 / 端口等) |
| O4 | 文件格式版本号行(如 `; format=1`) | 本期不加(宽容解析已覆盖演进) |
---
## 变更记录
| 日期 | 变更 |
|---|---|
| 2026-09-13 | v1.0:初版设计(基于现状调研) |
| 2026-09-13 | v1.1:`get_fattime` 由 SD2506 RTC 改为运行时间伪时(零 I2C 流量、无 RTC 依赖);相应回退 SD2506 驱动锁(P2 修正) |
| 2026-09-13 | v1.2:`get_fattime` 移入 `App/sys_clock.c` 改用墙钟(与调试日志时间戳同源,mtime 为真实时间;修正伪时的 24h 回绕盲区与日期失真) |
| 2026-09-13 | v1.3:`get_fattime` 移入独立适配文件 `App/ff_time.c`(时间子系统不再引用存储头文件);`sys_clock` 新增纯时间 API `sys_clock_get_time()` |
| 2026-09-13 | v1.4:`get_fattime` 改为时间提供回调——实现回归 `Lib/FatFs/ffsystem.c`(FatFs 指定的 OS 胶水文件,不引用 App 头),App 启动时注册墙钟时间源;删除 `App/ff_time.c`;保持分层单向(与既有平台后端模式同型) |
| 2026-09-13 | v1.5:`get_fattime` 改为直引用(ffsystem.c 直接引用 `sys_clock` 墙钟);去掉回调注册机制(删除 `ffsystem.h`、`app_main.c` 注册步骤);反向依赖在架构总览 §1.2 登记为工程唯一例外 |
| 2026-09-13 | v1.6:`sys_clock` 由 ③基础服务层改归 **④中间件层**(通用平台时间服务),文件移入 `App/util/`(与 crc/ringbuf 同目录):`ffsystem` → `sys_clock` 成为**层内引用**,工程反向依赖消除;"反向依赖例外" 措辞同步撤销 |
| 2026-09-13 | v1.7:FS 并发定案 **A+**(REENTRANT 互斥 + 三条一致性协议:写 tmp+rename 扩至 FTP STOR / 沉降去抖重载 / 配置文件单写者收敛 netTask);**actor 方案(单所有者队列 + 全代理层)经完整评估否决**(代理 ~400–600 行、RAM +4KB、符号改名/影子头、自死锁纪律——结构优势可由三协议 ~40 行等价中性化);新增 D15,D9 改为"一切写入 tmp+rename",P4 增列 lftpd STOR 改造 |
| 2026-09-14 | v1.8:**P3 完成**(双端 0/0):`cfg_file` 段键表引擎 + `cfg_file_store` 双端后端 + `net_cfg` [network] 段集成(对外 API 不变,D14 兑现)+ `net_cfg_legacy` 旧档迁移;无头实测通过(工厂模板生成 / 编辑回读 / 非法键工厂回退 / 未知段容留 / 迁移三态 / 双端 0 警告);§6 接口修订为实现版(store 带路径参数 + 三路径 getter;变更回调随段注册,撤销独立 `cfg_file_register_cb`);§8 静态增量按实测 706 B 修订 |
+9 -8
View File
@@ -23,8 +23,8 @@
|---|---|---|---|
| ① 业务层 | `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_clock` `sys_events` `app_cfg` `task/*` `lftpd` `time_sync` |
| ④ 中间件层 | `Drivers/BSP/NET/`、`Lib/`、`App/util/` | 通用库 / 协议栈 | `net_socket` `net_select` `FatFs` `nand_ftl(dhara)` `LVGL` `util(crc·ringbuf)` |
| ③ 基础服务层 | `App/`、`Drivers/BSP/NET/lftpd/` | 应用侧服务与编排 | `app_main` `sys_events` `app_cfg` `task/*` `lftpd` `time_sync` |
| ④ 中间件层 | `Drivers/BSP/NET/`、`Lib/`、`App/util/` | 通用库 / 协议栈 | `net_socket` `net_select` `FatFs` `nand_ftl(dhara)` `LVGL` `sys_clock` `util(crc·ringbuf)` |
| ⑤ 驱动层 | `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 / UART5 / USART1 / DMA / GPIO / TIM7 |
@@ -132,7 +132,7 @@ flowchart LR
CHIP[("GD5F2GQ5UE<br/>NAND 2Gbit")]
subgraph USERS["使用方"]
NC["net_cfg<br/>/cfg/netcfg.bin"]
NC["net_cfg<br/>/config.ini(INI [network] 段)"]
LF["lftpd<br/>FTP 文件读写"]
ST["测试套件<br/>STORAGE/GD5F"]
end
@@ -147,7 +147,7 @@ flowchart LR
end
```
- 掉电持久化:`net_cfg` 配置(magic `'NCFG'` + ver + CRC-16/MODBUS),**保存后下次上电生效**。
- 掉电持久化:`net_cfg` 配置(通用配置文件 `/config.ini`,INI `[network]` 段,cfg_file 引擎),保存后下次上电生效、或立即生效(网络重建)、外部变更 4s 沉降后自动重载;旧版 NCFG 二进制(`/cfg/netcfg.bin`)首启自动迁移。
- NAND 坏块表(BBT)持久化在保留块池,不交给 dhara 管理。
---
@@ -227,12 +227,11 @@ STM32F4-Base/
│ └── main.c 入口 → app_main_init → osKernelStart
├── App/ 应用层(业务编排,非 CubeMX)
│ ├── app_main.c 启动编排(时间子系统优先)
│ ├── sys_clock.c 墙钟基准 / Unix epoch
│ ├── time_sync.c TCP 对时服务
│ ├── sys_events.h 就绪屏障 bit 定义
│ ├── app_cfg.h 功能编译开关(APP_NET/ADC/RS485_ENABLE)
│ ├── task/ net_task / ui_task / adc_task* / rs485_task*
│ └── util/ crc(CRC-16/8)、ringbuf
│ └── util/ crc(CRC-16/8)、ringbuf、sys_clock(墙钟)
├── Drivers/BSP/ 纯手工驱动/服务(不受 CubeMX 保护)
│ ├── CH395F/ 以太网 MAC+PHY
│ ├── GD5F2GQ5UE/ NAND + dhara FTL
@@ -240,7 +239,7 @@ STM32F4-Base/
│ ├── SD2506/ RTC
│ ├── RS485/ 半双工串口
│ ├── NET/ net_socket / net_select / lftpd
│ ├── NETCFG/ 网口配置持久化(MCU + 模拟器双后端)
│ ├── NETCFG/ 网口配置(INI [network] 段 + 旧档 NCFG 迁移)
│ ├── UI/ LVGL UI(与模拟器共用)
│ └── dbg_log.h/dbg_cfg.h 分级日志
├── Lib/ 第三方:LVGL v9.2、FatFs、FreeRTOS、CMSIS
@@ -259,7 +258,7 @@ STM32F4-Base/
```mermaid
flowchart LR
SRC["共享源码<br/>Drivers/BSP/UI/*<br/>Drivers/BSP/NETCFG/net_cfg.c"]
SRC["共享源码<br/>Drivers/BSP/UI/*<br/>Drivers/BSP/NETCFG/net_cfg.c<br/>App/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:串口采集 + 烧录 + 日志校验"]
@@ -305,3 +304,5 @@ flowchart LR
| 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_file` 引擎 + `cfg_file_store` 双端后端);`net_cfg` 对外 API 不变;旧档首启自动迁移;`cfg_file` 列为 MCU/模拟器共享源码 |
+4 -4
View File
@@ -385,13 +385,13 @@ pre-commit run --all-files
| 参数 | 工厂默认 | 修改方式 |
|---|---|---|
| IP | **192.168.1.100**(静态,无 DHCP) | 屏幕 `通讯参数 → 网口设置`(修改密码 `123`)保存后**立即生效**(网络重建约 1~3 s);或预置 `/cfg/netcfg.bin` |
| IP | **192.168.1.100**(静态,无 DHCP) | 屏幕 `通讯参数 → 网口设置`(修改密码 `123`)保存后**立即生效**(网络重建约 1~3 s);或预置 `/config.ini`(INI;旧版 `/cfg/netcfg.bin` 首启自动迁移) |
| 子网掩码 | 255.255.255.0(/24) | 同上 |
| 默认网关 | 192.168.1.1 | 同上 |
| MAC | 全零 = 芯片内置 MAC | 同上(全零是"使用芯片内置 MAC"的哨兵,不是"MAC 为 0") |
| PHY | 强制 100M 全双工 | 固件固定,不可配 |
- 配置持久化:NAND 上 FatFS 文件 `/cfg/netcfg.bin`(magic `'NCFG'` + CRC-16/MODBUS;缺失/损坏回退工厂默认,不带崩网络)
- 配置持久化:NAND 上通用配置文件 `/config.ini`(INI `[network]` 段;缺失/损坏回退工厂默认并重建模板,不带崩网络)
- 立即生效机制:保存 → netTask 重建网络 → 代次号递增 → 三个 TCP 服务自动重建监听(**既有连接会断开**,属预期)
### 4.2 服务端口(现场连通性核对)
@@ -406,7 +406,7 @@ pre-commit run --all-files
### 4.3 存储与运行时
- 文件系统:FatFS(FAT32)挂 NAND;**上电无卷自动格式化**(`f_mkfs`)——格式化前 NAND 上无数据(含网口配置)
- 文件系统:FatFS(FAT32)挂 NAND;**上电无卷自动格式化**(`f_mkfs`);格式化会清除卷上全部文件(含网口配置 `/config.ini`)
- RTC:SD2506 芯片(驱动 `Drivers/BSP/SD2506/`)提供墙钟基准与日志时间戳;首次使用建议先经 8888 对时
- 启动时序:`app_main_init`(存储 → 时钟 → RS485 → NAND → ADC → 启动转换)→ 就绪屏障(STORAGE / NET / FTP / TIMESYNC 事件)→ 六任务运行;详见 `docs/项目启动顺序管理.md`、`docs/项目架构总览.md`
@@ -420,7 +420,7 @@ pre-commit run --all-files
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
| 5 | UI 功能 | 屏幕 / 远端操作 | 菜单 7 项导航、内容页、密码(查看 `000` / 修改 `123`)、网口设置保存后立即生效 |
| 6 | 对时 | PC 推送 8888 | 设备墙钟更新、日志时间戳正确 |
| 7 | 掉电保持 | 写文件 / 改网口配置 → 重启 | 数据仍在(`/cfg/netcfg.bin` 按新值生效) |
| 7 | 掉电保持 | 写文件 / 改网口配置 → 重启 | 数据仍在(`/config.ini` 按新值生效) |
| 8 | LED 状态 | 目检 | 6 路 LED 随系统状态变化(低电平点亮) |
---