1155 lines
43 KiB
C
1155 lines
43 KiB
C
/******************************************************************************
|
||
* 模块名称: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 <string.h>
|
||
#include <stdio.h>
|
||
#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);
|
||
}
|
||
}
|