Files
STM32F4-Base/docs/CH395F_Test_Guide.md
2026-08-25 11:49:31 +08:00

48 KiB
Raw Blame History

CH395F 网络测试规范

1. 概述

本文档定义 CH395F 以太网驱动STM32F407ZGTxSPI2 接口)在单连接 socket 模式下的测试规范。测试按阶段TM-PHY-NN组织每个阶段包含独立测试用例TC-NET-NNN

设计原则

原则 描述 验证用例
DP-01 单连接 socketSocket 0 唯一复用监听和数据通道CONNECT 后切换数据通道DISCONNECT/TIMEOUT 后重新 OPEN → LISTEN TC-NET-201203, 601609, 906
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-901~905

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 归零导致剩余数据丢失
  • 发送 0Bnet_send 返回 -1NET_ERR_INVAL

测试环境

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~10_TESTS 完整网络栈netTask 运行 net_poll() 轮询testTask 经 net_socket API 在 TCP 监听循环中执行阶段 6~10

执行顺序严格递增:先烧录硬件层固件完成阶段 1→2→3→4→5全部通过再切换为 NET 层固件执行阶段 6→…→10。硬件层测试期间 netTask 轮询不存在,从根上排除中断偷取与 SPI 并发问题。

启用测试

编辑 test/ch395f_test.h,取消注释对应 ENABLE_PHASEx_TESTS 宏(同一模式内 Phase 6/7/8/9 可共存于 testTask 循环)。


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 已初始化
2. 未启用其他阶段

测试步骤与判定标准

操作 成功标准 失败标准
1 ch395f_check_exist()
SPI写: CMD=0x06, DATA=0x57
回读: ~0x57 = 0xA8
返回 0x00CH395F_ERR_SUCCESS
= 回读字节 0xA8 匹配 ~0x57
返回 0xFFCH395F_STATUS_NOT_DETECTED
= 回读字节 ≠ 0xA8,回显取反不匹配
2 ch395f_get_version() 版本值 == 0x4ACH395F 芯片版本) 版本值 ≠ 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 位)
注:测试入口处已预读一次清除 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 == 0x00CH395F_ERR_SUCCESS CMD_STATUS0x00Socket 操作失败)
8 ch395f_set_arp(10, 5)ch395f_get_cmd_status() CMD_STATUS == 0x00 CMD_STATUS0x00
9 ch395f_set_ttl(0, 64)ch395f_get_cmd_status() CMD_STATUS == 0x00 CMD_STATUS0x00

通过标准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 8081PC 作 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_SUCCESS0x00 返回值非 0x00Socket 硬件异常或 SPI 通信失败)
3 ch395f_tcp_connect(0) 发起 TCP 连接 返回 CH395F_ERR_SUCCESS0x00 返回值非 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 回到 CLOSED0x00)状态 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 8081TCP 回显服务器)
2. TC-NET-201 通过Socket 0 硬件正常)

测试步骤与判定标准(每轮 i = 0,1,2本地端口取 50000 + i 规避 TIME_WAIT

操作 成功标准 失败标准
1 ch395f_open_socket(0) 打开 Socket 0 返回 CH395F_ERR_SUCCESS0x00 返回值非 0x00Socket 硬件异常或 SPI 通信失败)
2 ch395f_tcp_connect(0) 发起 TCP 连接 返回 CH395F_ERR_SUCCESS0x00 返回值非 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 回到 CLOSED0x00)状态 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 Server192.168.1.2:8081 无监听)
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_SUCCESS0x00),命令异步下发不立即报错 命令返回值非 0x00Socket 硬件异常)
3 轮询 GET_GLOB_INT_STATUS(sock+4) 位,等待负向结果,超时窗 30s同时扫全局 UNREACH 位作备注 收到 SOCK_TIMEOUTDISCONNECT,且期间未收到 CH395F_SINT_STAT_CONNECT。按耗时细分:
<3s = RST 快速拒绝(端口关闭,实测主路径——芯片毫秒级上报 SOCK_TIMEOUT
≥3s = 真无应答(离线/防火墙丢包)。全局 UNREACHARP 失败)仅备注不影响判定
误收到 CONNECTPC 服务未关闭,环境错误——串口给出明确指引)或 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_TESTSch395fTestTask 启动后自动运行
出口 串口 "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——第 130 轮对应本用例PC 发 64B第 3160 轮对应 TC-302变长脚本 "HELLO" 就绪探针只回显、不计数,不占用用例预算。

