Files
STM32F4-Base/docs/CH395F_Trap_Records.md

539 lines
28 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 驱动陷阱记录
记录开发过程中遇到的所有坑、根因、解决方案,避免重复踩坑。
---
## Trap 01Socket 4~7 自动分配不到
### 现象
多连接模式下最多接受 3 个客户端Socket 1~3Socket 4~7 的 CONNECT 中断从不触发,新客户端 SYN 被静默丢弃。
### 根因
`net_listen()` 中数据 Socket 的配置顺序错误。CH395F 在 `TCP_LISTEN` 时刻**一次性扫描**当前所有可用 Socket 并建立内部查找表。如果数据 Socket 4~7 在 `TCP_LISTEN` **之后**才配置缓冲区/协议/端口,自动分配逻辑不认识它们。
### 正确顺序
1. 先配置数据 Socket 1~7`SET_SEND_BUF → SET_RECV_BUF → SET_SOUR_PORT → SET_PROTO_TYPE_TCP`
2. 最后配置监听 Socket 0`SET_SEND_BUF → SET_RECV_BUF → SET_PROTO_TYPE_TCP → SET_SOUR_PORT → OPEN_SOCKET → TCP_LISTEN`
### 注意事项
- 数据 Socket 不调 `OPEN_SOCKET`,由 CH395F 在连接到达时自动打开
- DISCONNECT 后 CH395F 自动关闭 Socket但保留协议/端口/缓冲区配置,可被再次分配
- 手册 §9.2.6 要求数据 Socket **先设源端口再设协议类型**(与监听 Socket 顺序相反)
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_listen()`
### 发现时间
Phase 8 压力测试2025-07
---
## Trap 02数据 Socket 缓冲区重叠
### 现象
多连接模式下多个 Socket 数据互相覆盖,收到错误数据或客户端挂死。
### 根因
CH395F 默认只为 Socket 0~3 各分配独立收发缓冲(共 48 块 × 512B 用满,见手册 §5.45Socket 4~7 为零块。`net_listen()` 未对 Socket 4~7 显式分配独立缓冲区块,后续代码误用相同块号(如 28~31导致数据重叠。
### 解决方案
每个数据 Socket 独占 4 个缓冲区块2 发 + 2 收),按 `ds * 4` 基址分配,确保不重叠:
| Socket | 发送块 | 接收块 |
|--------|--------|--------|
| 1 | 4~5 | 6~7 |
| 2 | 8~9 | 10~11 |
| 3 | 12~13 | 14~15 |
| 4 | 16~17 | 18~19 |
| 5 | 20~21 | 22~23 |
| 6 | 24~25 | 26~27 |
| 7 | 28~29 | 30~31 |
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_listen()`
### 发现时间
Phase 8 压力测试2025-07
---
## Trap 03DHCP 包 xid 偏移错误
### 现象
Python DHCP Server 收到 DISCOVER 包后无法匹配 Offer/Ack 的 xid握手失败。
### 根因
错误使用了 `data[232:236]` 作为 xidWireshark 某旧版显示误导),实际 DHCP 标准偏移为 `data[4:8]`
### 正确偏移
- xid`data[4:8]`
- msg_type`data[242]`
- client_mac`data[28:34]`
- magic cookie`data[236:240]`
### 对应文件
`test/ch395f_socket_test.py` → DHCP server 模式
### 发现时间
Phase 6, 2025-07
---
## Trap 04UDP 发送后必须等待 SENDBUF_FREE
### 现象
UDP 发送后立即执行接收操作,接收长度始终为 0 或数据错误。
### 根因
CH395F 手册要求每次 `WRITE_SEND_BUF` 后必须等待 `SINT_STAT_SENBUF_FREE` 中断,否则下次写入或接收操作可能失败。
### 解决方案
每次 `ch395f_write_send_buf()` 后轮询 `ch395f_get_sock_int_status()` 检查 `SINT_STAT_SENBUF_FREE` 标志。
### 对应文件
`Drivers/BSP/CH395F/ch395f.c` / test code in `ch395f_test.c`
### 发现时间
Phase 6 广播宣告模式2025-07
---
## Trap 05CH395F 与 RTL8305NBI 自动协商不兼容
### 现象
`ch395f_get_phy_status()` 始终返回 `PHY_DISCONN`,链路无法建立。
### 根因
CH395F PHY 与 RTL8305NBI-CG 直连时自动协商失败。
### 解决方案
初始化协议栈后强制设为 100M 全双工:
```c
ch395f_set_phy(CH395F_PHY_100M_FULL);
```
### 对应文件
`Drivers/BSP/CH395F/ch395f.c`
### 发现时间
项目初期硬件联调
---
## Trap 06TCP KeepAlive 参数必须为 500ms 倍数
### 现象
设置 KeepAlive 后 TCP 连接几秒内 TIMEOUT 断开。
### 根因
CH395F 内部定时器以 500ms 为基准单位,传入非 500ms 倍数的值导致未定义行为。
`IDLE` 必须 > `INTVL`
### 正确值
```c
ch395f_set_keepalive_idle(60000); // 60s500 倍数)
ch395f_set_keepalive_intvl(5000); // 5s500 倍数)
ch395f_set_keepalive_cnt(3); // 3 次
```
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_init()`
### 发现时间
Phase 2 测试2025-07
---
## Trap 07TCP 关闭重连直接 close不要 disconnect
### 现象
`disconnect → close` 后 Socket 卡在 `FIN_WAIT_2` 数分钟,无法重新打开。
### 根因
`disconnect` 发送 FIN 将 Socket 推入 `FIN_WAIT_2`,此后 `close` 不再发 RST必须等远端发 FIN 才能关闭。
### 解决方案
直接调用 `ch395f_close_socket()`,在 ESTABLISHED 或 CLOSE_WAIT 下会发 RST 立即终止。
关闭后轮询 `ch395f_get_socket_status()` 等待 `sock=0x00``open_socket`
### 发现时间
Phase 2 TCP Client 测试2025-07
---
## Trap 08RECV 中断电平触发,避免无限循环
### 现象
`net_poll()` 中 RECV 中断无限触发主循环echo 等)永远得不到执行。
### 根因
CH395F 的 RECV 中断是电平触发的——只要接收缓冲区有数据就保持 `INT#` 低电平。
`do { ... } while(int_status != 0)` 会无限循环。
### 解决方案
`net_poll()` 使用 `do { ... } while(0)` 每次只处理一批中断,由主循环负责读取数据。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_poll()`
---
## 附CH395F 驱动关键点
### 初始化顺序
- 必须按手册9.2.1节顺序:`SET_MAC → SET_IP/GWIP/MASK → INIT_CH395 → SET_PHY`
- **IP/网关/掩码必须在 `INIT_CH395` 之前设置**INIT 会读取并锁定当前寄存器值到协议栈
- **`SET_PHY` 必须在 `INIT_CH395` 之后**,复位 MAC/PHY 建立物理链路,不影响已锁定的协议栈参数
- **`CMD_PING_ENABLE` 不需要显式调用**INIT 后默认可用
### SPI 通信
- 每次 SPI 事务需调用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹
- **大数据量收发使用 SPI2 DMA**`ch395f_write_send_buf()``ch395f_read_recv_buf()` 已改造为 DMA 批量传输DMA1_Stream3 RX, DMA1_Stream4 TX命令和配置操作仍使用逐字节轮询
- DMA 缓冲区:`s_spi2_dma_tx_buf[1500]` / `s_spi2_dma_rx_buf[1500]`4 字节对齐
### TCP 参数配置(必须在 INIT_CH395 之前)
- **重传参数**`SET_RETRAN_COUNT`默认12次最大20`SET_RETRAN_PERIOD`默认500ms最大1000ms总重传时间 = 次数 × 周期
- **KeepAlive 参数必须为 500ms 的倍数**`IDLE`默认20000ms`INTVL`默认15000ms且 IDLE > INTVL传入非 500 倍数的值导致 CH395F 内部定时器异常
- **KeepAlive 默认关闭**:需在 `SINT_STAT_CONNECT` 后调用 `ch395f_set_keepalive_enable(sock, 1)` 启用
### Socket 4~7 缓冲区
- CH395F 默认只给 Socket 0~3 分配独立收发缓冲(共 48 块 × 512B 用满Socket 4~7 为零块
- 多连接模式下使用 Socket 4~7 时,**必须在 `open_socket` 之前**显式分配:
- `ch395f_set_send_buf(sock, start_block, count)`
- `ch395f_set_recv_buf(sock, start_block, count)`
- 不分配会导致发送数据为固定垃圾内容(`0x0028` 填充)、接收缓冲区无法存储数据
### TCP 关闭重连
- **直接调用 `ch395f_close_socket()`,不要先调 `ch395f_tcp_disconnect()`**
-`disconnect → close`disconnect 发 FIN 推入 FIN_WAIT_2close 不再发 RSTSocket 卡住数分钟
-`close` 直接:在 ESTABLISHED 或 CLOSE_WAIT 下 close 发 RST 立即终止,瞬间回到 CLOSED
- 关闭后需轮询 `ch395f_get_socket_status()` 等待 `sock=0x00``open_socket`
### SOCK_TIMEOUT 处理
- 长时间无数据时可能触发 `SINT_STAT_SOCK_TIMEOUT`**不应视为致命错误**——记录日志后继续操作即可,不要因此关闭 Socket
### UDP 模式区分
- **DesIP=0xFFFFFFFF → UDP Server 模式**:接受任意来源数据,接收数据前 8 字节为信息头(`reserved[2] src_port[2] src_ip[4]`),回发前需设置 `SET_DES_IP``SET_DES_PORT`
- **DesIP=具体 IP → UDP Client 模式**:只接收指定 IP:Port 的数据,接收数据无信息头
### UDP 发送缓冲
- 每次 `ch395f_write_send_buf()` 后必须等待 `SINT_STAT_SENBUF_FREE` 中断,否则下次写入会被 CH395F 静默丢弃
---
## Trap 09PHY link up 后立即 open_socket 失败
### 现象
`net_listen()` 在 PHY_CHANGE 中断同一时刻调用 `ch395f_open_socket()` 返回错误码(非 BUSY 超时),导致首次 listen 失败。3 秒后重试成功。
```
[NET] PHY_CHANGE: 0x08 ← PHY 刚 link up (100M FULL)
[ERR] error listening on socket ← 同一秒 open_socket 失败
[FTP] waiting for connection... ← 3秒后重试成功
```
### 根因
CH395F 的 PHY link up 后,内部 TCP/IP 协议栈需要额外时间完成初始化ARP 缓存、路由表等)。在 PHY_CHANGE 中断触发的同一 `net_poll()` 迭代内立即调用 `OPEN_SOCKET`,芯片可能返回 `ERR_BUSY` 或其他错误码。
### 解决方案
1. **应用层重试兜底**FTP Server (`lftpd_start`) 的 `while(1)` 循环中listen 失败后 `vTaskDelay(3000)` 重试3 秒足够 PHY 稳定
2. **启动延时消峰**FTP 任务启动时 `osDelay(7000)`,等 netTask 完成 `net_init() → PHY 协商 → 首轮 net_poll()` 后再发起 listen此时 PHY 已稳定 2 秒以上,首次就成功
### 注意事项
- 此问题在单连接模式 (`FUN_PARA=0x00`) 和多连接模式 (`0x02`) 下均存在,与 FUN_PARA 无关
- 如果多个应用任务同时启动 listen可能全部首次失败、全部重试成功——建议各任务错峰启动
---
## Trap 10消息队列值拷贝导致结果无法回传
### 现象
`net_send()` / `net_recv()` / `net_listen()` / `net_accept()` / `net_close()` 等线程安全 API通过消息队列委托 netTask 执行)永远返回 `-1`,即使 netTask 内部操作成功。
### 根因
CMSIS-RTOS v2 `osMessageQueue` 传递 `net_msg_t` 结构体是**值拷贝**。调用方 puts 后 `msg.result = -1`栈变量netTask 侧 `osMessageQueueGet` 拿到的是**队列中的副本**,修改副本的 `msg.result = 0``xTaskNotifyGive` 通知调用方。调用方的栈变量 `msg.result` 仍然是 `-1`,从未被更新。
```c
// 调用方ftpTask
msg.result = -1;
osMessageQueuePut(&msg); // 值拷贝到队列
xTaskNotifyWait(&notified); // 等待通知
return msg.result; // 永远是 -1
// 处理方netTask
osMessageQueueGet(&msg); // 拿到的是队列副本
msg.result = actual_result; // 修改的是副本
xTaskNotify(...); // 通知回去了,但值丢了
```
### 解决方案
不通过消息队列回传结果,改用 **FreeRTOS 通知值**notification value携带返回值
**ProducernetTask**
```c
xTaskNotify(msg.caller, (uint32_t)msg.result, eSetValueWithOverwrite);
```
**Consumer调用方**
```c
uint32_t notified;
xTaskNotifyWait(0, 0, &notified, portMAX_DELAY);
msg.result = (int)notified;
```
### 注意事项
- 指针成员(`msg.buf`不受值拷贝影响——netTask 拿到的是同一个指针,可以读写调用方的缓冲区
- `net_connect()` 不使用消息队列(在 netTask 内直接调用 `_locked` 版本),不受此 bug 影响
- 内核级 API`net_send_sock` / `net_recv_sock` / `net_listen_locked`)直接操作,同样不受影响
---
## Trap 11`net_accept_locked` 拒绝 standalone_accept 的 ESTABLISHED 状态
### 现象
FTP 客户端 TCP 连接成功standalone accept220 欢迎语不发送,`lftpd_inet_accept()` 死循环。客户端连接成功但永远收不到任何 FTP 响应。
### 根因
`net_accept_locked()` 入口检查:
```c
if (p_listen_sock->state != NET_SOCK_STATE_LISTENING) {
return -1; // ← 直接拒绝!
}
```
standalone accept 后 state 已变为 `ESTABLISHED`,永远到不了后续的 `standalone_accept` 检查——死锁。
### 解决方案
```c
if (p_listen_sock->state != NET_SOCK_STATE_LISTENING &&
!(p_listen_sock->standalone_accept && p_listen_sock->state == NET_SOCK_STATE_ESTABLISHED)) {
return -1;
}
```
允许 `standalone_accept + ESTABLISHED` 组合通过。
### 注意事项
- 不影响多连接模式(多连接监听 Socket 状态始终为 LISTENING由数据 Socket 承载连接)
- 仅影响 standalone_accept 模式PASV 数据通道、单连接 FTP
---
## Trap 12FatFS `FF_USE_LFN=0` + 路径 `/` 前缀 → `FR_INVALID_NAME`
### 现象
FTP STOR 上传文件时 `f_open("/upload_test.txt", FA_WRITE | FA_CREATE_ALWAYS)` 返回 `err=6`FR_INVALID_NAME`upload_test.txt` 文件名 11+3 字符 > 8.3 限制同样触发 err=6。
### 根因
1. `ffconf.h``FF_USE_LFN = 0`(长文件名禁用),最大文件名 8+3 字符
2. FatFS 不接受 `/file` 格式的路径(需要 `file``0:file`
3. `lftpd_io_canonicalize_path("/", "file")` 输出必然带 `/` 前缀
4. 根目录路径 `"/"` 剥离后为空字符串,`f_stat("")` 同样失败
### 解决方案
```c
/* 剥离 "/" 前缀,根目录用 "." 表示 */
static const char *to_fatfs_path(const char *path) {
if (path == NULL || *path == '\0') return ".";
if (*path == '/') {
path++;
if (*path == '\0') return ".";
}
return path;
}
```
所有 `lftpd_io_*` 函数调用 FatFS 前统一使用 `to_fatfs_path(path)`
### 注意事项
- 长文件名需设置 `FF_USE_LFN = 1` 并配置 `FF_LFN_UNICODE`,会显著增加 RAM 占用
- `CWD .``CWD /` 同样受此问题影响,修复后正常工作
---
## 附FTP 支持改造完整问题清单
| # | 问题 | 类型 | 修复位置 |
|---|------|------|----------|
| 1 | 消息队列值拷贝6 个 API 永远返回 -1 | Trap 10 | `net_socket.c` |
| 2 | PHY link up 后 open_socket 失败 | Trap 09 | `freertos.c` + `lftpd.c` |
| 3 | `net_accept_locked` 拒绝 ESTABLISHED | Trap 11 | `net_socket.c` |
| 4 | CH395F 多连接模式不支持多监听 | 架构 | `net_socket.c: fun_para=0x00` |
| 5 | PASV 端口 0net_bind 无自动分配) | 缺功能 | `net_socket.c: s_dynamic_port` |
| 6 | FatFS 路径 `/` 不兼容 + LFN 禁用 | Trap 12 | `lftpd_io.c: to_fatfs_path()` |
| 7 | `cmd_pasv` 连关连开 listen 失败 | Trap 09 子类 | `lftpd.c: 3次重试` |
| 8 | `ftpTask` 启动时 PHY 未稳定 | Trap 09 子类 | `freertos.c: osDelay(7000)` |
| 9 | 会话结束后立即重建监听失败 | Trap 09 子类 | `lftpd.c: vTaskDelay(200ms)` |
| 10 | `s_file_open` 单文件限制 | 已知限制 | `lftpd_io.c` (全局变量) |
| 11 | CH395F DMA buffer 太小,分批读导致 recv_len 归零 | Trap 13 | `ch395f.c + lftpd.c` |
| 12 | `malloc` 嵌入式环境失败 | Trap 13 子类 | `lftpd.c: static buffer` |
| 13 | `net_listen_locked` 缓冲区覆盖s0/s1 争用 block 0-3 | Trap 02 翻版 | `net_socket.c: 单连接用默认 buf` |
| 14 | CH395F TIMEOUT 立即触发(数据连接无数据) | Trap 09 子类 | `net_socket.c: CLOSED 状态处理` |
| 15 | 发送单次写 >1KB 写穿 1KB 硬件发送 FIFO | Trap 20 | `net_socket.c: NET_SEND_CHUNK_MAX` |
| 15 | `NET_RECV_TIMEOUT_MS=5s` 控制通道超时断开 | 配置 | `net_config.h: 30000` |
| 16 | `receive_file` 0 bytes 当作成功 | 逻辑 | `lftpd.c: total==0 则失败` |
| 17 | MobaXterm 连数据端口不发数据(客户端特殊行为) | 外部 | Python 抓包确认,超时恢复 |
---
## Trap 13CH395F `recv_len` 分批读取时归零
### 现象
MobaXterm 上传文件时 CH395F 收到数据(`s1 int=0x04` RECV_OK 持续触发),但 `ch395f_get_recv_len(1)` 第一次返回 2920读 1024 字节后第二次永远返回 0。`GINT=0x0020` 反复触发但无数据可读——死循环。
### 根因
`ch395f_read_recv_buf` 读部分数据后CH395F 的 `recv_len` 寄存器**被重置为 0**(非递减)。缓冲区中剩余的 1896 字节仍然存在RECV_OK 标志为真),但 `ch395f_get_recv_len` 报告 0。
同时 `CH395F_DMA_BUF_SIZE=1500`header 4 + max 1496 data无法一次读取完整的 4096 字节 CH395F 接收缓冲区。
### 解决方案
1. `CH395F_DMA_BUF_SIZE` 从 1500 增大到 4100支持一次读 4096 字节 header+data
2. `receive_file` 缓冲区从 1024 增大到 4096一次性读空 CH395F 接收缓冲区
3. 不能使用 `malloc`(嵌入式堆可能不可用),改用 `static unsigned char s_recv_buf[4096]`
### 注意事项
- CH395F 接收缓冲区默认 socket 0: 4096B, socket 1: 4096B。读空后客户端继续发数据recv_len 重新递增——正常
- 清理中断状态后 `handle_timeout_event` 中的 `ch395f_close_socket` 必须显式调用,确保下次 `open_socket` 成功
---
## Trap 14close 后 DISCONNECT 滞后锁存,污染下次会话首次中断读取
### 现象
`close_socket` → 轮询 `GET_SOCKET_STATUS` 已确认 CLOSED 后,紧接的下一会话(重新 open + connect在第一次读 `GET_INT_STATUS_SN` 时读到**上一会话遗留的 DISCONNECT 事件**0ms 即命中),导致负向判定/状态机误判。实测TC-202 三轮重连后立即跑 TC-203PC 端握手实际成功(连接被接受),固件却因陈旧 DISCONNECT 抢先判"未连接"。
### 根因
- `wait-CLOSED` 轮询用的 `GET_SOCKET_STATUS` 是**纯查询,不清中断**
- TCP 拆除过程中断DISCONNECT可能在与 CLOSED 状态达成几乎同时、甚至之后才锁存;
- 该事件无人消费,跨会话存活到下一次 `open_socket` 之后。
### 解决
1. **预清**:新会话 OPEN 前读一次 `GET_GLOB_INT_STATUS_ALL` + `GET_INT_STATUS_SN` 丢弃陈旧事件;
2. **反核实**:依据中断做结论前,用 `GET_SOCKET_STATUS` 验证实际状态一致(如"负向事件 vs ESTABLISHED"矛盾时以状态为准)。
### 参考
`test/ch395f_test_task.c: phase2_tc203()`(双保险实现);同类思路见 Phase 1 入口的 PHY_CHANGE 预清。
---
## Trap 15DHCP 成功后 IP 寄存器提交滞后
### 现象
`GET_DHCP_STATUS` 已置 0x00 后立即 `GET_IP_INF`,回读到的仍是**旧静态配置**(如 192.168.1.100而芯片实际已按新租约192.168.1.77)正常收发——串口打印与 Socket6 广播宣告均带出过期 IP。2026-08-24 上板实测:固件各检查全 PASS6/6但 PC 侧 HELLO 发往租约地址才得到应答,三方数据矛盾构成假阳性。
### 根因
DHCP 状态位仅代表协商流程完成IP/MASK/GW 寄存器的提交与状态置位是异步的,存在数百毫秒级窗口(实测 ~1s 内完成翻转)。
### 解决
1. **稳定轮询**:连续 3 次读 `GET_IP_INF`(间隔 300ms上限 8s结果一致且非零才采信
2. **交叉比对**PC 端将收到的宣告 IP 与所分配租约比对,不一致即报错兜底。
### 参考
`test/ch395f_test_task.c: phase4_run()``docs/CH395F_Test_Guide.md` 阶段 4 风险表 #4
---
## Trap 16NET 层 TCP Client 目的 IP 字节序写反 → CONNECT TIMEOUT
### 现象
NET 层 TCP Client阶段 7`net_connect()` 返回 0`net_poll()``sock0 CONNECT TIMEOUT`,连接永远建立不起来;而硬件层 TCP Client阶段 2同一 `net_init`/FUN_PARA=0x08同一目标 192.168.1.2:8081完全正常。PC 同网段、防火墙关闭、服务端正常监听。
### 根因
`net_connect_locked()``sockaddr_in.sin_addr.s_addr`(网络字节序,`net_inet_addr` 返回)按 **低字节在前** 拆成 `remote_ip_arr[4]`,得到 `[2,1,168,192]`,再交给 `ch395f_set_des_ip()`。但 **CH395F 的目的 IP/端口寄存器是大端(网络字节序)**(阶段 2 用 `test_parse_ip("192.168.1.2")` 直接得到 `[192,168,1,2]` 即成功,可证)。于是芯片把 SYN 发往 `2.1.168.192`——一个跨网段、不可达的地址SYN 被网关丢弃 → 无 SYN-ACK → `CONNECT TIMEOUT`
**误导性排查**`CMD_GET_REMOT_IPP_SN` 回读的是**小端**字节序(`ch395f.c` 注释 "IP和端口均为低字节在前")。把写反的 `[2,1,168,192]` 读回再按小端解释,恰好又得到 `192.168.1.2`,使回读日志看起来"目的地址正确",掩盖了真实写反。
### 解决
1. `net_connect_locked()` 提取 `remote_ip_arr` 改为 **大端**`arr[0]=(ip>>24)&0xFF … arr[3]=ip&0xFF`ip 为网络字节序 s_addr
2. 同样修正 UDP 发送路径 `net_send_locked()``set_des_ip` 的目的 IP 数组(同样低字节在前 bug
3. 诊断回读 `GET_REMOT_IPP` 时按其小端语义还原:`dip=(rip[3]<<24)|…|rip[0]`,再按 `>>24…` 打印,方可显示芯片实际目的 IP。
### 注意事项
- `net_inet_addr` 返回网络字节序;`sin_addr.s_addr` 一律按网络字节序理解。
- 任何写 CH395F IP/端口寄存器set_des_ip / set_ip_addr / set_gwip_addr / set_mask_addr都传**大端数组**,与 `test_parse_ip` 产物一致。
- 回读类命令GET_REMOT_IPP / GET_IP_INF 等)若文档标注"低字节在前",打印时务必先按小端重组再格式化,否则会误判。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_connect_locked` 提取 `remote_ip_arr` 大端;`net_send_locked` UDP 目的 IP 同样修正;`[CONN]` 回读按小端重组);`Drivers/BSP/NET/net_types.h``remote_ip_arr` 注释改为大端);`test/ch395f_test_task.c``test_parse_ip` 可作为大端构建范本)。
### 发现时间
阶段 7 NET 层 TCP Client 上板实测2026-08-25。
---
## Trap 17单连接模式 client 重连被上一条连接的残留 DISCONNECT 秒断
### 现象
阶段 7 TC701首次 TCP Client 连接 + 回显正常TC702close 后同 Socket 0 重连 ×3每轮不同本地端口每次都 `[CONN] openOK` 成功、但紧接着 `sock0 DISCONNECTED, releasing`连接被秒断PC 端看到"已建立连接但对端立即关闭、未收到数据"。阶段 6 的 `auto_relisten`/断线处理逻辑同源,但 `auto_relisten` 标志本身不是元凶client 该标志为 0
### 根因
单连接模式只有 Socket 0新旧连接复用它。`net_close_locked()``close_socket()` 后芯片要等对端 FIN/ACK 才把 Socket 置 `SOCK_CLOSED` 并上报 `DISCONNECT` 中断耗时数毫秒。TC702 几乎在 TC701 关闭同时复用 Socket 0 去 `tcp_connect`:旧连接的 `DISCONNECT` 中断在 TC702 已 `ESTABLISHED` 之后才到达,`net_poll()``handle_disconnect_event()` 把状态置 `CLOSED` → 新连接被误判为"刚建立就断开"。`GET_REMOT_IPP` 回读仅确认目的地址正确,掩盖不了此事件串扰。
### 解决
1. `net_close_locked()``close_socket()` 后轮询 `ch395f_get_socket_status()` 直到 `CH395F_SOCKET_CLOSED`,再读 `ch395f_get_sock_int_status()` / `ch395f_get_glob_int_status_all()` 清掉中断状态(上限 500msLAN 通常几毫秒),彻底排空上一条连接残留的 `DISCONNECT`
2. `net_connect_locked()``open_socket()` 成功后再次读空本 Socket 中断状态,作为双保险。
### 注意事项
- 任何"关闭→复用同一 Socket 0 重新建连"的场景都必须先排空旧连接的中断,否则旧 `DISCONNECT` 会串扰新连接。
- 若关闭后阻塞等待影响实时性(多连接模式另有 Socket应改为非阻塞的"代次/epoch"机制;单连接模式直接排空即可。
- `net_close_locked` 内阻塞 `osDelay` 仅在 netTask 上下文,单连接模式无其它并发 Socket安全。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_close_locked` 排空残留 DISCONNECT`net_connect_locked` open 后清中断)。
### 发现时间
阶段 7 NET 层 TCP Client 上板实测2026-08-25。
## Trap 18多 Socket 模式切换 + 8 个 Socket 缓冲分配
### 现象
阶段 7 仅测了 Socket 0单连接模式只有 Socket 0 可用)。要验证 Socket 1~7 也能作为 client必须把 `FUN_PARA` 从单 Socket 模式切到多 Socket 模式,否则 `open_socket(1~7)` 被芯片拒绝/无响应1~7 完全不可用。
### 根因
`CMD_SET_FUN_PARA`**bit0** 决定 Socket 数量模式:`0`=单 Socket 模式(仅 Socket 08KB 缓冲全归它);`1`=多 Socket 模式Socket 0~7 全部可用,缓冲需自行分配)。之前 `net_init``0x08`bit0=0即单 Socket 模式,故 1~7 不存在。注意 bit1TCP Server 单/多连接)与 bit0 是两回事:本方案只翻 bit0多 Socketbit1 保持 0TCP Server 单连接),即"8 个 Socket 可用,但 Socket 0 作服务端时只接 1 客户端"。
### 解决
1. `net_init``ch395f_set_fun_para(0x09)`bit0=1, bit1=0, bit3=1
2. `ch395f_init()` 之后,循环 0~7 用 `ch395f_set_send_buf(sock, i*6+4, 2)` / `ch395f_set_recv_buf(sock, i*6, 4)` 给每个 Socket 分配独立收发缓冲。按手册 §5.4548 块 × 512B = 24KB+ §9.2.8 最优分配:每 Socket 6 块 = 接收 4 块(2KB) + 发送 2 块(1KB)`ch395f_set_tcp_mss(1024)`,恰好用尽 0~47 块、互不重叠。
3. MSS 选取依据 §9.2.8:接收缓冲(2KB) ≥ 2×MSS(1024) 满足建议、≥ MSS 满足必须;发送缓冲(1KB) ≤ 8KB 满足必须。MSS 取 1024 为 8-Socket 共享 24KB 时的最大值。
4. `net_init` 缓冲分配顺序遵循 Trap 01先配数据 Socket再配监听 Socket 0本 Net 层 Socket 按需 open故统一在 init 一次性配好。UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设也同步为同一布局。
5. PC 端 `tcp_server` 改为每连接一线程的并发回显(`--max-conn` 为并发计数),否则 Phase 7 全量连接会触发累计计数上限而拒掉 4~7Trap 19
### 注意事项
- 4~7 的缓冲必须显式分配,否则其 CONNECT/RECV 中断不触发Trap 01/02
- 上述布局**以手册 §5.45 的 48×512B 缓冲几何为准**,并已由 Phase 7 TC70440/40 PASS实测确认块 0~47 全部有效(此前文档中"32×1KB"记录有误。Socket 4~7 分配与 8 路并发回显均验证通过。
- 多 Socket 模式下 TCP Server 单连接语义不变Socket 0 监听且连接停留在本 SocketSockets 1~7 由应用当作独立 client 使用。
- `net_poll``GET_GLOB_INT_STATUS_ALL`2 字节,支持 0~7并遍历 0~7Socket n 中断位即 `bit(n+4)`Socket 0~3→bit4~7Socket 4~7→bit8~11原代码正确无需改动。
### 对应文件
`Drivers/BSP/NET/net_socket.c``net_init` FUN_PARA + 缓冲分配 + MSS`test/net_test_task.c`(新增 TC704 八 Socket 并发回显);`test/ch395f_socket_test.py``tcp_server` 并发 + `--max-conn`)。
### 发现时间
阶段 7 扩展 8 Socket 测试2026-08-25。
## Trap 20发送缓冲区单次写入超过硬件 FIFO 上限(写穿)
### 现象
大文件/批量发送(如阶段 8 的 100KB或 FTP 上传)时,数据在中间某处错乱、连接被对端 RST、或在 `WRITE_SEND_BUF` 后芯片不再产生 `SEND_OK`,表现为发送卡死或后续字节丢失。该问题只在单次发送长度 > 1KB 时稳定复现。
### 根因
`net_send_locked`TCP`net_sendto`UDP在分块时以 **`NET_DMA_MAX_PAYLOAD`(2KB) / `NET_SEND_BUF_SIZE`(4KB)** 作为单次写长度上限,但 CH395F 每个 Socket 的**硬件发送 FIFO 只有 `CH395F_SEND_BLOCKS × CH395F_RAM_BLOCK_SIZE` = 2×512 = 1KB**(由 MSS=1024 推导,见 `ch395f.h`)。当某次调用 `net_send/sendto` 传入 >1KB 数据,驱动会在 `send_ready` 置位后一次性 `WRITE_SEND_BUF(2KB/4KB)`,直接**写穿 1KB 发送 FIFO**(超出部分覆盖/丢失或破坏 FIFO 指针),导致后续数据错位。原代码误把"SPI DMA 缓冲上限(2KB)"或"软件 API 缓冲(4KB)"当成硬件发送缓冲上限。
### 解决
1. `ch395f.h` 新增硬件缓冲字节宏:`CH395F_SEND_BUF_SIZE_BYTES = CH395F_SEND_BLOCKS × CH395F_RAM_BLOCK_SIZE`=1KB并补充 `CH395F_RECV_BUF_SIZE_BYTES`
2. `net_socket.c` 新增 `NET_SEND_CHUNK_MAX = min(NET_DMA_MAX_PAYLOAD, CH395F_SEND_BUF_SIZE_BYTES)`,即**以硬件发送 FIFO 为硬上限**(当前 1KB < 2KB DMA 载荷,故生效 1KB
3. `net_send_locked``net_sendto` 的单次写长度上限均由原来的 `NET_DMA_MAX_PAYLOAD`/`NET_SEND_BUF_SIZE` 改为 `NET_SEND_CHUNK_MAX``send_ready` 门控保证只有 FIFO 空闲时才写入,故写入 ≤1KB 安全(写满后等 `SEND_OK` 再写下一块)。
4. 接收方向不受影响:`net_recv_locked` 只读 `ch395f_get_recv_len`(≤ 接收 FIFO 2KB并受调用方 `len` 截断,不会写穿。
### 注意事项
- 该上限由 `CH395F_TCP_MSS` 推导,若将来调大 MSS 使发送缓冲 >2KB`NET_SEND_CHUNK_MAX` 自动跟随(仍取硬件缓冲与 DMA 载荷的较小者),无需改此处逻辑。
- 调用方(阶段 8、FTP `lftpd`)仍可按任意长度调用 `net_send`,实际分块由驱动保证 ≤ 硬件 FIFO。
### 对应文件
`Drivers/BSP/CH395F/ch395f.h``CH395F_SEND_BUF_SIZE_BYTES` 等);`Drivers/BSP/NET/net_socket.c``NET_SEND_CHUNK_MAX` + `net_send_locked`/`net_sendto` 分块上限)。
### 发现时间
阶段 8 大文件传输实现2026-08-26。