# 网口设置页配置说明 > 版本:v2.3 日期:2026-10-11 > 适用工程:STM32F4-Base(STM32F407ZGTx + CH395F 以太网控制器) > 范围:UI「网口设置」页可配置的网口参数(IP 地址 / 子网掩码 / 默认网关 / MAC 地址)—— > 参数与默认值、持久化、生效链路、页面操作与权限。 --- ## 0. 速览 | 项目 | 内容 | |---|---| | **可配置** | IP 地址、子网掩码、默认网关、MAC 地址 | | **入口** | 屏幕:`通讯参数 → 网口设置`(修改密码 `123` 可编辑;查看密码 `000` 只读) | | **存储** | NAND Flash 文件 `/config.ini`(INI `[network]` 段,通用配置文件,掉电不丢);旧版 `/cfg/netcfg.bin` **不再读取**(存在则忽略,见 §3.2) | | **生效** | 按「保存」后**立即生效**(网络重建约 1~3s,既有 TCP 连接会断开,无需重启) | | **工厂默认** | `192.168.31.100` / `255.255.255.0` / `192.168.31.1` / 芯片内置 MAC | **典型操作(改 IP)**: 1. 屏幕进入 `通讯参数 → 网口设置`,输入修改密码 `123` 2. 选中 `IP地址` 行 → 回车进编辑 → `↑/↓` 调数字、`←/→` 移光标 → 回车确认 3. 选中 `保存` 行 → 回车 → 显示 `OK` 4. 约 1~3 秒后设备在新 IP 上恢复服务(FTP / 对时 / 远程显示自动重建监听) --- ## 1. 可配置参数与工厂默认值 定义在 `Drivers/BSP/NETCFG/net_cfg.h` 的 `net_cfg_t`: | 参数 | 字段 | 工厂默认 | 说明 | |---|---|---|---| | IP 地址 | `ip[4]` | **192.168.31.100** | 静态 IP(不启用 DHCP) | | 子网掩码 | `mask[4]` | **255.255.255.0** | /24 | | 默认网关 | `gw[4]` | **192.168.31.1** | | | MAC 地址 | `mac[6]` | **全零** | 全零 = 不覆盖芯片内置 MAC(见 §2) | 工厂默认值是**单一来源**:`net_cfg.c` 顶部 `NET_CFG_DEF_*` 宏,同时用于 `s_cfg` 静态初始化与 `s_defaults`(引擎注册的 `p_defaults` 目标),避免两处漂移。 > **RAM 副本自启动即有效**:`s_cfg` 以工厂默认值**静态初始化**(落 `.data`),不依赖 `net_cfg_load()` 是否被调用——即便存储异常,页面与网络拿到的也是有效配置而非全零。 --- ## 2. MAC 地址语义 | 存储值 | `net_cfg_get_mac()` 返回 | 网络初始化时 | |---|---|---| | 非全零 | 存储的 MAC | 写入芯片 EEPROM | | 全零(默认) | 芯片实际 MAC 影子(已回写时);否则全零 | **不写**,保持芯片内置 MAC | - 芯片实际 MAC 由 netTask 在网络初始化成功后读回,经 `net_cfg_set_active_mac()` 存入**影子区**——供页面只读显示(页面不直接触 SPI)。 - MAC 写芯片 EEPROM,**每上电重放同值幂等**。 --- ## 3. 持久化 ### 3.1 配置文件(INI `/config.ini`) 网口参数是**通用配置文件** `/config.ini` 的一个段(NAND 卷根,UTF-8/CRLF,文本编辑器可直接改): ```ini ; ============================================================ ; STM32F4-Base 通用配置文件 ; 格式: INI (UTF-8 / CRLF),本文件为配置唯一来源 ; - 以 ';' 或 '#' 开头的行为注释,保存时保留 ; - 未知段/键保存时保留;缺失段/键保存时写回 ; - 编辑后经 FTP 上传或设备端保存,设备 4s 沉降后自动重载 ; ============================================================ [network] ip=192.168.31.100 mask=255.255.255.0 gw=192.168.31.1 mac=00:00:00:00:00:00 ``` > 出厂模板同时含 `[ftp]` 段(`user` / `pwd_hash` / `write`,见 §3.4)——本块只展示 `[network]` 部分。 - **加载校验(逐键容错)**:单个键值非法(越界/格式错)不影响其他键——非法键回工厂默认并留痕(`parse: [network] <键> value invalid`);文件缺失 → 自动生成本模板;文件 > 4KB = 损坏 → 工厂默认 + 重建模板(坏配置不带崩网络)。 - **保存(外科手术式)**:注释 / 未知段 / 未知键逐行保留,受管键按 RAM 副本原位改写、缺失键补写;写盘 = 临时文件 `/config.TMP` + `f_rename` 原子替换(掉电不损坏目标文件)。 - **外部变更自动重载**(P4 阶段接入):2s 轮询检测 (size, mtime) 变化、4s 沉降(连续 2 次轮询稳定)后重载 `[network]` 段并重建网络——FTP 上传编辑后的文件无需重启即生效。 完整设计(引擎 / 双端 / 并发模型 D15)见 `docs/子系统设计/配置文件设计说明.md`。 ### 3.2 旧版二进制档 `/cfg/netcfg.bin`:已不再兼容(2026-10-10) 旧版格式 `/cfg/netcfg.bin`(28B:magic `'NCFG'` + ver + CRC-16/MODBUS + 18B 字段)的"首次启动自动迁移"已于 **2026-10-10 删除**(`net_cfg_legacy.c/h` 整体移除,该格式的解析代码不再存在)。 | `/config.ini` | `/cfg/netcfg.bin` | 行为 | |---|---|---| | 缺失 | 存在(有效或无效都一样) | 生成出厂模板 → **工厂默认**;旧档**被忽略**(不读、不删、不迁移) | | 缺失 | 缺失 | 生成出厂模板 → 工厂默认 | | 存在 | — | 正常加载现有文件 | - **删除理由**:该一次性路径把整条配置**写**链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)无条件挂进启动期调用图,使 defaultTask 的 armlink 静态最坏栈链 **+600 B**;而本仓库不存在携带旧档的设备。 - **对使用方的影响**:卷上仍残留旧档的设备升级本版本后,网口配置**回落工厂默认**(`192.168.31.100/24`、gw `.1`),需经 UI「网口设置」或 FTP 上传 `/config.ini` 重新配置。 ### 3.3 存储后端(双端) `App/cfg/cfg_file_store.h` 定义接口(带路径的文件操作),两平台各一份实现(互斥编译): | 平台 | 文件 | 位置 | |---|---|---| | MCU | `App/cfg/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(秒级)。 ### 3.4 `[ftp]` 段:FTP 鉴权(2026-10-11 新增) FTP 服务(lftpd,:21)自 2026-10-11 起启用口令鉴权。配置与 `[network]` **同文件、同引擎**(`net_cfg_load()` 内统一注册,键表与工厂默认见 `Drivers/BSP/NETCFG/ftp_cfg.c/h`;鉴权状态机在 `Drivers/BSP/NET/lftpd/lftpd_auth.c/h`,纯函数、主机侧可测): ```ini [ftp] user=admin pwd_hash=d82494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892 write=1 ``` | 键 | 类型 | 工厂默认 | 说明 | |---|---|---|---| | `user` | STR(≤15 字符) | `admin` | 期望用户名,**大小写不敏感** | | `pwd_hash` | STR(64 位 hex) | `d82494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892`(即口令 `admin`) | **SHA-256(user‖password)** 摘要,hex 大小写不敏感 | | `write` | UINT(0/1) | `1` | 1 = 允许 STOR/DELE;**0 = 写命令一律 `550` 拒**(只读卷) | **鉴权流程**: 1. `USER`:不论用户名对错一律回 `331`(不泄露"用户是否存在";错用户名在 `PASS` 阶段仍走同耗时哈希路径拉平时间)。 2. `PASS`:计算 SHA-256(期望用户名‖口令),与 `pwd_hash` **恒定时间比较** → 对回 `230`、错回 `530`。 3. **未登录命令门**:登录成功前仅 `USER` / `PASS` / `QUIT` / `FEAT` / `SYST` / `NOOP` 放行,其余命令一律 `530`。 4. **失败锁定**:连续 **5 次**失败 → **锁定 60 s**(窗内所有 `PASS` 直接 `530`、不再计数);登录成功清零计数;锁定状态**全局跨会话**、易失(重启清零)。 **改口令步骤**(无 UI 入口;下载 `/config.ini` 编辑后上传,或经串口/烧录工具改写): 1. 生成新摘要 hex(对 `user + 新口令` 拼接串做 SHA-256;下例中 `user` / `pwd` 为占位,替换为字符串字面量,如 `('admin'+'新口令')`): ```powershell python -c "import hashlib;print(hashlib.sha256((user+pwd).encode()).hexdigest())" ``` 2. 把结果写入 `[ftp]` 段的 `pwd_hash=`(hex 大小写随意); 3. 上传回设备——配置引擎 poll 沉降 **≈4 s** 后重载,**下次登录生效**(既有会话不受影响)。 **忘口令恢复路径**:口令**不可**从 `pwd_hash` 反推(这正是哈希的意义)——经**串口/烧录工具改写配置**,或**重烧固件**回落出厂默认,然后立即改口令。 **出厂默认口令告警**:`pwd_hash` 仍为出厂值时,每次启动打 INFO 锚点 `[FTP] using factory default password`(改了 `pwd_hash` 即消失)。 > **诚实边界**:FTP 协议**明文传输口令**——本方案挡的是**未授权访问**,不挡**网络嗅探**(CH395F 上实现 TLS 不现实)。`pwd_hash` 只防 `/config.ini` 泄露后的**离线口令还原**,盐 = 用户名(无每机随机盐,局域网威胁模型下够用)。 ### 3.5 `[run_par]` 段:运行参数(2026-10-25 新增,「数据可信」运行参数段一项) 遥测死区 9 点的持久载体(引擎属主 defaultTask;**现场远程写入口 = 104 段 `0x8030~0x8038`**,本段键供 FTP 手改 / 排障直读,逐项语义与地址表 → `docs/子系统设计/IEC104上行设计说明.md` §3.4)。**值 = 工程量毫单位 int32 有符号** (`db_ua=500` 即 0.5 V;电流域 `db_ia=5` 即 0.005 A;频率域 `db_freq=500` 即 0.5 Hz)。 | 键 | 通道 | 工厂默认 | 104 写路径合法域(毫单位) | |---|---|---|---| | `db_ua` `db_ub` `db_uc` `db_un` | 电压死区 | 500 | 0 ~ 50000(50 V) | | `db_ia` `db_ib` `db_ic` `db_in` | 电流死区 | 5 | 0 ~ 1000(1 A) | | `db_freq` | 频率死区 | 500 | 0 ~ 5000(5 Hz) | - 键数 9 ≤ 16,**通道系数覆盖不落本段**——系数唯一真源 = `[adc_cal]` / `[adc_cal_ofs]` 两段(标定落盘与整定入口布局,docs/任务规划/数据可信.md §4.4;104 `0x8040~0x804F` 写 = `adc_cal_set` 代理,lead 裁②a)。 - 守卫在**写路径**(104/壳 API 拒 NaN/负/越程);**文件通路引擎照收**(同 adc_cal 的 「人工编辑 + 沉降去抖」契约)——死区超程只让上送变钝、不毁量纲,无合法带哨兵。 - 生效链路:load/poll 原地改副本 → 104 COV 每拍实时读(无回调,104 改读快照与运行参数段契约); 104 写成功 → `SYS_EVT_RUN_PAR_SAVE` → 本任务 `cfg_file_save()` 整档外科落盘。 ### 3.6 `[comm]` 段:IEC 60870-5-104 通讯参数(2026-10-26 新增,「UI 补齐」波次里的非 UI 前置项,道G task-8) 104 被控站的公共地址与监听端口持久载体(段属主 = `App/protocol/iec104/` 配对模块: 工厂默认与合法带见 `iec104_comm.h`、键表与实现见 `iec104_comm.c`;注册 `iec104_comm_init()` 在 `app_main_init()` 内核前——「先注册后 load」契约同 [adc_cal] / [run_par]): | 键 | 含义 | 工厂默认 | getter 合法带(带外自愈回默认) | |---|---|---|---| | `ca` | 公共地址(= 链路地址,ASDU 头 2B) | 1 | 1 ~ 65535 | | `port` | 被控站 TCP 监听端口 | 2404 | 1024 ~ 65535 | - **生效 = 改后重启**(与 [network] 免重启重建**不同**,验收口径 → `docs/任务规划/UI补齐.md` §2 第 5 行):iec104Task 启动时读一次 getter; 端口另在下次重 bind(拆链/代次重建)自然生效。不挂段回调、不新增事件位—— CH395F 单连接 socket 上「换端口/拒绝重听」= close + re-listen,做成热生效 会被端口扫描打成 SPI/看门狗风暴(详论 → 本段只登记,正文唯一详述处 = `docs/子系统设计/IEC104上行设计说明.md` §8)。 - **主站地址(accept 侧 IP 白名单)本段不做**:无既有消费点 + 上述拒绝代价 + 配错即锁死真主站,待送检/现场需求立项;读者勿假设该键已存在。 - 落盘:UI 保存(task-9)经 `net_cfg_save_request()` → `SYS_EVT_CFG_SAVE` 整档 外科 save;引擎属主 = defaultTask 不变。 - 容量台账:本段上线后 `/config.ini` 受管段 = 6/8([network] 4 键 / [ftp] 3 键 / [adc_cal] 9 键 / [adc_cal_ofs] 8 键 / [run_par] 9 键 / [comm] 2 键);段/键上限 的机器钉 = `test/host/cfg_file` TC-CONFIG-056/057。 --- ## 4. 生效链路 ### 4.1 上电启动 ```mermaid sequenceDiagram autonumber participant DT as defaultTask participant NT as netTask participant CFG as net_cfg participant CH as CH395F Note over DT,NT: 上电:CubeMX 已创建任务与事件组,调度器启动 DT->>DT: FatFS 挂载(f_mount;无卷则 f_mkfs) DT-->>NT: 置 SYS_EVT_STORAGE_READY(就绪屏障) NT->>NT: 等 SYS_EVT_STORAGE_READY(≤10s;超时用工厂默认继续) NT->>CFG: net_cfg_load() Note over CFG: 注册 [network] 段 → 读 /config.ini;缺失/非法键 → 工厂默认 CFG-->>NT: RAM 副本(有效) NT->>CFG: net_cfg_get() → net_cfg_fmt_ipv4() CFG-->>NT: "192.168.001.100" NT->>CH: 初始化网络(下发 IP / 掩码 / 网关 / MAC) CH-->>NT: 芯片实际 MAC NT->>CFG: net_cfg_set_active_mac()(MAC 影子) NT->>NT: 置 SYS_EVT_NET_READY(广播网络就绪) ``` ### 4.2 保存后立即生效 保存网口参数后,设备立即按新参数重建网络,无需重启。 **机制**:`请求 → netTask 重建 → 代次号 → 服务自愈` **约束与对策**:网络重建会复位 CH395F,所有监听 Socket 随之失效;而三个 TCP 服务(FTP :21 / 对时 :8888 / 远程显示 :7003)的监听由各自任务持有。为此引入"网络代次号"——各服务检测到代次变化后主动重建监听,在新 IP 上恢复服务。 ```mermaid sequenceDiagram participant UI as 网口设置页 participant CFG as net_cfg participant NT as netTask participant NS as net_socket participant SVC as FTP/对时/远程显示 UI->>CFG: 保存行 ENTER → net_cfg_save()(写盘) CFG->>NT: net_cfg_request_apply() → 回调置 SYS_EVT_NET_RECONFIG NT->>NT: 主循环检测到该位 → 重建网络(新 IP/掩码/网关/MAC) NT->>NS: net_generation_inc()(代次号 +1,此刻网络已可用) NT->>SVC: 重新广播 SYS_EVT_NET_READY SVC->>SVC: 轮询发现代次变化 → 关闭旧监听 → 重建监听(新 IP) ``` **三个服务的自愈点**: | 服务 | 端口 | 检测位置 | 自愈方式 | |---|---|---|---| | FTP | 21 | `lftpd_inet_accept()` | 代次变化 → 返回 -1 → `lftpd_start` 外层循环重建监听 | | 对时 | 8888 | `time_sync_task()` 的 accept 循环 | 代次变化 → 关旧监听 → 回外层重建 | | 远程显示 | 7003 | `ui_net_sink_poll()` | 代次变化 → 关旧监听/客户端 → 重建 | **关键设计点**: 1. **SPI 归 netTask 独占**:UI 只置事件位,不直接操作 CH395F。 2. **代次号在链路就绪后才递增**:避免服务在网络未就绪时抢先重建,也避免 uiTask 阻塞在消息队列上冻结界面。 3. **无回调 = 空操作**:模拟器不注册应用回调,行为退化为"仅写盘"。 4. **既有连接必断**:IP 变更导致连接不可达,属网络层语义。 --- ## 5. 页面操作(网口设置页) 菜单路径:`通讯参数 → 网口设置`(菜单节点 30,行尾 ` #P` 需密码)。页面模块 `Drivers/BSP/UI/ui_page_net_cfg.c`。 ### 5.1 页面布局 | 行 | 名称 | 值格式 | |---|---|---| | 0 | IP地址 | `192.168.001.100`(每段 `%03u` 补零,**定长 15 字符** = 4 段 × 3 位 + 3 个点) | | 1 | 子网掩码 | `255.255.255.000` | | 2 | 默认网关 | `192.168.001.001` | | 3 | MAC地址 | `A4:94:B6:12:34:56`(6 字节十六进制,冒号分隔) | | 4 | 保存 | `ENTER` → 保存后显示 `OK` / `ERR` | ### 5.2 权限 `ui_pwd_def.h` 编译期密码: | 输入的密码 | 授予权限 | 页面行为 | |---|---|---| | 查看密码 `000` | `UI_PERM_VIEW` | **只读**:ENTER 无效,仅可浏览 | | 修改密码 `123` | `UI_PERM_MODIFY` | **可编辑**:ENTER 进编辑模式 | ### 5.3 编辑模式 ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)、`←/→` 移字节光标(首↔末环绕)、ENTER 确认退出。调整**实时落 RAM 副本**(`net_cfg_set`),**只有保存行 ENTER 才写盘并应用**(`net_cfg_save` + `net_cfg_request_apply`);ESC 退出不写盘、改动丢弃(重启后恢复上次保存值)。 > 值格式与解析约定:页面写定长 `%03u` 字符串 → 网络初始化按十进制逐段解析、容忍前导零(如 `"001"` → 1)。 --- ## 6. 文件与 API 索引 | 文件 | 职责 | |---|---| | `Drivers/BSP/NETCFG/net_cfg.h/.c` | 配置核心:RAM 副本 + 工厂默认 + 加载/保存编排 + MAC 影子 + IPv4 格式化([network] 段注册) | | `App/cfg/cfg_file.h/.c` | 通用配置文件引擎(双端共享核心:解析/校验/外科保存/模板/沉降轮询) | | `App/cfg/cfg_file_store.h` | 存储后端接口(带路径的文件操作) | | `App/cfg/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` | 上电加载配置 + 消费"立即应用"请求重建网络 | | `Drivers/BSP/NET/net_socket.h/.c` | 网络代次号(服务自愈依据) | **对外 API**(`net_cfg.h`): | API | 说明 | |---|---| | `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 副本只读指针(恒非空) | | `net_cfg_set()` | 更新 RAM 副本(不写盘) | | `net_cfg_set_active_mac()` | 回写芯片实际 MAC 影子(netTask 调) | | `net_cfg_get_mac()` | 取显示用 MAC(存储非零 → 存储;否则影子;否则全零) | | `net_cfg_fmt_ipv4()` | 4 字节 → `"192.168.001.100"` 定长 15 字符串(缓冲 len ≥ 16) | --- ## 7. 陷阱 1. **保存即生效,但连接会断**:保存后重建网络(约 1~3s),既有 TCP 连接(FTP / 对时 / 远程显示)会断开并自动重建监听——IP 变更的必然结果,非缺陷。 2. **MAC 全零是哨兵**,不是"MAC 为 0":要恢复芯片内置 MAC,把 MAC 字段改回全零并保存。 3. **配置存在 NAND 上**:存储挂载失败时 `net_cfg_load` 回退工厂默认,网络仍能起,但保存会失败(页面显示 `ERR`)。 4. **变更令牌是分钟级**:FAT 时间为分钟粒度——同分钟内重写同长度文件,轮询检测不到变化(罕见;下次保存/改长度即恢复检测)。 5. **改配置需"修改密码"**:查看密码 `000` 进页面是只读,ENTER 无效(防误改)。 6. **新增 TCP 服务需处理重建**:须在其监听循环中检测 `net_get_generation()` 变化并重建监听,否则网络重建后不再可达(见 §4.2)。 --- ## 8. 相关文档 | 主题 | 文档 | |---|---| | CH395F 已知陷阱 | `docs/问题记录/CH395F_Trap_Records.md` | | 网络测试指南 | `docs/测试说明/板级测试/CH395F_Test_Guide.md` | | 启动顺序 / 就绪屏障 | `docs/架构设计/项目启动顺序管理.md` | | 项目分层架构 | `docs/架构设计/项目架构总览.md` | | 内容页框架(含网口设置页) | `docs/UI设计/内容页架构.md` |