Files
STM32F4-Base/docs/CH395F_Trap_Records.md
2026-07-21 22:42:39 +08:00

289 lines
11 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 各分配 4 个缓冲区块(共 32 块用完Socket 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 各分配 4 个缓冲区块(共 32 块用完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`)直接操作,同样不受影响