Files
STM32F4-Base/docs/CH395F_Test_Guide.md

1191 lines
69 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CH395F 网络测试规范
## 1. 概述
本文档定义 CH395F 以太网驱动STM32F407ZGTxSPI2 接口)的测试规范。当前固件运行于**多 Socket 模式**`FUN_PARA=0x09`8 个 Socket 0~7 全部可用Socket 0 作 TCP Server 单连接覆盖单连接与多并发客户端场景。测试按阶段TM-PHY-NN组织每个阶段包含独立测试用例TC-NET-NNN
### 设计原则
| 原则 | 描述 | 验证用例 |
|------|------|----------|
| DP-01 | 单连接 socketSocket 0 唯一复用监听和数据通道CONNECT 后切换数据通道DISCONNECT/TIMEOUT 后重新 OPEN → LISTEN | TC-NET-201~203, 601~606, 701~703, 801, 802, 901, 902, 1001~1006 |
| DP-02 | 中断集中处理:所有 CH395F 中断在 `net_poll()` 中通过 `GET_GLOB_INT_STATUS` 统一读取并清除ISR 及其他位置不处理 | TC-NET-101 |
| DP-03 | 线程安全:所有 CH395F 操作通过 `netMsgQueue` 在 netTask 串行处理 | TC-NET-301~302 |
| DP-04 | DMA 边界安全:`CH395F_SPI_DMA_BUF_SIZE=4100`,单次事务最大载荷 `4096B`,超限自动分块 | TC-NET-1001~1005 |
### CH395F 缓冲架构(关键参数)
| 参数 | 值 | 说明 |
|------|-----|------|
| CH395F 内部总缓冲 | **24KB48 块 × 512B** | 手册 §5.45CH395F.md "内置 24K RAM"。**经 Phase 7 实测确认块 0~47 全部有效**(此前 32×1KB 记录有误) |
| 上电默认分配 | Socket 0~3 各 接收8块(4KB)+发送4块(2KB)Socket 4~7 未分配 | 见 §5.45,用到 >4 个 Socket 必须重分配 |
| **本固件分配net_init** | 8 个 Socket 各 **接收 4 块(2KB) + 发送 2 块(1KB)** | 块数由 `CH395F_TCP_MSS` 推导(接收 `CH395F_RECV_BLOCKS`=2×MSS、发送 `CH395F_SEND_BLOCKS`=1×MSS每块 512B起始块 `recv=i*6``send=i*6+4`;用尽 0~47互不重叠 |
| **TCP MSS** | **1024** | 由接收缓冲(2KB) ≥ 2×MSS 决定8 Socket 共享 24KB 时的最大值 |
| **SPI DMA 缓冲** | **4100B** | `CH395F_SPI_DMA_BUF_SIZE` |
| **单次 SPI 最大载荷** | **4096B** | `CH395F_SPI_DMA_MAX_PAYLOAD` |
| recv_len 最大值 | 65535 | `uint16_t` |
> **关键约束**:本固件每个 Socket 接收缓冲为 **2KB**4 块 × 512B发送缓冲为 **1KB**2 块)。`recv_len` 在调 `read_recv_buf` 读取**部分**数据后归零Trap 13故每次接收应一次性读空当前可用数据≤2KB。`CH395F_SPI_DMA_BUF_SIZE=4100` 足以覆盖单 Socket 完整 2KB 接收缓冲。
### 多 Socket / 8 端口并发分配
`net_init``ch395f_set_fun_para(0x09)` 启用 8 个 Socket 后,循环 0~7 调用 `SET_RECV_BUF` / `SET_SEND_BUF` 重分配(必须在 `OPEN_SOCKET` 之前,顺序遵循 Trap 01先数据 Socket 1~7 再监听 Socket 0
| Socket | 接收起始块 | 接收块数 | 接收大小 | 发送起始块 | 发送块数 | 发送大小 |
|--------|-----------|---------|---------|-----------|---------|---------|
| 0 | 0 | 4 | 2KB | 4 | 2 | 1KB |
| 1 | 6 | 4 | 2KB | 10 | 2 | 1KB |
| 2 | 12 | 4 | 2KB | 16 | 2 | 1KB |
| 3 | 18 | 4 | 2KB | 22 | 2 | 1KB |
| 4 | 24 | 4 | 2KB | 28 | 2 | 1KB |
| 5 | 30 | 4 | 2KB | 34 | 2 | 1KB |
| 6 | 36 | 4 | 2KB | 40 | 2 | 1KB |
| 7 | 42 | 4 | 2KB | 46 | 2 | 1KB |
UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设使用同一布局。该分配已通过 **Phase 7 TC7048 Socket 并发回显40/40 PASS** 验证。
### 大文件传输20KB+)策略
多 Socket 模式下,每个 Socket 拥有独立收发缓冲(接收 2KB / 发送 1KB见上表。超过单 Socket 接收缓冲2KB的数据通过分块传输
```
发送端net_send(buf, 20480)
→ net_send_locked 分块写入 CH395F 发送缓冲(每 Socket 发送缓冲 1KB
→ 块 1: ch395f_write_send_buf(sock, buf[0..1023], 1024) // DMA 事务 1
→ 等待 SENBUF_FREE
→ 块 2: ch395f_write_send_buf(sock, buf[1024..2047], 1024) // DMA 事务 2
→ ...
→ 共 20 次 DMA 事务完成 20KBMSS=1024TCP 每段 ≤1024B
接收端net_recv(buf, 20480)
→ net_recv_locked 循环拉取
→ ch395f_get_recv_len() 返回硬件缓冲中当前可用数据≤2KB
→ ch395f_read_recv_buf() 一次性读空硬件缓冲≤2KB/DMA 事务)
→ 循环直至全部 20480B 收完
```
**边界条件**
- 发送 `≤1KB`:一次写发送缓冲完成(受发送缓冲 1KB 限制)
- 接收缓冲满2KB必须一次性读空否则 `recv_len` 归零导致剩余数据丢失Trap 13
- 发送 `0B``net_send` 返回 `-1``NET_ERR_INVAL`
> **注**:当前 `net_send` 的分块上限常量 `NET_DMA_MAX_PAYLOAD=4096` 大于单 Socket 发送缓冲(1KB),单次 `WRITE_SEND_BUF` 写入量受发送缓冲大小约束大文件传输Phase 6/8/10需在硬件上实测确认无溢出。
### 测试环境
```
PC (192.168.1.2) ──── 交换机 ──── CH395F (192.168.1.100)
MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
```
### 构建模式与执行顺序
测试固件通过条件编译分为**两种互斥形态**`test/ch395f_test.h` 中同时启用两层会触发 `#error`
| 构建模式 | 启用宏 | 固件形态 |
|----------|--------|----------|
| **硬件层模式** | `ENABLE_PHASE1~5_TESTS` | **netTask 不创建**CH395F 协议栈初始化由 ch395fTestTask 完成后独占芯片执行阶段 1~5裸调 `ch395f_*` 命令) |
| **NET 层模式** | `ENABLE_PHASE6~11_TESTS` | 完整网络栈netTask 运行 `net_poll()` 轮询testTask 经 `net_socket` API 执行阶段 6~11`net_layer_test_main()` 回显、`net_layer_client_test_main()` Client 等) |
**执行顺序严格递增**:先烧录硬件层固件完成阶段 1→2→3→4→5全部通过再切换为 NET 层固件执行阶段 6→7→…→11。硬件层测试期间 netTask 轮询不存在,从根上排除中断偷取与 SPI 并发问题。
### 启用测试
编辑 `test/ch395f_test.h`,取消注释对应 `ENABLE_PHASEx_TESTS` 宏(同一模式内 Phase 6/7/8/9/10/11 可共存于 testTask 循环)。**注意:阶段 6 Server 回显为持续监听循环,故 Client 阶段 11 需单独构建(不与 `ENABLE_PHASE6_TESTS` 同时启用),否则 Client 用例不会被执行。**
---
## 2. 阶段 1寄存器与 SPI 命令验证
| 阶段 ID | TM-PHY-01 |
|---------|-----------|
| **类型** | 独立运行 |
| **耗时** | ~1 秒 |
| **入口** | `ch395fTestTask` 启动后自动运行 |
| **出口** | 串口 "Phase 1 Tests Complete" 且 0 失败 |
### TC-NET-101: SPI 命令路径完整性
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-101 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 验证所有 SPI 命令路径返回预期值 |
| **前置条件** | 1. CH395F 已上电SPI2 已初始化<br>2. 未启用其他阶段 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_check_exist()`<br>SPI写: CMD=0x06, DATA=0x57<br>回读: ~0x57 = 0xA8 | 返回 `0x00``CH395F_ERR_SUCCESS`<br>= 回读字节 `0xA8` 匹配 `~0x57` | 返回 `0xFF``CH395F_STATUS_NOT_DETECTED`<br>= 回读字节 ≠ `0xA8`,回显取反不匹配 |
| 2 | `ch395f_get_version()` | 版本值 == `0x4A`CH395F 芯片版本) | 版本值 ≠ `0x4A`(芯片不匹配或未响应) |
| 3 | `ch395f_set_ip_addr(192.168.1.100)``ch395f_get_ip_inf()` 回读 IP | `ip_info[0..3] == 192.168.1.100` | 任意字节不匹配(写入/回读不一致) |
| 4 | `ch395f_set_gwip_addr(192.168.1.1)``ch395f_get_ip_inf()` 回读 GW | `ip_info[4..7] == 192.168.1.1` | 任意字节不匹配 |
| 5 | `ch395f_set_mask_addr(255.255.255.0)``ch395f_get_ip_inf()` 回读 MASK | `ip_info[8..11] == 255.255.255.0` | 任意字节不匹配 |
| 6 | `ch395f_get_glob_int_status_all()`16 位)<br>注:测试入口处已预读一次清除 `PHY_CHANGE` 残留(`net_stack_bring_up()` 强制 PHY 重协商 + 等待链路期间锁存,硬件层模式无 netTask 消费) | 返回值 == `0x0000`(无待处理中断) | 返回值 ≠ `0x0000`(有残留中断未清除) |
| 7 | `ch395f_open_socket(0)``ch395f_close_socket(0)``ch395f_get_cmd_status()` | `CMD_STATUS == 0x00``CH395F_ERR_SUCCESS` | `CMD_STATUS``0x00`Socket 操作失败) |
| 8 | `ch395f_set_arp(10, 5)``ch395f_get_cmd_status()` | `CMD_STATUS == 0x00` | `CMD_STATUS``0x00` |
| 9 | `ch395f_set_ttl(0, 64)``ch395f_get_cmd_status()` | `CMD_STATUS == 0x00` | `CMD_STATUS``0x00` |
**通过标准**9/9 通过0 失败。任一失败 → 阶段标记 FAIL后续阶段不执行
**覆盖原则**DP-02
---
## 3. 阶段 2TCP ClientCH395F 主动发起连接)
| 阶段 ID | TM-PHY-02 |
|---------|-----------|
| **类型** | 需 PC 配合 |
| **耗时** | ~60 秒 |
| **入口** | `ch395fTestTask` 启动后自动运行 |
| **出口** | 串口 "Phase 2 Tests Complete" 且 0 失败 |
**分层说明**:本阶段属于**硬件层模式**netTask 不创建CH395F 由 ch395fTestTask 独占),仅使用 `ch395f_*` 裸命令验证芯片 TCP Client 行为TCP 状态机、`CH395F_SINT_STAT_CONNECT` / `CH395F_SINT_STAT_SOCK_TIMEOUT` 中断、发送/接收缓冲与 `CH395F_SOCKET_CLOSED` 状态流转。
---
### TC-NET-201硬件层: TCP Client 连接与数据交换
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-201 |
| **优先级** | P0 |
| **类型** | 功能测试(硬件层) |
| **标题** | ch395f TCP Client → PC TCP Server64 字节收发 |
| **前置条件** | PC: `python test/ch395f_socket_test.py tcp_server --port 8081`PC 作 TCP 回显服务器,收到数据原样回显) |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_set_proto_type(0, CH395F_PROTO_TYPE_TCP)` + 设置目标 IP `192.168.1.2`、端口 `8081`、本地端口 `50000` | 配置写入成功CMD_STATUS == `0x00` | 配置失败SPI 异常或寄存器未更新) |
| 2 | `ch395f_open_socket(0)` 打开 Socket 0 | 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`Socket 硬件异常或 SPI 通信失败) |
| 3 | `ch395f_tcp_connect(0)` 发起 TCP 连接 | 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`(连接命令下发失败) |
| 4 | 轮询 `GET_GLOB_INT_STATUS``(sock+4)` 位 + `SINT_STAT` 等待 `CH395F_SINT_STAT_CONNECT`,超时 10s | 检测到 CONNECT 中断(连接已建立,进入 ESTABLISHED | 超时未建立连接(目标不可达 / 链路异常) |
| 5 | `ch395f_write_send_buf(0, buf, 64)` 发送 64 字节递增模式数据(`0x00..0x3F` | 发送完成且产生 `SENDBUF_FREE` 中断 | 发送缓冲未释放 / 发送失败 |
| 6 | 轮询 `GET_RECV_LEN` > 0 读取 PC 回显,超时 10sPC 端 tcp_server 收到数据后原样回显) | 收到 64 字节且与原数据逐字节一致 | 回显超时或任意字节不符 |
| 7 | `ch395f_close_socket(0)` 并轮询 `GET_SOCKET_STATUS` 等待 `CH395F_SOCKET_CLOSED` | Socket 回到 CLOSED`0x00`)状态 | 5s 内未回到 CLOSED状态机卡死 |
**通过标准**7/7 通过0 失败。任一失败 → 阶段标记 FAIL后续阶段不执行
**覆盖原则**DP-01open→connect→send→recv→close 主路径,硬件层)
---
### TC-NET-202硬件层: TCP Client 关闭重连 ×3 轮
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-202 |
| **优先级** | P0 |
| **类型** | 功能测试 / 恢复测试(硬件层) |
| **标题** | 关闭→重连循环 3 轮,每轮含数据交换 |
| **前置条件** | 1. PC 运行 `python ch395f_socket_test.py tcp_server --port 8081`TCP 回显服务器)<br>2. TC-NET-201 通过Socket 0 硬件正常) |
**测试步骤与判定标准**(每轮 i = 0,1,2本地端口取 `50000 + i` 规避 TIME_WAIT
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_open_socket(0)` 打开 Socket 0 | 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`Socket 硬件异常或 SPI 通信失败) |
| 2 | `ch395f_tcp_connect(0)` 发起 TCP 连接 | 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`(连接命令下发失败) |
| 3 | 轮询 `GET_GLOB_INT_STATUS``(sock+4)` 位,并读取 `SINT_STAT` 等待 `CH395F_SINT_STAT_CONNECT`,超时 10s | 检测到 CONNECT 中断(连接已建立) | 超时未建立连接(对端/链路异常) |
| 4 | `ch395f_write_send_buf(0, buf, 64)` 发送 64 字节递增模式数据(`0x00..0x3F` | 发送完成且产生 `SENDBUF_FREE` 中断 | 发送缓冲未释放 / 发送失败 |
| 5 | 轮询 `GET_RECV_LEN` > 0 读取 PC 回显,超时 10sPC 端 tcp_server 收到数据后原样回显) | 收到 64 字节且与原数据逐字节一致 | 回显超时或任意字节不符 |
| 6 | `ch395f_close_socket(0)` 并轮询 `GET_SOCKET_STATUS` 等待 `CH395F_SOCKET_CLOSED` | Socket 回到 CLOSED`0x00`)状态 | 5s 内未回到 CLOSED状态机卡死 |
**通过标准**3/3 轮全部 6 步通过0 失败。任一失败 → 阶段标记 FAIL后续不执行
**覆盖原则**DP-01open→connect→close→re-open→reconnect硬件层
---
### TC-NET-203硬件层: TCP Client 连接超时
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-203 |
| **优先级** | P1 |
| **类型** | 负向测试(硬件层) |
| **标题** | 目标端口无服务时连接超时 |
| **前置条件** | 1. PC **未**运行 TCP Server`192.168.1.2:8081` 无监听)<br>2. TC-NET-201 通过 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_set_proto_type(0, CH395F_PROTO_TYPE_TCP)` + 设置目标 IP `192.168.1.2`、端口 `8081` | 配置写入成功CMD_STATUS == `0x00` | 配置失败SPI 异常或寄存器未更新) |
| 2 | `ch395f_open_socket(0)``ch395f_tcp_connect(0)` | OPEN / CONNECT 均返回 `CH395F_ERR_SUCCESS``0x00`),命令异步下发不立即报错 | 命令返回值非 `0x00`Socket 硬件异常) |
| 3 | 轮询 `GET_GLOB_INT_STATUS``(sock+4)` 位,等待负向结果,超时窗 30s同时扫全局 `UNREACH` 位作备注 | 收到 `SOCK_TIMEOUT``DISCONNECT`,且期间未收到 `CH395F_SINT_STAT_CONNECT`。按耗时细分:<br>**<3s** = RST 快速拒绝(端口关闭,实测主路径——芯片毫秒级上报 SOCK_TIMEOUT<br>**≥3s** = 真无应答(离线/防火墙丢包)。全局 `UNREACH`ARP 失败)仅备注不影响判定 | 误收到 `CONNECT`PC 服务未关闭,环境错误——串口给出明确指引)或 30s 内无任何中断(状态机挂死) |
| 4 | `ch395f_close_socket(0)` 并轮询 `GET_SOCKET_STATUS` 等待 `CH395F_SOCKET_CLOSED` | Socket 正常关闭、系统无崩溃 / 死锁 | 关闭异常或任务挂死 |
**通过标准**:检测到 `SOCK_TIMEOUT` / `DISCONNECT` 且系统稳定0 崩溃。任一异常 → 阶段标记 FAIL
**覆盖原则**DP-01异常路径的 connect 失败处理,硬件层)
---
## 4. 阶段 3UDP Server EchoCH395F 作为 UDP Server
| 阶段 ID | TM-PHY-03 |
|---------|-----------|
| **类型** | 需 PC 配合 |
| **耗时** | ~60 秒 |
| **入口** | `ENABLE_PHASE3_TESTS`ch395fTestTask 启动后自动运行 |
| **出口** | 串口 "Phase 3 Tests Complete" 且 0 失败 |
**分层说明**:本阶段属于**硬件层模式**netTask 不创建),仅用 `ch395f_*` 裸命令验证芯片 UDP 能力UDP Socket 打开、数据报接收、**包头源地址解析**与按源地址动态回显。
> CH395F UDP 接收帧格式(`ch395f_read_recv_buf` 返回):[0-1] 保留|[2-3] 源端口(大端)|[4-7] 源 IP[8+] 载荷。回显前须以此源地址重设目标,且每次发送后等 `SENDBUF_FREE` 确认Trap 04
---
### TC-NET-301硬件层: UDP 基础回显
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-301 |
| **优先级** | P0 |
| **类型** | 功能测试(硬件层) |
| **标题** | 30 轮 64B UDP 数据报回显 |
| **前置条件** | PC: `python test/ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000` |
> **会话结构**(固件单会话,与 udp_client 脚本约定):累计 **60 个有效数据包**后关闭 Socket——第 1~30 轮对应本用例PC 发 64B第 31~60 轮对应 TC-302变长脚本 `"HELLO"` 就绪探针只回显、**不计数**,不占用用例预算。
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 配置 Socket 0proto=UDP、`sour_port=60000`、des=广播 `255.255.255.255:0`,后 `open_socket(0)` | OPEN 返回 `0x00` | 返回非 `0x00`Socket/SPI 异常) |
| 2 | 轮询 `GET_RECV_LEN``rlen > 8` 时读收并解析包头源端口/源 IP单轮超时 30s | 收到 PC 数据报且解析合法 | 30s 无数据(链路/环境异常) |
| 3 | 以源地址重设目标 → `write_send_buf` 回显载荷 → 等 `SENBUF_FREE`(超时 2s | 发送缓冲释放,进入下一轮 | 发送缓冲超时 |
| 4 | 重复至第 30 个有效包后 `close_socket(0)`(串口按 `UDP echo #N` 连续递增) | 前 30 轮全部 PASS | 任意轮回显超时/解析错误(提前终止) |
**通过标准**30/30 轮回显成功0 失败。任一失败 → 阶段标记 FAIL
**覆盖原则**DP-03UDP 收发路径,包头源地址解析)
---
### TC-NET-302硬件层: UDP 合规测试(变长载荷)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-302 |
| **优先级** | P1 |
| **类型** | 合规测试(硬件层) |
| **标题** | 变长载荷1B / 64B / 200B各 10 轮回显,验证 `rlen-8` 解析与载荷完整性 |
| **前置条件** | 同 TC-NET-301同一固件会话的第 31~60 轮);载荷上限受 `rx_buf[300]` 约束≤292B |
**测试步骤**:同 TC-NET-301 流程PC 端 udp_client Phase C 按 1B→64B→200B 各 `--count` 轮(默认 10循环发送MCU 回显后由 PC 逐字节比对一致性。
**通过标准**30/30 轮回显成功且 PC 端比对无误0 失败。任一失败 → 阶段标记 FAIL
**覆盖原则**DP-03变长载荷边界
---
## 5. 阶段 4DHCP 动态获取 IP
| 阶段 ID | TM-PHY-04 |
|---------|-----------|
| **类型** | PC 配合(脚本绑定 UDP/67Windows 需**管理员权限** |
| **耗时** | ~10s上限 30s DISCOVER 轮询 + 30s HELLO 等待) |
| **入口** | `ENABLE_PHASE4_TESTS`ch395fTestTask 启动后自动运行 |
| **出口** | 串口 "Phase 4: N/N PASSED (failed=0)" |
| **注意** | **改变 CH395F IP 配置**,必须独立运行(勿与其他阶段连跑) |
### 机制与会话结构
**DHCP 报文流**PC = DHCP ServerCH395F = Client
```
CH395F PC (dhcp_server)
| DISCOVER (xid, chaddr) ---------> | data[242]==1
| <-------- OFFER (yiaddr=分配IP) | xid/chaddr 原样回填,双广播发送
| REQUEST (server_id 校验) ------> | data[242]==3
| <-------- ACK (掩码/网关/租约) | data[242]==5
应用 IP -> Socket6 广播宣告 -> HELLO 回显
```
**关键实现约束**
- **xid/chaddr 回填**OFFER/ACK 必须原样回传 DISCOVER 的 `xid([4:8])``chaddr([28:34])`Trap 03早期 xid 偏移错误导致芯片丢弃响应)
- **option53 位置**:报文类型在 `data[242]`236B 固定头 + magic `63825363` + option 偏移),`len<=250` 视为无效包
- **双广播发送**OFFER/ACK 同时发 `255.255.255.255``192.168.1.255`(规避 Windows 定向广播路由限制)
- **REQUEST 重试**PC 最多等 8×5s期间收到重复 DISCOVER 则重发 OFFER
**固件会话结构**phase4_run
| 序 | 操作 | 参数 |
|----|------|------|
| 1 | `ch395f_set_dhcp(1)` 启动协商 | - |
| 2 | 轮询 `GET_DHCP_STATUS == 0x00` | 超时 30s间隔 500ms循环中同步读清 GINT_ALL |
| 3 | `GET_IP_INF` 轮询至连续 3 次一致(间隔 300ms上限 8s后打印 | 连续一致且非 `0.0.0.0`(见风险 #4 |
| 4 | Socket 6UDP本地 :60000send/recv buf 各 512B`255.255.255.255:60001` 广播 4B 本机 IP 宣告 | SENBUF_FREE 等待 2s |
| 5 | 同 Socket 收 PC `"HELLO"`(5B),按包头源地址回显一次后关闭 | 窗口 30s`PHASE4_HELLO_WAIT_MS` |
---
### TC-NET-401硬件层: DHCP 完整握手
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-401 |
| **优先级** | P0 |
| **类型** | 功能测试(硬件层) |
| **标题** | DISCOVER → OFFER → REQUEST → ACK 全流程获取 IP |
| **前置条件** | 管理员 PowerShell: `python test/ch395f_socket_test.py dhcp_server --ip 192.168.1.77 --dhcp-iface 192.168.1.2``--ip` 建议 ≠ 静态 IP以证明确为动态获取 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 先启动 dhcp_server复位 MCUENABLE_PHASE4_TESTS | PC 打印 "等待 CH395F DISCOVER" | 60s 无 DISCOVER链路/SPI 异常) |
| 2 | CH395F 发 DISCOVER → PC 回 OFFER | PC: "收到 DISCOVER (xid=..)" + "发送 OFFER" | msg_type ≠ 1 或 len ≤ 250 |
| 3 | CH395F 发 REQUEST → PC 回 ACK | PC: "收到 REQUEST" + "发送 ACK → {offer_ip}" | 8×5s 内无 REQUEST仅重发 OFFER |
| 4 | 芯片应用配置,串口回读 IP | "DHCP completed within 30s" PASS 且 IP == {offer_ip} | 状态轮询超时或 IP = 0.0.0.0 |
**通过标准**:步 2~4 全部满足,串口 IP 与 PC 分配一致。任一失败 → 阶段标记 FAIL
**覆盖原则**DP-03DHCP 自动配置路径)
---
### TC-NET-402硬件层: DHCP 后连通性验证(宣告 + HELLO 回显)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-402 |
| **优先级** | P1 |
| **类型** | 功能测试(硬件层) |
| **标题** | 获取 IP 后广播宣告 + 按 UDP 包头源地址回显 HELLO |
| **前置条件** | TC-NET-401 通过;同一脚本会话内自动衔接 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | Socket 6 广播 4B IP 宣告 → :60001 | 串口 "DHCP announcement sent"PC 打印 "收到 IP 宣告: {offer_ip}" | OPEN 失败 / SENBUF_FREE 2s 超时 |
| 2 | PC 发 `"HELLO"` → {offer_ip}:60000最多 5 次,每次等 2s | 串口 "HELLO echo OK" | 30s 窗口内无有效 HELLO |
| 3 | 芯片按包头源地址回显 HELLO关闭 Socket 6 | PC: "连通性验证通过" | PC 5 轮 retry 全超时 |
**通过标准**:步 1~3 全部满足HELLO 至少 1 次完整往返;固件设计为单次回显后关闭)。任一失败 → 阶段标记 FAIL
**覆盖原则**DP-03动态 IP 下的端到端 UDP 通路)
---
## 6. 阶段 5大文件传输硬件层
| 阶段 ID | TM-PHY-05 |
|---------|-----------|
| **类型** | PC 配合(`tcp_bulk`:先收 256KB 不回声 → 再按 LCG 回放 256KB |
| **耗时** | ~20 秒256KB 顺序双向TX 256KB + RX 256KB视链路/SPI 吞吐) |
| **入口** | `ENABLE_PHASE5_TESTS`ch395fTestTask 启动后自动运行 |
| **出口** | 串口 "Phase 5: N/N PASSED (failed=0)" + 吞吐统计 |
| **注意** | 与 NET 层阶段 7 同主题不同层:本阶段用 `ch395f_*` 裸命令直驱芯片,不经 net_socket/netTask |
> 编号说明:阶段 5 用例 **501/502**(顺序双测向完整性 + 变总量合规);原 PHY 测试(旧编号 901/902因板载拓扑不可测已删除见风险表 #1
### 为什么 2KB 接收缓冲不会丢数据TCP 流控原理)
PC 一次回放 256KB而本固件为每个 Socket 分配的接收缓冲仅 2KB4 块 × 512B——不丢数据的保证**不靠缓冲够大,靠 TCP 流控(通告窗口)**。
数据不是全部驻留芯片,而是在一串缓冲区里"接力"
```
PC OS 发送缓冲(64~256KB) → 网络在途 → CH395F 接收缓冲(2KB) → MCU 读取
```
CH395F 是完整 TCP 栈 offload 芯片,内部实现 TCP 状态机,按**接收缓冲剩余空间**向 PC 通告接收窗口advertised window
1. PC 发数据,芯片收进自己的 2KB 接收环形缓冲;
2. 缓冲快满 → 通告窗口收缩直至 **窗口=0** → PC 的 `sendall` **阻塞**TCP 背压),剩余数据停在 PC 自身 OS 发送缓冲,不再发出;
3. MCU `GET_RECV_LEN``READ_RECV_BUF` 读走 → 缓冲腾空 → 芯片重新通告窗口 → PC 恢复发送。
因此是**背压联动、自节流**:数据永不因芯片缓冲溢出被丢弃,只会"堵一会儿"。MCU 平均读取速率 ≥ PC 发送速率即可持续吞吐(本测试每 2ms 轮询、每次读 2KB平均速率远高于发送速率缓冲常年接近空
"先查长度再读"`GET_RECV_LEN``READ_RECV_BUF`)安全的原因:查询返回"当前未读字节数",流控保证其必然 ≤ 缓冲容量;查询与读取之间新到的数据进入环形缓冲尾部,由芯片读写指针管理,**不会覆盖未读数据**,留待下一次迭代读取。
会真正丢数据的只有两种情况:**MCU 复位 / TCP 连接断开**(连接级丢失,属预期失败)、**软件 bug**(读错长度/越界导致错位)。正常流控下接收缓冲本身不会溢出。
---
### 机制与会话结构
**顺序双测向协议**TX 与 RX 单向隔离,独立测吞吐/完整性):
```
MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081)
| open/connect -> CONNECT accept
|== 步2A: 连续发送 256KB (单向, 不等回显) ===|
| 写 4KB -> 等 SENBUF_FREE(2s) -> 下一块 |
|------------------------------------------>| recv 收满 256KB (不回声, LCG 校验)
|== 步2B: 连续接收 256KB ===================|
|<------------------------------------------| sendall 按 LCG 回放 256KB
| get_recv_len -> 滚动校验 (exp[] 按块生成) |
|===========================================|
close -> PC 侧 "阶段A 收满...校验一致" + "阶段B 回放完成"
```
**要点**
- **为什么顺序双测向**:单向隔离,分别测得**纯 TX / 纯 RX** 吞吐与完整性——不被"回声耦合"掩盖各自链路的真实能力
- **TX 节奏**:写 4096B → 等 `SENBUF_FREE`(芯片把 socket 缓冲数据搬入内部协议栈/MAC 缓冲后触发)→ 写下一块,全程不等回显;单向发送阶段 PC 只收不回声,故无回读死锁问题
- **RX 校验**PC 按相同 LCG 模式回放MCU 期望流按块生成 `exp[]` 字节对位 + `sum_rx` 累加,天然处理 TCP 分包/粘包
- 单块 4096B 数据 = 触发 DP-04 的 DMA 单次读边界(注意:单 Socket 发送缓冲实际为 1KBnet_send 写入需受此约束)
- 双重完整性:逐字节比对 + 全量 32 位累加和 `SUM(TX)==SUM(RX)`(双向同源 LCG相等即双向一致
- 双向独立吞吐打印TX/RX 各自 KB/s作为 SPI 时钟/驱动调整的回归基线
**固件会话结构**phase5_run
| 序 | 操作 | 参数/超时 |
|----|------|----------|
| 1 | close 清残留 → proto/des_ip/des_port/sour_port 配置 → open+connect | 两级中断等 CONNECT15s |
| 2A | 连续发送 64×4KB = 256KBPC 只收) | SENBUF_FREE 单块 2s |
| 2B | 连续接收 256KBPC 按 LCG 回放)滚动校验 | RX 停滞 5s 判 FAIL |
| 3 | close打印双向吞吐与校验结论 | blk_tx/blk_rx==64 且 SUM(TX)==SUM(RX) |
---
### TC-NET-501硬件层: 256KB 顺序双向(纯 TX + 纯 RX完整性
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-501 |
| **优先级** | P0 |
| **类型** | 功能测试(硬件层) |
| **标题** | 先连续发送 256KBPC 只收),再连续接收 256KBPC 回放),逐字节校验 + 总账一致 |
| **前置条件** | PC: `python test/ch395f_socket_test.py tcp_bulk --port 8081 --bulk 262144 --chunk 4096`(先启动等待连接) |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 复位 MCU → 裸命令 connect 到 :8081 | "connected within 15000ms" | 连接超时 / cmd_status 非 0x00 |
| 2A | 连续发送 64×4KB每 64KB 打印 TX 进度 | "TX 64/64 blocks (262144 bytes)"PC "阶段A 收满 262144 字节LCG 校验一致" | SENBUF_FREE 2s 超时 / PC 端校验不一致 |
| 2B | 连续接收 256KB 并滚动校验,每 64KB 打印 RX 进度 | "RX 262144/262144 bytes (64 blocks)"PC "阶段B 回放 262144 字节完成" | RX 停滞 5s / 字节不匹配 |
| 3 | close 并输出结论 | "TX phase: 64/64 blocks" + "RX phase: ..." + "checksum TX==RX" | bad_chunks>0 / 累加和不一致 |
**通过标准**:步 1~3 全部满足。任一失败 → 阶段标记 FAIL
**覆盖原则**DP-02两级中断轮询、DP-044KB DMA 读边界)
---
### TC-NET-502硬件层: 变总量与跨包分块合规
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-502 |
| **优先级** | P1 |
| **类型** | 合规测试(硬件层) |
| **标题** | 三档总量64KB/256KB/1MB+ 非整除块边界4093B回归 |
| **前置条件** | 同 TC-NET-501每档修改 `PHASE5_TOTAL_BYTES` / `PHASE5_CHUNK_SIZE` 宏后重编译,且 PC `--bulk``chunks×PHASE5_CHUNK_SIZE``--chunk``PHASE5_CHUNK_SIZE` 一致 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `PHASE5_TOTAL_BYTES=64*1024` 重编译PC `--bulk 65536` 跑一遍 | 全绿 | 同 TC-501 |
| 2 | `PHASE5_TOTAL_BYTES=1024*1024` 重编译PC `--bulk 1048576` 跑一遍 | 全绿且吞吐无劣化趋势 | 同上 |
| 3 | `PHASE5_CHUNK_SIZE=4093`(非 4 对齐制造跨包边界PC `--chunk 4093 --bulk 261952` 跑一遍 | 全绿4093 档实际回传 `64×4093=261952B`,不足一块的尾数不上送,完成判定按实际上送字节) | 尾块校验失败 |
**通过标准**:三组配置均全绿。任一失败 → 阶段标记 FAIL
**覆盖原则**DP-04跨包/非对齐边界)
> **记录2026-08-24 实测)**:三档全部 **6/6 PASSED**checksum 逐档相等 ——
> 64KB4096B 块): TX 420 / RX 364 KB/s1MB4096B 块): TX 449 / RX 391 KB/s
> 4093B 块261952B: TX 448 / RX 368 KB/s。吞吐随总量/块大小基本恒定,
> 双方向均约 370~450 KB/s疑似 SPI 总线SPI2 DMA为瓶颈。
---
## 7. 阶段 6NET 层 TCP Server 回显
| 阶段 ID | TM-PHY-06 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 持续运行Server 模式)/ 约 60sClient 模式) |
| **入口** | `ENABLE_PHASE6_TESTS``net_layer_test_main()``net_test_task.c`,持续监听回显循环) |
| **出口** | 用户终止Server/ 用例结束自动回到 IDLEClient |
本阶段在 NET 层(单连接 socketDP-01`net_socket` API 驱动MCU 作 TCP Server 监听 8080PC 主动连接,验证回显通路、单连接生命周期与字节级完整性 —— 已实现net 层自动重监听,应用层零生命周期代码)
> Client 主动连接(原 TC-NET-607/608/609现 701/702/703已独立为 **阶段 7**(见 §8使用独立宏 `ENABLE_PHASE7_TESTS` 与独立入口 `net_layer_client_test_main()`。
### 7.1 架构与会话结构
**Server 回显模式**`net_layer_test_main()``net_test_task.c`,由 `ENABLE_PHASE6_TESTS` 启用):
**架构图(组件)**
```
┌──────────────┐ TCP 8080 ┌──────────────────────────┐
│ PC (Python) │ ───────────────────────────────────────────▶ │ CH395F (192.168.1.100) │
│ tcp_client / │ Ethernet │ Socket 0单连接复用
│ tcp_server │ (SPI2, 分频=8) └────────────┬─────────────┘
└──────────────┘ SPI │
┌──────────────────────────┐
│ MCU (STM32F407) │
│ netTask: net_poll() 10ms │
│ testTask: │
│ net_layer_test_main() │
│ └─ phase6_run() 原样回显 │
└──────────────────────────┘
```
**会话时序图(单连接生命周期)**
```
PC (Client) CH395F Socket 0 MCU testTask
│ │ │
│ SYN ──────────────────▶│ │
│◀──────────────── SYN+ACK │ │
│ ACK ──────────────────▶│ ESTABLISHED │
│ │── 中断/GlobInt ───────────▶│ net_poll()
│ │ │ net_accept()→phase_active
│ [64B 数据] ───────────▶│ recv_len = N │
│ │── 中断 ───────────────────▶│ net_poll()
│ │ │ phase6_run():
│ │ │ net_recv(N) → 字节校验
│ │ │ TEST_CHECK(累加和)
│◀──────────── [64B 回显]─│ net_send() │
│ (逐字节比对 + 累加和) │ │
│ (重复 N 轮 / 多包) │ │
│ │ │
│ FIN/RST ───────────────▶│ DISCONNECT │
│ │── 中断 ───────────────────▶│ net_poll()
│ │ │ net_recv EOF → auto_relisten
│ │◀─ 自动 OPEN + TCP_LISTEN │
│ SYN(新连接) ───────────▶│ LISTEN 自动恢复,重新接受 │
```
- `phase6_run()` 为**纯回显**:非阻塞 `net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT)` 收到 N 字节后,`net_send(sockfd, s_rx_buf, N, 0)` 原样回显;不做固件侧字节自检(字节完整性由 PC 侧 `data == test_data` 校验)。
- 单连接约束DP-01Socket 0 复用监听/数据通道连接断开DISCONNECT / KeepAlive TIMEOUT / 静默丢失)后由 **net 层自动重监听**`auto_relisten``net_recv` 收到 EOF 时内部重新 `OPEN` + `TCP_LISTEN`),应用层无需 `net_close`/`net_listen`,下一次 `net_accept` 即接到新连接,行为等同 Linux BSD Socket 监听常驻。
> Client 主动连接(原 TC-NET-607/608/609现 701/702/703已独立为 **阶段 7**,架构与用例见 §8。
### TC-NET-601: Socket 0 单连接生命周期
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-601 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | Socket 0OPEN → TCP_LISTEN → ESTABLISHED单连接 |
| **前置条件** | 1. `net_init("192.168.1.100")` 成功(`g_net_ready==1`netTask 周期 `net_poll()` 运行)<br>2. `net_layer_test_main()``net_socket`+`net_bind(8080)`+`net_listen`(进入 LISTEN<br>3. PC: `python .\test\ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080` |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 连接 8080 | `net_accept()` 返回 fd== 监听 fd单连接`net_get_sock(state)==ESTABLISHED` | 连接未被接受 / 状态未进入 ESTABLISHED |
| 2 | `phase6_run()` 非阻塞收 PC 数据 | `net_recv(DONTWAIT)` 返回 > 0 | 收超时(连接未真建立) |
| 3 | 固件收 PC 数据 → 字节级比对 + 32 位累加和 `TEST_CHECK``net_send()` 原样回显 | PC 收到与发送完全一致的数据且固件 `TEST_CHECK` 通过 | 回显丢失 / 错乱 / 固件校验失败 |
| 4 | 串口观测 `net_get_sock` 状态 | 仅 Socket 0 状态变化,无其它 Socket 干扰 | 出现非预期 Socket 状态跳变 |
**通过标准**:步 1~4 全部满足,单连接生命周期正常。任一失败 → 标记 FAIL
**覆盖原则**DP-01OPEN→LISTEN→ESTABLISHED 单连接主路径) |
### TC-NET-602: 64 字节小包回显
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-602 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 10 轮 64B TCP 回显(原样回显,小包) |
| **前置条件** | 同 TC-NET-601连接已建立ESTABLISHED |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 依次发送 10×64B递增模式 `0x00..0x3F` | 每轮 `net_recv` 收 64B | 收包不足 64B |
| 2 | `phase6_run()` 收 64B → 原样回显 | PC 逐轮收到 64B 且 `data == test_data` 一致 | 回显丢失 / 错乱 / PC 校验不符 |
| 3 | PC 汇总 10 轮并逐字节校验 | 10/10 匹配 | 任一轮失败 |
**通过标准**10/10 匹配0 失败。任一失败 → 标记 FAIL
**覆盖原则**DP-01单连接数据通路小包 |
### TC-NET-603: 1460 字节回显(当前固件 MSS=10241460B 会按 MSS 分片)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-603 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 1460B 回显(注:当前固件 TCP MSS=10241460B 超过 MSS 将由 CH395F 按 MSS 分片) |
| **前置条件** | 同 TC-NET-601连接已建立 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 发单包 1460B当前 TCP MSS=10241460B 会被分片为 1024+436`net_send` 亦按 1KB 发送缓冲分块) | `net_recv` 一次收 1460B≤2KB 接收缓冲DMABUF=4100 一次读空) | 接收字节数 ≠ 1460被截断 / 分片丢失) |
| 2 | 固件收 1460B → 原样回显 | PC 收到 1460B 且 `data == test_data` 一致 | 回显错乱 / PC 校验不符 |
| 3 | PC 校验 | 1460B 完全匹配 | 任意字节不符 |
**通过标准**1460B 完全匹配,无截断。任一失败 → 标记 FAIL
**覆盖原则**DP-041460 ≤ 2KB 接收缓冲,单次 DMA 读空) |
### TC-NET-604: 4096 字节缓冲满回显
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-604 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 4096B 回显(接收缓冲仅 2KB分两次取空 |
| **前置条件** | 同 TC-NET-601`CH395F_SPI_DMA_BUF_SIZE=4100` 可一次性读空 2KB 硬件缓冲 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 发单包 4096B实际单 Socket 接收缓冲为 2KB固件分两次 `net_recv` 取空) | `net_recv` 累计收满 4096B分次 | 接收字节数 ≠ 4096丢失见 TRAP-13 |
| 2 | 固件收 4096B → 原样回显(`net_send` 分块) | PC 收到 4096B 且 `data == test_data` 一致 | 回显错乱 / PC 校验不符 |
| 3 | PC 校验 | 4096B 完全匹配 | 任意字节不符 |
**通过标准**4096B 完全匹配。任一失败 → 标记 FAIL
**覆盖原则**DP-042KB 接收缓冲边界 + 单次 DMA 上限) |
### TC-NET-605: 断开重连
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-605 |
| **优先级** | P0 |
| **类型** | 恢复测试 |
| **标题** | PC 断开后 Socket 0 自动重新监听并接受新连接 |
| **前置条件** | 同 TC-NET-601连接已建立已完成至少一轮 64B 回显) |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 关闭连接FIN/RST | 固件 `net_recv` 收 EOF、`net` 层自动 `auto_relisten` 回到 LISTEN | 状态未变化(卡 ESTABLISHED |
| 2 | net 层自动重监听,无需应用干预(`net_recv` EOF 内部重新 OPEN + TCP_LISTEN | `net_accept` 在新连接到达时再次返回 fd | 未回到 LISTEN状态机卡死 |
| 3 | PC 重新连接 8080 | `net_accept` 再次返回 fd进入 ESTABLISHED | 重连失败 |
| 4 | PC 发 64B → 固件原样回显 | 回显正常且 PC `data == test_data` 通过 | 回显失败 / PC 校验不符 |
**通过标准**重连成功且回显正常0 失败。任一失败 → 标记 FAIL
**覆盖原则**DP-01DISCONNECT → CLOSED → re-OPEN → LISTEN 单连接恢复) |
### TC-NET-606: 连接异常恢复(静默断开 / KeepAlive 超时)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-606 |
| **优先级** | P1 |
| **类型** | 恢复测试 |
| **标题** | 无干净 FIN 或 KeepAlive 超时后 Socket 0 自动重新监听 |
| **前置条件** | 1. 同 TC-NET-601连接已建立<br>2. KeepAlive 触发场景需 `ENABLE_PHASE11_TESTS``ch395f_set_keepalive_*` 已配置(探测时长本身由 TC-NET-1101 覆盖) |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | PC 静默断开(如直接断开网线 / 断电,不发 FIN或 KeepAlive 探测超时触发 TIMEOUT | 固件经 `net_get_sock(state)` 轮询发现非 ESTABLISHED | 状态长期卡 ESTABLISHED未恢复 |
| 2 | 固件 net 层 `auto_relisten` 自动回到 LISTEN应用层无 `net_close`/`net_listen` 调用) | 回到 LISTEN等待新连接 | 未恢复监听 |
| 3 | PC 重连 → 发 64B → 验证回显 | 回显正常 | 回显失败 |
**通过标准**:自动恢复监听,无需手动复位。任一异常 → 标记 FAIL
**覆盖原则**DP-01异常路径恢复KeepAlive 探测机制见 TC-NET-1101 |
---
## 8. 阶段 7NET 层 TCP Client 主动连接
| 阶段 ID | TM-PHY-07 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 约 60s |
| **入口** | `ENABLE_PHASE7_TESTS``net_layer_client_test_main()``net_test_task.c` |
| **出口** | 用例结束自动回到 IDLE |
> 与硬件层阶段 2 的 TC-NET-201/202/203 一一对应(芯片行为 → API 封装),验证 `net_socket` / `net_connect` / `net_send` / `net_recv` / `net_close` 对 TCP Client 主路径及异常路径的正确封装。
> 由独立宏 `ENABLE_PHASE7_TESTS` 启用,独立于 Server 回显阶段 6`ENABLE_PHASE6_TESTS`);因阶段 6 为持续监听循环Client 阶段需**单独构建**(不与 `ENABLE_PHASE6_TESTS` 同时启用)。
> 原编号 204/205/206原阶段 2 的 NET 层部分2026-08 重排后为 701/702/703。
### 8.1 架构与会话结构
```
net_socket(NET_AF_INET, NET_SOCK_STREAM, 0) // 取空闲 Socket非监听 Socket 0
→ net_connect(PC:8081) // 异步下发net_poll 推进状态机
→ net_select(sockfd+1, NULL, &wfds, NULL, &tv) // 等可写 = ESTABLISHED
→ net_send(64B) → net_recv(64B) 校验 // 主路径701
→ net_close() // 释放
```
**架构图(组件)**
```
┌──────────────┐ TCP 8081 ┌──────────────────────────┐
│ PC (Python) │ ◀─────────────────────────────────────────── │ CH395F (192.168.1.100) │
│ tcp_server │ Ethernet │ Socket N非 0 空闲) │
│ (回显服务器) │ (SPI2, 分频=8) └────────────┬─────────────┘
└──────────────┘ SPI │
┌──────────────────────────┐
│ MCU (STM32F407) │
│ netTask: net_poll() 10ms │
│ testTask: │
│ net_layer_client_test_main│
│ └─ net_connect→收发→close│
└──────────────────────────┘
```
**会话时序图Client 主动连接)**
```
PC (Server :8081) CH395F Socket N MCU testTask
│ │ │
│ (Python tcp_server) │ │ net_layer_client_test_main()
│ │◀─ net_socket()+net_connect()│ (异步下发 CONNECT)
│◀────────────── SYN ──────│ │
│────── SYN+ACK ─────────▶│ ESTABLISHED │
│ │── 中断/GlobInt ───────────▶│ net_poll()
│ │ │ net_select() 可写 → 建链
│◀────────────── [64B] ────│ net_send() │
│ (回显服务器原样返回) │ │
│ [64B 回显] ───────────▶│ recv_len = N │
│ │── 中断 ───────────────────▶│ net_poll()
│ │ │ net_recv(N) → 字节校验
│ │ │ TEST_CHECK(累加和)
│ │◀─ net_close() ─────────────│ 释放 Socket N
```
- `net_layer_client_test_main()` 为**独立入口**:使用非 0 空闲 Socket 连接 PC不在 `net_layer_test_main()` 的监听循环内;由 `StartCh395fTestTask` 的 NET 分支按宏调用。
- **固件自检**`net_recv` 后逐字节比对 + 32 位累加和 `TEST_CHECK`固件侧独立校验PC 同时校验。
- 关闭重连702、连接超时703见对应 TC。
### TC-NET-701: TCP Client 连接与数据交换
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-701 |
| **优先级** | P0 |
| **类型** | 功能测试NET 层) |
| **标题** | net_socket TCP Client → PC TCP Server64 字节收发 |
| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_server --port 8081`TCP 回显服务器)<br>2. `net_init()` 已完成(`g_net_ready == 1`netTask 周期 `net_poll()` 运行中)<br>3. `ENABLE_PHASE7_TESTS` 已定义,入口为 `net_layer_client_test_main()`(见「构建模式与执行顺序」)<br>4. 建议:硬件层固件中 TC-NET-201/202/203 已通过(跨构建前置,见「构建模式与执行顺序」) |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM, 0)` 创建 TCP socket | 返回 sockfd`0~7`),非 `-1` | 返回 `-1`(无空闲 Socket 或 net 层未初始化) |
| 2 | 构造 `struct net_sockaddr_in`sin_family=`NET_AF_INET`sin_port=`net_htons(8081)`sin_addr=`net_inet_addr("192.168.1.2")`)后 `net_connect(sockfd, &addr, len)` | 返回 `0`(连接命令已异步下发) | 返回 `-1`(地址非法或协议栈异常) |
| 3 | 在 netTask 周期 `net_poll()` 推进下,调用 `net_select(sockfd+1, NULL, &wfds, NULL, &tv)` 等待可写事件,超时 10s | 返回正数且 `NET_FD_ISSET(sockfd)` 指示连接建立ESTABLISHED | 超时未连接,`net_errno == NET_ERR_WOULDBLOCK` 持续 |
| 4 | `net_send(sockfd, buf, 64, 0)` 发送 64 字节递增模式数据(`0x00..0x3F` | 返回值 == `64`(全部发出) | 返回值 `< 0`(发送失败)或 `≠ 64`(未完全发出) |
| 5 | `net_recv(sockfd, buf, 64, 0)` 阻塞接收回显,超时 10s | 返回值 == `64`、与原数据逐字节一致、且固件字节级比对 + 累加和 `TEST_CHECK` 通过 | 返回值 `< 0`(超时/断开)或 `≠ 64`(数据不完整)或任意字节不符 或 固件 `TEST_CHECK` 失败 |
| 6 | `net_close(sockfd)` 关闭 socketTCP 先断开连接,释放控制块) | 返回 `0` | 返回 `-1`(关闭失败) |
**通过标准**6/6 通过0 失败。任一失败 → 标记 FAIL
**覆盖原则**DP-01net_socket→net_connect→net_send→net_recv→net_close 主路径NET 层)
### TC-NET-702: TCP Client 关闭重连 ×3 轮
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-702 |
| **优先级** | P0 |
| **类型** | 功能测试 / 恢复测试NET 层) |
| **标题** | close→重连循环 3 轮,每轮含数据交换 |
| **前置条件** | 同 TC-NET-701 |
**测试步骤与判定标准**(每轮 i = 0,1,2
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM, 0)` 创建 socket | 返回 sockfd 非 `-1` | 返回 `-1`(无空闲 Socket |
| 2 | 构造地址 + `net_connect(sockfd, &addr, len)`,再 `net_select` 等待可写确认 ESTABLISHED | 连接建立成功 | 连接失败或超时未建立 |
| 3 | `net_send(sockfd, buf, 64, 0)``net_recv(sockfd, buf, 64, 0)` | 收发各 `64` 字节、固件字节级比对 + 累加和 `TEST_CHECK` 通过 | 收发失败 / 数据不符 / 固件 `TEST_CHECK` 失败 |
| 4 | `net_close(sockfd)` 关闭并释放控制块 | 返回 `0` | 返回 `-1` |
**通过标准**3/3 轮全部通过0 失败。任一失败 → 标记 FAIL
**覆盖原则**DP-01net 层 open→connect→close→re-open→reconnect
### TC-NET-703: TCP Client 连接超时
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-703 |
| **优先级** | P1 |
| **类型** | 负向测试NET 层) |
| **标题** | 目标端口无服务时 net_connect 超时 |
| **前置条件** | 1. PC **未**运行 TCP Server`192.168.1.2:8081` 无监听)<br>2. TC-NET-701 通过 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `net_socket(...)` + `net_connect(sockfd, &addr, len)`(目标不可达) | `net_connect` 返回 `0`(异步下发) | 返回 `-1`(地址非法) |
| 2 | `net_select(sockfd+1, NULL, &wfds, NULL, &tv)` 等待可写,超时窗 30s | 返回 `0`(超时,连接未建立),`net_errno` 指示超时/不可达 | 误报告可写(异常响应)或 30s 内无结果(挂死) |
| 3 | `net_close(sockfd)` 释放 socket | 返回 `0` | 返回 `-1` |
**通过标准**`net_select` 超时且资源正确释放0 崩溃。任一异常 → 标记 FAIL
**覆盖原则**DP-01NET 层异常路径 connect 失败处理)
> **实现说明**TC-NET-703 与 TC-NET-701/702 的 PC 环境互斥(前者要求 PC 服务关闭、后两者要求开启),故固件中 TC-NET-703 由子开关 `PHASE7_TEST_TIMEOUT` 单独启用——默认 `ENABLE_PHASE7_TESTS` 仅跑 701/702单独开启 `PHASE7_TEST_TIMEOUT` 并关闭 PC 服务后重编,方可执行 703。
### TC-NET-704: 8-Socket 并发 TCP Client多 Socket 模式验证)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-704 |
| **优先级** | P0 |
| **类型** | 功能测试 / 并发测试NET 层) |
| **标题** | 同时打开 8 个 Socket 连接 PC Server各自 64B 收发 |
| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_server --port 8081 --max-conn 8`(并发回显,`--max-conn` 为并发计数)<br>2. `net_init()` 已置 `FUN_PARA=0x09`8 Socket 模式8 个 Socket 缓冲按「CH395F 缓冲架构」分配(每 Socket 接收 2KB + 发送 1KBMSS=1024<br>3. `ENABLE_PHASE7_TESTS` 已定义 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 循环 i=0..7`net_socket()` 创建 8 个 TCP socket本地端口 51000+i`net_connect(192.168.1.2:8081)` | 8 个 `net_connect` 均返回 `0` | 任一返回 `-1` |
| 2 | netTask `net_poll()` 推进下,`net_select` 等待全部 8 路可写 | 8 路均在 10s 内 ESTABLISHED | 任一超时未建立 |
| 3 | 每路 `net_send(sockfd, 64B)``net_recv(sockfd, 64B)` 校验 | 8 路收发各 64B、字节级比对 + 累加和 `TEST_CHECK` 通过 | 任一收发失败 / 数据不符 / `TEST_CHECK` 失败 |
**通过标准**8/8 路全部通过0 失败。任一失败 → 标记 FAIL
**覆盖原则**DP-01多 Socket 并发主路径、缓冲架构8×6 块分配MSS=1024
> **注**PC 端 `--max-conn` 须为**并发**计数(连接关闭即减),否则 Phase 7 全量701+702+704 共 12 连接)会按累计计数超限而拒掉 4~7见 Trap 19。该用例已随 Phase 7 实测 **40/40 PASS** 验证。
---
## 9. 阶段 8大文件 / 批量传输100KB文件收发
| 阶段 ID | TM-PHY-08 |
|---------|-----------|
| **类型** | PC 配合PC 作 TCP Client 连接 MCU:8080netTask 运行 |
| **耗时** | 取决于文件大小与速率100KB 约数秒) |
| **入口** | `ENABLE_PHASE8_TESTS` |
| **配置** | `PHASE8_FILE_SIZE`(默认 102400 = 100KB`PHASE8_DIRECTION`SEND / RECV / ECHO默认 ECHO |
| **PC 配合** | `python ./test/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(文件发送)<br>`... --op send --file tx_100k.bin --size 102400`(文件接收) |
> 架构MCU 为 TCP Server`net_test_task` 监听 8080`net_accept` 后 `phase8_run(acc)`PC 用 `tcp_file` 子命令作 Client 连接 MCU:8080。一次 `phase8_run` 对应一次 PC 连接。
### 设计要点
- **流式分块,不缓存全文件**`s_tx_buf`/`s_rx_buf` 仅 32KB100KB 须按窗口逐块生成/校验(递增模式 `(offset+i)&0xFF`),避免占用大块 RAM。
- **方向覆盖(由 `PHASE8_DIRECTION` 选择,或分三次连接分别跑)**
- **SENDTC-NET-801**MCU 流式发出 100KBPC 落盘 `rx_100k.bin` 并校验。
- **RECVTC-NET-802**PC 读 `tx_100k.bin` 流式发出(尾部附 4 字节 CRC32MCU 逐块接收并字节校验 + 整包 CRC32 比对。
- **ECHOTC-NET-803**MCU 发 100KB 由 PC 原样回显MCU 收齐校验round-trip覆盖收发双路径
- **完整性校验**
- 字节级:递增模式 `(offset+i)&0xFF`,接收端按 offset 逐字节比对MCU 侧自校验)。
- 整包级:发送端对 100KB 计算 CRC32接收端比对RECV 方向由 PC 在文件末尾附加 4 字节 CRC32小端MCU 收齐后比对自身运行的 CRC32。
### TC-NET-801: 100KB 文件发送MCU→PC
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-801 |
| **优先级** | P0 |
| **类型** | 功能测试(文件发送) |
| **标题** | MCU 流式发送 100KB 至 PC 并落盘校验 |
| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op recv --file rx_100k.bin --size 102400`(先连接等待数据)<br>2. `ENABLE_PHASE8_TESTS``PHASE8_FILE_SIZE=102400``PHASE8_DIRECTION=SEND`<br>3. `net_init` 完成MSS=1024 |
| **测试步骤** | 1. MCU `net_accept` 建链PC 已连接)<br>2. 循环填充一个滑动窗口(≤ s_tx_buf并调用 `net_send``net_send` 内部按硬件发送 FIFO`NET_SEND_CHUNK_MAX`=1KB见 Trap 20自动拆分写入直至 100KB 发完<br>3. PC 接收全部字节写入 `rx_100k.bin`,按递增模式或预置 CRC32 校验 |
| **通过标准** | PC 收满 102400B 且逐字节/CRC32 校验一致MCU 无发送错误 |
| **覆盖原则** | DP-04分块发送、DMA 边界、缓冲架构1KB 发送缓冲) |
| **实测** | ✅ PASS2026-08-26固件 `PHASE8_DIRECTION=SEND`PC `tcp_file --op recv` 收满 102400B递增模式逐字节校验通过CRC32=0x9A0E0C8C耗时 1209ms / 82.7 KB/s落盘 `rx_100k.bin` 成功。 |
### TC-NET-802: 100KB 文件接收PC→MCU
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-802 |
| **优先级** | P0 |
| **类型** | 功能测试(文件接收) |
| **标题** | PC 流式发送 100KB 文件MCU 接收并校验 |
| **前置条件** | 1. PC: `python ./test/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op send --file tx_100k.bin --size 102400`(先连接并发送;无文件则生成 100KB 递增模式)<br>2. 同 TC-NET-801`PHASE8_DIRECTION=RECV` |
| **测试步骤** | 1. MCU `net_accept` 建链PC 已连接)<br>2. 循环 `net_recv`≤2KB/次Trap 13 一次读空)→ 按 offset 字节校验 + 累计 CRC32直至收满 100KB<br>3. PC 在文件末尾附 4 字节 CRC32小端MCU 收齐后比对 CRC32 |
| **通过标准** | MCU 收满 102400B字节校验全过且 CRC32 匹配,无溢出/截断 |
| **覆盖原则** | DP-04接收缓冲 2KB 一次读空、Trap 13 |
| **实测** | 🟡 待验证(参考:默认递增模式 100KB 的 CRC32 经 SEND 实测为 `0x9A0E0C8C`RECV 方向 MCU 累计 CRC32 应与之相等) |
### TC-NET-803: 100KB 回显 round-trip可选
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-803 |
| **优先级** | P1 |
| **类型** | 恢复/压力测试 |
| **标题** | MCU 发 100KB 经 PC 回显原样返回MCU 收齐校验 |
| **前置条件** | MCU: `PHASE8_DIRECTION=ECHO`net_accept 监听 8080PC: `python ./test/ch395f_socket_test.py tcp_file --ip 192.168.1.100 --port 8080 --op echo`PC 作 Client 连 MCU 并原样回显) |
| **测试步骤** | MCU 发 100KB → PC 回显 → MCU 收 100KB 字节校验 |
| **通过标准** | 100KB 全过,验证 100KB 经回显链路完整 |
| **覆盖原则** | DP-01、DP-04 |
| **实测** | 🟢 已验证MCU 收齐 102400B 逐字节校验通过PC 回显客户端 1241ms @ 80.6 KB/s对端关闭后正常退出修复了 phase8_run 未关闭连接导致 MCU 在同连接上无限重复、PC 挂起的问题) |
### 实现说明
`phase8_run()` 状态机netTestTask`net_accept` 后执行,`sockfd` 为已建链 fd
```
IDLE → ACCEPT → [SEND: 分块发 → DONE] / [RECV: 分块收+校验 → DONE] / [ECHO: 分块发 → 分块收(回显) → DONE] → 关闭 → 重新 ACCEPT
```
- **发送**`phase8_fill(buf, chunk, offset)` 按窗口生成递增模式;`net_send` 经消息队列转发 netTask`net_send_locked` 按发送缓冲(1KB)/`CH395F_SPI_DMA_MAX_PAYLOAD` 自动拆分。
- **接收**`net_recv` 每次读 ≤2KB=接收缓冲Trap 13 一次读空),`phase8_verify(buf, n, offset)` 字节校验 + 累计 CRC32。
- **100KB 不整体驻留 RAM**`s_tx_buf`/`s_rx_buf`=32KB 仅作滑动窗口;`PHASE8_FILE_SIZE` 控制总量。
- **PC `tcp_file` 子命令**(新增,作 Client 连 MCU:8080`recv` 接收 `--size` 字节落盘并校验;`send``--file` 发送并附 4 字节 CRC32 尾。
---
## 10. 阶段 9Select/Poll I/O 多路复用(单连接)
| 阶段 ID | TM-PHY-09 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 持续运行 |
| **入口** | `ENABLE_PHASE9_TESTS` |
### TC-NET-901: net_select() 单 socket 就绪检测
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-901 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | `net_select()` 在 Socket 0 数据到达时正确返回 |
| **前置条件** | Socket 0 ESTABLISHED |
| **测试步骤** | 1. PC 发送数据<br>2. `net_select(1, &readfds, ...)` → 返回 > 0<br>3. `NET_FD_ISSET(0, &readfds)` == true<br>4. `net_recv()``net_send()` 回显 |
| **通过标准** | 回显成功 |
| **覆盖原则** | DP-02select 内部调 net_poll |
### TC-NET-902: net_select() 超时
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-902 |
| **优先级** | P1 |
| **类型** | 负向测试 |
| **标题** | 100ms 超时内无数据则返回 0 |
| **前置条件** | Socket 0 ESTABLISHEDPC 静置 |
| **测试步骤** | `net_select(1, &readfds, ..., 100ms)` |
| **通过标准** | 返回值 == 0无崩溃 |
---
## 11. 阶段 10边界条件测试
| 阶段 ID | TM-PHY-10 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 按需 |
| **入口** | `ENABLE_PHASE10_TESTS` |
### TC-NET-1001: DMA 边界 14095 字节)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1001 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 发送 4095 字节SPI 最大载荷 1单次 DMA 可容纳) |
| **前置条件** | Socket 0 ESTABLISHED |
| **测试步骤** | MCU 生成 4095B 递增模式数据 → `net_send_sock(sk, data, 4095)` → 等待 PC 回显 → 验证 |
| **通过标准** | 4095B 完全匹配 |
| **覆盖原则** | DP-04 |
### TC-NET-1002: DMA 边界4096 字节)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1002 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 发送 4096 字节(等于 SPI 最大载荷,填满单次 DMA |
| **测试步骤** | MCU 发 4096B → 等待回显 → 验证 |
| **通过标准** | 4096B 完全匹配 |
| **覆盖原则** | DP-04 |
### TC-NET-1003: DMA 边界 +14097 字节)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1003 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 发送 4097 字节(超过 SPI 最大载荷,自动拆两次 DMA4096+1 |
| **测试步骤** | MCU 发 4097B → `net_send_locked` 自动拆 4096+1 → 等待回显 → 验证 |
| **通过标准** | 4097B 完全匹配,两次 DMA 无缝衔接 |
| **覆盖原则** | DP-04 |
### TC-NET-1004: 零长度收发
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1004 |
| **优先级** | P1 |
| **类型** | 负向测试 |
| **标题** | `net_send_sock(sk, buf, 0)` 返回 1 |
| **测试步骤** | `net_send_sock(sk, s_phase9_tx, 0)` |
| **通过标准** | 返回 < 0无崩溃 |
### TC-NET-1005: 连续快速发送 20 小包
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1005 |
| **优先级** | P1 |
| **类型** | 压力测试 |
| **标题** | 20 × 100 字节连续快速发送(验证 SENDBUF_FREE 等待机制) |
| **测试步骤** | 循环 20 次:`net_send_sock(sk, buf, 100)`,间隔 1ms |
| **通过标准** | 20/20 成功,回显 2000B 完全匹配 |
### TC-NET-1006: 快速连接-断开 ×20 轮
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1006 |
| **优先级** | P1 |
| **类型** | 稳定性测试 |
| **标题** | 20 次快速连接-断开循环 |
| **测试步骤** | PC 连 8080 → 立即断开 → 等 s0 恢复 LISTEN → 重复 20 次 |
| **通过标准** | 20/20 不卡死s0 始终回到 LISTEN |
---
## 12. 阶段 11KeepAlive 超时断开
| 阶段 ID | TM-PHY-11 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | ~65 秒60s idle + 15s probes |
| **入口** | `ENABLE_PHASE11_TESTS` |
| **配置** | KeepAlive 空闲 60s探测间隔 5s3 次 |
### TC-NET-1101: KeepAlive 触发断开
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1101 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | TCP 连接空闲超过 KeepAlive 阈值后自动断开 |
| **前置条件** | 1. `ch395f_set_keepalive_idle(60000)`<br>2. `ch395f_set_keepalive_intvl(5000)`<br>3. `ch395f_set_keepalive_cnt(3)`<br>4. PC 连接后静置(不发数据) |
| **测试步骤** | 1. PC 连接 8080不发数据<br>2. 等待 KeepAlive 探测60s idle + 3×5s = 75s 最大)<br>3. CH395F 接收 TIMEOUT → s0 → CLOSED → LISTEN |
| **通过标准** | 超时时间 ≥ 60ss0 回到 LISTEN |
---
## 13. Python 测试脚本参考
| 命令 | 模式 | 说明 |
|------|------|------|
| `tcp_server --port 8081` | PC 作 TCP 回显服务器(回显循环) | 测试 TCP Client阶段 2 硬件层;阶段 7 Client 701/702/703 |
| `tcp_client --ip ... --port 8080` | PC 作 TCP Client | 测试 TCP Echo阶段 6/8/9 |
| `tcp_file --ip ... --port 8080 --size 20480` | PC 作文件接收 | 测试大文件传输(阶段 7 |
| `udp_client --ip ... --port 60000` | PC 作 UDP Client | 测试 UDP Echo阶段 3 |
| `udp_server --port 8082` | PC 作 UDP Server | 接收 CH395F UDP 数据 |
| `dhcp_server ...` | DHCP Server | 内置 DHCP阶段 4需管理员权限 |
### 通用参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `--ip` | 192.168.1.100 | 目标 IP |
| `--port` | 因模式而异 | 端口 |
| `--count` | 10 | 测试次数 |
| `--timeout` | 30 | 超时秒数 |
| `--size` | 1024 | 包大小 |
---
## 14. 已知陷阱
| ID | 陷阱 | 现象 | 修复 |
|----|------|------|------|
| TRAP-03 | DHCP xid 偏移错误 | DHCP 握手失败 | xid 在 `data[4:8]` |
| TRAP-04 | UDP 无 SENDBUF_FREE 等待 | 后续接收挂起 | 每次 write 后等 SENDBUF_FREE |
| TRAP-05 | PHY 不兼容 RTL8305NBI | 链路抖动 | `ch395f_set_phy(0x08)` 强制 100M_FULL |
| TRAP-06 | KeepAlive 非 500ms 倍数 | 参数不生效 | 必须 500ms 倍数 |
| TRAP-07 | TCP 误用 disconnect | 卡 FIN_WAIT_2 | 用 `close_socket`(RST) 非 `disconnect` |
| TRAP-08 | RECV 电平中断无限循环 | netTask 饿死 | `net_poll` 每次只一批,数据在应用中读 |
| TRAP-09 | PHY 刚建立 link 立即 open | 首次连接失败 | 启动延时 + 重试 |
| TRAP-10 | 消息队列值拷贝丢失 | API 返回旧值 | 改用 `xTaskNotify` |
| TRAP-11 | `net_accept_locked` 拒 ESTABLISHED | FTP PASV 失败 | 允许 ESTABLISHED |
| TRAP-12 | FatFS 不接受 `/` 前缀 | FTP 路径解析失败 | `to_fatfs_path()` 剥离 |
| **TRAP-13** | **`recv_len` 分批读取归零** | **大包截断** | **DMA 缓冲增至 4100一次读空 ✅ 已修复** |
| **TRAP-14** | **`NET_SEND_BUF_SIZE` > DMA 限制** | **>1496B 发送静默失败** | **`net_send_locked``NET_DMA_MAX_PAYLOAD` 分块 ✅ 已修复** |
---
## 附录 B通过/失败汇总
| 阶段 | TC ID | 优先级 | 类型 | 状态 |
|------|-------|--------|------|------|
| TM-PHY-01 | TC-NET-101 | P0 | 功能测试 | ✅ |
| TM-PHY-01 | TC-NET-102 | P1 | 功能测试 | ✅ |
| TM-PHY-02 | TC-NET-201 | P0 | 功能测试 | ✅ |
| TM-PHY-02 | TC-NET-202 | P0 | 恢复测试 | ✅ |
| TM-PHY-02 | TC-NET-203 | P1 | 负向测试 | ✅ |
| TM-PHY-03 | TC-NET-301 | P0 | 功能测试 | ✅ |
| TM-PHY-03 | TC-NET-302 | P1 | 合规测试 | ✅ |
| TM-PHY-04 | TC-NET-401 | P0 | 功能测试 | ✅ |
| TM-PHY-04 | TC-NET-402 | P1 | 功能测试 | ✅ |
| TM-PHY-05 | TC-NET-501 | P0 | 功能测试 | ✅ |
| TM-PHY-05 | TC-NET-502 | P1 | 合规测试 | ✅ |
| TM-PHY-06 | TC-NET-601 | P0 | 功能测试 | ✅ |
| TM-PHY-06 | TC-NET-602 | P0 | 功能测试 | ✅ |
| TM-PHY-06 | TC-NET-603 | P0 | 边界测试 | ✅ |
| TM-PHY-06 | TC-NET-604 | P0 | 边界测试 | ✅ |
| TM-PHY-06 | TC-NET-605 | P0 | 恢复测试 | ✅ |
| TM-PHY-06 | TC-NET-606 | P1 | 恢复测试 | ✅ |
| TM-PHY-07 | TC-NET-701 | P0 | 功能测试 | ✅ |
| TM-PHY-07 | TC-NET-702 | P0 | 恢复测试 | ✅ |
| TM-PHY-07 | TC-NET-703 | P1 | 负向测试 | ✅ |
| TM-PHY-07 | TC-NET-704 | P0 | 并发测试 | ✅ |
| TM-PHY-08 | TC-NET-801 | P0 | 文件发送 | ✅ |
| TM-PHY-08 | TC-NET-802 | P0 | 文件接收 | 🟡 |
| TM-PHY-08 | TC-NET-803 | P1 | 回显/压力 | 🟡 |
| TM-PHY-09 | TC-NET-901 | P0 | 功能测试 | 🟡 |
| TM-PHY-09 | TC-NET-902 | P1 | 负向测试 | 🟡 |
| TM-PHY-10 | TC-NET-1001 | P0 | 边界测试 | 🟡 |
| TM-PHY-10 | TC-NET-1002 | P0 | 边界测试 | 🟡 |
| TM-PHY-10 | TC-NET-1003 | P0 | 边界测试 | 🟡 |
| TM-PHY-10 | TC-NET-1004 | P1 | 负向测试 | 🟡 |
| TM-PHY-10 | TC-NET-1005 | P1 | 压力测试 | 🟡 |
| TM-PHY-10 | TC-NET-1006 | P1 | 稳定性测试 | 🟡 |
| TM-PHY-11 | TC-NET-1101 | P1 | 功能测试 | 🟡 |
✅ = 已实现并验证 | 🟡 = 已实现待验证 | 🟠 = 待实现
## 附录 C设计原则覆盖
| 原则 | 覆盖用例 |
|------|----------|
| DP-01单 socket 生命周期 | TC-NET-202, 203, 601, 605, 606, 701, 702, 703, 704, 801, 802, 901, 902, 1001~1006 |
| DP-02中断即读即清 | TC-NET-101, 102, 501, 502, 901 |
| DP-03消息队列线程安全 | TC-NET-301, 302, 401 |
| DP-04DMA 边界安全 | TC-NET-501, 502, 603, 604, 801, 802, 1001~1005 |
## 附录 DCH395F 边界条件速查
| 操作 | 大小 | 行为 |
|------|------|------|
| `net_send(buf, ≤1496)` | 1~1496B | 单次 DMA 事务(旧 DMABUF=1500 时完全容纳) |
| `net_send(buf, 1497~4096)` | 1497~4096B | 旧代码**静默失败**;现 DMABUF=4100 **一次 DMA 容纳** |
| `net_send(buf, 4097~65535)` | >4096B | `net_send_locked` **自动分块**(受单 Socket 发送缓冲 1KB 约束;当前按 `NET_DMA_MAX_PAYLOAD=4096` 拆分,需实测) |
| `net_send(buf, 0)` | 0 | 返回 `-1``NET_ERR_INVAL` |
| `ch395f_get_recv_len()` | 0~65535 | 返回 CH395F 硬件缓冲当前可用数据量 |
| `ch395f_read_recv_buf(buf, 4096)` | 4096B | 一次性读空 Socket 0 整个接收缓冲 |
| `ch395f_read_recv_buf(buf, 100)` | 100B | ⚠️ `recv_len` 之后**归零**,剩余数据**丢失** |