测试步骤与判定标准

操作 成功标准 失败标准
1 配置 Socket 0proto=UDP、sour_port=60000、des=广播 255.255.255.255:0,后 open_socket(0) OPEN 返回 0x00 返回非 0x00Socket/SPI 异常)
2 轮询 GET_RECV_LENrlen > 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_TESTSch395fTestTask 启动后自动运行
出口 串口 "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.255192.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 各 512B255.255.255.255:60001 广播 4B 本机 IP 宣告 SENBUF_FREE 等待 2s
5 同 Socket 收 PC "HELLO"(5B),按包头源地址回显一次后关闭 窗口 30sPHASE4_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_TESTSch395fTestTask 启动后自动运行
出口 串口 "Phase 5: N/N PASSED (failed=0)" + 吞吐统计
注意 与 NET 层阶段 7 同主题不同层:本阶段用 ch395f_* 裸命令直驱芯片,不经 net_socket/netTask

编号说明:阶段 5 用例 501/502(顺序双测向完整性 + 变总量合规);原 PHY 测试(旧编号 901/902因板载拓扑不可测已删除见风险表 #1

为什么 4KB 接收缓冲不会丢数据TCP 流控原理)

PC 一次回放 256KB而 CH395F socket 接收缓冲默认仅 4KB——不丢数据的保证不靠缓冲够大,靠 TCP 流控(通告窗口)

数据不是全部驻留芯片,而是在一串缓冲区里"接力"

PC OS 发送缓冲(64~256KB) → 网络在途 → CH395F 接收缓冲(4KB) → MCU 读取

CH395F 是完整 TCP 栈 offload 芯片,内部实现 TCP 状态机,按接收缓冲剩余空间向 PC 通告接收窗口advertised window

  1. PC 发数据,芯片收进自己的 4KB 接收环形缓冲;
  2. 缓冲快满 → 通告窗口收缩直至 窗口=0 → PC 的 sendall 阻塞TCP 背压),剩余数据停在 PC 自身 OS 发送缓冲,不再发出;
  3. MCU GET_RECV_LENREAD_RECV_BUF 读走 → 缓冲腾空 → 芯片重新通告窗口 → PC 恢复发送。

因此是背压联动、自节流:数据永不因芯片缓冲溢出被丢弃,只会"堵一会儿"。MCU 平均读取速率 ≥ PC 发送速率即可持续吞吐(本测试每 2ms 轮询、每次读 4KB平均速率远高于发送速率缓冲常年接近空

"先查长度再读"GET_RECV_LENREAD_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 = socket 默认发送缓冲上限,同时命中 DP-04 的 DMA 单次读边界
  • 双重完整性:逐字节比对 + 全量 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 --bulkchunks×PHASE5_CHUNK_SIZE--chunkPHASE5_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 PASSEDchecksum 逐档相等 —— 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为瓶颈。


已知风险与待实施项

# 事项 影响 状态
1 原 PHY 测试(旧编号 901/902删除CH395F 直连板载 RTL8305NBI 交换机PHY 恒 link up拔插外线上联无法在芯片 PHY 产生变化事件2026-08-24 实测 0/1 FAIL 后决议) 编号 901/902 弃用 已移除
2 DP-05PHY 链路恢复)失去覆盖对象 设计原则表缩减 已同步删除
3 吞吐基线2026-08-24 实测TX 420449 KB/s、RX 364391 KB/s随总量/块大小基本恒定,疑似 SPI 总线为瓶颈 后续 SPI 时钟/驱动优化以该基线为参照 已记录

7. 阶段 6NET 层 TCPServer 回显 + Client 主动连接)

阶段 ID TM-PHY-06
类型 PC 配合netTask 运行
耗时 持续运行
入口 ENABLE_PHASE6_TESTSch395fTestTask TCP 回显循环中运行
出口 用户终止

