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();