2026-03-14 08:47:57 +08:00

94 lines
6.7 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.

## Context
当前 TCPClient 工程运行在裸机模式下,主循环直接轮询 WCHNET 和 DVP 任务。QDX 网络栈的 `qdx_tcp_logic` 被设计为多线程架构3 个后台线程1 个连接管理 + 2 个接收线程),依赖 `qdx_port.h` 定义的 11 个 HAL 函数线程、互斥锁、延时、TCP socket但这些函数在 `qdx_port_template.c` 中全部为空 stub。
关键约束:
- CH32V307 拥有 64KB SRAMFreeRTOS 堆配置为 12KB
- WCHNET 是 WCH 私有 TCP/IP 协议栈,**非 BSD socket 模型**——采用中断回调通知 + 同步轮询接收的混合模式
- WCHNET 必须周期性调用 `WCHNET_MainTask()` 和全局中断处理才能驱动协议栈运转
- 当前 `net_config.h``WCHNET_NUM_TCP = 1`但双流架构5511 控制流 + 5512 数据流)需要 2 个 TCP socket
- FreeRTOS 的 RISC-V 移植已存在于 `prj/FreeRTOS_Core/FreeRTOS/portable/GCC/RISC-V/`
## Goals / Non-Goals
**Goals:**
- 实现 `qdx_port.c` 全部 11 个 HAL 函数,使 `qdx_tcp_logic` 三线程架构实际运行
- 将裸机主循环迁移为 FreeRTOS 多任务模型
- 桥接 WCHNET 回调模型与 `qdx_port_tcp_recv` 的阻塞/半阻塞语义
- 补全 `OnConfigUpdate``OnDetectionResult` 回调逻辑
**Non-Goals:**
- 不修改 `qdx_tcp_logic.c``qdx_protocol.c``qdx_preprocess.c` 的内部逻辑
- 不实现外部硬件 DI 触发模式和连拍功能(属于后续 change
- 不移植到 LwIP 或其他 TCP/IP 协议栈
## Decisions
### Decision 1FreeRTOS 任务划分
将系统拆分为 4+3 个 FreeRTOS 任务:
| 任务 | 优先级 | 栈大小 | 职责 |
|------|--------|--------|------|
| `task_wchnet` | 6 (高) | 1024 words | 周期调用 `WCHNET_MainTask()` + 全局中断处理 |
| `task_business` | 5 | 1024 words | DVP 采集轮询 + 触发判定 + 预处理 + 封包发送 |
| `tcp_mgr` | 3 | 512 words | 由 `TcpLogic_Start()` 创建,连接管理与心跳 |
| `tcp_rx_c` | 4 | 512 words | 由 `TcpLogic_Start()` 创建,控制流接收 |
| `tcp_rx_d` | 4 | 512 words | 由 `TcpLogic_Start()` 创建,数据流接收 |
**理由**`task_wchnet` 优先级最高,因为 WCHNET 协议栈需要及时处理底层以太网帧和 TCP 状态机;`task_business` 次之,确保 DVP 帧不丢失TCP 后台线程优先级最低,属于非实时任务。
**备选方案**:将 WCHNET 轮询放在定时器回调中而非独立任务——但 `WCHNET_MainTask()` 执行时间不确定,不适合放在中断上下文。
### Decision 2WCHNET 接收桥接方案——信号量 + 环形缓冲
WCHNET 通过 `SINT_STAT_RECV` 中断通知数据到达,而 `qdx_port_tcp_recv` 被调用方期望为可阻塞/超时返回。桥接方案:
1. 为每个 socket 维护一个**接收环形缓冲区**`RxRingBuf`2920 字节)和一个**二值信号量**`xSemaphoreRx`
2.`WCHNET_HandleSockInt``SINT_STAT_RECV` 分支中:调用 `WCHNET_SocketRecv()` 将数据读入 `RxRingBuf`,然后 `xSemaphoreGiveFromISR(xSemaphoreRx)`
3. `qdx_port_tcp_recv()` 实现:先检查 `RxRingBuf` 是否有数据,有则直接拷贝返回;无则 `xSemaphoreTake(xSemaphoreRx, pdMS_TO_TICKS(100))` 阻塞等待最多 100ms超时返回 0
**理由**:这种方式让 recv 线程在无数据时让出 CPU通过信号量阻塞同时避免了忙等 10ms 轮询的 CPU 浪费。环形缓冲解耦了中断上下文读取和应用层消费的速率差异。
**备选方案**FreeRTOS Stream Buffer——语义更匹配但引入额外依赖且 WCHNET 的 `SocketRecv` 已提供了长度信息,环形缓冲更简单可控。
### Decision 3Socket 映射管理
WCHNET 使用 `uint8_t socketid`0~30标识 socket`qdx_port.h` 使用 `void* qdx_socket_t` 不透明句柄。映射方案:
- 维护一个静态数组 `SocketCtx_t g_sock_ctx[MAX_SOCKETS]`MAX_SOCKETS = 2每个元素包含
- `uint8_t wchnet_sock_id` — WCHNET socket ID
- `uint8_t connected` — 是否已连接
- `RxRingBuf_t rx_ring` — 接收环形缓冲
- `SemaphoreHandle_t rx_sem` — 接收通知信号量
- `qdx_port_tcp_connect()` 分配空闲的 `SocketCtx_t`,调用 `WCHNET_SocketCreat` + `WCHNET_SocketConnect`,返回 `&g_sock_ctx[i]` 作为句柄
- `qdx_port_tcp_send/recv/close()` 从句柄中提取 `wchnet_sock_id` 操作 WCHNET API
**Config 变更**`WCHNET_NUM_TCP` 从 1 改为 2`WCHNET_MAX_SOCKET_NUM` 相应变为 2。
### Decision 4TIM2 共享——FreeRTOS Tick + WCHNET Timer
当前 TIM2 以 10ms 周期驱动 `WCHNET_TimeIsr()`。FreeRTOS 需要 2ms tick`configTICK_RATE_HZ = 500`)。方案:
- 将 TIM2 周期改为 **2ms**(匹配 FreeRTOS tick
- TIM2 ISR 中每次调用 `xPortSysTickHandler()`FreeRTOS tick
- 设软件计数器,每累计 5 次(= 10ms调用一次 `WCHNET_TimeIsr(WCHNETTIMERPERIOD)`
**理由**:共用一个硬件定时器节省外设资源,软件分频几乎无开销。
### Decision 5`qdx_port_tcp_connect` 中的连接等待
WCHNET 的 `WCHNET_SocketConnect()` 是异步的——它发起三次握手后立即返回,连接完成通过 `SINT_STAT_CONNECT` 中断通知。但 `qdx_port_tcp_connect` 语义是阻塞直到连接建立。
方案:在 `SocketCtx_t` 中增加 `SemaphoreHandle_t connect_sem`。调用 `WCHNET_SocketConnect``xSemaphoreTake(connect_sem, pdMS_TO_TICKS(5000))` 阻塞。在 `SINT_STAT_CONNECT` 回调中 `xSemaphoreGive(connect_sem)`。超时返回 NULL。
## Risks / Trade-offs
**[RAM 占用偏紧]** → FreeRTOS 堆 12KB + 5 个任务栈约 9KB + WCHNET 内部约 10KB + 2×2920 socket 缓冲 + 2×2920 环形缓冲 + 2×10KB 发送缓冲区 ≈ 52KB / 64KB。**缓解**:严格控制任务栈大小,使用 `uxTaskGetStackHighWaterMark` 运行时监测;发送缓冲保持 10KB 不变(已经预分配)。如仍紧张可将 FreeRTOS 堆缩减至 8KB。
**[WCHNET 非线程安全]** → WCHNET API 未声明线程安全性,多个 FreeRTOS 任务可能并发调用 send/recv。**缓解**:所有 WCHNET API 调用send、recv、socket 操作)统一通过 `task_wchnet` 任务的消息队列委托执行,或者使用全局互斥锁保护。初期采用互斥锁方案,复杂度更低。
**[中断上下文限制]** → `WCHNET_HandleSockInt` 在中断上下文被调用(通过 `WCHNET_HandleGlobalInt` → TIM2/ETH ISR 链),其中调用 `WCHNET_SocketRecv``xSemaphoreGiveFromISR` 必须确保安全。**缓解**:将 `WCHNET_HandleGlobalInt` 移出 ISR改为在 `task_wchnet` 中轮询调用 `WCHNET_QueryGlobalInt`,这样 recv 回调运行在任务上下文,可安全使用 FreeRTOS API。
**[双 Socket 内存增长]** → `WCHNET_NUM_TCP` 从 1 变为 2`WCHNET_MEM_HEAP_SIZE``WCHNET_NUM_POOL_BUF` 相应增加约 3KB。**缓解**CH32V307 有 64KB SRAM增量可接受。