1407 lines
59 KiB
C
1407 lines
59 KiB
C
/******************************************************************************
|
||
* 模块名称:CH395F Network Test Task
|
||
* 模块功能:CH395F 全阶段测试入口,统一在独立 FreeRTOS 任务中运行。
|
||
* 测试分两组:
|
||
* Phase 1~5(硬件层)— 阻塞式独占执行(各自有内部循环/等待),netTask 未运行
|
||
* Phase 4~11(NET 层)— 事件驱动,TCP 监听循环中运行(需 PC 客户端连接)
|
||
* 所有测试结果通过 TEST_CHECK/TEST_REPORT 宏输出到串口。
|
||
* 作者:王建锋
|
||
* 创建日期:2026-07-22
|
||
******************************************************************************/
|
||
|
||
#include <string.h>
|
||
#include "ch395f_test_task.h"
|
||
#include "net_test_task.h"
|
||
#include "cmsis_os.h"
|
||
#include "net_task.h"
|
||
#include "net_socket.h"
|
||
#include "ch395f_test.h"
|
||
#include "ch395f.h"
|
||
#include "test_config.h"
|
||
#include "storage_test.h"
|
||
#include "gd5f_test.h"
|
||
#include "sd2506_test.h"
|
||
|
||
#define DBG_TAG "[TEST_TASK]"
|
||
#include "dbg_log.h"
|
||
|
||
/* netTask 初始化完成标志(由 net_task.c 在 net_stack_bring_up 成功后置 1)。
|
||
* 仅 NET 层模式使用 —— 硬件层模式下 netTask 不创建,由本任务自行初始化。 */
|
||
extern volatile uint8_t g_net_ready;
|
||
|
||
/*
|
||
* 全局测试统计数据,由 TEST_CHECK/TEST_REPORT 宏自动管理。
|
||
* 每次 TEST_CHECK 自增 total 并根据条件增 passed/failed;
|
||
* TEST_REPORT 汇总输出,不会自动清零 —— 每个 Phase 内自行 memset 重置。
|
||
*/
|
||
test_stats_t g_test_stats;
|
||
|
||
|
||
/* ===================================================================
|
||
* Phase 1 — 寄存器与 SPI 命令路径验证
|
||
*
|
||
* 测试目的:确认 CH395F 与 MCU 之间的 SPI 通信链路正常,所有底层命令
|
||
* 能够正确发送并得到预期回复。这是其他所有 Phase 的前提条件。
|
||
*
|
||
* 共 9 项测试(P1-01 ~ P1-09),逐项执行,汇总报告。
|
||
* =================================================================== */
|
||
|
||
/* 解析点分十进制 IP 字符串为数组(避免引入 sscanf 库)。
|
||
* 仅硬件层阶段(1~5)使用,NET 层无需此辅助。 */
|
||
#if defined(ENABLE_HW_LAYER_TESTS)
|
||
static void test_parse_ip(const char *str, uint8_t *ip_arr)
|
||
{
|
||
uint32_t val;
|
||
int32_t i;
|
||
|
||
for (i = 0; i < 4; i++) {
|
||
val = 0;
|
||
while (*str >= '0' && *str <= '9') {
|
||
val = val * 10 + (*str - '0');
|
||
str++;
|
||
}
|
||
ip_arr[i] = (uint8_t)val;
|
||
if (*str == '.') {
|
||
str++;
|
||
}
|
||
}
|
||
}
|
||
#endif /* ENABLE_HW_LAYER_TESTS */
|
||
|
||
#ifdef ENABLE_PHASE1_TESTS
|
||
static void phase1_run(void) {
|
||
uint8_t ip_info[20];
|
||
|
||
/* ---- 前置清全局中断(读即清除) ----
|
||
* net_stack_bring_up() 在 set_phy(100M_FULL) 强制重协商 + 等待链路建立
|
||
* 期间,芯片会锁存 GINT_STAT_PHY_CHANGE(0x0004)标志;硬件层模式下无
|
||
* netTask 轮询消费它,若不清除将导致 P1-06 的"原始状态 0x0000"检查误报。
|
||
* 此处读取一次丢弃,保证 P1-06 反映的是测试期间的纯净状态。 */
|
||
(void)ch395f_get_glob_int_status_all();
|
||
|
||
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);
|
||
int32_t 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);
|
||
int32_t 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);
|
||
int32_t 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 回显服务器(python test/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
|
||
#ifndef PHASE2_TC203_TIMEOUT_MS
|
||
#define PHASE2_TC203_TIMEOUT_MS 30000 /* TC-203 连接超时窗(指南规定 30s) */
|
||
#endif
|
||
|
||
/* TCP Client 单次会话:对应 TC-NET-201 步 2~7 与 TC-NET-202 每轮 6 步。
|
||
* 调用前须已设置协议类型 / 目标 IP / 目标端口(TC-NET-201 步 1)。
|
||
* 每轮使用不同本地端口 local_port 规避 TIME_WAIT。
|
||
* 返回 1 表示全流程通过,0 表示中途失败(已记录 TEST_CHECK)。
|
||
*/
|
||
/* TC-NET-201(硬件层):单次 TCP Client 连接与数据交换,步骤与文档 7 步严格对应 */
|
||
static void phase2_tc201(void)
|
||
{
|
||
uint8_t sock = 0; /* 本用例操作的 Socket 索引,固定 0(单连接模式) */
|
||
uint8_t ip_arr[4]; /* 目标 IP 四字节数组,由 test_parse_ip() 解析 PHASE2_REMOTE_IP 填入 */
|
||
uint8_t tx[64]; /* 发送缓冲:64 字节递增模式数据 0x00..0x3F */
|
||
uint8_t rx[300]; /* 接收缓冲:存放 PC 回显数据,300 > 64 预留安全余量 */
|
||
uint8_t r; /* ch395f_* 命令返回值(0x00 = CH395F_ERR_SUCCESS) */
|
||
uint8_t ok; /* 当前步骤结果标志:1 = 成功跳出等待循环,0 = 超时失败 */
|
||
uint16_t p; /* 逐字节比对循环索引(rx[p] vs tx[p]) */
|
||
uint16_t n; /* 实际参与比对的字节数 = MIN(rl, 64) */
|
||
uint16_t rl; /* 本次实际读取长度 = MIN(收到长度, sizeof(rx)),防缓冲区溢出 */
|
||
uint32_t tick; /* HAL_GetTick() 快照;用无符号减法测超时窗口(回绕安全) */
|
||
int16_t k; /* 通用循环索引(如构造 tx 递增模式) */
|
||
int16_t match; /* 回显校验结果:1 = 全部字节一致,0 = 发现不符(提前 break) */
|
||
|
||
test_parse_ip(PHASE2_REMOTE_IP, ip_arr);
|
||
|
||
/* 步1:配置协议类型 / 目标 IP / 目标端口 / 本地端口 */
|
||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||
ch395f_set_des_ip(sock, ip_arr);
|
||
ch395f_set_des_port(sock, PHASE2_REMOTE_PORT);
|
||
ch395f_set_sour_port(sock, (uint16_t)PHASE2_LOCAL_PORT);
|
||
TEST_CHECK(ch395f_get_cmd_status() == CH395F_ERR_SUCCESS,
|
||
"TC201 step1 cfg proto/ip/port");
|
||
if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) return;
|
||
|
||
/* 构造 64 字节递增模式数据 0x00..0x3F */
|
||
for (k = 0; k < 64; k++) {
|
||
tx[k] = (uint8_t)k;
|
||
}
|
||
|
||
/* 步2:OPEN Socket 0 */
|
||
r = ch395f_open_socket(sock);
|
||
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC201 step2 open");
|
||
if (r != CH395F_ERR_SUCCESS) return;
|
||
|
||
/* 步3:CONNECT */
|
||
r = ch395f_tcp_connect(sock);
|
||
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC201 step3 connect");
|
||
if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }
|
||
|
||
/* 步4:等待 CONNECT 中断
|
||
*
|
||
* CH395F 的中断上报是【两级】结构:
|
||
*
|
||
* 第 1 级 —— 全局中断状态 GET_GLOB_INT_STATUS_ALL(16 位,读即清除)
|
||
* 只回答"哪个 Socket 发生了事件",不回答具体是什么事件。
|
||
* 16 位布局(见 ch395f.h GINT_STAT 宏定义):
|
||
* bit0 UNREACH (0x0001) 目标不可达
|
||
* bit1 IP_CONFLI (0x0002) IP 冲突
|
||
* bit2 PHY_CHANGE (0x0004) 链路状态变化
|
||
* bit3 DHCP (0x0008) DHCP 事件
|
||
* bit4~7 Socket0~3 (0x0010/0x20/0x40/0x80)
|
||
* bit8~11 Socket4~7 (0x0100~0x0800)
|
||
* bit15 DHCPV6 (0x8000)
|
||
*
|
||
* 第 2 级 —— Socket 中断状态 CMD_GET_INT_STATUS_SN(8 位,读即清除)
|
||
* 才包含具体事件类型:SENBUF_FREE / SEND_OK / RECV_OK /
|
||
* CONNECT / DISCONNECT / DISCARD / SOCK_TIMEOUT。
|
||
* 必须在第 1 级提示"本 Socket 有事件"后再读,否则读了也是空。
|
||
*
|
||
* 【掩码 (1U << (sock + 4)) 的由来】
|
||
* 全局状态的 bit0~3 被 4 个全局事件占用,因此 Socket N 的事件标志
|
||
* 固定从 bit4 开始排列 —— Socket N 对应第 (N + 4) 位:
|
||
* sock=0 → 1<<4 = 0x0010(CH395F_GINT_STAT_SOCK0)
|
||
* sock=1 → 1<<5 = 0x0020(CH395F_GINT_STAT_SOCK1)
|
||
* ...
|
||
* sock=7 → 1<<11 = 0x0800(CH395F_GINT_STAT_SOCK7)
|
||
* `int_st & (1U << (sock + 4))` 非 0
|
||
* ⇔ 芯片报告"Socket [sock] 有待处理事件",
|
||
* 此时才值得再花一次 SPI 事务去读第 2 级确认具体事件。
|
||
*/
|
||
tick = HAL_GetTick();
|
||
ok = 0;
|
||
while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) {
|
||
/* 第 1 级:读全局状态。注意本次读取本身会清掉全部全局标志
|
||
* 并拉高 INT#(读即清除),所以每轮循环都在重新采样。 */
|
||
uint16_t int_st = ch395f_get_glob_int_status_all();
|
||
|
||
/* 本 Socket 在全局寄存器中有事件挂起? */
|
||
if (int_st & (1U << (sock + 4))) {
|
||
/* 第 2 级:读本 Socket 的具体事件类型(同样读即清除) */
|
||
uint8_t si = ch395f_get_sock_int_status(sock);
|
||
if (si & CH395F_SINT_STAT_CONNECT) {
|
||
/* bit3 = 0x08:TCP 三次握手完成,进入 ESTABLISHED */
|
||
ok = 1;
|
||
break;
|
||
}
|
||
/* 是别的事件(理论上此阶段不应出现),已被读清、直接忽略,
|
||
* 继续下一轮轮询等 CONNECT */
|
||
}
|
||
HAL_Delay(10); /* 10ms 轮询周期,直至 CONNECT 或超时退出 */
|
||
}
|
||
TEST_CHECK(ok, "TC201 step4 connected");
|
||
if (!ok) {
|
||
ch395f_close_socket(sock);
|
||
return;
|
||
}
|
||
HAL_Delay(100);
|
||
|
||
/* 步5:发送 64 字节,并等待 SENBUF_FREE 中断确认发送缓冲释放 */
|
||
ch395f_write_send_buf(sock, tx, 64);
|
||
tick = HAL_GetTick();
|
||
ok = 0;
|
||
while (HAL_GetTick() - tick < 2000) {
|
||
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_SENBUF_FREE) {
|
||
ok = 1;
|
||
break;
|
||
}
|
||
}
|
||
HAL_Delay(10);
|
||
}
|
||
TEST_CHECK(ok, "TC201 step5 sendbuf_free");
|
||
if (!ok) {
|
||
ch395f_close_socket(sock);
|
||
return;
|
||
}
|
||
|
||
/* 步6:等待回显并逐字节验证(递增模式 0x00..0x3F)
|
||
*
|
||
* 背景:步 5 已把 tx[64] 发给 PC,PC 端 tcp_server 收到后原样回显。
|
||
* 本步骤最多等 10 秒,每轮轮询做两件事:
|
||
* ① 查芯片接收缓冲有没有回显数据到达(优先处理);
|
||
* ② 查有没有 DISCONNECT / TIMEOUT 事件(连接断了就别傻等了)。
|
||
*
|
||
* 关键 API 语义区别:
|
||
* ch395f_get_recv_len() —— 只查询积压字节数,不取走数据(可反复调用)
|
||
* ch395f_read_recv_buf() —— 真正从芯片 RX FIFO 取出数据(取走即消耗)
|
||
*/
|
||
tick = HAL_GetTick();
|
||
ok = 0; /* 尚未收到任何回显数据 */
|
||
while (HAL_GetTick() - tick < 10000) {
|
||
|
||
/* ---- ① 数据到达检查 ----
|
||
* rlen:芯片 RX 缓冲区当前积压的字节数(纯查询、不消耗)。
|
||
* rlen > 0 说明 PC 回显已到达,进入读取 + 校验流程。 */
|
||
uint16_t rlen = ch395f_get_recv_len(sock);
|
||
if (rlen > 0) {
|
||
|
||
/* 截断保护:一次最多读 sizeof(rx)=300 字节,
|
||
* 防止 rlen 异常偏大时 read_recv_buf 写越界 rx[] */
|
||
rl = (rlen > (uint16_t)sizeof(rx)) ? (uint16_t)sizeof(rx) : rlen;
|
||
|
||
/* 清零将要填充的区域(防御性写法,避免残留脏字节干扰比对) */
|
||
memset(rx, 0, rl);
|
||
|
||
/* 从芯片 RX FIFO 取出回显 —— 此刻起这些数据已被消费 */
|
||
ch395f_read_recv_buf(sock, rx, rl);
|
||
|
||
/* 参与比对的长度:预期回显 64 字节;若 TCP 分片导致本次
|
||
* 只到了一部分(rl < 64),则只比对已到的部分 */
|
||
n = (rl < 64) ? rl : 64;
|
||
|
||
/* 判定初值:必须收满 64 字节才算通过。若 rl < 64(分片/短读),
|
||
* match 直接为 0 → "echo verify" 判 FAIL */
|
||
match = (rl >= 64);
|
||
|
||
/* 与发送的递增模式逐字节比对:tx[k]=k,任一不符立即置 FAIL 退出 */
|
||
for (p = 0; p < n; p++) {
|
||
if (rx[p] != tx[p]) {
|
||
match = 0;
|
||
break;
|
||
}
|
||
}
|
||
TEST_CHECK(match, "TC201 step6 echo verify");
|
||
|
||
ok = 1; /* 标记"收到过数据",循环外的 echo rcvd 检查不再重复报错 */
|
||
break; /* 校验结论已得出,无论 PASS/FAIL 都跳出 */
|
||
}
|
||
|
||
/* ---- ② 连接异常检查(还没等到数据才走到这里)----
|
||
* 两级中断:全局状态确认本 Socket 有事件 → 再读具体事件类型。
|
||
* 注意先查数据再查断开:断开事件可能和最后一批数据同时到达,
|
||
* 数据优先保证"临终回显"不被漏掉。 */
|
||
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 | CH395F_SINT_STAT_SOCK_TIMEOUT)) {
|
||
break; /* 对端断开 / 超时:继续等也不会有回显了。
|
||
ok 保持 0,跳出后由下方统一判 FAIL */
|
||
}
|
||
}
|
||
|
||
/* 每 50ms 一轮。若本轮只读了部分数据(rlen > 300 的极端情况),
|
||
* 剩余字节下一轮 get_recv_len 会继续报告,不会丢 */
|
||
HAL_Delay(50);
|
||
}
|
||
/* 到这里只有两种情况:
|
||
* ok==1:收到过数据,结果已由循环内 "echo verify" 记录;
|
||
* ok==0:10s 内什么都没收到 / 中途断开 —— 在此补一条 FAIL。 */
|
||
if (!ok) {
|
||
TEST_CHECK(0, "TC201 step6 echo rcvd");
|
||
}
|
||
|
||
/* 步7:关闭 Socket 并等待状态回到 CLOSED(CH395F_SOCKET_CLOSED = 0x00) */
|
||
ch395f_close_socket(sock);
|
||
tick = HAL_GetTick();
|
||
ok = 0;
|
||
while (HAL_GetTick() - tick < 5000) {
|
||
uint8_t st[2];
|
||
ch395f_get_socket_status(sock, st);
|
||
if (st[0] == CH395F_SOCKET_CLOSED) {
|
||
ok = 1;
|
||
break;
|
||
}
|
||
HAL_Delay(50);
|
||
}
|
||
TEST_CHECK(ok, "TC201 step7 closed");
|
||
HAL_Delay(200);
|
||
}
|
||
|
||
/* TC-NET-202(硬件层):关闭重连 x PHASE2_LOOP_COUNT 轮,每轮 6 步 */
|
||
static void phase2_tc202_once(uint8_t sock, uint16_t local_port)
|
||
{
|
||
uint8_t tx[64];
|
||
uint8_t rx[300];
|
||
uint8_t r;
|
||
uint8_t ok;
|
||
uint16_t p;
|
||
uint16_t n;
|
||
uint16_t rl;
|
||
uint32_t tick;
|
||
int32_t k;
|
||
int32_t match;
|
||
|
||
for (k = 0; k < 64; k++) {
|
||
tx[k] = (uint8_t)k;
|
||
}
|
||
|
||
/* 步1:设置本地端口并 OPEN Socket 0 */
|
||
ch395f_set_sour_port(sock, local_port);
|
||
r = ch395f_open_socket(sock);
|
||
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC202 step1 open");
|
||
if (r != CH395F_ERR_SUCCESS) return;
|
||
|
||
/* 步2:CONNECT */
|
||
r = ch395f_tcp_connect(sock);
|
||
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC202 step2 connect");
|
||
if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }
|
||
|
||
/* 步3:等待 CONNECT 中断 */
|
||
tick = HAL_GetTick();
|
||
ok = 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) { ok = 1; break; }
|
||
}
|
||
HAL_Delay(10);
|
||
}
|
||
TEST_CHECK(ok, "TC202 step3 connected");
|
||
if (!ok) { ch395f_close_socket(sock); return; }
|
||
HAL_Delay(100);
|
||
|
||
/* 步4:发送 64 字节 + SENBUF_FREE */
|
||
ch395f_write_send_buf(sock, tx, 64);
|
||
tick = HAL_GetTick();
|
||
ok = 0;
|
||
while (HAL_GetTick() - tick < 2000) {
|
||
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_SENBUF_FREE) { ok = 1; break; }
|
||
}
|
||
HAL_Delay(10);
|
||
}
|
||
TEST_CHECK(ok, "TC202 step4 sendbuf_free");
|
||
if (!ok) { ch395f_close_socket(sock); return; }
|
||
|
||
/* 步5:等待回显并验证 */
|
||
tick = HAL_GetTick();
|
||
ok = 0;
|
||
while (HAL_GetTick() - tick < 10000) {
|
||
uint16_t rlen = ch395f_get_recv_len(sock);
|
||
if (rlen > 0) {
|
||
rl = (rlen > (uint16_t)sizeof(rx)) ? (uint16_t)sizeof(rx) : rlen;
|
||
memset(rx, 0, rl);
|
||
ch395f_read_recv_buf(sock, rx, rl);
|
||
n = (rl < 64) ? rl : 64;
|
||
match = (rl >= 64);
|
||
for (p = 0; p < n; p++) {
|
||
if (rx[p] != tx[p]) { match = 0; break; }
|
||
}
|
||
TEST_CHECK(match, "TC202 step5 echo verify");
|
||
ok = 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 | CH395F_SINT_STAT_SOCK_TIMEOUT)) break;
|
||
}
|
||
HAL_Delay(50);
|
||
}
|
||
if (!ok) TEST_CHECK(0, "TC202 step5 echo rcvd");
|
||
|
||
/* 步6:关闭 + 等待 CLOSED */
|
||
ch395f_close_socket(sock);
|
||
tick = HAL_GetTick();
|
||
ok = 0;
|
||
while (HAL_GetTick() - tick < 5000) {
|
||
uint8_t st[2];
|
||
ch395f_get_socket_status(sock, st);
|
||
if (st[0] == CH395F_SOCKET_CLOSED) { ok = 1; break; }
|
||
HAL_Delay(50);
|
||
}
|
||
TEST_CHECK(ok, "TC202 step6 closed");
|
||
HAL_Delay(200);
|
||
}
|
||
|
||
static void phase2_tc202(void)
|
||
{
|
||
uint8_t sock = 0;
|
||
uint8_t ip_arr[4];
|
||
int32_t i;
|
||
|
||
test_parse_ip(PHASE2_REMOTE_IP, ip_arr);
|
||
|
||
/* 协议 / 目标端口在重连间保持不变,仅每轮更换本地端口 */
|
||
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++) {
|
||
phase2_tc202_once(sock, (uint16_t)(PHASE2_LOCAL_PORT + i));
|
||
}
|
||
}
|
||
|
||
/* TC-NET-203(硬件层):TCP Client 连接超时(负向测试,P1)
|
||
*
|
||
* 前置环境:PC 未在 192.168.1.2:8081 提供服务(与 TC-201/202 互斥,
|
||
* 需先关闭 PC 端 tcp_server)。固件自行辨别环境错误:
|
||
* 若等待期间误收到 CONNECT,说明 PC 服务未关,判 FAIL 并给出指引。
|
||
*
|
||
* 负向通过路径(本质都是"未建立连接",按事件+耗时细分原因):
|
||
* SOCK_TIMEOUT(快, <3s) —— 实测主路径:对端在线但端口关闭,RST 拒绝,
|
||
* 芯片毫秒级上报,并非等待重传耗尽
|
||
* SOCK_TIMEOUT(慢, ≥3s) —— 对端无响应(掉电/防火墙丢包),重传耗尽后报超时
|
||
* DISCONNECT —— 预留兼容路径(SYN_SENT 阶段实测基本不出现)
|
||
* UNREACH(全局位) —— ARP 失败、对端不在线(仅作备注,不影响判定)
|
||
*
|
||
* 已知陷阱(Trap 14):上一会话 close 的 DISCONNECT 会滞后锁存在
|
||
* SINT_SN(wait-CLOSED 的 STATUS 查询不清中断)。防御双保险:
|
||
* ① 步 2 前预清两级中断;
|
||
* ② 判负向后用 GET_SOCKET_STATUS 反核实,ESTABLISHED 即改判环境错误。
|
||
*/
|
||
static void phase2_tc203(void)
|
||
{
|
||
uint8_t sock = 0; /* 操作 Socket 0(单连接模式) */
|
||
uint8_t ip_arr[4]; /* 目标 IP 四字节数组 */
|
||
uint16_t sour_port = (uint16_t)(PHASE2_LOCAL_PORT + 16); /* 避开 201/202 用过的端口防 TIME_WAIT */
|
||
uint32_t tick;
|
||
uint32_t elapsed; /* 步3 从 CONNECT 到收到负向事件的耗时(ms),用于细分原因 */
|
||
uint8_t r;
|
||
uint8_t neg_si; /* 步3 命中的 socket 事件位(SOCK_TIMEOUT/DISCONNECT) */
|
||
uint8_t ev; /* 步3 结果:0=无事件 1=收到负向事件(过) 2=误收到CONNECT(环境错误) */
|
||
|
||
DBG_INFO("--- TC-NET-203: connect timeout (PC 8081 MUST be OFF) ---");
|
||
|
||
test_parse_ip(PHASE2_REMOTE_IP, ip_arr);
|
||
|
||
/* 步1:配置协议/目标 IP/目标端口/本地端口,校验 CMD_STATUS */
|
||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||
ch395f_set_des_ip(sock, ip_arr);
|
||
ch395f_set_des_port(sock, (uint16_t)PHASE2_REMOTE_PORT);
|
||
ch395f_set_sour_port(sock, sour_port);
|
||
TEST_CHECK(ch395f_get_cmd_status() == CH395F_ERR_SUCCESS,
|
||
"TC203 step1 cfg proto/ip/port");
|
||
if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) return;
|
||
|
||
/* 预清陈旧事件(Trap 14):上一会话 close 的 DISCONNECT 可能滞后锁存
|
||
* 在 SINT_SN 中(wait-CLOSED 轮询的 STATUS 是纯查询、不清中断),
|
||
* 若不清除,本用例首次读中断会误吃旧事件导致负向误判。 */
|
||
(void)ch395f_get_glob_int_status_all();
|
||
(void)ch395f_get_sock_int_status(sock);
|
||
|
||
/* 步2:OPEN + CONNECT。命令为异步下发,此时均应立即返回成功,
|
||
* 真正的失败结果由步 3 的中断体现 */
|
||
r = ch395f_open_socket(sock);
|
||
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC203 step2 open");
|
||
if (r != CH395F_ERR_SUCCESS) return;
|
||
|
||
r = ch395f_tcp_connect(sock);
|
||
TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC203 step2 connect");
|
||
if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }
|
||
|
||
/* 步3:30s 两级中断轮询,等"连不上"的证据。
|
||
* 先扫全局 UNREACH 位作备注,再查本 socket 位取具体事件。
|
||
*
|
||
* 实测行为(2026-08 上板):PC 端口关闭时 SYN 被 RST 拒绝,
|
||
* 芯片以【毫秒级 SOCK_TIMEOUT】上报,并非等待 30s 静默重传耗尽;
|
||
* DISCONNECT 仅在已建立连接断开时触发,SYN_SENT 阶段基本不会出现。
|
||
* 因此同时记录耗时用于细分失败原因:
|
||
* <3s → 对端主动拒绝(RST,端口关闭)
|
||
* ≥3s → 真无应答(对端离线/防火墙丢包,重传耗尽) */
|
||
tick = HAL_GetTick();
|
||
ev = 0;
|
||
neg_si = 0;
|
||
while (HAL_GetTick() - tick < PHASE2_TC203_TIMEOUT_MS) {
|
||
uint16_t int_st = ch395f_get_glob_int_status_all();
|
||
|
||
if (int_st & CH395F_GINT_STAT_UNREACH) {
|
||
DBG_INFO(" global UNREACH: ARP fail (peer offline)");
|
||
}
|
||
|
||
if (int_st & (1U << (sock + 4))) {
|
||
uint8_t si = ch395f_get_sock_int_status(sock);
|
||
if (si & (CH395F_SINT_STAT_SOCK_TIMEOUT | CH395F_SINT_STAT_DISCONNECT)) {
|
||
neg_si = si; /* 记下事件位,跳出后再细分原因 */
|
||
ev = 1;
|
||
break;
|
||
}
|
||
if (si & CH395F_SINT_STAT_CONNECT) {
|
||
ev = 2; /* 连上了 = PC 服务没关,属环境错误而非芯片故障 */
|
||
break;
|
||
}
|
||
}
|
||
HAL_Delay(10);
|
||
}
|
||
|
||
elapsed = HAL_GetTick() - tick;
|
||
|
||
/* 反核实(Trap 14 第二道防线):负向事件与实际状态矛盾时以状态为准。
|
||
* 若收到"负向事件"但 socket 实际已 ESTABLISHED,说明刚才读到的是
|
||
* 上一会话遗留的陈旧 DISCONNECT,真实结果是连接成功 —— 环境错误。 */
|
||
if (ev == 1) {
|
||
uint8_t st_chk[2];
|
||
ch395f_get_socket_status(sock, st_chk);
|
||
if (st_chk[0] == CH395F_TCP_ESTABLISHED) {
|
||
DBG_ERROR(" negative event but socket ESTABLISHED -> stale DISCONNECT!");
|
||
ev = 2;
|
||
}
|
||
}
|
||
|
||
if (ev == 1) {
|
||
const char *why;
|
||
const char *short_tag;
|
||
|
||
if (neg_si & CH395F_SINT_STAT_DISCONNECT) {
|
||
why = "DISCONNECT: peer sent RST (port closed)";
|
||
short_tag = "DISC/RST";
|
||
} else if (elapsed < 3000U) {
|
||
/* 毫秒级超时 = 收到了 RST 快速失败,而非静默无应答 */
|
||
why = "SOCK_TIMEOUT fast: peer actively refused (RST, port closed)";
|
||
short_tag = "T/O-fast(refused)";
|
||
} else {
|
||
why = "SOCK_TIMEOUT slow: no response (offline / firewall drop)";
|
||
short_tag = "T/O-slow(silence)";
|
||
}
|
||
DBG_INFO(" negative event @%lums: %s", elapsed, why);
|
||
TEST_CHECK(1, "TC203 step3 no-connect OK (%lums %s)", elapsed, short_tag);
|
||
} else if (ev == 2) {
|
||
DBG_ERROR(" CONNECT received -> PC 8081 server is still running!");
|
||
TEST_CHECK(0, "TC203 step3 connected?! stop PC server and retry");
|
||
} else {
|
||
TEST_CHECK(0, "TC203 step3 no interrupt in %dms (state machine hang?)",
|
||
PHASE2_TC203_TIMEOUT_MS);
|
||
}
|
||
|
||
/* 步4:无论步 3 结论如何都关闭 Socket,确认状态机可恢复到 CLOSED */
|
||
ch395f_close_socket(sock);
|
||
tick = HAL_GetTick();
|
||
ev = 0;
|
||
while (HAL_GetTick() - tick < 5000) {
|
||
uint8_t st[2];
|
||
ch395f_get_socket_status(sock, st);
|
||
if (st[0] == CH395F_SOCKET_CLOSED) { ev = 1; break; }
|
||
HAL_Delay(50);
|
||
}
|
||
TEST_CHECK(ev == 1, "TC203 step4 closed");
|
||
|
||
HAL_Delay(200);
|
||
}
|
||
|
||
static void phase2_run(void)
|
||
{
|
||
DBG_INFO("=== CH395F Phase 2 Tests (TCP Client) ===");
|
||
|
||
DBG_INFO("--- TC-NET-201: connect & 64B exchange ---");
|
||
phase2_tc201();
|
||
|
||
DBG_INFO("--- TC-NET-202: close/reconnect x%d ---", PHASE2_LOOP_COUNT);
|
||
phase2_tc202();
|
||
|
||
DBG_INFO("--- TC-NET-203: negative test, stop PC 8081 server first ---");
|
||
phase2_tc203();
|
||
|
||
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_client 发包到 192.168.1.100:60000
|
||
* 会话结构:累计 PHASE3_TOTAL_ROUNDS(60) 个有效数据包后关闭 Socket
|
||
* 第 1~30 轮 = TC-301(PC 发 64B);第 31~60 轮 = TC-302(1B/64B/200B)
|
||
* "HELLO"(5B) 为 PC 就绪探针:照常回显但不计入轮次
|
||
* 通过:60/60 有效轮回显成功。
|
||
* 失败:任意轮回显超时或解析错误。
|
||
* =================================================================== */
|
||
|
||
#ifdef ENABLE_PHASE3_TESTS
|
||
|
||
/* UDP 有效回显轮数:1~30 = TC-301(64B),31~60 = TC-302(变长)。
|
||
* "HELLO" 探针只回显不计数,与 test/ch395f_socket_test.py udp_client 约定 */
|
||
#ifndef PHASE3_TOTAL_ROUNDS
|
||
#define PHASE3_TOTAL_ROUNDS 60
|
||
#endif
|
||
|
||
static void phase3_run(void) {
|
||
uint8_t sock = 0;
|
||
uint8_t rx_buf[300] = {0};
|
||
int32_t counted;
|
||
|
||
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);
|
||
|
||
/*
|
||
* 计数式回显循环:累计 PHASE3_TOTAL_ROUNDS 个有效数据包。
|
||
* CH395F UDP 接收数据格式(read_recv_buf 返回):
|
||
* [0-1] 保留
|
||
* [2-3] 源端口(大端)
|
||
* [4-7] 源 IP
|
||
* [8+] 载荷数据
|
||
* 回显前需根据源 IP/Port 设置目标地址,写回载荷数据。
|
||
* 每次 write_send_buf 后需等待 SENDBUF_FREE 中断,确保数据已
|
||
* 从 MCU 传输到 CH395F 内部发送缓冲,避免后续接收覆盖。
|
||
*
|
||
* "HELLO"(5B) 为 PC 就绪探针:照常回显但不计入轮次,
|
||
* 使脚本探针阶段不占用用例预算(与 udp_client 脚本约定)。
|
||
*/
|
||
counted = 0;
|
||
while (counted < PHASE3_TOTAL_ROUNDS) {
|
||
uint32_t tick = HAL_GetTick();
|
||
int32_t echo_ok = 0;
|
||
int32_t is_probe = 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];
|
||
|
||
is_probe = (payload_len == 5 &&
|
||
memcmp(payload, "HELLO", 5) == 0);
|
||
|
||
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);
|
||
}
|
||
|
||
if (!echo_ok) {
|
||
TEST_CHECK(0, "UDP echo #%d timeout", counted + 1);
|
||
break;
|
||
}
|
||
if (!is_probe) {
|
||
counted++;
|
||
TEST_CHECK(1, "UDP echo #%d", counted);
|
||
}
|
||
}
|
||
|
||
ch395f_close_socket(sock);
|
||
TEST_CHECK(1, "UDP socket closed");
|
||
TEST_REPORT("Phase 3");
|
||
}
|
||
#endif
|
||
|
||
/* ===================================================================
|
||
* Phase 4 — 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_PHASE4_TESTS
|
||
#ifndef PHASE4_ANNOUNCE_PORT
|
||
#define PHASE4_ANNOUNCE_PORT 60001
|
||
#endif
|
||
#ifndef PHASE4_ANNOUNCE_SOCK
|
||
#define PHASE4_ANNOUNCE_SOCK 6
|
||
#endif
|
||
#ifndef PHASE4_HELLO_WAIT_MS
|
||
#define PHASE4_HELLO_WAIT_MS 30000 /* HELLO 回显窗:PC 在 ACK 后固定 sleep(3)s,
|
||
* 窗口须覆盖 PC 侧启动延迟(旧值 5s 重叠仅 ~2s) */
|
||
#endif
|
||
|
||
static void phase4_run(void) {
|
||
uint8_t ip_info[20] = {0};
|
||
int32_t same = 0;
|
||
|
||
DBG_INFO("=== CH395F Phase 4 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 phase4_end;
|
||
|
||
/*
|
||
* IP 寄存器提交滞后(2026-08-24 上板实测):DHCP 状态已置成功、
|
||
* 芯片已按新租约通信,但此刻 get_ip_inf 仍回读旧静态配置——
|
||
* 串口打印与宣告广播会带出过期 IP。
|
||
* 对策:轮询直至连续 3 次(间隔 300ms)读到相同非零值才采信。
|
||
*/
|
||
{
|
||
uint8_t last[20] = {0};
|
||
tick = HAL_GetTick();
|
||
while (HAL_GetTick() - tick < 8000) {
|
||
memset(ip_info, 0, sizeof(ip_info));
|
||
ch395f_get_ip_inf(ip_info);
|
||
if ((ip_info[0] || ip_info[1] || ip_info[2] || ip_info[3]) &&
|
||
memcmp(last, ip_info, sizeof(ip_info)) == 0) {
|
||
if (++same >= 3) break;
|
||
} else {
|
||
same = 0;
|
||
}
|
||
if (memcmp(last, ip_info, sizeof(ip_info)) != 0) {
|
||
DBG_INFO(" DHCP IP report: %d.%d.%d.%d",
|
||
ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
|
||
memcpy(last, ip_info, sizeof(ip_info));
|
||
}
|
||
HAL_Delay(300);
|
||
}
|
||
memcpy(ip_info, last, sizeof(ip_info));
|
||
}
|
||
TEST_CHECK(same >= 3, "IP registers settled (3 consecutive reads)");
|
||
|
||
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 = PHASE4_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, PHASE4_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);
|
||
|
||
int32_t hello_ok = 0;
|
||
uint32_t wait = HAL_GetTick();
|
||
while (HAL_GetTick() - wait < PHASE4_HELLO_WAIT_MS) {
|
||
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 %dms", PHASE4_HELLO_WAIT_MS);
|
||
ch395f_close_socket(anno_sock);
|
||
}
|
||
}
|
||
|
||
phase4_end:
|
||
TEST_REPORT("Phase 4");
|
||
}
|
||
#endif
|
||
|
||
/* ===================================================================
|
||
* Phase 5 — 大文件传输(硬件层裸命令)
|
||
*
|
||
* 测试目的:不依赖 net_socket/netTask,直接用 ch395f_* 命令验证
|
||
* TCP 批量数据搬运能力:
|
||
* 1. 裸命令建立 TCP Client 连接(open/connect/等 CONNECT)
|
||
* 2. 顺序双测向:先连续发送 256KB(PC 只收不回声,SENBUF_FREE 流控),
|
||
* 再连续接收 256KB(PC 按相同 LCG 模式回放,滚动校验),内存 O(CHUNK)
|
||
* 3. 全量累加和 TX/RX 对总账 + 双向独立吞吐基线
|
||
*
|
||
* PC 配合:python test/ch395f_socket_test.py tcp_bulk --port 8081 --bulk 262144 --chunk 4096
|
||
* (先收满 256KB 不回声,再按 LCG 模式回放 256KB)
|
||
* 通过:全部块校验一致,且 SUM(TX)==SUM(RX)。
|
||
* 失败:连接超时 / SENBUF_FREE 超时 / RX 停滞 / 数据不一致。
|
||
* =================================================================== */
|
||
|
||
#ifdef ENABLE_PHASE5_TESTS
|
||
|
||
/* 回传参数(TC-NET-502 合规测试时按档位修改重编译) */
|
||
#ifndef PHASE5_REMOTE_IP
|
||
#define PHASE5_REMOTE_IP "192.168.1.2"
|
||
#endif
|
||
#ifndef PHASE5_REMOTE_PORT
|
||
#define PHASE5_REMOTE_PORT 8081 /* PC tcp_server 监听端口 */
|
||
#endif
|
||
#ifndef PHASE5_LOCAL_PORT
|
||
#define PHASE5_LOCAL_PORT 50010
|
||
#endif
|
||
#ifndef PHASE5_TOTAL_BYTES
|
||
#define PHASE5_TOTAL_BYTES (256*1024) /* 回传总量 */
|
||
#endif
|
||
#ifndef PHASE5_CHUNK_SIZE
|
||
#define PHASE5_CHUNK_SIZE 4093 /* 单块 = socket 默认发送缓冲,DP-04 边界 */
|
||
#endif
|
||
#ifndef PHASE5_CONN_TIMEOUT_MS
|
||
#define PHASE5_CONN_TIMEOUT_MS 15000
|
||
#endif
|
||
#define PHASE5_TX_TIMEOUT_MS 2000 /* 单块 SENBUF_FREE 等待上限 */
|
||
#define PHASE5_RX_STALL_TIMEOUT_MS 5000 /* 发送完成后 RX 无进展上限 */
|
||
|
||
/* 32 位累加和:足以发现丢字节/错位,比 CRC32 便宜 */
|
||
static uint32_t phase5_sum32(const uint8_t *p, uint32_t n) {
|
||
uint32_t s = 0;
|
||
while (n--) s += *p++;
|
||
return s;
|
||
}
|
||
|
||
/* 按块索引确定性生成模式数据(LCG):发送与回读两侧可独立复现,逐字节比对 */
|
||
static void phase5_gen_block(uint8_t *buf, uint32_t idx) {
|
||
uint32_t seed = 0xC2D2E6F9u ^ (idx * 2654435761u);
|
||
uint32_t k;
|
||
|
||
for (k = 0; k < PHASE5_CHUNK_SIZE; k++) {
|
||
seed = seed * 1664525u + 1013904223u;
|
||
buf[k] = (uint8_t)(seed >> 24);
|
||
}
|
||
}
|
||
|
||
static void phase5_run(void) {
|
||
static uint8_t tx[PHASE5_CHUNK_SIZE];
|
||
static uint8_t rx[PHASE5_CHUNK_SIZE];
|
||
static uint8_t exp[PHASE5_CHUNK_SIZE];
|
||
uint8_t sock = 0;
|
||
uint8_t ip_arr[4];
|
||
int32_t chunks;
|
||
int32_t bad = 0;
|
||
int32_t conn_ok = 0;
|
||
int32_t blk_tx = 0;
|
||
int32_t blk_rx = 0;
|
||
int32_t can_write = 1;
|
||
int32_t progress_64k = -1;
|
||
uint32_t send_bytes;
|
||
uint32_t sum_tx = 0;
|
||
uint32_t sum_rx = 0;
|
||
uint32_t rx_total = 0;
|
||
uint32_t exp_off = 0;
|
||
uint32_t rx_stall = 0;
|
||
uint32_t tx_sw = 0;
|
||
uint32_t t0;
|
||
|
||
DBG_INFO("=== CH395F Phase 5 Tests (HW Bulk Transfer) ===");
|
||
DBG_INFO(" target %s:%d, %d bytes x %d B/chunk",
|
||
PHASE5_REMOTE_IP, PHASE5_REMOTE_PORT,
|
||
PHASE5_TOTAL_BYTES, PHASE5_CHUNK_SIZE);
|
||
|
||
/*
|
||
* 步1:裸命令建立 TCP 连接。
|
||
* 先 close 清理上轮残留(结果不关心),再配置并 connect。
|
||
*/
|
||
(void)ch395f_close_socket(sock);
|
||
HAL_Delay(50);
|
||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||
test_parse_ip(PHASE5_REMOTE_IP, ip_arr);
|
||
ch395f_set_des_ip(sock, ip_arr);
|
||
ch395f_set_des_port(sock, PHASE5_REMOTE_PORT);
|
||
ch395f_set_sour_port(sock, PHASE5_LOCAL_PORT);
|
||
TEST_CHECK(ch395f_open_socket(sock) == CH395F_ERR_SUCCESS &&
|
||
ch395f_tcp_connect(sock) == CH395F_ERR_SUCCESS,
|
||
"step1 open+connect issued");
|
||
if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) {
|
||
TEST_REPORT("Phase 5");
|
||
return;
|
||
}
|
||
|
||
t0 = HAL_GetTick();
|
||
while (HAL_GetTick() - t0 < PHASE5_CONN_TIMEOUT_MS) {
|
||
uint16_t gs = ch395f_get_glob_int_status_all();
|
||
if ((gs & (1U << (sock + 4))) &&
|
||
(ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_CONNECT)) {
|
||
conn_ok = 1;
|
||
break;
|
||
}
|
||
HAL_Delay(10);
|
||
}
|
||
TEST_CHECK(conn_ok, "connected within %dms", PHASE5_CONN_TIMEOUT_MS);
|
||
if (!conn_ok) {
|
||
(void)ch395f_close_socket(sock);
|
||
TEST_REPORT("Phase 5");
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* 步2A:连续发送 256KB(单向,PC 只收不回声)。
|
||
* 节奏:写 4096B → 等 SENBUF_FREE(芯片搬走数据后触发)→ 写下一块,
|
||
* 全程不等回显,测得纯 TX 吞吐。
|
||
* 步2B:连续接收 256KB(PC 收到 256KB 后按相同 LCG 模式回放),
|
||
* 对字节流做滚动校验(期望流按块生成 exp[],字节对位 + sum_rx 累加),
|
||
* 天然处理 TCP 分包/粘包,测得纯 RX 吞吐。
|
||
*/
|
||
chunks = PHASE5_TOTAL_BYTES / PHASE5_CHUNK_SIZE;
|
||
send_bytes = (uint32_t)chunks * PHASE5_CHUNK_SIZE; /* 实际上送字节(4093 档 < TOTAL,余尾不送) */
|
||
|
||
/* ---- 步2A:连续发送 ----
|
||
* 目标:把 chunks×PHASE5_CHUNK_SIZE 字节按 LCG 模式数据一次性连续交出,
|
||
* 全程不等回显,测得纯 TX 吞吐。
|
||
*
|
||
* 发送状态机(can_write 标志):
|
||
* can_write == 1 : 发送缓冲空闲,可写入下一块
|
||
* can_write == 0 : 上一块已写入,等待芯片把数据搬出 socket 发送缓冲
|
||
* (首次进入 can_write 初始为 1,因为连接建立后发送缓冲为空)
|
||
*
|
||
* 芯片写节奏约束(CH395 手册 / Trap 记录):
|
||
* 每次 WRITE_SEND_BUF 后必须收到 SINT_STAT_SENBUF_FREE 才能再次写入,
|
||
* 否则下次写入会被芯片静默丢弃。SENBUF_FREE 表示芯片已把本次写入的数据
|
||
* 从 socket 发送缓冲搬到内部协议栈/MAC 缓冲(腾出空间),故每写一块
|
||
* 就转 can_write=0 等待该事件——这正是"连续发送"的天然流控节拍,
|
||
* 不会因超过发送缓冲(默认 4096B)而溢出。
|
||
*/
|
||
{
|
||
uint32_t tx_t0 = HAL_GetTick(); /* 发送阶段起点:用于计算 TX 吞吐 */
|
||
blk_tx = 0; /* 已交出的块计数 */
|
||
can_write = 1; /* 初始:连接后发送缓冲为空,可直接写 */
|
||
while (blk_tx < chunks && !bad) {
|
||
if (can_write) {
|
||
/* ---- 发送缓冲空闲:生成并写入一块 ---- */
|
||
phase5_gen_block(tx, (uint32_t)blk_tx); /* 确定性生成第 blk_tx 块模式(TX/RX 可复现) */
|
||
sum_tx += phase5_sum32(tx, PHASE5_CHUNK_SIZE); /* 先累加发送侧总账(与 RX 侧对账用) */
|
||
ch395f_write_send_buf(sock, tx, PHASE5_CHUNK_SIZE); /* 4096B 单次写入(= 发送缓冲上限,DP-04 边界) */
|
||
blk_tx++; /* 该块已交出 */
|
||
can_write = 0; /* 进入等待 SENBUF_FREE 状态 */
|
||
tx_sw = HAL_GetTick(); /* 记录等待起点(供超时判定) */
|
||
} else {
|
||
/* ---- 等待上一次写入被芯片搬走(SENBUF_FREE) ----
|
||
* 两级中断轮询(DP-02):先读全局中断状态确认本 socket 有事件,
|
||
* 再读该 socket 的中断状态确认具体为 SENBUF_FREE。
|
||
* 读全局中断状态即清除中断标志(读即清)。
|
||
*/
|
||
uint16_t gs = ch395f_get_glob_int_status_all();
|
||
if ((gs & (1U << (sock + 4))) && /* socket 中断位(socket 0 → bit 4) */
|
||
(ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE)) {
|
||
can_write = 1; /* 缓冲已腾空,下一轮写入下一块 */
|
||
} else if (HAL_GetTick() - tx_sw >= PHASE5_TX_TIMEOUT_MS) {
|
||
/* 超时:芯片迟迟未腾出缓冲 —— 对端未收/链路异常/芯片堵死 */
|
||
bad++;
|
||
DBG_ERROR("TX SENBUF_FREE stall @chunk %d", blk_tx);
|
||
break;
|
||
}
|
||
}
|
||
/* ---- 进度打印(每交出 64KB 打一行) ---- */
|
||
if ((int)(((uint32_t)blk_tx * PHASE5_CHUNK_SIZE) / (64 * 1024)) != progress_64k) {
|
||
progress_64k = (int)(((uint32_t)blk_tx * PHASE5_CHUNK_SIZE) / (64 * 1024));
|
||
DBG_INFO(" TX %d/%d blocks (%lu bytes)", blk_tx, chunks,
|
||
(uint32_t)blk_tx * PHASE5_CHUNK_SIZE);
|
||
}
|
||
}
|
||
/* 全部块已写入发送缓冲即视为发送完成:最后一块已交给芯片,
|
||
* 无需再等其 SENBUF_FREE,数据会随 TCP 正常发往对端。 */
|
||
TEST_CHECK(bad == 0, "TX phase: %d/%d blocks (%lu bytes)",
|
||
blk_tx, chunks, (uint32_t)blk_tx * PHASE5_CHUNK_SIZE);
|
||
{
|
||
uint32_t ms = HAL_GetTick() - tx_t0;
|
||
DBG_INFO(" TX throughput: %lu bytes in %lums (%lu KB/s)",
|
||
(uint32_t)blk_tx * PHASE5_CHUNK_SIZE, ms,
|
||
ms > 0 ? (uint32_t)(((uint32_t)blk_tx * PHASE5_CHUNK_SIZE) / ms) : 0u);
|
||
}
|
||
}
|
||
|
||
/* ---- 步2B:连续接收 ----
|
||
* 目标:PC 收到 256KB 后按相同 LCG 模式回放,MCU 连续排空接收缓冲并对
|
||
* 字节流做滚动校验,测得纯 RX 吞吐。
|
||
*
|
||
* 滚动校验原理:
|
||
* 期望流按块确定性生成,同一时刻只保留当前块的 exp[](内存 O(CHUNK))。
|
||
* exp_off 指向当前期望块内的偏移;回显流可能分包/粘包,故按"字节流"消费:
|
||
* 每次读到 rlen 字节 → 切成若干段(take,每段不超过当前期望块剩余)逐字节
|
||
* 与 exp[exp_off+k] 比对,同时累加 sum_rx;当前块消费完(exp_off==CHUNK)
|
||
* 则回绕并生成下一块期望 —— 与 TCP 的分包/粘包边界完全解耦。
|
||
*
|
||
* 停滞检测(rx_stall):
|
||
* 记录"最后收到数据"的时刻,>PHASE5_RX_STALL_TIMEOUT_MS 无新数据即失败。
|
||
* 用最后进度而非阶段起点,是为了兼容 PC 端回放开始的延迟与块间间隙。
|
||
*/
|
||
if (!bad) {
|
||
uint32_t rx_t0 = HAL_GetTick(); /* 接收阶段起点:用于计算 RX 吞吐 */
|
||
exp_off = 0; /* 当前期望块内的消费偏移 */
|
||
blk_rx = 0; /* 已完整校验的块数 */
|
||
rx_total = 0; /* 已接收字节总数 */
|
||
rx_stall = rx_t0; /* 最后收到数据的时刻(停滞检测基准) */
|
||
progress_64k = -1; /* 重置进度打印基准(TX 阶段已推进过) */
|
||
phase5_gen_block(exp, 0); /* 预生成第 0 块期望数据 */
|
||
while (!bad && rx_total < send_bytes) {
|
||
/* ---- 排空接收缓冲:一次可能读出多包数据,全部消费完再进入下一步 ---- */
|
||
for (;;) {
|
||
uint16_t rlen = ch395f_get_recv_len(sock); /* 查询当前可读字节数 */
|
||
if (rlen == 0) {
|
||
break; /* 缓冲空,跳出排空循环 */
|
||
}
|
||
if (rlen > PHASE5_CHUNK_SIZE) {
|
||
rlen = PHASE5_CHUNK_SIZE; /* 单次读取上限 = 块大小(DP-04 4KB DMA 边界) */
|
||
}
|
||
ch395f_read_recv_buf(sock, rx, rlen); /* 一次 SPI 事务读走 rlen 字节 */
|
||
{
|
||
uint32_t off = 0; /* 本次读取数据内的消费偏移 */
|
||
while (off < rlen) {
|
||
uint32_t take = PHASE5_CHUNK_SIZE - exp_off; /* 当前期望块剩余 */
|
||
uint32_t k;
|
||
if (take > (uint32_t)(rlen - off)) {
|
||
take = rlen - off; /* 截断:本次读取只消费到块边界为止 */
|
||
}
|
||
for (k = 0; k < take; k++) {
|
||
uint8_t b = rx[off + k];
|
||
if (b != exp[exp_off + k]) {
|
||
bad++; /* 逐字节比对失败(定位精确偏移) */
|
||
DBG_ERROR("rx mismatch @%lu", rx_total + off + k);
|
||
}
|
||
sum_rx += b; /* 同时累加接收侧总账 */
|
||
}
|
||
off += take;
|
||
exp_off += take;
|
||
rx_total += take;
|
||
if (exp_off == PHASE5_CHUNK_SIZE) {
|
||
exp_off = 0; /* 当前块消费完:回绕并预生成下一块期望 */
|
||
blk_rx++;
|
||
if (blk_rx < chunks) {
|
||
phase5_gen_block(exp, blk_rx);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
rx_stall = HAL_GetTick(); /* 有数据到达:推进停滞检测基准 */
|
||
}
|
||
if (bad) {
|
||
break;
|
||
}
|
||
/* ---- 停滞保护:发送方已回放完或链路异常,迟迟无新数据 ---- */
|
||
if (HAL_GetTick() - rx_stall >= PHASE5_RX_STALL_TIMEOUT_MS) {
|
||
bad++;
|
||
DBG_ERROR("RX stall @%lu/%lu", rx_total, send_bytes);
|
||
break;
|
||
}
|
||
/* ---- 进度打印(每收到 64KB 打一行) ---- */
|
||
if ((int)(rx_total / (64 * 1024)) != progress_64k) {
|
||
progress_64k = (int)(rx_total / (64 * 1024));
|
||
DBG_INFO(" RX %lu/%lu bytes (%d blocks)", rx_total, send_bytes, blk_rx);
|
||
}
|
||
HAL_Delay(2); /* 轮询间隔:让出总线/降低 SPI 事务频率 */
|
||
}
|
||
TEST_CHECK(bad == 0, "RX phase: %lu/%lu bytes (%d blocks)", rx_total, send_bytes, blk_rx);
|
||
{
|
||
uint32_t ms = HAL_GetTick() - rx_t0;
|
||
DBG_INFO(" RX throughput: %lu bytes in %lums (%lu KB/s)",
|
||
rx_total, ms,
|
||
ms > 0 ? (uint32_t)(rx_total / ms) : 0u);
|
||
}
|
||
}
|
||
TEST_CHECK(sum_tx == sum_rx, "checksum TX==RX (%08x/%08x)", sum_tx, sum_rx);
|
||
|
||
/* 步3:关闭,PC 侧应统计到收满 256KB(校验一致)并回放完成 */
|
||
ch395f_close_socket(sock);
|
||
TEST_CHECK(1, "socket closed");
|
||
TEST_REPORT("Phase 5");
|
||
}
|
||
#endif
|
||
void StartCh395fTestTask(void *argument)
|
||
{
|
||
(void)argument;
|
||
|
||
DBG_INFO("Test task started, suite=%s", TEST_SUITE_NAME);
|
||
|
||
#if defined(TEST_SUITE_CH395F)
|
||
#if defined(ENABLE_HW_LAYER_TESTS)
|
||
/* 硬件层模式(阶段 1~5):netTask 未创建,由本任务完成一次性协议栈
|
||
* 初始化并独占 CH395F —— 无 net_poll() 并发读取中断,无 SPI 并发事务 */
|
||
if (net_stack_bring_up() != 0) {
|
||
DBG_ERROR("net stack bring-up FAIL");
|
||
} else {
|
||
/* ==================== 阻塞阶段 ====================
|
||
* Phase 1~5(硬件层)在 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_PHASE4_TESTS
|
||
phase4_run();
|
||
#endif
|
||
#ifdef ENABLE_PHASE5_TESTS
|
||
phase5_run();
|
||
#endif
|
||
}
|
||
#else
|
||
/* NET 层模式(阶段 6~11):等待 netTask 完成 net_init() 及 CH395F 初始化。
|
||
* Phase 7(TCP Client)与阶段 6/8~11(TCP Server 监听循环)互斥,
|
||
* 二者共用单连接 Socket 0,故按宏选择入口。 */
|
||
while (!g_net_ready) {
|
||
osDelay(100);
|
||
}
|
||
DBG_INFO("netTask ready");
|
||
#if defined(ENABLE_PHASE7_TESTS)
|
||
net_layer_client_test_main();
|
||
#else
|
||
net_layer_test_main();
|
||
#endif
|
||
#endif /* TEST_SUITE_CH395F */
|
||
#elif defined(TEST_SUITE_STORAGE)
|
||
storage_test_run();
|
||
#elif defined(TEST_SUITE_GD5F)
|
||
gd5f_test_run();
|
||
#elif defined(TEST_SUITE_RTC)
|
||
sd2506_test_run();
|
||
#endif
|
||
|
||
for (;;) {
|
||
osDelay(1000);
|
||
}
|
||
}
|