通过测试排除了一些问题
This commit is contained in:
29
AGENTS.md
29
AGENTS.md
@@ -133,6 +133,35 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
|
||||
- **TCP 重传参数**:`SET_RETRAN_COUNT`(默认12次,最大20)和 `SET_RETRAN_PERIOD`(默认500ms,最大1000ms)必须在 `INIT_CH395` 之前设置,总重传时间 = 次数 × 周期
|
||||
- **KeepAlive 参数必须为 500ms 的倍数**:`SET_KEEPALIVE_IDLE`(默认20000ms)、`SET_KEEPALIVE_INTVL`(默认15000ms)单位为 ms,且 IDLE 必须 > INTVL,均为 500 的倍数;传入非 500 倍数的值会导致未定义行为(CH395F 内部定时器异常,TCP 连接几秒内 TIMEOUT)
|
||||
- **KeepAlive 默认关闭**:需在 `SINT_STAT_CONNECT` 后调用 `ch395f_set_keepalive_enable(sock, 1)` 启用
|
||||
- **Socket 4-7 默认无收发缓冲**:CH395F 默认只为 Socket 0-3 各分配 4 个缓冲区块(2048 字节)。多连接模式下使用 Socket 4-7 时,**必须在 `open_socket` 之前**显式分配:
|
||||
- `ch395f_set_send_buf(sock, 28, 2)` — 分配发送缓冲(例:块 28-29,共 1024 字节)
|
||||
- `ch395f_set_recv_buf(sock, 30, 2)` — 分配接收缓冲(例:块 30-31,共 1024 字节)
|
||||
- 不分配会导致发送数据为固定垃圾内容(`0x0028` 填充)、接收缓冲区无法存储数据
|
||||
- **TCP 关闭重连的正确方法**:**直接调用 `ch395f_close_socket()`,不要先调 `ch395f_tcp_disconnect()`**
|
||||
- ❌ `disconnect` → `close`:`disconnect` 发送 FIN 将 Socket 推入 FIN_WAIT_2,此后 `close` 不再发送 RST,Socket 卡在 FIN_WAIT_2 必须等远端发 FIN 才能关闭(可能需数分钟超时)
|
||||
- ✅ `close` 直接:在 ESTABLISHED 或 CLOSE_WAIT 状态下调用 `close` 会发送 RST 立即终止连接,Socket 瞬间回到 CLOSED(已验证 `closed: sock=0x00 tcp=0x00`)
|
||||
- 关闭后需轮询 `ch395f_get_socket_status()` 等待 `sock=0x00` 再 `open_socket`,典型耗时 < 1s
|
||||
- **SOCK_TIMEOUT 中断**:长时间无数据时 CH395F 可能触发 `SINT_STAT_SOCK_TIMEOUT`,不应将其视为致命错误——记录日志后继续操作即可,不要因此关闭 Socket
|
||||
- **UDP 客户端/服务器模式**(手册 §9.2.4):通过 `SET_DES_IP_SN` 的 IP 地址区分:
|
||||
- **DesIP=0xFFFFFFFF(255.255.255.255)→ UDP Server 模式**:接受任意来源的数据,接收缓冲区中数据前 8 字节为信息头(`[0-1]reserved [2-3]src_port(LE) [4-7]src_ip [8+]payload`),回发前需设置 `SET_DES_IP` 和 `SET_DES_PORT` 指定目标
|
||||
- **DesIP=具体 IP → UDP Client 模式**:只接受指定 IP:Port 的数据,接收数据无信息头,只能向预设的目标发送
|
||||
- **UDP 发送缓冲必须等 SENDBUF_FREE**(手册要求):每次 `ch395f_write_send_buf()` 后必须等待 `SINT_STAT_SENBUF_FREE` 中断(0x01),否则下次写入会被 CH395F 静默丢弃。可通过轮询 `ch395f_get_sock_int_status()` 检查此标志
|
||||
|
||||
## 测试说明
|
||||
|
||||
- **测试代码文件**:`Drivers/BSP/CH395F/ch395f_test.h` / `ch395f_test.c`
|
||||
- **PC 端测试脚本**:`test/ch395f_socket_test.py`,支持 Python 3.7+
|
||||
- **调用入口**:`Src/main.c` 的 `USER CODE BEGIN 2` 区域
|
||||
- **Phase 1**(9 项寄存器测试):✅ 通过
|
||||
- **Phase 2**(TCP Client 收发 + 关闭重连 ×3 轮):✅ 通过
|
||||
- **Phase 3**(UDP Server Echo + PING + 大包):✅ 通过
|
||||
- **Phase 4**(NET 层 TCP Echo):✅ 通过,主循环永久运行
|
||||
- **Phase 5**(NET 层 UDP Echo):✅ 通过
|
||||
- **Phase 6**(DHCP 自动获取 IP):✅ 通过
|
||||
- **Phase 7**(Select/Poll I/O 多路复用):✅ 通过
|
||||
- **Phase 8**(多客户端 7 并发):✅ 通过,成功率 100%
|
||||
- **测试拓扑**:PC (192.168.1.2) ↔ 交换机 ↔ CH395F (192.168.1.100)
|
||||
- **详细使用说明**:见 `docs/CH395F_Test_Guide.md`
|
||||
|
||||
## GD5F2GQ5UE 驱动关键点
|
||||
|
||||
|
||||
@@ -124,6 +124,14 @@ static void ch395f_spi_dma_transfer(uint16_t len)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 检查传输是否成功:SPI 错误码必须为 0 */
|
||||
if (hspi2.ErrorCode != 0U)
|
||||
{
|
||||
DBG_INFO("SPI DMA done with errors: ErrorCode=0x%lX, SPI_State=%d",
|
||||
hspi2.ErrorCode, (int)hspi2.State);
|
||||
__HAL_SPI_CLEAR_OVRFLAG(&hspi2);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1177,6 +1185,9 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len)
|
||||
/* 拷贝用户数据到 DMA 发送缓冲区 */
|
||||
memcpy(&s_spi2_dma_tx_buf[4], p_data, len);
|
||||
|
||||
/* 数据同步屏障:确保 memcpy 写入在 DMA 读取前对总线可见 */
|
||||
__DSB();
|
||||
|
||||
/* CS 拉低 -> DMA 批量传输 -> CS 拉高 */
|
||||
ch395f_spi_begin();
|
||||
ch395f_spi_dma_transfer(total);
|
||||
|
||||
576
Drivers/BSP/CH395F/ch395f_test.c
Normal file
576
Drivers/BSP/CH395F/ch395f_test.c
Normal file
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* 模块名称:CH395F Driver Test Suite
|
||||
* 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例
|
||||
* 适用平台:STM32F4 系列(CH395F 以太网芯片)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-19
|
||||
* 修改记录:
|
||||
* 2026-07-19 Phase 1/2/3 implemented
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include "ch395f_test.h"
|
||||
#include "ch395f.h"
|
||||
#include "net_socket.h"
|
||||
#define DBG_TAG "[CH395T]"
|
||||
#include "dbg_log.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* 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 20
|
||||
#endif
|
||||
#ifndef PHASE3_TIMEOUT_MS
|
||||
#define PHASE3_TIMEOUT_MS 30000
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 恢复 TCP Server 多连接模式的数据 Socket 配置
|
||||
*/
|
||||
static void restore_data_sockets(uint16_t port)
|
||||
{
|
||||
int i;
|
||||
for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++)
|
||||
{
|
||||
ch395f_set_proto_type((uint8_t)i, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port((uint8_t)i, port);
|
||||
}
|
||||
DBG_INFO("data socks restored (port %d)", port);
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 1 ============================
|
||||
*/
|
||||
|
||||
void ch395f_phase1_tests(void)
|
||||
{
|
||||
uint8_t i;
|
||||
int ver;
|
||||
|
||||
DBG_INFO("=== CH395F Phase 1 Tests ===");
|
||||
|
||||
/* Test 1: GET_VERSION */
|
||||
ver = ch395f_get_version();
|
||||
DBG_INFO("GET_VERSION: 0x%02X (ver=%d)", ver, ver & 0x3F);
|
||||
|
||||
/* Test 2: GET_CMD_STATUS */
|
||||
uint8_t cmd_st = ch395f_get_cmd_status();
|
||||
DBG_INFO("GET_CMD_STATUS: 0x%02X (%s)",
|
||||
cmd_st,
|
||||
cmd_st == CH395F_ERR_SUCCESS ? "SUCCESS" :
|
||||
cmd_st == CH395F_ERR_BUSY ? "BUSY" : "OTHER");
|
||||
|
||||
/* Test 3: GET_IP_INF */
|
||||
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]);
|
||||
DBG_INFO(" IP match: %s", (ip_info[0]==192 && ip_info[1]==168 && ip_info[2]==1 && ip_info[3]==100) ? "YES" : "NO");
|
||||
DBG_INFO(" GW match: %s", (ip_info[4]==192 && ip_info[5]==168 && ip_info[6]==1 && ip_info[7]==1) ? "YES" : "NO");
|
||||
DBG_INFO(" MASK match: %s", (ip_info[8]==255 && ip_info[9]==255 && ip_info[10]==255 && ip_info[11]==0) ? "YES" : "NO");
|
||||
|
||||
/* Test 4: GINT_STATUS_ALL (2-byte version) */
|
||||
uint16_t gint_all = ch395f_get_glob_int_status_all();
|
||||
DBG_INFO("GINT_STATUS_ALL: 0x%04X (lo=0x%02X hi=0x%02X)", gint_all, gint_all & 0xFF, (gint_all >> 8) & 0xFF);
|
||||
|
||||
/* Test 5: GINT_STATUS (1-byte version) — read-clear check */
|
||||
uint8_t gint_1b = ch395f_get_glob_int_status();
|
||||
DBG_INFO("GINT_STATUS_1byte (after clear): 0x%02X (expect 0x00 after read-clear)", gint_1b);
|
||||
|
||||
/* Test 6: SOCK_STATUS(0) */
|
||||
{
|
||||
uint8_t st[2];
|
||||
ch395f_get_socket_status(0, st);
|
||||
DBG_INFO("SOCK_STATUS(0): sock=0x%02X(OPEN), tcp=0x%02X(LISTEN)", st[0], st[1]);
|
||||
}
|
||||
|
||||
/* Test 7: SET_ARP */
|
||||
ch395f_set_arp(10, 5);
|
||||
DBG_INFO("SET_ARP(period=10*100ms, cnt=5): OK");
|
||||
|
||||
/* Test 8: SET_TTL */
|
||||
ch395f_set_ttl(0, 64);
|
||||
DBG_INFO("SET_TTL(sock=0, ttl=64): OK");
|
||||
|
||||
/* Test 9: CLEAR_RECV_BUF + SOCK_INT poll */
|
||||
ch395f_clear_recv_buf(0);
|
||||
DBG_INFO("CLEAR_RECV_BUF(sock=0): OK");
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t si = ch395f_get_sock_int_status(i);
|
||||
DBG_INFO("SOCK_INT(%d): 0x%02X", i, si);
|
||||
}
|
||||
|
||||
DBG_INFO("=== Phase 1 Tests Complete ===");
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 2 ============================
|
||||
*/
|
||||
|
||||
void ch395f_phase2_tests(void)
|
||||
{
|
||||
uint8_t sock;
|
||||
uint8_t status;
|
||||
uint8_t sock_int;
|
||||
uint8_t rx_buf[300];
|
||||
int i;
|
||||
uint32_t tick;
|
||||
uint16_t int_st;
|
||||
|
||||
DBG_INFO("=== CH395F Phase 2 Tests (TCP Client - Direct Driver) ===");
|
||||
|
||||
/* 解析目标 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);
|
||||
|
||||
sock = (uint8_t)(NET_MAX_SOCKETS - 1);
|
||||
|
||||
/* 一次性配置 */
|
||||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_send_buf(sock, 28, 2);
|
||||
ch395f_set_recv_buf(sock, 30, 2);
|
||||
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);
|
||||
|
||||
ch395f_set_sour_port(sock, PHASE2_LOCAL_PORT + i);
|
||||
|
||||
status = ch395f_open_socket(sock);
|
||||
if (status != CH395F_ERR_SUCCESS) { DBG_ERROR("open failed"); goto phase2_end; }
|
||||
status = ch395f_tcp_connect(sock);
|
||||
if (status != CH395F_ERR_SUCCESS) { DBG_ERROR("connect failed"); ch395f_close_socket(sock); goto phase2_end; }
|
||||
|
||||
/* 等待 CONNECT */
|
||||
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) { DBG_INFO("CONNECT"); break; }
|
||||
}
|
||||
HAL_Delay(10);
|
||||
}
|
||||
if (HAL_GetTick() - tick >= PHASE2_TIMEOUT_MS) { DBG_ERROR("connect timeout"); ch395f_close_socket(sock); goto phase2_end; }
|
||||
|
||||
HAL_Delay(100);
|
||||
|
||||
/* 发送测试数据 */
|
||||
{
|
||||
uint8_t test_buf[64];
|
||||
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);
|
||||
DBG_INFO("Sending test data (64 bytes)");
|
||||
ch395f_write_send_buf(sock, test_buf, 64);
|
||||
}
|
||||
|
||||
/* Echo 循环 */
|
||||
for (;;)
|
||||
{
|
||||
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);
|
||||
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"); goto phase2_iter_done; }
|
||||
if (sock_int & CH395F_SINT_STAT_SOCK_TIMEOUT) { DBG_INFO("SOCK_TIMEOUT"); goto phase2_iter_done; }
|
||||
}
|
||||
HAL_Delay(50);
|
||||
}
|
||||
}
|
||||
|
||||
phase2_iter_done:
|
||||
/* 关闭 socket */
|
||||
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);
|
||||
}
|
||||
|
||||
phase2_end:
|
||||
restore_data_sockets(8080);
|
||||
DBG_INFO("=== Phase 2 Tests Complete ===");
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 3 ============================
|
||||
*/
|
||||
|
||||
void ch395f_phase3_tests(void)
|
||||
{
|
||||
uint8_t sock;
|
||||
uint8_t rx_buf[300];
|
||||
uint32_t tick, echo_start;
|
||||
int i;
|
||||
|
||||
DBG_INFO("=== CH395F Phase 3 Tests (UDP) ===");
|
||||
|
||||
sock = (uint8_t)(NET_MAX_SOCKETS - 1);
|
||||
|
||||
/* UDP Server 模式:DesIP=0xFFFFFFFF → 接受任意来源 */
|
||||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP);
|
||||
ch395f_set_send_buf(sock, 28, 2);
|
||||
ch395f_set_recv_buf(sock, 30, 2);
|
||||
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);
|
||||
|
||||
if (ch395f_open_socket(sock) != CH395F_ERR_SUCCESS)
|
||||
{
|
||||
DBG_ERROR("UDP open failed");
|
||||
goto phase3_end;
|
||||
}
|
||||
DBG_INFO("UDP socket %d opened (server, port=%d, accept any)", sock, PHASE3_LOCAL_PORT);
|
||||
|
||||
HAL_Delay(200);
|
||||
|
||||
/* Echo 循环:收到 → 解析来源 → 回显,最多 PHASE3_ECHO_COUNT 轮 */
|
||||
echo_start = HAL_GetTick();
|
||||
for (i = 0; i < PHASE3_ECHO_COUNT; i++)
|
||||
{
|
||||
tick = HAL_GetTick();
|
||||
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);
|
||||
|
||||
/* 8 字节头: [0-1]reserved [2-3]src_port(LE) [4-7]src_ip [8+]payload */
|
||||
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);
|
||||
|
||||
/* 等 SENDBUF_FREE 再收下一个(CH395F 要求写前必须空闲) */
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
HAL_Delay(50);
|
||||
}
|
||||
|
||||
if (HAL_GetTick() - tick >= PHASE3_TIMEOUT_MS)
|
||||
{
|
||||
DBG_INFO("UDP recv timeout (%ds)", PHASE3_TIMEOUT_MS / 1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= PHASE3_ECHO_COUNT)
|
||||
DBG_INFO("Max echoes reached (%d)", PHASE3_ECHO_COUNT);
|
||||
else
|
||||
DBG_INFO("Elapsed: %lums", HAL_GetTick() - echo_start);
|
||||
|
||||
ch395f_close_socket(sock);
|
||||
DBG_INFO("UDP socket closed");
|
||||
|
||||
phase3_end:
|
||||
restore_data_sockets(8080);
|
||||
DBG_INFO("=== Phase 3 Tests Complete ===");
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 4 ============================
|
||||
* NET 层 TCP Echo:在主循环中调用,用 net_recv + net_send 回显
|
||||
*/
|
||||
|
||||
static char s_phase4_buf[2048];
|
||||
|
||||
void ch395f_phase4_tests(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
#ifdef ENABLE_PHASE4_TESTS
|
||||
for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++)
|
||||
{
|
||||
net_sock_t *sk = net_get_sock(i);
|
||||
if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int n = net_recv(i, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT);
|
||||
if (n > 0)
|
||||
{
|
||||
net_send(i, s_phase4_buf, n, 0);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)i;
|
||||
(void)s_phase4_buf;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 5 ============================
|
||||
* NET 层 UDP Echo:主循环中运行,用 net_recvfrom + net_sendto 回显
|
||||
*/
|
||||
|
||||
#ifndef PHASE5_LOCAL_PORT
|
||||
#define PHASE5_LOCAL_PORT 60000
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
static int s_phase5_sock = -1;
|
||||
static char s_phase5_buf[2048];
|
||||
#endif
|
||||
|
||||
void ch395f_phase5_init(void)
|
||||
{
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
struct net_sockaddr_in addr;
|
||||
|
||||
s_phase5_sock = net_socket(NET_AF_INET, NET_SOCK_DGRAM, 0);
|
||||
if (s_phase5_sock < 0)
|
||||
{
|
||||
DBG_ERROR("Phase5: net_socket failed");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = NET_AF_INET;
|
||||
addr.sin_port = net_htons(PHASE5_LOCAL_PORT);
|
||||
|
||||
if (net_bind(s_phase5_sock, (struct net_sockaddr *)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
DBG_ERROR("Phase5: net_bind failed");
|
||||
net_close(s_phase5_sock);
|
||||
s_phase5_sock = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
DBG_INFO("Phase5: UDP socket %d bound to port %d", s_phase5_sock, PHASE5_LOCAL_PORT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ch395f_phase5_tests(void)
|
||||
{
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
if (s_phase5_sock < 0) return;
|
||||
|
||||
struct net_sockaddr_in src;
|
||||
int addrlen = sizeof(src);
|
||||
|
||||
int n = net_recvfrom(s_phase5_sock, s_phase5_buf, sizeof(s_phase5_buf),
|
||||
NET_MSG_DONTWAIT,
|
||||
(struct net_sockaddr *)&src, &addrlen);
|
||||
if (n > 0)
|
||||
{
|
||||
net_sendto(s_phase5_sock, s_phase5_buf, n, 0,
|
||||
(struct net_sockaddr *)&src, sizeof(src));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 6 ============================
|
||||
* DHCP 自动获取 IP 测试
|
||||
*/
|
||||
|
||||
void ch395f_phase6_tests(void)
|
||||
{
|
||||
#ifdef ENABLE_PHASE6_TESTS
|
||||
uint8_t status;
|
||||
uint8_t ip_info[20];
|
||||
uint32_t tick;
|
||||
|
||||
DBG_INFO("=== CH395F Phase 6 Tests (DHCP) ===");
|
||||
|
||||
/* 1. 启用 DHCP */
|
||||
ch395f_set_dhcp(1);
|
||||
DBG_INFO("DHCP enabled, waiting for IP...");
|
||||
|
||||
/* 2. 轮询等待 DHCP 完成 */
|
||||
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)
|
||||
{
|
||||
DBG_INFO("DHCP: SUCCESS");
|
||||
break;
|
||||
}
|
||||
|
||||
HAL_Delay(500);
|
||||
}
|
||||
|
||||
if (HAL_GetTick() - tick >= 30000)
|
||||
{
|
||||
status = ch395f_get_dhcp_status();
|
||||
DBG_ERROR("DHCP timeout (status=0x%02X)", status);
|
||||
goto phase6_end;
|
||||
}
|
||||
|
||||
/* 3. 读取获取的 IP */
|
||||
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]);
|
||||
DBG_INFO(" DNS1: %d.%d.%d.%d", ip_info[12], ip_info[13], ip_info[14], ip_info[15]);
|
||||
|
||||
if (ip_info[0] == 0 && ip_info[1] == 0 && ip_info[2] == 0 && ip_info[3] == 0)
|
||||
{
|
||||
DBG_ERROR("DHCP: got 0.0.0.0 (failed)");
|
||||
}
|
||||
|
||||
phase6_end:
|
||||
DBG_INFO("=== Phase 6 Tests Complete ===");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================ Phase 7 ============================
|
||||
* Select/Poll I/O 多路复用测试 — net_select() + net_recv/send echo
|
||||
*/
|
||||
|
||||
#include "net_select.h"
|
||||
|
||||
#ifdef ENABLE_PHASE7_TESTS
|
||||
static char s_phase7_buf[2048];
|
||||
#endif
|
||||
|
||||
void ch395f_phase7_tests(void)
|
||||
{
|
||||
#ifdef ENABLE_PHASE7_TESTS
|
||||
int i;
|
||||
net_fd_set readfds;
|
||||
net_timeval tv;
|
||||
|
||||
NET_FD_ZERO(&readfds);
|
||||
|
||||
/* 把 ESTABLISHED 状态的 TCP 数据 socket 加入监听集合 */
|
||||
for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++)
|
||||
{
|
||||
net_sock_t *sk = net_get_sock(i);
|
||||
if (sk && sk->in_use && sk->state == NET_SOCK_STATE_ESTABLISHED)
|
||||
{
|
||||
NET_FD_SET(i, &readfds);
|
||||
}
|
||||
}
|
||||
|
||||
/* 50ms 超时,避免阻塞主循环 */
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 50000;
|
||||
|
||||
int n = net_select(NET_MAX_SOCKETS, &readfds, NULL, NULL, &tv);
|
||||
if (n <= 0) return;
|
||||
|
||||
/* 处理就绪的 socket */
|
||||
for (i = 0; i < NET_MAX_SOCKETS; i++)
|
||||
{
|
||||
if (!NET_FD_ISSET(i, &readfds)) continue;
|
||||
|
||||
int len = net_recv(i, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT);
|
||||
if (len > 0)
|
||||
{
|
||||
net_send(i, s_phase7_buf, len, 0);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)0;
|
||||
#endif
|
||||
}
|
||||
85
Drivers/BSP/CH395F/ch395f_test.h
Normal file
85
Drivers/BSP/CH395F/ch395f_test.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 模块名称:CH395F Driver Test Suite
|
||||
* 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例,
|
||||
* 覆盖 SPI 命令路径、协议栈配置、Socket 状态机等
|
||||
* 适用平台:STM32F4 系列(CH395F 以太网芯片)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-19
|
||||
* 修改记录:
|
||||
*/
|
||||
|
||||
#ifndef __CH395F_TEST_H
|
||||
#define __CH395F_TEST_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 阶段使能开关(取消注释即启用对应阶段)
|
||||
* 注意:Phase 6 (DHCP) 会改变 IP,建议独立运行
|
||||
*/
|
||||
//#define ENABLE_PHASE1_TESTS /* 寄存器读写 + 中断一致性 */
|
||||
//#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 层 UDP Echo(需主循环调用) */
|
||||
//#define ENABLE_PHASE6_TESTS /* DHCP 自动获取 IP(会改 IP!) */
|
||||
//#define ENABLE_PHASE7_TESTS /* Select/Poll I/O 多路复用 */
|
||||
|
||||
/*
|
||||
* Phase 1: 底层寄存器测试(版本、IP、中断、ARP/TTL 等)
|
||||
* 非破坏性,net_init() 后即可运行
|
||||
*/
|
||||
void ch395f_phase1_tests(void);
|
||||
|
||||
/*
|
||||
* Phase 2: TCP Client 功能测试(3 轮收发 + 关闭重连)
|
||||
* 需 PC 端先运行:
|
||||
* python test/ch395f_socket_test.py tcp_server --port 8081
|
||||
*/
|
||||
void ch395f_phase2_tests(void);
|
||||
|
||||
/*
|
||||
* Phase 3: UDP Server Echo 测试(HELLO 探针 + PING + 大包)
|
||||
* 需 PC 端先运行:
|
||||
* python test/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000 --timeout 30
|
||||
*/
|
||||
void ch395f_phase3_tests(void);
|
||||
|
||||
/*
|
||||
* Phase 4: NET 层 TCP Echo(主循环中运行)
|
||||
* 需 PC 端运行(主循环进入后):
|
||||
* python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080
|
||||
*/
|
||||
void ch395f_phase4_tests(void);
|
||||
|
||||
/*
|
||||
* Phase 5: NET 层 UDP Echo(主循环中运行)
|
||||
* 需 PC 端运行(主循环进入后):
|
||||
* python test/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000 --timeout 30
|
||||
* 限定条件:需在 USER CODE BEGIN 2 中调用 ch395f_phase5_init() 创建 Socket,
|
||||
* USER CODE BEGIN 3 中调用 ch395f_phase5_tests() 做 echo
|
||||
*/
|
||||
void ch395f_phase5_init(void);
|
||||
void ch395f_phase5_tests(void);
|
||||
|
||||
/*
|
||||
* Phase 6: DHCP 测试
|
||||
* 自动获取 IP,打印结果。需独立运行(会改变 IP)
|
||||
*/
|
||||
void ch395f_phase6_tests(void);
|
||||
|
||||
/*
|
||||
* Phase 7: Select/Poll 测试(主循环中运行)
|
||||
* 使用 net_select() 检测可读 socket,net_recv/net_send 回显
|
||||
* 需 PC 端运行:
|
||||
* python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080
|
||||
*/
|
||||
void ch395f_phase7_tests(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __CH395F_TEST_H */
|
||||
@@ -328,7 +328,41 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen)
|
||||
/* 设置本地端口(转换为主机序) */
|
||||
sock->local_port = net_ntohs(addr_in->sin_port);
|
||||
|
||||
/* UDP (SOCK_DGRAM): 直接在此打开 CH395F Socket */
|
||||
if (sock->type == NET_SOCK_DGRAM)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
/* Socket 4-7 默认无收发缓冲,需显式分配 */
|
||||
if (sockfd >= 4)
|
||||
{
|
||||
ch395f_set_send_buf((uint8_t)sockfd, 28, 2);
|
||||
ch395f_set_recv_buf((uint8_t)sockfd, 30, 2);
|
||||
}
|
||||
|
||||
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_UDP);
|
||||
ch395f_set_sour_port((uint8_t)sockfd, sock->local_port);
|
||||
|
||||
/* DesIP=0xFFFFFFFF → UDP Server 模式(接受任意来源) */
|
||||
{
|
||||
uint8_t broadcast[4] = {0xFF, 0xFF, 0xFF, 0xFF};
|
||||
ch395f_set_des_ip((uint8_t)sockfd, broadcast);
|
||||
}
|
||||
ch395f_set_des_port((uint8_t)sockfd, 0);
|
||||
|
||||
status = ch395f_open_socket((uint8_t)sockfd);
|
||||
if (status != CH395F_ERR_SUCCESS)
|
||||
{
|
||||
s_net_errno = NET_ERR_BUSY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sock->state = NET_SOCK_STATE_UDP_OPEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
sock->state = NET_SOCK_STATE_BOUND;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -377,9 +411,15 @@ int net_listen(int sockfd, int backlog)
|
||||
ch395f_tcp_listen(sockfd);
|
||||
|
||||
/* 多连接模式:为数据 Socket 设置协议类型和源端口(不 OPEN)
|
||||
* Socket 4-7 默认无收发缓冲,需在此分配
|
||||
* CH395F 根据这些信息在客户端连接时自动分配空闲 Socket */
|
||||
for (int ds = NET_TCP_SERVER_DATA_SOCK_START; ds < NET_MAX_SOCKETS; ds++)
|
||||
{
|
||||
if (ds >= 4)
|
||||
{
|
||||
ch395f_set_send_buf((uint8_t)ds, 28, 2);
|
||||
ch395f_set_recv_buf((uint8_t)ds, 30, 2);
|
||||
}
|
||||
ch395f_set_proto_type((uint8_t)ds, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_sour_port((uint8_t)ds, sock->local_port);
|
||||
}
|
||||
@@ -798,11 +838,12 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
{
|
||||
struct net_sockaddr_in *addr_in = (struct net_sockaddr_in *)src_addr;
|
||||
addr_in->sin_family = NET_AF_INET;
|
||||
addr_in->sin_port = net_htons((uint16_t)(header[4] | (header[5] << 8)));
|
||||
addr_in->sin_addr.s_addr = ((uint32_t)header[0]) |
|
||||
((uint32_t)header[1] << 8) |
|
||||
((uint32_t)header[2] << 16) |
|
||||
((uint32_t)header[3] << 24);
|
||||
/* CH395F UDP 头: [0-1] reserved [2-3] src_port(LE) [4-7] src_ip */
|
||||
addr_in->sin_port = net_htons((uint16_t)(header[2] | (header[3] << 8)));
|
||||
addr_in->sin_addr.s_addr = ((uint32_t)header[4]) |
|
||||
((uint32_t)header[5] << 8) |
|
||||
((uint32_t)header[6] << 16) |
|
||||
((uint32_t)header[7] << 24);
|
||||
*addrlen = sizeof(struct net_sockaddr_in);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ extern "C" {
|
||||
/*
|
||||
* 总开关:取消注释启用所有调试输出
|
||||
*/
|
||||
/* #define DBG_ENABLE */
|
||||
#define DBG_ENABLE
|
||||
|
||||
/*
|
||||
* 调试级别开关:取消注释额外输出 DBG_DEBUG 级别
|
||||
|
||||
@@ -952,6 +952,11 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\CH395F\ch395f.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>ch395f_test.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\CH395F\ch395f_test.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>fal_flash_gd5f2gq5ue.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
|
||||
@@ -1,4 +1,50 @@
|
||||
*** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin'
|
||||
Build target 'STM32F407-Demo'
|
||||
Rebuild target 'STM32F407-Demo'
|
||||
assembling startup_stm32f407xx.s...
|
||||
compiling dma.c...
|
||||
compiling stm32f4xx_it.c...
|
||||
compiling gpio.c...
|
||||
compiling stm32f4xx_hal_flash_ramfunc.c...
|
||||
compiling i2c.c...
|
||||
compiling stm32f4xx_hal_msp.c...
|
||||
compiling stm32f4xx_hal_flash_ex.c...
|
||||
compiling stm32f4xx_hal_exti.c...
|
||||
compiling spi.c...
|
||||
compiling stm32f4xx_hal_pwr_ex.c...
|
||||
compiling stm32f4xx_hal_i2c_ex.c...
|
||||
compiling stm32f4xx_hal_cortex.c...
|
||||
compiling stm32f4xx_hal_dma.c...
|
||||
compiling stm32f4xx_hal_gpio.c...
|
||||
compiling main.c...
|
||||
compiling stm32f4xx_hal.c...
|
||||
compiling stm32f4xx_hal_pwr.c...
|
||||
compiling stm32f4xx_hal_rcc_ex.c...
|
||||
compiling usart.c...
|
||||
compiling stm32f4xx_hal_flash.c...
|
||||
compiling stm32f4xx_hal_rcc.c...
|
||||
compiling stm32f4xx_hal_dma_ex.c...
|
||||
compiling stm32f4xx_hal_i2c.c...
|
||||
compiling fdb.c...
|
||||
compiling fal.c...
|
||||
compiling fdb_utils.c...
|
||||
compiling fal_flash.c...
|
||||
compiling fal_partition.c...
|
||||
compiling fdb_tsdb.c...
|
||||
compiling fdb_kvdb.c...
|
||||
compiling system_stm32f4xx.c...
|
||||
compiling stm32f4xx_hal_spi.c...
|
||||
compiling fal_flash_gd5f2gq5ue.c...
|
||||
compiling ch395f.c...
|
||||
compiling ch395f_test.c...
|
||||
compiling stm32f4xx_hal_uart.c...
|
||||
compiling tpafe5160.c...
|
||||
compiling rs485.c...
|
||||
compiling sd2506.c...
|
||||
compiling gd5f2gq5ue.c...
|
||||
compiling net_select.c...
|
||||
compiling net_socket.c...
|
||||
linking...
|
||||
Program Size: Code=19580 RO-data=544 RW-data=40 ZI-data=2840
|
||||
FromELF: creating hex file...
|
||||
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
|
||||
Build Time Elapsed: 00:00:01
|
||||
Build Time Elapsed: 00:00:12
|
||||
|
||||
121
Src/main.c
121
Src/main.c
@@ -35,6 +35,7 @@
|
||||
#include "rs485.h"
|
||||
#include "net_socket.h"
|
||||
#include "net_select.h"
|
||||
#include "ch395f_test.h"
|
||||
/* USER CODE END Includes */
|
||||
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -49,10 +50,8 @@
|
||||
|
||||
/* Private macro -------------------------------------------------------------*/
|
||||
/* USER CODE BEGIN PM */
|
||||
#define TS() do { \
|
||||
uint32_t _t = HAL_GetTick(); \
|
||||
printf("[%lu.%03lu] ", _t / 1000UL, _t % 1000UL); \
|
||||
} while(0)
|
||||
#define DBG_TAG "[MAIN]"
|
||||
#include "dbg_log.h"
|
||||
/* USER CODE END PM */
|
||||
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
@@ -64,7 +63,6 @@ rs485_handle_t g_rs485;
|
||||
uint8_t g_rs485_rx_buf[RS485_RX_BUF_SIZE];
|
||||
/* TCP Server Echo 测试 */
|
||||
static int s_listen_sock = -1;
|
||||
static uint8_t s_echo_buf[1500];
|
||||
/* USER CODE END PV */
|
||||
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
@@ -119,65 +117,59 @@ int main(void)
|
||||
HAL_Delay(100);
|
||||
|
||||
/* BSD Socket API 初始化(包含 CH395F 复位/配置/协议栈初始化/多连接模式) */
|
||||
printf("[NET] init: %s\r\n",
|
||||
DBG_INFO("net init: %s",
|
||||
(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1") == 0) ? "OK" : "FAIL");
|
||||
|
||||
/* 读取 MAC */
|
||||
uint8_t mac[6];
|
||||
ch395f_get_mac_addr(mac);
|
||||
printf("[CH395F] MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
|
||||
DBG_INFO("MAC: %02X:%02X:%02X:%02X:%02X:%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
/* 强制 100M 全双工,建立 PHY 链路 */
|
||||
ch395f_set_phy(CH395F_PHY_100M_FULL);
|
||||
HAL_Delay(3000);
|
||||
uint8_t phy = ch395f_get_phy_status();
|
||||
const char *phy_str = "UNKNOWN";
|
||||
switch (phy) {
|
||||
case CH395F_PHY_DISCONN: phy_str = "DISCONNECT"; break;
|
||||
case CH395F_PHY_10M_FULL: phy_str = "10M FULL"; break;
|
||||
case CH395F_PHY_10M_HALF: phy_str = "10M HALF"; break;
|
||||
case CH395F_PHY_100M_FULL: phy_str = "100M FULL"; break;
|
||||
case CH395F_PHY_100M_HALF: phy_str = "100M HALF"; break;
|
||||
}
|
||||
printf("[CH395F] PHY: %s\r\n", phy_str);
|
||||
DBG_INFO("PHY: %s",
|
||||
(phy == CH395F_PHY_100M_FULL) ? "100M FULL" :
|
||||
(phy == CH395F_PHY_100M_HALF) ? "100M HALF" :
|
||||
(phy == CH395F_PHY_10M_FULL) ? "10M FULL" :
|
||||
(phy == CH395F_PHY_10M_HALF) ? "10M HALF" :
|
||||
(phy == CH395F_PHY_DISCONN) ? "DISCONNECT" : "UNKNOWN");
|
||||
|
||||
/* GD5F 初始化测试 */
|
||||
int ret = gd5f2gq5ue_init();
|
||||
const char *gd5f_str = "UNKNOWN";
|
||||
switch (ret) {
|
||||
case GD5F_OK: gd5f_str = "OK"; break;
|
||||
case GD5F_ERROR: gd5f_str = "ERROR"; break;
|
||||
case GD5F_BUSY_TIMEOUT: gd5f_str = "BUSY TIMEOUT"; break;
|
||||
case GD5F_ECC_ERROR: gd5f_str = "ECC ERROR"; break;
|
||||
case GD5F_PROGRAM_FAIL: gd5f_str = "PROGRAM FAIL"; break;
|
||||
case GD5F_ERASE_FAIL: gd5f_str = "ERASE FAIL"; break;
|
||||
case GD5F_ID_MISMATCH: gd5f_str = "ID MISMATCH"; break;
|
||||
}
|
||||
printf("[GD5F] init: %s\r\n", gd5f_str);
|
||||
DBG_INFO("NAND init: %s",
|
||||
(ret == GD5F_OK) ? "OK" :
|
||||
(ret == GD5F_ERROR) ? "ERROR" :
|
||||
(ret == GD5F_BUSY_TIMEOUT) ? "BUSY TIMEOUT" :
|
||||
(ret == GD5F_ECC_ERROR) ? "ECC ERROR" :
|
||||
(ret == GD5F_PROGRAM_FAIL) ? "PROGRAM FAIL" :
|
||||
(ret == GD5F_ERASE_FAIL) ? "ERASE FAIL" :
|
||||
(ret == GD5F_ID_MISMATCH) ? "ID MISMATCH" : "UNKNOWN");
|
||||
|
||||
/* TPAFE5160 ADC 测试 */
|
||||
int tp_ret = tpafe5160_init();
|
||||
printf("[TPAFE5160] init: %s\r\n",
|
||||
DBG_INFO("ADC init: %s",
|
||||
(tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT");
|
||||
|
||||
/* SD2506 RTC 初始化测试 */
|
||||
int sd_ret = sd2506_init();
|
||||
printf("[SD2506] init: %s\r\n",
|
||||
DBG_INFO("SD2506 init: %s",
|
||||
(sd_ret == SD2506_OK) ? "OK" : "I2C ERROR");
|
||||
|
||||
if (sd_ret == SD2506_OK) {
|
||||
/* 读取 ID */
|
||||
uint8_t sd_id[8];
|
||||
sd2506_get_id(sd_id);
|
||||
printf("[SD2506] ID: %02X%02X%02X%02X%02X%02X%02X%02X\r\n",
|
||||
DBG_INFO("SD2506 ID: %02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
sd_id[0], sd_id[1], sd_id[2], sd_id[3],
|
||||
sd_id[4], sd_id[5], sd_id[6], sd_id[7]);
|
||||
|
||||
/* 读取当前时间 */
|
||||
sd2506_time_t sd_time;
|
||||
sd2506_get_time(&sd_time);
|
||||
printf("[SD2506] time: %04d-%02d-%02d %02d:%02d:%02d (week %d)\r\n",
|
||||
DBG_INFO("SD2506 time: %04d-%02d-%02d %02d:%02d:%02d (week %d)",
|
||||
sd_time.year, sd_time.month, sd_time.day,
|
||||
sd_time.hour, sd_time.minute, sd_time.second,
|
||||
sd_time.week);
|
||||
@@ -185,18 +177,18 @@ int main(void)
|
||||
/* 读取温度 */
|
||||
int8_t sd_temp;
|
||||
sd2506_get_temperature(&sd_temp);
|
||||
printf("[SD2506] temp: %d C\r\n", sd_temp);
|
||||
DBG_INFO("SD2506 temp: %d C", sd_temp);
|
||||
|
||||
/* 读取电池电压 */
|
||||
uint16_t sd_vbat;
|
||||
sd2506_get_battery_voltage(&sd_vbat);
|
||||
printf("[SD2506] battery: %d.%02d V\r\n",
|
||||
DBG_INFO("SD2506 battery: %d.%02d V",
|
||||
sd_vbat / 1000, (sd_vbat % 1000) / 10);
|
||||
}
|
||||
|
||||
/* RS-485 初始化(UART5 + PD0 方向控制) */
|
||||
rs485_init(&g_rs485, &huart5, ST_DIR4_GPIO_Port, ST_DIR4_Pin);
|
||||
printf("[RS485] init: OK (UART5, DE=PD0)\r\n");
|
||||
DBG_INFO("RS485 init: OK (UART5, DE=PD0)");
|
||||
|
||||
/* 启动第一次 RS-485 接收 */
|
||||
rs485_receive_start(&g_rs485, g_rs485_rx_buf, RS485_RX_BUF_SIZE);
|
||||
@@ -204,7 +196,7 @@ int main(void)
|
||||
/* 创建 TCP Server Echo 测试 */
|
||||
{
|
||||
s_listen_sock = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
|
||||
printf("[NET] TCP listen socket: %d\r\n", s_listen_sock);
|
||||
DBG_INFO("TCP listen socket: %d", s_listen_sock);
|
||||
|
||||
struct net_sockaddr_in serv_addr;
|
||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||
@@ -213,20 +205,37 @@ int main(void)
|
||||
serv_addr.sin_addr.s_addr = NET_INADDR_ANY;
|
||||
|
||||
if (net_bind(s_listen_sock, (struct net_sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) {
|
||||
printf("[NET] TCP bind: OK (port 8080)\r\n");
|
||||
DBG_INFO("TCP bind: OK (port 8080)");
|
||||
} else {
|
||||
printf("[NET] TCP bind: FAIL\r\n");
|
||||
DBG_ERROR("TCP bind: FAIL");
|
||||
}
|
||||
|
||||
if (net_listen(s_listen_sock, NET_TCP_SERVER_MAX_CLIENTS) == 0) {
|
||||
printf("[NET] TCP listen: OK (max_clients=%d)\r\n", NET_TCP_SERVER_MAX_CLIENTS);
|
||||
DBG_INFO("TCP listen: OK (max_clients=%d)", NET_TCP_SERVER_MAX_CLIENTS);
|
||||
} else {
|
||||
printf("[NET] TCP listen: FAIL\r\n");
|
||||
DBG_ERROR("TCP listen: FAIL");
|
||||
}
|
||||
}
|
||||
|
||||
/* 启动第一次转换(CubeMX 已配置 BUSY EXTI 中断) */
|
||||
tpafe5160_start_conv_irq();
|
||||
|
||||
/* CH395F 驱动测试 — 各阶段独立运行,注释 define 可跳过 */
|
||||
#ifdef ENABLE_PHASE1_TESTS
|
||||
ch395f_phase1_tests();
|
||||
#endif
|
||||
#ifdef ENABLE_PHASE3_TESTS
|
||||
ch395f_phase3_tests();
|
||||
#endif
|
||||
#ifdef ENABLE_PHASE2_TESTS
|
||||
ch395f_phase2_tests();
|
||||
#endif
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
ch395f_phase5_init();
|
||||
#endif
|
||||
#ifdef ENABLE_PHASE6_TESTS
|
||||
ch395f_phase6_tests();
|
||||
#endif
|
||||
/* USER CODE END 2 */
|
||||
|
||||
/* Infinite loop */
|
||||
@@ -238,28 +247,17 @@ int main(void)
|
||||
/* USER CODE BEGIN 3 */
|
||||
/* 网络轮询(必须在主循环中调用) */
|
||||
net_poll();
|
||||
|
||||
/* TCP Server Echo 处理 —— 多连接模式,socket 0 监听,socket 1~7 收发 */
|
||||
{
|
||||
int i;
|
||||
for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++)
|
||||
{
|
||||
net_sock_t *sk = net_get_sock(i);
|
||||
if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t rlen = ch395f_get_recv_len((uint8_t)i);
|
||||
if (rlen > 0) {
|
||||
uint16_t read_len = (rlen > sizeof(s_echo_buf)) ? sizeof(s_echo_buf) : rlen;
|
||||
ch395f_read_recv_buf((uint8_t)i, s_echo_buf, read_len);
|
||||
TS(); printf("[NET] recv sock%d: %d bytes\r\n", i, read_len);
|
||||
ch395f_write_send_buf((uint8_t)i, s_echo_buf, read_len);
|
||||
TS(); printf("[NET] send sock%d: %d bytes\r\n", i, read_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_PHASE4_TESTS
|
||||
/* Phase 4: NET 层 TCP Echo */
|
||||
ch395f_phase4_tests();
|
||||
#endif
|
||||
/* Phase 5: NET 层 UDP Echo */
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
ch395f_phase5_tests();
|
||||
#endif
|
||||
#ifdef ENABLE_PHASE7_TESTS
|
||||
ch395f_phase7_tests();
|
||||
#endif
|
||||
|
||||
// /* TPAFE5160 中断模式读取 */
|
||||
// if (tpafe5160_data_ready()) {
|
||||
@@ -327,6 +325,7 @@ void SystemClock_Config(void)
|
||||
}
|
||||
|
||||
/* USER CODE BEGIN 4 */
|
||||
|
||||
/*
|
||||
* HAL UART 扩展接收回调(IDLE 空闲帧中断触发)
|
||||
* 当 RS-485 总线空闲超过 1 个字符时间后触发
|
||||
|
||||
225
docs/CH395F_Test_Guide.md
Normal file
225
docs/CH395F_Test_Guide.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# CH395F 测试使用指南
|
||||
|
||||
## 目录
|
||||
|
||||
1. [快速开始](#1-快速开始)
|
||||
2. [各阶段测试](#2-各阶段测试)
|
||||
- [Phase 1: 寄存器测试](#phase-1-寄存器测试)
|
||||
- [Phase 2: TCP Client 收发+重连](#phase-2-tcp-client)
|
||||
- [Phase 3: UDP Server Echo](#phase-3-udp-server-echo)
|
||||
- [Phase 4: NET 层 TCP Echo](#phase-4-net-层-tcp-echo)
|
||||
- [Phase 5: NET 层 UDP Echo](#phase-5-net-层-udp-echo)
|
||||
- [Phase 6: DHCP](#phase-6-dhcp)
|
||||
- [Phase 7: Select/Poll](#phase-7-selectpoll)
|
||||
- [Phase 8: 多客户端并发](#phase-8-多客户端并发)
|
||||
3. [Python 脚本参考](#3-python-脚本参考)
|
||||
4. [常见问题](#4-常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 1. 快速开始
|
||||
|
||||
### 启用/禁用测试
|
||||
|
||||
编辑 `Drivers/BSP/CH395F/ch395f_test.h`,取消注释想要测试的阶段:
|
||||
|
||||
```c
|
||||
//#define ENABLE_PHASE1_TESTS ← 取消注释即启用
|
||||
#define ENABLE_PHASE2_TESTS ← 已启用
|
||||
//#define ENABLE_PHASE3_TESTS ← 注释掉跳过
|
||||
```
|
||||
|
||||
### 测试拓扑
|
||||
|
||||
```
|
||||
PC (192.168.1.2) ──── 交换机 ──── CH395F (192.168.1.100)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 各阶段测试
|
||||
|
||||
### Phase 1: 寄存器测试
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 独立运行,无需 PC 配合 |
|
||||
| 耗时 | ~1 秒 |
|
||||
| 测试项 | 芯片版本、命令状态、IP/网关/掩码、全局中断一致性、Socket 中断、ARP、TTL |
|
||||
|
||||
**操作**:
|
||||
```
|
||||
1. 只启用 ENABLE_PHASE1_TESTS
|
||||
2. 编译烧录
|
||||
3. 看串口输出
|
||||
```
|
||||
|
||||
**通过标准**:串口打印 `=== Phase 1 Tests Complete ===`,中间无 ERROR。
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: TCP Client
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 PC 配合 |
|
||||
| 耗时 | ~60 秒 |
|
||||
| 测试项 | TCP Client 连接、收发 64 字节、PING echo、大包 echo、关闭重连 ×3 轮 |
|
||||
| 默认端口 | CH395F → PC:8081 |
|
||||
|
||||
**操作**:
|
||||
```
|
||||
终端 1: python test/ch395f_socket_test.py tcp_server --port 8081 --timeout 180
|
||||
终端 2: 编译烧录(只启用 ENABLE_PHASE2_TESTS)
|
||||
```
|
||||
|
||||
**通过标准**:串口显示 3 轮 `closed: sock=0x00`,PC 显示 3 次连接且 PING echo 匹配。
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: UDP Server Echo
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 PC 配合 |
|
||||
| 耗时 | ~30 秒 |
|
||||
| 测试项 | UDP Server 模式、8 字节头解析、PING echo ×5、大包 echo ×10 |
|
||||
| 默认端口 | CH395F:60000,PC 发到 60000 |
|
||||
|
||||
**操作**:
|
||||
```
|
||||
终端 1: 编译烧录(只启用 ENABLE_PHASE3_TESTS),看到 "UDP socket 7 opened"
|
||||
终端 2: python test/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000 --timeout 30
|
||||
```
|
||||
|
||||
**通过标准**:PC 显示 PING 5/5 + 大包 10/10。
|
||||
|
||||
> **关键发现**:CH395F UDP 分两种模式——DesIP=`255.255.255.255` 是 Server 模式(接受任意来源),否则只能收指定 IP:Port。每次 `write_send_buf` 后必须等 `SINT_STAT_SENDBUF_FREE`。
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: NET 层 TCP Echo
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 PC 配合,主循环运行 |
|
||||
| 耗时 | 按需 |
|
||||
| 测试项 | `net_recv()` + `net_send()` 非阻塞 echo |
|
||||
| 默认端口 | CH395F TCP Server:8080,PC 连到 8080 |
|
||||
|
||||
**操作**:
|
||||
```
|
||||
终端 1: 编译烧录(只启用 ENABLE_PHASE4_TESTS),等主循环启动
|
||||
终端 2: python test/ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080
|
||||
```
|
||||
|
||||
**通过标准**:10/10 回显匹配 + 大数据传输完整。
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: NET 层 UDP Echo
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 PC 配合,主循环运行 |
|
||||
| 耗时 | 按需 |
|
||||
| 测试项 | `net_recvfrom()` + `net_sendto()` 非阻塞 echo |
|
||||
| 默认端口 | CH395F UDP:60000 |
|
||||
|
||||
**操作**:
|
||||
```
|
||||
终端 1: 编译烧录(只启用 ENABLE_PHASE5_TESTS),等主循环启动
|
||||
终端 2: python test/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000 --timeout 30
|
||||
```
|
||||
|
||||
**通过标准**:PING 5/5 + 大包 10/10。
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: DHCP
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 DHCP Server |
|
||||
| 耗时 | ~10 秒 |
|
||||
| 测试项 | 启用 DHCP、轮询状态、打印分配的 IP |
|
||||
|
||||
> ⚠️ **DHCP 会改变 CH395F IP!** 必须独立运行,不能与其他 Phase 同时启用。
|
||||
|
||||
**操作**:
|
||||
```
|
||||
终端 1: python test/dhcp_server.py ← 启动 DHCP Server(需管理员权限)
|
||||
终端 2: 编译烧录(只启用 ENABLE_PHASE6_TESTS)
|
||||
```
|
||||
|
||||
**通过标准**:PC 显示 `DHCP 分配完成!`,串口显示 IP/网关/掩码。
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: Select/Poll
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 PC 配合,主循环运行 |
|
||||
| 耗时 | 按需 |
|
||||
| 测试项 | `net_select()` 多路复用检测可读 socket → echo |
|
||||
| 默认端口 | CH395F TCP Server:8080 |
|
||||
|
||||
**操作**:同 Phase 4(TCP Client 连 8080)。
|
||||
|
||||
**通过标准**:同 Phase 4(10/10 回显匹配)。
|
||||
|
||||
> Phase 4 和 Phase 7 功能相同但实现不同,通常只启用一个。Phase 7 额外验证了 `NET_FD_SET/ISSET` 宏和 `net_select()`。
|
||||
|
||||
---
|
||||
|
||||
### Phase 8: 多客户端并发
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 类型 | 需 PC 配合,主循环运行 |
|
||||
| 耗时 | ~2 秒 |
|
||||
| 测试项 | 7 个 TCP 客户端并发,每客户端 10 轮 PING echo |
|
||||
| 默认端口 | CH395F TCP Server:8080 |
|
||||
|
||||
**操作**:
|
||||
```
|
||||
终端 1: 编译烧录(启用 ENABLE_PHASE4_TESTS 或 ENABLE_PHASE7_TESTS)
|
||||
终端 2: python test/ch395f_socket_test.py stress --ip 192.168.1.100 --port 8080 --count 10 --timeout 30
|
||||
```
|
||||
|
||||
**通过标准**:7 并发 × 10 轮 = 70/70 成功率 100%。
|
||||
|
||||
---
|
||||
|
||||
## 3. Python 脚本参考
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `tcp_server --port 8081` | PC 作 TCP Server,测试 CH395F TCP Client |
|
||||
| `tcp_client --ip x.x.x.x --port 8080` | PC 作 TCP Client,测试 CH395F TCP Server echo |
|
||||
| `udp_client --ip x.x.x.x --port 60000 --timeout 30` | PC 发 UDP PING,测试 CH395F UDP echo |
|
||||
| `udp_server --port 8082 --timeout 60` | PC 作 UDP Server,接收 CH395F 数据 |
|
||||
| `stress --ip x.x.x.x --port 8080 --count 10` | 7 并发压力测试 |
|
||||
| `dhcp_server.py` | 轻量 DHCP Server(管理员权限) |
|
||||
|
||||
通用参数:
|
||||
- `--ip`:目标 IP(默认 192.168.1.100)
|
||||
- `--port`:端口(各模式默认不同)
|
||||
- `--count`:测试次数(默认 10)
|
||||
- `--timeout`:超时秒数(默认 30)
|
||||
- `--size`:数据包大小(默认 1024)
|
||||
|
||||
---
|
||||
|
||||
## 4. 常见问题
|
||||
|
||||
| 问题 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| Phase 2 `open_socket: 0x20 (FAIL)` | socket 未完全关闭 | 已修复:先 close(RST)不等 disconnect |
|
||||
| Phase 2 收到 `\x00(\x00\x00...` 乱码 | socket 4-7 默认无发送缓冲 | `set_send_buf(sock, 28, 2)` 分配 |
|
||||
| Phase 3 UDP 收不到数据 | DesIP 设为具体 IP 而非 255.255.255.255 | 设 DesIP=`0xFFFFFFFF` 进入 Server 模式 |
|
||||
| Phase 3 HELLO 收到但 PING 收不到 | 没等 `SENDBUF_FREE` 就第二次写 | 每次 `write_send_buf` 后轮询 `SINT_STAT_SENDBUF_FREE` |
|
||||
| Python 报 `WinError 10054` | STM32 还没启动 | 等串口看到阶段启动信息再跑 Python |
|
||||
| Phase 5 UDP 不工作 | NET 层 `net_bind()` 没打开 CH395F Socket | 已修复:`net_bind()` 对 DGRAM 类型自动打开 |
|
||||
| `net_recvfrom()` 源地址错误 | UDP 头解析偏移错了 | 已修复:端口读 `header[2-3]`,IP 读 `header[4-7]` |
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
CH395F BSD Socket API 测试程序
|
||||
用于测试 STM32F4 + CH395F 的网络功能
|
||||
@@ -105,73 +106,77 @@ def tcp_server_test(args):
|
||||
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.settimeout(30)
|
||||
server_socket.settimeout(args.timeout)
|
||||
|
||||
try:
|
||||
server_socket.bind(('0.0.0.0', args.port))
|
||||
server_socket.listen(1)
|
||||
log_success(f"服务器启动,监听端口 {args.port}")
|
||||
log_info("等待 CH395F TCP Client 连接...")
|
||||
|
||||
for conn_idx in range(3):
|
||||
try:
|
||||
server_socket.settimeout(args.timeout)
|
||||
log_info(f"等待第 {conn_idx+1} 个连接(超时 {args.timeout}s)...")
|
||||
client_socket, client_addr = server_socket.accept()
|
||||
log_success(f"连接来自: {client_addr[0]}:{client_addr[1]}")
|
||||
log_success(f"连接#{conn_idx+1} 来自: {client_addr[0]}:{client_addr[1]}")
|
||||
client_socket.settimeout(5)
|
||||
|
||||
# 测试接收数据
|
||||
# Test 1: 接收数据
|
||||
log_info("-" * 40)
|
||||
log_info("测试 1: 接收数据")
|
||||
for i in range(args.count):
|
||||
log_info(f"[连接{conn_idx+1}] 测试 1: 接收数据")
|
||||
try:
|
||||
data = client_socket.recv(args.size)
|
||||
if data:
|
||||
log_success(f"收到数据 ({len(data)} bytes): {data[:50]}...")
|
||||
else:
|
||||
log_error("收到空数据")
|
||||
break
|
||||
except socket.timeout:
|
||||
log_error("接收超时")
|
||||
|
||||
# Test 2: PING echo
|
||||
log_info(f"[连接{conn_idx+1}] 测试 2: 双向通信 (PING)")
|
||||
ping_ok = 0
|
||||
for i in range(5):
|
||||
try:
|
||||
test_data = f"PING_{i}".encode()
|
||||
client_socket.send(test_data)
|
||||
data = client_socket.recv(args.size)
|
||||
if data and data == test_data:
|
||||
log_success(f"回显匹配: {test_data.decode()}")
|
||||
ping_ok += 1
|
||||
elif data:
|
||||
log_error(f"回显不匹配! 收到: {data[:20]}")
|
||||
break
|
||||
except socket.timeout:
|
||||
log_error("通信超时")
|
||||
break
|
||||
except Exception as e:
|
||||
log_error(f"接收错误: {e}")
|
||||
log_error(f"通信错误: {e}")
|
||||
break
|
||||
log_info(f"PING 匹配: {ping_ok}/5")
|
||||
|
||||
# 测试发送数据
|
||||
log_info("-" * 40)
|
||||
log_info("测试 2: 发送数据")
|
||||
# Test 3: 大包发送
|
||||
log_info(f"[连接{conn_idx+1}] 测试 3: 大包发送")
|
||||
for i in range(args.count):
|
||||
try:
|
||||
test_data = generate_data(min(100, args.size))
|
||||
client_socket.send(test_data)
|
||||
log_success(f"发送数据 ({len(test_data)} bytes)")
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
log_error(f"发送错误: {e}")
|
||||
break
|
||||
|
||||
# 测试双向通信
|
||||
log_info("-" * 40)
|
||||
log_info("测试 3: 双向通信")
|
||||
for i in range(min(5, args.count)):
|
||||
try:
|
||||
# 发送
|
||||
test_data = f"PING_{i}".encode()
|
||||
client_socket.send(test_data)
|
||||
log_info(f"发送: {test_data.decode()}")
|
||||
|
||||
# 接收回显
|
||||
data = client_socket.recv(args.size)
|
||||
if data:
|
||||
log_info(f"收到: {data.decode()}")
|
||||
if data == test_data:
|
||||
log_success("回显数据匹配!")
|
||||
else:
|
||||
log_error("回显数据不匹配!")
|
||||
except Exception as e:
|
||||
log_error(f"通信错误: {e}")
|
||||
break
|
||||
log_success(f"发送 {args.count} 包完成")
|
||||
|
||||
client_socket.close()
|
||||
log_success("测试完成,连接已关闭")
|
||||
log_success(f"连接#{conn_idx+1} 测试完成")
|
||||
time.sleep(0.5)
|
||||
|
||||
except socket.timeout:
|
||||
log_error(f"等待连接#{conn_idx+1} 超时")
|
||||
break
|
||||
except Exception as e:
|
||||
log_error(f"连接#{conn_idx+1} 错误: {e}")
|
||||
break
|
||||
|
||||
log_success("全部连接测试完成")
|
||||
|
||||
except socket.timeout:
|
||||
log_error("等待连接超时")
|
||||
@@ -329,66 +334,83 @@ def udp_server_test(args):
|
||||
|
||||
|
||||
def udp_client_test(args):
|
||||
"""UDP 客户端测试(测试 CH395F UDP Server)"""
|
||||
"""UDP Echo 测试(测试 CH395F UDP Server)"""
|
||||
log_info("=" * 60)
|
||||
log_info("UDP 客户端测试 - 测试 CH395F UDP Server 功能")
|
||||
log_info("UDP Echo 测试")
|
||||
log_info("=" * 60)
|
||||
|
||||
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
client_socket.settimeout(5)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
dest = (args.ip, args.port)
|
||||
|
||||
try:
|
||||
dest_addr = (args.ip, args.port)
|
||||
|
||||
# 发送数据
|
||||
log_info("-" * 40)
|
||||
log_info("测试 1: 发送 UDP 数据")
|
||||
for i in range(args.count):
|
||||
# Phase A: HELLO 探针等待 STM32 就绪
|
||||
log_info(f"Phase A: 等待 CH395F 就绪(超时 {args.timeout}s)")
|
||||
sock.settimeout(2)
|
||||
t0 = time.time()
|
||||
ready = False
|
||||
while time.time() - t0 < args.timeout:
|
||||
try:
|
||||
test_data = f"UDP_MSG_{i:04d}".encode()
|
||||
client_socket.sendto(test_data, dest_addr)
|
||||
log_success(f"发送数据到 {args.ip}:{args.port} ({len(test_data)} bytes)")
|
||||
|
||||
# 等待回显
|
||||
data, addr = client_socket.recvfrom(args.size)
|
||||
if data:
|
||||
if data == test_data:
|
||||
log_success(f"回显数据匹配! (from {addr[0]}:{addr[1]})")
|
||||
else:
|
||||
log_error("回显数据不匹配!")
|
||||
else:
|
||||
log_error("收到空数据")
|
||||
sock.sendto(b"HELLO", dest)
|
||||
data, addr = sock.recvfrom(64)
|
||||
if data == b"HELLO":
|
||||
log_success(f"就绪 ({time.time()-t0:.1f}s, from {addr[0]}:{addr[1]})")
|
||||
ready = True
|
||||
break
|
||||
|
||||
time.sleep(0.2)
|
||||
except socket.timeout:
|
||||
log_warn("接收超时")
|
||||
pass
|
||||
if not ready:
|
||||
log_error(f"未就绪({args.timeout}s 超时)")
|
||||
return
|
||||
|
||||
sock.settimeout(5)
|
||||
|
||||
# Phase B: PING Echo
|
||||
log_info("-" * 40)
|
||||
log_info("Phase B: PING Echo(5 轮)")
|
||||
ok = 0
|
||||
for i in range(5):
|
||||
msg = f"PING_{i}".encode()
|
||||
try:
|
||||
sock.sendto(msg, dest)
|
||||
data, _ = sock.recvfrom(64)
|
||||
if data == msg:
|
||||
ok += 1
|
||||
log_success(f"PING_{i} OK")
|
||||
else:
|
||||
log_error(f"PING_{i} mismatch: {data[:20]}")
|
||||
break
|
||||
except socket.timeout:
|
||||
log_error(f"PING_{i} timeout")
|
||||
break
|
||||
time.sleep(0.02)
|
||||
log_info(f"PING: {ok}/5")
|
||||
|
||||
# Phase C: 大包 Echo
|
||||
log_info("-" * 40)
|
||||
log_info(f"Phase C: 大包 Echo({args.count} 轮)")
|
||||
for i in range(args.count):
|
||||
td = generate_data(min(100, args.size))
|
||||
try:
|
||||
sock.sendto(td, dest)
|
||||
data, _ = sock.recvfrom(args.size)
|
||||
if data == td:
|
||||
log_success(f"#{i+1} OK ({len(data)}B)")
|
||||
else:
|
||||
log_error(f"#{i+1} mismatch")
|
||||
break
|
||||
time.sleep(0.02)
|
||||
except socket.timeout:
|
||||
log_warn(f"#{i+1} timeout")
|
||||
except Exception as e:
|
||||
log_error(f"发送/接收错误: {e}")
|
||||
log_error(f"#{i+1}: {e}")
|
||||
break
|
||||
|
||||
# 测试大数据
|
||||
log_info("-" * 40)
|
||||
log_info("测试 2: 大数据 UDP 传输")
|
||||
try:
|
||||
large_data = generate_data(min(512, args.size))
|
||||
client_socket.sendto(large_data, dest_addr)
|
||||
log_success(f"发送大数据 ({len(large_data)} bytes)")
|
||||
|
||||
data, addr = client_socket.recvfrom(args.size)
|
||||
if data and data == large_data:
|
||||
log_success("大数据传输完整!")
|
||||
else:
|
||||
log_error("大数据传输失败")
|
||||
except Exception as e:
|
||||
log_error(f"大数据传输错误: {e}")
|
||||
|
||||
log_success("测试完成")
|
||||
log_success("Phase 3 完成")
|
||||
|
||||
except Exception as e:
|
||||
log_error(f"客户端错误: {e}")
|
||||
log_error(f"错误: {e}")
|
||||
finally:
|
||||
client_socket.close()
|
||||
sock.close()
|
||||
|
||||
|
||||
# ==================== 回显测试 ====================
|
||||
@@ -401,13 +423,13 @@ def tcp_echo_server_test(args):
|
||||
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.settimeout(60)
|
||||
server_socket.settimeout(args.timeout)
|
||||
|
||||
try:
|
||||
server_socket.bind(('0.0.0.0', args.port))
|
||||
server_socket.listen(1)
|
||||
log_success(f"回显服务器启动,端口 {args.port}")
|
||||
log_info("等待连接...")
|
||||
log_info(f"等待连接(超时 {args.timeout}s)...")
|
||||
|
||||
client_socket, client_addr = server_socket.accept()
|
||||
log_success(f"连接来自: {client_addr[0]}:{client_addr[1]}")
|
||||
@@ -656,6 +678,7 @@ def main():
|
||||
parser.add_argument('--port', type=int, default=8080, help='端口号')
|
||||
parser.add_argument('--count', type=int, default=10, help='测试次数')
|
||||
parser.add_argument('--size', type=int, default=1024, help='数据包大小')
|
||||
parser.add_argument('--timeout', type=int, default=180, help='接受连接超时(秒,默认180)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
156
test/dhcp_server.py
Normal file
156
test/dhcp_server.py
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
轻量 DHCP Server — 用于 CH395F Phase 6 测试
|
||||
用法:以管理员身份运行
|
||||
python dhcp_server.py --iface 192.168.1.2
|
||||
"""
|
||||
import socket
|
||||
import struct
|
||||
import argparse
|
||||
import time
|
||||
|
||||
DHCP_SERVER_PORT = 67
|
||||
DHCP_CLIENT_PORT = 68
|
||||
|
||||
# DHCP 消息类型
|
||||
DHCP_DISCOVER = 1
|
||||
DHCP_OFFER = 2
|
||||
DHCP_REQUEST = 3
|
||||
DHCP_ACK = 5
|
||||
|
||||
# DHCP 选项
|
||||
OPT_MESSAGE_TYPE = 53
|
||||
OPT_SERVER_ID = 54
|
||||
OPT_REQUESTED_IP = 50
|
||||
OPT_SUBNET_MASK = 1
|
||||
OPT_ROUTER = 3
|
||||
OPT_DNS = 6
|
||||
OPT_LEASE_TIME = 51
|
||||
OPT_END = 255
|
||||
|
||||
SERVER_IP = "192.168.1.2"
|
||||
OFFER_IP = "192.168.1.100"
|
||||
SUBNET_MASK = "255.255.255.0"
|
||||
ROUTER = "192.168.1.1"
|
||||
LEASE_TIME = 3600 # 1 小时
|
||||
|
||||
def ip_to_bytes(ip):
|
||||
return bytes(map(int, ip.split('.')))
|
||||
|
||||
def build_dhcp_offer(discover_pkt, server_ip, offer_ip):
|
||||
"""构建 DHCP OFFER 包"""
|
||||
transaction_id = discover_pkt[232:236] # Bootp header 前 4 字节
|
||||
client_mac = discover_pkt[28:34]
|
||||
|
||||
# BOOTP 头
|
||||
op = b'\x02' # Boot reply
|
||||
htype = b'\x01' # Ethernet
|
||||
hlen = b'\x06' # MAC len
|
||||
hops = b'\x00'
|
||||
xid = transaction_id
|
||||
secs = b'\x00\x00'
|
||||
flags = b'\x00\x00'
|
||||
ciaddr = b'\x00\x00\x00\x00'
|
||||
yiaddr = ip_to_bytes(offer_ip) # Your IP
|
||||
siaddr = ip_to_bytes(server_ip) # Server IP
|
||||
giaddr = b'\x00\x00\x00\x00'
|
||||
chaddr = client_mac + b'\x00' * 10 # Client MAC (padded to 16)
|
||||
sname = b'\x00' * 64
|
||||
file = b'\x00' * 128
|
||||
magic = b'\x63\x82\x53\x63' # DHCP magic cookie
|
||||
|
||||
# DHCP 选项
|
||||
options = b''
|
||||
options += struct.pack('BBB', OPT_MESSAGE_TYPE, 1, DHCP_OFFER)
|
||||
options += struct.pack('BB', OPT_SERVER_ID, 4) + ip_to_bytes(server_ip)
|
||||
options += struct.pack('BB', OPT_REQUESTED_IP, 4) + ip_to_bytes(offer_ip)
|
||||
options += struct.pack('BB', OPT_SUBNET_MASK, 4) + ip_to_bytes(SUBNET_MASK)
|
||||
options += struct.pack('BB', OPT_ROUTER, 4) + ip_to_bytes(ROUTER)
|
||||
options += struct.pack('BB', OPT_DNS, 4) + ip_to_bytes(ROUTER)
|
||||
options += bytes([OPT_LEASE_TIME, 4]) + struct.pack('!I', LEASE_TIME)
|
||||
options += bytes([OPT_END])
|
||||
|
||||
pkt = op + htype + hlen + hops + xid + secs + flags + ciaddr + yiaddr + siaddr + giaddr + chaddr + sname + file + magic + options
|
||||
return pkt
|
||||
|
||||
def build_dhcp_ack(request_pkt, server_ip, offer_ip):
|
||||
"""构建 DHCP ACK 包"""
|
||||
transaction_id = request_pkt[232:236]
|
||||
client_mac = request_pkt[28:34]
|
||||
|
||||
op = b'\x02'
|
||||
htype = b'\x01'
|
||||
hlen = b'\x06'
|
||||
hops = b'\x00'
|
||||
xid = transaction_id
|
||||
secs = b'\x00\x00'
|
||||
flags = b'\x00\x00'
|
||||
ciaddr = b'\x00\x00\x00\x00'
|
||||
yiaddr = ip_to_bytes(offer_ip)
|
||||
siaddr = ip_to_bytes(server_ip)
|
||||
giaddr = b'\x00\x00\x00\x00'
|
||||
chaddr = client_mac + b'\x00' * 10
|
||||
sname = b'\x00' * 64
|
||||
file = b'\x00' * 128
|
||||
magic = b'\x63\x82\x53\x63'
|
||||
|
||||
options = b''
|
||||
options += struct.pack('BBB', OPT_MESSAGE_TYPE, 1, DHCP_ACK)
|
||||
options += struct.pack('BB', OPT_SERVER_ID, 4) + ip_to_bytes(server_ip)
|
||||
options += struct.pack('BB', OPT_SUBNET_MASK, 4) + ip_to_bytes(SUBNET_MASK)
|
||||
options += struct.pack('BB', OPT_ROUTER, 4) + ip_to_bytes(ROUTER)
|
||||
options += struct.pack('BB', OPT_DNS, 4) + ip_to_bytes(ROUTER)
|
||||
options += bytes([OPT_LEASE_TIME, 4]) + struct.pack('!I', LEASE_TIME)
|
||||
options += bytes([OPT_END])
|
||||
|
||||
return op + htype + hlen + hops + xid + secs + flags + ciaddr + yiaddr + siaddr + giaddr + chaddr + sname + file + magic + options
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="轻量 DHCP Server for CH395F 测试")
|
||||
parser.add_argument("--iface", default=SERVER_IP, help="网卡 IP 地址")
|
||||
args = parser.parse_args()
|
||||
|
||||
offer_ip = OFFER_IP
|
||||
server_ip = args.iface
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
sock.bind(('0.0.0.0', DHCP_SERVER_PORT))
|
||||
sock.settimeout(60)
|
||||
|
||||
print(f"DHCP Server 启动, 网卡 IP={server_ip}, 分配 IP={offer_ip}")
|
||||
print("等待 CH395F DHCP DISCOVER...")
|
||||
|
||||
try:
|
||||
# 第一阶段:DISCOVER → OFFER
|
||||
data, addr = sock.recvfrom(4096)
|
||||
msg_type = data[232 + 4 + 1] if len(data) > 250 else 0
|
||||
client_mac = ':'.join(f'{b:02X}' for b in data[28:34])
|
||||
print(f"收到 DHCP DISCOVER from {client_mac}")
|
||||
|
||||
offer = build_dhcp_offer(data, server_ip, offer_ip)
|
||||
sock.sendto(offer, ('255.255.255.255', DHCP_CLIENT_PORT))
|
||||
print("发送 DHCP OFFER")
|
||||
|
||||
# 第二阶段:REQUEST → ACK
|
||||
sock.settimeout(10)
|
||||
try:
|
||||
data, addr = sock.recvfrom(4096)
|
||||
client_mac = ':'.join(f'{b:02X}' for b in data[28:34])
|
||||
print(f"收到 DHCP REQUEST from {client_mac}")
|
||||
|
||||
ack = build_dhcp_ack(data, server_ip, offer_ip)
|
||||
sock.sendto(ack, ('255.255.255.255', DHCP_CLIENT_PORT))
|
||||
print(f"发送 DHCP ACK → {offer_ip}")
|
||||
print("DHCP 分配完成!")
|
||||
except socket.timeout:
|
||||
print("等待 DHCP REQUEST 超时")
|
||||
|
||||
except socket.timeout:
|
||||
print("等待 DHCP DISCOVER 超时(60s)")
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user