两部分组成:

  • Server 模式TC-NET-601~606MCU 监听 8080PC 主动连接,验证回显通路 —— 已实现 🟡
  • Client 模式扩展TC-NET-607/608/609MCU 经 BSD Socket API 主动连接 PC Server补全 net_connect() 路径 —— 待实现 🟠(原编号 204/205/206属阶段 2 NET 层部分2026-08 重排)

TC-NET-601: Socket 0 单连接生命周期

字段
ID TC-NET-601
优先级 P0
类型 功能测试
标题 Socket 0: OPEN → TCP_LISTEN → ESTABLISHED单连接
前置条件 1. net_init("192.168.1.100") 成功
2. net_listen(sock0) 已调用
3. PC: python ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080
测试步骤 1. PC 连接 8080
2. Socket 0 收到 CONNECT → ESTABLISHED
3. 发数据 → 回显
通过标准 仅 s0 状态变化;串口 s0:int=0x08 → ESTABLISHED
覆盖原则 DP-01

TC-NET-602: 64 字节小包回显

字段
ID TC-NET-602
优先级 P0
类型 功能测试
标题 10 轮 64B TCP 回显
测试步骤 PC 发 10×64BMCU 回显
通过标准 10/10 匹配

TC-NET-603: 1460 字节 MSS 回显

字段
ID TC-NET-603
优先级 P0
类型 边界测试
标题 MSS 大小1460BTCP 回显
测试步骤 PC 发 1460BMCU 回显
通过标准 1460B 完全匹配,无截断

TC-NET-604: 4096 字节缓冲满回显

字段
ID TC-NET-604
优先级 P0
类型 边界测试
标题 填满 Socket 0 接收缓冲4096B回显
前置条件 DMABUF=4100 可一次性读空
测试步骤 PC 发 4096BMCU 经 DMA 一次性读取并回显
通过标准 4096B 完全匹配
覆盖原则 DP-04

TC-NET-605: 断开重连

字段
ID TC-NET-605
优先级 P0
类型 恢复测试
标题 PC 断开后 Socket 0 自动重新监听并接受新连接
测试步骤 1. PC 断连 → s0 收 DISCONNECT → CLOSED → re-OPEN → LISTEN
2. PC 重连 → s0 → ESTABLISHED
3. 发 64B → 验证回显
通过标准 重连成功,回显正常
覆盖原则 DP-01

TC-NET-606: 超时恢复

字段
ID TC-NET-606
优先级 P1
类型 恢复测试
标题 KeepAlive 超时后 Socket 0 自动重新监听
测试步骤 1. PC 断连后停止响应
2. 等待 KeepAlive 触发 TIMEOUT
3. s0 → CLOSED → re-OPEN → LISTEN
4. PC 重连 → 验证回显
通过标准 自动恢复,无需手动复位
覆盖原则 DP-01

Client 模式扩展:主动连接 PCTC-NET-607 / 608 / 609🟠 待实现)

与硬件层阶段 2 的 TC-NET-201/202/203 一一对应(芯片行为 → API 封装),验证 net_socket / net_connect / net_send / net_recv / net_close 对 TCP Client 主路径及异常路径的正确封装。 本组用例原编号 204/205/206原阶段 2 的 NET 层部分2026-08 重排后为 607/608/609实现于 ENABLE_PHASE6_TESTS 固件中。

TC-NET-607NET 层): TCP Client 连接与数据交换

字段
ID TC-NET-607
优先级 P0
类型 功能测试NET 层)
标题 net_socket TCP Client → PC TCP Server64 字节收发
前置条件 1. PC: python ch395f_socket_test.py tcp_server --port 8081TCP 回显服务器)
2. net_init() 已完成(g_net_ready == 1netTask 周期 net_poll() 运行中)
3. 建议:硬件层固件中 TC-NET-201/202/203 已通过(跨构建前置,见「构建模式与执行顺序」)

测试步骤与判定标准

