diff --git a/App/task/net_task.c b/App/task/net_task.c index f9bdb8e..d4a6101 100644 --- a/App/task/net_task.c +++ b/App/task/net_task.c @@ -18,13 +18,12 @@ #include "net_socket.h" #include "net_select.h" #include "ch395f.h" -#include "ch395f_test.h" #define DBG_TAG "[NET_TASK]" #include "dbg_log.h" -/* 测试缓冲大小定义(供各 Phase 测试使用) */ -#define NET_TASK_BUF_SIZE 65536 +/* 网络初始化完成标志(供 ch395f_test_task 等外部任务等待) */ +volatile uint8_t g_net_ready = 0; /* * 函数功能:网络任务初始化 @@ -72,6 +71,7 @@ void net_task_func(void const *arg) (phy == CH395F_PHY_10M_FULL) ? "10M FULL" : (phy == CH395F_PHY_10M_HALF) ? "10M HALF" : (phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN"); + g_net_ready = 1; } else { DBG_ERROR("net init: FAIL"); } @@ -82,36 +82,6 @@ void net_task_func(void const *arg) for (;;) { net_poll(); net_process_messages(); - - /* - * 以下测试共用 Socket 0(单连接模式): - * Phase 4 — TCP Echo 基础收发 - * Phase 5 — 大文件传输 - * Phase 7 — Select/Poll - * Phase 8 — 边界条件 - * Phase 10 — KeepAlive - * 各 Phase 通过 ch395f_test.h 中的宏独立开关 - */ -#ifdef ENABLE_PHASE4_TESTS - ch395f_phase4_tests(); -#endif - -#ifdef ENABLE_PHASE5_TESTS - ch395f_phase5_tests(); -#endif - -#ifdef ENABLE_PHASE7_TESTS - ch395f_phase7_tests(); -#endif - -#ifdef ENABLE_PHASE8_TESTS - ch395f_phase8_tests(); -#endif - -#ifdef ENABLE_PHASE10_TESTS - ch395f_phase10_tests(); -#endif - osDelay(10); } } diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index dda3d94..90f3e9f 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -1,11 +1,16 @@ /* - * 模块名称:CH395F SPI Ethernet Protocol Stack Driver - * 模块功能:CH395F 以太网协议栈芯片 SPI 驱动实现,提供芯片检测、初始化�? * 网络参数配置、PHY 管理、DHCP、Socket 管理及数据收发功�? * 适用平台:STM32F4 系列(SPI2 接口�? * 作者:王建�? * 创建日期�?026-03-14 - * 修改记录�? * 2026-03-14 王建�? 创建初始版本,按代码规范重构 - * 2026-07-18 王建�? 改�?write_send_buf / read_recv_buf 使用 SPI2 DMA 批量传输 + * 模块名称:CH395F SPI以太网协议栈驱动 + * 模块功能:CH395F以太网协议栈芯片SPI驱动实现,提供芯片检测、初始化、 + * 网络参数配置、PHY管理、DHCP、Socket管理及数据收发功能 + * 适用平台:STM32F4系列(SPI2接口) + * 作者:王建锋 + * 创建日期:2026-03-14 + * 修改记录: + * 2026-03-14 王建锋 创建初始版本,按代码规范重构 + * 2026-07-18 王建锋 改进write_send_buf/read_recv_buf使用SPI2 DMA批量传输 */ -/* 头文件包含区 - 先包含系统头文件,再包含本模块头文件 */ +/* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */ #include #include #include "ch395f.h" @@ -21,52 +26,65 @@ /* * 私有宏定义区 */ -#define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试字�?*/ -#define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms�?*/ -#define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms�?*/ -#define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI 事务开始延时(ms�?*/ -/* DMA 缓冲大小使用 ch395f.h 中公开的 CH395F_SPI_DMA_BUF_SIZE */ +#define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试数据 */ +#define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */ +#define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms) */ +#define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI事务开始延时(ms) */ +/* DMA缓冲大小使用ch395f.h中公开的CH395F_SPI_DMA_BUF_SIZE */ #define CH395F_DMA_BUF_SIZE CH395F_SPI_DMA_BUF_SIZE /* - * 私有变量�?- DMA 传输相关 + * 静态变量定义区 - DMA传输相关 */ -static volatile uint8_t s_spi2_dma_done = 0U; /* DMA 传输完成标志 */ +static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */ __attribute__((aligned(4))) -static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA 发送缓冲区 */ +static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */ __attribute__((aligned(4))) -static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA 接收缓冲�?*/ +static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA接收缓冲区 */ /* - * 私有函数声明�? */ + * 私有函数声明区 + */ static void ch395f_spi_begin(void); static void ch395f_spi_end(void); static void ch395f_spi_dma_transfer(uint16_t len); /* - * 私有函数实现�? */ + * 私有函数定义区 + */ /* - * 函数功能:开�?SPI 事务(CS 拉高 -> 拉低�? * 函数说明:手册要�?SPI 模式下字节间无需间隔�? * CS 拉高再拉低满足最小片选间隔要�? */ + * 函数功能:启动一次SPI事务(CS高→低) + * 限定条件:SPI2外设已初始化 + * 函数说明:1. 先将CS置高再置低,满足片选信号最小间隔要求 + * 2. SPI模式下字节间无需额外延时 + */ static void ch395f_spi_begin(void) { - /* CS 先拉高,确保空闲状�?*/ + /* CS置高确保空闲状态 */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); - /* CS 拉低,启动事务(无需额外延时,SPI 时序�?SCK 频率决定�?*/ + /* CS置低启动事务(无需额外延时,SPI时序由SCK频率决定) */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_RESET); } /* - * 函数功能:结�?SPI 事务(CS 拉高�? */ + * 函数功能:结束一次SPI事务(CS置高) + * 限定条件:SPI2外设已初始化 + */ static void ch395f_spi_end(void) { - /* CS 拉高,结束事�?*/ + /* CS置高结束事务 */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); } /* - * 函数功能:DMA 全双工传输并等待完成 - * 入口参数:len - 传输字节�? uint16_t - * 函数说明:发�?s_spi2_dma_tx_buf,接收到 s_spi2_dma_rx_buf�? * 阻塞等待 DMA 完成回调置位标志 + * 函数功能:DMA全双工传输并等待完成 + * 入口参数:len - 传输长度(字节) uint16_t 1 - CH395F_DMA_BUF_SIZE + * 限定条件:1. s_spi2_dma_tx_buf中已填充待发送数据 + * 2. 需确保DMA传输缓冲区已对齐到4字节边界 + * 函数说明:1. 发送s_spi2_dma_tx_buf内容,同时接收数据存入s_spi2_dma_rx_buf + * 2. 阻塞等待DMA完成回调置标志位 + * 3. 超时(1秒)时强制中止SPI DMA并打印错误信息 + * 4. 传输完成后检查SPI错误码并清除溢出标志 */ static void ch395f_spi_dma_transfer(uint16_t len) { HAL_StatusTypeDef status; @@ -102,13 +120,13 @@ static void ch395f_spi_dma_transfer(uint16_t len) { __HAL_DMA_GET_COUNTER(hspi2.hdmatx)); DBG_ERROR(" SPI2_CR1=0x%lX, CR2=0x%lX, SR=0x%lX", SPI2->CR1, SPI2->CR2, SPI2->SR); - /* 强制复位 SPI2 DMA 状态,避免死锁 */ + /* 强制中止SPI2 DMA状态,避免死锁 */ HAL_SPI_Abort(&hspi2); return; } } - /* 检查传输是否成功:SPI 错误码必须为 0 */ + /* 检查传输是否成功:SPI错误码必须为0 */ if (hspi2.ErrorCode != 0U) { DBG_INFO("SPI DMA done with errors: ErrorCode=0x%lX, SPI_State=%d", hspi2.ErrorCode, (int)hspi2.State); @@ -117,10 +135,11 @@ static void ch395f_spi_dma_transfer(uint16_t len) { } /* - * 函数功能:SPI 收发完成回调 - * 函数说明:由 HAL SPI 驱动�?DMA TX+RX 完成后调用(HAL_SPI_TransmitReceive_DMA 触发此回调) + * 函数功能:SPI传输完成回调函数 + * 入口参数:hspi - SPI句柄指针 SPI_HandleTypeDef* + * 函数说明:由HAL SPI驱动在DMA TX+RX完成后调用 */ -/* SPI1 DMA 完成标志(定义在 gd5f2gq5ue.c 中) */ +/* SPI1 DMA完成标志(定义在gd5f2gq5ue.c中) */ extern volatile uint8_t g_spi1_dma_done; void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { @@ -132,8 +151,9 @@ void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { } /* - * 函数功能:SPI 错误回调 - * 函数说明:由 HAL SPI 驱动�?DMA 传输出错时调用,释放等待 + * 函数功能:SPI错误回调函数 + * 入口参数:hspi - SPI句柄指针 SPI_HandleTypeDef* + * 函数说明:由HAL SPI驱动在DMA传输错误时调用,释放等待者 */ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { @@ -144,74 +164,79 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { } /* - * 公共函数实现�? */ + * 公共函数定义区 + */ /* - * 函数功能:向 CH395 写入命令字节 - * 入口参数:cmd - 命令�? uint8_t 0x00 - 0xFF - * 返回值:命令码回显�? uint8_t - * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 - * 函数说明:SPI 模式下命令与数据之间无需间隔 + * 函数功能:向CH395写入一个命令字节 + * 入口参数:cmd - 命令字节 uint8_t 0x00 - 0xFF + * 返回值:命令回显字节 uint8_t + * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 + * 函数说明:SPI模式下命令和数据字节之间无需间隔 */ uint8_t ch395f_write_cmd(uint8_t cmd) { uint8_t rx_data; - /* 通过 SPI 发送命令并接收回显 */ + /* 通过SPI发送命令并接收回显 */ HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } /* - * 函数功能:向 CH395 写入数据字节 + * 函数功能:向CH395写入一个数据字节 * 入口参数:data - 数据字节 uint8_t 0x00 - 0xFF - * 返回值:数据回显�? uint8_t - * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 + * 返回值:数据回显字节 uint8_t + * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 */ uint8_t ch395f_write_data(uint8_t data) { uint8_t rx_data; - /* 通过 SPI 发送数据并接收回显 */ + /* 通过SPI发送数据并接收回显 */ HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } /* - * 函数功能:从 CH395 读取数据字节 + * 函数功能:从CH395读取一个数据字节 * 返回值:接收到的数据字节 uint8_t - * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 + * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 + * 函数说明:发送0xFF作为SPI时钟源,同时读取芯片返回的数据 */ uint8_t ch395f_read_data(void) { uint8_t tx_dummy = 0xFFU; uint8_t rx_data; - /* 发�?0xFF 作为时钟源,读取芯片返回的数�?*/ + /* 发送0xFF作为时钟源,读取芯片返回数据 */ HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } /* - * 函数功能:检�?CH395 芯片是否存在且通信正常 - * 返回值:CH395F_STATUS_OK - 检测到芯片,CH395F_STATUS_NOT_DETECTED - 未检测到 - * 限定条件:SPI2 已正确初始化 - * 函数说明:发�?CMD_CHECK_EXIST (0x06) 及测试字�?0x57�? * 芯片应返回按位取反�?0xA8 + * 函数功能:检测CH395芯片是否存在且通信正常 + * 返回值:CH395F_STATUS_OK - 芯片检测成功 ch395f_status_t + * CH395F_STATUS_NOT_DETECTED - 芯片未检测到 ch395f_status_t + * 限定条件:SPI2外设已正确初始化 + * 函数说明:1. 发送CMD_CHECK_EXIST(0x06)后跟测试数据0x57 + * 2. 芯片应返回测试数据的按位取反结果0xA8 + * 3. 回显匹配则返回成功,否则返回未检测到 */ ch395f_status_t ch395f_check_exist(void) { uint8_t test_data = CH395F_CHECK_EXIST_TEST_DATA; uint8_t reply_data; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送检测命�?*/ + /* 发送检测命令 */ ch395f_write_cmd(CH395F_CMD_CHECK_EXIST); - /* 发送测试数�?*/ + /* 发送测试数据 */ ch395f_write_data(test_data); /* 读取芯片回复 */ reply_data = ch395f_read_data(); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); /* 验证回复是否为测试数据的按位取反 */ @@ -223,303 +248,313 @@ ch395f_status_t ch395f_check_exist(void) { } /* - * 函数功能:读�?CH395 芯片版本�? * 返回值:版本字节(bit5:0 为版本号)uint8_t - * 限定条件:芯片已通过 ch395f_check_exist() 检�? */ + * 函数功能:读取CH395芯片版本号 + * 返回值:版本字节(bits 5:0 = 版本号) uint8_t + * 限定条件:芯片已通过ch395f_check_exist()检测 + */ uint8_t ch395f_get_version(void) { uint8_t version; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送版本读取命�?*/ + /* 发送版本读取命令 */ ch395f_write_cmd(CH395F_CMD_GET_IC_VER); - /* 读取版本�?*/ + /* 读取版本号 */ version = ch395f_read_data(); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); return version; } /* - * 函数功能:获取远�?IP 和端�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * p_buf - 6 字节输出缓冲区(4字节IP + 2字节端口�? * 限定条件:TCP Server 模式下连接建立后调用 - * 函数说明:IP 低字节在前,端口低字节在�? */ + * 函数功能:获取远程IP地址和端口号 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * p_buf - 6字节输出缓冲区指针(4字节IP + 2字节端口) uint8_t* + * 限定条件:1. p_buf不可为NULL + * 2. 需在TCP Server模式下连接建立后调用 + * 函数说明:IP和端口均为低字节在前 + */ void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_buf == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送获取远�?IP 和端口命�?*/ + /* 发送获取远程IP和端口命令 */ ch395f_write_cmd(CH395F_CMD_GET_REMOT_IPP_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 读取 4 字节 IP 地址 */ + /* 读取4字节IP地址 */ for (i = 0; i < 4; i++) { p_buf[i] = ch395f_read_data(); } - /* 读取 2 字节端口�?*/ + /* 读取2字节端口号 */ for (i = 4; i < 6; i++) { p_buf[i] = ch395f_read_data(); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?CH395 功能参数 - * 入口参数:para - 4 字节功能参数 uint32_t - * 限定条件:必须在 ch395f_init() 之前调用 + * 函数功能:设置CH395功能参数 + * 入口参数:para - 4字节功能参数 uint32_t + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_fun_para(uint32_t para) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置功能参数命�?*/ + /* 发送设置功能参数命令 */ ch395f_write_cmd(CH395F_CMD_SET_FUN_PARA); - /* 发�?4 字节参数(低字节在前�?*/ + /* 发送4字节参数(低字节在前) */ ch395f_write_data((uint8_t)(para)); ch395f_write_data((uint8_t)(para >> 8)); ch395f_write_data((uint8_t)(para >> 16)); ch395f_write_data((uint8_t)(para >> 24)); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?TCP MSS - * 入口参数:mss - MSS �? uint16_t 60~1460 - * 限定条件:必须在 ch395f_init() 之前调用 + * 函数功能:设置TCP最大分段大小(MSS) + * 入口参数:mss - MSS值 uint16_t 60 - 1460 + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_tcp_mss(uint16_t mss) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?TCP MSS 命令 */ + /* 发送设置TCP MSS命令 */ ch395f_write_cmd(CH395F_CMD_SET_TCP_MSS); - /* 发�?2 字节 MSS 值(低字节在前) */ + /* 发送2字节MSS值(低字节在前) */ ch395f_write_data(mss & 0xFFU); ch395f_write_data((mss >> 8) & 0xFFU); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?Socket 接收缓冲�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * start_block - 起始块地址 uint8_t - * block_count - 块个�? uint8_t - * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 + * 函数功能:设置Socket接收缓冲区 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * start_block - 起始块地址 uint8_t + * block_count - 块数量 uint8_t + * 限定条件:必须在OPEN_SOCKET_SN之前调用 */ void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置接收缓冲区命令 */ ch395f_write_cmd(CH395F_CMD_SET_RECV_BUF); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送起始块地址 */ ch395f_write_data(start_block); - /* 发送块个数 */ + /* 发送块数量 */ ch395f_write_data(block_count); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?Socket 发送缓冲区 - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * start_block - 起始块地址 uint8_t - * block_count - 块个�? uint8_t - * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 + * 函数功能:设置Socket发送缓冲区 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * start_block - 起始块地址 uint8_t + * block_count - 块数量 uint8_t + * 限定条件:必须在OPEN_SOCKET_SN之前调用 */ void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置发送缓冲区命令 */ ch395f_write_cmd(CH395F_CMD_SET_SEND_BUF); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送起始块地址 */ ch395f_write_data(start_block); - /* 发送块个数 */ + /* 发送块数量 */ ch395f_write_data(block_count); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:清�?Socket 接收缓冲�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 限定条件:Socket 已打开 + * 函数功能:清除Socket接收缓冲区 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 限定条件:Socket必须已打开 */ void ch395f_clear_recv_buf(uint8_t sock) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送清空接收缓冲区命令 */ + /* 发送清除接收缓冲区命令 */ ch395f_write_cmd(CH395F_CMD_CLEAR_RECV_BUF_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?TCP KeepAlive 空闲时间 - * 入口参数:time - 空闲时间(秒�? uint32_t - * 限定条件:必须在 ch395f_init() 之前调用 + * 函数功能:设置TCP KeepAlive空闲时间 + * 入口参数:time - 空闲时间(秒) uint32_t + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_keepalive_idle(uint32_t time) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?KeepAlive 空闲时间命令 */ + /* 发送设置KeepAlive空闲时间命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_IDLE); - /* 发�?4 字节时间值(低字节在前) */ + /* 发送4字节时间值(低字节在前) */ ch395f_write_data((uint8_t)(time)); ch395f_write_data((uint8_t)(time >> 8)); ch395f_write_data((uint8_t)(time >> 16)); ch395f_write_data((uint8_t)(time >> 24)); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?TCP KeepAlive 探测间隔 - * 入口参数:time - 间隔时间(秒�? uint32_t - * 限定条件:必须在 ch395f_init() 之前调用 + * 函数功能:设置TCP KeepAlive探测间隔 + * 入口参数:time - 探测间隔(秒) uint32_t + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_keepalive_intvl(uint32_t time) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?KeepAlive 探测间隔命令 */ + /* 发送设置KeepAlive探测间隔命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_INTVL); - /* 发�?4 字节时间值(低字节在前) */ + /* 发送4字节时间值(低字节在前) */ ch395f_write_data((uint8_t)(time)); ch395f_write_data((uint8_t)(time >> 8)); ch395f_write_data((uint8_t)(time >> 16)); ch395f_write_data((uint8_t)(time >> 24)); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?TCP KeepAlive 探测次数 + * 函数功能:设置TCP KeepAlive探测次数 * 入口参数:cnt - 探测次数 uint8_t - * 限定条件:必须在 ch395f_init() 之前调用 + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_keepalive_cnt(uint8_t cnt) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?KeepAlive 探测次数命令 */ + /* 发送设置KeepAlive探测次数命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_CNT); - /* 发送探测次�?*/ + /* 发送探测次数 */ ch395f_write_data(cnt); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:启�?禁用指定 Socket �?KeepAlive - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * enable - 1 启用�? 禁用 uint8_t - * 限定条件:Socket 已打开 + * 函数功能:使能/禁能指定Socket的KeepAlive功能 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * enable - 使能标志 uint8_t 1 = 使能,0 = 禁能 + * 限定条件:Socket必须已打开 */ void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?KeepAlive 使能命令 */ + /* 发送设置KeepAlive使能命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 发送使能标�?*/ + /* 发送使能标志 */ ch395f_write_data(enable); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?TCP 重传次数 - * 入口参数:cnt - 重传次数 uint8_t�?=使用默认�?�? * 限定条件:必须在 ch395f_init() 之前调用 + * 函数功能:设置TCP重传次数 + * 入口参数:cnt - 重传次数 uint8_t 0 = 使用默认值 + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_retrans_count(uint8_t cnt) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置重传次数命�?*/ + /* 发送设置重传次数命令 */ ch395f_write_cmd(CH395F_CMD_SET_RETRAN_COUNT); - /* 发送重传次�?*/ + /* 发送重传次数 */ ch395f_write_data(cnt); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?TCP 重传周期 + * 函数功能:设置TCP重传周期 * 入口参数:period - 重传周期(毫秒) uint16_t - * 限定条件:必须在 ch395f_init() 之前调用 + * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_retrans_period(uint16_t period) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置重传周期命�?*/ + /* 发送设置重传周期命令 */ ch395f_write_cmd(CH395F_CMD_SET_RETRAN_PERIOD); - /* 发�?2 字节周期值(低字节在前) */ + /* 发送2字节周期值(低字节在前) */ ch395f_write_data(period & 0xFFU); ch395f_write_data((period >> 8) & 0xFFU); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:软件复�?CH395 - * 返回值:CH395F_STATUS_OK - * 限定条件:SPI2 已正确初始化 - * 函数说明:复位耗时�?15ms(TE0),函数内部已包含延�? */ + * 函数功能:软件复位CH395芯片 + * 返回值:CH395F_STATUS_OK - 复位成功 ch395f_status_t + * 限定条件:SPI2外设已正确初始化 + * 函数说明:复位耗时约15ms(TE0),函数内部包含延时等待 + */ ch395f_status_t ch395f_reset(void) { DBG_INFO("ch395f_reset: spi_begin"); ch395f_spi_begin(); @@ -539,29 +574,35 @@ ch395f_status_t ch395f_reset(void) { } /* - * 函数功能:查询命令执行状�? * 返回值:状态字节(CH395F_ERR_SUCCESS / CH395F_ERR_BUSY / ...)uint8_t - * 限定条件:仅在长执行命令(如初始化)后调�? */ + * 函数功能:查询命令执行状态 + * 返回值:状态字节(CH395F_ERR_SUCCESS / CH395F_ERR_BUSY / ...) uint8_t + * 限定条件:仅用于耗时较长的命令(如初始化)之后查询 + */ uint8_t ch395f_get_cmd_status(void) { uint8_t status; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送状态查询命�?*/ + /* 发送状态查询命令 */ ch395f_write_cmd(CH395F_CMD_GET_CMD_STATUS); - /* 读取状态字�?*/ + /* 读取状态字节 */ status = ch395f_read_data(); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* - * 函数功能:轮询等待命令执行完�? * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS �?CH395F_CMD_RET_ABORT�? * 限定条件:在发送长执行命令后调�? * 函数说明:每 20ms 查询一次,直到命令完成或超�? */ + * 函数功能:轮询等待命令执行完成 + * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t + * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功,CH395F_ERR_BUSY = 超时) uint8_t + * 限定条件:需在发送耗时较长的命令后调用 + * 函数说明:每20ms查询一次,直到命令完成或超时 + */ uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) { uint8_t status; uint32_t retry_count = 0U; @@ -582,10 +623,13 @@ uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) { } /* - * 函数功能:初始化 CH395(包�?MAC、PHY、TCP/IP 协议栈) - * 返回值:CH395F_STATUS_OK - 成功,CH395F_STATUS_TIMEOUT - 超时失败 - * 限定条件:芯片已通过 ch395f_reset() 复位 - * 函数说明:阻塞等待初始化完成或超时(芯片需要约 200ms,这里等最�?4s�? */ + * 函数功能:初始化CH395(包括MAC、PHY、TCP/IP协议栈) + * 返回值:CH395F_STATUS_OK - 初始化成功 ch395f_status_t + * CH395F_STATUS_TIMEOUT - 初始化超时 ch395f_status_t + * CH395F_STATUS_ERROR - 初始化失败 ch395f_status_t + * 限定条件:芯片已通过ch395f_reset()复位 + * 函数说明:芯片初始化需约200ms,函数内等待最多4秒 + */ ch395f_status_t ch395f_init(void) { uint8_t status; @@ -603,330 +647,350 @@ ch395f_status_t ch395f_init(void) { } /* - * 函数功能:设�?CH395 IP 地址(低字节在前�? * 入口参数:p_ip - 4 字节 IP 地址指针 uint8_t* - * 限定条件:指针非空,芯片已初始化 + * 函数功能:设置CH395的IP地址(低字节在前) + * 入口参数:p_ip - 4字节IP地址指针 uint8_t* 192.168.x.x格式 + * 限定条件:1. p_ip不可为NULL + * 2. 芯片已初始化 */ void ch395f_set_ip_addr(uint8_t *p_ip) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_ip == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?IP 命令 */ + /* 发送设置IP命令 */ ch395f_write_cmd(CH395F_CMD_SET_IP_ADDR); - /* 逐字节发�?IP 地址 */ + /* 逐字节发送IP地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_ip[i]); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设置网�?IP 地址(低字节在前�? * 入口参数:p_gwip - 4 字节网关 IP 指针 uint8_t* - * 限定条件:指针非空,芯片已初始化 + * 函数功能:设置CH395的网关IP地址(低字节在前) + * 入口参数:p_gwip - 4字节网关IP指针 uint8_t* + * 限定条件:1. p_gwip不可为NULL + * 2. 芯片已初始化 */ void ch395f_set_gwip_addr(uint8_t *p_gwip) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_gwip == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置网关命�?*/ + /* 发送设置网关命令 */ ch395f_write_cmd(CH395F_CMD_SET_GWIP_ADDR); - /* 逐字节发送网�?IP */ + /* 逐字节发送网关IP地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_gwip[i]); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设置子网掩码(低字节在前) - * 入口参数:p_mask - 4 字节子网掩码指针 uint8_t* - * 限定条件:指针非空,芯片已初始化 + * 函数功能:设置CH395的子网掩码(低字节在前) + * 入口参数:p_mask - 4字节子网掩码指针 uint8_t* + * 限定条件:1. p_mask不可为NULL + * 2. 芯片已初始化 */ void ch395f_set_mask_addr(uint8_t *p_mask) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_mask == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置掩码命�?*/ + /* 发送设置掩码命令 */ ch395f_write_cmd(CH395F_CMD_SET_MASK_ADDR); - /* 逐字节发送子网掩�?*/ + /* 逐字节发送子网掩码 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_mask[i]); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?MAC 地址(低字节在前,存储于 EEPROM�? * 入口参数:p_mac - 6 字节 MAC 地址指针 uint8_t* - * 限定条件:指针非空,芯片已初始化 - * 函数说明:执行耗时�?30us(TE5�? */ + * 函数功能:设置CH395的MAC地址(低字节在前,存入EEPROM) + * 入口参数:p_mac - 6字节MAC地址指针 uint8_t* + * 限定条件:1. p_mac不可为NULL + * 2. 芯片已初始化 + * 函数说明:执行耗时约30us(TE5) + */ void ch395f_set_mac_addr(uint8_t *p_mac) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_mac == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?MAC 命令 */ + /* 发送设置MAC命令 */ ch395f_write_cmd(CH395F_CMD_SET_MAC_ADDR); - /* 逐字节发�?MAC 地址 */ + /* 逐字节发送MAC地址 */ for (i = 0; i < 6; i++) { ch395f_write_data(p_mac[i]); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:从 CH395 读取 MAC 地址 - * 出口参数:p_mac - 6 字节 MAC 地址缓冲区指�? uint8_t* - * 限定条件:指针非�? */ + * 函数功能:读取CH395的MAC地址 + * 出口参数:p_mac - 6字节MAC地址缓冲区指针 uint8_t* + * 限定条件:p_mac不可为NULL + */ void ch395f_get_mac_addr(uint8_t *p_mac) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_mac == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送读�?MAC 命令 */ + /* 发送读取MAC命令 */ ch395f_write_cmd(CH395F_CMD_GET_MAC_ADDR); - /* 逐字节读�?MAC 地址 */ + /* 逐字节读取MAC地址 */ for (i = 0; i < 6; i++) { p_mac[i] = ch395f_read_data(); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:读�?IP 信息(IP + 掩码 + DNS,共 20 字节�? * 出口参数:p_buf - 20 字节缓冲区指�? uint8_t* - * 限定条件:指针非�? */ + * 函数功能:读取IP信息(IP地址 + 子网掩码 + DNS,共20字节) + * 出口参数:p_buf - 20字节缓冲区指针 uint8_t* + * 限定条件:p_buf不可为NULL + */ void ch395f_get_ip_inf(uint8_t *p_buf) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_buf == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送读�?IP 信息命令 */ + /* 发送读取IP信息命令 */ ch395f_write_cmd(CH395F_CMD_GET_IP_INF); - /* 逐字节读�?IP 信息(共 20 字节�?*/ + /* 逐字节读取IP信息(共20字节) */ for (i = 0; i < 20; i++) { p_buf[i] = ch395f_read_data(); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:获�?PHY 连接状�? * 返回值:PHY 状态码(CH395F_PHY_DISCONN、CH395F_PHY_10M_FULL 等)uint8_t - * 限定条件:芯片已初始�? */ + * 函数功能:获取PHY连接状态 + * 返回值:PHY状态码(CH395F_PHY_DISCONN / CH395F_PHY_10M_FULL / ...) uint8_t + * 限定条件:芯片已初始化 + */ uint8_t ch395f_get_phy_status(void) { uint8_t status; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送读�?PHY 状态命�?*/ + /* 发送读取PHY状态命令 */ ch395f_write_cmd(CH395F_CMD_GET_PHY_STATUS); - /* 读取 PHY 状�?*/ + /* 读取PHY状态 */ status = ch395f_read_data(); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* - * 函数功能:设�?PHY 连接模式 - * 入口参数:phystat - 连接模式�? uint8_t 0x20 = 自动协商 - * 限定条件:芯片已初始�? */ + * 函数功能:设置PHY连接模式 + * 入口参数:phystat - 连接模式 uint8_t 0x20 = 自动协商 + * 限定条件:芯片已初始化 + */ void ch395f_set_phy(uint8_t phystat) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设�?PHY 命令 */ + /* 发送设置PHY命令 */ ch395f_write_cmd(CH395F_CMD_SET_PHY); - /* 发送连接模式参�?*/ + /* 发送连接模式参数 */ ch395f_write_data(phystat); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:使�?关闭 CH395 响应 PING - * 入口参数:enable - 1 使能 PING 响应�? 关闭 - * 限定条件:芯片已初始�? * 函数说明:手�?5.37 CMD_PING_ENABLE,默认关�? */ + * 函数功能:使能/禁能CH395的PING响应 + * 入口参数:enable - 使能标志 uint8_t 1 = 使能PING响应,0 = 禁能 + * 限定条件:芯片已初始化 + * 函数说明:参考手册5.37节CMD_PING_ENABLE,默认禁能 + */ void ch395f_ping_enable(uint8_t enable) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发�?PING 使能命令 */ + /* 发送PING使能命令 */ ch395f_write_cmd(CH395F_CMD_PING_ENABLE); - /* 发送使能标�?*/ + /* 发送使能标志 */ ch395f_write_data(enable); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:启�?禁用 DHCP - * 入口参数:enable - 1 启用�? 禁用 uint8_t - * 限定条件:芯片已初始�? */ + * 函数功能:使能/禁能DHCP + * 入口参数:enable - 使能标志 uint8_t 1 = 使能,0 = 禁能 + * 限定条件:芯片已初始化 + */ void ch395f_set_dhcp(uint8_t enable) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发�?DHCP 使能命令 */ + /* 发送DHCP使能命令 */ ch395f_write_cmd(CH395F_CMD_DHCP_ENABLE); - /* 发送启�?禁用参数 */ + /* 发送使能/禁能参数 */ ch395f_write_data(enable); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:获�?DHCP 状�? * 返回值:0 = 成功,非�?= 错误/超时 uint8_t - * 限定条件:芯片已初始化且 DHCP 已启�? */ + * 函数功能:获取DHCP状态 + * 返回值:0 = 成功,非零 = 错误/超时 uint8_t + * 限定条件:芯片已初始化且DHCP已使能 + */ uint8_t ch395f_get_dhcp_status(void) { uint8_t status; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送读�?DHCP 状态命�?*/ + /* 发送读取DHCP状态命令 */ ch395f_write_cmd(CH395F_CMD_GET_DHCP_STATUS); - /* 读取 DHCP 状�?*/ + /* 读取DHCP状态 */ status = ch395f_read_data(); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* - * 函数功能:设�?Socket 协议类型 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * proto - 协议类型 uint8_t CH395F_PROTO_TYPE_xxx - * 限定条件:Socket 未打开 + * 函数功能:设置Socket协议类型 + * 入口参数:sock - Socket索引 uint8_t 0 - 3 + * proto - 协议类型 uint8_t CH395F_PROTO_TYPE_xxx + * 限定条件:Socket必须处于关闭状态 */ void ch395f_set_proto_type(uint8_t sock, uint8_t proto) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置协议类型命�?*/ + /* 发送设置协议类型命令 */ ch395f_write_cmd(CH395F_CMD_SET_PROTO_TYPE_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 发送协议类�?*/ + /* 发送协议类型 */ ch395f_write_data(proto); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?Socket 目标 IP 地址 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * p_ip - 4 字节目标 IP 指针 uint8_t* - * 限定条件:指针非�? */ + * 函数功能:设置Socket目标IP地址 + * 入口参数:sock - Socket索引 uint8_t 0 - 3 + * p_ip - 4字节目标IP指针 uint8_t* + * 限定条件:p_ip不可为NULL + */ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip) { uint8_t i; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_ip == NULL) { return; } - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置目�?IP 命令 */ + /* 发送设置目标IP命令 */ ch395f_write_cmd(CH395F_CMD_SET_IP_ADDR_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 逐字节发送目�?IP */ + /* 逐字节发送目标IP地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_ip[i]); } - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?Socket 目标端口(小端序�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * port - 目标端口 uint16_t - * 限定条件:无 + * 函数功能:设置Socket目标端口(小端格式) + * 入口参数:sock - Socket索引 uint8_t 0 - 3 + * port - 目标端口号 uint16_t */ void ch395f_set_des_port(uint8_t sock, uint16_t port) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送设置目标端口命�?*/ + /* 发送设置目标端口命令 */ ch395f_write_cmd(CH395F_CMD_SET_DES_PORT_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送端口低字节 */ @@ -935,24 +999,23 @@ void ch395f_set_des_port(uint8_t sock, uint16_t port) { /* 发送端口高字节 */ ch395f_write_data((port >> 8) & 0xFFU); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:设�?Socket 源端口(小端序) - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * port - 源端�? uint16_t - * 限定条件:无 + * 函数功能:设置Socket源端口(小端格式) + * 入口参数:sock - Socket索引 uint8_t 0 - 3 + * port - 源端口号 uint16_t */ void ch395f_set_sour_port(uint8_t sock, uint16_t port) { - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置源端口命令 */ ch395f_write_cmd(CH395F_CMD_SET_SOUR_PORT_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送端口低字节 */ @@ -961,15 +1024,15 @@ void ch395f_set_sour_port(uint8_t sock, uint16_t port) { /* 发送端口高字节 */ ch395f_write_data((port >> 8) & 0xFFU); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); } /* - * 函数功能:打开 Socket - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t - * 限定条件:协议类型、目�?IP、端口已设置 + * 函数功能:打开Socket + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t + * 限定条件:协议类型、目标IP和端口必须已设置 */ uint8_t ch395f_open_socket(uint8_t sock) { ch395f_spi_begin(); @@ -981,10 +1044,10 @@ uint8_t ch395f_open_socket(uint8_t sock) { } /* - * 函数功能:关�?Socket - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t - * 限定条件:Socket 已打开 + * 函数功能:关闭Socket + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t + * 限定条件:Socket必须已打开 */ uint8_t ch395f_close_socket(uint8_t sock) { ch395f_spi_begin(); @@ -996,10 +1059,10 @@ uint8_t ch395f_close_socket(uint8_t sock) { } /* - * 函数功能:启�?TCP 监听模式 - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t - * 限定条件:Socket 已打开且协议类型为 TCP + * 函数功能:启动TCP监听模式 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t + * 限定条件:Socket必须已打开且协议类型为TCP */ uint8_t ch395f_tcp_listen(uint8_t sock) { ch395f_spi_begin(); @@ -1011,10 +1074,10 @@ uint8_t ch395f_tcp_listen(uint8_t sock) { } /* - * 函数功能:启�?TCP 连接 - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t - * 限定条件:Socket 已打开且协议类型为 TCP + * 函数功能:发起TCP连接 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t + * 限定条件:Socket必须已打开且协议类型为TCP */ uint8_t ch395f_tcp_connect(uint8_t sock) { ch395f_spi_begin(); @@ -1026,10 +1089,11 @@ uint8_t ch395f_tcp_connect(uint8_t sock) { } /* - * 函数功能:断开 TCP 连接 - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t - * 限定条件:TCP 已建立连�? */ + * 函数功能:断开TCP连接 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t + * 限定条件:TCP连接必须已建立 + */ uint8_t ch395f_tcp_disconnect(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_TCP_DISCONNECT_SN); @@ -1040,133 +1104,129 @@ uint8_t ch395f_tcp_disconnect(uint8_t sock) { } /* - * 函数功能:向 Socket 发送缓冲区写入数据(DMA 批量传输�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * p_data - 数据指针 uint8_t* - * len - 数据长度 uint16_t - * 限定条件:指针非空,长度大于 0,TCP 已连接或 UDP 已打开 - * 函数说明:将命令头和数据打包�?DMA 缓冲区,一�?SPI DMA 传输完成 + * 函数功能:向Socket发送缓冲区写入数据(DMA批量传输) + * 入口参数:sock - Socket索引 uint8_t 0 - 3 + * p_data - 数据指针 uint8_t* + * len - 数据长度 uint16_t + * 限定条件:1. p_data不可为NULL + * 2. 数据长度必须大于0 + * 3. TCP必须已连接或UDP已打开 + * 函数说明:1. 将命令头和数据打包到DMA发送缓冲区 + * 2. 一次性SPI DMA传输完成 + * 3. 数据同步屏障确保memcpy写入对DMA可见 */ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_data == NULL) { return; } - /* 计算总长度:1(cmd) + 1(sock) + 2(len) + data */ + /* 计算总长度:1字节命令 + 1字节Socket + 2字节长度 + 数据 */ total = 4U + len; if (total > CH395F_DMA_BUF_SIZE) { return; } - /* 打包命令头到 DMA 发送缓冲区 */ + /* 打包命令头到DMA发送缓冲区 */ s_spi2_dma_tx_buf[0] = CH395F_CMD_WRITE_SEND_BUF_SN; s_spi2_dma_tx_buf[1] = sock; s_spi2_dma_tx_buf[2] = len & 0xFFU; s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; - /* 拷贝用户数据�?DMA 发送缓冲区 */ + /* 拷贝用户数据到DMA发送缓冲区 */ memcpy(&s_spi2_dma_tx_buf[4], p_data, len); - /* 数据同步屏障:确�?memcpy 写入�?DMA 读取前对总线可见 */ + /* 数据同步屏障:确保memcpy写入对DMA可见 */ __DSB(); - /* CS 拉低 -> DMA 批量传输 -> CS 拉高 */ + /* CS低 → DMA批量传输 → CS高 */ ch395f_spi_begin(); ch395f_spi_dma_transfer(total); ch395f_spi_end(); } /* - * 函数功能:从 Socket 接收缓冲区读取数据(DMA 批量传输�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * p_data - 输出缓冲区指�? uint8_t* - * len - 待读取数据长�? uint16_t - * 限定条件:指针非空,长度大于 0,接收缓冲区有数�? * 函数说明:命令头打包到发送缓冲区(数据部分填 0xFF 作时钟)�? * 一�?SPI DMA 全双工传输,有效数据从接收缓冲区偏移 4 字节处拷�? */ + * 函数功能:从Socket接收缓冲区读取数据(DMA批量传输) + * 入口参数:sock - Socket索引 uint8_t 0 - 3 + * p_data - 输出缓冲区指针 uint8_t* + * len - 待读取数据长度 uint16_t + * 限定条件:1. p_data不可为NULL + * 2. 数据长度必须大于0 + * 3. 接收缓冲区中必须有数据 + * 函数说明:1. 命令头打包到发送缓冲区(数据部分填充0xFF作为时钟源) + * 2. 一次性SPI DMA全双工传输 + * 3. 有效数据从接收缓冲区偏移4处拷贝到用户缓冲区 + */ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; - /* 检查输入参数合法�?*/ + /* 检查输入参数合法性 */ if (p_data == NULL) { return; } - /* 计算总长度:1(cmd) + 1(sock) + 2(len) + data */ + /* 计算总长度:1字节命令 + 1字节Socket + 2字节长度 + 数据 */ total = 4U + len; if (total > CH395F_DMA_BUF_SIZE) { return; } - /* 命令头打包到 DMA 发送缓冲区 */ + /* 打包命令头到DMA发送缓冲区 */ s_spi2_dma_tx_buf[0] = CH395F_CMD_READ_RECV_BUF_SN; s_spi2_dma_tx_buf[1] = sock; s_spi2_dma_tx_buf[2] = len & 0xFFU; s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; - /* 数据部分填充 0xFF(SPI 时钟驱动用) */ + /* 数据部分填充0xFF(作为SPI时钟源) */ memset(&s_spi2_dma_tx_buf[4], 0xFFU, len); - /* CS 拉低 -> DMA 全双工传输(发命令头 + 收数据) -> CS 拉高 */ + /* CS低 → DMA全双工传输(发送命令头 + 接收数据) → CS高 */ ch395f_spi_begin(); ch395f_spi_dma_transfer(total); ch395f_spi_end(); - /* �?DMA 接收缓冲区偏�?4 字节处拷贝有效数据到用户缓冲�?*/ + /* 从DMA接收缓冲区偏移4处拷贝有效数据到用户缓冲区 */ memcpy(p_data, &s_spi2_dma_rx_buf[4], len); } /* - * 函数功能:获�?Socket 接收缓冲区数据长�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 函数功能:获取Socket接收缓冲区中数据长度 + * 入口参数:sock - Socket索引 uint8_t 0 - 3 * 返回值:接收数据长度 uint16_t - * 限定条件:Socket 已打开 + * 限定条件:Socket必须已打开 */ uint16_t ch395f_get_recv_len(uint8_t sock) { uint16_t recv_len; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送获取接收长度命�?*/ + /* 发送获取接收长度命令 */ ch395f_write_cmd(CH395F_CMD_GET_RECV_LEN_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 读取长度低字�?*/ + /* 读取长度低字节 */ recv_len = ch395f_read_data(); /* 读取长度高字节并组合 */ recv_len |= (uint16_t)ch395f_read_data() << 8; - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); return recv_len; } /* - * 函数功能:获取全局中断状�? * 返回值:中断状态字�? uint8_t - * 限定条件:芯片已初始�? */ -uint8_t ch395f_get_glob_int_status(void) { - uint8_t status; - - /* 开�?SPI 事务 */ - ch395f_spi_begin(); - - /* 发送读取全局中断状态命�?*/ - ch395f_write_cmd(CH395F_CMD_GET_GLOB_INT_STATUS); - - /* 读取中断状�?*/ - status = ch395f_read_data(); - - /* 结束 SPI 事务 */ - ch395f_spi_end(); - - return status; -} - -/* - * 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7�? * 返回值:中断状�?uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6�? * 限定条件:芯片已初始�? */ + * 函数功能:获取全局中断状态(2字节版,支持Socket 0~7) + * 返回值:中断状态 uint16_t(低字节 = Socket 0~3 + PHY/DHCP, + * 高字节 = Socket 4~7 + DHCPv6) + * 限定条件:芯片已初始化 + */ uint16_t ch395f_get_glob_int_status_all(void) { uint16_t status; @@ -1180,33 +1240,42 @@ uint16_t ch395f_get_glob_int_status_all(void) { } /* - * 函数功能:获�?Socket 中断状�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:Socket 中断状态字�? uint8_t - * 限定条件:芯片已初始�? */ + * 函数功能:获取Socket中断状态 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * 返回值:Socket中断状态字节 uint8_t + * 限定条件:芯片已初始化 + */ uint8_t ch395f_get_sock_int_status(uint8_t sock) { uint8_t status; - /* 开�?SPI 事务 */ + /* 启动SPI事务 */ ch395f_spi_begin(); - /* 发送读�?Socket 中断状态命�?*/ + /* 发送读取Socket中断状态命令 */ ch395f_write_cmd(CH395F_CMD_GET_INT_STATUS_SN); - /* 发�?Socket 索引 */ + /* 发送Socket索引 */ ch395f_write_data(sock); - /* 读取中断状�?*/ + /* 读取中断状态 */ status = ch395f_read_data(); - /* 结束 SPI 事务 */ + /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* - * 函数功能:获�?Socket 状态(2 字节输出�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * status - 2 字节输出缓冲�?[0]=Socket状�? [1]=TCP状�? * 限定条件:芯片已初始�? */ + * 函数功能:获取Socket状态(2字节输出) + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * p_status - 2字节输出缓冲区指针 uint8_t* + * [0] = Socket状态, [1] = TCP状态 + * 限定条件:芯片已初始化 + * 函数说明:Socket状态: 00H=CLOSED, 05H=OPEN + * TCP状态: 00H=CLOSED, 01H=LISTEN, 02H=SYN_SENT, + * 03H=SYN_RCVD, 04H=ESTABLISHED + */ void ch395f_get_socket_status(uint8_t sock, uint8_t *p_status) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_GET_SOCKET_STATUS_SN); @@ -1217,11 +1286,11 @@ void ch395f_get_socket_status(uint8_t sock, uint8_t *p_status) { } /* - * 函数功能:设�?ARP 重传参数 - * 入口参数:period_100ms - ARP 重传周期(单�?100ms�?=默认�?10�?秒) uint8_t - * cnt - ARP 重传次数�?=默认�?3�? uint8_t - * 限定条件:应�?ch395f_init() 之后调用 - * 函数说明:周期最大值约 25.5 秒,255 表示无限重传 + * 函数功能:设置ARP重传参数 + * 入口参数:period_100ms - ARP重传周期(单位:100ms,默认:10 = 1s) uint8_t 0 - 255 + * cnt - ARP重传次数(默认:3) uint8_t 0 - 255 + * 限定条件:应在ch395f_init()之后调用 + * 函数说明:最大周期约25.5秒,255表示无限重传 */ void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt) { ch395f_spi_begin(); @@ -1232,10 +1301,10 @@ void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt) { } /* - * 函数功能:设�?Socket TTL - * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * ttl - TTL �? uint8_t 最�?128 - * 限定条件:Socket 已打开,TCP Server 模式下不生效 + * 函数功能:设置Socket的TTL值 + * 入口参数:sock - Socket索引 uint8_t 0 - 7 + * ttl - TTL值 uint8_t 最大128 + * 限定条件:Socket必须已打开,TCP Server模式下不生效 */ void ch395f_set_ttl(uint8_t sock, uint8_t ttl) { ch395f_spi_begin(); @@ -1247,7 +1316,9 @@ void ch395f_set_ttl(uint8_t sock, uint8_t ttl) { /* * 函数功能:获取不可达信息(用于调试连接失败原因) - * 出口参数:p_buf - 8 字节输出缓冲区(类型+协议�?端口+IP�? * 限定条件:收�?GINT_STAT_UNREACH 中断后调�? */ + * 出口参数:p_buf - 8字节输出缓冲区指针(类型+协议+端口+IP) uint8_t* + * 限定条件:收到GINT_STAT_UNREACH中断后调用 + */ void ch395f_get_unreach_info(uint8_t *p_buf) { uint8_t i = 0; @@ -1258,5 +1329,3 @@ void ch395f_get_unreach_info(uint8_t *p_buf) { } ch395f_spi_end(); } - - diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h index 1daab72..36e71d0 100644 --- a/Drivers/BSP/CH395F/ch395f.h +++ b/Drivers/BSP/CH395F/ch395f.h @@ -528,13 +528,6 @@ uint16_t ch395f_get_recv_len(uint8_t sock); * 函数声明区 - 中断状态 */ -/* - * 函数功能:获取全局中断状态 - * 返回值:中断状态字节 uint8_t - * 限定条件:芯片已初始化 - */ -uint8_t ch395f_get_glob_int_status(void); - /* * 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7) * 返回值:中断状态 uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6) diff --git a/Drivers/BSP/CH395F/ch395f_test.c b/Drivers/BSP/CH395F/ch395f_test.c deleted file mode 100644 index ed57f5c..0000000 --- a/Drivers/BSP/CH395F/ch395f_test.c +++ /dev/null @@ -1,914 +0,0 @@ -/* - * 模块名称:CH395F Driver Test Suite - * 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例, - * 覆盖 SPI 命令路径、Socket 状态机、大文件传输、边界条件等 - * 适用平台:STM32F4 系列(CH395F 以太网芯片) - * 作者:王建锋 - * 创建日期:2026-07-19 - * 修改记录: - * 2026-07-19 Phase 1/2/3 implemented - * 2026-07-22 全面重构为单连接模式,增加 Phase 5/8/10 - */ - -#include -#include -#include "ch395f_test.h" -#include "ch395f.h" -#include "net_socket.h" -#include "net_select.h" -#define DBG_TAG "[CH395T]" -#include "dbg_log.h" - -test_stats_t g_test_stats; - -/* - * Phase 2 配置默认值 - */ -#ifndef PHASE2_REMOTE_IP -#define PHASE2_REMOTE_IP "192.168.1.2" -#endif -#ifndef PHASE2_REMOTE_PORT -#define PHASE2_REMOTE_PORT 8081 -#endif -#ifndef PHASE2_LOCAL_PORT -#define PHASE2_LOCAL_PORT 50000 -#endif -#ifndef PHASE2_LOOP_COUNT -#define PHASE2_LOOP_COUNT 3 -#endif -#ifndef PHASE2_TIMEOUT_MS -#define PHASE2_TIMEOUT_MS 10000 -#endif - -/* - * Phase 3 配置默认值 - */ -#ifndef PHASE3_REMOTE_IP -#define PHASE3_REMOTE_IP "192.168.1.2" -#endif -#ifndef PHASE3_REMOTE_PORT -#define PHASE3_REMOTE_PORT 8082 -#endif -#ifndef PHASE3_LOCAL_PORT -#define PHASE3_LOCAL_PORT 60000 -#endif -#ifndef PHASE3_ECHO_COUNT -#define PHASE3_ECHO_COUNT 30 -#endif -#ifndef PHASE3_TIMEOUT_MS -#define PHASE3_TIMEOUT_MS 30000 -#endif - -/* - * Phase 5 配置默认值(大文件传输) - */ -#ifndef PHASE5_LOCAL_PORT -#define PHASE5_LOCAL_PORT 60000 -#endif -#ifndef PHASE5_FILE_SIZE -#define PHASE5_FILE_SIZE 20480 /* 20KB */ -#endif -#ifndef PHASE5_CHUNK_SIZE -#define PHASE5_CHUNK_SIZE 4096 /* 单次 NET 层收发块大小 */ -#endif -#ifndef PHASE5_BUF_SIZE -#define PHASE5_BUF_SIZE 65536 /* 接收缓冲最大 64KB */ -#endif - -/* - * Phase 8 配置默认值(边界条件) - */ -#ifndef PHASE8_LOCAL_PORT -#define PHASE8_LOCAL_PORT 8080 -#endif - -/* - * Phase 10 配置默认值(KeepAlive) - */ -#ifndef PHASE10_KEEPALIVE_IDLE_MS -#define PHASE10_KEEPALIVE_IDLE_MS 60000 /* 空闲 60 秒触发探测 */ -#endif - -/* ============================ Phase 1 ============================ */ - -void ch395f_phase1_tests(void) { - uint8_t i = 0; - int ver = 0; - - DBG_INFO("=== CH395F Phase 1 Tests ==="); - - ver = ch395f_get_version(); - TEST_CHECK(ver > 0, "GET_VERSION: 0x%02X (ver=%d)", ver, ver & 0x3F); - - uint8_t cmd_st = ch395f_get_cmd_status(); - TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, - "GET_CMD_STATUS: 0x%02X (%s)", cmd_st, - cmd_st == CH395F_ERR_SUCCESS ? "SUCCESS" : - cmd_st == CH395F_ERR_BUSY ? "BUSY" : "OTHER"); - - uint8_t ip_info[20]; - ch395f_get_ip_inf(ip_info); - DBG_INFO("GET_IP_INF:"); - DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); - DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); - DBG_INFO(" MASK: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); - DBG_INFO(" DNS1: %d.%d.%d.%d", ip_info[12], ip_info[13], ip_info[14], ip_info[15]); - DBG_INFO(" DNS2: %d.%d.%d.%d", ip_info[16], ip_info[17], ip_info[18], ip_info[19]); - TEST_CHECK(ip_info[0]==192 && ip_info[1]==168 && ip_info[2]==1 && ip_info[3]==100, - "IP match"); - TEST_CHECK(ip_info[4]==192 && ip_info[5]==168 && ip_info[6]==1 && ip_info[7]==1, - "GW match"); - TEST_CHECK(ip_info[8]==255 && ip_info[9]==255 && ip_info[10]==255 && ip_info[11]==0, - "MASK match"); - - uint16_t gint_all = ch395f_get_glob_int_status_all(); - TEST_CHECK((gint_all & 0xFF) == 0x04 || (gint_all & 0xFF) == 0x00, - "GINT_STATUS_ALL: 0x%04X", gint_all); - - uint8_t gint_1b = ch395f_get_glob_int_status(); - TEST_CHECK(gint_1b == 0x00, - "GINT_STATUS_1byte: 0x%02X (expect 0x00)", gint_1b); - - { - uint8_t st[2]; - ch395f_get_socket_status(0, st); - TEST_CHECK(st[0] == 0x05 && st[1] == 0x01, - "SOCK_STATUS(0): sock=0x%02X, tcp=0x%02X", st[0], st[1]); - } - - ch395f_set_arp(10, 5); - TEST_CHECK(1, "SET_ARP(period=10*100ms, cnt=5): OK"); - - ch395f_set_ttl(0, 64); - TEST_CHECK(1, "SET_TTL(sock=0, ttl=64): OK"); - - ch395f_clear_recv_buf(0); - TEST_CHECK(1, "CLEAR_RECV_BUF(sock=0): OK"); - for (i = 0; i < 8; i++) { - uint8_t si = ch395f_get_sock_int_status(i); - TEST_CHECK(si == 0x00, "SOCK_INT(%d): 0x%02X", i, si); - } - - TEST_REPORT("Phase 1"); -} - -/* ============================ Phase 2 ============================ */ - -void ch395f_phase2_tests(void) { - uint8_t sock = 0; - uint8_t sock_int = 0; - uint8_t rx_buf[300] = {0}; - int i = 0; - uint32_t tick = 0; - uint16_t int_st = 0; - int iter_pass = 0; - - DBG_INFO("=== CH395F Phase 2 Tests (TCP Client - Direct Driver) ==="); - - uint8_t ip_arr[4]; - { - uint32_t a[4]; - sscanf(PHASE2_REMOTE_IP, "%lu.%lu.%lu.%lu", &a[0], &a[1], &a[2], &a[3]); - ip_arr[0] = (uint8_t)a[0]; - ip_arr[1] = (uint8_t)a[1]; - ip_arr[2] = (uint8_t)a[2]; - ip_arr[3] = (uint8_t)a[3]; - } - DBG_INFO("Target: %s:%d", PHASE2_REMOTE_IP, PHASE2_REMOTE_PORT); - - sock = 0; - - ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP); - ch395f_set_des_ip(sock, ip_arr); - ch395f_set_des_port(sock, PHASE2_REMOTE_PORT); - - for (i = 0; i < PHASE2_LOOP_COUNT; i++) { - DBG_INFO("--- Iteration %d/%d ---", i + 1, PHASE2_LOOP_COUNT); - iter_pass = 1; - - ch395f_set_sour_port(sock, PHASE2_LOCAL_PORT + i); - - if (ch395f_open_socket(sock) != CH395F_ERR_SUCCESS) { TEST_CHECK(0, "open socket"); iter_pass = 0; goto phase2_iter_done; } - if (ch395f_tcp_connect(sock) != CH395F_ERR_SUCCESS) { TEST_CHECK(0, "tcp connect"); iter_pass = 0; ch395f_close_socket(sock); goto phase2_iter_done; } - - tick = HAL_GetTick(); - while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) { - int_st = ch395f_get_glob_int_status_all(); - if (int_st & (1U << (sock + 4))) { - sock_int = ch395f_get_sock_int_status(sock); - if (sock_int & CH395F_SINT_STAT_CONNECT) break; - } - HAL_Delay(10); - } - if (HAL_GetTick() - tick >= PHASE2_TIMEOUT_MS) { TEST_CHECK(0, "connect timeout"); iter_pass = 0; ch395f_close_socket(sock); goto phase2_iter_done; } - - HAL_Delay(100); - - { - uint8_t test_buf[64] = {0}; - const char *msg = "HelloFromCH395F_TCP_Client! This is a 64-byte test message."; - int p, mlen = (int)strlen(msg); - memcpy(test_buf, msg, mlen); - for (p = mlen; p < 64; p++) test_buf[p] = (uint8_t)('0' + (p - mlen) % 10); - ch395f_write_send_buf(sock, test_buf, 64); - } - - { - int got_echo = 0; - tick = HAL_GetTick(); - while (HAL_GetTick() - tick < 10000) { - uint16_t rlen = ch395f_get_recv_len(sock); - if (rlen > 0) { - uint16_t read_len = (rlen > sizeof(rx_buf)) ? (uint16_t)sizeof(rx_buf) : rlen; - memset(rx_buf, 0, sizeof(rx_buf)); - ch395f_read_recv_buf(sock, rx_buf, read_len); - DBG_INFO("recv: %d bytes [%.30s]", read_len, (char *)rx_buf); - ch395f_write_send_buf(sock, rx_buf, read_len); - got_echo = 1; - break; - } - int_st = ch395f_get_glob_int_status_all(); - if (int_st & (1U << (sock + 4))) { - sock_int = ch395f_get_sock_int_status(sock); - if (sock_int & CH395F_SINT_STAT_DISCONNECT) { DBG_INFO("DISCONNECT"); break; } - if (sock_int & CH395F_SINT_STAT_SOCK_TIMEOUT) { DBG_INFO("SOCK_TIMEOUT"); break; } - } - HAL_Delay(50); - } - if (!got_echo) { TEST_CHECK(0, "echo recv (iter %d)", i + 1); iter_pass = 0; } - } - -phase2_iter_done: - if (iter_pass) TEST_CHECK(1, "Iteration %d PASS", i + 1); - - ch395f_close_socket(sock); - { - uint8_t st[2]; - uint32_t wait = HAL_GetTick(); - while (HAL_GetTick() - wait < 5000) { - ch395f_get_socket_status(sock, st); - if (st[0] == CH395F_SOCKET_CLOSED) break; - HAL_Delay(50); - } - DBG_INFO("closed: sock=0x%02X tcp=0x%02X", st[0], st[1]); - } - HAL_Delay(200); - } - - TEST_REPORT("Phase 2"); -} - -/* ============================ Phase 3 ============================ */ - -void ch395f_phase3_tests(void) { - uint8_t sock = 0; - uint8_t rx_buf[300] = {0}; - uint32_t tick = 0; - int i = 0; - int echo_ok = 0; - - DBG_INFO("=== CH395F Phase 3 Tests (UDP) ==="); - - sock = 0; - - ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP); - ch395f_set_sour_port(sock, PHASE3_LOCAL_PORT); - { - uint8_t zero_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF}; - ch395f_set_des_ip(sock, zero_ip); - } - ch395f_set_des_port(sock, 0); - - TEST_CHECK(ch395f_open_socket(sock) == CH395F_ERR_SUCCESS, - "UDP socket %d opened (port=%d)", sock, PHASE3_LOCAL_PORT); - - HAL_Delay(200); - - for (i = 0; i < PHASE3_ECHO_COUNT; i++) { - tick = HAL_GetTick(); - echo_ok = 0; - while (HAL_GetTick() - tick < PHASE3_TIMEOUT_MS) { - uint16_t rlen = ch395f_get_recv_len(sock); - if (rlen > 8) { - memset(rx_buf, 0, sizeof(rx_buf)); - ch395f_read_recv_buf(sock, rx_buf, rlen); - - uint16_t payload_len = rlen - 8; - uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8); - uint8_t *src_ip = &rx_buf[4]; - uint8_t *payload = &rx_buf[8]; - - DBG_INFO("UDP #%d: %dB from %d.%d.%d.%d:%d [%.32s]", - i + 1, payload_len, - src_ip[0], src_ip[1], src_ip[2], src_ip[3], src_port, - payload_len > 0 ? (char *)payload : ""); - - ch395f_set_des_ip(sock, src_ip); - ch395f_set_des_port(sock, src_port); - ch395f_write_send_buf(sock, payload, payload_len); - - { - uint32_t swait = HAL_GetTick(); - while (HAL_GetTick() - swait < 2000) { - uint16_t gs = ch395f_get_glob_int_status_all(); - if (gs & (1U << (sock + 4))) { - if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) - break; - } - HAL_Delay(10); - } - } - echo_ok = 1; - break; - } - HAL_Delay(50); - } - TEST_CHECK(echo_ok, "UDP echo #%d/%d", i + 1, PHASE3_ECHO_COUNT); - if (!echo_ok) break; - } - - ch395f_close_socket(sock); - TEST_CHECK(1, "UDP socket closed"); - - TEST_REPORT("Phase 3"); -} - -/* ============================ Phase 4 ============================ */ -/* - * NET 层 TCP Echo(单连接模式) - * 在 netTask 中运行,仅操作 Socket 0 - * 覆盖:64B、1460B(MSS)、4096B(缓冲满) 回显 - */ - -#ifdef ENABLE_PHASE4_TESTS -static char s_phase4_buf[8192]; -static uint32_t s_phase4_echo_count = 0; -#endif - -void ch395f_phase4_tests(void) { -#ifdef ENABLE_PHASE4_TESTS - net_sock_t *sk = net_get_sock(0); - if (sk == NULL || !sk->in_use) { - return; - } - - if (sk->state == NET_SOCK_STATE_ESTABLISHED) { - int n = net_recv_sock(sk, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT); - if (n > 0) { - int sent = net_send_sock(sk, s_phase4_buf, n); - if (sent == n) { - s_phase4_echo_count++; - if (s_phase4_echo_count <= 10 || (s_phase4_echo_count % 100 == 0)) { - DBG_INFO("Phase4: echo #%lu (%d bytes)", s_phase4_echo_count, n); - } - } - } - } -#endif -} - -/* ============================ Phase 5 ============================ */ -/* - * NET 层大文件传输测试 - * 验证超过 CH395F 内部缓冲区大小(4KB)的连续数据收发 - * 覆盖:20KB、64KB 分块传输 - */ - -#ifdef ENABLE_PHASE5_TESTS -static int s_phase5_active = 0; -static uint32_t s_phase5_total_sent = 0; -static uint32_t s_phase5_total_recv = 0; -static uint8_t s_phase5_tx_buf[PHASE5_FILE_SIZE]; -static uint8_t s_phase5_rx_buf[PHASE5_BUF_SIZE]; - -static void phase5_fill_pattern(uint8_t *buf, uint32_t size, uint32_t offset) { - for (uint32_t i = 0; i < size; i++) { - buf[i] = (uint8_t)((offset + i) & 0xFF); - } -} - -static int phase5_verify_pattern(uint8_t *buf, uint32_t size, uint32_t offset) { - for (uint32_t i = 0; i < size; i++) { - if (buf[i] != (uint8_t)((offset + i) & 0xFF)) { - DBG_ERROR("Phase5: verify fail at offset %lu (expected 0x%02X, got 0x%02X)", - offset + i, (uint8_t)((offset + i) & 0xFF), buf[i]); - return 0; - } - } - return 1; -} -#endif - -void ch395f_phase5_init(void) { -#ifdef ENABLE_PHASE5_TESTS - s_phase5_active = 0; - s_phase5_total_sent = 0; - s_phase5_total_recv = 0; - DBG_INFO("Phase5: file transfer test, size=%d bytes, chunk=%d bytes", - PHASE5_FILE_SIZE, PHASE5_CHUNK_SIZE); -#endif -} - -void ch395f_phase5_tests(void) { -#ifdef ENABLE_PHASE5_TESTS - net_sock_t *sk = net_get_sock(0); - if (sk == NULL || !sk->in_use) { - return; - } - - if (!s_phase5_active) { - if (sk->state == NET_SOCK_STATE_ESTABLISHED) { - s_phase5_active = 1; - s_phase5_total_sent = 0; - s_phase5_total_recv = 0; - - phase5_fill_pattern(s_phase5_tx_buf, PHASE5_FILE_SIZE, 0); - DBG_INFO("Phase5: starting %d-byte file transfer (sock=0)", PHASE5_FILE_SIZE); - } - return; - } - - /* 发送阶段:分块发送,每块 PHASE5_CHUNK_SIZE */ - if (s_phase5_total_sent < PHASE5_FILE_SIZE) { - uint32_t remaining = PHASE5_FILE_SIZE - s_phase5_total_sent; - uint32_t chunk = (remaining > PHASE5_CHUNK_SIZE) ? PHASE5_CHUNK_SIZE : remaining; - - int sent = net_send_sock(sk, s_phase5_tx_buf + s_phase5_total_sent, (int)chunk); - if (sent > 0) { - s_phase5_total_sent += (uint32_t)sent; - DBG_INFO("Phase5: sent %d/%d bytes", s_phase5_total_sent, PHASE5_FILE_SIZE); - } - return; - } - - /* 接收阶段:分块接收并验证 */ - if (s_phase5_total_recv < PHASE5_FILE_SIZE) { - uint32_t remaining = PHASE5_FILE_SIZE - s_phase5_total_recv; - int max_read = (int)(remaining < NET_TASK_BUF_SIZE ? remaining : NET_TASK_BUF_SIZE); - - int n = net_recv_sock(sk, s_phase5_rx_buf, max_read, NET_MSG_DONTWAIT); - if (n > 0) { - int ok = phase5_verify_pattern(s_phase5_rx_buf, (uint32_t)n, s_phase5_total_recv); - TEST_CHECK(ok, "Phase5: recv verify at offset %lu (%d/%d bytes)", - s_phase5_total_recv, s_phase5_total_recv + (uint32_t)n, PHASE5_FILE_SIZE); - s_phase5_total_recv += (uint32_t)n; - } - return; - } - - /* 完成 */ - if (s_phase5_total_recv >= PHASE5_FILE_SIZE) { - TEST_CHECK(1, "Phase5: %d-byte file transfer complete (sent=%lu, recv=%lu)", - PHASE5_FILE_SIZE, s_phase5_total_sent, s_phase5_total_recv); - s_phase5_active = 0; - } -#endif -} - -/* ============================ Phase 6 ============================ */ - -#ifndef PHASE6_ANNOUNCE_PORT -#define PHASE6_ANNOUNCE_PORT 60001 -#endif -#ifndef PHASE6_ANNOUNCE_SOCK -#define PHASE6_ANNOUNCE_SOCK 6 -#endif - -void ch395f_phase6_tests(void) { -#ifdef ENABLE_PHASE6_TESTS - uint8_t status = 0; - uint8_t ip_info[20] = {0}; - uint32_t tick = 0; - - DBG_INFO("=== CH395F Phase 6 Tests (DHCP) ==="); - - ch395f_set_dhcp(1); - TEST_CHECK(1, "DHCP enabled, waiting for IP..."); - - HAL_Delay(500); - tick = HAL_GetTick(); - while (HAL_GetTick() - tick < 30000) { - uint16_t gint = ch395f_get_glob_int_status_all(); - if (gint & CH395F_GINT_STAT_DHCP) { - DBG_INFO("DHCP interrupt"); - } - - status = ch395f_get_dhcp_status(); - if (status == 0x00) { - TEST_CHECK(1, "DHCP: SUCCESS"); - break; - } - - HAL_Delay(500); - } - - TEST_CHECK(HAL_GetTick() - tick < 30000, - "DHCP completed within 30s (status=0x%02X)", status); - - if (HAL_GetTick() - tick >= 30000) { - goto phase6_end; - } - - ch395f_get_ip_inf(ip_info); - DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); - DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); - DBG_INFO(" MASK: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); - - TEST_CHECK(ip_info[0] != 0 || ip_info[1] != 0 || ip_info[2] != 0 || ip_info[3] != 0, - "IP = %d.%d.%d.%d (non-zero)", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); - - { -#define PHASE6_HELLO_PORT 60000 - uint8_t anno_sock = PHASE6_ANNOUNCE_SOCK; - uint8_t bcast_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF}; - uint8_t rx_buf[64] = {0}; - - ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP); - ch395f_set_send_buf(anno_sock, 24, 2); - ch395f_set_recv_buf(anno_sock, 26, 2); - ch395f_set_sour_port(anno_sock, PHASE6_HELLO_PORT); - ch395f_set_des_ip(anno_sock, bcast_ip); - ch395f_set_des_port(anno_sock, 0); - - if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) { - ch395f_set_des_ip(anno_sock, bcast_ip); - ch395f_set_des_port(anno_sock, PHASE6_ANNOUNCE_PORT); - ch395f_write_send_buf(anno_sock, ip_info, 4); - - { - uint32_t swait = HAL_GetTick(); - while (HAL_GetTick() - swait < 2000) { - uint16_t gs = ch395f_get_glob_int_status_all(); - if (gs & (1U << (anno_sock + 4))) { - if (ch395f_get_sock_int_status(anno_sock) & CH395F_SINT_STAT_SENBUF_FREE) - break; - } - HAL_Delay(10); - } - } - DBG_INFO("DHCP announcement: IP=%d.%d.%d.%d (port %d->%d)", - ip_info[0], ip_info[1], ip_info[2], ip_info[3], - PHASE6_HELLO_PORT, PHASE6_ANNOUNCE_PORT); - TEST_CHECK(1, "DHCP announcement sent"); - - ch395f_set_des_ip(anno_sock, bcast_ip); - ch395f_set_des_port(anno_sock, 0); - - uint32_t wait = HAL_GetTick(); - uint8_t hello_ok = 0; - while (HAL_GetTick() - wait < 5000) { - ch395f_get_glob_int_status_all(); - uint16_t rlen = ch395f_get_recv_len(anno_sock); - if (rlen > 8) { - memset(rx_buf, 0, sizeof(rx_buf)); - ch395f_read_recv_buf(anno_sock, rx_buf, rlen); - uint16_t payload_len = rlen - 8; - uint8_t *payload = &rx_buf[8]; - - if (payload_len == 5 && memcmp(payload, "HELLO", 5) == 0) { - uint8_t *src_ip = &rx_buf[4]; - uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8); - ch395f_set_des_ip(anno_sock, src_ip); - ch395f_set_des_port(anno_sock, src_port); - ch395f_write_send_buf(anno_sock, payload, payload_len); - HAL_Delay(100); - DBG_INFO("HELLO response sent to %d.%d.%d.%d:%d", - src_ip[0], src_ip[1], src_ip[2], src_ip[3], src_port); - TEST_CHECK(1, "HELLO echo OK"); - hello_ok = 1; - break; - } - } - HAL_Delay(50); - } - TEST_CHECK(hello_ok, "HELLO echo within 5s"); - - ch395f_close_socket(anno_sock); - } else { - TEST_CHECK(0, "DHCP announcement: open socket failed"); - } - } - -phase6_end: - TEST_REPORT("Phase 6"); -#endif -} - -/* ============================ Phase 7 ============================ */ -/* - * Select/Poll I/O 多路复用测试(单连接模式) - * 在 netTask 中运行,仅操作 Socket 0 - */ - -#ifdef ENABLE_PHASE7_TESTS -static char s_phase7_buf[8192]; -static uint32_t s_phase7_count = 0; -#endif - -void ch395f_phase7_tests(void) { -#ifdef ENABLE_PHASE7_TESTS - net_sock_t *sk = net_get_sock(0); - if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED) { - return; - } - - net_fd_set readfds; - net_timeval tv; - - NET_FD_ZERO(&readfds); - NET_FD_SET(0, &readfds); - - tv.tv_sec = 0; - tv.tv_usec = 50000; - - int n = net_select(1, &readfds, NULL, NULL, &tv); - if (n <= 0) return; - - if (NET_FD_ISSET(0, &readfds)) { - int len = net_recv_sock(sk, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT); - if (len > 0) { - net_send_sock(sk, s_phase7_buf, len); - s_phase7_count++; - TEST_CHECK(1, "Phase7: select echo #%lu (%d bytes)", s_phase7_count, len); - } - } -#endif -} - -/* ============================ Phase 8 ============================ */ -/* - * 边界条件测试(单连接模式,netTask 中运行) - * 覆盖:零长度收发、DMA 边界(4095/4096/4097B)、 - * recv 缓冲区小于数据、连续快速发送、快速重连 - */ - -#ifdef ENABLE_PHASE8_TESTS -static int s_phase8_stage = 0; /* 0=idle, 1=4095B, 2=4096B, 3=4097B, 4=zero, 5=fastsend */ -static uint32_t s_phase8_sent = 0; -static uint32_t s_phase8_recv = 0; -static uint8_t s_phase8_tx[8192]; -static uint8_t s_phase8_rx[8192]; - -static void phase8_fill(uint8_t *buf, uint32_t size, uint8_t seed) { - for (uint32_t i = 0; i < size; i++) { - buf[i] = (uint8_t)(seed + i); - } -} - -static int phase8_verify(uint8_t *buf, uint32_t size, uint8_t seed) { - for (uint32_t i = 0; i < size; i++) { - if (buf[i] != (uint8_t)(seed + i)) { - return 0; - } - } - return 1; -} -#endif - -void ch395f_phase8_init(void) { -#ifdef ENABLE_PHASE8_TESTS - s_phase8_stage = 0; - DBG_INFO("Phase8: boundary tests initialized"); -#endif -} - -void ch395f_phase8_tests(void) { -#ifdef ENABLE_PHASE8_TESTS - net_sock_t *sk = net_get_sock(0); - if (sk == NULL || !sk->in_use) { - return; - } - - if (sk->state == NET_SOCK_STATE_LISTENING) { - /* 等待连接 */ - return; - } - - if (sk->state != NET_SOCK_STATE_ESTABLISHED) { - s_phase8_stage = 0; - return; - } - - switch (s_phase8_stage) { - case 0: { - /* Stage 1: 发送 4095 字节(DMA 边界 -1:单次 SPI 事务可容纳) */ - DBG_INFO("Phase8: stage 1/5 — send 4095 bytes (DMA boundary -1)"); - phase8_fill(s_phase8_tx, 4095, 0x10); - int sent = net_send_sock(sk, s_phase8_tx, 4095); - TEST_CHECK(sent == 4095, "Phase8: send 4095 bytes (sent=%d)", sent); - s_phase8_sent = (sent > 0) ? (uint32_t)sent : 0; - s_phase8_recv = 0; - s_phase8_stage = 1; - break; - } - case 1: { - /* 等待接收 4095 字节回显 */ - int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, - (int)(4095 - s_phase8_recv), NET_MSG_DONTWAIT); - if (n > 0) { - s_phase8_recv += (uint32_t)n; - if (s_phase8_recv >= 4095) { - int ok = phase8_verify(s_phase8_rx, 4095, 0x10); - TEST_CHECK(ok, "Phase8: verify 4095 bytes echo"); - s_phase8_stage = 2; - } - } - break; - } - case 2: { - /* Stage 2: 发送 4096 字节(DMA 边界:刚好填满 SPI 事务) */ - DBG_INFO("Phase8: stage 2/5 — send 4096 bytes (DMA boundary exact)"); - phase8_fill(s_phase8_tx, 4096, 0x20); - int sent = net_send_sock(sk, s_phase8_tx, 4096); - TEST_CHECK(sent == 4096, "Phase8: send 4096 bytes (sent=%d)", sent); - s_phase8_sent = (sent > 0) ? (uint32_t)sent : 0; - s_phase8_recv = 0; - s_phase8_stage = 3; - break; - } - case 3: { - int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, - (int)(4096 - s_phase8_recv), NET_MSG_DONTWAIT); - if (n > 0) { - s_phase8_recv += (uint32_t)n; - if (s_phase8_recv >= 4096) { - int ok = phase8_verify(s_phase8_rx, 4096, 0x20); - TEST_CHECK(ok, "Phase8: verify 4096 bytes echo"); - s_phase8_stage = 4; - } - } - break; - } - case 4: { - /* Stage 3: 发送 4097 字节(DMA 边界 +1:需两次 SPI 事务) */ - DBG_INFO("Phase8: stage 3/5 — send 4097 bytes (DMA boundary +1, two SPI xacts)"); - phase8_fill(s_phase8_tx, 4097, 0x30); - int sent = net_send_sock(sk, s_phase8_tx, 4097); - TEST_CHECK(sent == 4097, "Phase8: send 4097 bytes (sent=%d)", sent); - s_phase8_sent = (sent > 0) ? (uint32_t)sent : 0; - s_phase8_recv = 0; - s_phase8_stage = 5; - break; - } - case 5: { - int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, - (int)(4097 - s_phase8_recv), NET_MSG_DONTWAIT); - if (n > 0) { - s_phase8_recv += (uint32_t)n; - if (s_phase8_recv >= 4097) { - int ok = phase8_verify(s_phase8_rx, 4097, 0x30); - TEST_CHECK(ok, "Phase8: verify 4097 bytes echo"); - s_phase8_stage = 6; - } - } - break; - } - case 6: { - /* Stage 4: 零长度发送(验证错误处理) */ - DBG_INFO("Phase8: stage 4/5 — zero-length send/recv"); - int sent = net_send_sock(sk, s_phase8_tx, 0); - TEST_CHECK(sent < 0, "Phase8: send 0 bytes returns %d (expected <0)", sent); - s_phase8_stage = 7; - break; - } - case 7: { - /* Stage 5: 连续快速发送 20 个小包(验证 SENDBUF_FREE 等待机制) */ - DBG_INFO("Phase8: stage 5/5 — rapid 20x 100-byte sends"); - phase8_fill(s_phase8_tx, 100, 0x40); - int all_ok = 1; - for (int i = 0; i < 20; i++) { - int sent = net_send_sock(sk, s_phase8_tx, 100); - if (sent != 100) { - TEST_CHECK(0, "Phase8: rapid send #%d failed (sent=%d)", i + 1, sent); - all_ok = 0; - break; - } - osDelay(1); - } - if (all_ok) { - TEST_CHECK(1, "Phase8: 20x rapid sends OK"); - s_phase8_sent = 2000; - s_phase8_recv = 0; - s_phase8_stage = 8; - } else { - s_phase8_stage = 0; - } - break; - } - case 8: { - /* 接收所有快速发送的回显 */ - int n = net_recv_sock(sk, s_phase8_rx + s_phase8_recv, - (int)(2000 - s_phase8_recv), NET_MSG_DONTWAIT); - if (n > 0) { - s_phase8_recv += (uint32_t)n; - if (s_phase8_recv >= 2000) { - TEST_CHECK(1, "Phase8: all 2000 bytes echo received"); - TEST_REPORT("Phase 8"); - s_phase8_stage = 0; - } - } - break; - } - default: - s_phase8_stage = 0; - break; - } -#endif -} - -/* ============================ Phase 9 ============================ */ -/* - * PHY 链路恢复测试(需物理插拔网线) - * 在 main.c 中单独调用,或在 netTask 中通过宏启用 - */ - -void ch395f_phase9_tests(void) { -#ifdef ENABLE_PHASE9_TESTS - DBG_INFO("=== CH395F Phase 9 Tests (PHY Recovery) ==="); - - /* TC-NET-901: 拔线后检测 PHY 断开 */ - uint32_t wait = HAL_GetTick(); - uint8_t phy_disconnected = 0; - while (HAL_GetTick() - wait < 15000) { - uint16_t gint = ch395f_get_glob_int_status_all(); - if (gint & CH395F_GINT_STAT_PHY_CHANGE) { - uint8_t phy = ch395f_get_phy_status(); - DBG_INFO("PHY_CHANGE: 0x%02X", phy); - if (phy == CH395F_PHY_DISCONN) { - TEST_CHECK(1, "PHY disconnect detected"); - phy_disconnected = 1; - break; - } - } - HAL_Delay(100); - } - if (!phy_disconnected) { - TEST_CHECK(0, "PHY disconnect NOT detected within 15s (unplug cable?)"); - goto phase9_end; - } - - /* TC-NET-902: 插回网线后检测链路恢复 */ - wait = HAL_GetTick(); - uint8_t phy_reconnected = 0; - while (HAL_GetTick() - wait < 30000) { - uint16_t gint = ch395f_get_glob_int_status_all(); - if (gint & CH395F_GINT_STAT_PHY_CHANGE) { - uint8_t phy = ch395f_get_phy_status(); - DBG_INFO("PHY_CHANGE: 0x%02X", phy); - if (phy != CH395F_PHY_DISCONN) { - TEST_CHECK(1, "PHY reconnected (0x%02X)", phy); - phy_reconnected = 1; - break; - } - } - HAL_Delay(100); - } - if (!phy_reconnected) { - TEST_CHECK(0, "PHY reconnect NOT detected within 30s (re-plug cable?)"); - } - -phase9_end: - TEST_REPORT("Phase 9"); -#endif -} - -/* ============================ Phase 10 ============================ */ -/* - * KeepAlive 超时断开测试 - * 在 netTask 中运行,监视 Socket 0 的 DISCONNECT/TIMEOUT 事件 - * PC 连接后静置(不发数据),验证 KeepAlive 在空闲超时后触发断开 - */ - -#ifdef ENABLE_PHASE10_TESTS -static int s_phase10_connected = 0; -static uint32_t s_phase10_start_tick = 0; -#endif - -void ch395f_phase10_tests(void) { -#ifdef ENABLE_PHASE10_TESTS - net_sock_t *sk = net_get_sock(0); - if (sk == NULL || !sk->in_use) { - return; - } - - if (!s_phase10_connected) { - if (sk->state == NET_SOCK_STATE_ESTABLISHED) { - s_phase10_connected = 1; - s_phase10_start_tick = HAL_GetTick(); - DBG_INFO("Phase10: connection established, waiting for KeepAlive timeout (%dms)...", - PHASE10_KEEPALIVE_IDLE_MS); - } - return; - } - - /* 已建立连接,等待断开(KeepAlive 触发) */ - if (sk->state == NET_SOCK_STATE_LISTENING) { - uint32_t elapsed = HAL_GetTick() - s_phase10_start_tick; - TEST_CHECK(elapsed >= PHASE10_KEEPALIVE_IDLE_MS, - "Phase10: KeepAlive triggered after %lums (idle=%dms)", - elapsed, PHASE10_KEEPALIVE_IDLE_MS); - s_phase10_connected = 0; - TEST_REPORT("Phase 10"); - } -#endif -} diff --git a/Drivers/BSP/CH395F/ch395f_test.h b/Drivers/BSP/CH395F/ch395f_test.h deleted file mode 100644 index bbd48be..0000000 --- a/Drivers/BSP/CH395F/ch395f_test.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 模块名称:CH395F Driver Test Suite - * 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例, - * 覆盖 SPI 命令路径、协议栈配置、Socket 状态机、大文件传输、 - * 边界条件、KeepAlive、PHY 链路恢复等,基于单连接 socket 模式 - * 适用平台:STM32F4 系列(CH395F 以太网芯片) - * 作者:王建锋 - * 创建日期:2026-07-19 - * 修改记录: - * 2026-07-22 全面重构为单连接模式,增加大文件传输、边界条件等测试 - * - * === 测试阶段一览 === - * Phase | 类型 | 描述 - * ---------|--------------|----------------------------------------------- - * Phase 1 | 独立运行 | 寄存器读写 + SPI 命令路径验证 - * Phase 2 | PC 配合 | TCP Client 收发 + 关闭重连 - * Phase 3 | PC 配合 | UDP Server Echo(HELLO + PING + 大包) - * Phase 4 | netTask | NET 层 TCP Echo(单连接模式,基础收发验证) - * Phase 5 | netTask | NET 层大文件传输(20KB+ 分块收发) - * Phase 6 | PC 配合 | DHCP 自动获取 IP - * Phase 7 | netTask | Select/Poll I/O 多路复用 - * Phase 8 | netTask | 边界条件测试(零长度、DMA 边界、缓冲溢出) - * Phase 9 | 需拔插网线 | PHY 链路恢复 - * Phase 10 | netTask | KeepAlive 超时断开测试 - */ - -#ifndef __CH395F_TEST_H -#define __CH395F_TEST_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/* - * 阶段使能开关(取消注释即启用对应阶段) - * 注意:一次仅启用一个阶段(Phase 4/5/7/8 共享 netTask 循环,可同时启用) - * Phase 6 (DHCP) 会改变 IP,必须独立运行 - */ -//#define ENABLE_PHASE1_TESTS /* 寄存器读写 + SPI 命令路径 */ -//#define ENABLE_PHASE2_TESTS /* TCP Client 收发 + 关闭重连 */ -//#define ENABLE_PHASE3_TESTS /* UDP Server Echo */ -#define ENABLE_PHASE4_TESTS /* NET 层 TCP Echo(单连接基础收发) */ -//#define ENABLE_PHASE5_TESTS /* NET 层大文件传输(20KB+ 分块) */ -//#define ENABLE_PHASE6_TESTS /* DHCP 自动获取 IP(会改 IP!独立运行) */ -//#define ENABLE_PHASE7_TESTS /* Select/Poll I/O 多路复用 */ -//#define ENABLE_PHASE8_TESTS /* 边界条件测试 */ -//#define ENABLE_PHASE9_TESTS /* PHY 链路恢复测试 */ -//#define ENABLE_PHASE10_TESTS /* KeepAlive 超时断开测试 */ - -/* 测试统计 */ -typedef struct { - uint16_t total; - uint16_t passed; - uint16_t failed; -} test_stats_t; - -extern test_stats_t g_test_stats; - -#define TEST_CHECK(cond, fmt, ...) do { \ - g_test_stats.total++; \ - if (cond) { \ - g_test_stats.passed++; \ - DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); \ - } else { \ - g_test_stats.failed++; \ - DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); \ - } \ -} while (0) - -#define TEST_REPORT(name) do { \ - DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", \ - name, g_test_stats.passed, g_test_stats.total, g_test_stats.failed); \ -} while (0) - -/* - * Phase 1: 底层寄存器测试 - * 通过: 9 项全部打印 OK/SUCCESS/YES/0x00 - * 失败: 任何一项 FAIL/ERROR/MISMATCH - */ -void ch395f_phase1_tests(void); - -/* - * Phase 2: TCP Client 收发 + 关闭重连 ×3 轮 - * 通过: 日志打印 3 轮 "PASS" - * 失败: 任何一轮打印 "FAIL" 或超时 - * PC 端预启动: - * python test/ch395f_socket_test.py tcp_server --port 8081 - */ -void ch395f_phase2_tests(void); - -/* - * Phase 3: UDP Server Echo + PING + 大包 - * 通过: 30/30 回显匹配 - * 失败: 任何回显超时或丢包 - * PC 端: - * python test/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000 - */ -void ch395f_phase3_tests(void); - -/* - * Phase 4: NET 层 TCP Echo(单连接模式,netTask 中运行) - * 验证 Socket 0 单连接生命周期:LISTEN → ESTABLISHED → DISCONNECT → re-LISTEN - * 覆盖载荷:64B、1460B(MSS)、4096B(缓冲满)、20KB(大文件分块) - * PC 端: - * python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080 - */ -void ch395f_phase4_tests(void); - -/* - * Phase 5: NET 层大文件传输(netTask 中运行) - * 验证超过 CH395F 内部缓冲区大小(4KB)的连续数据收发 - * 覆盖载荷:20480B(20KB)、65535B(64KB,recv_len上限) - * PC 端: - * python test/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --size 20480 - */ -void ch395f_phase5_init(void); -void ch395f_phase5_tests(void); - -/* - * Phase 6: DHCP 自动获取 IP - * 通过: DHCP SUCCESS + IP 非零 + 广播宣告 + HELLO 回显 - * 失败: DHCP timeout 或宣告/HELLO 失败 - * 注意: 会改变 IP,需独立运行 - * PC 端:管理员权限 - * python test/ch395f_socket_test.py dhcp_server --dhcp-iface 192.168.1.2 --ip 192.168.1.100 - */ -void ch395f_phase6_tests(void); - -/* - * Phase 7: Select/Poll I/O 多路复用(netTask 中运行) - * 验证 net_select() 和 net_poll_events() 在单 socket 模式下正确工作 - * PC 端: - * python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080 - */ -void ch395f_phase7_tests(void); - -/* - * Phase 8: 边界条件测试(netTask 中运行) - * 覆盖:零长度收发、DMA 边界(4095/4096/4097B)、recv 缓冲区小于数据、 - * 连续快速发送、多轮快速连接-断开循环 - * PC 端: - * python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080 - */ -void ch395f_phase8_init(void); -void ch395f_phase8_tests(void); - -/* - * Phase 9: PHY 链路恢复测试 - * 通过拔插网线验证 PHY 断开检测和链路恢复后 socket 自动重新监听 - * 需物理操作,PC 端: - * python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080 - */ -void ch395f_phase9_tests(void); - -/* - * Phase 10: TCP KeepAlive 超时断开测试 - * 验证 KeepAlive 在空闲超时后触发断开并自动重新监听 - * PC 端:连接后静置(不发数据),等待 KeepAlive 触发 - * python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080 --silent - */ -void ch395f_phase10_tests(void); - -#ifdef __cplusplus -} -#endif - -#endif /* __CH395F_TEST_H */ diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 4f1e718..fa2f90d 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -340,7 +340,7 @@ USE_HAL_DRIVER,STM32F407xx,NDEBUG - ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../App;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs + ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../App;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test @@ -1216,11 +1216,6 @@ 1 ..\Drivers\BSP\CH395F\ch395f.c - - ch395f_test.c - 1 - ..\Drivers\BSP\CH395F\ch395f_test.c - gd5f2gq5ue.c 1 @@ -2006,6 +2001,16 @@ + + Test + + + ch395f_test_task.c + 1 + ..\test\ch395f_test_task.c + + + ::CMSIS diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt index 28ae0ef..a9224ba 100644 --- a/MDK-ARM/build_log.txt +++ b/MDK-ARM/build_log.txt @@ -3,69 +3,69 @@ Rebuild target 'STM32F407-Demo' assembling startup_stm32f407xx.s... compiling crc.c... compiling ringbuf.c... -compiling gpio.c... -compiling stm32f4xx_hal_msp.c... -compiling spi.c... -compiling stm32f4xx_it.c... -compiling stm32f4xx_hal_flash_ramfunc.c... -compiling stm32f4xx_hal_timebase_tim.c... -compiling usart.c... compiling i2c.c... -compiling app_main.c... -compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_msp.c... compiling dma.c... -compiling stm32f4xx_hal_gpio.c... +compiling spi.c... +compiling stm32f4xx_hal_timebase_tim.c... +compiling stm32f4xx_hal_flash_ramfunc.c... +compiling stm32f4xx_it.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling gpio.c... compiling stm32f4xx_hal_flash.c... compiling sys_clock.c... -compiling adc_task.c... +compiling stm32f4xx_hal_gpio.c... +compiling app_main.c... +compiling usart.c... compiling stm32f4xx_hal_rcc_ex.c... +compiling adc_task.c... compiling rs485_task.c... compiling stm32f4xx_hal_rcc.c... +compiling net_task.c... compiling freertos.c... compiling stm32f4xx_hal_tim_ex.c... -compiling net_task.c... compiling main.c... compiling stm32f4xx_hal_tim.c... +compiling stm32f4xx_hal_pwr.c... compiling stm32f4xx_hal_dma_ex.c... compiling stm32f4xx_hal_dma.c... -compiling stm32f4xx_hal_pwr.c... compiling stm32f4xx_hal_pwr_ex.c... -compiling stm32f4xx_hal_i2c_ex.c... -compiling stm32f4xx_hal_exti.c... -compiling system_stm32f4xx.c... -compiling stm32f4xx_hal_cortex.c... -compiling stm32f4xx_hal.c... -compiling rs485.c... compiling lftpd_string.c... -compiling sd2506.c... +compiling stm32f4xx_hal_cortex.c... compiling croutine.c... +compiling stm32f4xx_hal_i2c_ex.c... +compiling stm32f4xx_hal.c... +compiling list.c... +compiling event_groups.c... +compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_exti.c... +compiling queue.c... +compiling sd2506.c... +compiling stream_buffer.c... +compiling rs485.c... compiling tpafe5160.c... +compiling timers.c... +compiling stm32f4xx_hal_spi.c... compiling ch395f.c... compiling gd5f2gq5ue.c... -compiling stm32f4xx_hal_spi.c... -compiling nand_ftl.c... -compiling net_select.c... -compiling ch395f_test.c... -compiling stm32f4xx_hal_uart.c... -compiling event_groups.c... -compiling lftpd_inet.c... -compiling list.c... -compiling lftpd.c... -compiling net_socket.c... -compiling heap_4.c... -compiling stream_buffer.c... -compiling timers.c... -compiling queue.c... -compiling map.c... -compiling stm32f4xx_hal_i2c.c... -compiling journal.c... -compiling port.c... compiling tasks.c... -compiling ff.c... +compiling nand_ftl.c... compiling lftpd_io.c... +compiling stm32f4xx_hal_uart.c... +compiling heap_4.c... +compiling net_select.c... +compiling lftpd_inet.c... +compiling lftpd.c... +compiling map.c... +compiling net_socket.c... +compiling port.c... +compiling journal.c... +compiling stm32f4xx_hal_i2c.c... +compiling ff.c... +compiling ch395f_test_task.c... compiling cmsis_os2.c... linking... -Program Size: Code=59404 RO-data=2172 RW-data=408 ZI-data=66744 +Program Size: Code=63268 RO-data=3840 RW-data=408 ZI-data=58552 FromELF: creating hex file... ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). -Build Time Elapsed: 00:00:17 +Build Time Elapsed: 00:00:18 diff --git a/STM32F407-Demo.ioc b/STM32F407-Demo.ioc index 077e305..bebe763 100644 --- a/STM32F407-Demo.ioc +++ b/STM32F407-Demo.ioc @@ -72,7 +72,7 @@ Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphData FREERTOS.FootprintOK=true FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE,Queues01 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 +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;ch395fTestTask,16,4096,StartCh395fTestTask,As weak,NULL,Dynamic,NULL,NULL FREERTOS.configENABLE_FPU=1 FREERTOS.configTOTAL_HEAP_SIZE=30720 File.Version=6 diff --git a/Src/freertos.c b/Src/freertos.c index 1448d9b..59c0f34 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -34,6 +34,7 @@ #include "ff.h" #include "gd5f2gq5ue.h" #include "lftpd.h" +#include "../test/ch395f_test_task.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -100,6 +101,13 @@ const osThreadAttr_t ftpTask_attributes = { .stack_size = 1024 * 4, .priority = (osPriority_t) osPriorityNormal, }; +/* Definitions for ch395fTestTask */ +osThreadId_t ch395fTestTaskHandle; +const osThreadAttr_t ch395fTestTask_attributes = { + .name = "ch395fTestTask", + .stack_size = 4096 * 4, + .priority = (osPriority_t) osPriorityBelowNormal, +}; /* Definitions for netMsgQueue */ osMessageQueueId_t netMsgQueueHandle; const osMessageQueueAttr_t netMsgQueue_attributes = { @@ -114,6 +122,7 @@ const osMessageQueueAttr_t netMsgQueue_attributes = { void StartDefaultTask(void *argument); void StartNetTask(void *argument); void StartFtpTask(void *argument); +void StartCh395fTestTask(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ @@ -155,7 +164,10 @@ void MX_FREERTOS_Init(void) { netTaskHandle = osThreadNew(StartNetTask, NULL, &netTask_attributes); /* creation of ftpTask */ - ftpTaskHandle = osThreadNew(StartFtpTask, NULL, &ftpTask_attributes); + //ftpTaskHandle = osThreadNew(StartFtpTask, NULL, &ftpTask_attributes); + + /* creation of ch395fTestTask */ + ch395fTestTaskHandle = osThreadNew(StartCh395fTestTask, NULL, &ch395fTestTask_attributes); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ @@ -307,6 +319,24 @@ __weak void StartFtpTask(void *argument) /* USER CODE END StartFtpTask */ } +/* USER CODE BEGIN Header_StartCh395fTestTask */ +/** +* @brief Function implementing the ch395fTestTask thread. +* @param argument: Not used +* @retval None +*/ +/* USER CODE END Header_StartCh395fTestTask */ +__weak void StartCh395fTestTask(void *argument) +{ + /* USER CODE BEGIN StartCh395fTestTask */ + /* Infinite loop */ + for(;;) + { + osDelay(1); + } + /* USER CODE END StartCh395fTestTask */ +} + /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ diff --git a/Src/main.c b/Src/main.c index a0160dd..7e40511 100644 --- a/Src/main.c +++ b/Src/main.c @@ -36,7 +36,6 @@ #include "rs485.h" #include "net_socket.h" #include "net_select.h" -#include "ch395f_test.h" #include "app_main.h" #include "sys_clock.h" /* USER CODE END Includes */ @@ -182,22 +181,7 @@ int main(void) /* 启动第一次转换(CubeMX 已配置 BUSY EXTI 中断) */ tpafe5160_start_conv_irq(); - /* CH395F 驱动测试 — 各阶段独立运行,注释 define 可跳过 */ -#ifdef ENABLE_PHASE1_TESTS - ch395f_phase1_tests(); -#endif -#ifdef ENABLE_PHASE3_TESTS - ch395f_phase3_tests(); -#endif -#ifdef ENABLE_PHASE2_TESTS - ch395f_phase2_tests(); -#endif -#ifdef ENABLE_PHASE5_TESTS - ch395f_phase5_init(); -#endif -#ifdef ENABLE_PHASE6_TESTS - ch395f_phase6_tests(); -#endif + /* CH395F 测试已全部移至 ch395fTestTask */ /* USER CODE END 2 */ /* Init scheduler */ diff --git a/docs/CH395F_Test_Guide.md b/docs/CH395F_Test_Guide.md index 1603198..1866671 100644 --- a/docs/CH395F_Test_Guide.md +++ b/docs/CH395F_Test_Guide.md @@ -63,7 +63,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) ### 启用测试 -编辑 `Drivers/BSP/CH395F/ch395f_test.h`,取消注释对应 `ENABLE_PHASEx_TESTS` 宏。 +编辑 `test/ch395f_test.h`,取消注释对应 `ENABLE_PHASEx_TESTS` 宏。 --- @@ -73,7 +73,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) |---------|-----------| | **类型** | 独立运行 | | **耗时** | ~1 秒 | -| **入口** | `ch395f_phase1_tests()` 在 `main.c USER CODE BEGIN 2` | +| **入口** | `ch395fTestTask` 启动后自动运行 | | **出口** | 串口 "Phase 1 Tests Complete" 且 0 失败 | ### TC-NET-101: SPI 命令路径完整性 @@ -85,23 +85,24 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) | **类型** | 功能测试 | | **标题** | 验证所有 SPI 命令路径返回预期值 | | **前置条件** | 1. CH395F 已上电,SPI2 已初始化
2. 未启用其他阶段 | -| **测试步骤** | 1. `ch395f_check_exist()` → 验证回显测试字节按位取反
2. `ch395f_get_version()` → 验证版本 != 0xFF 且 != 0x00
3. 设 IP 192.168.1.100,回读验证
4. 设网关 192.168.1.1,回读验证
5. 设掩码 255.255.255.0,回读验证
6. `ch395f_get_glob_int_status()` → 验证返回 0x00
7. 打开→关闭 Socket 0 → 验证 `CMD_STATUS == CH395F_ERR_SUCCESS`
8. 设置 ARP 参数
9. 设置 TTL | -| **预期结果** | 全部 9 项打印 `OK`/`SUCCESS`/`0x00` | -| **通过标准** | 9/9 通过,0 失败 | -| **覆盖原则** | DP-02 | -### TC-NET-102: 全局中断状态一致性 +**测试步骤与判定标准**: -| 字段 | 值 | -|------|-----| -| **ID** | TC-NET-102 | -| **优先级** | P1 | -| **类型** | 功能测试 | -| **标题** | 无事件时 `GET_GLOB_INT_STATUS_ALL` 返回 0 | -| **前置条件** | TC-NET-101 通过,芯片空闲 | -| **测试步骤** | 1. 读 `ch395f_get_glob_int_status_all()`
2. 读 `ch395f_get_glob_int_status()`(1 字节版) | -| **预期结果** | 两者均为 0 | -| **通过标准** | 两次读取均为 0 | +| 步 | 操作 | 成功标准 | 失败标准 | +|----|------|----------|----------| +| 1 | `ch395f_check_exist()`
SPI写: CMD=0x06, DATA=0x57
回读: ~0x57 = 0xA8 | 返回 `0x00`(`CH395F_ERR_SUCCESS`)
= 回读字节 `0xA8` 匹配 `~0x57` | 返回 `0xFF`(`CH395F_STATUS_NOT_DETECTED`)
= 回读字节 ≠ `0xA8`,回显取反不匹配 | +| 2 | `ch395f_get_version()` | 版本值 == `0x4A`(CH395F 芯片版本) | 版本值 ≠ `0x4A`(芯片不匹配或未响应) | +| 3 | `ch395f_set_ip_addr(192.168.1.100)` → `ch395f_get_ip_inf()` 回读 IP | `ip_info[0..3] == 192.168.1.100` | 任意字节不匹配(写入/回读不一致) | +| 4 | `ch395f_set_gwip_addr(192.168.1.1)` → `ch395f_get_ip_inf()` 回读 GW | `ip_info[4..7] == 192.168.1.1` | 任意字节不匹配 | +| 5 | `ch395f_set_mask_addr(255.255.255.0)` → `ch395f_get_ip_inf()` 回读 MASK | `ip_info[8..11] == 255.255.255.0` | 任意字节不匹配 | +| 6 | `ch395f_get_glob_int_status_all()`(16 位) | 返回值 == `0x0000`(无待处理中断) | 返回值 ≠ `0x0000`(有残留中断未清除) | +| 7 | `ch395f_open_socket(0)` → `ch395f_close_socket(0)` → `ch395f_get_cmd_status()` | `CMD_STATUS == 0x00`(`CH395F_ERR_SUCCESS`) | `CMD_STATUS` 非 `0x00`(Socket 操作失败) | +| 8 | `ch395f_set_arp(10, 5)` → `ch395f_get_cmd_status()` | `CMD_STATUS == 0x00` | `CMD_STATUS` 非 `0x00` | +| 9 | `ch395f_set_ttl(0, 64)` → `ch395f_get_cmd_status()` | `CMD_STATUS == 0x00` | `CMD_STATUS` 非 `0x00` | + +**通过标准**:9/9 通过,0 失败。任一失败 → 阶段标记 FAIL,后续阶段不执行 + +**覆盖原则**:DP-02 --- @@ -111,7 +112,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) |---------|-----------| | **类型** | 需 PC 配合 | | **耗时** | ~60 秒 | -| **入口** | `ch395f_phase2_tests()` 在 `main.c USER CODE BEGIN 2` | +| **入口** | `ch395fTestTask` 启动后自动运行 | | **出口** | 串口 "Phase 2 Tests Complete" 且 0 失败 | ### TC-NET-201: TCP Client 连接与数据交换 @@ -123,8 +124,18 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) | **类型** | 功能测试 | | **标题** | CH395F TCP Client → PC TCP Server,64 字节收发 | | **前置条件** | PC: `python ch395f_socket_test.py tcp_server --port 8081` | -| **测试步骤** | 1. CH395F 开 Socket 0,配置 TCP
2. 连接 PC:8081
3. 发 64 字节 → 收回显 → 比较 | -| **通过标准** | 64 字节收发一致,无超时 | + +**测试步骤与判定标准**: + +| 步 | 操作 | 成功标准 | 失败标准 | +|----|------|----------|----------| +| 1 | CH395F open Socket 0 → 配置 TCP 协议 | `ch395f_open_socket(0)` 返回 `CH395F_ERR_SUCCESS`(`0x00`) | 返回值非 `0x00`(Socket 硬件异常或 SPI 通信失败) | +| 2 | 设置目标 IP 为 PC 地址(`192.168.1.2`),端口 `8081` → 发起 TCP 连接 | `ch395f_tcp_connect(0)` 返回 `CH395F_ERR_SUCCESS`(`0x00`) | 返回值非 `0x00`(连接超时、目标不可达或协议配置错误) | +| 3 | 发送 64 字节递增模式数据(`0x00, 0x01, ..., 0x3F`) | `net_send()` 返回值 == `64`(全部发出) | 返回值 < `0`(发送失败)或返回值 ≠ `64`(未完全发出) | +| 4 | 阻塞等待接收回显,超时 5 秒 | `net_recv()` 返回值 == `64`(收到全部回显) | 返回值 < `0`(超时/断开)或返回值 ≠ `64`(数据不完整) | +| 5 | 逐字节比较收发数据 | 所有 64 字节完全一致 | 任意字节不一致(回显错误或数据错位) | + +**通过标准**:5/5 通过,0 失败。任一失败 → 阶段标记 FAIL,后续阶段不执行 ### TC-NET-202: TCP Client 关闭重连 ×3 轮 @@ -159,7 +170,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) |---------|-----------| | **类型** | 需 PC 配合 | | **耗时** | ~30 秒 | -| **入口** | `ch395f_phase3_tests()` 在 `main.c USER CODE BEGIN 2` | +| **入口** | `ch395fTestTask` 启动后自动运行 | | **出口** | 串口 "Phase 3 Tests Complete" 且 0 失败 | ### TC-NET-301: UDP 回显 — HELLO + PING + 大包 ×30 @@ -196,7 +207,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) |---------|-----------| | **类型** | PC 配合,netTask 运行 | | **耗时** | 持续运行 | -| **入口** | `ENABLE_PHASE4_TESTS`,netTask 中循环调用 `ch395f_phase4_tests()` | +| **入口** | `ENABLE_PHASE4_TESTS`,`ch395fTestTask` TCP 回显循环中运行 | | **出口** | 用户终止 | ### TC-NET-401: Socket 0 单连接生命周期 @@ -310,7 +321,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC) ### 实现说明 -`ch395f_phase5_tests()` 状态机: +`phase5_run()` 状态机(`ch395fTestTask` 内): ``` IDLE → (连接建立) → SENDING(分块发) → RECVING(分块收+验证) → DONE → IDLE diff --git a/docs/测试开发标准流程.md b/docs/测试开发标准流程.md new file mode 100644 index 0000000..91ae729 --- /dev/null +++ b/docs/测试开发标准流程.md @@ -0,0 +1,244 @@ +# 嵌入式测试开发标准流程(基于 STM32F4 + CH395F 经验) + +> 适用范围:STM32 裸机/FreeRTOS 项目,Keil MDK-ARM v5 (ARMCC),串口日志输出测试结果。 + +--- + +## 1. 测试规划(文档先行) + +在 `docs/` 下创建测试规范文档,定义以下结构: + +### 1.1 阶段概览表 + +| 阶段 ID | 类型 | 耗时 | 入口 | 出口 | +|---------|------|------|------|------| +| TM-PHY-01 | 独立运行 | ~1s | 宏启用 | 串口汇总 | + +### 1.2 每个测试用例 + +| 字段 | 说明 | +|------|------| +| **ID** | `TC-NET-NNN`,唯一编号 | +| **优先级** | P0(核心)/ P1(重要) | +| **类型** | 功能测试 / 边界测试 / 负向测试 / 压力测试 / 恢复测试 | +| **标题** | 一句话描述 | +| **前置条件** | 硬件状态、PC 端命令、依赖的其他用例 | +| **测试步骤** | 编号操作序列 | +| **成功标准** | 精确到"返回值 == 0x00"、"延迟 >= 60000ms" | +| **失败标准** | 每种失败对应的现象 | +| **覆盖原则** | 对应哪个设计原则 | + +关键原则:**先写判定标准,再写测试代码**,避免"测了但不知道算不算过"。 + +--- + +## 2. 测试框架搭建 + +### 2.1 目录结构 + +``` +test/ + ch395f_test.h # 宏定义 + TEST_CHECK/TEST_REPORT + test_stats_t + ch395f_test_task.h # 仅导出 StartCh395fTestTask + ch395f_test_task.c # 全 Phase 实现 +``` + +### 2.2 模板文件 + +#### `ch395f_test.h` — 宏+类型 + +```c +// 阶段使能开关(取消注释即启用) +#define ENABLE_PHASE1_TESTS + +// 测试统计 +typedef struct { + uint16_t total; + uint16_t passed; + uint16_t failed; +} test_stats_t; + +extern test_stats_t g_test_stats; + +#define TEST_CHECK(cond, fmt, ...) do { \ + g_test_stats.total++; \ + if (cond) { \ + g_test_stats.passed++; \ + DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); \ + } else { \ + g_test_stats.failed++; \ + DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); \ + } \ +} while (0) + +#define TEST_REPORT(name) do { \ + DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", \ + name, g_test_stats.passed, g_test_stats.total, g_test_stats.failed); \ +} while (0) +``` + +#### `ch395f_test_task.c` — 每个 Phase 的模板 + +```c +#ifdef ENABLE_PHASEX_TESTS +/* + * Phase X — 功能说明 + * + * 测试目的:(概括) + * 前置条件:(硬件/软件/PC 端) + * 通过:(整体判定条件) + * 失败:(整体判定条件) + */ +static void phaseX_run(void) { + DBG_INFO("=== CH395F Phase X Tests ==="); + + /* ---- TC-NET-NNN: 用例标题 ---- */ + { + // 原理说明 + // 通过:精确条件 + // 失败:精确条件 + ret = some_api(); + DBG_INFO(" expect: ..."); + DBG_INFO(" actual: ..."); + TEST_CHECK(ret == EXPECTED, "description"); + } + + TEST_REPORT("Phase X"); +} +#endif +``` + +--- + +## 3. 编码规范 + +### 3.1 DBG_INFO 三行输出 + +每个判定点输出三行,一眼看出"期望什么、拿了什么、过没过": + +``` + [P1-01] ch395f_check_exist() + expect: 0x00 (~0x57 = 0xA8) + actual: 0x00 + [PASS] P1-01 check_exist = 0x00 (expect 0x00) +``` + +### 3.2 硬编码值加注释 + +```c +// 好: +ch395f_status_t ret = ch395f_check_exist(); +// SPI 写入 0x06 (CMD_CHECK_EXIST) + 0x57 (测试字节) +// 成功:回复 == ~0x57 == 0xA8(CH395F_ERR_SUCCESS = 0x00) +// 失败:回复 != 0xA8(CH395F_STATUS_NOT_DETECTED = 0xFF) + +// 不好: +ch395f_status_t ret = ch395f_check_exist(); +TEST_CHECK(ret == 0, "check_exist OK"); +``` + +### 3.3 无中文字符串 + +ARMCC v5 不识别 UTF-8 多字节字符。`DBG_INFO`/`DBG_ERROR` 中只写 ASCII。注释可以写中文。 + +### 3.4 栈安全 + +大缓冲区(如 64KB)用 `static` 全局,不放任务栈: + +```c +#define TEST_BUF_SIZE 65536 +static uint8_t s_rx_buf[TEST_BUF_SIZE]; +``` + +### 3.5 条件编译消除未使用变量警告 + +```c +static uint8_t s_rx_buf[TEST_BUF_SIZE]; +#if defined(ENABLE_PHASE5_TESTS) || defined(ENABLE_PHASE8_TESTS) +static uint8_t s_tx_buf[TEST_BUF_SIZE]; +#endif +``` + +--- + +## 4. 统一入口 + +### 4.1 所有测试在 FreeRTOS 任务中串行执行 + +``` +ch395fTestTask: + 1. 等待 g_net_ready(netTask 初始化完成) + 2. 阻塞执行 Phase 1/2/3/6/9(内部循环,无需外部连接) + 3. 创建 TCP 监听 Socket(端口 8080,单连接模式) + 4. 事件驱动循环 Phase 4/5/7/8/10(等待 PC 连接) +``` + +### 4.2 不要在 main.c 中直接调用测试 + +main.c 只做硬件初始化和启动 OS,不包含任何测试逻辑。 + +### 4.3 测试文件的手动注册 + +新测试文件需: +1. 添加到 `test/` 目录 +2. 修改 `MDK-ARM/STM32F407-Demo.uvprojx` 添加文件引用和 IncludePath + +--- + +## 5. 文档与代码同步 + +- 文档中的入口函数名、宏名、流程描述必须与代码一致 +- 代码变更后立即更新文档,否则文档两天内就会失效 +- 测试用例的通过/失败标准在**文档和代码注释中都写清楚** +- 文档使用标准 Markdown 表格,避免复杂嵌套导致渲染异常 + +--- + +## 6. 编译验证 + +### 6.1 底线 + +**`0 Error(s), 0 Warning(s)`**,不可妥协。 + +### 6.2 常见警告处理 + +| 警告 | 原因 | 解决 | +|------|------|------| +| `#177-D: variable was declared but never referenced` | 条件编译导致 | 加 `#ifdef` 包裹变量声明 | +| `#870-D: invalid multibyte character sequence` | DBG_INFO 中有中文 | 改为纯 ASCII | +| 隐式类型转换 | 参数类型不匹配 | 加显式 `(uint8_t)` 等 cast | + +### 6.3 构建命令 + +```bat +MDK-ARM\build.bat +:: 或项目根目录执行 @build +``` + +退出码:0 = 成功 | 1 = 有警告(不通过)| 2+ = 错误 + +--- + +## 7. 迭代节奏 + +``` +文档(规划 + 判定标准) + → 代码(添加 Phase 实现) + → @build(0 Error(s), 0 Warning(s)) + → 烧录验证(串口观察 PASS/FAIL) + → 根据实际结果修正判定标准 + → 更新文档(保持同步) + → 下一阶段 +``` + +--- + +## 附录:本项目的测试文件清单 + +| 文件 | 作用 | +|------|------| +| `test/ch395f_test.h` | 测试宏、类型定义、Phase 使能开关 | +| `test/ch395f_test_task.h` | 导出 `StartCh395fTestTask` | +| `test/ch395f_test_task.c` | 全 10 个 Phase 实现 | +| `docs/CH395F_Test_Guide.md` | 测试规范、用例表格、通过/失败标准 | +| `docs/CH395F_Trap_Records.md` | 已知硬件/软件陷阱及修复 | diff --git a/test/ch395f_test.h b/test/ch395f_test.h new file mode 100644 index 0000000..c4a4ec8 --- /dev/null +++ b/test/ch395f_test.h @@ -0,0 +1,81 @@ +/* + * 模块名称:CH395F Driver Test Suite + * 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例, + * 覆盖 SPI 命令路径、协议栈配置、Socket 状态机、大文件传输、 + * 边界条件、KeepAlive、PHY 链路恢复等,基于单连接 socket 模式 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-19 + * 修改记录: + * 2026-07-22 全面重构为单连接模式,增加大文件传输、边界条件等测试 + * + * === 测试阶段一览 === + * Phase | 类型 | 描述 + * ---------|--------------|----------------------------------------------- + * Phase 1 | 独立运行 | 寄存器读写 + SPI 命令路径验证 + * Phase 2 | PC 配合 | TCP Client 收发 + 关闭重连 + * Phase 3 | PC 配合 | UDP Server Echo(HELLO + PING + 大包) + * Phase 4 | testTask | NET 层 TCP Echo(单连接模式,基础收发验证) + * Phase 5 | testTask | NET 层大文件传输(20KB+ 分块收发) + * Phase 6 | PC 配合 | DHCP 自动获取 IP + * Phase 7 | testTask | Select/Poll I/O 多路复用 + * Phase 8 | testTask | 边界条件测试(零长度、DMA 边界、缓冲溢出) + * Phase 9 | 需拔插网线 | PHY 链路恢复 + * Phase 10 | testTask | KeepAlive 超时断开测试 + */ + +#ifndef __CH395F_TEST_H +#define __CH395F_TEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * 阶段使能开关(取消注释即启用对应阶段) + * 注意:一次仅启用一个阶段(Phase 4/5/7/8 共享 testTask 循环,可同时启用) + * Phase 6 (DHCP) 会改变 IP,必须独立运行 + */ +#define ENABLE_PHASE1_TESTS /* 寄存器读写 + SPI 命令路径 */ +//#define ENABLE_PHASE2_TESTS /* TCP Client 收发 + 关闭重连 */ +//#define ENABLE_PHASE3_TESTS /* UDP Server Echo */ +//#define ENABLE_PHASE4_TESTS /* NET 层 TCP Echo(单连接基础收发) */ +//#define ENABLE_PHASE5_TESTS /* NET 层大文件传输(20KB+ 分块) */ +//#define ENABLE_PHASE6_TESTS /* DHCP 自动获取 IP(会改 IP!独立运行) */ +//#define ENABLE_PHASE7_TESTS /* Select/Poll I/O 多路复用 */ +//#define ENABLE_PHASE8_TESTS /* 边界条件测试 */ +//#define ENABLE_PHASE9_TESTS /* PHY 链路恢复测试 */ +//#define ENABLE_PHASE10_TESTS /* KeepAlive 超时断开测试 */ + +/* 测试统计 */ +typedef struct { + uint16_t total; + uint16_t passed; + uint16_t failed; +} test_stats_t; + +extern test_stats_t g_test_stats; + +#define TEST_CHECK(cond, fmt, ...) do { \ + g_test_stats.total++; \ + if (cond) { \ + g_test_stats.passed++; \ + DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); \ + } else { \ + g_test_stats.failed++; \ + DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); \ + } \ +} while (0) + +#define TEST_REPORT(name) do { \ + DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", \ + name, g_test_stats.passed, g_test_stats.total, g_test_stats.failed); \ +} while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* __CH395F_TEST_H */ diff --git a/test/ch395f_test_task.c b/test/ch395f_test_task.c new file mode 100644 index 0000000..0696b14 --- /dev/null +++ b/test/ch395f_test_task.c @@ -0,0 +1,1154 @@ +/****************************************************************************** + * 模块名称:CH395F Network Test Task + * 模块功能:CH395F 全阶段测试入口,统一在独立 FreeRTOS 任务中运行。 + * 测试分两组: + * Phase 1/2/3/6/9 — 阻塞式,TCP 监听前依次执行(各自有内部循环/等待) + * Phase 4/5/7/8/10 — 事件驱动,TCP 监听循环中运行(需 PC 客户端连接) + * 所有测试结果通过 TEST_CHECK/TEST_REPORT 宏输出到串口。 + * 作者:王建锋 + * 创建日期:2026-07-22 + ******************************************************************************/ + +#include +#include +#include "ch395f_test_task.h" +#include "cmsis_os.h" +#include "net_socket.h" +#include "ch395f_test.h" +#include "ch395f.h" + +#define DBG_TAG "[TEST_TASK]" +#include "dbg_log.h" + +/* netTask 初始化完成标志(由 net_task.c 在 net_init 成功后置 1) */ +extern volatile uint8_t g_net_ready; + +/* + * 全局测试统计数据,由 TEST_CHECK/TEST_REPORT 宏自动管理。 + * 每次 TEST_CHECK 自增 total 并根据条件增 passed/failed; + * TEST_REPORT 汇总输出,不会自动清零 —— 每个 Phase 内自行 memset 重置。 + */ +test_stats_t g_test_stats; + +/* + * 收发缓冲区 —— 全局静态,不在任务栈上。 + * s_tx_buf 仅在 Phase 5(大文件传输)和 Phase 8(边界条件)中使用, + * 其他 Phase 不需要发送固定模式数据,故条件编译节省 RAM。 + */ +#define TEST_BUF_SIZE 65536 +#if defined(ENABLE_PHASE4_TESTS) || defined(ENABLE_PHASE5_TESTS) || \ + defined(ENABLE_PHASE7_TESTS) || defined(ENABLE_PHASE8_TESTS) || \ + defined(ENABLE_PHASE10_TESTS) +static uint8_t s_rx_buf[TEST_BUF_SIZE]; +#endif +#if defined(ENABLE_PHASE5_TESTS) || defined(ENABLE_PHASE8_TESTS) +static uint8_t s_tx_buf[TEST_BUF_SIZE]; +#endif + +/* =================================================================== + * Phase 1 — 寄存器与 SPI 命令路径验证 + * + * 测试目的:确认 CH395F 与 MCU 之间的 SPI 通信链路正常,所有底层命令 + * 能够正确发送并得到预期回复。这是其他所有 Phase 的前提条件。 + * + * 共 9 项测试(P1-01 ~ P1-09),逐项执行,汇总报告。 + * =================================================================== */ + +#ifdef ENABLE_PHASE1_TESTS +static void phase1_run(void) { + uint8_t ip_info[20]; + + DBG_INFO("=== CH395F Phase 1 Tests ==="); + DBG_INFO("--- 9 test items (P1-01 ~ P1-09) ---"); + + /* ---- P1-01: ch395f_check_exist() ---- + * 原理:SPI 写入命令 0x06 (CMD_CHECK_EXIST) 后跟测试字节 0x57。 + * CH395F 硬件将测试字节按位取反后返回,期望收到 ~0x57 = 0xA8。 + * 通过:函数返回 CH395F_ERR_SUCCESS(0x00)= SPI 回显取反匹配。 + * 失败:返回 CH395F_STATUS_NOT_DETECTED(0xFF)= SPI 通信异常或芯片未响应。 + */ + { + ch395f_status_t ret = ch395f_check_exist(); + DBG_INFO(" [P1-01] ch395f_check_exist()"); + DBG_INFO(" SPI write: CMD=0x06, DATA=0x57"); + DBG_INFO(" expect: ~0x57 = 0xA8"); + DBG_INFO(" actual: 0x%02X", (uint8_t)ret); + TEST_CHECK(ret == CH395F_ERR_SUCCESS, + "P1-01 check_exist = 0x%02X (expect 0x00=~0x57=0xA8 match)", (uint8_t)ret); + } + + /* ---- P1-02: ch395f_get_version() ---- + * 原理:SPI 写入命令 0x27 (CMD_GET_IC_VER),读取芯片版本字节。 + * 通过:版本值 == 0x4A(CH395F 硬件版本)。 + * 失败:版本值 ≠ 0x4A(芯片型号不匹配、未正确初始化或损坏)。 + * 注意:不同批次的 CH395F 版本号可能不同,若更换芯片需更新此判断值。 + */ + { + uint8_t ver = (uint8_t)ch395f_get_version(); + DBG_INFO(" [P1-02] ch395f_get_version()"); + DBG_INFO(" expect: 0x4A"); + DBG_INFO(" actual: 0x%02X", ver); + TEST_CHECK(ver == 0x4A, + "P1-02 version = 0x%02X (expect 0x4A)", ver); + } + + /* ---- P1-03: 设置 IP → 回读验证 ---- + * 原理:ch395f_set_ip_addr() 将 IP 写入 CH395F 内部寄存器, + * 然后通过 ch395f_get_ip_inf() 回读完整网络信息(IP+GW+MASK)。 + * 前 4 字节为 IP 地址。 + * 通过:回读 IP[0..3] == 192.168.1.100(与写入值一致)。 + * 失败:任意字节不匹配(SPI 写入/回读路径异常或寄存器未更新)。 + */ + { + uint8_t set_ip[4] = {192, 168, 1, 100}; + ch395f_set_ip_addr(set_ip); + memset(ip_info, 0, sizeof(ip_info)); + ch395f_get_ip_inf(ip_info); + int match = (ip_info[0] == 192 && ip_info[1] == 168 && + ip_info[2] == 1 && ip_info[3] == 100); + DBG_INFO(" [P1-03] ch395f_set_ip_addr(192.168.1.100)"); + DBG_INFO(" expect: IP[0..3] = 192.168.1.100"); + DBG_INFO(" actual: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); + TEST_CHECK(match, "P1-03 IP = %d.%d.%d.%d (expect 192.168.1.100)", + ip_info[0], ip_info[1], ip_info[2], ip_info[3]); + } + + /* ---- P1-04: 设置网关 → 回读验证 ---- + * 原理:同 P1-03,通过 ch395f_set_gwip_addr() 写入,ch395f_get_ip_inf() + * 回读的第 5-8 字节为网关地址。 + * 通过:回读 GW[0..3] == 192.168.1.1。 + * 失败:任意字节不匹配。 + */ + { + uint8_t set_gw[4] = {192, 168, 1, 1}; + ch395f_set_gwip_addr(set_gw); + memset(ip_info, 0, sizeof(ip_info)); + ch395f_get_ip_inf(ip_info); + int match = (ip_info[4] == 192 && ip_info[5] == 168 && + ip_info[6] == 1 && ip_info[7] == 1); + DBG_INFO(" [P1-04] ch395f_set_gwip_addr(192.168.1.1)"); + DBG_INFO(" expect: GW[0..3] = 192.168.1.1"); + DBG_INFO(" actual: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); + TEST_CHECK(match, "P1-04 GW = %d.%d.%d.%d (expect 192.168.1.1)", + ip_info[4], ip_info[5], ip_info[6], ip_info[7]); + } + + /* ---- P1-05: 设置掩码 → 回读验证 ---- + * 原理:同 P1-03,ch395f_set_mask_addr() 写入,ch395f_get_ip_inf() + * 回读的第 9-12 字节为子网掩码。 + * 通过:回读 MASK[0..3] == 255.255.255.0。 + * 失败:任意字节不匹配。 + */ + { + uint8_t set_mask[4] = {255, 255, 255, 0}; + ch395f_set_mask_addr(set_mask); + memset(ip_info, 0, sizeof(ip_info)); + ch395f_get_ip_inf(ip_info); + int match = (ip_info[8] == 255 && ip_info[9] == 255 && + ip_info[10] == 255 && ip_info[11] == 0); + DBG_INFO(" [P1-05] ch395f_set_mask_addr(255.255.255.0)"); + DBG_INFO(" expect: MASK[0..3] = 255.255.255.0"); + DBG_INFO(" actual: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]); + TEST_CHECK(match, "P1-05 MASK = %d.%d.%d.%d (expect 255.255.255.0)", + ip_info[8], ip_info[9], ip_info[10], ip_info[11]); + } + + /* ---- P1-06: ch395f_get_glob_int_status_all()(16 位版) ---- + * 原理:读取全局中断状态寄存器(2 字节版本,含所有 Socket 中断位)。 + * 在无任何网络事件的空闲状态下,应为 0x0000。 + * 通过:返回 0x0000。 + * 失败:返回非 0x0000(有残留中断,如前序操作异常触发但未清除)。 + * 注意:此寄存器为读后清零,故先读 16 位版再读 1 字节版。 + */ + { + uint16_t gint_all = ch395f_get_glob_int_status_all(); + DBG_INFO(" [P1-06] ch395f_get_glob_int_status_all()"); + DBG_INFO(" expect: 0x0000"); + DBG_INFO(" actual: 0x%04X", gint_all); + TEST_CHECK(gint_all == 0x0000, + "P1-06 glob_int_status_all = 0x%04X (expect 0x0000)", gint_all); + } + + /* ---- P1-07: 打开→关闭 Socket 0 → 验证 CMD_STATUS ---- + * 原理:连续执行 open_socket(0) 和 close_socket(0),然后读取 + * 命令状态寄存器,确认 Socket 状态机正常流转。 + * 通过:CMD_STATUS == 0x00(CH395F_ERR_SUCCESS),表示两次命令均成功执行。 + * 失败:CMD_STATUS 非 0x00(Socket 硬件状态异常或命令序列错误)。 + */ + { + uint8_t open_ret = ch395f_open_socket(0); + uint8_t close_ret = ch395f_close_socket(0); + uint8_t cmd_st = ch395f_get_cmd_status(); + DBG_INFO(" [P1-07] ch395f_open_socket(0) -> ch395f_close_socket(0)"); + DBG_INFO(" expect: CMD_STATUS == 0x00 (CH395F_ERR_SUCCESS)"); + DBG_INFO(" actual: open=%d, close=%d, CMD_STATUS=0x%02X", + open_ret, close_ret, cmd_st); + TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, + "P1-07 open=%d close=%d cmd=0x%02X (expect 0x00)", + open_ret, close_ret, cmd_st); + } + + /* ---- P1-08: 设置 ARP 参数 ---- + * 原理:ch395f_set_arp(period, retry) 配置 ARP 请求周期和重试次数。 + * period = 10(×100ms = 1s)、retry = 5(最多 5 次重试)。 + * 通过:CMD_STATUS == 0x00(参数写入成功)。 + * 失败:CMD_STATUS 非 0x00(命令未被执行 —— 参数无效或 SPI 异常)。 + */ + { + ch395f_set_arp(10, 5); + uint8_t cmd_st = ch395f_get_cmd_status(); + DBG_INFO(" [P1-08] ch395f_set_arp(period=10, retry=5)"); + DBG_INFO(" expect: CMD_STATUS == 0x00"); + DBG_INFO(" actual: CMD_STATUS = 0x%02X", cmd_st); + TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, + "P1-08 set_arp CMD_STATUS = 0x%02X (expect 0x00)", cmd_st); + } + + /* ---- P1-09: 设置 TTL ---- + * 原理:ch395f_set_ttl(sock, ttl) 设置指定 Socket 的 IP 报 TTL 值。 + * sock = 0(Socket 0)、ttl = 64(默认值)。 + * 通过:CMD_STATUS == 0x00。 + * 失败:CMD_STATUS 非 0x00。 + */ + { + ch395f_set_ttl(0, 64); + uint8_t cmd_st = ch395f_get_cmd_status(); + DBG_INFO(" [P1-09] ch395f_set_ttl(sock=0, ttl=64)"); + DBG_INFO(" expect: CMD_STATUS == 0x00"); + DBG_INFO(" actual: CMD_STATUS = 0x%02X", cmd_st); + TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS, + "P1-09 set_ttl CMD_STATUS = 0x%02X (expect 0x00)", cmd_st); + } + + TEST_REPORT("Phase 1"); +} +#endif + +/* =================================================================== + * Phase 2 — TCP Client(CH395F 作为客户端主动连接 PC) + * + * 测试目的:验证 CH395F 作为 TCP Client 发起连接、发送数据、接收回显、 + * 关闭连接、重连的完整生命周期。重复 3 轮以验证连接稳定性。 + * + * 前置条件:PC 上运行 TCP Server(python ch395f_socket_test.py tcp_server --port 8081) + * 通过:3/3 轮回显成功。 + * 失败:任意轮连接失败、回显超时或数据不一致。 + * =================================================================== */ + +#ifdef ENABLE_PHASE2_TESTS +#ifndef PHASE2_REMOTE_IP +#define PHASE2_REMOTE_IP "192.168.1.2" +#endif +#ifndef PHASE2_REMOTE_PORT +#define PHASE2_REMOTE_PORT 8081 +#endif +#ifndef PHASE2_LOCAL_PORT +#define PHASE2_LOCAL_PORT 50000 +#endif +#ifndef PHASE2_LOOP_COUNT +#define PHASE2_LOOP_COUNT 3 +#endif +#ifndef PHASE2_TIMEOUT_MS +#define PHASE2_TIMEOUT_MS 10000 +#endif + +static void phase2_run(void) { + uint8_t sock = 0; + uint8_t rx_buf[300] = {0}; + int i; + + DBG_INFO("=== CH395F Phase 2 Tests (TCP Client) ==="); + + /* 解析远程 IP 字符串为字节数组 */ + uint8_t ip_arr[4]; + { + uint32_t a[4]; + sscanf(PHASE2_REMOTE_IP, "%lu.%lu.%lu.%lu", &a[0], &a[1], &a[2], &a[3]); + ip_arr[0] = (uint8_t)a[0]; + ip_arr[1] = (uint8_t)a[1]; + ip_arr[2] = (uint8_t)a[2]; + ip_arr[3] = (uint8_t)a[3]; + } + DBG_INFO("Target: %s:%d", PHASE2_REMOTE_IP, PHASE2_REMOTE_PORT); + + /* + * 配置 Socket 0 为 TCP Client 模式: + * - 协议类型 TCP + * - 目标 IP/端口:PC Server + * - 本地端口:循环内每次+1,避免 TIME_WAIT 冲突 + */ + sock = 0; + ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP); + ch395f_set_des_ip(sock, ip_arr); + ch395f_set_des_port(sock, PHASE2_REMOTE_PORT); + + for (i = 0; i < PHASE2_LOOP_COUNT; i++) { + DBG_INFO("--- Iteration %d/%d ---", i + 1, PHASE2_LOOP_COUNT); + + /* + * 每轮使用不同本地端口(防止前一轮 TIME_WAIT 未完全释放导致绑定失败)。 + * 先 OPEN() 创建 Socket,再 CONNECT() 发 SYN。 + * OPEN() 和 CONNECT() 都是异步命令,需轮询中断状态确认连接完成。 + */ + ch395f_set_sour_port(sock, PHASE2_LOCAL_PORT + i); + + if (ch395f_open_socket(sock) != CH395F_ERR_SUCCESS) { TEST_CHECK(0, "open socket"); continue; } + if (ch395f_tcp_connect(sock) != CH395F_ERR_SUCCESS) { TEST_CHECK(0, "tcp connect"); ch395f_close_socket(sock); continue; } + + /* 等待 CONNECT 中断(最多 PHASE2_TIMEOUT_MS) */ + uint32_t tick = HAL_GetTick(); + uint8_t connected = 0; + while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) { + uint16_t int_st = ch395f_get_glob_int_status_all(); + if (int_st & (1U << (sock + 4))) { + uint8_t si = ch395f_get_sock_int_status(sock); + if (si & CH395F_SINT_STAT_CONNECT) { connected = 1; break; } + } + HAL_Delay(10); + } + if (!connected) { TEST_CHECK(0, "connect timeout"); ch395f_close_socket(sock); continue; } + + HAL_Delay(100); + + /* + * 发送 64 字节测试数据(前部为可读字符串,后部填充数字字符)。 + * 然后等待 PC Server 回显(将收到的数据原样发回), + * 收到后立即把回显数据再次发送回去(实现双向验证)。 + */ + { + uint8_t test_buf[64] = {0}; + const char *msg = "HelloFromCH395F_TCP_Client! This is a 64-byte test message."; + int p, mlen = (int)strlen(msg); + memcpy(test_buf, msg, mlen); + for (p = mlen; p < 64; p++) test_buf[p] = (uint8_t)('0' + (p - mlen) % 10); + ch395f_write_send_buf(sock, test_buf, 64); + } + + /* 等待回显(最多 10 秒),收到后原样发回作为二次验证 */ + int got_echo = 0; + uint32_t wait = HAL_GetTick(); + while (HAL_GetTick() - wait < 10000) { + uint16_t rlen = ch395f_get_recv_len(sock); + if (rlen > 0) { + uint16_t read_len = (rlen > sizeof(rx_buf)) ? (uint16_t)sizeof(rx_buf) : rlen; + memset(rx_buf, 0, sizeof(rx_buf)); + ch395f_read_recv_buf(sock, rx_buf, read_len); + ch395f_write_send_buf(sock, rx_buf, read_len); + got_echo = 1; + break; + } + uint16_t int_st = ch395f_get_glob_int_status_all(); + if (int_st & (1U << (sock + 4))) { + uint8_t si = ch395f_get_sock_int_status(sock); + if (si & CH395F_SINT_STAT_DISCONNECT) break; + if (si & CH395F_SINT_STAT_SOCK_TIMEOUT) break; + } + HAL_Delay(50); + } + TEST_CHECK(got_echo, "Echo #%d", i + 1); + + /* + * 关闭 Socket(使用 close_socket 而非 disconnect,避免进入 FIN_WAIT_2)。 + * 等待状态机回到 CLOSED(CH395F_SOCKET_CLOSED = 0x05)。 + */ + ch395f_close_socket(sock); + { + uint8_t st[2]; + wait = HAL_GetTick(); + while (HAL_GetTick() - wait < 5000) { + ch395f_get_socket_status(sock, st); + if (st[0] == CH395F_SOCKET_CLOSED) break; + HAL_Delay(50); + } + DBG_INFO("closed: sock=0x%02X tcp=0x%02X", st[0], st[1]); + } + HAL_Delay(200); + } + + TEST_REPORT("Phase 2"); +} +#endif + +/* =================================================================== + * Phase 3 — UDP Echo(CH395F 作为 UDP Server) + * + * 测试目的:验证 CH395F UDP 协议栈的收发能力,包括: + * 1. UDP Socket 打开和关闭 + * 2. 从任意源接收 UDP 数据报 + * 3. 解析 UDP 包头的源 IP/端口 + * 4. 向对应源地址回显数据 + * 5. SENDBUF_FREE 中断等待(确保发送缓冲可用) + * + * 前置条件:PC 发送 UDP 包到 192.168.1.100:60000 + * 通过:30/30 轮回显成功。 + * 失败:任意轮回显超时或解析错误。 + * =================================================================== */ + +#ifdef ENABLE_PHASE3_TESTS +static void phase3_run(void) { + uint8_t sock = 0; + uint8_t rx_buf[300] = {0}; + int i; + + DBG_INFO("=== CH395F Phase 3 Tests (UDP) ==="); + + /* + * 配置 Socket 0 为 UDP Server: + * - 协议类型 UDP + * - 本地端口 60000 + * - 目标 IP/端口初始为广播(0xFF.0xFF.0xFF.0xFF:0), + * 收到数据后根据包头中的源地址动态设置。 + */ + sock = 0; + ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP); + ch395f_set_sour_port(sock, 60000); + { + uint8_t bcast[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + ch395f_set_des_ip(sock, bcast); + } + ch395f_set_des_port(sock, 0); + + TEST_CHECK(ch395f_open_socket(sock) == CH395F_ERR_SUCCESS, + "UDP socket %d opened (port=60000)", sock); + HAL_Delay(200); + + /* + * 循环接收并回显,最多 30 轮。 + * CH395F UDP 接收数据格式(read_recv_buf 返回): + * [0-1] 保留 + * [2-3] 源端口(大端) + * [4-7] 源 IP + * [8+] 载荷数据 + * 回显前需根据源 IP/Port 设置目标地址,写回载荷数据。 + * 每次 write_send_buf 后需等待 SENDBUF_FREE 中断,确保数据已 + * 从 MCU 传输到 CH395F 内部发送缓冲,避免后续接收覆盖。 + */ + for (i = 0; i < 30; i++) { + uint32_t tick = HAL_GetTick(); + int echo_ok = 0; + while (HAL_GetTick() - tick < 30000) { + uint16_t rlen = ch395f_get_recv_len(sock); + if (rlen > 8) { + memset(rx_buf, 0, sizeof(rx_buf)); + ch395f_read_recv_buf(sock, rx_buf, rlen); + + uint16_t payload_len = rlen - 8; + uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8); + uint8_t *src_ip = &rx_buf[4]; + uint8_t *payload = &rx_buf[8]; + + ch395f_set_des_ip(sock, src_ip); + ch395f_set_des_port(sock, src_port); + ch395f_write_send_buf(sock, payload, payload_len); + + uint32_t sw = HAL_GetTick(); + while (HAL_GetTick() - sw < 2000) { + uint16_t gs = ch395f_get_glob_int_status_all(); + if (gs & (1U << (sock + 4))) { + if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE) + break; + } + HAL_Delay(10); + } + echo_ok = 1; + break; + } + HAL_Delay(50); + } + TEST_CHECK(echo_ok, "UDP echo #%d", i + 1); + if (!echo_ok) break; + } + + ch395f_close_socket(sock); + TEST_CHECK(1, "UDP socket closed"); + TEST_REPORT("Phase 3"); +} +#endif + +/* =================================================================== + * Phase 4 — NET 层 TCP Echo(基础收发) + * + * 测试目的:验证 BSD Socket API(net_socket)的单连接 TCP Echo 通路。 + * Socket 0 复用监听和数据通道。PC 发任意数据,MCU 原样返回。 + * 这是 Phase 5/7/8/10 的基础。 + * + * 入口条件:Socket 0 处于 ESTABLISHED(TCP 连接已建立)。 + * 通过:数据收发一致。 + * 失败:send/recv 数量不匹配。 + * =================================================================== */ + +#ifdef ENABLE_PHASE4_TESTS +static uint32_t s_phase4_count = 0; + +static void phase4_run(int sockfd) { + /* + * 使用 NET_MSG_DONTWAIT 非阻塞接收 —— 没有数据时立即返回, + * 不阻塞任务循环(Phase 5/7/8/10 也共享同一个循环)。 + * 收到数据后立即原样发送回显。 + */ + int n = net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT); + if (n > 0) { + int sent = net_send(sockfd, s_rx_buf, n, 0); + if (sent != n) { + DBG_ERROR("echo: send %d != recv %d", sent, n); + return; + } + s_phase4_count++; + if (s_phase4_count <= 10 || (s_phase4_count % 100 == 0)) { + DBG_INFO("Phase4: echo #%lu (%d bytes)", s_phase4_count, n); + } + } +} +#endif + +/* =================================================================== + * Phase 5 — 大文件传输(超过 CH395F 硬件缓冲 4KB) + * + * 测试目的:验证超过 Socket 0 接收缓冲(4KB)的数据在 net_send/ + * net_recv 层的正确处理。net_send_locked 自动将大数据 + * 拆分为 ≤4096B 的块,每块通过一次 DMA 事务写入 CH395F。 + * 接收端同样分多次调用 ch395f_read_recv_buf 读空硬件缓冲。 + * + * 数据模式:递增字节(s_tx_buf[i] = i & 0xFF),接收端按 offset 逐字节比对。 + * 通过:全部 20480 字节逐字节匹配。 + * 失败:任意字节不一致或收发过程中断。 + * =================================================================== */ + +#ifdef ENABLE_PHASE5_TESTS +#define PHASE5_FILE_SIZE 20480 + +static void phase5_fill(uint8_t *buf, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + buf[i] = (uint8_t)(i & 0xFF); + } +} + +static int phase5_verify(uint8_t *buf, uint32_t size, uint32_t offset) { + for (uint32_t i = 0; i < size; i++) { + if (buf[i] != (uint8_t)((offset + i) & 0xFF)) { + DBG_ERROR("Phase5: mismatch at %lu (expect 0x%02X, got 0x%02X)", + offset + i, (uint8_t)((offset + i) & 0xFF), buf[i]); + return 0; + } + } + return 1; +} + +static void phase5_run(int sockfd) { + DBG_INFO("Phase5: starting %d-byte file transfer", PHASE5_FILE_SIZE); + + phase5_fill(s_tx_buf, PHASE5_FILE_SIZE); + + /* + * 发送阶段:分块发送,每块最多 4096B。 + * net_send 内部通过消息队列转发到 netTask,由 net_send_locked + * 按 CH395F_SPI_DMA_MAX_PAYLOAD(4096)自动拆分 DMA 事务。 + * 等待 PC 端回显(echod)全部数据。 + */ + uint32_t sent = 0; + while (sent < PHASE5_FILE_SIZE) { + int chunk = (PHASE5_FILE_SIZE - sent) > 4096 ? 4096 : (PHASE5_FILE_SIZE - sent); + int n = net_send(sockfd, s_tx_buf + sent, chunk, 0); + if (n <= 0) { + DBG_ERROR("Phase5: send fail at %lu (ret=%d)", sent, n); + return; + } + sent += (uint32_t)n; + DBG_INFO("Phase5: sent %lu/%d", sent, PHASE5_FILE_SIZE); + } + + /* + * 接收阶段:分块接收,逐块验证。 + * PC 回显数据需与发送数据逐字节一致(递增模式)。 + * 验证失败立即返回,不继续接收剩余数据。 + */ + uint32_t recvd = 0; + while (recvd < PHASE5_FILE_SIZE) { + int n = net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, 0); + if (n <= 0) { + DBG_ERROR("Phase5: recv fail at %lu (ret=%d)", recvd, n); + return; + } + int ok = phase5_verify(s_rx_buf, (uint32_t)n, recvd); + TEST_CHECK(ok, "Phase5: verify at %lu (%d bytes)", recvd, n); + if (!ok) return; + recvd += (uint32_t)n; + } + + TEST_CHECK(1, "Phase5: %d-byte file transfer complete", PHASE5_FILE_SIZE); +} +#endif + +/* =================================================================== + * Phase 6 — DHCP 自动获取 IP + * + * 测试目的:验证 CH395F DHCP 客户端功能,包括: + * 1. DHCP DISCOVER → OFFER → REQUEST → ACK 完整握手 + * 2. DHCP 成功后 IP 地址非零 + * 3. 广播宣告(UDP 广播本机 IP) + * 4. 接收并回显 HELLO 消息 + * + * 注意:DHCP 成功后会改变 CH395F 的 IP 配置,需与之前静态 IP 测试隔离。 + * 前置条件:PC 上运行 DHCP Server(管理员权限)。 + * 通过:DHCP SUCCESS + IP 非零 + HELLO 回显成功。 + * 失败:DHCP 超时或 HELLO 无响应。 + * =================================================================== */ + +#ifdef ENABLE_PHASE6_TESTS +#ifndef PHASE6_ANNOUNCE_PORT +#define PHASE6_ANNOUNCE_PORT 60001 +#endif +#ifndef PHASE6_ANNOUNCE_SOCK +#define PHASE6_ANNOUNCE_SOCK 6 +#endif + +static void phase6_run(void) { + uint8_t ip_info[20] = {0}; + + DBG_INFO("=== CH395F Phase 6 Tests (DHCP) ==="); + + /* + * 启用 DHCP:ch395f_set_dhcp(1) 使 CH395F 进入 DHCP 自动协商。 + * 然后轮询 DHCP 状态寄存器:0x00 = 成功,非 0x00 = 进行中/失败。 + * 最多等待 30 秒。 + */ + ch395f_set_dhcp(1); + TEST_CHECK(1, "DHCP enabled, waiting for IP..."); + + HAL_Delay(500); + uint32_t tick = HAL_GetTick(); + uint8_t dhcp_ok = 0; + while (HAL_GetTick() - tick < 30000) { + ch395f_get_glob_int_status_all(); + if (ch395f_get_dhcp_status() == 0x00) { dhcp_ok = 1; break; } + HAL_Delay(500); + } + TEST_CHECK(dhcp_ok, "DHCP completed within 30s"); + if (!dhcp_ok) goto phase6_end; + + ch395f_get_ip_inf(ip_info); + DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); + TEST_CHECK(ip_info[0]!=0||ip_info[1]!=0||ip_info[2]!=0||ip_info[3]!=0, + "IP non-zero"); + + /* + * DHCP 成功后广播宣告本机 IP:通过 Socket 6(额外 UDP Socket) + * 向 255.255.255.255:60001 发送 4 字节 IP 地址。 + * 然后等待 PC 回复 HELLO(5 字节),收到后原样回显。 + */ + { + uint8_t anno_sock = PHASE6_ANNOUNCE_SOCK; + uint8_t bcast[4] = {0xFF,0xFF,0xFF,0xFF}; + uint8_t rx_buf[64] = {0}; + + ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP); + ch395f_set_send_buf(anno_sock, 24, 2); + ch395f_set_recv_buf(anno_sock, 26, 2); + ch395f_set_sour_port(anno_sock, 60000); + ch395f_set_des_ip(anno_sock, bcast); + ch395f_set_des_port(anno_sock, 0); + + if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) { + ch395f_set_des_ip(anno_sock, bcast); + ch395f_set_des_port(anno_sock, PHASE6_ANNOUNCE_PORT); + ch395f_write_send_buf(anno_sock, ip_info, 4); + uint32_t sw = HAL_GetTick(); + while (HAL_GetTick() - sw < 2000) { + uint16_t gs = ch395f_get_glob_int_status_all(); + if (gs & (1U << (anno_sock + 4))) { + if (ch395f_get_sock_int_status(anno_sock) & CH395F_SINT_STAT_SENBUF_FREE) break; + } + HAL_Delay(10); + } + TEST_CHECK(1, "DHCP announcement sent"); + + ch395f_set_des_ip(anno_sock, bcast); + ch395f_set_des_port(anno_sock, 0); + + int hello_ok = 0; + uint32_t wait = HAL_GetTick(); + while (HAL_GetTick() - wait < 5000) { + ch395f_get_glob_int_status_all(); + uint16_t rlen = ch395f_get_recv_len(anno_sock); + if (rlen > 8) { + memset(rx_buf,0,sizeof(rx_buf)); + ch395f_read_recv_buf(anno_sock, rx_buf, rlen); + uint16_t plen = rlen - 8; + uint8_t *payload = &rx_buf[8]; + if (plen == 5 && memcmp(payload, "HELLO", 5) == 0) { + uint8_t *src_ip = &rx_buf[4]; + uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8); + ch395f_set_des_ip(anno_sock, src_ip); + ch395f_set_des_port(anno_sock, src_port); + ch395f_write_send_buf(anno_sock, payload, plen); + TEST_CHECK(1, "HELLO echo OK"); + hello_ok = 1; + break; + } + } + HAL_Delay(50); + } + TEST_CHECK(hello_ok, "HELLO echo within 5s"); + ch395f_close_socket(anno_sock); + } + } + +phase6_end: + TEST_REPORT("Phase 6"); +} +#endif + +/* =================================================================== + * Phase 7 — Select/Poll I/O 多路复用 + * + * 测试目的:验证 net_select() API 在单 Socket 模式下正确工作: + * 1. Socket 0 有数据到达时 select 返回正数 + * 2. NET_FD_ISSET 正确指示就绪的 Socket + * 3. 超时(50ms 无数据)时返回 0 + * + * 入口条件:Socket 0 处于 ESTABLISHED。 + * 通过:select 检测到数据并成功回显。 + * 失败:select 未检测到数据或回显失败。 + * =================================================================== */ + +#ifdef ENABLE_PHASE7_TESTS +#include "net_select.h" +static uint32_t s_phase7_count = 0; + +static void phase7_run(int sockfd) { + net_fd_set readfds; + net_timeval tv; + + /* + * 配置 select:只监测 Socket 0 的可读状态,超时 50ms。 + * NET_FD_ZERO 清空集合,NET_FD_SET 添加 Socket 0。 + * 50ms 超时确保不阻塞任务循环太久。 + */ + NET_FD_ZERO(&readfds); + NET_FD_SET(sockfd, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 50000; + + int nfds = net_select(sockfd + 1, &readfds, NULL, NULL, &tv); + if (nfds <= 0) return; + + /* Socket 0 可读:非阻塞接收并回显 */ + if (NET_FD_ISSET(sockfd, &readfds)) { + int n = net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT); + if (n > 0) { + int sent = net_send(sockfd, s_rx_buf, n, 0); + if (sent == n) { + s_phase7_count++; + TEST_CHECK(1, "Phase7: select echo #%lu (%d bytes)", s_phase7_count, n); + } + } + } +} +#endif + +/* =================================================================== + * Phase 8 — 边界条件测试 + * + * 测试目的:验证以下 DMA 和协议栈边界情况: + * 1. TC-NET-801: 4095B(SPI 最大载荷 -1,单次 DMA 容纳) + * 2. TC-NET-802: 4096B(= SPI 最大载荷,填满单次 DMA) + * 3. TC-NET-803: 4097B(超过 SPI 最大载荷,拆 2 次 DMA) + * 4. TC-NET-804: 零长度发送(返回 -1) + * 5. TC-NET-805: 20 × 100B 连续快速发送 + * + * 各用例通过状态机(s_phase8_stage)串行执行,每次 phase8_run 推进一个 + * 步骤(发送→等待→接收→验证→下一阶段)。发送数据为递增模式字节 + * (buf[i] = seed + i),接收端按相同模式验证。 + * + * 入口条件:Socket 0 处于 ESTABLISHED。 + * 通过:各用例自身判定。 + * =================================================================== */ + +#ifdef ENABLE_PHASE8_TESTS +static int s_phase8_stage = 0; +static uint32_t s_phase8_recv = 0; + +static void phase8_fill(uint8_t *buf, uint32_t size, uint8_t seed) { + for (uint32_t i = 0; i < size; i++) { + buf[i] = (uint8_t)(seed + i); + } +} + +static int phase8_verify(uint8_t *buf, uint32_t size, uint8_t seed) { + for (uint32_t i = 0; i < size; i++) { + if (buf[i] != (uint8_t)(seed + i)) return 0; + } + return 1; +} + +static void phase8_run(int sockfd) { + /* 阶段 0 → 1:发送 4095B */ + if (s_phase8_stage == 0) { + DBG_INFO("Phase8: 4095B (DMA-1)"); + phase8_fill(s_tx_buf, 4095, 0x10); + int sent = net_send(sockfd, s_tx_buf, 4095, 0); + TEST_CHECK(sent == 4095, "Phase8: send 4095B (sent=%d)", sent); + s_phase8_recv = 0; + s_phase8_stage = 1; + return; + } + /* 阶段 1:接收并验证 4095B 回显 */ + if (s_phase8_stage == 1) { + int n = net_recv(sockfd, s_rx_buf + s_phase8_recv, (int)(4095 - s_phase8_recv), 0); + if (n > 0) { + s_phase8_recv += (uint32_t)n; + if (s_phase8_recv >= 4095) { + TEST_CHECK(phase8_verify(s_rx_buf, 4095, 0x10), "Phase8: verify 4095B"); + s_phase8_stage = 2; + } + } + return; + } + /* 阶段 2 → 3:发送 4096B(DMA 边界精确值) */ + if (s_phase8_stage == 2) { + DBG_INFO("Phase8: 4096B (DMA exact)"); + phase8_fill(s_tx_buf, 4096, 0x20); + int sent = net_send(sockfd, s_tx_buf, 4096, 0); + TEST_CHECK(sent == 4096, "Phase8: send 4096B (sent=%d)", sent); + s_phase8_recv = 0; + s_phase8_stage = 3; + return; + } + /* 阶段 3:接收并验证 4096B 回显 */ + if (s_phase8_stage == 3) { + int n = net_recv(sockfd, s_rx_buf + s_phase8_recv, (int)(4096 - s_phase8_recv), 0); + if (n > 0) { + s_phase8_recv += (uint32_t)n; + if (s_phase8_recv >= 4096) { + TEST_CHECK(phase8_verify(s_rx_buf, 4096, 0x20), "Phase8: verify 4096B"); + s_phase8_stage = 4; + } + } + return; + } + /* 阶段 4 → 5:发送 4097B(超过 DMA 上限,自动拆 4096+1) */ + if (s_phase8_stage == 4) { + DBG_INFO("Phase8: 4097B (DMA+1, 2 SPI xacts)"); + phase8_fill(s_tx_buf, 4097, 0x30); + int sent = net_send(sockfd, s_tx_buf, 4097, 0); + TEST_CHECK(sent == 4097, "Phase8: send 4097B (sent=%d)", sent); + s_phase8_recv = 0; + s_phase8_stage = 5; + return; + } + /* 阶段 5:接收并验证 4097B 回显 */ + if (s_phase8_stage == 5) { + int n = net_recv(sockfd, s_rx_buf + s_phase8_recv, (int)(4097 - s_phase8_recv), 0); + if (n > 0) { + s_phase8_recv += (uint32_t)n; + if (s_phase8_recv >= 4097) { + TEST_CHECK(phase8_verify(s_rx_buf, 4097, 0x30), "Phase8: verify 4097B"); + s_phase8_stage = 6; + } + } + return; + } + /* 阶段 6:零长度发送 —— 期望 net_send 返回 -1(NET_ERR_INVAL) */ + if (s_phase8_stage == 6) { + DBG_INFO("Phase8: zero-length send"); + int sent = net_send(sockfd, s_tx_buf, 0, 0); + TEST_CHECK(sent < 0, "Phase8: send 0B returns %d (expect <0)", sent); + s_phase8_stage = 7; + return; + } + /* 阶段 7:20 × 100B 快速发送 */ + if (s_phase8_stage == 7) { + DBG_INFO("Phase8: 20x rapid sends (100B each)"); + phase8_fill(s_tx_buf, 100, 0x40); + int all_ok = 1; + for (int i = 0; i < 20; i++) { + if (net_send(sockfd, s_tx_buf, 100, 0) != 100) { + TEST_CHECK(0, "Phase8: rapid #%d fail", i + 1); + all_ok = 0; break; + } + osDelay(1); + } + if (all_ok) { + TEST_CHECK(1, "Phase8: 20x rapid sends OK"); + s_phase8_recv = 0; + s_phase8_stage = 8; + } else { + s_phase8_stage = 0; + } + return; + } + /* 阶段 8:接收 2000B 回显总和并验证 */ + if (s_phase8_stage == 8) { + int n = net_recv(sockfd, s_rx_buf + s_phase8_recv, (int)(2000 - s_phase8_recv), 0); + if (n > 0) { + s_phase8_recv += (uint32_t)n; + if (s_phase8_recv >= 2000) { + TEST_CHECK(1, "Phase8: all echo received"); + TEST_REPORT("Phase 8"); + s_phase8_stage = 0; + } + } + return; + } +} +#endif + +/* =================================================================== + * Phase 9 — PHY 链路恢复 + * + * 测试目的:验证 CH395F 的 PHY 状态监测功能: + * 1. 拔网线后 net_poll 检测到 PHY_CHANGE 中断并识别 DISCONN + * 2. 插回网线后 PHY 自动恢复连接 + * + * 需物理操作(拔插网线),测试会等待最多 15s 检测断开、30s 检测重连。 + * 通过:断开和重连均被检测到。 + * 失败:任意步骤超时。 + * =================================================================== */ + +#ifdef ENABLE_PHASE9_TESTS +static void phase9_run(void) { + DBG_INFO("=== CH395F Phase 9 Tests (PHY Recovery) ==="); + + /* + * 第一步:等待 PHY 断开(需拔网线)。 + * 轮询 GINT 状态寄存器中的 PHY_CHANGE 位, + * 然后读取 PHY 状态确认是否为 DISCONN。 + * 超时 15 秒 —— 若超时未检测到,提示拔网线。 + */ + uint8_t phy_disconnected = 0; + uint32_t wait = HAL_GetTick(); + while (HAL_GetTick() - wait < 15000) { + if (ch395f_get_glob_int_status_all() & CH395F_GINT_STAT_PHY_CHANGE) { + if (ch395f_get_phy_status() == CH395F_PHY_DISCONN) { + TEST_CHECK(1, "PHY disconnect detected"); + phy_disconnected = 1; + break; + } + } + HAL_Delay(100); + } + if (!phy_disconnected) { + TEST_CHECK(0, "PHY disconnect NOT detected (unplug cable?)"); + goto phase9_end; + } + + /* + * 第二步:等待 PHY 重连(需插回网线)。 + * 同样检测 PHY_CHANGE 中断,然后读取 PHY 状态确认不是 DISCONN。 + * 超时 30 秒。 + */ + uint8_t phy_reconnected = 0; + wait = HAL_GetTick(); + while (HAL_GetTick() - wait < 30000) { + if (ch395f_get_glob_int_status_all() & CH395F_GINT_STAT_PHY_CHANGE) { + uint8_t phy = ch395f_get_phy_status(); + if (phy != CH395F_PHY_DISCONN) { + TEST_CHECK(1, "PHY reconnected (0x%02X)", phy); + phy_reconnected = 1; + break; + } + } + HAL_Delay(100); + } + if (!phy_reconnected) { + TEST_CHECK(0, "PHY reconnect NOT detected (re-plug cable?)"); + } + +phase9_end: + TEST_REPORT("Phase 9"); +} +#endif + +/* =================================================================== + * Phase 10 — TCP KeepAlive 超时断开 + * + * 测试目的:验证 CH395F 的 TCP KeepAlive 机制: + * 1. 连接空闲超过 idle 阈值(60s)后开始发送探测包 + * 2. 探测无响应(默认 3 次 × 5s = 15s)后触发 TIMEOUT + * 3. Socket 自动关闭并回到 LISTEN + * + * 入口条件:Socket 0 处于 ESTABLISHED,PC 连接后静置(不发数据)。 + * 通过:KeepAlive 断开时间 ≥ 60s(从连接建立到 recv 返回 0 的间隔)。 + * 失败:不到 60s 就断开(可能是其他原因导致)或 75s 后仍未断开。 + * =================================================================== */ + +#ifdef ENABLE_PHASE10_TESTS +static int s_phase10_armed = 0; +static uint32_t s_phase10_start = 0; + +static void phase10_run(int sockfd) { + /* + * 第一次调用:记录开始时间并 arm。 + * 后续调用:非阻塞 recv,若返回 0 表示连接已断开(KeepAlive 触发)。 + * 计算耗时与 60s idle 阈值比较。 + */ + if (!s_phase10_armed) { + s_phase10_armed = 1; + s_phase10_start = HAL_GetTick(); + DBG_INFO("Phase10: idle wait for KeepAlive timeout (60s)"); + return; + } + + int n = net_recv(sockfd, s_rx_buf, 64, NET_MSG_DONTWAIT); + if (n == 0) { + uint32_t elapsed = HAL_GetTick() - s_phase10_start; + TEST_CHECK(elapsed >= 60000, + "Phase10: KeepAlive triggered after %lums (expect >=60000ms)", elapsed); + TEST_REPORT("Phase 10"); + s_phase10_armed = 0; + } +} +#endif + +/* =================================================================== + * 任务入口:StartCh395fTestTask + * + * 执行顺序: + * 1. 等待 g_net_ready(netTask 初始化完成标志) + * 2. 阻塞执行 Phase 1/2/3/6/9(各 Phase 内部有独立循环和等待) + * 3. 创建 TCP 监听 Socket(单连接模式,端口 8080) + * 4. 进入事件驱动循环,等待 PC 连接并执行 Phase 4/5/7/8/10 + * + * 单连接模式说明: + * Socket 0 复用监听和数据通道。accept 返回的 fd 与监听 fd 相同。 + * 连接断开后需重新 listen。连接恢复检测在循环末尾通过 + * net_get_sock() 检查状态并自动处理。 + * =================================================================== */ + +void StartCh395fTestTask(void *argument) +{ + (void)argument; + + int sockfd = -1; + int phase_active = 0; + uint32_t accept_retry = 0; + struct net_sockaddr_in client_addr; + int addrlen = sizeof(client_addr); + + DBG_INFO("Test task started, waiting for netTask..."); + + /* 等待 netTask 完成 net_init() 及 CH395F 初始化 */ + while (!g_net_ready) { + osDelay(100); + } + DBG_INFO("netTask ready"); + + /* ==================== 阻塞阶段 ==================== + * Phase 1/2/3/6/9 在 TCP 监听之前执行,不依赖外部连接。 + * 各 Phase 有各自的测试逻辑,完成后继续下一 Phase。 + */ +#ifdef ENABLE_PHASE1_TESTS + phase1_run(); +#endif +#ifdef ENABLE_PHASE2_TESTS + phase2_run(); +#endif +#ifdef ENABLE_PHASE3_TESTS + phase3_run(); +#endif +#ifdef ENABLE_PHASE6_TESTS + phase6_run(); +#endif +#ifdef ENABLE_PHASE9_TESTS + phase9_run(); +#endif + + /* ==================== 创建 TCP 监听 ==================== */ + + DBG_INFO("Creating test socket..."); + sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0); + if (sockfd < 0) { + DBG_ERROR("Test task: net_socket failed"); + goto task_end; + } + DBG_INFO("Test task: socket %d created", sockfd); + + { + struct net_sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = NET_AF_INET; + addr.sin_port = net_htons(8080); + if (net_bind(sockfd, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) { + DBG_ERROR("Test task: net_bind failed"); + goto task_end; + } + DBG_INFO("Test task: bound to port 8080"); + } + + if (net_listen(sockfd, 0) < 0) { + DBG_ERROR("Test task: net_listen failed"); + goto task_end; + } + DBG_INFO("Test task: listening on 8080 (single-socket mode)"); + + /* ==================== 事件驱动阶段 ==================== + * Phase 4/5/7/8/10 在 TCP 监听循环中运行,需要 PC 客户端连接。 + * 没有连接时循环等待;连接建立后执行对应 Phase。 + * 断开后自动重新 listen。 + */ + for (;;) { + if (!phase_active) { + int acc = net_accept(sockfd, (struct net_sockaddr *)&client_addr, &addrlen); + if (acc < 0) { + if (accept_retry % 100 == 0) { + DBG_INFO("Test task: waiting for connection..."); + } + accept_retry++; + osDelay(100); + continue; + } + accept_retry = 0; + phase_active = 1; + DBG_INFO("Test task: client accepted (fd=%d)", acc); + + /* 连接建立,重置各 Phase 内部状态 */ +#ifdef ENABLE_PHASE8_TESTS + s_phase8_stage = 0; +#endif +#ifdef ENABLE_PHASE10_TESTS + s_phase10_armed = 0; +#endif + } + + /* 每 10ms 依次执行各已启用 Phase */ +#ifdef ENABLE_PHASE4_TESTS + phase4_run(sockfd); +#endif +#ifdef ENABLE_PHASE5_TESTS + phase5_run(sockfd); + phase_active = 0; /* Phase 5 一次性完成,发完即止 */ +#endif +#ifdef ENABLE_PHASE7_TESTS + phase7_run(sockfd); +#endif +#ifdef ENABLE_PHASE8_TESTS + phase8_run(sockfd); +#endif +#ifdef ENABLE_PHASE10_TESTS + phase10_run(sockfd); +#endif + + /* + * 连接状态检查:通过 net_get_sock 获取 Socket 控制块, + * 如果连接已断开(state != ESTABLISHED),关闭并重新 listen。 + * 这是单连接模式的关键 —— Socket 0 必须始终处于 LISTEN 或 ESTABLISHED。 + */ + { + net_sock_t *sk = net_get_sock(sockfd); + if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED) { + DBG_INFO("Test task: connection closed, re-listen..."); + net_close(sockfd); + osDelay(500); + net_listen(sockfd, 0); + phase_active = 0; + } + } + + osDelay(10); + } + +task_end: + for (;;) { + osDelay(1000); + } +} diff --git a/test/ch395f_test_task.h b/test/ch395f_test_task.h new file mode 100644 index 0000000..66bf166 --- /dev/null +++ b/test/ch395f_test_task.h @@ -0,0 +1,14 @@ +#ifndef __CH395F_TEST_TASK_H +#define __CH395F_TEST_TASK_H + +#ifdef __cplusplus +extern "C" { +#endif + +void StartCh395fTestTask(void *argument); + +#ifdef __cplusplus +} +#endif + +#endif /* __CH395F_TEST_TASK_H */