通用配置文件 P1–P3:设计文档、FS 并发 A+ 定案、FatFs 墙钟接入、cfg_file 双端引擎与 net_cfg 集成(旧档 NCFG 首启迁移)
This commit is contained in:
@@ -41,7 +41,7 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
- **app_main_init 顺序**(时间子系统优先,使后续日志带时间戳):`sd2506_init` → `sys_clock_init`(墙钟基准)→ `rs485_init` → `gd5f2gq5ue_init` → `tpafe5160_init` → SD2506 诊断 → `tpafe5160_start_conv_irq`
|
||||
- **就绪屏障**(`App/sys_events.h` Event Flags,取代魔法延时/全局轮询):`SYS_EVT_NET_READY`(bit0, netTask 置位) / `SYS_EVT_STORAGE_READY`(bit1, defaultTask 置位) / `SYS_EVT_FTP_READY`(bit2) / `SYS_EVT_TIMESYNC_READY`(bit3);消费者用 `osEventFlagsWait` 阻塞等待
|
||||
- **defaultTask**:FatFS 挂载(`f_mount` forced,无卷自动 `f_mkfs` FAT32)→ 置 `SYS_EVT_STORAGE_READY` → LED 闪烁循环
|
||||
- **netTask**:等 `SYS_EVT_STORAGE_READY`(≤10s)→ `net_stack_bring_up()`(`net_cfg_load` 读 Flash 网口配置 → `net_init(ip,mask,gw,mac)` + PHY 强制 100M 全双工 + 等链路 ≤3s → 回写芯片实际 MAC 影子 → 递增网络代次号)→ 置 `SYS_EVT_NET_READY` → 循环 `net_poll()` + `net_process_messages()` + `osDelay(1)`(tick=1ms);循环内消费 `SYS_EVT_NET_RECONFIG`(UI 保存网口配置后请求)→ 立即重建网络(新 IP 生效,无需重启;各 TCP 服务据代次号重建监听)
|
||||
- **netTask**:等 `SYS_EVT_STORAGE_READY`(≤10s)→ `net_stack_bring_up()`(`net_cfg_load` 读网口配置(`/config.ini` [network] 段,首启自动迁旧档 NCFG)→ `net_init(ip,mask,gw,mac)` + PHY 强制 100M 全双工 + 等链路 ≤3s → 回写芯片实际 MAC 影子 → 递增网络代次号)→ 置 `SYS_EVT_NET_READY` → 循环 `net_poll()` + `net_process_messages()` + `osDelay(1)`(tick=1ms);循环内消费 `SYS_EVT_NET_RECONFIG`(UI 保存网口配置后请求)→ 立即重建网络(新 IP 生效,无需重启;各 TCP 服务据代次号重建监听)
|
||||
- **ftpTask**:`osEventFlagsWait(NET_READY | STORAGE_READY, WaitAll)` 后 `lftpd_start("/", 21, &ftp)`(已无 7s 魔法延时)
|
||||
- **testTask**:测试套件入口(`test/test_config.h` 选择;产品构建下空转)
|
||||
- **uiTask**:等 `SYS_EVT_NET_READY` 后 `ui_port_init` → `ui_menu_init` → 启动 UI net sink
|
||||
@@ -71,7 +71,7 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
- `net_select.h/c` — select/poll I/O 多路复用
|
||||
- 线程安全通过消息队列(`netMsgQueue`)实现,所有 CH395F 操作在 netTask 中串行处理
|
||||
- `net_init` 顺序:check_exist → reset → `set_fun_para(0x09)` → MSS/重传/KeepAlive → IP → `ch395f_init()` → `ch395f_set_mac_addr()`(mac 非空且非全零时)→ Socket 0~7 缓冲分配 → ping 使能
|
||||
- 网口参数(IP/掩码/网关/MAC)取自 Flash 配置(`Drivers/BSP/NETCFG/`:magic 'NCFG' + ver + CRC-16/MODBUS;工厂默认 192.168.1.100/24、gw .1,MAC 全零 = 芯片内置),保存后**下次上电生效**;PHY 强制 100M 全双工
|
||||
- 网口参数(IP/掩码/网关/MAC)取自通用配置文件 `/config.ini` 的 [network] 段(`Drivers/BSP/NETCFG/` + `App/cfg_file` 引擎:工厂默认 192.168.1.100/24、gw .1,MAC 全零 = 芯片内置),保存后**立即生效**(网络重建)或下次上电生效;旧版 `/cfg/netcfg.bin`(NCFG)首启自动迁移;PHY 强制 100M 全双工
|
||||
- **新文件需手动添加到 Keil MDK 工程**才能编译
|
||||
|
||||
## 测试构建模式
|
||||
@@ -92,7 +92,8 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
| `App/task/net_task.c` | netTask 主循环(网络初始化 + 轮询) |
|
||||
| `test/test_config.h` | 测试套件选择(互斥开关) |
|
||||
| `Drivers/BSP/NET/` | BSD Socket API 网络抽象层 + lftpd |
|
||||
| `Drivers/BSP/NETCFG/` | 网口配置(Flash 持久化 + CRC,网口设置内容页数据源) |
|
||||
| `Drivers/BSP/NETCFG/` | 网口配置(通用配置文件 `/config.ini` [network] 段 + 旧档 NCFG 迁移;网口设置内容页数据源) |
|
||||
| `App/cfg_file.*` + `App/cfg_file_store*` | 通用配置文件引擎(双端共享核心 + 平台存储后端:INI 解析/校验/外科保存/模板/沉降轮询) |
|
||||
| `Drivers/BSP/CH395F/` | CH395F SPI 驱动 |
|
||||
| `Drivers/BSP/GD5F2GQ5UE/` | NAND Flash 驱动 + dhara FTL + FatFS diskio |
|
||||
| `Drivers/BSP/TPAFE5160/` | ADC 并行接口驱动 |
|
||||
@@ -104,6 +105,7 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
|
||||
详见 `docs/` 目录:
|
||||
- `docs/项目架构总览.md` — 六层全景架构(分层/任务/启动时序/存储·网络·UI 栈)
|
||||
- `docs/配置文件设计说明.md` — 通用配置文件(INI `/config.ini`:设计决策 D1–D15 / 双端支持 / 阶段规划 P1–P8 / 测试与风险)
|
||||
- `docs/网络配置说明.md` — 网口设置页配置(UI 可改的 IP/掩码/网关/MAC:持久化 / 立即生效 / 页面操作)
|
||||
- `docs/问题记录/CH395F_Trap_Records.md` — CH395F 已知陷阱
|
||||
- `docs/问题记录/GD5F2GQ5UE_Trap_Records.md` — GD5F2GQ5UE 已知陷阱
|
||||
|
||||
+1192
File diff suppressed because it is too large
Load Diff
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 模块名称:cfg_file
|
||||
* 模块功能:通用配置文件引擎(双端共享核心)公共接口——INI 段/键表解析校验、
|
||||
* 外科手术式保存(保留注释/未知内容,缺失段/键写回)、出厂模板生成、
|
||||
* 沉降去抖轮询与分段变更回调;平台文件操作经 cfg_file_store 后端
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-14
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成)
|
||||
*/
|
||||
|
||||
#ifndef CFG_FILE_H
|
||||
#define CFG_FILE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* 配置文件长度上限(字节);超过 = 文件损坏,按出厂模板重建 */
|
||||
#define CFG_FILE_MAX_LEN 4096u
|
||||
|
||||
/* 引擎可注册段数上限 */
|
||||
#define CFG_FILE_MAX_SECTIONS 8u
|
||||
|
||||
/* 单段键数上限 */
|
||||
#define CFG_FILE_MAX_KEYS 16u
|
||||
|
||||
/* 沉降去抖:变更基线连续 N 次轮询不变才重载(轮询周期 2s → 4s 沉降) */
|
||||
#define CFG_FILE_SETTLE_CNT 2u
|
||||
|
||||
/*
|
||||
* 键值类型
|
||||
* UINT:无符号十进制;len = 目标字段字节数(1/2/4,小端)
|
||||
* IP4 :点分 IPv4 "a.b.c.d";字段 uint8_t[4]
|
||||
* MAC :十六进制 MAC "xx:xx:xx:xx:xx:xx"(分隔符兼容 - 与 .);字段 uint8_t[6]
|
||||
* STR :ASCII 字符串;字段 char[len](len = 缓冲长度,自动补 NUL)
|
||||
*/
|
||||
typedef enum {
|
||||
CFG_VAL_UINT = 0,
|
||||
CFG_VAL_IP4,
|
||||
CFG_VAL_MAC,
|
||||
CFG_VAL_STR
|
||||
} cfg_val_type_t;
|
||||
|
||||
/* 键定义(offset/len 均相对段 RAM 副本起始) */
|
||||
typedef struct {
|
||||
const char *name; /* 键名(小写) */
|
||||
cfg_val_type_t type; /* 值类型 */
|
||||
size_t offset; /* 字段偏移 */
|
||||
size_t len; /* 字段长度 */
|
||||
} cfg_key_def_t;
|
||||
|
||||
/* 段注册描述符 */
|
||||
typedef struct {
|
||||
const char *name; /* 段名(≤ 15 字符) */
|
||||
void *p_data; /* 段 RAM 副本(模块自有静态区,引擎原地更新) */
|
||||
const void *p_defaults; /* 工厂默认值(与 p_data 同布局;load 先复位再解析) */
|
||||
size_t data_size; /* RAM 副本字节数 */
|
||||
const cfg_key_def_t *p_keys; /* 键表 */
|
||||
uint8_t key_count; /* 键数(1 ~ CFG_FILE_MAX_KEYS) */
|
||||
void (*p_cb)(void); /* 变更回调(可空;poll 重载后该段有差异时调用;
|
||||
注册段 data_size 须 ≤ 64 字节方可挂回调) */
|
||||
} cfg_section_reg_t;
|
||||
|
||||
/*
|
||||
* 函数功能:注册配置段(引擎建立"段 → RAM 副本 + 键表"映射)
|
||||
* 入口参数:p_reg - 段注册描述符(不可为 NULL) const cfg_section_reg_t *
|
||||
* 返回值:0 - 成功;-1 - 参数非法/段名重复/段数超限 int
|
||||
* 限定条件:启动早期调用一次(重复注册同段名拒绝);
|
||||
* 注册后 p_data/p_defaults/p_keys 生命周期须覆盖引擎使用期
|
||||
*/
|
||||
int cfg_file_register(const cfg_section_reg_t *p_reg);
|
||||
|
||||
/*
|
||||
* 函数功能:从配置文件加载全部段(文件 = 配置唯一来源)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 正常(含回退工厂默认);-1 - 严重错误(RAM 已置工厂默认,仍安全) int
|
||||
* 限定条件:至少注册一个段
|
||||
* 函数说明: 1. 各段先复位工厂默认(文件缺失/损坏的键 = 工厂默认)
|
||||
* 2. 文件缺失 → 按当前 RAM 副本生成出厂模板写盘(首启建文件)
|
||||
* 3. 文件存在 → 解析:有效键写入 RAM 副本;无效键留工厂默认并留痕
|
||||
* 4. 文件超长(> CFG_FILE_MAX_LEN)= 损坏 → 工厂默认 + 重建模板
|
||||
*/
|
||||
int cfg_file_load(void);
|
||||
|
||||
/*
|
||||
* 函数功能:保存全部段到配置文件(外科手术式)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功;-1 - 写失败(原文件保持不动) int
|
||||
* 限定条件:至少注册一个段
|
||||
* 函数说明:文件存在 → 逐行保留注释/未知段/未知键,受管段/键按 RAM 副本
|
||||
* 值原位改写,缺失段/键补写;文件缺失/损坏 → 按 RAM 副本重建
|
||||
* 完整文档(含文件头注释);写盘 = 临时文件 + 原子改名
|
||||
*/
|
||||
int cfg_file_save(void);
|
||||
|
||||
/*
|
||||
* 函数功能:配置文件变更轮询(沉降去抖 + 重载 + 分段变更回调)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:由单一任务周期调用(建议 2s,收敛于 netTask)
|
||||
* 函数说明:(size, mtime 变更令牌) 相对基线变化且连续 CFG_FILE_SETTLE_CNT
|
||||
* 次轮询稳定 → 重载全部段(各段复位工厂默认后解析);挂回调段
|
||||
* 有差异 → 调用其回调(如触发网络重建);重载后刷新基线
|
||||
* (回调触发的写盘不引发二次重载)
|
||||
*/
|
||||
void cfg_file_poll(void);
|
||||
|
||||
/*
|
||||
* 函数功能:取段 RAM 副本指针
|
||||
* 入口参数:p_name - 段名 const char *
|
||||
* 返回值:段 RAM 副本指针;未注册返回 NULL void *
|
||||
* 限定条件:无
|
||||
*/
|
||||
void *cfg_file_section_data(const char *p_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CFG_FILE_H */
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 模块名称:cfg_file_store
|
||||
* 模块功能:cfg_file 平台存储后端接口——带路径的文件操作抽象;
|
||||
* MCU 实现 = NAND 上 FatFS(cfg_file_store_mcu.c),
|
||||
* 模拟器实现 = 宿主文件系统(cfg_file_store_sim.c)
|
||||
* 适用平台:双端接口(实现分居两端)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-14
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-14 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
#ifndef CFG_FILE_STORE_H
|
||||
#define CFG_FILE_STORE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* 文件变更基线:size + mtime 变更令牌
|
||||
* mtime 仅作等值比较(不等 = 可能变更):
|
||||
* MCU 端 = FAT 打包时间(fdate<<16 | fmtime,分钟级粒度)
|
||||
* 模拟器 = 宿主文件 mtime(秒级)
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t size;
|
||||
uint32_t mtime;
|
||||
} cfg_file_stat_t;
|
||||
|
||||
/*
|
||||
* 函数功能:读文件(最多 cap 字节)
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* cap - 缓冲容量 size_t
|
||||
* p_nread- 实际读字节数输出(不可为 NULL) size_t *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:调用方保证 p_buf 容量 ≥ 所需长度(引擎读上限 CFG_FILE_MAX_LEN)
|
||||
*/
|
||||
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread);
|
||||
|
||||
/*
|
||||
* 函数功能:原子写文件(先写临时文件,成功后改名覆盖目标)
|
||||
* 入口参数:p_path - 目标文件路径 const char *
|
||||
* p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
|
||||
* 限定条件:中途失败不得损坏目标文件(掉电安全)
|
||||
*/
|
||||
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len);
|
||||
|
||||
/*
|
||||
* 函数功能:文件状态(size + mtime 变更令牌)
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* p_st - 输出(不可为 NULL) cfg_file_stat_t *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st);
|
||||
|
||||
/*
|
||||
* 函数功能:删除文件
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/删除失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_remove(const char *p_path);
|
||||
|
||||
/* 函数功能:取配置文件路径(如 MCU "/config.ini"、模拟器 "sim_config.ini") */
|
||||
const char *cfg_store_config_path(void);
|
||||
|
||||
/* 函数功能:取配置文件临时文件路径(write_atomic 内部用) */
|
||||
const char *cfg_store_tmp_path(void);
|
||||
|
||||
/* 函数功能:取旧版配置遗留文件路径(net_cfg_legacy 迁移用) */
|
||||
const char *cfg_store_legacy_path(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CFG_FILE_STORE_H */
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 模块名称:cfg_file_store (MCU 后端)
|
||||
* 模块功能:cfg_file 存储后端——NAND 上 FatFS 文件 /config.ini(卷根,8.3 合规);
|
||||
* 原子写 = 临时文件 /config.TMP + f_rename;mtime 变更令牌 =
|
||||
* (fdate << 16) | fmtime;旧版遗留文件 /cfg/netcfg.bin 供迁移读取
|
||||
* 适用平台:STM32F407ZGTx
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-14
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-14 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg_file_store.h"
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static const char s_path[] = "/config.ini";
|
||||
static const char s_path_tmp[] = "/config.TMP";
|
||||
static const char s_path_legacy[] = "/cfg/netcfg.bin";
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:读文件(最多 cap 字节)
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* cap - 缓冲容量 size_t
|
||||
* p_nread - 实际读字节数输出(不可为 NULL) size_t *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:卷已挂载(未挂载时 f_open 返回 FR_NO_SYSTEM,转 -1)
|
||||
*/
|
||||
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
|
||||
FIL f;
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
|
||||
if (p_buf == NULL || p_nread == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_open(&f, p_path, FA_READ);
|
||||
if (fr != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_read(&f, p_buf, (UINT)cap, &br);
|
||||
(void)f_close(&f);
|
||||
if (fr != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
*p_nread = (size_t)br;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:原子写文件(先写 /config.TMP,成功后 f_rename 覆盖目标)
|
||||
* 入口参数:p_path - 目标文件路径 const char *
|
||||
* p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
|
||||
* 限定条件:卷已挂载;中途失败(含掉电)不损坏目标文件
|
||||
*/
|
||||
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
|
||||
FIL f;
|
||||
FRESULT fr;
|
||||
UINT bw = 0u;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_open(&f, s_path_tmp, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (fr != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_write(&f, (const void *)p_buf, (UINT)len, &bw);
|
||||
(void)f_close(&f);
|
||||
if (fr != FR_OK || bw != (UINT)len) {
|
||||
(void)f_unlink(s_path_tmp);
|
||||
return -1;
|
||||
}
|
||||
fr = f_rename(s_path_tmp, p_path);
|
||||
if (fr != FR_OK) {
|
||||
(void)f_unlink(s_path_tmp);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:文件状态(size + mtime 变更令牌)
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* p_st - 输出(不可为 NULL) cfg_file_stat_t *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:无
|
||||
* 函数说明:FAT 时间分钟级粒度——同分钟内同长度重写不可检出(可接受,
|
||||
* 见《配置文件设计说明》§6.2 mtime 令牌说明)
|
||||
*/
|
||||
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
|
||||
FILINFO fno;
|
||||
FRESULT fr;
|
||||
|
||||
if (p_st == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_stat(p_path, &fno);
|
||||
if (fr != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
p_st->size = (uint32_t)fno.fsize;
|
||||
p_st->mtime = (uint32_t)(((uint32_t)fno.fdate << 16) | (uint32_t)fno.ftime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:删除文件
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/删除失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_remove(const char *p_path) {
|
||||
if (f_unlink(p_path) != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取配置文件路径
|
||||
* 入口参数:无
|
||||
* 返回值:"/config.ini"(卷根,8.3 合规:CONFIG + INI) const char *
|
||||
* 限定条件:无
|
||||
*/
|
||||
const char *cfg_store_config_path(void) {
|
||||
return s_path;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取配置文件临时文件路径
|
||||
* 入口参数:无
|
||||
* 返回值:"/config.TMP"(卷根,8.3 合规:CONFIG + TMP) const char *
|
||||
* 限定条件:无
|
||||
*/
|
||||
const char *cfg_store_tmp_path(void) {
|
||||
return s_path_tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取旧版配置遗留文件路径
|
||||
* 入口参数:无
|
||||
* 返回值:"/cfg/netcfg.bin"(旧 NCFG 二进制格式) const char *
|
||||
* 限定条件:无
|
||||
*/
|
||||
const char *cfg_store_legacy_path(void) {
|
||||
return s_path_legacy;
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
* 创建日期:2026-07-19
|
||||
* 修改记录:
|
||||
* v1.0 2026-07-19 王建锋 创建初始版本
|
||||
* v1.1 2026-09-13 王建锋 新增 sys_clock_get_time()(日历时间获取器,供 FatFs 时间钩子等使用)
|
||||
* v1.2 2026-09-13 王建锋 文件移入 App/util/(改归 ④中间件层:通用平台时间服务,多服务共用)
|
||||
*/
|
||||
|
||||
#include "sys_clock.h"
|
||||
@@ -323,6 +325,29 @@ char *sys_clock_get_str(char *buf, size_t buf_size) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获取当前系统墙钟时间(日历时间)
|
||||
* 入口参数:p_time - 输出时间结构指针(非 NULL)
|
||||
* 出口参数:p_time - 填充年/月/日/时/分/秒/星期
|
||||
* 返回值:0 - 成功, -1 - 失败(墙钟未初始化或参数无效)
|
||||
* 限定条件:sys_clock_init() 已调用
|
||||
* 函数说明:1. 取当前 Unix 秒数后经 unix_to_sd2506() 换算为日历时间
|
||||
* 2. 与调试日志时间戳同一时间源(开机 RTC 基准 + tick 推进,运行期零 I2C)
|
||||
*/
|
||||
int sys_clock_get_time(sd2506_time_t *p_time) {
|
||||
uint32_t unix_secs = sys_clock_get();
|
||||
|
||||
if (p_time == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (unix_secs == UINT32_MAX) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
unix_to_sd2506(unix_secs, p_time);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:设置(更新)系统墙钟时间基准,并可写回 SD2506 RTC
|
||||
* 入口参数:unix_secs - 新的 Unix 时间戳 uint32_t 0~0x7FFFFFFF
|
||||
@@ -7,6 +7,8 @@
|
||||
* 创建日期:2026-07-19
|
||||
* 修改记录:
|
||||
* v1.0 2026-07-19 王建锋 创建初始版本
|
||||
* v1.1 2026-09-13 王建锋 新增 sys_clock_get_time()(日历时间获取器,供 FatFs 时间钩子等使用)
|
||||
* v1.2 2026-09-13 王建锋 文件移入 App/util/(改归 ④中间件层:通用平台时间服务,多服务共用)
|
||||
*/
|
||||
|
||||
#ifndef SYS_CLOCK_H
|
||||
@@ -48,6 +50,17 @@ uint32_t sys_clock_get(void);
|
||||
*/
|
||||
char *sys_clock_get_str(char *buf, size_t buf_size);
|
||||
|
||||
/*
|
||||
* 函数功能:获取当前系统墙钟时间(日历时间)
|
||||
* 入口参数:p_time - 输出时间结构指针(非 NULL)
|
||||
* 出口参数:p_time - 填充年/月/日/时/分/秒/星期
|
||||
* 返回值:0 - 成功, -1 - 失败(墙钟未初始化或参数无效)
|
||||
* 限定条件:sys_clock_init() 已调用
|
||||
* 函数说明:等价于 "sys_clock_get() + 内部日历换算",为需要日历字段的
|
||||
* 使用方(如 FatFs 时间钩子)提供类型化接口
|
||||
*/
|
||||
int sys_clock_get_time(sd2506_time_t *p_time);
|
||||
|
||||
/*
|
||||
* 函数功能:设置系统墙钟时间
|
||||
* 入口参数:unix_secs - Unix epoch time (秒)
|
||||
@@ -1,13 +1,15 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration Core (配置核心)
|
||||
* 模块功能:RAM 配置副本 + 工厂默认 + 加载/保存编排 + 实际 MAC 影子 +
|
||||
* IPv4 字符串转换;磁盘格式 magic 'NCFG' + ver + CRC-16(MODBUS)
|
||||
* + 18B 字段(两端小端一致,sizeof 含尾部对齐填充)
|
||||
* IPv4 字符串转换;存储经 cfg_file 通用配置文件引擎(INI [network]
|
||||
* 段:ip/mask/gw/mac);旧版 NCFG 二进制格式归 net_cfg_legacy
|
||||
* (首启一次性迁移)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-10 王建锋 创建初始版本
|
||||
* v1.1 2026-09-14 王建锋 存储迁移至 cfg_file 引擎(INI);旧 NCFG 代码移至 net_cfg_legacy
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -19,33 +21,21 @@
|
||||
|
||||
#include "net_cfg.h"
|
||||
|
||||
#include "net_cfg_store.h"
|
||||
#include "net_cfg_legacy.h"
|
||||
|
||||
#include "cfg_file.h"
|
||||
|
||||
#define DBG_TAG "[NET_CFG]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* 私有宏定义区 */
|
||||
|
||||
#define NET_CFG_MAGIC 0x4647434Eu /* 'NCFG'(文件字节序 N,C,G,F;小端装载) */
|
||||
#define NET_CFG_VER 1u /* 磁盘格式版本 */
|
||||
|
||||
/* 工厂默认值(单一来源:s_cfg 初值与 s_cfg_defaults 共用,避免两处漂移) */
|
||||
/* 工厂默认值(单一来源:s_cfg 初值与 s_defaults 共用,避免两处漂移) */
|
||||
#define NET_CFG_DEF_IP { 192u, 168u, 1u, 100u }
|
||||
#define NET_CFG_DEF_MASK { 255u, 255u, 255u, 0u }
|
||||
#define NET_CFG_DEF_GW { 192u, 168u, 1u, 1u }
|
||||
#define NET_CFG_DEF_MAC { 0u, 0u, 0u, 0u, 0u, 0u }
|
||||
|
||||
/* 磁盘格式(两端布局一致:u32 4 对齐;字段区 18B 连续,CRC 覆盖之) */
|
||||
typedef struct {
|
||||
uint32_t magic; /* NET_CFG_MAGIC */
|
||||
uint16_t ver; /* 格式版本 */
|
||||
uint16_t crc; /* CRC-16/MODBUS(覆盖 ip..mac 共 18B) */
|
||||
uint8_t ip[4];
|
||||
uint8_t mask[4];
|
||||
uint8_t gw[4];
|
||||
uint8_t mac[6];
|
||||
} net_cfg_blob_t;
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
/* RAM 配置副本:以工厂默认值静态初始化(落 .data),自启动即有效——
|
||||
@@ -60,51 +50,62 @@ static uint8_t s_active_mac[6]; /* 芯片实际 MAC 影子(netTask 回写
|
||||
static uint8_t s_active_valid; /* 影子有效标志(0 = 尚未回写) */
|
||||
static net_cfg_apply_cb_t s_apply_cb; /* "立即应用"平台回调(未注册 = 空操作) */
|
||||
|
||||
/* 工厂默认(引擎注册 p_defaults 指向;与 s_cfg 初值共用宏单一来源) */
|
||||
static const net_cfg_t s_defaults = {
|
||||
.ip = NET_CFG_DEF_IP,
|
||||
.mask = NET_CFG_DEF_MASK,
|
||||
.gw = NET_CFG_DEF_GW,
|
||||
.mac = NET_CFG_DEF_MAC,
|
||||
};
|
||||
|
||||
/* [network] 段键表(net_cfg_t 布局内偏移) */
|
||||
static const cfg_key_def_t s_net_keys[] = {
|
||||
{ "ip", CFG_VAL_IP4, offsetof(net_cfg_t, ip), 4u },
|
||||
{ "mask", CFG_VAL_IP4, offsetof(net_cfg_t, mask), 4u },
|
||||
{ "gw", CFG_VAL_IP4, offsetof(net_cfg_t, gw), 4u },
|
||||
{ "mac", CFG_VAL_MAC, offsetof(net_cfg_t, mac), 6u },
|
||||
};
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:CRC-16/MODBUS(无查表逐位算法)
|
||||
* 入口参数:p_data - 数据指针 const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:CRC-16 值 uint16_t
|
||||
* 限定条件:p_data 非空(len 可为 0,返回初始值 0xFFFF)
|
||||
* 函数说明:文件私有(双端共享、避免跨模块依赖:模拟器不编译 App/util;
|
||||
* 数据量 ≤32B,性能无关)
|
||||
* 函数功能:[network] 段变更回调(poll 重载检出差异时由引擎调用)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无(未注册"立即应用"回调时空操作)
|
||||
* 函数说明:转平台回调(MCU:netTask 置 SYS_EVT_NET_RECONFIG 重建网络)
|
||||
*/
|
||||
static uint16_t s_crc16_modbus(const uint8_t *p_data, size_t len) {
|
||||
uint16_t crc = 0xFFFFu;
|
||||
|
||||
for (size_t i = 0u; i < len; i++) {
|
||||
crc = (uint16_t)(crc ^ (uint16_t)p_data[i]);
|
||||
for (int b = 0; b < 8; b++) {
|
||||
if (crc & 0x0001u) {
|
||||
crc = (uint16_t)((crc >> 1) ^ 0xA001u);
|
||||
}
|
||||
else {
|
||||
crc = (uint16_t)(crc >> 1);
|
||||
}
|
||||
}
|
||||
static void s_net_changed_cb(void) {
|
||||
if (s_apply_cb != NULL) {
|
||||
s_apply_cb();
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:填充工厂默认配置
|
||||
* 入口参数:p_cfg - 输出结构指针(不可为 NULL) net_cfg_t *
|
||||
* 函数功能:向 cfg_file 引擎注册 [network] 段(幂等)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:p_cfg 非空
|
||||
* 函数说明:与既有硬编码组网值一致(192.168.1.100/24,gw .1,MAC 全零
|
||||
* = 芯片内置),保证首启/存储异常时行为与旧版本相同
|
||||
* 限定条件:load/save 前调用
|
||||
* 函数说明:段数据 = s_cfg RAM 副本;工厂默认 = s_defaults;
|
||||
* 变更回调触发"立即应用"
|
||||
*/
|
||||
static void s_cfg_defaults(net_cfg_t *p_cfg) {
|
||||
const net_cfg_t def = {
|
||||
.ip = NET_CFG_DEF_IP,
|
||||
.mask = NET_CFG_DEF_MASK,
|
||||
.gw = NET_CFG_DEF_GW,
|
||||
.mac = NET_CFG_DEF_MAC,
|
||||
};
|
||||
static void s_net_register(void) {
|
||||
static uint8_t s_done;
|
||||
cfg_section_reg_t reg;
|
||||
|
||||
*p_cfg = def;
|
||||
if (s_done != 0u) {
|
||||
return;
|
||||
}
|
||||
(void)memset(®, 0, sizeof(reg));
|
||||
reg.name = "network";
|
||||
reg.p_data = &s_cfg;
|
||||
reg.p_defaults = &s_defaults;
|
||||
reg.data_size = sizeof(net_cfg_t);
|
||||
reg.p_keys = s_net_keys;
|
||||
reg.key_count = 4u;
|
||||
reg.p_cb = s_net_changed_cb;
|
||||
(void)cfg_file_register(®);
|
||||
s_done = 1u;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -130,27 +131,16 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
|
||||
* 函数说明:见 net_cfg.h 同名函数说明
|
||||
*/
|
||||
int net_cfg_load(void) {
|
||||
net_cfg_blob_t blob;
|
||||
s_net_register();
|
||||
|
||||
if (net_cfg_store_read((uint8_t *)&blob, sizeof(blob)) != 0) {
|
||||
/* 文件缺失/不可读(首启、存储未就绪):工厂默认 */
|
||||
s_cfg_defaults(&s_cfg);
|
||||
DBG_INFO("load: no valid file, factory defaults");
|
||||
/* 首启先迁旧档 NCFG(有效 → 旧值物化进新文件);新文件已存在则跳过 */
|
||||
(void)net_cfg_legacy_migrate();
|
||||
|
||||
if (cfg_file_load() != 0) {
|
||||
/* 严重错误:引擎已将 RAM 副本复位工厂默认,行为仍安全 */
|
||||
DBG_ERROR("load: cfg_file_load FAIL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (blob.magic != NET_CFG_MAGIC || blob.ver != NET_CFG_VER
|
||||
|| s_crc16_modbus(blob.ip, 18u) != blob.crc) {
|
||||
/* 格式损坏:工厂默认并留痕(不让坏配置带崩网络) */
|
||||
s_cfg_defaults(&s_cfg);
|
||||
DBG_ERROR("load: blob invalid (magic/ver/crc), factory defaults");
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(s_cfg.ip, blob.ip, 4u);
|
||||
memcpy(s_cfg.mask, blob.mask, 4u);
|
||||
memcpy(s_cfg.gw, blob.gw, 4u);
|
||||
memcpy(s_cfg.mac, blob.mac, 6u);
|
||||
DBG_INFO("load: OK ip=%u.%u.%u.%u",
|
||||
(unsigned)s_cfg.ip[0], (unsigned)s_cfg.ip[1],
|
||||
(unsigned)s_cfg.ip[2], (unsigned)s_cfg.ip[3]);
|
||||
@@ -162,24 +152,20 @@ int net_cfg_load(void) {
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功;-1 - 写失败 int
|
||||
* 限定条件:MCU 端应在存储就绪后调用
|
||||
* 函数说明:组装磁盘格式经后端写盘;下次上电生效
|
||||
* 函数说明:经 cfg_file 引擎写盘(外科手术式保存:保留注释/未知内容,
|
||||
* 临时文件 + 原子改名);下次上电(netTask 在 net_init 前加载)
|
||||
* 或 poll 4s 沉降后生效
|
||||
*/
|
||||
int net_cfg_save(void) {
|
||||
net_cfg_blob_t blob;
|
||||
int r;
|
||||
|
||||
memcpy(blob.ip, s_cfg.ip, 4u);
|
||||
memcpy(blob.mask, s_cfg.mask, 4u);
|
||||
memcpy(blob.gw, s_cfg.gw, 4u);
|
||||
memcpy(blob.mac, s_cfg.mac, 6u);
|
||||
blob.magic = NET_CFG_MAGIC;
|
||||
blob.ver = NET_CFG_VER;
|
||||
blob.crc = s_crc16_modbus(blob.ip, 18u);
|
||||
|
||||
if (net_cfg_store_write((const uint8_t *)&blob, sizeof(blob)) != 0) {
|
||||
DBG_ERROR("save: store write FAILED");
|
||||
s_net_register();
|
||||
r = cfg_file_save();
|
||||
if (r != 0) {
|
||||
DBG_ERROR("save: cfg_file_save FAIL");
|
||||
return -1;
|
||||
}
|
||||
DBG_INFO("save: OK (file written)");
|
||||
DBG_INFO("save: OK (config file written)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration (NET CFG)
|
||||
* 模块功能:网络参数配置(IP/掩码/网关/MAC)——双端共享核心:RAM 配置副本 +
|
||||
* 工厂默认 + 加载/保存编排 + 实际 MAC 影子;存储后端经 net_cfg_store.h
|
||||
* 抽象(MCU = NAND 上 FatFS 文件 /cfg/netcfg.bin,模拟器 = 本地文件
|
||||
* sim_netcfg.bin);磁盘格式 = magic + ver + CRC-16 + 18B 字段;
|
||||
* MAC 全零哨兵 = 不覆盖芯片内置 MAC;保存后下次上电生效
|
||||
* (每次上电 netTask 在 net_init 前加载)
|
||||
* 工厂默认 + 加载/保存编排 + 实际 MAC 影子;存储经 cfg_file 通用
|
||||
* 配置文件引擎(INI [network] 段:ip/mask/gw/mac;MCU = NAND 上
|
||||
* /config.ini,模拟器 = sim_config.ini);MAC 全零哨兵 = 不覆盖芯片
|
||||
* 内置 MAC;保存后下次上电生效(每次上电 netTask 在 net_init 前
|
||||
* 加载)或 poll 4s 沉降后生效
|
||||
* 函数说明:旧版 NCFG 二进制格式(magic+ver+CRC-16+18B 字段)归 net_cfg_legacy,
|
||||
* 首启一次性迁移进通用配置文件
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-10 王建锋 创建初始版本
|
||||
* v1.1 2026-09-14 王建锋 存储迁移至 cfg_file 引擎(INI [network] 段)
|
||||
*/
|
||||
|
||||
#ifndef NET_CFG_H
|
||||
@@ -41,9 +44,11 @@ typedef struct {
|
||||
* 返回值:恒 0(RAM 恒有效:文件缺失/损坏回退工厂默认) int
|
||||
* 限定条件:MCU 端应在存储就绪(SYS_EVT_STORAGE_READY)后调用;
|
||||
* 未就绪时文件读失败,自动回退工厂默认(不阻塞网络启动)
|
||||
* 函数说明: 1. 文件缺失(首启/未保存过)→ 工厂默认
|
||||
* 2. magic/ver/CRC 错误 → 工厂默认并留痕(不让坏配置带崩网络)
|
||||
* 3. 校验通过 → 装入 RAM 副本
|
||||
* 函数说明: 1. 注册 [network] 段(幂等)
|
||||
* 2. 文件缺失 → 先迁旧档 NCFG(有效 → 旧值物化进新文件),否则
|
||||
* 生成出厂模板
|
||||
* 3. 解析文件:有效键 → RAM 副本;无效/缺失键 → 工厂默认并留痕
|
||||
* 4. 严重错误 → RAM 副本 = 工厂默认(不让坏配置带崩网络)
|
||||
*/
|
||||
int net_cfg_load(void);
|
||||
|
||||
@@ -52,8 +57,9 @@ int net_cfg_load(void);
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功;-1 - 写失败 int
|
||||
* 限定条件:MCU 端应在存储就绪后调用
|
||||
* 函数说明:组装磁盘格式(magic+ver+CRC-16+18B 字段)经后端写盘;
|
||||
* 下次上电生效(netTask 在 net_init 前加载)
|
||||
* 函数说明:经 cfg_file 引擎写盘(外科手术式保存:保留注释/未知内容,
|
||||
* 临时文件 + 原子改名);下次上电(netTask 在 net_init 前加载)
|
||||
* 或 poll 4s 沉降后生效
|
||||
*/
|
||||
int net_cfg_save(void);
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 模块名称:net_cfg_legacy
|
||||
* 模块功能:旧版 NCFG 二进制配置一次性迁移——读遗留文件 → 校验 magic/ver/CRC →
|
||||
* 填网络配置 RAM 副本 → 经 cfg_file 引擎生成新文件(带旧值)→ 删旧文件
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用;平台路径经 cfg_file_store 后端)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-14
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-14 王建锋 创建初始版本(P3:net_cfg 引擎化,旧格式代码迁入)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <string.h>
|
||||
|
||||
#include "net_cfg_legacy.h"
|
||||
|
||||
#include "net_cfg.h"
|
||||
|
||||
#include "cfg_file.h"
|
||||
|
||||
#include "cfg_file_store.h"
|
||||
|
||||
#define DBG_TAG "[NET_CFG]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* 常量定义区 */
|
||||
|
||||
#define NET_CFG_MAGIC 0x4647434Eu /* 'NCFG'(文件字节序 N,C,G,F;小端装载) */
|
||||
#define NET_CFG_VER 1u /* 磁盘格式版本 */
|
||||
|
||||
/* 旧磁盘格式(两端布局一致:u32 4 对齐;字段区 18B 连续,CRC 覆盖之) */
|
||||
typedef struct {
|
||||
uint32_t magic; /* NET_CFG_MAGIC */
|
||||
uint16_t ver; /* 格式版本 */
|
||||
uint16_t crc; /* CRC-16/MODBUS(覆盖 ip..mac 共 18B) */
|
||||
uint8_t ip[4];
|
||||
uint8_t mask[4];
|
||||
uint8_t gw[4];
|
||||
uint8_t mac[6];
|
||||
} net_cfg_blob_t;
|
||||
|
||||
/* 静态函数声明区 */
|
||||
static uint16_t s_crc16_modbus(const uint8_t *p_data, size_t len);
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:CRC-16/MODBUS(无查表逐位算法)
|
||||
* 入口参数:p_data - 数据指针 const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:CRC-16 值 uint16_t
|
||||
* 限定条件:p_data 非空(len 可为 0,返回初始值 0xFFFF)
|
||||
* 函数说明:文件私有(数据量 ≤32B,性能无关)
|
||||
*/
|
||||
static uint16_t s_crc16_modbus(const uint8_t *p_data, size_t len) {
|
||||
uint16_t crc = 0xFFFFu;
|
||||
|
||||
for (size_t i = 0u; i < len; i++) {
|
||||
crc = (uint16_t)(crc ^ (uint16_t)p_data[i]);
|
||||
for (int b = 0; b < 8; b++) {
|
||||
if (crc & 0x0001u) {
|
||||
crc = (uint16_t)((crc >> 1) ^ 0xA001u);
|
||||
}
|
||||
else {
|
||||
crc = (uint16_t)(crc >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:旧 NCFG 二进制配置一次性迁移到通用配置文件
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 迁移成功,或无旧文件/新文件已存在(正常);-1 - 迁移失败 int
|
||||
* 限定条件:调用方须已注册 [network] 段(net_cfg_load 内部完成)
|
||||
* 函数说明:见 net_cfg_legacy.h 同名函数说明
|
||||
*/
|
||||
int net_cfg_legacy_migrate(void) {
|
||||
net_cfg_blob_t blob;
|
||||
uint8_t buf[32];
|
||||
size_t nread = 0u;
|
||||
cfg_file_stat_t st;
|
||||
net_cfg_t tmp;
|
||||
const char *p_legacy;
|
||||
|
||||
/* 新文件已存在 = 已迁移过(或已有配置):跳过 */
|
||||
if (cfg_store_stat(cfg_store_config_path(), &st) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
p_legacy = cfg_store_legacy_path();
|
||||
if (cfg_store_read(p_legacy, buf, sizeof(buf), &nread) != 0
|
||||
|| nread != sizeof(net_cfg_blob_t)) {
|
||||
DBG_INFO("legacy: no legacy file, skip");
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void)memcpy(&blob, buf, sizeof(blob));
|
||||
if (blob.magic != NET_CFG_MAGIC || blob.ver != NET_CFG_VER
|
||||
|| s_crc16_modbus(blob.ip, 18u) != blob.crc) {
|
||||
DBG_INFO("legacy: blob invalid (magic/ver/crc), file kept for inspection");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 填 RAM 副本(net_cfg_get 只读,经 net_cfg_set 可写接口整体回灌)
|
||||
* → 经引擎物化旧值到新文件(此时新文件必缺失 → 生成模板带旧值) */
|
||||
(void)memcpy(&tmp, net_cfg_get(), sizeof(tmp));
|
||||
(void)memcpy(tmp.ip, blob.ip, 4u);
|
||||
(void)memcpy(tmp.mask, blob.mask, 4u);
|
||||
(void)memcpy(tmp.gw, blob.gw, 4u);
|
||||
(void)memcpy(tmp.mac, blob.mac, 6u);
|
||||
net_cfg_set(&tmp);
|
||||
|
||||
if (net_cfg_save() != 0) {
|
||||
DBG_ERROR("legacy: migrate save FAIL");
|
||||
return -1;
|
||||
}
|
||||
(void)cfg_store_remove(p_legacy);
|
||||
|
||||
DBG_INFO("legacy: migrated to config file (ip=%u.%u.%u.%u)",
|
||||
(unsigned)blob.ip[0], (unsigned)blob.ip[1],
|
||||
(unsigned)blob.ip[2], (unsigned)blob.ip[3]);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 模块名称:net_cfg_legacy
|
||||
* 模块功能:旧版 NCFG 二进制配置一次性迁移到通用配置文件(net_cfg 旧磁盘格式
|
||||
* 的拥有者:magic 'NCFG' + ver + CRC-16/MODBUS + 18B 字段)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用;平台路径经 cfg_file_store 后端)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-14
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-14 王建锋 创建初始版本(P3:net_cfg 引擎化,旧格式代码迁入)
|
||||
*/
|
||||
|
||||
#ifndef NET_CFG_LEGACY_H
|
||||
#define NET_CFG_LEGACY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:旧 NCFG 二进制配置(28B)一次性迁移到通用配置文件
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 迁移成功,或无旧文件/新文件已存在(正常);
|
||||
* -1 - 迁移失败(新文件未生成) int
|
||||
* 限定条件:调用方须已注册 [network] 段(net_cfg_load 内部完成)
|
||||
* 函数说明:新文件已存在 → 跳过(已迁移过);旧文件有效 → 填网络配置 RAM
|
||||
* 副本 → 经引擎生成新文件(带旧值)→ 删旧文件;旧文件缺失/无效 →
|
||||
* 不动旧文件(留人工检查),新文件按工厂默认生成
|
||||
*/
|
||||
int net_cfg_legacy_migrate(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NET_CFG_LEGACY_H */
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration Store (存储后端接口)
|
||||
* 模块功能:net_cfg 加载/保存的磁盘读写抽象——每平台一份实现(互斥编译):
|
||||
* MCU = NAND 上 FatFS 文件 /cfg/netcfg.bin(net_cfg_store_mcu.c);
|
||||
* 模拟器 = 工作目录本地文件 sim_netcfg.bin(net_cfg_store_sim.c)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(接口共用;两端各编一份实现)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-10 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
#ifndef NET_CFG_STORE_H
|
||||
#define NET_CFG_STORE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:读取配置文件
|
||||
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* len - 文件长度(须与磁盘格式一致,net_cfg.c 的 net_cfg_blob_t) size_t
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:后端存储可用(MCU:FatFS 已挂载;未挂载时实现直接返回 -1 即可)
|
||||
*/
|
||||
int net_cfg_store_read(uint8_t *p_buf, size_t len);
|
||||
|
||||
/*
|
||||
* 函数功能:写配置文件(不存在则创建,存在则覆盖)
|
||||
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度(须与磁盘格式一致) size_t
|
||||
* 返回值:0 - 成功;-1 - 写失败 int
|
||||
* 限定条件:后端存储可用
|
||||
*/
|
||||
int net_cfg_store_write(const uint8_t *p_buf, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NET_CFG_STORE_H */
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration Store (MCU 后端)
|
||||
* 模块功能:net_cfg 存储后端——NAND 上 FatFS 文件 /cfg/netcfg.bin
|
||||
* (目录 /cfg 首次写入时自动创建;卷由 defaultTask 挂载并广播
|
||||
* SYS_EVT_STORAGE_READY 后方可读写)
|
||||
* 适用平台:STM32F407ZGTx
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-10 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include "net_cfg_store.h"
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static const char s_path[] = "/cfg/netcfg.bin";
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:读取配置文件
|
||||
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* len - 文件长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:FatFS 已挂载(未挂载时 f_open 返回 FR_NO_SYSTEM,转 -1)
|
||||
*/
|
||||
int net_cfg_store_read(uint8_t *p_buf, size_t len) {
|
||||
FIL f;
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_open(&f, s_path, FA_READ);
|
||||
if (fr != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_read(&f, p_buf, (UINT)len, &br);
|
||||
(void)f_close(&f);
|
||||
return (fr == FR_OK && br == (UINT)len) ? 0 : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写配置文件(目录不存在则创建;文件覆盖写)
|
||||
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写失败 int
|
||||
* 限定条件:FatFS 已挂载
|
||||
*/
|
||||
int net_cfg_store_write(const uint8_t *p_buf, size_t len) {
|
||||
FIL f;
|
||||
FRESULT fr;
|
||||
UINT bw = 0u;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
(void)f_mkdir("/cfg"); /* 目录已存在(FR_EXIST)不影响后续建文件 */
|
||||
fr = f_open(&f, s_path, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (fr != FR_OK) {
|
||||
return -1;
|
||||
}
|
||||
fr = f_write(&f, (const void *)p_buf, (UINT)len, &bw);
|
||||
(void)f_close(&f);
|
||||
return (fr == FR_OK && bw == (UINT)len) ? 0 : -1;
|
||||
}
|
||||
+2
-2
@@ -31,11 +31,11 @@
|
||||
#define FF_USE_TRIM 0
|
||||
#define FF_FS_TINY 0
|
||||
#define FF_FS_EXFAT 0
|
||||
#define FF_FS_NORTC 1
|
||||
#define FF_FS_NORTC 0
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2026
|
||||
#define FF_FS_NOFSINFO 0
|
||||
#define FF_FS_LOCK 0
|
||||
#define FF_FS_REENTRANT 0
|
||||
#define FF_FS_REENTRANT 1
|
||||
#define FF_FS_TIMEOUT 1000
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 模块名称:FatFs OS 依赖层 (ffsystem)
|
||||
* 模块功能:FF_FS_REENTRANT 卷互斥钩子 + get_fattime 时间钩子(直引用系统墙钟)
|
||||
* 适用平台:STM32F407ZGTx(MCU 端;模拟器端不编译 FatFS)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-13
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-13 王建锋 初始版本:osMutex 卷锁(1s 超时)
|
||||
* v1.1 2026-09-13 王建锋 get_fattime 采用运行时间伪时(零 I2C 流量、无 RTC 依赖)
|
||||
* v1.2 2026-09-13 王建锋 get_fattime 移入独立适配文件 App/ff_time.c(与调试日志墙钟同源);本文件仅留互斥钩子
|
||||
* v1.3 2026-09-13 王建锋 get_fattime 回归本文件并改为时间提供回调(App 启动时注册墙钟,
|
||||
* 本文件不引用 App 头文件,保持分层单向)
|
||||
* v1.4 2026-09-13 王建锋 get_fattime 改为直引用 sys_clock 墙钟(去掉回调注册机制;
|
||||
* sys_clock 归 ④中间件层(App/util/),层内引用,无反向依赖)
|
||||
*/
|
||||
|
||||
#include "ff.h"
|
||||
#include "cmsis_os2.h"
|
||||
#include "sys_clock.h" /* 墙钟:层内中间件引用(sys_clock 归 ④中间件层,App/util/) */
|
||||
|
||||
#if (!FF_FS_NORTC)
|
||||
|
||||
/*
|
||||
* 函数名称:FatFs 时间钩子 (get_fattime) —— 提供当前时间的 FAT 时间格式
|
||||
* 入口参数:无
|
||||
* 返 回 值:FAT 时间 ((year-1980)<<25 | month<<21 | day<<16 | hour<<11 | minute<<5 | second/2);
|
||||
* 墙钟无效时返回固定值(FF_NORTC 约定:2026-01-01 00:00:00)
|
||||
* 限定条件:sys_clock_init() 已调用(App 启动时,早于 osKernelStart)
|
||||
* 函数说明:1. 时间源 = 系统墙钟(与调试日志时间戳同源:开机 RTC 基准 + tick 推进,运行期零 I2C)
|
||||
* 2. 文件 mtime 与日志时间戳一致;ff.h 声明,ff.c 在目录项创建 /
|
||||
* 写后关文件 / f_mkfs 时调用
|
||||
* 3. 本文件为 FatFs 指定的 OS 胶水文件——FatFs 与平台间的
|
||||
* 唯一耦合点(互斥 + 时间)
|
||||
*/
|
||||
DWORD get_fattime(void)
|
||||
{
|
||||
sd2506_time_t t;
|
||||
|
||||
if (sys_clock_get_time(&t) != 0 || t.year < 1980u) {
|
||||
return (((DWORD)(FF_NORTC_YEAR - 1980u) << 25) |
|
||||
((DWORD)FF_NORTC_MON << 21) |
|
||||
((DWORD)FF_NORTC_MDAY << 16));
|
||||
}
|
||||
|
||||
return ((DWORD)(t.year - 1980u) << 25)
|
||||
| ((DWORD)t.month << 21)
|
||||
| ((DWORD)t.day << 16)
|
||||
| ((DWORD)t.hour << 11)
|
||||
| ((DWORD)t.minute << 5)
|
||||
| (DWORD)(t.second / 2u);
|
||||
}
|
||||
|
||||
#endif /* !FF_FS_NORTC */
|
||||
|
||||
#if FF_FS_REENTRANT
|
||||
|
||||
/*
|
||||
* FatFs 可重入同步对象:[0] = 系统锁,[1..FF_VOLUMES] = 卷锁
|
||||
* 首次 f_mount 时创建;获取超时(FF_FS_TIMEOUT)时对应 f_* 返回 FR_TIMEOUT
|
||||
* (优雅失败,由调用方下节拍重试,无死锁)
|
||||
*/
|
||||
static osMutexId_t s_fs_mutex[FF_VOLUMES + 1u];
|
||||
|
||||
int ff_mutex_create(int vol)
|
||||
{
|
||||
static const osMutexAttr_t s_attr = { "ffs", 0u, NULL, 0u };
|
||||
|
||||
if (s_fs_mutex[vol] != NULL) {
|
||||
return 1;
|
||||
}
|
||||
s_fs_mutex[vol] = osMutexNew(&s_attr);
|
||||
return (s_fs_mutex[vol] != NULL) ? 1 : 0;
|
||||
}
|
||||
|
||||
void ff_mutex_delete(int vol)
|
||||
{
|
||||
if (s_fs_mutex[vol] != NULL) {
|
||||
(void)osMutexDelete(s_fs_mutex[vol]);
|
||||
s_fs_mutex[vol] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int ff_mutex_take(int vol)
|
||||
{
|
||||
if (s_fs_mutex[vol] == NULL) {
|
||||
return 1; /* 锁对象未创建 = 无并发,视为已取得 */
|
||||
}
|
||||
return (osMutexAcquire(s_fs_mutex[vol], FF_FS_TIMEOUT) == osOK) ? 1 : 0;
|
||||
}
|
||||
|
||||
void ff_mutex_give(int vol)
|
||||
{
|
||||
if (s_fs_mutex[vol] != NULL) {
|
||||
(void)osMutexRelease(s_fs_mutex[vol]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* FF_FS_REENTRANT */
|
||||
@@ -760,10 +760,20 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\app_main.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>cfg_file.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\cfg_file.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>cfg_file_store_mcu.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\cfg_file_store_mcu.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>sys_clock.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\sys_clock.c</FilePath>
|
||||
<FilePath>..\App\util\sys_clock.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>time_sync.c</FileName>
|
||||
@@ -1242,9 +1252,9 @@
|
||||
<FilePath>..\Drivers\BSP\NETCFG\net_cfg.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>net_cfg_store_mcu.c</FileName>
|
||||
<FileName>net_cfg_legacy.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\NETCFG\net_cfg_store_mcu.c</FilePath>
|
||||
<FilePath>..\Drivers\BSP\NETCFG\net_cfg_legacy.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>nand_ftl.c</FileName>
|
||||
@@ -1999,6 +2009,11 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Lib\FatFs\ff.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>ffsystem.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Lib\FatFs\ffsystem.c</FilePath>
|
||||
</File>
|
||||
</Files>
|
||||
</Group>
|
||||
<Group>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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 分层
|
||||
|
||||

|
||||
|
||||
### 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
@@ -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
@@ -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 随系统状态变化(低电平点亮) |
|
||||
|
||||
---
|
||||
|
||||
@@ -52,6 +52,7 @@ include_directories(
|
||||
${UI_DIR} # ui_menu.h, ui_font_cn_12.h
|
||||
${NET_DIR} # net_socket.h / net_types.h(net_* API 双端共享核心)
|
||||
${NETCFG_DIR} # net_cfg.h(网络配置,双端共享核心)
|
||||
${PROJECT_ROOT}/App # cfg_file.h / cfg_file_store.h(通用配置文件引擎,双端共享核心)
|
||||
${PROJECT_ROOT}/Drivers/BSP # dbg_log.h(使用 simulator 的 dbg_cfg.h 配置)
|
||||
${SDL2_DIR}/include # SDL2 头文件
|
||||
)
|
||||
@@ -86,10 +87,12 @@ set(UI_SOURCES
|
||||
${UI_DIR}/ui_net_sink.c
|
||||
)
|
||||
|
||||
# 网络配置(双端共享核心 + 模拟器存储后端;MCU 端编 net_cfg_store_mcu.c,见 Keil 工程)
|
||||
# 网络配置(双端共享核心 + cfg_file 引擎 + 平台存储后端;MCU 端编 cfg_file_store_mcu.c,见 Keil 工程)
|
||||
set(NETCFG_SOURCES
|
||||
${NETCFG_DIR}/net_cfg.c
|
||||
sim/net_cfg_store_sim.c
|
||||
${NETCFG_DIR}/net_cfg_legacy.c
|
||||
${PROJECT_ROOT}/App/cfg_file.c
|
||||
sim/cfg_file_store_sim.c
|
||||
)
|
||||
|
||||
# 网络后端(net_* 核心 API 的 Winsock 实现 + UI 推流平台后端;MCU 端另编 net_socket.c 与 _mcu 后端)
|
||||
|
||||
+6
-6
@@ -17,7 +17,7 @@ simulator/
|
||||
│ ├── sim_port.c / .h # 显示/输入端口(SDL 窗口 + 键盘 + mono 主题),替代 MCU 端 ui_port.c
|
||||
│ ├── net_socket_sim.c # 网络后端:Winsock 实现 net_* BSD API,替代 MCU 端 net_socket.c(CH395F)
|
||||
│ ├── ui_net_sink_backend_sim.c # RemoDispBus 平台 backend:取帧/注键(配合共享 ui_net_sink.c)
|
||||
│ └── net_cfg_store_sim.c # 网口配置存储:本地 sim_netcfg.bin
|
||||
│ └── cfg_file_store_sim.c # 配置文件存储后端:本地 sim_config.ini(旧档 sim_netcfg.bin 首启自动迁移)
|
||||
├── lv_conf.h # 模拟器专用 LVGL 配置(与 MCU 端 Lib/lv_conf.h 相互独立)
|
||||
├── i1_to_png.py # 截图转 PNG:把 --screenshot 的 I1(1bpp) 原始位图转成 PNG(见 §3.5)
|
||||
├── tests/ # 无头回归脚本 + 本地黄金参考(见 §3.6,流程文档 docs/UI 设计/UI_自动化测试流程.md)
|
||||
@@ -46,10 +46,10 @@ simulator/
|
||||
| `sim/sim_port.c` | 创建 SDL 窗口显示设备(`lv_sdl_window_create`)、初始化 **mono 主题**(`lv_theme_mono_init`,1bpp 必需)、创建键盘输入设备并挂默认输入组。**不创建鼠标**(纯键盘导航,与 MCU 端 keypad 一致)。`sim_port_get_screen_mono()` 经 `SDL_RenderReadPixels` 读回屏幕像素转 1bpp,供推流取帧 |
|
||||
| `sim/net_socket_sim.c` | 网络后端:Winsock 实现 `net_*` BSD Socket API(`socket`/`bind`/`listen`/`accept`/`recv`/`send`/`close`),替代 MCU 端 `Drivers/BSP/NET/net_socket.c`(CH395F) |
|
||||
| `sim/ui_net_sink_backend_sim.c` | RemoDispBus 平台 backend:`ui_net_sink_get_lcdmem`→`sim_port_get_screen_mono`、`ui_net_sink_inject_key`→`lv_group_send_data`;配合**共享** `Drivers/BSP/UI/ui_net_sink.c`(协议服务端,双端同一份,TCP 7003) |
|
||||
| `sim/net_cfg_store_sim.c` | 网口配置存储后端:本地文件 `sim_netcfg.bin`(`fopen`),替代 MCU 端 `Drivers/BSP/NETCFG/net_cfg_store_mcu.c`(Flash) |
|
||||
| `sim/cfg_file_store_sim.c` | 通用配置文件存储后端:本地文件 `sim_config.ini`(`fopen`/`rename` 原子写),替代 MCU 端 `App/cfg_file_store_mcu.c`(FatFS);旧档 `sim_netcfg.bin` 首启自动迁移进 `sim_config.ini` |
|
||||
| `lv_conf.h` | 模拟器专用 LVGL 配置。与 MCU 端 `Lib/lv_conf.h` 的关键差异见 §3.3 |
|
||||
| `dbg_log.h` | MCU 版 `dbg_log.h` 依赖 `sys_clock.h`(RTC 时间戳),PC 无法编译。此 stub 把 `DBG_INFO/ERROR/DEBUG` 映射到 `printf`;因 `simulator/` 在 include 路径最前,**优先生效**覆盖 MCU 版 |
|
||||
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
|
||||
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ 通用配置文件引擎(`../App/cfg_file.c`、`../Drivers/BSP/NETCFG` 的 net_cfg/net_cfg_legacy)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
|
||||
| `i1_to_png.py` | 把 `--screenshot` 产出的 I1(1bpp) 原始位图转成 PNG,便于离线比对设计稿。详见 §3.5 |
|
||||
| `tests/` | 无头回归脚本(菜单 8 状态像素回归、内容页文本断言 + 黄金参考回归);`ref/` 为**本地生成物、不入库**(首次 `--update` 生成)。详见 §3.6 |
|
||||
|
||||
@@ -112,7 +112,7 @@ LVGL 场景(同一份 ui_menu.c)
|
||||
| 输入 | 远端 keypad(`ui_port_queue_key`,网络回传键码) | SDL 键盘(`lv_sdl_keyboard_create`) |
|
||||
| 主题 | 无主题(`LV_USE_THEME_* = 0`),控件用内置基础样式 | **mono 主题必须开启**(否则 1bpp 黑底黑字) |
|
||||
| 日志 | `Drivers/BSP/dbg_log.h`(USART1 + RTC 时间戳) | `simulator/dbg_log.h`(printf stub) |
|
||||
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无,纯 UI |
|
||||
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无(仅编译纯软件部分的配置文件引擎 net_cfg/cfg_file,可离线验证配置加载/保存/旧档迁移) |
|
||||
| 时钟 | `lv_tick_inc` 由 ui_task 手动喂(10ms 周期) | SDL 驱动内部 `lv_tick_set_cb(SDL_GetTicks)`,**不可再手动喂** |
|
||||
|
||||
### 2.4 关键设计决策
|
||||
@@ -121,7 +121,7 @@ LVGL 场景(同一份 ui_menu.c)
|
||||
2. **CMake + MSVC**:`Visual Studio 16 2019` 生成器,`-A x64`;MSVC 下加 `/utf-8` 消除中文注释的 C4819 警告
|
||||
3. **共享 UI 源码**:`ui_menu.c` / `ui_font_cn_12.c` 直接编译 `Drivers/BSP/UI/` 原件,改一处两端同时生效
|
||||
4. **纯键盘导航**:不创建鼠标/触摸设备,与 MCU 端 keypad 操作语义一致(↑↓←→ / Enter / Esc)
|
||||
5. **仅 UI 预览**:不含网络/存储/ADC 等硬件业务,无法验证与外设相关的逻辑
|
||||
5. **UI 为主**:不含 CH395F 网络 / NAND 存储 / ADC 等硬件业务;通用配置文件引擎(`cfg_file`)与 `net_cfg` 核心为纯软件,配置加载/保存/旧档迁移可在模拟器离线验证(`sim_config.ini` / `sim_netcfg.bin`)
|
||||
|
||||
---
|
||||
|
||||
@@ -259,7 +259,7 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI 设计/UI_自动化
|
||||
|
||||
## 五、边界说明
|
||||
|
||||
- 模拟器**只跑 UI**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务
|
||||
- 模拟器**跑 UI + 配置文件引擎**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务;`cfg_file` 引擎与 `net_cfg`(纯软件部分)在模拟器编译运行,可离线验证配置加载/保存/旧档迁移
|
||||
- 模拟器**含** `ui_net_sink`(Winsock 版 RemoDispBus 推流服务端,TCP 7003)——远端看图链路
|
||||
可在模拟器上验证(`simulator/tests/check_remo_sink.py`);CH395F 真实网络栈仍只在 MCU 上验证
|
||||
- `build/` 为生成物,可随时删除重建(已在 `.gitignore` 中忽略)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 模块名称:cfg_file_store (模拟器后端)
|
||||
* 模块功能:cfg_file 存储后端——模拟器工作目录宿主文件 sim_config.ini;
|
||||
* 原子写 = 临时文件 sim_config.ini.tmp + rename;mtime 变更令牌 =
|
||||
* 宿主文件 mtime(秒级);旧版遗留文件 sim_netcfg.bin 供迁移读取
|
||||
* 适用平台:PC 模拟器
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-14
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-14 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "cfg_file_store.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static const char s_path[] = "sim_config.ini";
|
||||
static const char s_path_tmp[] = "sim_config.ini.tmp";
|
||||
static const char s_path_legacy[] = "sim_netcfg.bin";
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:读文件(最多 cap 字节)
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* cap - 缓冲容量 size_t
|
||||
* p_nread - 实际读字节数输出(不可为 NULL) size_t *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
if (p_buf == NULL || p_nread == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fp = fopen(p_path, "rb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
n = fread(p_buf, 1u, cap, fp);
|
||||
(void)fclose(fp);
|
||||
*p_nread = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:原子写文件(先写 sim_config.ini.tmp,成功后 rename 覆盖目标)
|
||||
* 入口参数:p_path - 目标文件路径 const char *
|
||||
* p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fp = fopen(s_path_tmp, "wb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
n = fwrite(p_buf, 1u, len, fp);
|
||||
(void)fclose(fp);
|
||||
if (n != len) {
|
||||
(void)remove(s_path_tmp);
|
||||
return -1;
|
||||
}
|
||||
if (rename(s_path_tmp, p_path) != 0) {
|
||||
(void)remove(s_path_tmp);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:文件状态(size + mtime 变更令牌)
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* p_st - 输出(不可为 NULL) cfg_file_stat_t *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
|
||||
struct stat st;
|
||||
|
||||
if (p_st == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (stat(p_path, &st) != 0) {
|
||||
return -1;
|
||||
}
|
||||
p_st->size = (uint32_t)st.st_size;
|
||||
p_st->mtime = (uint32_t)st.st_mtime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:删除文件
|
||||
* 入口参数:p_path - 文件路径 const char *
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/删除失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int cfg_store_remove(const char *p_path) {
|
||||
if (remove(p_path) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取配置文件路径
|
||||
* 入口参数:无
|
||||
* 返回值:"sim_config.ini"(模拟器工作目录) const char *
|
||||
* 限定条件:无
|
||||
*/
|
||||
const char *cfg_store_config_path(void) {
|
||||
return s_path;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取配置文件临时文件路径
|
||||
* 入口参数:无
|
||||
* 返回值:"sim_config.ini.tmp" const char *
|
||||
* 限定条件:无
|
||||
*/
|
||||
const char *cfg_store_tmp_path(void) {
|
||||
return s_path_tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取旧版配置遗留文件路径
|
||||
* 入口参数:无
|
||||
* 返回值:"sim_netcfg.bin"(与 MCU 端旧格式一致,两端可互读) const char *
|
||||
* 限定条件:无
|
||||
*/
|
||||
const char *cfg_store_legacy_path(void) {
|
||||
return s_path_legacy;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration Store (模拟器后端)
|
||||
* 模块功能:net_cfg 存储后端——模拟器工作目录本地文件 sim_netcfg.bin
|
||||
* (磁盘格式与 MCU 端一致,两端可互读)
|
||||
* 适用平台:PC 模拟器
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-09-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-09-10 王建锋 创建初始版本
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <stdio.h>
|
||||
|
||||
#include "net_cfg_store.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
static const char s_path[] = "sim_netcfg.bin";
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:读取配置文件
|
||||
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
|
||||
* len - 文件长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int net_cfg_store_read(uint8_t *p_buf, size_t len) {
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fp = fopen(s_path, "rb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
n = fread(p_buf, 1u, len, fp);
|
||||
fclose(fp);
|
||||
return (n == len) ? 0 : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写配置文件(不存在则创建,存在则覆盖)
|
||||
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写失败 int
|
||||
* 限定条件:无
|
||||
*/
|
||||
int net_cfg_store_write(const uint8_t *p_buf, size_t len) {
|
||||
FILE *fp;
|
||||
size_t n;
|
||||
|
||||
if (p_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
fp = fopen(s_path, "wb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
n = fwrite(p_buf, 1u, len, fp);
|
||||
fclose(fp);
|
||||
return (n == len) ? 0 : -1;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ L_UI = dict(
|
||||
)
|
||||
L_SVC = dict(
|
||||
title="③ 基础服务层", sub="应用侧服务 / 编排", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["app_main(启动编排)", "sys_clock(墙钟基准)", "sys_events(就绪屏障)",
|
||||
chips=["app_main(启动编排)", "sys_events(就绪屏障)",
|
||||
"app_cfg(编译开关)", "task/(net · ui · adc* · rs485*)",
|
||||
"lftpd(FTP :21)", "time_sync(TCP 对时 :8888)"],
|
||||
)
|
||||
@@ -61,7 +61,7 @@ L_MW = dict(
|
||||
title="④ 中间件层", sub="通用库 / 协议栈", color="#16a34a", bg="#f0fdf4",
|
||||
chips=["net_socket / net_select(BSD API)",
|
||||
"FatFs(文件系统)", "nand_ftl(dhara FTL)", "LVGL v9.2(图形库)",
|
||||
"util(crc · ringbuf)"],
|
||||
"sys_clock(墙钟基准)", "util(crc · ringbuf)"],
|
||||
)
|
||||
L_DRV = dict(
|
||||
title="⑤ 驱动层", sub="Drivers/BSP · 芯片时序/中断", color="#d97706", bg="#fffbeb",
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/images/配置文件分层架构.svg —— 通用配置文件(/config.ini)分层架构图(纯标准库,无需第三方依赖)。
|
||||
|
||||
由 gen_net_stack_svg.py 复制改数据区:配置体系六层结构
|
||||
(docs/配置文件设计说明.md §4.1):
|
||||
PC 端(编辑器/FTP 客户端)→ lftpd FTP 服务 → /config.ini 文件 → cfg_file 引擎 → 段适配(键表)→ 消费者。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_cfg_arch_svg.py
|
||||
输出:
|
||||
docs/images/配置文件分层架构.svg
|
||||
改架构时:编辑本文件顶部的 LAYERS / ROWS 数据后重跑即可。
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "images", "配置文件分层架构.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
W = 1200
|
||||
PAD = 26
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
BAND_TITLE_FONT = 17
|
||||
BAND_SUB_FONT = 12
|
||||
CHIP_FONT = 13
|
||||
|
||||
CHIP_H = 38 # 芯片盒高
|
||||
CHIP_GAP = 10 # 芯片水平间距
|
||||
CHIP_ROW_GAP = 10 # 芯片行间距
|
||||
CHIP_PAD = 16 # 芯片内左右留白
|
||||
BAND_PAD_X = 18 # 框内左右留白
|
||||
BAND_TITLE_H = 38 # 框内标题区高
|
||||
BAND_PAD_BOTTOM = 16
|
||||
BAND_H_GAP = 26 # 同一行内并列框的间距
|
||||
BAND_V_GAP = 44 # 行间距(放箭头)
|
||||
TITLE_H = 78 # 顶部标题区高
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
# ----------------------------- 架构数据(改这里) -----------------------------
|
||||
L_PC = dict(
|
||||
title="PC 端(外部)", sub="文本编辑器 + FTP 客户端", color="#475569", bg="#f1f5f9",
|
||||
chips=["文本编辑器:改 /config.ini(注释 / 加段 / 改值)",
|
||||
"FTP 客户端:RETR 下载 / STOR 上传(端口 21)"],
|
||||
)
|
||||
L_FTP = dict(
|
||||
title="lftpd FTP 服务(端口 21)", sub="ftpTask · 单连接 · root = /(= 卷根)", color="#7c3aed", bg="#f5f3ff",
|
||||
chips=["RETR / STOR / LIST / DELE / CWD(lftpd 现状能力)",
|
||||
"STOR = 原地覆盖(无 tmp+rename)",
|
||||
"路径规范化封死 .. 穿越"],
|
||||
)
|
||||
L_FILE = dict(
|
||||
title="/config.ini(通用配置文件)", sub="FatFS 卷根(NAND GD5F2GQ5UE · dhara FTL)", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["INI 文本:[段] / key=value / ; 注释",
|
||||
"CRLF · UTF-8(容忍 BOM)· ≤ 4KB",
|
||||
"设备写:tmp + rename(原子);变更识别:size + mtime"],
|
||||
)
|
||||
L_ENGINE = dict(
|
||||
title="cfg_file(配置文件引擎)", sub="App/cfg_file.c · 双端共享", color="#2563eb", bg="#eff6ff",
|
||||
chips=["INI 解析 / 序列化(逐键校验 · 容错 · 自愈)",
|
||||
"外科手术式保存(保留注释 / 未知段 → 降级安全)",
|
||||
"2s 变更轮询(f_stat)+ 段变更回调",
|
||||
"出厂模板生成 · 旧 NCFG blob 迁移"],
|
||||
)
|
||||
L_SECT = dict(
|
||||
title="段适配(键表)", sub="段 RAM 副本 = 模块静态结构体(稳态 0 增量)", color="#16a34a", bg="#f0fdf4",
|
||||
chips=["net_cfg [network]:ip / mask / gateway / mac",
|
||||
"未来:[adc] · [rs485] · [ftp] ...(三步接入)"],
|
||||
)
|
||||
L_CONS = dict(
|
||||
title="消费者", sub="读 / 写段 RAM 副本 + 应用", color="#e11d48", bg="#fff1f2",
|
||||
chips=["netTask:组网 / 重建(代次号自愈)",
|
||||
"UI 网口设置页:显示 / 编辑 / 保存(零改动)",
|
||||
"未来模块:cfg_file_section_data() 取段数据"],
|
||||
)
|
||||
|
||||
# 每一行可放多个"同级并列"的层(上层只依赖下方各层)
|
||||
ROWS = [
|
||||
[L_PC],
|
||||
[L_FTP],
|
||||
[L_FILE],
|
||||
[L_ENGINE],
|
||||
[L_SECT],
|
||||
[L_CONS],
|
||||
]
|
||||
|
||||
FOOT = "稳态 RAM 零增量(段数据 = 模块既有结构体);暂态堆缓冲 ≤9KB(仅 load/save 时);FF_FS_REENTRANT=1 卷锁串行化多任务 FS 访问;模拟器端 = 宿主文件 sim_config.ini(同核心)"
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def text_w(s, fs):
|
||||
"""粗略估算文本像素宽:CJK/全角/圈码/箭头 ≈ 1.0em,其余 ≈ 0.58em。
|
||||
|
||||
取偏大估计(宁可芯片略宽,不可文字溢出盒外)。
|
||||
"""
|
||||
w = 0.0
|
||||
for ch in s:
|
||||
w += fs * (1.0 if ord(ch) >= 0x2460 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
def chip_w(label):
|
||||
return text_w(label, CHIP_FONT) + 2 * CHIP_PAD
|
||||
|
||||
|
||||
def wrap_chips(chips, avail):
|
||||
rows, cur, cur_w = [], [], 0.0
|
||||
for c in chips:
|
||||
cw = chip_w(c)
|
||||
need = cw if not cur else cur_w + CHIP_GAP + cw
|
||||
if cur and need > avail:
|
||||
rows.append(cur)
|
||||
cur, cur_w = [c], cw
|
||||
else:
|
||||
cur.append(c)
|
||||
cur_w = need
|
||||
if cur:
|
||||
rows.append(cur)
|
||||
return rows
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
# 布局:逐行排布(行内可并列多个框,行高取该行最高框)
|
||||
placements = [] # (layer, chip_rows, x, y, w, h)
|
||||
y = TITLE_H
|
||||
for row in ROWS:
|
||||
n = len(row)
|
||||
bw = (W - 2 * PAD - BAND_H_GAP * (n - 1)) // n
|
||||
bands = []
|
||||
for i, layer in enumerate(row):
|
||||
bx = PAD + i * (bw + BAND_H_GAP)
|
||||
avail = bw - 2 * BAND_PAD_X
|
||||
rows = wrap_chips(layer["chips"], avail)
|
||||
bh = BAND_TITLE_H + len(rows) * CHIP_H + (len(rows) - 1) * CHIP_ROW_GAP + BAND_PAD_BOTTOM
|
||||
bands.append([layer, rows, bx, y, bw, bh])
|
||||
row_h = max(b[5] for b in bands)
|
||||
for b in bands:
|
||||
b[5] = row_h # 同行等高(视觉"同级")
|
||||
placements.extend(bands)
|
||||
y += row_h + BAND_V_GAP
|
||||
total_h = y - BAND_V_GAP + 54
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, 40, TITLE_FONT, esc("通用配置文件分层架构(/config.ini)")))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, 62, SUB_FONT,
|
||||
esc("INI 文本文件 · FTP 上传/下载 · 段键表引擎 · MCU/模拟器双端共享")))
|
||||
|
||||
for (layer, rows, bx, by, bw, bh) in placements:
|
||||
# 框背景 + 边框
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="12" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.45"/>' % (bx, by, bw, bh, layer["bg"], layer["color"]))
|
||||
# 标题(层名 + 目录/职责说明)
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (bx + BAND_PAD_X, by + 26, BAND_TITLE_FONT, layer["color"], esc(layer["title"])))
|
||||
tw = text_w(layer["title"], BAND_TITLE_FONT)
|
||||
add('<text x="%.0f" y="%d" font-size="%s" fill="#64748b">%s</text>'
|
||||
% (bx + BAND_PAD_X + tw + 12, by + 26, BAND_SUB_FONT, esc(layer["sub"])))
|
||||
# 芯片
|
||||
cy = by + BAND_TITLE_H
|
||||
for row in rows:
|
||||
cx = bx + BAND_PAD_X
|
||||
for c in row:
|
||||
cw = chip_w(c)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#ffffff" '
|
||||
'stroke="%s" stroke-opacity="0.55"/>' % (cx, cy, cw, CHIP_H, layer["color"]))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#1e293b" text-anchor="middle">%s</text>'
|
||||
% (cx + cw / 2, cy + CHIP_H / 2 + CHIP_FONT * 0.35, CHIP_FONT, esc(c)))
|
||||
cx += cw + CHIP_GAP
|
||||
cy += CHIP_H + CHIP_ROW_GAP
|
||||
|
||||
# 行间箭头:每个上层框向下引一条
|
||||
row_first = [] # 每行首个 placement 的下标
|
||||
idx = 0
|
||||
for row in ROWS:
|
||||
row_first.append(idx)
|
||||
idx += len(row)
|
||||
for r in range(len(ROWS) - 1):
|
||||
y0 = placements[row_first[r]][3] + placements[row_first[r]][5] + 6
|
||||
y1 = placements[row_first[r + 1]][3] - 8
|
||||
for i in range(row_first[r], row_first[r] + len(ROWS[r])):
|
||||
(_, _, bx, _, bw, _) = placements[i]
|
||||
ax = bx + bw / 2
|
||||
add('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#94a3b8" '
|
||||
'stroke-width="2.5"/>' % (ax, y0, ax, y1 - 7))
|
||||
add('<path d="M %.1f %.1f l -6 -9 h 12 z" fill="#94a3b8"/>' % (ax, y1))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, esc(FOOT)))
|
||||
|
||||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
|
||||
% (W, total_h, W, total_h, FONT, "\n".join(out)))
|
||||
return svg, total_h
|
||||
|
||||
|
||||
def main():
|
||||
svg, h = build()
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(svg)
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(svg)
|
||||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user