操作 成功标准 失败标准
1 net_socket(NET_AF_INET, NET_SOCK_STREAM, 0) 创建 TCP socket 返回 sockfd0~7),非 -1 返回 -1(无空闲 Socket 或 net 层未初始化)
2 构造 struct net_sockaddr_insin_family=NET_AF_INETsin_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 且与原数据逐字节一致 返回值 < 0(超时/断开)或 ≠ 64(数据不完整)或任意字节不符
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-608NET 层): TCP Client 关闭重连 ×3 轮

字段
ID TC-NET-608
优先级 P0
类型 功能测试 / 恢复测试NET 层)
标题 close→重连循环 3 轮,每轮含数据交换
前置条件 同 TC-NET-607

测试步骤与判定标准(每轮 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 字节且逐字节一致 收发失败或数据不符
4 net_close(sockfd) 关闭并释放控制块 返回 0 返回 -1

通过标准3/3 轮全部通过0 失败。任一失败 → 标记 FAIL

覆盖原则DP-01net 层 open→connect→close→re-open→reconnect

TC-NET-609NET 层): TCP Client 连接超时

字段
ID TC-NET-609
优先级 P1
类型 负向测试NET 层)
标题 目标端口无服务时 net_connect 超时
前置条件 1. PC 运行 TCP Server192.168.1.2:8081 无监听)
2. TC-NET-607 通过

测试步骤与判定标准

操作 成功标准 失败标准
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 失败处理)


8. 阶段 7大文件传输20KB+

阶段 ID TM-PHY-07
类型 PC 配合netTask 运行
耗时 取决于文件大小和速率
入口 ENABLE_PHASE7_TESTS
配置 PHASE7_FILE_SIZE(默认 20480

TC-NET-701: 20KB 分块传输

字段
ID TC-NET-701
优先级 P0
类型 功能测试
标题 20480 字节 TCP 分块收发(>Socket 0 硬件缓冲 4KB
前置条件 net_send_locked 支持自动分块(每块 ≤4096B
测试步骤 1. MCU 填 20KB 递增模式数据
2. MCU 分块发送,每块 4096B
3. PC 回显
4. MCU 接收并逐字节验证
通过标准 20KB 完全匹配,每字节验证通过
覆盖原则 DP-04

TC-NET-702: 64KB 传输recv_len 上限)

字段
ID TC-NET-702
优先级 P1
类型 边界测试
标题 65535 字节传输(recv_len uint16 上限)
前置条件 TC-NET-701 通过
测试步骤 同 501文件大小 65535
通过标准 65535B 完全匹配
覆盖原则 DP-04

实现说明

phase7_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接收端逐字节比较

9. 阶段 8Select/Poll I/O 多路复用(单连接)

阶段 ID TM-PHY-08
类型 PC 配合netTask 运行
耗时 持续运行
入口 ENABLE_PHASE8_TESTS

TC-NET-801: net_select() 单 socket 就绪检测

字段
ID TC-NET-801
优先级 P0
类型 功能测试
标题 net_select() 在 Socket 0 数据到达时正确返回
前置条件 Socket 0 ESTABLISHED
测试步骤 1. PC 发送数据
2. net_select(1, &readfds, ...) → 返回 > 0
3. NET_FD_ISSET(0, &readfds) == true
4. net_recv()net_send() 回显
通过标准 回显成功
覆盖原则 DP-02select 内部调 net_poll

TC-NET-802: net_select() 超时

字段
ID TC-NET-802
优先级 P1
类型 负向测试
标题 100ms 超时内无数据则返回 0
前置条件 Socket 0 ESTABLISHEDPC 静置
测试步骤 net_select(1, &readfds, ..., 100ms)
通过标准 返回值 == 0无崩溃

10. 阶段 9边界条件测试

阶段 ID TM-PHY-09
类型 PC 配合netTask 运行
耗时 按需
入口 ENABLE_PHASE9_TESTS

TC-NET-901: DMA 边界 14095 字节)

字段
ID TC-NET-901
优先级 P0
类型 边界测试
标题 发送 4095 字节SPI 最大载荷 1单次 DMA 可容纳)
前置条件 Socket 0 ESTABLISHED
测试步骤 MCU 生成 4095B 递增模式数据 → net_send_sock(sk, data, 4095) → 等待 PC 回显 → 验证
通过标准 4095B 完全匹配
覆盖原则 DP-04

