Files
TCP2UART/项目技术实现.md
T

293 lines
8.2 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.
# 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 手册、需求说明、技术实现三份文档不得再出现历史展开式字段