293 lines
8.2 KiB
Markdown
293 lines
8.2 KiB
Markdown
# TCP2UART 项目技术实现
|
||
|
||
## 一、文档目的
|
||
|
||
本文档描述 `TCP2UART` 项目的最终内部实现口径。
|
||
|
||
本文档只围绕最终协议模型展开:
|
||
|
||
- `MUX`:串口承载层
|
||
- `NET`:全局网络配置层
|
||
- `LINK[idx]`:实例配置与连接管理层
|
||
|
||
不再保留历史 `S1... / C1...` 外部字段模型。
|
||
|
||
## 二、当前工程基础
|
||
|
||
当前工程基础约束如下:
|
||
|
||
1. MCU:`STM32F103R8T6`
|
||
2. 网络芯片:`CH390D`
|
||
3. 软件架构:`bare-metal main loop`
|
||
4. 协议栈:`lwIP RAW API + NO_SYS=1`
|
||
5. 调试输出:`SEGGER RTT`
|
||
6. 不使用 `FreeRTOS`
|
||
7. 不实现 DHCP
|
||
|
||
## 三、总体架构
|
||
|
||
```text
|
||
+--------------------------------------------------+
|
||
| AT / Control Plane |
|
||
| USART1 AT parser + MUX control frame parser |
|
||
+--------------------------------------------------+
|
||
| Configuration Model |
|
||
| MUX / NET / LINK[idx] |
|
||
+--------------------------------------------------+
|
||
| Routing & Session Layer |
|
||
| TCP instance scheduling + UART dispatch |
|
||
+--------------------------------------------------+
|
||
| Transport Poll Loop |
|
||
| ethernetif_poll / sys_check_timeouts / uart poll |
|
||
+--------------------------------------------------+
|
||
| Driver Layer |
|
||
| CH390 / lwIP netif / UART DMA+IDLE / HAL |
|
||
+--------------------------------------------------+
|
||
```
|
||
|
||
## 四、最终协议实现模型
|
||
|
||
### 4.1 MUX 帧承载层
|
||
|
||
数据口启用 MUX 后,统一处理如下帧:
|
||
|
||
```text
|
||
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
||
```
|
||
|
||
实现职责:
|
||
|
||
1. 识别帧边界
|
||
2. 解析长度字段
|
||
3. 提取 `SRCID`
|
||
4. 解析 `DSTMASK`
|
||
5. 按控制帧或数据帧分流
|
||
|
||
### 4.2 控制帧与数据帧分离
|
||
|
||
控制规则固定如下:
|
||
|
||
- `DSTMASK = 0x00`:系统控制帧
|
||
- `DSTMASK != 0x00`:业务数据帧
|
||
|
||
系统控制帧处理要求:
|
||
|
||
1. `PAYLOAD` 解释为 AT 文本
|
||
2. AT 文本必须以 `\r\n` 结束
|
||
3. 控制帧进入 AT 命令处理链路
|
||
|
||
业务数据帧处理要求:
|
||
|
||
1. `SRCID` 表示单一源端点
|
||
2. `DSTMASK` 表示目标端点集合
|
||
3. 路由层根据 `DSTMASK` 做多目标分发
|
||
|
||
### 4.3 统一端点编码
|
||
|
||
内部与外部文档统一使用以下端点编码:
|
||
|
||
| 端点 | 编码 |
|
||
|------|------|
|
||
| `C1` | `0x01` |
|
||
| `C2` | `0x02` |
|
||
| `UART2` | `0x04` |
|
||
| `UART3` | `0x08` |
|
||
| `S1` | `0x10` |
|
||
| `S2` | `0x20` |
|
||
|
||
实现要求:
|
||
|
||
- `SRCID` 为单值
|
||
- `DSTMASK` 为位图
|
||
- `DSTMASK=0x00` 仅保留为控制帧
|
||
|
||
## 五、配置层设计
|
||
|
||
### 5.1 MUX 记录
|
||
|
||
`MUX` 为全局记录,仅控制设备数据口是否进入 MUX 承载模式。
|
||
|
||
取值:
|
||
|
||
- `0`:普通透传
|
||
- `1`:MUX 透传
|
||
|
||
### 5.2 NET 记录
|
||
|
||
`NET` 为全局静态网络记录:
|
||
|
||
```text
|
||
IP,MASK,GW,MAC
|
||
```
|
||
|
||
说明:
|
||
|
||
- 设备只有一张网卡,因此不为每个实例单独配置本地 IP
|
||
- 当前实现目标中不包含 DHCP
|
||
|
||
### 5.3 LINK 记录
|
||
|
||
`LINK[idx]` 为统一实例记录:
|
||
|
||
```text
|
||
EN,LPORT,RIP,RPORT,UART
|
||
```
|
||
|
||
固定索引映射:
|
||
|
||
- `0 = S1`
|
||
- `1 = S2`
|
||
- `2 = C1`
|
||
- `3 = C2`
|
||
|
||
字段职责:
|
||
|
||
- `EN`:实例启用状态
|
||
- `LPORT`:本地端口
|
||
- `RIP / RPORT`:对端地址与端口
|
||
- `UART`:对应业务数据口
|
||
|
||
说明:
|
||
|
||
- `Server` 与 `Client` 共享同一记录结构
|
||
- `Server` 的 `RIP / RPORT` 可作为对端约束或预设
|
||
- `Client` 的 `RIP / RPORT` 表示远端目标
|
||
|
||
## 六、模块职责调整
|
||
|
||
### 6.1 配置模块 `config.c/.h`
|
||
|
||
最终职责:
|
||
|
||
1. 解析 `AT+MUX`
|
||
2. 解析 `AT+NET`
|
||
3. 解析 `AT+LINK`
|
||
4. 加载与保存配置
|
||
5. 处理 `SAVE / RESET / DEFAULT`
|
||
|
||
不再以历史展开式字段作为外部接口模型。
|
||
|
||
### 6.2 UART 透传模块 `uart_trans.c/.h`
|
||
|
||
最终职责:
|
||
|
||
1. 保持 `USART2 / USART3` 的 `DMA + IDLE` 接收发送基线
|
||
2. 在 `MUX=0` 时执行普通透传
|
||
3. 在 `MUX=1` 时执行 MUX 帧收发
|
||
4. 将控制帧与业务数据帧分流
|
||
|
||
### 6.3 TCP Server / Client 模块
|
||
|
||
最终职责:
|
||
|
||
1. 不再从外部协议角度区分不同字段模型
|
||
2. 统一受 `LINK[idx]` 配置驱动
|
||
3. 由调度层决定实例与 UART 的数据交换路径
|
||
|
||
### 6.4 `v1.1.0` 低 RAM TCP 背压修复
|
||
|
||
`v1.1.0` 起,`TCP -> UART` 路径补充如下实现约束,用于解决“TCP 接收过快、UART 发送过慢时本地缓存被冲垮”的问题,同时尽量不新增静态 RAM:
|
||
|
||
1. 继续复用 `tcp_server` / `tcp_client` 现有 `RX ring`,不为每个连接新增独立的大块 pending payload 缓冲。
|
||
2. `tcp_server_on_recv()` / `tcp_client_on_recv()` 不再在回调内立即 `tcp_recved()`。
|
||
3. lwIP 交来的 `pbuf` 在回调中通过 `pbuf_ref()` 转为应用持有,再释放回调上下文的原始引用;后续由应用在主循环中继续把数据泵入 `RX ring`,最终在消费完成后释放。
|
||
4. 当 `RX ring` 暂时装不下时,剩余数据保留在 `hold_pbuf + hold_offset` 中,等待主循环下一轮继续搬运。
|
||
5. 只有当数据真正从 `TCP RX ring` 被 `drop` 掉,也就是已经被下游 `UART` 接收进入发送路径时,才调用 `tcp_recved()` 释放 TCP 接收窗口。
|
||
|
||
这样做的效果是:
|
||
|
||
1. `UART` 慢时,TCP 窗口不会继续无条件放大。
|
||
2. 对端发送速度会被 lwIP 接收窗口自然压制。
|
||
3. 修复点建立在已有 ring 与主循环调度之上,不引入 `FreeRTOS` 或新的大块静态缓存。
|
||
|
||
#### RAW 与 MUX 的分流规则
|
||
|
||
在 `v1.1.0` 中,`TCP` 侧拿到的都是纯 payload,因此 `TCP` 背压逻辑在 `RAW` 与 `MUX` 两种模式下共用到 `UART commit` 之前:
|
||
|
||
1. `RAW` 模式:
|
||
- 主循环先查看 `uart_trans_tx_free()`
|
||
- 再按 `min(tcp_available, tx_free, APP_TCP_TO_UART_CHUNK_SIZE)` 从 TCP ring `peek`
|
||
- `uart_trans_write()` 实际写入多少,就 `drop + tcp_recved` 多少
|
||
2. `MUX` 模式:
|
||
- `TCP` payload 本身不带帧头尾
|
||
- 只有当 `UART TX free >= payload_len + 6` 时,才在栈上临时编码一帧并一次性写入 `UART TX ring`
|
||
- 只有整帧成功入队后,才按原始 payload 长度执行 `drop + tcp_recved`
|
||
|
||
该设计保证:
|
||
|
||
1. `RAW` 模式允许流式逐步提交
|
||
2. `MUX` 模式保持“单个 UART 输出帧必须完整入队”的语义
|
||
3. `TCP` 接收窗口始终以真实下游消费进度为准,而不是以“回调里已经 memcpy 到本地”作为提交点
|
||
|
||
#### RAM 与 chunk 策略
|
||
|
||
为给新增的 `hold_pbuf / hold_offset` 状态字段让位,并进一步降低单轮转发压力,`v1.1.0` 同步采用以下策略:
|
||
|
||
1. 新增 `APP_TCP_TO_UART_CHUNK_SIZE = 128`
|
||
2. `TCP_SERVER_RX_BUFFER_SIZE` 从 `512` 调整为 `480`
|
||
3. `TCP_CLIENT_RX_BUFFER_SIZE` 从 `512` 调整为 `480`
|
||
|
||
设计意图:
|
||
|
||
1. 利用更小的单次转发块提升主循环调度颗粒度
|
||
2. 让 `MUX` 模式下 `payload + 6` 更容易完整进入 `UART TX ring`
|
||
3. 在静态 RAM 已接近上限时,为少量新状态字段回收空间
|
||
|
||
#### 构建基线
|
||
|
||
`v1.1.0` 以 `MDK-ARM/TCP2UART.uvprojx` 的 `TCP2UART` Target 为构建验收基线。
|
||
|
||
当前一次通过的参考结果:
|
||
|
||
1. `errors = 0`
|
||
2. `warnings = 0`
|
||
3. `flash_bytes = 56544`
|
||
4. `ram_bytes = 20376`
|
||
|
||
该结果说明修复后工程仍满足 `STM32F103R8T6` 的 `20KB RAM` 上限,但余量已经很小;后续若继续增加功能,应优先考虑复用现有缓冲与状态,而不是增加新的静态大数组。
|
||
|
||
## 七、主循环实现方向
|
||
|
||
主循环仍保持裸机轮询风格:
|
||
|
||
```c
|
||
while (1)
|
||
{
|
||
ethernetif_poll();
|
||
ethernetif_check_link();
|
||
sys_check_timeouts();
|
||
tcp_link_poll();
|
||
uart_mux_poll();
|
||
config_poll();
|
||
|
||
route_dispatch();
|
||
|
||
if (reset_requested) {
|
||
NVIC_SystemReset();
|
||
}
|
||
}
|
||
```
|
||
|
||
下一阶段实现要求:
|
||
|
||
1. 统一由 `LINK[idx]` 驱动实例状态
|
||
2. 统一由 `MUX` 决定数据口承载模式
|
||
3. 统一由 `route_dispatch()` 按 `SRCID / DSTMASK` 分发
|
||
|
||
## 八、实现边界
|
||
|
||
1. 保持单网卡静态网络模型
|
||
2. 不实现 DHCP
|
||
3. 不实现旧 `S1... / C1...` 外部协议字段
|
||
4. 不在文档中保留兼容层描述
|
||
5. 所有 AT 文本控制统一要求 `\r\n` 结束
|
||
|
||
## 九、文档一致性要求
|
||
|
||
后续实现、联调、测试与代码注释必须遵守以下统一口径:
|
||
|
||
1. 对外协议只使用 `MUX / NET / LINK`
|
||
2. 控制帧只使用 `DSTMASK=0x00`
|
||
3. MUX 帧格式固定为 `SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL`
|
||
4. AT 手册、需求说明、技术实现三份文档不得再出现历史展开式字段
|