TC-NET-902: DMA 边界4096 字节)

字段
ID TC-NET-902
优先级 P0
类型 边界测试
标题 发送 4096 字节(等于 SPI 最大载荷,填满单次 DMA
测试步骤 MCU 发 4096B → 等待回显 → 验证
通过标准 4096B 完全匹配
覆盖原则 DP-04

TC-NET-903: DMA 边界 +14097 字节)

字段
ID TC-NET-903
优先级 P0
类型 边界测试
标题 发送 4097 字节(超过 SPI 最大载荷,自动拆两次 DMA4096+1
测试步骤 MCU 发 4097B → net_send_locked 自动拆 4096+1 → 等待回显 → 验证
通过标准 4097B 完全匹配,两次 DMA 无缝衔接
覆盖原则 DP-04

TC-NET-904: 零长度收发

字段
ID TC-NET-904
优先级 P1
类型 负向测试
标题 net_send_sock(sk, buf, 0) 返回 1
测试步骤 net_send_sock(sk, s_phase9_tx, 0)
通过标准 返回 < 0无崩溃

TC-NET-905: 连续快速发送 20 小包

字段
ID TC-NET-905
优先级 P1
类型 压力测试
标题 20 × 100 字节连续快速发送(验证 SENDBUF_FREE 等待机制)
测试步骤 循环 20 次:net_send_sock(sk, buf, 100),间隔 1ms
通过标准 20/20 成功,回显 2000B 完全匹配

TC-NET-906: 快速连接-断开 ×20 轮

字段
ID TC-NET-906
优先级 P1
类型 稳定性测试
标题 20 次快速连接-断开循环
测试步骤 PC 连 8080 → 立即断开 → 等 s0 恢复 LISTEN → 重复 20 次
通过标准 20/20 不卡死s0 始终回到 LISTEN

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)
2. ch395f_set_keepalive_intvl(5000)
3. ch395f_set_keepalive_cnt(3)
4. PC 连接后静置(不发数据)
测试步骤 1. PC 连接 8080不发数据
2. 等待 KeepAlive 探测60s idle + 3×5s = 75s 最大)
3. CH395F 接收 TIMEOUT → s0 → CLOSED → LISTEN
通过标准 超时时间 ≥ 60ss0 回到 LISTEN

12. Python 测试脚本参考

命令 模式 说明
tcp_server --port 8081 PC 作 TCP 回显服务器(多连接循环) 测试 TCP Client阶段 2 硬件层;阶段 6 Client 扩展 607/608/609
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 数据
stress --ip ... --port 8080 --count 10 压力测试 7 并发 × 10 轮
dhcp_server ... DHCP Server 内置 DHCP阶段 4需管理员权限

通用参数

参数 默认值 说明
--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_lockedNET_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 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-06 TC-NET-607 P0 功能测试 🟠
TM-PHY-06 TC-NET-608 P0 恢复测试 🟠
TM-PHY-06 TC-NET-609 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 P1 负向测试 🟡
TM-PHY-09 TC-NET-901 P0 边界测试 🟡
TM-PHY-09 TC-NET-902 P0 边界测试 🟡
TM-PHY-09 TC-NET-903 P0 边界测试 🟡
TM-PHY-09 TC-NET-904 P1 负向测试 🟡
TM-PHY-09 TC-NET-905 P1 压力测试 🟡
TM-PHY-09 TC-NET-906 P1 稳定性测试 🟡
TM-PHY-10 TC-NET-1001 P1 功能测试 🟡

= 已实现并验证 | 🟡 = 已实现待验证 | 🟠 = 待实现

附录 B设计原则覆盖

原则 覆盖用例
DP-01单 socket 生命周期 TC-NET-202, 203, 601, 605, 606, 906
DP-02中断即读即清 TC-NET-101, 102, 501, 502, 801
DP-03消息队列线程安全 TC-NET-301, 302, 401
DP-04DMA 边界安全 TC-NET-501, 502, 603, 604, 701, 702, 901, 902, 903, 904, 905

附录 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 返回 -1NET_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 之后归零,剩余数据丢失