启动顺序管理
This commit is contained in:
@@ -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 */
|
||||
+23
-7
@@ -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)");
|
||||
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+6
-2
@@ -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();
|
||||
|
||||
@@ -775,26 +775,6 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\task\net_task.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>adc_task.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\task\adc_task.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>rs485_task.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\task\rs485_task.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>ringbuf.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\util\ringbuf.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>crc.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\util\crc.c</FilePath>
|
||||
</File>
|
||||
</Files>
|
||||
</Group>
|
||||
<Group>
|
||||
|
||||
+39
-43
@@ -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
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+30
-1
@@ -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 */
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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 */
|
||||
|
||||
|
||||
+1
-1
@@ -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 连接)
|
||||
|
||||
@@ -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()` 运行)<br>2. `net_layer_test_main()` 已 `net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN)<br>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()` 运行)<br>2. `net_layer_test_main()` 已 `net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN)<br>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 回显服务器)<br>2. `net_init()` 已完成(`g_net_ready == 1`,netTask 周期 `net_poll()` 运行中)<br>3. `ENABLE_PHASE7_TESTS` 已定义,入口为 `net_layer_client_test_main()`(见「构建模式与执行顺序」)<br>4. 建议:硬件层固件中 TC-NET-201/202/203 已通过(跨构建前置,见「构建模式与执行顺序」) |
|
||||
| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_server --port 8081`(TCP 回显服务器)<br>2. `net_init()` 已完成、SYS_EVT_NET_READY 已广播(netTask 周期 `net_poll()` 运行中)<br>3. `ENABLE_PHASE7_TESTS` 已定义,入口为 `net_layer_client_test_main()`(见「构建模式与执行顺序」)<br>4. 建议:硬件层固件中 TC-NET-201/202/203 已通过(跨构建前置,见「构建模式与执行顺序」) |
|
||||
|
||||
**测试步骤与判定标准**:
|
||||
|
||||
|
||||
@@ -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 把隐式的时间耦合变成显式的事件依赖,启动顺序自然清晰、健壮、易扩展。
|
||||
@@ -11,23 +11,24 @@
|
||||
|
||||
#include <string.h>
|
||||
#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();
|
||||
|
||||
Reference in New Issue
Block a user