Files
STM32F4-Base/docs/CH395F_Test_Guide.md
2026-08-22 19:53:24 +08:00

646 lines
26 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 模式**下的测试规范。测试按阶段TM-PHY-NN组织每个阶段包含独立测试用例TC-NET-NNN
### 设计原则
| 原则 | 描述 | 验证用例 |
|------|------|----------|
| DP-01 | 单连接 socketSocket 0 唯一复用监听和数据通道CONNECT 后切换数据通道DISCONNECT/TIMEOUT 后重新 OPEN → LISTEN | TC-NET-201~406 |
| 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-801~805 |
| DP-05 | PHY 链路恢复PHY 断连/重连后 socket 自动恢复至 LISTEN | TC-NET-901~902 |
### CH395F 缓冲架构(关键参数)
| 参数 | 值 | 说明 |
|------|-----|------|
| CH395F 内部总缓冲 | 32KB32 块 × 1KB | Socket 0~3 各 4KB 默认 |
| Socket 0 发送缓冲 | 4096B硬件默认 | 未显式配置时使用 |
| Socket 0 接收缓冲 | 4096B硬件默认 | 未显式配置时使用 |
| **SPI DMA 缓冲** | **4100B** | `CH395F_SPI_DMA_BUF_SIZE` |
| **单次 SPI 最大载荷** | **4096B** | `CH395F_SPI_DMA_MAX_PAYLOAD` |
| 单次 net_send 最大分块 | 4096B | 超限自动分多次 DMA |
| recv_len 最大值 | 65535 | `uint16_t` |
> **关键约束**CH395F 硬件接收缓冲 4096B。`recv_len` 在调 `read_recv_buf` 读取**部分**数据后归零Trap 13。因此每次接收调用需确保 `read_len >= recv_len`,或增大 DMA 缓冲以一次性读空。当前 `CH395F_SPI_DMA_BUF_SIZE=4100` 可覆盖完整 4096B 接收缓冲。
### 大文件传输20KB+)策略
单连接模式下,超过 Socket 0 硬件缓冲4KB的数据通过以下机制传输
```
发送端net_send(buf, 20480)
→ net_send_locked 分块,每块 ≤ 4096B
→ 块 1: ch395f_write_send_buf(sock, buf[0..4095], 4096) // DMA 事务 1
→ 等待 SENDBUF_FREE
→ 块 2: ch395f_write_send_buf(sock, buf[4096..8191], 4096) // DMA 事务 2
→ ...
→ 共 5 次 DMA 事务完成 20KB
接收端net_recv(buf, 20480)
→ net_recv_locked 循环拉取
→ ch395f_get_recv_len() 返回硬件缓冲中当前可用数据≤4096B
→ ch395f_read_recv_buf() 一次性读空硬件缓冲≤4096B/DMA 事务)
→ 循环直至全部 20480B 收完
```
**边界条件**
- 发送 `≤4096B`:一次 DMA 事务完成
- 发送 `4097B`:两次 DMA 事务4096 + 1
- 接收缓冲满4096B必须一次性读空否则 `recv_len` 归零导致剩余数据丢失
- 发送 `0B``net_send` 返回 `-1``NET_ERR_INVAL`
### 测试环境
```
PC (192.168.1.2) ──── 交换机 ──── CH395F (192.168.1.100)
MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
```
### 启用测试
编辑 `test/ch395f_test.h`,取消注释对应 `ENABLE_PHASEx_TESTS` 宏。
---
## 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 位) | 返回值 == `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 失败 |
### TC-NET-201: TCP Client 连接与数据交换
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-201 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | CH395F TCP Client → PC TCP Server64 字节收发 |
| **前置条件** | PC: `python ch395f_socket_test.py tcp_server --port 8081` |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | CH395F open Socket 0 → 配置 TCP 协议 | `ch395f_open_socket(0)` 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`Socket 硬件异常或 SPI 通信失败) |
| 2 | 设置目标 IP 为 PC 地址(`192.168.1.2`),端口 `8081` → 发起 TCP 连接 | `ch395f_tcp_connect(0)` 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`(连接超时、目标不可达或协议配置错误) |
| 3 | 发送 64 字节递增模式数据(`0x00, 0x01, ..., 0x3F` | `net_send()` 返回值 == `64`(全部发出) | 返回值 < `0`(发送失败)或返回值 ≠ `64`(未完全发出) |
| 4 | 阻塞等待接收回显,超时 5 秒 | `net_recv()` 返回值 == `64`(收到全部回显) | 返回值 < `0`(超时/断开)或返回值 ≠ `64`(数据不完整) |
| 5 | 逐字节比较收发数据 | 所有 64 字节完全一致 | 任意字节不一致(回显错误或数据错位) |
**通过标准**5/5 通过0 失败。任一失败 → 阶段标记 FAIL后续阶段不执行
### TC-NET-202: TCP Client 关闭重连 ×3 轮
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-202 |
| **优先级** | P0 |
| **类型** | 功能测试 / 恢复测试 |
| **标题** | 关闭→重连循环 3 轮,每轮含数据交换 |
| **测试步骤** | 每轮:关 Socket 0 → 重开 → 重连 → 发 64B → 验证回显 |
| **通过标准** | 3/3 轮通过 |
| **覆盖原则** | DP-01open→connect→close→re-open→reconnect |
### TC-NET-203: TCP Client 连接超时
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-203 |
| **优先级** | P1 |
| **类型** | 负向测试 |
| **标题** | 目标端口无服务时连接超时 |
| **前置条件** | PC 未运行 TCP Server |
| **测试步骤** | 1. 开 Socket 0 → 尝试连接 192.168.1.2:8081<br>2. 等待 TIMEOUT<br>3. 关 Socket 0 |
| **通过标准** | TIMEOUT 检测到,无崩溃 |
| **覆盖原则** | DP-01 |
---
## 4. 阶段 3UDP Server 回显
| 阶段 ID | TM-PHY-03 |
|---------|-----------|
| **类型** | 需 PC 配合 |
| **耗时** | ~30 秒 |
| **入口** | `ch395fTestTask` 启动后自动运行 |
| **出口** | 串口 "Phase 3 Tests Complete" 且 0 失败 |
### TC-NET-301: UDP 回显 — HELLO + PING + 大包 ×30
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-301 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | UDP Server 接收并回显 30 轮(含 HELLO、PING、1024B 大包) |
| **前置条件** | PC: `python ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 60000` |
| **测试步骤** | 1. PC 发 HELLO → MCU 回显<br>2. PC 发 5×PING → MCU 回显<br>3. PC 发 10×1024B → MCU 回显<br>4. PC 发 14×混合大小 → MCU 回显 |
| **通过标准** | 30/30 回显匹配 100% |
| **覆盖原则** | DP-03 |
### TC-NET-302: SENDBUF_FREE 等待合规
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-302 |
| **优先级** | P1 |
| **类型** | 合规测试 |
| **标题** | 每次 `write_send_buf` 后等待 `SENDBUF_FREE` |
| **前置条件** | TC-NET-301 运行中 |
| **测试步骤** | PC 连续发小包(<100ms 间隔) |
| **通过标准** | 30/30 成功,无丢包 |
| **覆盖原则** | DP-03 |
---
## 5. 阶段 4NET 层 TCP Echo单连接基础
| 阶段 ID | TM-PHY-04 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 持续运行 |
| **入口** | `ENABLE_PHASE4_TESTS``ch395fTestTask` TCP 回显循环中运行 |
| **出口** | 用户终止 |
### TC-NET-401: Socket 0 单连接生命周期
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-401 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | Socket 0: OPEN → TCP_LISTEN → ESTABLISHED单连接 |
| **前置条件** | 1. `net_init("192.168.1.100")` 成功<br>2. `net_listen(sock0)` 已调用<br>3. PC: `python ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080` |
| **测试步骤** | 1. PC 连接 8080<br>2. Socket 0 收到 CONNECT → ESTABLISHED<br>3. 发数据 → 回显 |
| **通过标准** | 仅 s0 状态变化;串口 `s0:int=0x08` → ESTABLISHED |
| **覆盖原则** | DP-01 |
### TC-NET-402: 64 字节小包回显
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-402 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 10 轮 64B TCP 回显 |
| **测试步骤** | PC 发 10×64BMCU 回显 |
| **通过标准** | 10/10 匹配 |
### TC-NET-403: 1460 字节 MSS 回显
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-403 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | MSS 大小1460BTCP 回显 |
| **测试步骤** | PC 发 1460BMCU 回显 |
| **通过标准** | 1460B 完全匹配,无截断 |
### TC-NET-404: 4096 字节缓冲满回显
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-404 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 填满 Socket 0 接收缓冲4096B回显 |
| **前置条件** | DMABUF=4100 可一次性读空 |
| **测试步骤** | PC 发 4096BMCU 经 DMA 一次性读取并回显 |
| **通过标准** | 4096B 完全匹配 |
| **覆盖原则** | DP-04 |
### TC-NET-405: 断开重连
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-405 |
| **优先级** | P0 |
| **类型** | 恢复测试 |
| **标题** | PC 断开后 Socket 0 自动重新监听并接受新连接 |
| **测试步骤** | 1. PC 断连 → s0 收 DISCONNECT → CLOSED → re-OPEN → LISTEN<br>2. PC 重连 → s0 → ESTABLISHED<br>3. 发 64B → 验证回显 |
| **通过标准** | 重连成功,回显正常 |
| **覆盖原则** | DP-01 |
### TC-NET-406: 超时恢复
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-406 |
| **优先级** | P1 |
| **类型** | 恢复测试 |
| **标题** | KeepAlive 超时后 Socket 0 自动重新监听 |
| **测试步骤** | 1. PC 断连后停止响应<br>2. 等待 KeepAlive 触发 TIMEOUT<br>3. s0 → CLOSED → re-OPEN → LISTEN<br>4. PC 重连 → 验证回显 |
| **通过标准** | 自动恢复,无需手动复位 |
| **覆盖原则** | DP-01 |
---
## 6. 阶段 5大文件传输20KB+
| 阶段 ID | TM-PHY-05 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 取决于文件大小和速率 |
| **入口** | `ENABLE_PHASE5_TESTS` |
| **配置** | `PHASE5_FILE_SIZE`(默认 20480|
### TC-NET-501: 20KB 分块传输
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-501 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 20480 字节 TCP 分块收发(>Socket 0 硬件缓冲 4KB |
| **前置条件** | `net_send_locked` 支持自动分块(每块 ≤4096B |
| **测试步骤** | 1. MCU 填 20KB 递增模式数据<br>2. MCU 分块发送,每块 4096B<br>3. PC 回显<br>4. MCU 接收并逐字节验证 |
| **通过标准** | 20KB 完全匹配,每字节验证通过 |
| **覆盖原则** | DP-04 |
### TC-NET-502: 64KB 传输recv_len 上限)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-502 |
| **优先级** | P1 |
| **类型** | 边界测试 |
| **标题** | 65535 字节传输(`recv_len` uint16 上限) |
| **前置条件** | TC-NET-501 通过 |
| **测试步骤** | 同 501文件大小 65535 |
| **通过标准** | 65535B 完全匹配 |
| **覆盖原则** | DP-04 |
### 实现说明
`phase5_run()` 状态机(`ch395fTestTask` 内):
```
IDLE → (连接建立) → SENDING(分块发) → RECVING(分块收+验证) → DONE → IDLE
```
- 每次 `net_send_sock` 自动分块(`net_send_locked` 内按 `NET_DMA_MAX_PAYLOAD=4096` 拆分)
- 每次 `net_recv_sock` 通过 `ch395f_get_recv_len` 获取硬件缓冲可用数据量≤4096B一次性读空
- 验证模式递增字节offset & 0xFF接收端逐字节比较
---
## 7. 阶段 6DHCP
| 阶段 ID | TM-PHY-06 |
|---------|-----------|
| **类型** | PC 配合Windows 需管理员权限) |
| **耗时** | ~15 秒 |
| **入口** | `ENABLE_PHASE6_TESTS` |
| **注意** | **改变 CH395F IP**,必须独立运行 |
### TC-NET-601: DHCP 完整握手
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-601 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | DISCOVER → OFFER → REQUEST → ACK |
| **前置条件** | PC: `python ch395f_socket_test.py dhcp_server ...`(管理员权限)|
| **测试步骤** | 1. CH395F 发 DISCOVER → PC 回 OFFER → CH395F 发 REQUEST → PC 回 ACK<br>2. CH395F 应用 IP 配置 |
| **通过标准** | DHCP SUCCESSIP != 0.0.0.0 |
| **覆盖原则** | DP-03 |
### TC-NET-602: DHCP 后连通性验证
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-602 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | DHCP 分配 IP 后广播宣告 + HELLO 回显 |
| **前置条件** | TC-NET-601 通过 |
| **测试步骤** | 1. CH395F 广播 HELLO含 IP<br>2. PC 收 HELLO<br>3. PC 发 3×HELLO 回显请求<br>4. CH395F 回显 |
| **通过标准** | 3/3 HELLO 回显 100% |
---
## 8. 阶段 7Select/Poll I/O 多路复用(单连接)
| 阶段 ID | TM-PHY-07 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 持续运行 |
| **入口** | `ENABLE_PHASE7_TESTS` |
### TC-NET-701: net_select() 单 socket 就绪检测
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-701 |
| **优先级** | 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-702: net_select() 超时
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-702 |
| **优先级** | P1 |
| **类型** | 负向测试 |
| **标题** | 100ms 超时内无数据则返回 0 |
| **前置条件** | Socket 0 ESTABLISHEDPC 静置 |
| **测试步骤** | `net_select(1, &readfds, ..., 100ms)` |
| **通过标准** | 返回值 == 0无崩溃 |
---
## 9. 阶段 8边界条件测试
| 阶段 ID | TM-PHY-08 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 按需 |
| **入口** | `ENABLE_PHASE8_TESTS` |
### TC-NET-801: DMA 边界 14095 字节)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-801 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 发送 4095 字节SPI 最大载荷 1单次 DMA 可容纳) |
| **前置条件** | Socket 0 ESTABLISHED |
| **测试步骤** | MCU 生成 4095B 递增模式数据 → `net_send_sock(sk, data, 4095)` → 等待 PC 回显 → 验证 |
| **通过标准** | 4095B 完全匹配 |
| **覆盖原则** | DP-04 |
### TC-NET-802: DMA 边界4096 字节)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-802 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 发送 4096 字节(等于 SPI 最大载荷,填满单次 DMA |
| **测试步骤** | MCU 发 4096B → 等待回显 → 验证 |
| **通过标准** | 4096B 完全匹配 |
| **覆盖原则** | DP-04 |
### TC-NET-803: DMA 边界 +14097 字节)
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-803 |
| **优先级** | P0 |
| **类型** | 边界测试 |
| **标题** | 发送 4097 字节(超过 SPI 最大载荷,自动拆两次 DMA4096+1 |
| **测试步骤** | MCU 发 4097B → `net_send_locked` 自动拆 4096+1 → 等待回显 → 验证 |
| **通过标准** | 4097B 完全匹配,两次 DMA 无缝衔接 |
| **覆盖原则** | DP-04 |
### TC-NET-804: 零长度收发
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-804 |
| **优先级** | P1 |
| **类型** | 负向测试 |
| **标题** | `net_send_sock(sk, buf, 0)` 返回 1 |
| **测试步骤** | `net_send_sock(sk, s_phase8_tx, 0)` |
| **通过标准** | 返回 < 0无崩溃 |
### TC-NET-805: 连续快速发送 20 小包
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-805 |
| **优先级** | P1 |
| **类型** | 压力测试 |
| **标题** | 20 × 100 字节连续快速发送(验证 SENDBUF_FREE 等待机制) |
| **测试步骤** | 循环 20 次:`net_send_sock(sk, buf, 100)`,间隔 1ms |
| **通过标准** | 20/20 成功,回显 2000B 完全匹配 |
### TC-NET-806: 快速连接-断开 ×20 轮
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-806 |
| **优先级** | P1 |
| **类型** | 稳定性测试 |
| **标题** | 20 次快速连接-断开循环 |
| **测试步骤** | PC 连 8080 → 立即断开 → 等 s0 恢复 LISTEN → 重复 20 次 |
| **通过标准** | 20/20 不卡死s0 始终回到 LISTEN |
---
## 10. 阶段 9PHY 链路恢复
| 阶段 ID | TM-PHY-09 |
|---------|-----------|
| **类型** | 需物理拔插网线 |
| **耗时** | ~45 秒 |
| **入口** | `ENABLE_PHASE9_TESTS` |
### TC-NET-901: PHY 断开检测
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-901 |
| **优先级** | P1 |
| **类型** | 恢复测试 |
| **标题** | 拔网线后 `net_poll` 检测到 PHY_CHANGE + DISCONN |
| **测试步骤** | 拔网线 → 等 `net_poll` 检测 `CH395F_GINT_STAT_PHY_CHANGE` → 读 PHY = DISCONN |
| **通过标准** | 15 秒内串口 "PHY disconnect detected" |
| **覆盖原则** | DP-02, DP-05 |
### TC-NET-902: PHY 重连 + Socket 恢复
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-902 |
| **优先级** | P1 |
| **类型** | 恢复测试 |
| **标题** | 插回网线后 Socket 0 自动监听,新连接正常 |
| **前置条件** | TC-NET-901 通过(已拔线) |
| **测试步骤** | 1. 插网线 → 等 PHY_CHANGE → 链路恢复<br>2. s0 re-OPEN → LISTEN<br>3. PC 连接 → 64B 回显 |
| **通过标准** | 30 秒内回显成功,无需手动复位 |
| **覆盖原则** | DP-01, DP-05 |
---
## 11. 阶段 10KeepAlive 超时断开
| 阶段 ID | TM-PHY-10 |
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | ~65 秒60s idle + 15s probes |
| **入口** | `ENABLE_PHASE10_TESTS` |
| **配置** | KeepAlive 空闲 60s探测间隔 5s3 次 |
### TC-NET-1001: KeepAlive 触发断开
| 字段 | 值 |
|------|-----|
| **ID** | TC-NET-1001 |
| **优先级** | 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 |
---
## 12. Python 测试脚本参考
| 命令 | 模式 | 说明 |
|------|------|------|
| `tcp_server --port 8081` | PC 作 TCP Server | 测试 TCP Client阶段 2 |
| `tcp_client --ip ... --port 8080` | PC 作 TCP Client | 测试 TCP Echo阶段 4/7/8 |
| `tcp_file --ip ... --port 8080 --size 20480` | PC 作文件接收 | 测试大文件传输(阶段 5 |
| `udp_client --ip ... --port 60000` | PC 作 UDP Client | 测试 UDP Echo阶段 3 |
| `udp_server --port 8082` | PC 作 UDP Server | 接收 CH395F UDP 数据 |
| `stress --ip ... --port 8080 --count 10` | 压力测试 | 7 并发 × 10 轮 |
| `dhcp_server ...` | DHCP Server | 内置 DHCP阶段 6需管理员权限 |
### 通用参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `--ip` | 192.168.1.100 | 目标 IP |
| `--port` | 因模式而异 | 端口 |
| `--count` | 10 | 测试次数 |
| `--timeout` | 30 | 超时秒数 |
| `--size` | 1024 | 包大小 |
---
## 13. 已知陷阱
| ID | 陷阱 | 现象 | 修复 |
|----|------|------|------|
| TRAP-01 | ~~多连接初始化顺序~~ | 已废弃(单连接模式) | — |
| TRAP-02 | ~~数据 socket 缓冲重叠~~ | 已废弃(单连接模式) | — |
| 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` 分块 ✅ 已修复** |
---
## 附录 A通过/失败汇总
| 阶段 | 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 | P0 | 功能测试 | 🟡 |
| TM-PHY-04 | TC-NET-403 | P0 | 边界测试 | 🟡 |
| TM-PHY-04 | TC-NET-404 | P0 | 边界测试 | 🟡 |
| TM-PHY-04 | TC-NET-405 | P0 | 恢复测试 | 🟡 |
| TM-PHY-04 | TC-NET-406 | 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 | P1 | 功能测试 | ✅ |
| TM-PHY-07 | TC-NET-701 | P0 | 功能测试 | 🟡 |
| TM-PHY-07 | TC-NET-702 | P1 | 负向测试 | 🟡 |
| TM-PHY-08 | TC-NET-801 | P0 | 边界测试 | 🟡 |
| TM-PHY-08 | TC-NET-802 | P0 | 边界测试 | 🟡 |
| TM-PHY-08 | TC-NET-803 | P0 | 边界测试 | 🟡 |
| TM-PHY-08 | TC-NET-804 | P1 | 负向测试 | 🟡 |
| TM-PHY-08 | TC-NET-805 | P1 | 压力测试 | 🟡 |
| TM-PHY-08 | TC-NET-806 | P1 | 稳定性测试 | 🟡 |
| TM-PHY-09 | TC-NET-901 | P1 | 恢复测试 | ✅ |
| TM-PHY-09 | TC-NET-902 | P1 | 恢复测试 | ✅ |
| TM-PHY-10 | TC-NET-1001 | P1 | 功能测试 | 🟡 |
✅ = 已实现并验证 | 🟡 = 已实现待验证 | 🟠 = 待实现
## 附录 B设计原则覆盖
| 原则 | 覆盖用例 |
|------|----------|
| DP-01单 socket 生命周期 | TC-NET-202, 203, 401, 405, 406, 806, 902 |
| DP-02中断集中处理 | TC-NET-101, 102, 701, 901 |
| DP-03消息队列线程安全 | TC-NET-301, 302, 601 |
| DP-04DMA 边界安全 | TC-NET-403, 404, 501, 502, 801, 802, 803, 804, 805 |
| DP-05PHY 链路恢复 | TC-NET-901, 902 |
## 附录 CCH395F 边界条件速查
| 操作 | 大小 | 行为 |
|------|------|------|
| `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` **自动分块**,每块 4096B |
| `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` 之后**归零**,剩余数据**丢失** |