构建测试

This commit is contained in:
2026-08-22 19:53:24 +08:00
parent a7f367f6a3
commit fc88bc8752
15 changed files with 2050 additions and 1578 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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 <string.h>
#include <stdio.h>
#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
}

View File

@@ -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 EchoHELLO + 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 <stdint.h>
/*
* 阶段使能开关(取消注释即启用对应阶段)
* 注意一次仅启用一个阶段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(64KBrecv_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 */