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