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