# 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` 上限，但余量已经很小；后续若继续增加功能，应优先考虑复用现有缓冲与状态，而不是增加新的静态大数组。

### 6.3 客户现场脏网络恢复增强

客户现场换 PC 后曾出现设备持续 ARP、`ping` 不通、TCP Client 不恢复的现象。抓包显示故障前后存在 IPv6、DHCPv6、mDNS、IGMP、LLDP 等与当前业务无关的网络噪声；当前固件为静态 IPv4、TCP2UART 与 ICMP 诊断模型，不依赖 UDP、IPv6、DHCP 或多播发现。

本阶段采用低 RAM 优先的恢复策略，不先扩大 `PBUF_POOL_SIZE`，而是在更靠近入口的位置减少无关帧对 lwIP pool 的占用：

1. `TCP Client CONNECTING` 增加应用层超时：
   - `TCP_CLIENT_CONNECT_TIMEOUT_MS = 10000`
   - `tcp_connect()` 返回 `ERR_OK` 后记录 `connect_start_ms`
   - `tcp_client_poll()` 发现 `CONNECTING` 超过超时时间后，注销回调、`tcp_abort()` 当前 PCB、释放 `hold_pbuf`，再按原有 `reconnect_interval_ms` 重连
   - `tcp_client_status_t.connect_timeout_count` 记录发生次数
2. `CH390` RX 入口增加 `pre-pbuf` 协议过滤：
   - 在 `pbuf_alloc(PBUF_RAW, ..., PBUF_POOL)` 之前先读取以太网头与最小 IPv4 头
   - 允许进入 lwIP 的协议限定为 `ARP`、`IPv4 ICMP`、`IPv4 TCP`
   - 默认丢弃 `IPv6`、`IPv4 UDP`、`IPv4 IGMP`、`LLDP`、未知 EtherType 与畸形头
   - 丢弃帧只跳过 CH390 RX FIFO 剩余字节，不分配 pbuf
3. 软件 MAC 过滤暂不启用：
   - 第一版只做协议层过滤，避免误杀广播 ARP 或未来硬件过滤策略变化
   - 目的 MAC 相关判断保留为后续可选增强

新增 CH390 诊断字段用于现场判断是否仍存在资源压力：

1. `rx_pbuf_alloc_failed`
2. `rx_filtered_frames`
3. `rx_filtered_ipv6`
4. `rx_filtered_udp`
5. `rx_filtered_igmp`
6. `rx_filtered_lldp`
7. `rx_filtered_other_eth`
8. `rx_filtered_other_ipv4`
9. `rx_filtered_malformed`
10. `rx_ipv4_icmp_frames`
11. `rx_ipv4_tcp_frames`
12. `rx_ipv4_udp_frames`

验收口径：

1. 正常 `ARP / ping / TCP Server / TCP Client` 功能不受影响
2. 客户脏网络中的 IPv6、UDP、IGMP、LLDP 噪声不会进入 lwIP pbuf pool
3. 若远端 TCP Server 不监听或静默丢 SYN，TCP Client 不再永久停留在 `CONNECTING`
4. 若过滤后 `rx_pbuf_alloc_failed` 仍持续增长，再评估从无关功能中回收 RAM 并调整 `PBUF_POOL_SIZE`

本阶段 Keil 构建验收结果：

1. `errors = 0`
2. `warnings = 0`
3. `flash_bytes = 57404`
4. `ram_bytes = 20440`

## 七、主循环实现方向

主循环仍保持裸机轮询风格：

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