修复了网络参数的保存逻辑
This commit is contained in:
@@ -98,6 +98,9 @@ simulator/SDL2/
|
||||
# 内容页黄金参考(ref/*.bin)与目检 PNG 均为本地生成物,不入库:
|
||||
# 首次/换机后先跑 check_content_pages.py --update 生成 ref/*.bin,再跑 regen_ref_png.py 出 PNG
|
||||
simulator/tests/ref/
|
||||
# 架构图 PNG 预览(源为 docs/*.svg,PNG 本地渲染生成,不入库)
|
||||
docs/项目架构总览.png
|
||||
docs/就绪屏障.png
|
||||
*.pyc
|
||||
|
||||
# Python venv (unified env, see AGENTS.md "Python 环境"; rebuild via requirements.txt)
|
||||
|
||||
@@ -41,12 +41,13 @@ 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)
|
||||
- **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 服务据代次号重建监听)
|
||||
- **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
|
||||
- **timeSyncTask**:TCP 墙钟时间同步(PC 推送)
|
||||
- 功能编译开关:`App/app_cfg.h`(`APP_NET_ENABLE` / `APP_ADC_ENABLE` / `APP_RS485_ENABLE`)
|
||||
- **全景架构图**:`docs/项目架构总览.md`(六层分层 / 任务与就绪屏障 / 启动时序 / 存储·网络·UI 栈 / 目录组织)
|
||||
|
||||
### 任务栈
|
||||
|
||||
@@ -102,6 +103,8 @@ Src/main.c → CubeMX 外设初始化 → app_main_init → osKernelStart
|
||||
## 驱动关键点
|
||||
|
||||
详见 `docs/` 目录:
|
||||
- `docs/项目架构总览.md` — 六层全景架构(分层/任务/启动时序/存储·网络·UI 栈)
|
||||
- `docs/网络配置说明.md` — 网口设置页配置(UI 可改的 IP/掩码/网关/MAC:持久化 / 立即生效 / 页面操作)
|
||||
- `docs/问题记录/CH395F_Trap_Records.md` — CH395F 已知陷阱
|
||||
- `docs/问题记录/GD5F2GQ5UE_Trap_Records.md` — GD5F2GQ5UE 已知陷阱
|
||||
- `docs/问题记录/SD2506_Trap_Records.md` — SD2506 RTC 已知陷阱
|
||||
|
||||
@@ -27,6 +27,7 @@ extern osEventFlagsId_t sys_evtHandle;
|
||||
#define SYS_EVT_STORAGE_READY (1u << 1) /* defaultTask:FTL/FatFS 挂载完成 */
|
||||
#define SYS_EVT_FTP_READY (1u << 2) /* ftpTask:lftpd_start 完成 */
|
||||
#define SYS_EVT_TIMESYNC_READY (1u << 3) /* timeSyncTask:首次时间同步完成 */
|
||||
#define SYS_EVT_NET_RECONFIG (1u << 4) /* UI:请求立即应用新网口配置(netTask 消费后重建网络) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -24,6 +24,20 @@
|
||||
#define DBG_TAG "[NET_TASK]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
/*
|
||||
* 函数功能:"立即应用网口配置"平台回调(注册给 net_cfg)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:UI 保存成功后经 net_cfg_request_apply() 调用(任意任务上下文)
|
||||
* 函数说明:只置事件位;真正的重建在主循环内完成(CH395F SPI 归 netTask 独占,
|
||||
* UI 不得直接操作)
|
||||
*/
|
||||
static void net_reconfig_request_cb(void) {
|
||||
if (sys_evtHandle != NULL) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:CH395F 协议栈一次性初始化(两种构建模式共用)
|
||||
* 入口参数:无
|
||||
@@ -96,6 +110,10 @@ int net_stack_bring_up(void) {
|
||||
(phy == CH395F_PHY_10M_HALF) ? "10M HALF" :
|
||||
(phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN");
|
||||
|
||||
/* 网络已重建完成、对外可用:递增代次号,通知各 TCP 服务(FTP/对时/远程显示)
|
||||
* 旧 Socket 已随芯片复位失效、应重建监听 */
|
||||
net_generation_inc();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -142,10 +160,26 @@ void net_task_func(void const *arg) {
|
||||
osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
|
||||
}
|
||||
|
||||
/* 注册"立即应用网口配置"回调:UI 保存成功后经 net_cfg_request_apply() 触发 */
|
||||
net_cfg_set_apply_cb(net_reconfig_request_cb);
|
||||
|
||||
DBG_INFO("netTask: started (single-socket mode)");
|
||||
|
||||
/* ==================== 主轮询循环 ==================== */
|
||||
for (;;) {
|
||||
/* UI 保存网口配置后请求立即应用:在此重建网络(新 IP/掩码/网关/MAC)。
|
||||
* 重建完成后代次号已递增,各 TCP 服务据此重建监听;期间连接会断开(预期)。 */
|
||||
if (sys_evtHandle != NULL
|
||||
&& (osEventFlagsGet(sys_evtHandle) & SYS_EVT_NET_RECONFIG) != 0u) {
|
||||
(void)osEventFlagsClear(sys_evtHandle, SYS_EVT_NET_RECONFIG);
|
||||
DBG_INFO("netTask: apply new net config (re-init)");
|
||||
if (net_stack_bring_up() == 0) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY);
|
||||
} else {
|
||||
DBG_ERROR("netTask: re-init FAIL");
|
||||
}
|
||||
}
|
||||
|
||||
net_poll();
|
||||
net_process_messages();
|
||||
osDelay(1);
|
||||
|
||||
@@ -114,6 +114,9 @@ void time_sync_task(void *argument) {
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
/* 记录本次监听对应的网络代次;代次变化(网络重建)则放弃旧监听重建 */
|
||||
uint32_t gen = net_get_generation();
|
||||
|
||||
int ls = time_sync_open_listener();
|
||||
if (ls < 0) {
|
||||
DBG_ERROR("time_sync: open listener FAIL, retry later");
|
||||
@@ -126,10 +129,19 @@ void time_sync_task(void *argument) {
|
||||
do {
|
||||
conn = net_accept(ls, NULL, NULL);
|
||||
if (conn < 0) {
|
||||
if (net_get_generation() != gen) {
|
||||
net_close(ls); /* 网络重建:旧监听失效,回外层重建 */
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
} while (conn < 0);
|
||||
|
||||
if (conn < 0) {
|
||||
DBG_INFO("time_sync: network re-init, relisten");
|
||||
continue;
|
||||
}
|
||||
|
||||
time_sync_handle_conn(conn);
|
||||
/* conn(单连接模式下即 ls)已被 net_close 释放,下次循环重建监听 */
|
||||
|
||||
|
||||
@@ -57,11 +57,19 @@ int lftpd_inet_get_socket_port(int socket)
|
||||
|
||||
int lftpd_inet_accept(int socket)
|
||||
{
|
||||
/* 网络重建(net_init)后旧 Socket 已失效:检测到代次变化即返回 -1,
|
||||
* 由 lftpd_start 外层循环重建监听(网络未就绪时其 listen 会重试) */
|
||||
uint32_t gen = net_get_generation();
|
||||
|
||||
while (1) {
|
||||
int s = net_accept(socket, NULL, NULL);
|
||||
if (s >= 0) {
|
||||
return s;
|
||||
}
|
||||
if (net_get_generation() != gen) {
|
||||
DBG_ERROR("network re-init: abort accept, relisten");
|
||||
return -1;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
/*
|
||||
* 模块名称:Network Configuration
|
||||
* 模块功能:网络子系统配置宏定义,包括最大Socket数量、缓冲区大小、
|
||||
* 超时时间等参数配置
|
||||
* 模块功能:网络子系统编译期参数——Socket 数量、收发超时、TCP KeepAlive。
|
||||
* 仅保留**实际被引用**的宏;未使用项已清理。
|
||||
* 适用平台:STM32F4 系列(CH395F 以太网芯片)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-18
|
||||
* 修改记录:
|
||||
* 2026-09-10 王建锋 清理未引用宏(TCP Server 监听 Socket 索引、收发缓冲、
|
||||
* 连接/接受超时、select/队列/轮询);KeepAlive 三宏改为
|
||||
* 实际生效值 60s/5s/3 并在 net_socket.c 引用(原宏为无效遗留值)
|
||||
*/
|
||||
|
||||
#ifndef NET_CONFIG_H
|
||||
@@ -22,37 +25,10 @@ extern "C" {
|
||||
#define NET_MAX_SOCKETS 8
|
||||
#endif
|
||||
|
||||
/*
|
||||
* TCP Server 多连接模式配置
|
||||
* 监听 Socket 索引和数据 Socket 起始索引
|
||||
*/
|
||||
#ifndef NET_TCP_SERVER_LISTEN_SOCK
|
||||
#define NET_TCP_SERVER_LISTEN_SOCK 0 /* 监听 Socket 索引 */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 缓冲区大小配置
|
||||
*/
|
||||
#ifndef NET_SEND_BUF_SIZE
|
||||
#define NET_SEND_BUF_SIZE 4096 /* 发送缓冲区大小(字节) */
|
||||
#endif
|
||||
|
||||
#ifndef NET_RECV_BUF_SIZE
|
||||
#define NET_RECV_BUF_SIZE 4096 /* 接收缓冲区大小(字节) */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 超时时间配置(毫秒)
|
||||
* 0 = 无限等待(阻塞模式)
|
||||
*/
|
||||
#ifndef NET_CONNECT_TIMEOUT_MS
|
||||
#define NET_CONNECT_TIMEOUT_MS 5000 /* 连接超时 */
|
||||
#endif
|
||||
|
||||
#ifndef NET_ACCEPT_TIMEOUT_MS
|
||||
#define NET_ACCEPT_TIMEOUT_MS 0 /* 接受连接超时(0=无限等待) */
|
||||
#endif
|
||||
|
||||
#ifndef NET_SEND_TIMEOUT_MS
|
||||
#define NET_SEND_TIMEOUT_MS 1000 /* 发送超时 */
|
||||
#endif
|
||||
@@ -62,39 +38,19 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* TCP KeepAlive 配置
|
||||
* TCP KeepAlive 配置(单位 ms;须为 500 的倍数且 IDLE > INTVL)
|
||||
* 由 net_socket.c 的 net_init() 在 ch395f_init() 之前下发
|
||||
*/
|
||||
#ifndef NET_KEEPALIVE_IDLE_MS
|
||||
#define NET_KEEPALIVE_IDLE_MS 7200000 /* KeepAlive 空闲时间(默认2小时) */
|
||||
#define NET_KEEPALIVE_IDLE_MS 60000 /* 空闲 60 秒后开始探测 */
|
||||
#endif
|
||||
|
||||
#ifndef NET_KEEPALIVE_INTVL_MS
|
||||
#define NET_KEEPALIVE_INTVL_MS 75000 /* KeepAlive 探测间隔(默认75秒) */
|
||||
#define NET_KEEPALIVE_INTVL_MS 5000 /* 5 秒探测一次 */
|
||||
#endif
|
||||
|
||||
#ifndef NET_KEEPALIVE_CNT
|
||||
#define NET_KEEPALIVE_CNT 9 /* KeepAlive 探测次数(默认9次) */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* select/poll 最大 fd 数量
|
||||
*/
|
||||
#ifndef NET_SELECT_MAX_FDS
|
||||
#define NET_SELECT_MAX_FDS NET_MAX_SOCKETS
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 轮询延时配置(毫秒)
|
||||
*/
|
||||
#ifndef NET_POLL_DELAY_MS
|
||||
#define NET_POLL_DELAY_MS 1 /* 非阻塞轮询延时 */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 消息队列配置
|
||||
*/
|
||||
#ifndef NET_MSG_QUEUE_LENGTH
|
||||
#define NET_MSG_QUEUE_LENGTH 8 /* 消息队列深度 */
|
||||
#define NET_KEEPALIVE_CNT 3 /* 最多探测 3 次 */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -40,6 +40,7 @@ static int s_net_errno = 0; /* 最后错误码 */
|
||||
static uint8_t s_net_initialized = 0; /* 初始化标志 */
|
||||
static uint8_t s_local_ip[4]; /* 本机 IP 缓存(网络序,net_getsockname 用) */
|
||||
static uint16_t s_dynamic_port = 49152; /* 动态端口分配起始值 */
|
||||
static volatile uint32_t s_net_generation = 0; /* 网络代次号:网络重建完成时递增(服务据此重建监听) */
|
||||
|
||||
/*
|
||||
* 消息队列 - 线程安全核心
|
||||
@@ -142,10 +143,11 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
|
||||
ch395f_set_retrans_count(8); /* 重传 8 次*/
|
||||
ch395f_set_retrans_period(500); /* 每次间隔 500ms,总计 4s 重传窗口 */
|
||||
|
||||
/* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,且 IDLE > INTVL)*/
|
||||
ch395f_set_keepalive_idle(60000); /* 空闲 60 秒后开始探测*/
|
||||
ch395f_set_keepalive_intvl(5000); /* 5 秒探测一次*/
|
||||
ch395f_set_keepalive_cnt(3); /* 最多探 3 次*/
|
||||
/* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,且 IDLE > INTVL)
|
||||
* 值来自 net_config.h 的 NET_KEEPALIVE_*(60s/5s/3)*/
|
||||
ch395f_set_keepalive_idle(NET_KEEPALIVE_IDLE_MS); /* 空闲 60 秒后开始探测*/
|
||||
ch395f_set_keepalive_intvl(NET_KEEPALIVE_INTVL_MS); /* 5 秒探测一次*/
|
||||
ch395f_set_keepalive_cnt(NET_KEEPALIVE_CNT); /* 最多探 3 次*/
|
||||
|
||||
/* 设置网络参数(必须在 INIT_CH395 之前调用 */
|
||||
if (ip != NULL) {
|
||||
@@ -207,6 +209,28 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取网络代次号
|
||||
* 入口参数:无
|
||||
* 返回值:代次号(每次网络重建完成递增) uint32_t
|
||||
* 限定条件:无
|
||||
* 函数说明:见 net_socket.h 同名函数说明
|
||||
*/
|
||||
uint32_t net_get_generation(void) {
|
||||
return s_net_generation;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:递增网络代次号(网络重建完成、对外可用时调用)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:net_init() 成功且 PHY/链路就绪后调用(当前仅 net_stack_bring_up)
|
||||
* 函数说明:见 net_socket.h 同名函数说明
|
||||
*/
|
||||
void net_generation_inc(void) {
|
||||
s_net_generation++;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件
|
||||
* 返回值:本次轮询中发生事件的 Socket 数量 int >= 0
|
||||
|
||||
@@ -62,6 +62,27 @@ typedef struct {
|
||||
*/
|
||||
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac);
|
||||
|
||||
/*
|
||||
* 函数功能:取网络代次号
|
||||
* 入口参数:无
|
||||
* 返回值:代次号(每次网络重建完成递增) uint32_t
|
||||
* 限定条件:无
|
||||
* 函数说明:TCP 服务(FTP / 对时 / 远程显示)据此感知"网络已重建"——代次变化
|
||||
* 表示旧 Socket 已随芯片复位失效,服务应放弃旧监听并重建。
|
||||
* 递增点在 net_stack_bring_up() 末尾(PHY/链路就绪后),避免服务在网络
|
||||
* 尚未可用时抢先重建、也避免 uiTask 阻塞在消息队列上。
|
||||
*/
|
||||
uint32_t net_get_generation(void);
|
||||
|
||||
/*
|
||||
* 函数功能:递增网络代次号(网络重建完成、对外可用时调用)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:net_init() 成功且 PHY/链路就绪后调用(当前仅 net_stack_bring_up)
|
||||
* 函数说明:见 net_get_generation()
|
||||
*/
|
||||
void net_generation_inc(void);
|
||||
|
||||
/*
|
||||
* 函数功能:轮询所有 Socket 状态(必须在主循环中调用)
|
||||
* 返回值:有事件发生的 socket 数量
|
||||
|
||||
@@ -58,6 +58,7 @@ static net_cfg_t s_cfg = {
|
||||
};
|
||||
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; /* "立即应用"平台回调(未注册 = 空操作) */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
@@ -178,10 +179,34 @@ int net_cfg_save(void) {
|
||||
DBG_ERROR("save: store write FAILED");
|
||||
return -1;
|
||||
}
|
||||
DBG_INFO("save: OK (effective at next power-on)");
|
||||
DBG_INFO("save: OK (file written)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:注册"立即应用"回调
|
||||
* 入口参数:cb - 回调函数(NULL = 注销) net_cfg_apply_cb_t
|
||||
* 返回值:无
|
||||
* 限定条件:应用启动早期注册一次即可
|
||||
* 函数说明:见 net_cfg.h 同名函数说明
|
||||
*/
|
||||
void net_cfg_set_apply_cb(net_cfg_apply_cb_t cb) {
|
||||
s_apply_cb = cb;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:请求立即应用当前 RAM 配置
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无(未注册回调时空操作)
|
||||
* 函数说明:见 net_cfg.h 同名函数说明
|
||||
*/
|
||||
void net_cfg_request_apply(void) {
|
||||
if (s_apply_cb != NULL) {
|
||||
s_apply_cb();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取 RAM 配置副本(只读指针)
|
||||
* 入口参数:无
|
||||
|
||||
@@ -57,6 +57,35 @@ int net_cfg_load(void);
|
||||
*/
|
||||
int net_cfg_save(void);
|
||||
|
||||
/*
|
||||
* "立即应用"回调类型:平台挂钩(可选)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 函数说明:MCU 端由 netTask 注册——置 SYS_EVT_NET_RECONFIG 事件,由 netTask
|
||||
* 重建网络;模拟器无网络栈,不注册(空操作)。经回调而非直接调用,
|
||||
* 避免 NETCFG(驱动层)反向依赖 App 层。
|
||||
*/
|
||||
typedef void (*net_cfg_apply_cb_t)(void);
|
||||
|
||||
/*
|
||||
* 函数功能:注册"立即应用"回调
|
||||
* 入口参数:cb - 回调函数(NULL = 注销) net_cfg_apply_cb_t
|
||||
* 返回值:无
|
||||
* 限定条件:应用启动早期注册一次即可
|
||||
* 函数说明:见 net_cfg_apply_cb_t
|
||||
*/
|
||||
void net_cfg_set_apply_cb(net_cfg_apply_cb_t cb);
|
||||
|
||||
/*
|
||||
* 函数功能:请求立即应用当前 RAM 配置
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无(未注册回调时空操作)
|
||||
* 函数说明:UI"网口设置"页保存行写盘成功后调用;真正生效由平台回调完成
|
||||
* (MCU:netTask 重建网络,无需重启)
|
||||
*/
|
||||
void net_cfg_request_apply(void);
|
||||
|
||||
/*
|
||||
* 函数功能:取 RAM 配置副本(只读指针)
|
||||
* 入口参数:无
|
||||
|
||||
@@ -35,6 +35,8 @@ static uint8_t g_rx[64];
|
||||
|
||||
static int g_listen = -1;
|
||||
static int g_client = -1;
|
||||
static uint16_t g_port; /* 监听端口(网络重建后重监听用) */
|
||||
static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */
|
||||
|
||||
/*
|
||||
* 函数功能:计算 RemoDispBus 数据段异或校验值
|
||||
@@ -204,6 +206,7 @@ static void remo_handle(const uint8_t *f, int n)
|
||||
*/
|
||||
int ui_net_sink_start(uint16_t port)
|
||||
{
|
||||
g_port = port;
|
||||
g_listen = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||
if (g_listen < 0) {
|
||||
return -1;
|
||||
@@ -225,6 +228,7 @@ int ui_net_sink_start(uint16_t port)
|
||||
return -1;
|
||||
}
|
||||
g_client = -1;
|
||||
g_gen = net_get_generation(); /* 成功才记录代次;失败保持旧值以便下轮重试 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -236,9 +240,21 @@ int ui_net_sink_start(uint16_t port)
|
||||
* 函数说明:1. 无客户端时尝试接受连接(不主动上报 INIT,由客户端按需请求)
|
||||
* 2. 有客户端时非阻塞收帧并解析
|
||||
* 3. 连接关闭或异常时复位,等待重监听,不阻塞界面刷新
|
||||
* 4. 网络重建(net_init 代次变化)时关闭旧监听并重建(IP 变更后自愈)
|
||||
*/
|
||||
void ui_net_sink_poll(void)
|
||||
{
|
||||
/* 网络重建(net_init 代次变化):旧监听/客户端已随芯片复位失效,重建监听 */
|
||||
if (net_get_generation() != g_gen) {
|
||||
if (g_listen >= 0) {
|
||||
net_close(g_listen);
|
||||
g_listen = -1;
|
||||
}
|
||||
g_client = -1;
|
||||
(void)ui_net_sink_start(g_port); /* 失败则下轮再试(g_gen 未更新) */
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_listen < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
* 模块名称:UI Page Net Cfg (网口设置内容页)
|
||||
* 模块功能:网口设置叶子节点的内容页数据 + 行为——5 行地址列表(IP 地址/子网
|
||||
* 掩码/默认网关/MAC 地址 + 保存行,5 行每页共 1 页);
|
||||
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=6 / 值 x=60;
|
||||
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=4 / 值 x=54;
|
||||
* 值取自 net_cfg RAM 副本(netTask 上电自 Flash 加载并回写实际 MAC);
|
||||
* 编辑模式:ENTER 进选中地址行(字节光标 = 活动字节,反显标记),
|
||||
* ↑↓调字节 0..255(环绕、值列实时刷新)、←→移字节光标(首↔末环绕)、
|
||||
* ENTER 确认退出编辑;
|
||||
* 权限:edit_capable=1——查看密码 → 只读(编辑/保存键无效),
|
||||
* 修改密码 → 可编辑;保存行 ENTER → net_cfg_save()(下次上电生效);
|
||||
* 修改密码 → 可编辑;保存行 ENTER → net_cfg_save() 写盘 +
|
||||
* net_cfg_request_apply() 通知 netTask 立即重建网络(无需重启);
|
||||
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
@@ -29,8 +30,9 @@
|
||||
/*
|
||||
* 布局常量(设备像素;1bpp 屏 160x160):
|
||||
* 5 行共 1 页(行槽顶 y = 24+14i,末行止于 y=93,与页脚 y=143 无冲突);
|
||||
* 名 x=6 / 值 x=60(值列须 ≥60:4 汉字名占 48px + 间隙;值列宽——
|
||||
* IP/掩码/网关 11 ASCII 字符 = 66px 止于 126,MAC 12 hex 字符 = 72px 止于 132)
|
||||
* 名 x=4 / 值 x=54(值列须 ≥54:4 汉字名占 48px + 间隙;值列宽——
|
||||
* IP/掩码/网关 11 ASCII 字符 = 66px 止于 119,MAC 17 字符(冒号分隔)= 102px 止于 155,
|
||||
* 均在选中白带 x=2..156 内)
|
||||
*/
|
||||
#define NC_ROW_COUNT 5u /* 4 地址行 + 1 保存行 */
|
||||
#define NC_ADDR_ROW_MAX 4u /* 地址行范围 0..3(IP/掩码/网关/MAC) */
|
||||
@@ -38,8 +40,8 @@
|
||||
#define NC_TOP_Y 24 /* 首行槽顶 y */
|
||||
#define NC_PITCH 14 /* 行距 */
|
||||
#define NC_BAND_H 13 /* 选中白带高 */
|
||||
#define NC_LEFT_X 6 /* 左列 x(名) */
|
||||
#define NC_RIGHT_X 60 /* 右列 x(值) */
|
||||
#define NC_LEFT_X 4 /* 左列 x(名) */
|
||||
#define NC_RIGHT_X 54 /* 右列 x(值) */
|
||||
#define NC_ROW_BUF_LEN 32u /* 行缓冲长度(与框架 CP_ROW_BUF_LEN 一致) */
|
||||
|
||||
/* 静态变量定义区 */
|
||||
@@ -84,7 +86,7 @@ static uint8_t nc_octet_count(uint16_t row) {
|
||||
* 返回值:无
|
||||
* 限定条件:p_buf 非空
|
||||
* 函数说明:IP/掩码/网关 = "192.168.001.100"(每段 3 位补零定长,字节 i 恒在
|
||||
* 字符 4i 起);MAC = "A494B6123456"(无分隔 hex,字节 i 恒在字符 2i 起)
|
||||
* 字符 4i 起);MAC = "A4:94:B6:12:34:56"(冒号分隔,字节 i 恒在字符 3i 起)
|
||||
*/
|
||||
static void nc_fmt_value(uint16_t row, char *p_buf) {
|
||||
const net_cfg_t *cfg = net_cfg_get();
|
||||
@@ -93,7 +95,7 @@ static void nc_fmt_value(uint16_t row, char *p_buf) {
|
||||
uint8_t mac[6];
|
||||
|
||||
net_cfg_get_mac(mac);
|
||||
(void)lv_snprintf(p_buf, NC_ROW_BUF_LEN, "%02X%02X%02X%02X%02X%02X",
|
||||
(void)lv_snprintf(p_buf, NC_ROW_BUF_LEN, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
(unsigned)mac[0], (unsigned)mac[1], (unsigned)mac[2],
|
||||
(unsigned)mac[3], (unsigned)mac[4], (unsigned)mac[5]);
|
||||
return;
|
||||
@@ -196,7 +198,7 @@ static void nc_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
|
||||
}
|
||||
|
||||
nc_fmt_value(sel, value);
|
||||
start = (uint32_t)s_cursor * ((sel == 3u) ? 2u : 4u);
|
||||
start = (uint32_t)s_cursor * ((sel == 3u) ? 3u : 4u); /* 字节 i 字符偏移:IP/掩码/网关 4i,MAC 3i(冒号分隔) */
|
||||
seg_len = (sel == 3u) ? 2u : 3u;
|
||||
|
||||
slot_y = NC_TOP_Y + (int32_t)sel * NC_PITCH;
|
||||
@@ -223,10 +225,17 @@ static void nc_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
|
||||
|
||||
{
|
||||
lv_draw_label_dsc_t ld;
|
||||
char seg[4]; /* 段文本:IP/掩码/网关 3 字符、MAC 2 字符,+ '\0' */
|
||||
|
||||
/* 只绘制"段文本"本身:若传整串(value + start),LVGL 在未置 EXPAND 标志时
|
||||
* 按 coords 宽度(此处为段宽)折行,会逐行向盒下方绘制、溢出到其它行/空白区。
|
||||
* 传段文本(宽度 ≤ 盒宽)则单行绘制,恰好落在光标盒内。 */
|
||||
memcpy(seg, value + start, seg_len);
|
||||
seg[seg_len] = '\0';
|
||||
|
||||
lv_draw_label_dsc_init(&ld);
|
||||
ld.base.layer = layer;
|
||||
ld.text = value + start;
|
||||
ld.text = seg;
|
||||
ld.text_static = 0u; /* 栈缓冲(同帧有效) */
|
||||
ld.color = lv_color_white();
|
||||
ld.ofs_x = 1; /* box.x1 = x0-1,+1 = x0(与框架行文本原点一致) */
|
||||
@@ -295,6 +304,9 @@ static bool nc_key_cb(lv_key_t key) {
|
||||
}
|
||||
if (sel == NC_SAVE_ROW) {
|
||||
s_saved = (net_cfg_save() == 0) ? 1u : 2u;
|
||||
if (s_saved == 1u) {
|
||||
net_cfg_request_apply(); /* 立即应用:通知 netTask 重建网络(无需重启) */
|
||||
}
|
||||
ui_content_page_invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ flowchart TB
|
||||
APP --> BSP --> MW --> HAL --> HW
|
||||
```
|
||||
|
||||
> 更详细的全景架构(六层分层 + 任务与就绪屏障 + 启动时序 + 存储/网络/UI 栈 + 目录组织)见 **`docs/项目架构总览.md`**。
|
||||
|
||||
### 启动流程
|
||||
|
||||
```
|
||||
@@ -369,9 +371,11 @@ pre-commit run --all-files
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| `docs/项目架构总览.md` | **项目全景架构**(六层分层 + 任务/就绪屏障 + 启动时序 + 存储/网络/UI 栈 + 目录组织) |
|
||||
| `docs/STM32F4-Base存储架构说明.md` | 三层存储软件栈详解(FatFS → dhara FTL → NAND) |
|
||||
| `docs/项目启动顺序管理.md` | Event Flags 就绪屏障设计 |
|
||||
| `docs/系统墙钟时间维护说明.md` | RTC + 墙钟时间子系统 |
|
||||
| `docs/网络配置说明.md` | 网口设置页配置(UI 可改的 IP/掩码/网关/MAC:持久化 + 立即生效 + 页面操作) |
|
||||
| `docs/UI 设计/UI_设计架构.md` | LVGL UI 系统架构(菜单 / 内容页 / 内存预算) |
|
||||
| `docs/UI 设计/菜单页面设计.md` | 菜单导航行为、数据流与内容页设计 |
|
||||
| `docs/UI 设计/UI_界面制作流程.md` | 参考图 → HTML 样机 → LVGL 代码管线(P0~P5 门禁) |
|
||||
|
||||
@@ -216,6 +216,8 @@ flowchart TD
|
||||
- **文本**:静态行 `text_static=1`(Flash 字符串不拷贝);动态行与页脚页码用栈缓冲
|
||||
(`lv_draw_label` 在同一刷新周期内**同步**光栅化,指针仅需调用期间有效)
|
||||
- **页脚**:`lv_snprintf` 现算(MSVC 对 `snprintf` 中文格式串误报 C4819,故用 LVGL 自带 `lv_snprintf`)
|
||||
|
||||
> ⚠️ **`lv_draw_label` 折行陷阱**:`lv_draw_label` 的换行宽度取 **`coords` 宽度**(未置 `LV_TEXT_FLAG_EXPAND` 时 `w = lv_area_get_width(coords)`)。在**窄区域**(光标盒 / 小方框)里传入**长于该区域的文本**时,LVGL 会按该宽度**折行并逐行向下绘制**,溢出到下方其它行与空白区(表现为"下方弹出一串字符")。规则:窄区域只传"恰好该区域宽度的文本"(如网口设置光标段只传 3 个字符),长文本请在整行宽条带内绘制。
|
||||
- 容器样式为**常量样式**(`LV_STYLE_CONST_INIT`,零 LVGL 池分配),见 `UI_设计架构.md` §5.6
|
||||
|
||||
## 四、页面描述符(content_page_def_t)
|
||||
@@ -473,3 +475,5 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash
|
||||
| 2026-09-09 | 密码页落地(§4.1):框架描述符新增 title_y/hide_deco/hide_footer/key_intercept + 新 API `ui_content_page_invalidate`;新模块 `ui_page_password`(3 数字框反显光标 + arm API,draw_cb+key_cb);菜单受保护叶子路由(arm→开密码页,确认一致后 `menu_password_ok` **先关页再续做进入动作**);字体 97→102 汉字(198 字形);双端注册;回归扩至 7 页 + 往返水位,五门禁全绿 |
|
||||
| 2026-09-09 | 权限模型(替代"每页专属密码",§4.1):表行尾注记 ` #P`(需密码,无类别);`ui_pwd_def.h` 两个编译期密码(查看 000 / 修改 123)+ 新 `ui_perm_t` 权限枚举——密码页输查看密码授只读权限、输修改密码授修改权限(只有修改权限进入后才能改参数);生成器同步改造并重新生成(pwd 列 = 0/1 布尔,`ui_menu_tab_has_pwd`);框架 `ui_content_page_open(id, perm)` 携授予权限 + 描述符 `edit_capable` + 新 API `ui_content_page_is_editable`(页面钩子据以判只读/可编辑);路由密码检查提到内容页判断之前(修复带内容页需密码叶子绕过密码的路由缺口);无密码叶子直接进、默认只读。§4.1 功能规则/待确认表/落地要点同步。验证:五门禁全绿(菜单 8 态 + 7 页像素一致、池 4784/4864 B 不变、Keil 0 错 0 警、代码规范 PASS) |
|
||||
| 2026-09-10 | 网口设置内容页:新增 `Drivers/BSP/NETCFG/`(Flash 持久化 magic 'NCFG'+CRC-16/MODBUS,工厂默认 192.168.1.100/24、gw .1、MAC 全零=芯片内置)+ 页面插件 `ui_page_net_cfg`(5 行动态值 + 可编辑:↑↓调字节 0..255 环绕 / ←→移光标 / ENTER 确认,保存行 ENTER → `net_cfg_save` 下次上电生效);框架新增描述符钩子 `init_cb`(开页重置运行态)+ API `ui_content_page_selected_row`;`net_init` 增 `mac` 参数、netTask 改为等 `SYS_EVT_STORAGE_READY` → `net_cfg_load` → `net_init(ip,mask,gw,mac)` → 回写实际 MAC 影子;模拟器 main 调 `net_cfg_load` 使参考图显示工厂默认值;字体修复:lv_font_conv 丢弃低码位符号(U+FF0C 全角逗号 < 0x4E2D 范围起点 → 页脚逗号长期空白)改由 `merge_extra_symbols()` 补回 + 原本缺字(存/密/户/本/用/钥)→ 111 字 / 208 字形;回归扩至 11 页(网口设置 4 态:可编辑/只读/编辑光标反显/保存 OK),五门禁全绿 |
|
||||
| 2026-09-10 | 修复网口设置页编辑态渲染 bug:光标段原传整串 `value + start`,而 `lv_draw_label` 按 `coords`(光标盒,仅段宽)折行 → 逐行向盒下方绘制、溢出到其它行与空白区("下方弹出一串字符")。改为只传**段文本**(IP/掩码/网关 3 字符、MAC 2 字符);§3.5 补"`lv_draw_label` 折行陷阱"说明 |
|
||||
| 2026-09-10 | 网口设置页 MAC 值改常识格式 **`A4:94:B6:12:34:56`**(冒号分隔,17 字符 = 102px);布局调整 名 x=6→4、值 x=60→54(MAC ink 止于 155,仍在白带 2..157 内);编辑光标段偏移 MAC 2i→3i |
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,89 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="802" viewBox="0 0 1400 802" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1400" height="802" fill="#ffffff"/>
|
||||
<text x="40" y="42" font-size="20" font-weight="700" fill="#0f172a">就绪屏障(Readiness Barrier)· 启动依赖链</text>
|
||||
<text x="40" y="68" font-size="13" fill="#64748b">CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待</text>
|
||||
<rect x="40" y="96" width="16" height="16" rx="4" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="64" y="108" font-size="12" fill="#475569">生产者(set)</text>
|
||||
<rect x="178" y="96" width="16" height="16" rx="4" fill="#ffffff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="202" y="108" font-size="12" fill="#475569">消费者(wait)</text>
|
||||
<rect x="324" y="96" width="16" height="16" rx="4" fill="#ffffff" stroke="#16a34a" stroke-width="2"/>
|
||||
<text x="348" y="108" font-size="12" fill="#475569">事件位(Event Flag)</text>
|
||||
<circle cx="520" cy="104" r="6" fill="#0d9488"/>
|
||||
<text x="536" y="108" font-size="12" fill="#475569">All 汇合(等全部位)</text>
|
||||
<line x1="694" y1="104" x2="710" y2="104" stroke="#94a3b8" stroke-width="2.4" stroke-dasharray="7 5"/>
|
||||
<text x="718" y="108" font-size="12" fill="#475569">预留 / 未接入</text>
|
||||
<rect x="400" y="166" width="420" height="462" rx="14" fill="#f8fafc" stroke="#64748b" stroke-opacity="0.35"/>
|
||||
<text x="416" y="192" font-size="14" font-weight="700" fill="#334155">sys_evtHandle(Event Flags 事件组)</text>
|
||||
<rect x="414" y="200" width="392" height="84" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.6"/>
|
||||
<rect x="428" y="227" width="30" height="30" rx="7" fill="#16a34a"/>
|
||||
<text x="443" y="247" font-size="14" font-weight="700" fill="#ffffff" text-anchor="middle">1</text>
|
||||
<text x="470" y="236" font-size="14" font-weight="700" fill="#16a34a">bit1 · STORAGE_READY</text>
|
||||
<text x="470" y="258" font-size="12" fill="#64748b">defaultTask 置位 · netTask / ftpTask 等待</text>
|
||||
<rect x="414" y="310" width="392" height="84" rx="10" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.6"/>
|
||||
<rect x="428" y="337" width="30" height="30" rx="7" fill="#0d9488"/>
|
||||
<text x="443" y="357" font-size="14" font-weight="700" fill="#ffffff" text-anchor="middle">0</text>
|
||||
<text x="470" y="346" font-size="14" font-weight="700" fill="#0d9488">bit0 · NET_READY</text>
|
||||
<text x="470" y="368" font-size="12" fill="#64748b">netTask 置位 · 4 个任务等待</text>
|
||||
<rect x="414" y="420" width="392" height="84" rx="10" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.6"/>
|
||||
<rect x="428" y="447" width="30" height="30" rx="7" fill="#d97706"/>
|
||||
<text x="443" y="467" font-size="14" font-weight="700" fill="#ffffff" text-anchor="middle">2</text>
|
||||
<text x="470" y="456" font-size="14" font-weight="700" fill="#d97706">bit2 · FTP_READY</text>
|
||||
<text x="470" y="478" font-size="12" fill="#64748b">ftpTask 置位 · 预留消费者</text>
|
||||
<rect x="414" y="530" width="392" height="84" rx="10" fill="#f8fafc" stroke="#94a3b8" stroke-opacity="0.6"/>
|
||||
<rect x="428" y="557" width="30" height="30" rx="7" fill="#94a3b8"/>
|
||||
<text x="443" y="577" font-size="14" font-weight="700" fill="#ffffff" text-anchor="middle">3</text>
|
||||
<text x="470" y="566" font-size="14" font-weight="700" fill="#94a3b8">bit3 · TIMESYNC_READY</text>
|
||||
<text x="470" y="588" font-size="12" fill="#64748b">预留(尚未接入调度)</text>
|
||||
<rect x="40" y="200" width="290" height="84" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
|
||||
<text x="56" y="234" font-size="15" font-weight="700" fill="#0f172a">defaultTask</text>
|
||||
<text x="56" y="258" font-size="12" fill="#64748b">FatFS 挂载(f_mount / f_mkfs)</text>
|
||||
<polyline points="330.0,242.0 400.0,242.0" fill="none" stroke="#16a34a" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 400.0 242.0 L 389.0 247.5 L 389.0 236.5 z" fill="#16a34a"/>
|
||||
<text x="365" y="234.0" font-size="11.5" fill="#16a34a" text-anchor="middle">set bit1</text>
|
||||
<rect x="40" y="310" width="290" height="84" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
|
||||
<text x="56" y="344" font-size="15" font-weight="700" fill="#0f172a">netTask</text>
|
||||
<text x="56" y="368" font-size="12" fill="#64748b">net_stack_bring_up()</text>
|
||||
<polyline points="330.0,352.0 400.0,352.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 400.0 352.0 L 389.0 357.5 L 389.0 346.5 z" fill="#0d9488"/>
|
||||
<text x="365" y="344.0" font-size="11.5" fill="#0d9488" text-anchor="middle">set bit0</text>
|
||||
<rect x="40" y="420" width="290" height="84" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
|
||||
<text x="56" y="454" font-size="15" font-weight="700" fill="#0f172a">ftpTask</text>
|
||||
<text x="56" y="478" font-size="12" fill="#64748b">lftpd_start("/", 21)</text>
|
||||
<polyline points="330.0,462.0 400.0,462.0" fill="none" stroke="#d97706" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 400.0 462.0 L 389.0 467.5 L 389.0 456.5 z" fill="#d97706"/>
|
||||
<text x="365" y="454.0" font-size="11.5" fill="#d97706" text-anchor="middle">set bit2</text>
|
||||
<rect x="910" y="200" width="450" height="84" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.5"/>
|
||||
<text x="926" y="234" font-size="15" font-weight="700" fill="#0f172a">netTask</text>
|
||||
<text x="926" y="258" font-size="12" fill="#64748b">wait bit1 · ≤10s</text>
|
||||
<rect x="910" y="310" width="450" height="84" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.5"/>
|
||||
<text x="926" y="344" font-size="15" font-weight="700" fill="#0f172a">ftpTask</text>
|
||||
<text x="926" y="368" font-size="12" fill="#64748b">wait bit0 + bit1 · All</text>
|
||||
<rect x="910" y="420" width="450" height="84" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.5"/>
|
||||
<text x="926" y="454" font-size="15" font-weight="700" fill="#0f172a">uiTask</text>
|
||||
<text x="926" y="478" font-size="12" fill="#64748b">wait bit0 · Forever</text>
|
||||
<rect x="910" y="530" width="450" height="84" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.5"/>
|
||||
<text x="926" y="564" font-size="15" font-weight="700" fill="#0f172a">timeSyncTask</text>
|
||||
<text x="926" y="588" font-size="12" fill="#64748b">wait bit0 · Forever</text>
|
||||
<rect x="910" y="640" width="450" height="84" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.5"/>
|
||||
<text x="926" y="674" font-size="15" font-weight="700" fill="#0f172a">testTask</text>
|
||||
<text x="926" y="698" font-size="12" fill="#64748b">wait bit0 · NET 模式</text>
|
||||
<polyline points="820.0,242.0 910.0,242.0" fill="none" stroke="#16a34a" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 910.0 242.0 L 899.0 247.5 L 899.0 236.5 z" fill="#16a34a"/>
|
||||
<text x="865" y="234.0" font-size="11.5" fill="#16a34a" text-anchor="middle">wait</text>
|
||||
<polyline points="832.0,242.0 832.0,352.0" fill="none" stroke="#16a34a" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<circle cx="832" cy="352.0" r="5.5" fill="#0d9488"/>
|
||||
<text x="841" y="344.0" font-size="11.5" font-weight="700" fill="#0d9488">All</text>
|
||||
<polyline points="820.0,352.0 862.0,352.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 862.0 352.0 L 851.0 357.5 L 851.0 346.5 z" fill="#0d9488"/>
|
||||
<polyline points="862.0,352.0 862.0,682.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<polyline points="862.0,352.0 910.0,352.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 910.0 352.0 L 899.0 357.5 L 899.0 346.5 z" fill="#0d9488"/>
|
||||
<polyline points="862.0,462.0 910.0,462.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 910.0 462.0 L 899.0 467.5 L 899.0 456.5 z" fill="#0d9488"/>
|
||||
<polyline points="862.0,572.0 910.0,572.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 910.0 572.0 L 899.0 577.5 L 899.0 566.5 z" fill="#0d9488"/>
|
||||
<polyline points="862.0,682.0 910.0,682.0" fill="none" stroke="#0d9488" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 910.0 682.0 L 899.0 687.5 L 899.0 676.5 z" fill="#0d9488"/>
|
||||
<text transform="rotate(-90 850.0 517.0)" x="850.0" y="517.0" font-size="11.5" fill="#0d9488" text-anchor="middle">wait bit0</text>
|
||||
<text x="40" y="784" font-size="12" fill="#94a3b8">生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set,不改已有任务。详见 docs/项目启动顺序管理.md</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
+252
@@ -0,0 +1,252 @@
|
||||
# 网口设置页配置说明
|
||||
|
||||
> 版本:v2.0 日期:2026-09-10
|
||||
> 适用工程:STM32F4-Base(STM32F407ZGTx + CH395F 以太网控制器)
|
||||
> 范围:UI「网口设置」页可配置的网口参数(IP 地址 / 子网掩码 / 默认网关 / MAC 地址)——
|
||||
> 参数与默认值、持久化、生效链路、页面操作与权限。
|
||||
|
||||
---
|
||||
|
||||
## 0. 速览
|
||||
|
||||
| 项目 | 内容 |
|
||||
|---|---|
|
||||
| **可配置** | IP 地址、子网掩码、默认网关、MAC 地址 |
|
||||
| **入口** | 屏幕:`通讯参数 → 网口设置`(修改密码 `123` 可编辑;查看密码 `000` 只读) |
|
||||
| **存储** | NAND Flash 文件 `/cfg/netcfg.bin`(掉电不丢) |
|
||||
| **生效** | 按「保存」后**立即生效**(网络重建约 1~3s,既有 TCP 连接会断开,无需重启) |
|
||||
| **工厂默认** | `192.168.1.100` / `255.255.255.0` / `192.168.1.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.1.100** | 静态 IP(不启用 DHCP) |
|
||||
| 子网掩码 | `mask[4]` | **255.255.255.0** | /24 |
|
||||
| 默认网关 | `gw[4]` | **192.168.1.1** | |
|
||||
| MAC 地址 | `mac[6]` | **全零** | 全零 = 不覆盖芯片内置 MAC(见 §2) |
|
||||
|
||||
工厂默认值是**单一来源**:`net_cfg.c` 顶部 `NET_CFG_DEF_*` 宏,同时用于 `s_cfg` 静态初始化与 `s_cfg_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 磁盘格式(`/cfg/netcfg.bin`)
|
||||
|
||||
`net_cfg.c` 的 `net_cfg_blob_t`(两端布局一致,小端):
|
||||
|
||||
| 偏移 | 字段 | 大小 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 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` |
|
||||
|
||||
- **加载校验**:magic / ver / CRC 任一不符 → 回退工厂默认并打印 `DBG_ERROR`(坏配置不带崩网络)。
|
||||
- **保存**:先建 `/cfg` 目录(`f_mkdir`,已存在不影响),再 `FA_CREATE_ALWAYS` 覆盖写。
|
||||
|
||||
### 3.2 存储后端(双端)
|
||||
|
||||
`net_cfg_store.h` 定义接口,两平台各一份实现(互斥编译):
|
||||
|
||||
| 平台 | 文件 | 位置 |
|
||||
|---|---|---|
|
||||
| MCU | `net_cfg_store_mcu.c` | NAND 上 FatFS 文件 `/cfg/netcfg.bin` |
|
||||
| 模拟器 | `net_cfg_store_sim.c` | 工作目录本地文件 `sim_netcfg.bin`(`fopen`) |
|
||||
|
||||
磁盘格式两端一致,文件可互读。
|
||||
|
||||
---
|
||||
|
||||
## 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: 读 /cfg/netcfg.bin;缺失/损坏 → 工厂默认
|
||||
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` 补零,**定长 11 字符**) |
|
||||
| 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 格式化 |
|
||||
| `Drivers/BSP/NETCFG/net_cfg_store.h` | 存储后端接口(读/写) |
|
||||
| `Drivers/BSP/NETCFG/net_cfg_store_mcu.c` | MCU 后端:FatFS `/cfg/netcfg.bin` |
|
||||
| `Drivers/BSP/NETCFG/net_cfg_store_sim.c` | 模拟器后端:本地 `sim_netcfg.bin` |
|
||||
| `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()` | 从存储加载到 RAM 副本;文件缺失/损坏回退工厂默认(恒返回 0) |
|
||||
| `net_cfg_save()` | 组装磁盘格式写盘;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"` 定长字符串 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 陷阱
|
||||
|
||||
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)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关文档
|
||||
|
||||
| 主题 | 文档 |
|
||||
|---|---|
|
||||
| CH395F 已知陷阱 | `docs/问题记录/CH395F_Trap_Records.md` |
|
||||
| 网络测试指南 | `docs/测试说明/CH395F_Test_Guide.md` |
|
||||
| 启动顺序 / 就绪屏障 | `docs/项目启动顺序管理.md` |
|
||||
| 项目分层架构 | `docs/项目架构总览.md` |
|
||||
| 内容页框架(含网口设置页) | `docs/UI 设计/内容页架构.md` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-09-10 | 初版:网口参数 + 协议栈参数 + 陷阱 |
|
||||
| 2026-09-10 | `net_config.h` 收敛:删除 8 个从未引用的遗留宏;KeepAlive 三宏改为实际生效值并由 `net_socket.c` 引用 |
|
||||
| 2026-09-10 | **保存后立即生效**:新增"应用回调 + 网络代次号"机制 |
|
||||
| 2026-09-10 | §0 速览重写为并列对比表 + 两条生效时机 + 分步操作 |
|
||||
| 2026-09-10 | §1.7 改为客观描述当前行为,补"三个服务的自愈点"表 |
|
||||
| 2026-09-10 | **v2.0 聚焦 UI 页面配置**:删除"协议栈参数(② 编译期常量)"整章;标题 / 范围 / 速览 / 索引 / 陷阱相应收敛为"网口设置页"内容 |
|
||||
| 2026-09-10 | §4.1 上电启动由文本流改为**时序图**(与 §4.2 一致,任务先后关系与配置回退更直观) |
|
||||
| 2026-09-10 | MAC 显示改为常识格式 **`A4:94:B6:12:34:56`**(冒号分隔,17 字符);页面布局左移(名 x=4 / 值 x=54)以容纳 102px 宽度,编辑光标段偏移随之为 3i |
|
||||
@@ -39,6 +39,11 @@
|
||||
|
||||
这与 CubeMX"一次性创建全部任务"的模式完全兼容:任务仍由 `MX_FREERTOS_Init` 统一创建,但各自在入口 `wait`,相当于"创建即挂起、就绪才放行"。
|
||||
|
||||

|
||||
|
||||
> 上图:**左 = 生产者(set)· 中 = 事件组 `sys_evtHandle` · 右 = 消费者(wait)**;任务卡与事件位对齐同一行,`bit1 → ftpTask` 的额外依赖用 **All 汇合点**(`osFlagsWaitAll`)表达。
|
||||
> 源码 `tools/arch_diagram/gen_barrier_svg.py`(改数据重跑即可),PNG 预览本地渲染、不入库。
|
||||
|
||||
### 为什么用 Event Flags 而非全局变量
|
||||
| 维度 | 全局标志 `g_net_ready` | Event Flags |
|
||||
|------|----------------------|-------------|
|
||||
@@ -65,6 +70,7 @@ extern osEventFlagsId_t sys_evtHandle;
|
||||
#define SYS_EVT_STORAGE_READY (1u << 1) /* defaultTask:FTL/FatFS 挂载完成 */
|
||||
#define SYS_EVT_FTP_READY (1u << 2) /* ftpTask:lftpd_start 完成 */
|
||||
#define SYS_EVT_TIMESYNC_READY (1u << 3) /* timeSyncTask:首次时间同步完成 */
|
||||
#define SYS_EVT_NET_RECONFIG (1u << 4) /* UI:请求立即应用新网口配置(netTask 消费后重建网络) */
|
||||
/* 后续新增子系统继续左移分配 bit,32 位上限内即可 */
|
||||
```
|
||||
|
||||
@@ -75,6 +81,7 @@ extern osEventFlagsId_t sys_evtHandle;
|
||||
| `SYS_EVT_STORAGE_READY` | `defaultTask`(挂载成功后) | 依赖文件系统的测试套件(如 STORAGE/GD5F) |
|
||||
| `SYS_EVT_FTP_READY` | `ftpTask`(`lftpd_start` 成功后) | 无(示例,预留给需要 FTP 就绪的后续功能) |
|
||||
| `SYS_EVT_TIMESYNC_READY` | (预留,暂无生产者置位) | 可选:对时精度敏感的业务 |
|
||||
| `SYS_EVT_NET_RECONFIG` | UI 网口设置页保存成功(经 `net_cfg` 应用回调置位) | `netTask` 主循环:消费后 `net_stack_bring_up()` 重建网络(新网口参数立即生效),并递增网络代次号 |
|
||||
|
||||
### 3.3 事件组创建时机
|
||||
`sys_evtHandle` 必须在**任何任务 `wait` 之前**存在。`main.c` 调用顺序为:
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
# STM32F4-Base 项目架构总览
|
||||
|
||||
> 版本:v1.0 日期:2026-09-10
|
||||
> 平台:STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2)+ FreeRTOS V10.3.1(CMSIS-RTOS2)
|
||||
> 用途:一页看清项目的分层、任务、数据流与目录组织。细节以各模块专项文档为准(见 §9)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 全景分层架构
|
||||
|
||||

|
||||
|
||||
> 上图由 `tools/arch_diagram/gen_arch_svg.py` 生成(**手绘 SVG**,布局确定、零连线交叉);改架构后编辑脚本顶部 `ROWS` 数据重跑即可,源数据与 §1.1 表一致。
|
||||
> 需要 PNG 预览(PPT/聊天分享):用 Edge 无头渲染 `docs/项目架构总览.svg` 即可(PNG 不入库)。
|
||||
|
||||
> 分层原则:**上层只依赖相邻下层(单向)**;**① 业务层为顶层**(仅 `net_cfg`,被 UI 与基础服务共用),**② UI 层与 ③ 基础服务层同级并列**。跨层的具体调用关系见 §1.2 表。
|
||||
|
||||
### 1.1 各层职责与关键模块
|
||||
|
||||
> ① 业务层(顶层,仅 `net_cfg`);② UI 层与 ③ 基础服务层**同级并列**(均使用 `net_cfg`);④ 中间件层 → ⑤ 驱动层 → ⑥ 内核/HAL → ⑦ 硬件 依次向下。
|
||||
|
||||
| 层 | 目录 | 职责 | 关键模块 |
|
||||
|---|---|---|---|
|
||||
| ① 业务层 | `Drivers/BSP/NETCFG/` | 顶层共享配置(被 UI 与基础服务共用) | `net_cfg` |
|
||||
| ② UI 层 | `Drivers/BSP/UI/` | LVGL 界面(与 PC 模拟器共用源码) | `ui_port` `ui_menu` `ui_menu_view` `ui_menu_tab` `ui_content_page` `ui_page_*` `ui_net_sink` `ui_font_cn_12` `ui_deco` |
|
||||
| ③ 基础服务层 | `App/`、`Drivers/BSP/NET/lftpd/` | 应用侧服务与编排 | `app_main` `sys_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)` |
|
||||
| ⑤ 驱动层 | `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 |
|
||||
| ⑥ 硬件 | — | STM32F407ZGTx @168MHz | SPI1 / SPI2 / I2C1 / UART5 / USART1 / DMA / GPIO / TIM7 |
|
||||
|
||||
### 1.2 关键跨层依赖(对应上图未画出的连线)
|
||||
|
||||
| 依赖 | 说明 |
|
||||
|---|---|
|
||||
| `ui_page_net_cfg` → `net_cfg` | 网口设置页显示/编辑 RAM 副本(不直接触 CH395F SPI) |
|
||||
| `netTask` → `net_cfg` → `net_socket` | 上电 `net_cfg_load` 后 `net_init(ip,mask,gw,mac)` |
|
||||
| `lftpd` / `net_cfg` / 测试 → `FatFs` → `nand_ftl` → `gd5f2gq5ue` | 文件与配置落 NAND |
|
||||
| `ui_net_sink` / `time_sync` → `net_socket` | 远程显示 :7003 / 对时 :8888 共用同一 TCP 栈 |
|
||||
| `ui_menu` → `ui_content_page` → `ui_page_*` | 框架 + 页面插件(描述符解耦) |
|
||||
| `time_sync` / `sys_clock` → `sd2506` | 墙钟基准与写回 RTC |
|
||||
| `ui_port` / `ui_page_*` → `LVGL` | UI 全部绘制走 LVGL |
|
||||
|
||||
> `*` `adc_task.c` / `rs485_task.c` 任务体已实现,但**当前未在 `freertos.c` 创建**(预留/待接入)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 运行时任务与就绪屏障
|
||||
|
||||
### 2.1 任务一览(`Src/freertos.c` 创建)
|
||||
|
||||
| 任务 | 栈 (字节) | 优先级 | 职责 | 入口 |
|
||||
|---|---|---|---|---|
|
||||
| `defaultTask` | 2048 | Normal | FatFS 挂载(无卷 `f_mkfs`)→ 置 `STORAGE_READY` → LED 闪烁 | `StartDefaultTask` |
|
||||
| `netTask` | 4096 | Normal | 等存储 → `net_stack_bring_up()` → 置 `NET_READY` → `net_poll` 轮询 | `StartNetTask` |
|
||||
| `ftpTask` | 4096 | Normal | 等网络+存储 → `lftpd_start("/", 21)` | `StartFtpTask` |
|
||||
| `testTask` | 4096 | BelowNormal | 测试套件入口(`test/test_config.h` 选择;产品构建空转) | `start_test_task` |
|
||||
| `uiTask` | 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_net_sink` → 10ms 刷新 | `ui_task_func` |
|
||||
| `timeSyncTask` | 2048 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC | `time_sync_task` |
|
||||
|
||||
> `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 48KB heap_4 堆分配。
|
||||
|
||||
### 2.2 就绪屏障(Event Flags,取代魔法延时)
|
||||
|
||||

|
||||
|
||||
> 三栏式:**左 = 生产者(set)· 中 = 事件组 `sys_evtHandle` · 右 = 消费者(wait)**。
|
||||
> 任务卡与事件位对齐同一行,箭头 = `set` / `wait` 关系;`bit1 → ftpTask` 的额外依赖用 **All 汇合点**表达(`osFlagsWaitAll`)。
|
||||
> 源码 `tools/arch_diagram/gen_barrier_svg.py`(改数据重跑即可);PNG 预览本地渲染、不入库。
|
||||
|
||||
**设计原则**:生产者与消费者互不知晓,只经事件组解耦;新增子系统只需定义新 bit + 各自 wait/set,不改已有任务。
|
||||
(详见 `docs/项目启动顺序管理.md`)
|
||||
|
||||
### 2.3 线程间通信
|
||||
|
||||
| 机制 | 对象 | 用途 |
|
||||
|---|---|---|
|
||||
| Event Flags | `sys_evtHandle` | 启动就绪屏障(§2.2) |
|
||||
| Message Queue | `netMsgQueue`(深度 8,`net_msg_t`) | 所有 CH395F 操作经队列**串行化**到 netTask 执行,保证 SPI 单线程访问 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 启动时序
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant M as main
|
||||
participant A as app_main_init
|
||||
participant K as osKernelStart
|
||||
participant D as defaultTask
|
||||
participant N as netTask
|
||||
participant F as ftpTask
|
||||
participant U as uiTask
|
||||
participant T as timeSyncTask
|
||||
|
||||
M->>M: HAL_Init + CubeMX 外设初始化<br/>(SPI1/SPI2/I2C1/UART5/USART1/DMA/GPIO/TIM7)
|
||||
M->>A: app_main_init()
|
||||
Note over A: 时间子系统优先,使后续日志带时间戳
|
||||
A->>A: sd2506_init() → sys_clock_init()(墙钟基准)
|
||||
A->>A: rs485_init() → gd5f2gq5ue_init()(BBT 扫描)
|
||||
A->>A: tpafe5160_init() → SD2506 诊断 → start_conv_irq()
|
||||
A-->>M: "App init OK"
|
||||
M->>K: osKernelStart()
|
||||
Note over K: MX_FREERTOS_Init 已创建 6 任务 + 事件组 + 队列
|
||||
|
||||
K->>D: 调度
|
||||
D->>D: f_mount(无卷则 f_mkfs FAT32)→ LED 循环
|
||||
D-->>N: set SYS_EVT_STORAGE_READY
|
||||
|
||||
N->>N: wait STORAGE_READY(≤10s,超时回退工厂默认)
|
||||
N->>N: net_cfg_load() → net_init(ip,mask,gw,mac)<br/>→ PHY 强制 100M 全双工 → 等链路 ≤3s → MAC 回写影子
|
||||
N-->>F: set SYS_EVT_NET_READY
|
||||
N->>N: 循环 net_poll() + net_process_messages() + osDelay(1)
|
||||
|
||||
F->>F: wait NET_READY|STORAGE_READY(All)→ lftpd_start("/", 21)
|
||||
U->>U: wait NET_READY → ui_port_init → ui_menu_init → ui_net_sink_start(7003)
|
||||
T->>T: wait NET_READY → TCP listen :8888
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 存储栈
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
FS["FatFs (Lib/FatFs)<br/>f_mount / f_open / f_mkfs"]
|
||||
DIO["diskio.c<br/>BSP 适配层"]
|
||||
FTL["nand_ftl.c<br/>dhara FTL(磨损均衡 + 逻辑块映射)"]
|
||||
NAND["gd5f2gq5ue.c<br/>页读写 / ECC / BBT 坏块表"]
|
||||
CHIP[("GD5F2GQ5UE<br/>NAND 2Gbit")]
|
||||
|
||||
subgraph USERS["使用方"]
|
||||
NC["net_cfg<br/>/cfg/netcfg.bin"]
|
||||
LF["lftpd<br/>FTP 文件读写"]
|
||||
ST["测试套件<br/>STORAGE/GD5F"]
|
||||
end
|
||||
|
||||
NC --> FS
|
||||
LF --> FS
|
||||
ST --> FS
|
||||
FS --> DIO --> FTL --> NAND --> CHIP
|
||||
|
||||
subgraph RTC["独立时间源"]
|
||||
SD["SD2506 RTC (I2C1)"] --> SC["sys_clock<br/>墙钟 epoch"]
|
||||
end
|
||||
```
|
||||
|
||||
- 掉电持久化:`net_cfg` 配置(magic `'NCFG'` + ver + CRC-16/MODBUS),**保存后下次上电生效**。
|
||||
- NAND 坏块表(BBT)持久化在保留块池,不交给 dhara 管理。
|
||||
|
||||
---
|
||||
|
||||
## 5. 网络栈与 TCP 消费者
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
CHIP[("CH395F<br/>MAC+PHY · SPI2 + DMA")] --> DRV["ch395f.c<br/>寄存器/命令 + 中断集中读取"]
|
||||
DRV --> NS["net_socket.c<br/>BSD Socket API(单连接模式)"]
|
||||
DRV --> NSEL["net_select.c<br/>select/poll 多路复用"]
|
||||
NS --> Q["netMsgQueue<br/>(串行化到 netTask)"]
|
||||
Q --> NT["netTask<br/>net_poll() + net_process_messages()"]
|
||||
NS --> C1["lftpd<br/>FTP :21"]
|
||||
NS --> C2["time_sync<br/>对时 :8888"]
|
||||
NS --> C3["ui_net_sink<br/>RemoDispBus :7003"]
|
||||
C3 -.->|"回送 1bpp 帧 + 接收远端按键"| CLIENT["DTU-RemoteLCD 客户端"]
|
||||
|
||||
NC["net_cfg<br/>IP/掩码/网关/MAC"] --> NS
|
||||
style NT fill:#efe,stroke:#484
|
||||
```
|
||||
|
||||
**单连接约束**:CH395F 配置为多 Socket + TCP Server 单连接(`set_fun_para(0x09)`),Socket 0 复用监听与数据通道;FTP / 对时 / 远程显示三个 TCP 服务**串行**占用连接,连接断开后自动回到 LISTEN。
|
||||
|
||||
---
|
||||
|
||||
## 6. UI 层架构(框架 + 页面插件)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph LOGIC["逻辑层"]
|
||||
UM["ui_menu.c<br/>级联框状态机 + 按键分发"]
|
||||
MT["ui_menu_tab.c<br/>菜单树生成物(ui_menu_tree.txt)"]
|
||||
end
|
||||
subgraph VIEW["显示层"]
|
||||
MV["ui_menu_view.c<br/>框体/行/装饰自绘"]
|
||||
DECO["ui_deco.c<br/>浅 V 装饰"]
|
||||
end
|
||||
subgraph FRAME["内容页框架"]
|
||||
CP["ui_content_page.c<br/>屏生命周期 + 描述符驱动绘制/按键"]
|
||||
end
|
||||
subgraph PLUGIN["页面插件(每页一文件)"]
|
||||
P1["ui_page_device_info"]
|
||||
P2["ui_page_ac"]
|
||||
P3["ui_page_dc"]
|
||||
P4["ui_page_password"]
|
||||
P5["ui_page_net_cfg"]
|
||||
end
|
||||
subgraph PORT["显示端口"]
|
||||
UP["ui_port.c<br/>160x160 1bpp 缓冲"]
|
||||
SINK["ui_net_sink.c<br/>远程推流"]
|
||||
end
|
||||
|
||||
UM --> MT
|
||||
UM --> MV
|
||||
UM --> CP
|
||||
MV --> DECO
|
||||
CP --> P1 & P2 & P3 & P4 & P5
|
||||
MV --> UP
|
||||
SINK --> UP
|
||||
P5 -.->|"net_cfg RAM 副本"| CFG["net_cfg"]
|
||||
P4 -.->|"ui_pwd_def.h 编译期密码"| PWD["查看 000 / 修改 123"]
|
||||
```
|
||||
|
||||
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),ESC 返回菜单屏。
|
||||
- 菜单受保护叶子(`ui_menu_tree.txt` 行尾 ` #P`):先弹密码页,授权后 `menu_password_ok` **先关页再续进入动作**。
|
||||
- 字体 `ui_font_cn_12.c` 由 `tools/font_gen/` 生成(宋体 12px 1bpp 子集,111 字/208 字形)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 目录组织
|
||||
|
||||
```
|
||||
STM32F4-Base/
|
||||
├── Src/ Inc/ CubeMX 生成:main.c / freertos.c / HAL 外设 / 中断 / 时基
|
||||
│ ├── freertos.c 6 任务 + netMsgQueue + sys_evt 创建(USER CODE 区自定义)
|
||||
│ └── 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
|
||||
├── Drivers/BSP/ 纯手工驱动/服务(不受 CubeMX 保护)
|
||||
│ ├── CH395F/ 以太网 MAC+PHY
|
||||
│ ├── GD5F2GQ5UE/ NAND + dhara FTL
|
||||
│ ├── TPAFE5160/ ADC
|
||||
│ ├── SD2506/ RTC
|
||||
│ ├── RS485/ 半双工串口
|
||||
│ ├── NET/ net_socket / net_select / lftpd
|
||||
│ ├── NETCFG/ 网口配置持久化(MCU + 模拟器双后端)
|
||||
│ ├── UI/ LVGL UI(与模拟器共用)
|
||||
│ └── dbg_log.h/dbg_cfg.h 分级日志
|
||||
├── Lib/ 第三方:LVGL v9.2、FatFs、FreeRTOS、CMSIS
|
||||
├── Middlewares/ ST 官方中间件
|
||||
├── MDK-ARM/ Keil 工程(uvprojx + scatter + build/download 脚本)
|
||||
├── simulator/ PC 模拟器(CMake + SDL2,共用 UI 源码 + 无头回归测试)
|
||||
├── test/ 测试套件(test_config.h 互斥选择,产品构建空转)
|
||||
├── tools/ font_gen(字体生成)/ menu_gen(菜单表生成)
|
||||
├── scripts/ 代码规范检查、RAM 报告等
|
||||
└── docs/ 设计/测试/问题记录文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 构建与双端
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SRC["共享源码<br/>Drivers/BSP/UI/*<br/>Drivers/BSP/NETCFG/net_cfg.c"]
|
||||
SRC --> MCU["MCU 端<br/>Keil MDK-ARM (ARMCC v5)<br/>@build → BUILD_OK"]
|
||||
SRC --> SIM["PC 模拟器<br/>CMake + SDL2 (MSVC)<br/>lvgl_sim.exe --keys"]
|
||||
MCU --> TEST["@test:串口采集 + 烧录 + 日志校验"]
|
||||
SIM --> REG["UI 回归:<br/>check_menu_rows.py<br/>check_content_pages.py"]
|
||||
```
|
||||
|
||||
| 目标 | 工具链 | 验证 |
|
||||
|---|---|---|
|
||||
| MCU 固件 | Keil MDK-ARM v5(`MDK-ARM/build.bat`) | `@build` 0 错 0 警 → `@test` 串口闭环 |
|
||||
| PC 模拟器 | CMake + SDL2 + MSVC(`simulator/build/`) | `check_menu_rows.py` + `check_content_pages.py`(文本断言 + 像素黄金参考) |
|
||||
|
||||
**功能编译开关**:`App/app_cfg.h`(网络/ADC/RS485);`test/test_config.h` 测试套件互斥选择(产品构建全注释)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关专项文档
|
||||
|
||||
| 主题 | 文档 |
|
||||
|---|---|
|
||||
| 启动顺序 / 就绪屏障 | `docs/项目启动顺序管理.md` |
|
||||
| 存储架构(NAND/FTL/FatFs) | `docs/STM32F4-Base存储架构说明.md` |
|
||||
| 墙钟时间维护 | `docs/系统墙钟时间维护说明.md` |
|
||||
| RAM 预算与优化 | `docs/内存管理/RAM使用分析与优化方案.md` |
|
||||
| UI 设计架构 / 内容页 / 菜单 | `docs/UI 设计/UI_设计架构.md` · `内容页架构.md` · `菜单页面设计.md` |
|
||||
| UI 自动化测试 | `docs/UI 设计/UI_自动化测试流程.md` |
|
||||
| 驱动已知陷阱 | `docs/问题记录/{CH395F,GD5F2GQ5UE,SD2506}_Trap_Records.md` |
|
||||
| 各模块测试指南 | `docs/测试说明/` |
|
||||
| 代码规范 | `docs/代码规范/` |
|
||||
|
||||
---
|
||||
|
||||
## 10. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-09-10 | 初版:六层全景架构 + 任务/就绪屏障 + 启动时序 + 存储栈 + 网络栈 + UI 分层 + 目录组织 |
|
||||
| 2026-09-10 | §1 全景分层架构重绘:由"36 节点 + 跨层虚线"改为**分层色带(每层一框)+ 职责表 + 跨层依赖表**,消除连线交叉、提升可读性 |
|
||||
| 2026-09-10 | §1 再改为**手绘 SVG**(`tools/arch_diagram/gen_arch_svg.py` → `项目架构总览.svg`:分层色带 + 模块芯片 + 单向下箭头,布局确定);PNG 预览本地渲染、不入库 |
|
||||
| 2026-09-10 | `net_cfg` 分层定位修正:按项目规划归入 **① 应用层**(原列在 ③ 服务/中间件);SVG 与 §1.1 表同步(模块物理路径仍在 `Drivers/BSP/NETCFG/`) |
|
||||
| 2026-09-10 | §1 布局修正:**应用层与 UI 层同级并列**(同一行并排,均向下依赖);生成器数据由"每层一行"改为 `ROWS`(行内可放多个并列层),顶层两框各引一条向下箭头 |
|
||||
| 2026-09-10 | §1 层级重定(按项目规划):**① 业务层**(改名,原"应用层")为**顶层**,含 `net_cfg`(被 UI 与基础服务共用)+ 业务编排;**② UI 层与 ③ 基础服务层同级并列**其下;`time_sync` 归基础服务层 |
|
||||
| 2026-09-10 | §1 定稿(严格版):**① 业务层顶层仅 `net_cfg` 一块**;`app_main`/`sys_clock`/`sys_events`/`app_cfg`/`task/*`/`util/*` 并入 **③ 基础服务层**(任务编排 + 网络/存储/图形);② UI 层与 ③ 同级并列 |
|
||||
| 2026-09-10 | §1 新增 **④ 中间件层**(通用库 / 协议栈:`net_socket`·`net_select`·`lftpd`·`FatFs`·`nand_ftl`·`LVGL`·`util`),位于 ③ 基础服务层与驱动层之间;③ 基础服务层收敛为**应用侧服务与编排**(`app_main`·`sys_clock`·`sys_events`·`app_cfg`·`task/*`·`time_sync`);层号顺延至 ⑦ 硬件 |
|
||||
| 2026-09-10 | `lftpd` 由 ④ 中间件层移入 **③ 基础服务层**(FTP 视为应用侧服务);中间件层余 `net_socket`·`net_select`·`FatFs`·`nand_ftl`·`LVGL`·`util` |
|
||||
| 2026-09-10 | §2.2 就绪屏障图由 Mermaid 改为**手绘 SVG**(`tools/arch_diagram/gen_barrier_svg.py` → `就绪屏障.svg`):三栏(生产者 / 事件组 / 消费者)+ 水平对齐 + All 汇合点,消除原图 9 条边的交叉;PNG 预览本地渲染、不入库 |
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1560" height="1000" viewBox="0 0 1560 1000" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1560" height="1000" fill="#ffffff"/>
|
||||
<text x="26" y="40" font-size="20" font-weight="700" fill="#0f172a">STM32F4-Base 项目分层架构</text>
|
||||
<text x="26" y="62" font-size="13" fill="#64748b">STM32F407ZGTx @168MHz · FreeRTOS V10.3.1 (CMSIS-RTOS2) · 上层只依赖相邻下层</text>
|
||||
<rect x="26" y="78" width="1508" height="92" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.45"/>
|
||||
<text x="44" y="104" font-size="17" font-weight="700" fill="#2563eb">① 业务层</text>
|
||||
<text x="134" y="104" font-size="12" fill="#64748b">顶层共享配置 · 被 UI 与基础服务共用</text>
|
||||
<rect x="44" y="116" width="394" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
|
||||
<text x="241" y="139" font-size="13" fill="#1e293b" text-anchor="middle">net_cfg(网口配置:IP/掩码/网关/MAC + Flash 持久化)</text>
|
||||
<rect x="26" y="214" width="741" height="188" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.45"/>
|
||||
<text x="44" y="240" font-size="17" font-weight="700" fill="#7c3aed">② UI 层</text>
|
||||
<text x="129" y="240" font-size="12" fill="#64748b">Drivers/BSP/UI · 与 PC 模拟器共用</text>
|
||||
<rect x="44" y="252" width="162" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="125" y="275" font-size="13" fill="#1e293b" text-anchor="middle">ui_port(显示端口)</text>
|
||||
<rect x="216" y="252" width="275" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="354" y="275" font-size="13" fill="#1e293b" text-anchor="middle">ui_menu / ui_menu_view(级联菜单)</text>
|
||||
<rect x="502" y="252" width="179" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="592" y="275" font-size="13" fill="#1e293b" text-anchor="middle">ui_menu_tab(菜单表)</text>
|
||||
<rect x="44" y="300" width="236" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="162" y="323" font-size="13" fill="#1e293b" text-anchor="middle">ui_content_page(内容页框架)</text>
|
||||
<rect x="290" y="300" width="179" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="380" y="323" font-size="13" fill="#1e293b" text-anchor="middle">ui_page_*(5 页插件)</text>
|
||||
<rect x="480" y="300" width="238" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="599" y="323" font-size="13" fill="#1e293b" text-anchor="middle">ui_net_sink(远程显示 :7003)</text>
|
||||
<rect x="44" y="348" width="208" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="148" y="371" font-size="13" fill="#1e293b" text-anchor="middle">ui_font_cn_12(中文字体)</text>
|
||||
<rect x="262" y="348" width="172" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="348" y="371" font-size="13" fill="#1e293b" text-anchor="middle">ui_deco(浅 V 装饰)</text>
|
||||
<rect x="793" y="214" width="741" height="188" rx="12" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.45"/>
|
||||
<text x="811" y="240" font-size="17" font-weight="700" fill="#0d9488">③ 基础服务层</text>
|
||||
<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="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>
|
||||
<rect x="44" y="484" width="284" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
|
||||
<text x="186" y="507" font-size="13" fill="#1e293b" text-anchor="middle">net_socket / net_select(BSD API)</text>
|
||||
<rect x="338" y="484" width="147" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
|
||||
<text x="412" y="507" font-size="13" fill="#1e293b" text-anchor="middle">FatFs(文件系统)</text>
|
||||
<rect x="495" y="484" width="186" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
|
||||
<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="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>
|
||||
<rect x="44" y="620" width="178" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="133" y="643" font-size="13" fill="#1e293b" text-anchor="middle">ch395f(SPI2 + DMA)</text>
|
||||
<rect x="232" y="620" width="163" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="314" y="643" font-size="13" fill="#1e293b" text-anchor="middle">gd5f2gq5ue(SPI1)</text>
|
||||
<rect x="406" y="620" width="195" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="503" y="643" font-size="13" fill="#1e293b" text-anchor="middle">tpafe5160(并行 16 位)</text>
|
||||
<rect x="611" y="620" width="133" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="677" y="643" font-size="13" fill="#1e293b" text-anchor="middle">sd2506(I2C1)</text>
|
||||
<rect x="754" y="620" width="171" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="840" y="643" font-size="13" fill="#1e293b" text-anchor="middle">rs485(UART5 + DE)</text>
|
||||
<rect x="26" y="718" width="1508" height="92" rx="12" fill="#f1f5f9" stroke="#475569" stroke-opacity="0.45"/>
|
||||
<text x="44" y="744" font-size="17" font-weight="700" fill="#475569">⑥ 内核 / HAL</text>
|
||||
<text x="176" y="744" font-size="12" fill="#64748b">调度 · 外设抽象</text>
|
||||
<rect x="44" y="756" width="329" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
|
||||
<text x="208" y="779" font-size="13" fill="#1e293b" text-anchor="middle">FreeRTOS V10.3.1(CMSIS-RTOS2 · heap_4)</text>
|
||||
<rect x="383" y="756" width="189" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
|
||||
<text x="478" y="779" font-size="13" fill="#1e293b" text-anchor="middle">STM32 HAL(时基 TIM7)</text>
|
||||
<rect x="582" y="756" width="144" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
|
||||
<text x="655" y="779" font-size="13" fill="#1e293b" text-anchor="middle">CMSIS / 启动文件</text>
|
||||
<rect x="26" y="854" width="1508" height="92" rx="12" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
|
||||
<text x="44" y="880" font-size="17" font-weight="700" fill="#e11d48">⑦ 硬件</text>
|
||||
<text x="117" y="880" font-size="12" fill="#64748b">STM32F407ZGTx @168MHz</text>
|
||||
<rect x="44" y="892" width="179" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="133" y="915" font-size="13" fill="#1e293b" text-anchor="middle">CH395F 以太网(SPI2)</text>
|
||||
<rect x="233" y="892" width="201" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="334" y="915" font-size="13" fill="#1e293b" text-anchor="middle">GD5F2GQ5UE NAND(SPI1)</text>
|
||||
<rect x="445" y="892" width="182" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="536" y="915" font-size="13" fill="#1e293b" text-anchor="middle">TPAFE5160 ADC(并行)</text>
|
||||
<rect x="637" y="892" width="163" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="718" y="915" font-size="13" fill="#1e293b" text-anchor="middle">SD2506 RTC(I2C1)</text>
|
||||
<rect x="810" y="892" width="140" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="881" y="915" font-size="13" fill="#1e293b" text-anchor="middle">RS-485(UART5)</text>
|
||||
<rect x="961" y="892" width="182" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="1052" y="915" font-size="13" fill="#1e293b" text-anchor="middle">USART1 调试(115200)</text>
|
||||
<rect x="1153" y="892" width="77" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="1192" y="915" font-size="13" fill="#1e293b" text-anchor="middle">6x LED</text>
|
||||
<line x1="780.0" y1="176.0" x2="780.0" y2="199.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 780.0 206.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="396.5" y1="408.0" x2="396.5" y2="431.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 396.5 438.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="1163.5" y1="408.0" x2="1163.5" y2="431.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 1163.5 438.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="780.0" y1="544.0" x2="780.0" y2="567.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 780.0 574.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="780.0" y1="680.0" x2="780.0" y2="703.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 780.0 710.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="780.0" y1="816.0" x2="780.0" y2="839.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 780.0 846.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<text x="26" y="982" font-size="12" fill="#94a3b8">上层只依赖相邻下层(单向);UI 层与基础服务层同级并列,均使用顶层 net_cfg。跨层调用细节见 docs/项目架构总览.md §1.2 * 预留未接入调度</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -71,7 +71,7 @@ TEXTS = {
|
||||
("r0名", "IP地址", (24, 38), True), ("r0值", "192.168.001.100", (24, 38), True),
|
||||
("r1名", "子网掩码", (38, 52), False), ("r1值", "255.255.255.000", (38, 52), False),
|
||||
("r2名", "默认网关", (52, 66), False), ("r2值", "192.168.001.001", (52, 66), False),
|
||||
("r3名", "MAC地址", (66, 80), False), ("r3值", "000000000000", (66, 80), False),
|
||||
("r3名", "MAC地址", (66, 80), False), ("r3值", "00:00:00:00:00:00", (66, 80), False),
|
||||
("r4名", "保存", (80, 95), False), ("r4值", "ENTER", (80, 95), False),
|
||||
("页脚", "第 1 页,共 1 页", (140, 160), False),
|
||||
],
|
||||
@@ -80,7 +80,7 @@ TEXTS = {
|
||||
("r0名", "IP地址", (24, 38), True), ("r0值", "192.168.001.100", (24, 38), True),
|
||||
("r1名", "子网掩码", (38, 52), False), ("r1值", "255.255.255.000", (38, 52), False),
|
||||
("r2名", "默认网关", (52, 66), False), ("r2值", "192.168.001.001", (52, 66), False),
|
||||
("r3名", "MAC地址", (66, 80), False), ("r3值", "000000000000", (66, 80), False),
|
||||
("r3名", "MAC地址", (66, 80), False), ("r3值", "00:00:00:00:00:00", (66, 80), False),
|
||||
("r4名", "保存", (80, 95), False), ("r4值", "ENTER", (80, 95), False),
|
||||
("页脚", "第 1 页,共 1 页", (140, 160), False),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/项目架构总览.svg —— 分层架构图(纯标准库,无需第三方依赖)。
|
||||
|
||||
为什么不用 Mermaid:自动布局不可控,多节点/跨层依赖会画成交叉的"意大利面"。
|
||||
本脚本手工排布:每层一个圆角框(标题在框内顶部 + 模块芯片自动换行),
|
||||
同一"行"可放多个并列层(如 应用层 与 UI 层 同级并排),行间单向下箭头。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_arch_svg.py
|
||||
输出:
|
||||
docs/项目架构总览.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", "项目架构总览.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
W = 1560
|
||||
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_BIZ = dict(
|
||||
title="① 业务层", sub="顶层共享配置 · 被 UI 与基础服务共用", color="#2563eb", bg="#eff6ff",
|
||||
chips=["net_cfg(网口配置:IP/掩码/网关/MAC + Flash 持久化)"],
|
||||
)
|
||||
L_UI = dict(
|
||||
title="② UI 层", sub="Drivers/BSP/UI · 与 PC 模拟器共用", color="#7c3aed", bg="#f5f3ff",
|
||||
chips=["ui_port(显示端口)", "ui_menu / ui_menu_view(级联菜单)", "ui_menu_tab(菜单表)",
|
||||
"ui_content_page(内容页框架)", "ui_page_*(5 页插件)",
|
||||
"ui_net_sink(远程显示 :7003)", "ui_font_cn_12(中文字体)", "ui_deco(浅 V 装饰)"],
|
||||
)
|
||||
L_SVC = dict(
|
||||
title="③ 基础服务层", sub="应用侧服务 / 编排", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["app_main(启动编排)", "sys_clock(墙钟基准)", "sys_events(就绪屏障)",
|
||||
"app_cfg(编译开关)", "task/(net · ui · adc* · rs485*)",
|
||||
"lftpd(FTP :21)", "time_sync(TCP 对时 :8888)"],
|
||||
)
|
||||
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)"],
|
||||
)
|
||||
L_DRV = dict(
|
||||
title="⑤ 驱动层", sub="Drivers/BSP · 芯片时序/中断", color="#d97706", bg="#fffbeb",
|
||||
chips=["ch395f(SPI2 + DMA)", "gd5f2gq5ue(SPI1)", "tpafe5160(并行 16 位)",
|
||||
"sd2506(I2C1)", "rs485(UART5 + DE)"],
|
||||
)
|
||||
L_KERN = dict(
|
||||
title="⑥ 内核 / HAL", sub="调度 · 外设抽象", color="#475569", bg="#f1f5f9",
|
||||
chips=["FreeRTOS V10.3.1(CMSIS-RTOS2 · heap_4)", "STM32 HAL(时基 TIM7)",
|
||||
"CMSIS / 启动文件"],
|
||||
)
|
||||
L_HW = dict(
|
||||
title="⑦ 硬件", sub="STM32F407ZGTx @168MHz", color="#e11d48", bg="#fff1f2",
|
||||
chips=["CH395F 以太网(SPI2)", "GD5F2GQ5UE NAND(SPI1)", "TPAFE5160 ADC(并行)",
|
||||
"SD2506 RTC(I2C1)", "RS-485(UART5)", "USART1 调试(115200)", "6x LED"],
|
||||
)
|
||||
|
||||
# 每一行可放多个"同级并列"的层(上层只依赖下方各层)
|
||||
ROWS = [
|
||||
[L_BIZ], # 顶层:业务层(仅共享配置 net_cfg)
|
||||
[L_UI, L_SVC], # UI 层与基础服务层同级并排,均使用 net_cfg
|
||||
[L_MW], # 中间件层(通用库 / 协议栈)
|
||||
[L_DRV],
|
||||
[L_KERN],
|
||||
[L_HW],
|
||||
]
|
||||
|
||||
FOOT = "上层只依赖相邻下层(单向);UI 层与基础服务层同级并列,均使用顶层 net_cfg。跨层调用细节见 docs/项目架构总览.md §1.2 * 预留未接入调度"
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
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("STM32F4-Base 项目分层架构")))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, 62, SUB_FONT,
|
||||
esc("STM32F407ZGTx @168MHz · FreeRTOS V10.3.1 (CMSIS-RTOS2) · 上层只依赖相邻下层")))
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
|
||||
|
||||
为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。
|
||||
本脚本手工排布为三栏:
|
||||
左 = 生产者(set) 中 = 事件组 sys_evtHandle 右 = 消费者(wait)
|
||||
任务卡与事件位各自对齐到同一水平行,箭头基本水平/正交;bit1 → ftpTask 的
|
||||
"与"依赖用汇合点(All)表达,避免跨行连线。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_barrier_svg.py
|
||||
输出:
|
||||
docs/就绪屏障.svg
|
||||
改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。
|
||||
"""
|
||||
import html
|
||||
import math
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "就绪屏障.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸 -----------------------------
|
||||
W = 1400
|
||||
PAD = 40
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
CARD_TITLE_FONT = 15
|
||||
CARD_NOTE_FONT = 12
|
||||
BIT_TITLE_FONT = 14
|
||||
BIT_NOTE_FONT = 12
|
||||
LABEL_FONT = 11.5
|
||||
FOOT_FONT = 12
|
||||
|
||||
H_CARD = 84 # 任务卡 / 事件位行高
|
||||
GAP = 26 # 行间距
|
||||
MAIN_Y = 200 # 首行 y
|
||||
CARD_W_L = 290 # 生产者卡宽
|
||||
CARD_W_R = 450 # 消费者卡宽
|
||||
PANEL_X, PANEL_W = 400, 420
|
||||
PROD_X = 40
|
||||
CONS_X = 910
|
||||
TAP_X = 832 # bit1 → All 汇合点的下引线 x
|
||||
BUS_X = 862 # bit0 分发总线 x
|
||||
ARROW_W = 2.2
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
C_PROD, BG_PROD = "#2563eb", "#eff6ff"
|
||||
C_CONS, BG_CONS = "#7c3aed", "#f5f3ff"
|
||||
|
||||
# ----------------------------- 数据(改这里) -----------------------------
|
||||
# 事件位(中间栏),自上而下 = 时序先后
|
||||
BITS = [
|
||||
dict(bit="bit1", flag="STORAGE_READY", color="#16a34a", bg="#f0fdf4",
|
||||
note="defaultTask 置位 · netTask / ftpTask 等待"),
|
||||
dict(bit="bit0", flag="NET_READY", color="#0d9488", bg="#f0fdfa",
|
||||
note="netTask 置位 · 4 个任务等待"),
|
||||
dict(bit="bit2", flag="FTP_READY", color="#d97706", bg="#fffbeb",
|
||||
note="ftpTask 置位 · 预留消费者"),
|
||||
dict(bit="bit3", flag="TIMESYNC_READY", color="#94a3b8", bg="#f8fafc",
|
||||
note="预留(尚未接入调度)"),
|
||||
]
|
||||
|
||||
# 生产者(左栏):row = 其置位的 bit 行
|
||||
PRODUCERS = [
|
||||
dict(name="defaultTask", note="FatFS 挂载(f_mount / f_mkfs)", row=0, label="set bit1"),
|
||||
dict(name="netTask", note="net_stack_bring_up()", row=1, label="set bit0"),
|
||||
dict(name="ftpTask", note='lftpd_start("/", 21)', row=2, label="set bit2"),
|
||||
]
|
||||
|
||||
# 消费者(右栏):row = 卡片排布行(前两行与 bit1 / bit0 对齐)
|
||||
CONSUMERS = [
|
||||
dict(name="netTask", note="wait bit1 · ≤10s", row=0),
|
||||
dict(name="ftpTask", note="wait bit0 + bit1 · All", row=1),
|
||||
dict(name="uiTask", note="wait bit0 · Forever", row=2),
|
||||
dict(name="timeSyncTask", note="wait bit0 · Forever", row=3),
|
||||
dict(name="testTask", note="wait bit0 · NET 模式", row=4),
|
||||
]
|
||||
|
||||
FOOT = ("生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set,"
|
||||
"不改已有任务。详见 docs/项目启动顺序管理.md")
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
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) >= 0x2E80 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
def row_y(r):
|
||||
return MAIN_Y + r * (H_CARD + GAP)
|
||||
|
||||
def row_cy(r):
|
||||
return row_y(r) + H_CARD / 2.0
|
||||
|
||||
def poly(points, color, dash=False, head=True, width=ARROW_W):
|
||||
pts = " ".join("%.1f,%.1f" % p for p in points)
|
||||
d = ' stroke-dasharray="7 5"' if dash else ''
|
||||
add('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f" '
|
||||
'stroke-linejoin="round"%s/>' % (pts, color, width, d))
|
||||
if head:
|
||||
(x1, y1), (x2, y2) = points[-2], points[-1]
|
||||
ang = math.atan2(y2 - y1, x2 - x1)
|
||||
hl, hw = 11.0, 5.5
|
||||
bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang)
|
||||
px, py = -math.sin(ang) * hw, math.cos(ang) * hw
|
||||
add('<path d="M %.1f %.1f L %.1f %.1f L %.1f %.1f z" fill="%s"/>'
|
||||
% (x2, y2, bx + px, by + py, bx - px, by - py, color))
|
||||
|
||||
def card(x, y, w, title, note, color, bg):
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" '
|
||||
'stroke="%s" stroke-opacity="0.5"/>' % (x, y, w, H_CARD, bg, color))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (x + 16, y + 34, CARD_TITLE_FONT, esc(title)))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (x + 16, y + 58, CARD_NOTE_FONT, esc(note)))
|
||||
|
||||
# 行/画布尺寸
|
||||
n_rows = max(len(CONSUMERS), len(BITS))
|
||||
main_bottom = row_y(n_rows - 1) + H_CARD
|
||||
total_h = main_bottom + 78
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
|
||||
# ---- 标题 ----
|
||||
add('<text x="%d" y="42" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, TITLE_FONT, esc("就绪屏障(Readiness Barrier)· 启动依赖链")))
|
||||
add('<text x="%d" y="68" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, SUB_FONT,
|
||||
esc("CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待")))
|
||||
|
||||
# ---- 图例 ----
|
||||
lx, ly = PAD, 108
|
||||
for kind, color, label in [
|
||||
("rect", C_PROD, "生产者(set)"),
|
||||
("rect", C_CONS, "消费者(wait)"),
|
||||
("rect", "#16a34a", "事件位(Event Flag)"),
|
||||
("dot", "#0d9488", "All 汇合(等全部位)"),
|
||||
("dash", "#94a3b8", "预留 / 未接入"),
|
||||
]:
|
||||
if kind == "rect":
|
||||
add('<rect x="%d" y="%d" width="16" height="16" rx="4" fill="#ffffff" '
|
||||
'stroke="%s" stroke-width="2"/>' % (lx, ly - 12, color))
|
||||
elif kind == "dot":
|
||||
add('<circle cx="%d" cy="%d" r="6" fill="%s"/>' % (lx + 8, ly - 4, color))
|
||||
else:
|
||||
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2.4" '
|
||||
'stroke-dasharray="7 5"/>' % (lx, ly - 4, lx + 16, ly - 4, color))
|
||||
lx += 24
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#475569">%s</text>' % (lx, ly, esc(label)))
|
||||
lx += text_w(label, 12) + 34
|
||||
|
||||
# ---- 事件组面板 ----
|
||||
py0 = MAIN_Y - 34
|
||||
py1 = row_y(len(BITS) - 1) + H_CARD + 14
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
|
||||
'stroke="#64748b" stroke-opacity="0.35"/>' % (PANEL_X, py0, PANEL_W, py1 - py0))
|
||||
head = "sys_evtHandle(Event Flags 事件组)"
|
||||
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#334155">%s</text>'
|
||||
% (PANEL_X + 16, py0 + 26, esc(head)))
|
||||
for i, b in enumerate(BITS):
|
||||
by = row_y(i)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.6"/>' % (PANEL_X + 14, by, PANEL_W - 28, H_CARD, b["bg"], b["color"]))
|
||||
add('<rect x="%d" y="%d" width="30" height="30" rx="7" fill="%s"/>'
|
||||
% (PANEL_X + 28, by + (H_CARD - 30) / 2, b["color"]))
|
||||
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#ffffff" '
|
||||
'text-anchor="middle">%s</text>'
|
||||
% (PANEL_X + 43, by + H_CARD / 2 + 5, esc(b["bit"][3])))
|
||||
tx = PANEL_X + 70
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (tx, by + 36, BIT_TITLE_FONT, b["color"], esc(b["bit"] + " · " + b["flag"])))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (tx, by + 58, BIT_NOTE_FONT, esc(b["note"])))
|
||||
|
||||
# ---- 生产者卡 + set 箭头 ----
|
||||
for p in PRODUCERS:
|
||||
y, cy = row_y(p["row"]), row_cy(p["row"])
|
||||
card(PROD_X, y, CARD_W_L, p["name"], p["note"], C_PROD, BG_PROD)
|
||||
poly([(PROD_X + CARD_W_L, cy), (PANEL_X, cy)], BITS[p["row"]]["color"])
|
||||
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
|
||||
% ((PROD_X + CARD_W_L + PANEL_X) / 2.0, cy - 8, LABEL_FONT,
|
||||
BITS[p["row"]]["color"], esc(p["label"])))
|
||||
|
||||
# ---- 消费者卡 ----
|
||||
for c in CONSUMERS:
|
||||
card(CONS_X, row_y(c["row"]), CARD_W_R, c["name"], c["note"], C_CONS, BG_CONS)
|
||||
|
||||
# ---- wait 连线:bit1 → netTask(水平) ----
|
||||
c0, c1 = row_cy(0), row_cy(1)
|
||||
col1, col0 = BITS[0]["color"], BITS[1]["color"]
|
||||
poly([(PANEL_X + PANEL_W, c0), (CONS_X, c0)], col1)
|
||||
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
|
||||
% ((PANEL_X + PANEL_W + CONS_X) / 2.0, c0 - 8, LABEL_FONT, col1, esc("wait")))
|
||||
|
||||
# ---- wait 连线:bit1 → All 汇合点(下引,与 bit0 相与) ----
|
||||
poly([(TAP_X, c0), (TAP_X, c1)], col1, head=False)
|
||||
add('<circle cx="%d" cy="%.1f" r="5.5" fill="%s"/>' % (TAP_X, c1, col0))
|
||||
add('<text x="%d" y="%.1f" font-size="%s" font-weight="700" fill="%s">All</text>'
|
||||
% (TAP_X + 9, c1 - 8, LABEL_FONT, col0))
|
||||
|
||||
# ---- wait 连线:bit0 → 总线 → 4 个消费者 ----
|
||||
c_last = row_cy(len(CONSUMERS) - 1)
|
||||
poly([(PANEL_X + PANEL_W, c1), (BUS_X, c1)], col0)
|
||||
poly([(BUS_X, c1), (BUS_X, c_last)], col0, head=False)
|
||||
for c in CONSUMERS[1:]:
|
||||
poly([(BUS_X, row_cy(c["row"])), (CONS_X, row_cy(c["row"]))], col0)
|
||||
mid_y = (c1 + c_last) / 2.0
|
||||
add('<text transform="rotate(-90 %.1f %.1f)" x="%.1f" y="%.1f" font-size="%s" '
|
||||
'fill="%s" text-anchor="middle">%s</text>'
|
||||
% (BUS_X - 12, mid_y, BUS_X - 12, mid_y, LABEL_FONT, col0, esc("wait bit0")))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, FOOT_FONT, 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