From 26c9098630cdde3c426e3b49bc9a7d5a21cc281f Mon Sep 17 00:00:00 2001 From: wandering1 <1624155937@qq.com> Date: Sat, 29 Aug 2026 11:10:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E9=A1=BA=E5=BA=8F=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/sys_events.h | 35 +++++ App/task/net_task.c | 30 ++++- App/task/net_task.h | 2 +- App/time_sync.c | 8 +- MDK-ARM/STM32F407-Demo.uvprojx | 20 --- MDK-ARM/build_log.txt | 82 ++++++------ STM32F407-Demo.ioc | 3 +- Src/freertos.c | 31 ++++- Src/main.c | 2 +- docs/测试开发标准流程.md | 2 +- docs/测试说明/CH395F_Test_Guide.md | 4 +- docs/项目启动顺序管理.md | 206 +++++++++++++++++++++++++++++ test/ch395f_test_task.c | 10 +- 13 files changed, 351 insertions(+), 84 deletions(-) create mode 100644 App/sys_events.h create mode 100644 docs/项目启动顺序管理.md diff --git a/App/sys_events.h b/App/sys_events.h new file mode 100644 index 00000000..1ba5c839 --- /dev/null +++ b/App/sys_events.h @@ -0,0 +1,35 @@ +/* + * 模块名称:系统就绪事件(启动顺序管理) + * 模块功能:用 CMSIS-RTOS v2 Event Flags 表达"子系统就绪",作为任务启动顺序的 + * 就绪屏障(readiness barrier)。生产者(netTask / defaultTask 等)在自身 + * 就绪后置位对应 bit;消费者(ftpTask / start_test_task / timeSyncTask 等) + * 在入口阻塞等待依赖的 bit,就绪才放行,取代魔法延时与全局标志轮询。 + * 适用平台:STM32F4 系列(CMSIS-RTOS V2) + * 作者:王建锋 + * 创建日期:2026-08-29 + */ + +#ifndef __SYS_EVENTS_H +#define __SYS_EVENTS_H + +#include "cmsis_os2.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 系统就绪事件组句柄(CubeMX 生成:FREERTOS Events 对象 sys_evt, + * 由 MX_FREERTOS_Init 在 freertos.c 的 generated 区域自动声明并 osEventFlagsNew 创建) */ +extern osEventFlagsId_t sys_evtHandle; + +/* 各子系统"就绪"事件位(32 位上限内的 bit 0~30) */ +#define SYS_EVT_NET_READY (1u << 0) /* netTask:net_init + PHY 稳定完成 */ +#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:首次时间同步完成 */ + +#ifdef __cplusplus +} +#endif + +#endif /* __SYS_EVENTS_H */ diff --git a/App/task/net_task.c b/App/task/net_task.c index c9283240..79452011 100644 --- a/App/task/net_task.c +++ b/App/task/net_task.c @@ -18,18 +18,16 @@ #include "net_socket.h" #include "net_select.h" #include "ch395f.h" +#include "sys_events.h" #define DBG_TAG "[NET_TASK]" #include "dbg_log.h" -/* 网络初始化完成标志(供 ch395f_test_task 等外部任务等待) */ -volatile uint8_t g_net_ready = 0; - /* * 函数功能:CH395F 协议栈一次性初始化(两种构建模式共用) * 入口参数:无 * 返 回 值:0 - 成功,-1 - 失败 - * 函数说明:net_init → MAC 打印 → PHY 强制 100M 全双工 → 置 g_net_ready。 + * 函数说明:net_init → MAC 打印 → PHY 强制 100M 全双工 → 广播网络就绪事件。 * NET 层模式由 netTask 调用;硬件层模式(阶段 1~5)netTask 不创建, * 由 testTask 调用后独占 CH395F。 */ @@ -56,8 +54,22 @@ int net_stack_bring_up(void) ipinf[8], ipinf[9], ipinf[10], ipinf[11]); ch395f_set_phy(CH395F_PHY_100M_FULL); - HAL_Delay(3000); - uint8_t phy = ch395f_get_phy_status(); + + /* 强制 PHY 模式后链路会短暂掉线再重建,CH395F 会锁存 GINT_STAT_PHY_CHANGE(0x04) 全局中断标志。 + * 这里轮询该标志等待"链路状态变化"(每次读取即清除),同时用 ch395f_get_phy_status() + * 确认链路是否已真正建立——避免因 PHY_CHANGE 只反映"掉线"边沿而误判就绪。 + * 链路一就绪(phy != DISCONN)即跳出;最多等约 3s(60 × 50ms),超时仍按实际状态继续(由下方日志暴露问题)。 + * 注:此处读取全局中断状态属"启动期一次性链路等待",net_poll 尚未启动,不与其中断集中处理冲突; + * NET 模式下该标志后续仍会被 net_poll 正常消费/清除(见 net_socket.c)。 */ + uint8_t phy = CH395F_PHY_DISCONN; + for (unsigned int i = 0U; i < 60U; i++) { + (void)ch395f_get_glob_int_status_all(); /* 读取并清除 PHY_CHANGE 等全局中断标志 */ + phy = ch395f_get_phy_status(); + if (phy != CH395F_PHY_DISCONN) { + break; + } + osDelay(50); + } DBG_INFO("PHY: %s", (phy == CH395F_PHY_100M_FULL) ? "100M FULL" : (phy == CH395F_PHY_100M_HALF) ? "100M HALF" : @@ -65,7 +77,6 @@ int net_stack_bring_up(void) (phy == CH395F_PHY_10M_HALF) ? "10M HALF" : (phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN"); - g_net_ready = 1; return 0; } @@ -102,6 +113,11 @@ void net_task_func(void const *arg) osDelay(1000); } } + + /* 广播"网络就绪",唤醒等待它的 ftpTask / start_test_task / timeSyncTask 等 */ + if (sys_evtHandle != NULL) { + osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY); + } DBG_INFO("netTask: started (single-socket mode)"); diff --git a/App/task/net_task.h b/App/task/net_task.h index 243d27be..18a3db49 100644 --- a/App/task/net_task.h +++ b/App/task/net_task.h @@ -16,7 +16,7 @@ extern "C" { #endif -/* CH395F 协议栈一次性初始化(net_init + MAC 打印 + PHY 配置 + g_net_ready 置位)。 +/* CH395F 协议栈一次性初始化(net_init + MAC 打印 + PHY 配置 + 广播网络就绪事件)。 * 返回 0 成功 / -1 失败。NET 层模式由 netTask 调用;硬件层模式(阶段 1~5) * netTask 不创建,由 testTask 调用后独占 CH395F。 */ int net_stack_bring_up(void); diff --git a/App/time_sync.c b/App/time_sync.c index 0ddbb329..0bffdb40 100644 --- a/App/time_sync.c +++ b/App/time_sync.c @@ -16,6 +16,7 @@ #include "time_sync.h" #include "net_socket.h" #include "sys_clock.h" +#include "sys_events.h" #define DBG_TAG "[TIME_SYNC]" #include "dbg_log.h" @@ -110,8 +111,11 @@ void timeSyncTask(void *argument) { (void)argument; - /* 等网络栈起来(与 StartFtpTask 的 7s 延迟同风格,确保 netTask 已开始处理消息) */ - vTaskDelay(pdMS_TO_TICKS(8000)); + /* 等网络栈真正就绪(取代拍脑袋的 8s 魔法延时),确保 netTask 已开始处理消息 */ + if (sys_evtHandle != NULL) { + (void)osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, + osFlagsWaitAny, osWaitForever); + } for (;;) { int ls = time_sync_open_listener(); diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 8c990ee5..a27e065a 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -775,26 +775,6 @@ 1 ..\App\task\net_task.c - - adc_task.c - 1 - ..\App\task\adc_task.c - - - rs485_task.c - 1 - ..\App\task\rs485_task.c - - - ringbuf.c - 1 - ..\App\util\ringbuf.c - - - crc.c - 1 - ..\App\util\crc.c - diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt index 88bb6005..3e8c860f 100644 --- a/MDK-ARM/build_log.txt +++ b/MDK-ARM/build_log.txt @@ -1,77 +1,73 @@ *** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' Rebuild target 'STM32F407-Demo' assembling startup_stm32f407xx.s... -compiling ringbuf.c... -compiling crc.c... +compiling gpio.c... +compiling dma.c... compiling stm32f4xx_hal_flash_ramfunc.c... compiling stm32f4xx_hal_flash_ex.c... -compiling i2c.c... -compiling app_main.c... -compiling stm32f4xx_hal_gpio.c... -compiling dma.c... -compiling stm32f4xx_hal_dma_ex.c... -compiling stm32f4xx_hal_msp.c... -compiling gpio.c... -compiling spi.c... compiling stm32f4xx_it.c... -compiling stm32f4xx_hal_rcc_ex.c... +compiling stm32f4xx_hal_msp.c... +compiling stm32f4xx_hal_dma.c... +compiling app_main.c... +compiling stm32f4xx_hal_timebase_tim.c... compiling stm32f4xx_hal_flash.c... compiling usart.c... -compiling stm32f4xx_hal_timebase_tim.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling spi.c... +compiling i2c.c... +compiling stm32f4xx_hal_gpio.c... compiling sys_clock.c... -compiling net_task.c... -compiling rs485_task.c... compiling time_sync.c... -compiling adc_task.c... +compiling stm32f4xx_hal_dma_ex.c... +compiling net_task.c... compiling stm32f4xx_hal_rcc.c... compiling main.c... compiling freertos.c... compiling stm32f4xx_hal_tim_ex.c... -compiling stm32f4xx_hal_tim.c... compiling lftpd_string.c... -compiling stm32f4xx_hal_dma.c... +compiling stm32f4xx_hal_tim.c... compiling croutine.c... -compiling event_groups.c... compiling list.c... -compiling stm32f4xx_hal.c... -compiling stm32f4xx_hal_pwr_ex.c... -compiling stream_buffer.c... +compiling event_groups.c... compiling queue.c... +compiling stream_buffer.c... compiling timers.c... -compiling stm32f4xx_hal_exti.c... compiling tasks.c... -compiling stm32f4xx_hal_pwr.c... -compiling stm32f4xx_hal_cortex.c... compiling heap_4.c... -compiling map.c... -compiling journal.c... compiling port.c... -compiling stm32f4xx_hal_i2c_ex.c... -compiling stm32f4xx_hal_i2c.c... +compiling journal.c... +compiling map.c... +compiling stm32f4xx_hal_pwr_ex.c... compiling ff.c... -compiling rs485.c... +compiling stm32f4xx_hal_cortex.c... +compiling lftpd_io.c... +compiling stm32f4xx_hal.c... compiling system_stm32f4xx.c... compiling sd2506.c... -compiling gd5f2gq5ue.c... -compiling ch395f.c... -compiling stm32f4xx_hal_spi.c... -compiling lftpd_io.c... -compiling nand_ftl.c... -compiling stm32f4xx_hal_uart.c... -compiling net_select.c... -compiling tpafe5160.c... +compiling stm32f4xx_hal_i2c_ex.c... +compiling rs485.c... compiling lftpd_inet.c... +compiling tpafe5160.c... +compiling ch395f.c... +compiling stm32f4xx_hal_exti.c... +compiling nand_ftl.c... +compiling net_select.c... +compiling gd5f2gq5ue.c... +compiling stm32f4xx_hal_spi.c... compiling lftpd.c... -compiling net_socket.c... compiling cmsis_os2.c... +compiling stm32f4xx_hal_uart.c... +compiling net_socket.c... +compiling stm32f4xx_hal_i2c.c... compiling ch395f_test_task.c... -compiling gd5f_test_task.c... -compiling test_config.c... -compiling sd2506_test_task.c... compiling net_test_task.c... +compiling test_config.c... +compiling gd5f_test_task.c... compiling storage_test_task.c... +compiling sd2506_test_task.c... linking... -Program Size: Code=59916 RO-data=2552 RW-data=412 ZI-data=51868 +Program Size: Code=60996 RO-data=2576 RW-data=412 ZI-data=51868 FromELF: creating hex file... ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). -Build Time Elapsed: 00:00:16 +Build Time Elapsed: 00:00:15 diff --git a/STM32F407-Demo.ioc b/STM32F407-Demo.ioc index a1904de8..4abea453 100644 --- a/STM32F407-Demo.ioc +++ b/STM32F407-Demo.ioc @@ -69,8 +69,9 @@ Dma.USART1_TX.0.PeriphDataAlignment=DMA_PDATAALIGN_BYTE Dma.USART1_TX.0.PeriphInc=DMA_PINC_DISABLE Dma.USART1_TX.0.Priority=DMA_PRIORITY_LOW Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +FREERTOS.Events01=sys_evt,Dynamic,NULL FREERTOS.FootprintOK=true -FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE,Queues01 +FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE,Queues01,Events01 FREERTOS.Queues01=netMsgQueue,8,net_msg_t,0,Dynamic,NULL,NULL FREERTOS.Tasks01=defaultTask,24,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;netTask,24,1024,StartNetTask,Default,NULL,Dynamic,NULL,NULL;ftpTask,24,1024,StartFtpTask,As weak,NULL,Dynamic,NULL,NULL;testTask,16,4096,start_test_task,As weak,NULL,Dynamic,NULL,NULL FREERTOS.configENABLE_FPU=1 diff --git a/Src/freertos.c b/Src/freertos.c index 90a0d5db..1c089cbc 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -37,6 +37,7 @@ #include "../test/ch395f_test_task.h" #include "../test/ch395f_test.h" /* ENABLE_NET_LAYER_TESTS:netTask 条件创建 */ #include "time_sync.h" +#include "sys_events.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -96,6 +97,11 @@ osMessageQueueId_t netMsgQueueHandle; const osMessageQueueAttr_t netMsgQueue_attributes = { .name = "netMsgQueue" }; +/* Definitions for sys_evt */ +osEventFlagsId_t sys_evtHandle; +const osEventFlagsAttr_t sys_evt_attributes = { + .name = "sys_evt" +}; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ @@ -162,6 +168,10 @@ void MX_FREERTOS_Init(void) { timeSyncTaskHandle = osThreadNew(timeSyncTask, NULL, &timeSyncTask_attributes); /* USER CODE END RTOS_THREADS */ + /* Create the event(s) */ + /* creation of sys_evt */ + sys_evtHandle = osEventFlagsNew(&sys_evt_attributes); + /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */ @@ -201,6 +211,10 @@ void StartDefaultTask(void *argument) } if (fr == FR_OK) { DBG_INFO("[FS] FATFS mounted"); + /* 广播"存储就绪",唤醒依赖文件系统的任务 */ + if (sys_evtHandle != NULL) { + osEventFlagsSet(sys_evtHandle, SYS_EVT_STORAGE_READY); + } } else { DBG_ERROR("[FS] f_mount failed (fr=%d)", (int)fr); } @@ -235,9 +249,24 @@ void StartNetTask(void *argument) __weak void StartFtpTask(void *argument) { /* USER CODE BEGIN StartFtpTask */ - osDelay(7000); + /* 等网络 + 存储都真正就绪后再 listen,取代拍脑袋的 7s 魔法延时 */ + /* + sys_evtHandle:等待哪个事件组 + SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY:要等哪几位(1u<<0 网络、1u<<1 存储) + osFlagsWaitAll:请求的位全部置位才唤醒(即"网络且存储都就绪") + osWaitForever:无限期等待,不设超时——任务会挂起(让出 CPU,零占用),直到生产者置位对应位才被唤醒 + */ + if (sys_evtHandle != NULL) { + (void)osEventFlagsWait(sys_evtHandle, + SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY, + osFlagsWaitAll, osWaitForever); + } lftpd_t ftp; lftpd_start("/", 21, &ftp); + /* 广播"FTP 服务就绪"(预留给依赖 FTP 的后续功能) */ + if (sys_evtHandle != NULL) { + osEventFlagsSet(sys_evtHandle, SYS_EVT_FTP_READY); + } /* USER CODE END StartFtpTask */ } diff --git a/Src/main.c b/Src/main.c index 033fcf51..8cd70f4b 100644 --- a/Src/main.c +++ b/Src/main.c @@ -114,7 +114,7 @@ int main(void) /* USER CODE BEGIN 2 */ HAL_Delay(100); - /* 应用层初始化(时间子系统优先,详见 app_main_init) */ + /* 应用层初始化(时间子系统优先,详见 app_main_init) */ app_main_init(); /* USER CODE END 2 */ diff --git a/docs/测试开发标准流程.md b/docs/测试开发标准流程.md index 91ae729d..42904012 100644 --- a/docs/测试开发标准流程.md +++ b/docs/测试开发标准流程.md @@ -167,7 +167,7 @@ static uint8_t s_tx_buf[TEST_BUF_SIZE]; ``` ch395fTestTask: - 1. 等待 g_net_ready(netTask 初始化完成) + 1. 等待 SYS_EVT_NET_READY(netTask 初始化完成,通过 Event Flags 广播) 2. 阻塞执行 Phase 1/2/3/6/9(内部循环,无需外部连接) 3. 创建 TCP 监听 Socket(端口 8080,单连接模式) 4. 事件驱动循环 Phase 4/5/7/8/10(等待 PC 连接) diff --git a/docs/测试说明/CH395F_Test_Guide.md b/docs/测试说明/CH395F_Test_Guide.md index edbececd..ff614034 100644 --- a/docs/测试说明/CH395F_Test_Guide.md +++ b/docs/测试说明/CH395F_Test_Guide.md @@ -577,7 +577,7 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081) | **优先级** | P0 | | **类型** | 功能测试 | | **标题** | Socket 0:OPEN → TCP_LISTEN → ESTABLISHED(单连接) | -| **前置条件** | 1. `net_init("192.168.1.100")` 成功(`g_net_ready==1`,netTask 周期 `net_poll()` 运行)
2. `net_layer_test_main()` 已 `net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN)
3. PC: `python .\test\ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080` | +| **前置条件** | 1. `net_init("192.168.1.100")` 成功、SYS_EVT_NET_READY 已广播(netTask 周期 `net_poll()` 运行)
2. `net_layer_test_main()` 已 `net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN)
3. PC: `python .\test\ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080` | **测试步骤与判定标准**: @@ -776,7 +776,7 @@ net_socket(NET_AF_INET, NET_SOCK_STREAM) // 取空闲 Socket(非监听 So | **优先级** | P0 | | **类型** | 功能测试(NET 层) | | **标题** | net_socket TCP Client → PC TCP Server,64 字节收发 | -| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_server --port 8081`(TCP 回显服务器)
2. `net_init()` 已完成(`g_net_ready == 1`,netTask 周期 `net_poll()` 运行中)
3. `ENABLE_PHASE7_TESTS` 已定义,入口为 `net_layer_client_test_main()`(见「构建模式与执行顺序」)
4. 建议:硬件层固件中 TC-NET-201/202/203 已通过(跨构建前置,见「构建模式与执行顺序」) | +| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_server --port 8081`(TCP 回显服务器)
2. `net_init()` 已完成、SYS_EVT_NET_READY 已广播(netTask 周期 `net_poll()` 运行中)
3. `ENABLE_PHASE7_TESTS` 已定义,入口为 `net_layer_client_test_main()`(见「构建模式与执行顺序」)
4. 建议:硬件层固件中 TC-NET-201/202/203 已通过(跨构建前置,见「构建模式与执行顺序」) | **测试步骤与判定标准**: diff --git a/docs/项目启动顺序管理.md b/docs/项目启动顺序管理.md new file mode 100644 index 00000000..d28526e0 --- /dev/null +++ b/docs/项目启动顺序管理.md @@ -0,0 +1,206 @@ +# 项目启动顺序管理(Event Flags 就绪屏障 · 已落地) + +> 已落地:用 CMSIS-RTOS v2 的 **Event Flags** 实现"就绪屏障",取代原先的全局标志轮询 + 魔法延时来管理任务启动顺序,消除脆弱的时间耦合。事件组句柄 `sys_evtHandle` 由 CubeMX(FREERTOS Events 对象 `sys_evt`)生成。 + +--- + +## 1. 背景与现状问题 + +### 1.1 现有任务 +| 任务 | 职责 | 当前启动依赖 | +|------|------|--------------| +| `defaultTask` | FTL + FatFS 存储挂载/测试(一次性) | 无(最先跑) | +| `netTask` | `net_init()` → `net_poll()` 周期轮询 | 无 | +| `ftpTask` | `lftpd_start("/", 21, &ftp)` | 等 `SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY`(`osFlagsWaitAll`) | +| `start_test_task`(testTask) | 统一测试入口,按 `TEST_SUITE_*` 分发 | NET 模式下 `osEventFlagsWait(SYS_EVT_NET_READY, osFlagsWaitAny, osWaitForever)` 阻塞等待 | +| `timeSyncTask` | 墙钟时间同步 | `osEventFlagsWait(SYS_EVT_NET_READY, …)` 后开始同步 | + +### 1.2 改造前(已废弃)的依赖管理手段 +> 以下为引入 Event Flags 之前的历史做法,仅作背景记录,当前代码已不再使用。 +1. **全局标志轮询**:`netTask` 就绪后置 `g_net_ready = 1`,`start_test_task` 用 + `while (!g_net_ready) { osDelay(100); }` 忙等。 +2. **魔法延时**:`ftpTask` 用 `osDelay(7000)` 硬等 PHY/网络稳定。 + +### 1.3 改造前的痛点(即引入 Event Flags 的动机) +- **魔法延时脆弱**:PHY 协商、DHCP、首轮 `net_poll()` 的耗时随硬件/环境波动;7s 是拍脑袋定值,一旦网络就绪变慢就失败。这正是 `docs/问题记录/CH395F_Trap_Records.md` 中记录的坑(Trap 09 子类:上电 PHY 未稳定即 listen)。 +- **轮询浪费节拍**:`start_test_task` 每 100ms 被唤醒一次去读一个全局变量,纯属空转。 +- **依赖隐式且分散**:谁依赖谁、何时就绪,散落在多个任务的延时与 `if` 里,没有统一视图,新增子系统极易遗漏同步。 +- **不可组合**:原全局标志只能表达"一个"条件;若某任务需"网络 **且** 存储都就绪"就无从表达。 + +--- + +## 2. 核心思想:就绪屏障(Readiness Barrier) + +**把"启动顺序"抽象为"子系统就绪事件",而不是"时间先后"。** + +- 每个子系统(网络、存储、FTP 服务等)在**真正准备好对外服务**的那一刻,广播一个"我好了"的事件标志。 +- 任何依赖该子系统的任务,在入口处**阻塞等待**对应事件标志,收到才继续;此前不占 CPU、不被调度唤醒。 +- 生产者与消费者**互相不知道对方存在**,只通过事件组解耦——新增任务只需在入口 `wait` 它需要的 bit,无需改动任何已有任务的代码。 + +这与 CubeMX"一次性创建全部任务"的模式完全兼容:任务仍由 `MX_FREERTOS_Init` 统一创建,但各自在入口 `wait`,相当于"创建即挂起、就绪才放行"。 + +### 为什么用 Event Flags 而非全局变量 +| 维度 | 全局标志 `g_net_ready` | Event Flags | +|------|----------------------|-------------| +| 等待方式 | 忙等轮询(浪费 tick) | 阻塞等待(零占用) | +| 多依赖组合 | 不支持 | `osFlagsWaitAll` / `osFlagsWaitAny` | +| 类型/作用域 | 裸 `volatile`,易误用 | 内核对象,API 受控 | +| 清空语义 | 手动维护 | `osFlagsNoClear` 可配 | +| 调试可见性 | 不可观测 | 可在 IDE 观测事件组值 | + +--- + +## 3. 方案设计 + +### 3.1 事件组与 bit 定义 +全项目共用一个事件组 `sys_evtHandle`,每位表示一个子系统"就绪": + +```c +/* sys_events.h(新建,或并入 net_task.h) */ +#include "cmsis_os2.h" + +extern osEventFlagsId_t sys_evtHandle; + +#define SYS_EVT_NET_READY (1u << 0) /* netTask:net_init + PHY 稳定完成 */ +#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:首次时间同步完成 */ +/* 后续新增子系统继续左移分配 bit,32 位上限内即可 */ +``` + +### 3.2 生产者 / 消费者矩阵 +| 事件 bit | 生产者(置位处) | 消费者(等待处) | +|----------|------------------|------------------| +| `SYS_EVT_NET_READY` | `netTask`(`net_task_func` 中 `net_stack_bring_up()` 成功后置位) | `ftpTask`、`start_test_task`(NET 模式)、`timeSyncTask` | +| `SYS_EVT_STORAGE_READY` | `defaultTask`(挂载成功后) | 依赖文件系统的测试套件(如 STORAGE/GD5F) | +| `SYS_EVT_FTP_READY` | `ftpTask`(`lftpd_start` 成功后) | 无(示例,预留给需要 FTP 就绪的后续功能) | +| `SYS_EVT_TIMESYNC_READY` | (预留,暂无生产者置位) | 可选:对时精度敏感的业务 | + +### 3.3 事件组创建时机 +`sys_evtHandle` 必须在**任何任务 `wait` 之前**存在。`main.c` 调用顺序为: +``` +osKernelInitialize(); +MX_FREERTOS_Init(); /* CubeMX 在此创建事件组并创建任务 */ +osKernelStart(); +``` +事件组**由 CubeMX 代码生成器创建**(见第 4 节 Step 1 的 Events 对象),生成器会在 +`MX_FREERTOS_Init` 的任务创建之前插入 `sys_evtHandle = osEventFlagsNew(&sys_evt_attributes);`, +因此无需手写创建代码。(CMSIS-RTOS v2 允许在 `osKernelInitialize()` 之后、 +`osKernelStart()` 之前创建内核对象。) + +--- + +## 4. 实施步骤 + +### Step 1 定义事件组 +- 新建 `App/sys_events.h`,放入第 3.1 节的 `extern osEventFlagsId_t sys_evtHandle;` 声明与 bit 宏。 +- 在 **CubeMX** 中配置事件组(推荐,避免手写创建代码): + 1. 打开 `STM32F407-Demo.ioc` → `Middleware` / `Software Components` → `FREERTOS` → `Configuration`, + 确认 `Mode` 为 **CMSIS_V2**(本项目即用此接口)。 + 2. 在 FREERTOS 配置树中找到 **Events(事件标志)** 分类,点击 `Add` 新建一个 Event Flags 对象。 + 3. 命名(Object Name)填 **`sys_evt`**(必须与此一致,代码生成器会据此产生句柄 `sys_evtHandle`)。 + 4. 其余参数默认,保存并 **Generate Code**。 +- 代码生成器自动在 `freertos.c` 生成: + - 句柄声明 `osEventFlagsId_t sys_evtHandle;` + - 属性 `const osEventFlagsAttr_t sys_evt_attributes = { .name = "sys_evt" };` + - 创建 `sys_evtHandle = osEventFlagsNew(&sys_evt_attributes);`(位于 `MX_FREERTOS_Init` 任务创建之前) + → 因此 `net_task.c` 不再需要手动定义/创建事件组,仅 `extern` 引用即可。 + +### Step 2 netTask 置位(生产者) +在 `App/task/net_task.c` 的 `net_task_func()` 中,`net_stack_bring_up()` 成功返回后(已退出失败死等分支)置位: +```c +/* 广播"网络就绪",唤醒等待它的 ftpTask / start_test_task / timeSyncTask 等 */ +if (sys_evtHandle != NULL) { + osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY); +} +``` +> 注意:置位点放在 `net_task_func` 而非 `net_stack_bring_up` 内部。`net_stack_bring_up` +> 是 NET 层 / 硬件层两种模式共用的纯初始化函数(硬件层由 testTask 调用并独占 CH395F), +> 若把置位塞进它内部,硬件层模式会误广播 NET_READY,导致 ftpTask 起 FTP 服务与硬件测试抢 CH395F。 +> 因此只在 netTask 上下文广播。 + +### Step 3 defaultTask 置位存储就绪(生产者) +在 `defaultTask` 完成 FTL/FatFS 挂载成功后: +```c +osEventFlagsSet(sys_evtHandle, SYS_EVT_STORAGE_READY); +``` + +### Step 4 ftpTask 改 wait(消费者) +将 `Src/freertos.c` 中 `StartFtpTask`(`__weak`)的: +```c +osDelay(7000); /* 旧:魔法延时 */ +lftpd_start("/", 21, &ftp); +``` +改为(网络 **且** 存储都就绪才 listen): +```c +/* 等网络 + 存储都真正就绪后再 listen,取代拍脑袋的 7s 魔法延时 */ +if (sys_evtHandle != NULL) { + (void)osEventFlagsWait(sys_evtHandle, + SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY, + osFlagsWaitAll, osWaitForever); +} +lftpd_start("/", 21, &ftp); +osEventFlagsSet(sys_evtHandle, SYS_EVT_FTP_READY); /* 自身就绪也广播(可选) */ +``` + +### Step 5 start_test_task 改 wait(消费者) +将 `test/ch395f_test_task.c` 中 NET 模式的: +```c +while (!g_net_ready) { /* 旧:忙等轮询 */ + osDelay(100); +} +``` +改为: +```c +(void)osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, osFlagsWaitAny, osWaitForever); +``` +若测试套件同时需要文件系统,可组合等待: +```c +uint32_t need = SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY; +osEventFlagsWait(sys_evtHandle, need, osFlagsWaitAll, osWaitForever); +``` + +### Step 6 timeSyncTask 必须等网络就绪(已实现) +`timeSyncTask` **依赖网络**,`App/time_sync.c` 中已在入口 `wait SYS_EVT_NET_READY`(`osFlagsWaitAny, osWaitForever`)后才开始时间同步,取代原先 8s 魔法延时。代码已落地: +```c +/* 等网络栈真正就绪,确保 netTask 已开始处理消息 */ +if (sys_evtHandle != NULL) { + (void)osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, + osFlagsWaitAny, osWaitForever); +} +``` +> 注:`SYS_EVT_TIMESYNC_READY` 目前仅定义、**暂无生产者置位**(预留给对时精度敏感的业务),故其生产者列为"可选/预留",消费者亦暂不存在。 + +--- + +## 5. 迁移前后对照 + +| 场景 | 改造前 | 改造后 | +|------|------|--------| +| `ftpTask` 等网络 | `osDelay(7000)` 硬等 | `osEventFlagsWait(NET_READY, …, osWaitForever)` 就绪即放行 | +| `start_test_task` 等网络 | `while(!g_net_ready) osDelay(100)` 每 100ms 空转 | `osEventFlagsWait(NET_READY, …, osWaitForever)` 零占用阻塞 | +| "网络且存储都就绪" | 无法实现 | `osEventFlagsWait(NET_READY|STORAGE_READY, osFlagsWaitAll, …)` | +| 新增子系统 X | 手动在依赖任务里加延时/轮询 | 定义 `SYS_EVT_X`,生产者置位、消费者 `wait`,互不改动 | + +--- + +## 6. 注意事项 / 潜在坑 + +1. **事件组必须先建后用**:`sys_evtHandle` 由 CubeMX(FREERTOS Events 对象 `sys_evt`)在 `MX_FREERTOS_Init` 的任务创建之前自动创建,因此早于所有 `wait`。若句柄异常为 `NULL`,`osEventFlagsSet/Wait` 会返回 `osError` 且不阻塞——消费者会直接越过等待往下跑,重新引入竞态;各等待点已用 `if (sys_evtHandle != NULL)` 防御。 +2. **`osWaitForever` 死锁风险**:若某生产者永远不置位(如初始化失败提前 `return`),消费者会永久阻塞。建议给关键 `wait` 加有限超时 + 错误日志,便于定位: + ```c + if (osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, + osFlagsWaitAny, 30000U) == 0) { + DBG_ERROR("wait NET_READY timeout"); /* 后续可走降级/出错处理 */ + } + ``` +3. **`wait` 只能在任务上下文**:`osEventFlagsWait` 不能在中断中调用;本项目所有等待点都在任务入口,安全。`osEventFlagsSet` 可在任务或中断中调用。 +4. **bit 上限**:事件标志为 32 位,最高位(1<<31)被 CMSIS-RTOS 保留,可用 bit 0~30。 +5. **测试构建(HW 层模式)的特殊性**:`TEST_SUITE_CH395F` 硬件层模式下 `netTask` 不创建,`start_test_task` 自己 `net_stack_bring_up()` 独占 CH395F。此时**没有 netTask 来置 `SYS_EVT_NET_READY`**(置位点只在 `net_task_func` 内)。结果:`ftpTask` / `timeSyncTask` 会一直阻塞在 NET_READY 上——这正符合"硬件层模式不需要 FTP / 网络时间同步、CH395F 被 testTask 独占"的设计,属预期行为,不构成冲突。若将来需要在硬件层模式也起 FTP,可让 `start_test_task` 在 bring-up 成功后自行 `osEventFlagsSet(NET_READY)`。 +6. **`g_net_ready` 去留**:**已完成淘汰**。`g_net_ready` 原全局标志已从代码中彻底移除(定义、置位、所有消费方均改为 Event Flags 等待),仅保留事件标志表达就绪。 + +--- + +## 7. 总结 + +**不要靠"等多少毫秒"来赌依赖就绪,而要靠"对方声明自己就绪了"来驱动启动**——Event Flags 把隐式的时间耦合变成显式的事件依赖,启动顺序自然清晰、健壮、易扩展。 diff --git a/test/ch395f_test_task.c b/test/ch395f_test_task.c index 212f0d20..0a2bd414 100644 --- a/test/ch395f_test_task.c +++ b/test/ch395f_test_task.c @@ -11,23 +11,24 @@ #include #include "ch395f_test_task.h" +#include "ch395f_test.h" #include "net_test_task.h" #include "cmsis_os.h" #include "net_task.h" #include "net_socket.h" -#include "ch395f_test.h" #include "ch395f.h" #include "test_config.h" #include "storage_test.h" #include "gd5f_test.h" #include "sd2506_test.h" +#include "sys_events.h" #define DBG_TAG "[TEST_TASK]" #include "dbg_log.h" /* netTask 初始化完成标志(由 net_task.c 在 net_stack_bring_up 成功后置 1)。 * 仅 NET 层模式使用 —— 硬件层模式下 netTask 不创建,由本任务自行初始化。 */ -extern volatile uint8_t g_net_ready; + /* * 全局测试统计数据,由 TEST_CHECK/TEST_REPORT 宏自动管理。 @@ -1381,9 +1382,8 @@ void ch395f_test_run(void) /* NET 层模式(阶段 6~11):等待 netTask 完成 net_init() 及 CH395F 初始化。 * Phase 7(TCP Client)与阶段 6/8~11(TCP Server 监听循环)互斥, * 二者共用单连接 Socket 0,故按宏选择入口。 */ - while (!g_net_ready) { - osDelay(100); - } + /* 等待 netTask 广播网络就绪(取代 g_net_ready 忙等轮询) */ + (void)osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, osFlagsWaitAny, osWaitForever); DBG_INFO("netTask ready"); #if defined(ENABLE_PHASE7_TESTS) net_layer_client_test_main();