# TCP2UART 项目技术实现

## 一、文档目的

本文档描述 `TCP2UART` 项目基于 `STM32F103RCT6 + FreeRTOS` 的最终内部实现口径。

本文档只围绕最终协议模型展开：

- `MUX`：串口承载层
- `NET`：全局网络配置层
- `LINK[idx]`：实例配置与连接管理层

不再保留历史 `S1... / C1...` 外部字段模型。

## 二、当前工程基础

当前工程基础约束如下：

1. MCU：`STM32F103RCT6`（256KB Flash / 48KB SRAM）
2. 网络芯片：`CH390D`
3. 软件架构：`FreeRTOS + lwIP NO_SYS=0`
4. 协议栈：`lwIP socket/netconn API`
5. 调试输出：`SEGGER RTT`
6. 使用 `FreeRTOS` 任务调度
7. 不实现 DHCP

## 三、总体架构

```text
+--------------------------------------------------+
| AT / Control Plane                               |
| USART1 AT parser + MUX control frame parser      |
+--------------------------------------------------+
| Configuration Model                              |
| MUX / NET / LINK[idx]                            |
+--------------------------------------------------+
| FreeRTOS Tasks                                   |
| NetworkTask / UartTask / ConfigTask / RouteTask  |
+--------------------------------------------------+
| Inter-Task Communication                         |
| Queue / Semaphore / Mutex / StreamBuffer         |
+--------------------------------------------------+
| lwIP TCP/IP Stack (NO_SYS=0)                     |
| tcpip_thread + socket/netconn + sys_arch         |
+--------------------------------------------------+
| Driver Layer                                     |
| CH390 / lwIP netif / UART DMA+IDLE / HAL         |
+--------------------------------------------------+
```

## 四、FreeRTOS 任务设计（路径 A：netconn + 多 TCP 任务）

### 4.1 架构路线

本项目采用 **路径 A**：`NO_SYS=0 + netconn API + 每个 TCP 连接独立任务`。

核心决策：
1. lwIP 以 `NO_SYS=0` 模式运行，`tcpip_thread` 由 lwIP 自动创建
2. TCP 连接使用 `netconn` 阻塞 API（`netconn_accept` / `netconn_recv` / `netconn_write`）
3. 每个 TCP Server 和 Client 实例各占一个独立任务
4. 任务间通过 Queue 传递指针 + 元数据描述符，实现零拷贝

### 4.2 任务列表（共 9 个任务 + 1 个 lwIP 自建）

| 任务名 | 优先级 | 栈(words) | 模式 | 职责 |
|--------|--------|-----------|------|------|
| `tcpip_thread` | 6 (最高) | 512 | 阻塞 | lwIP 内核线程（自动创建） |
| `NetPollTask` | 5 | 384 | 事件驱动 | `ethernetif_poll` + 链路检测 |
| `TcpSrvTask_S1` | 4 | 384 | 阻塞 | `netconn_accept` + S1 收发 |
| `TcpSrvTask_S2` | 4 | 384 | 阻塞 | `netconn_accept` + S2 收发 |
| `TcpCliTask_C1` | 4 | 256 | 阻塞 | `netconn_connect` + C1 收发 |
| `TcpCliTask_C2` | 4 | 256 | 阻塞 | `netconn_connect` + C2 收发 |
| `UartRxTask` | 4 | 384 | 事件驱动 | UART DMA/IDLE 接收 + MUX 帧提取 |
| `ConfigTask` | 2 | 256 | 阻塞 | AT 命令解析与响应 |
| `DefaultTask` | 1 | 128 | 周期 | LED 心跳 + IWDG 喂狗 |

说明：
- `tcpip_thread` 是 lwIP 自建的内核线程，处理所有协议栈内部事件
- 所有 `netconn_*` API 通过 `tcpip_thread` 消息机制实现线程安全
- `LWIP_TCPIP_CORE_LOCKING=1` 允许应用任务直接调用 `netconn_write` 而不经邮箱中转
- `tcpip_thread` 优先级最高（6），确保 TCP ACK 和超时处理不被延迟

### 4.3 零拷贝路由消息设计

```c
/* 路由消息描述符 - Queue 传递的是此结构的指针，不拷贝负载数据 */
typedef struct {
    uint8_t  src_id;       /* 源端点 ID */
    uint8_t  dst_mask;     /* 目标端点位图 */
    uint16_t len;          /* 数据长度 */
    uint8_t  conn_type;    /* 连接标识：LINK_S1/S2/C1/C2 */
    uint8_t *data;         /* 指向预分配静态缓冲区 */
} route_msg_t;
```

静态缓冲池（预分配，避免动态分配）：

```c
#define ROUTE_BUF_COUNT  4
#define ROUTE_BUF_SIZE   512

static uint8_t  g_route_buf_pool[ROUTE_BUF_COUNT][ROUTE_BUF_SIZE];
static volatile uint8_t g_route_buf_used[ROUTE_BUF_COUNT];
```

- 发送方：从池中获取空闲缓冲区，拷贝数据，填充 `route_msg_t`，Queue 发送指针
- 接收方：从 Queue 取 `route_msg_t*`，处理数据后标记缓冲区为可用
- 无动态分配，无堆碎片

### 4.4 任务间通信机制

```text
UART ISR ──[TaskNotify]──> UartRxTask ──[Queue*]──> TcpSrvTask / TcpCliTask
                              │                         ▲
                              ├──[Queue*]──────────────>─┘
                              │
                    DSTMASK=0 └──[Queue]──> ConfigTask

TcpSrvTask / TcpCliTask ──[Queue*]──> UartRxTask (UART TX 方向)

EXTI0 ISR ──[BinarySem]──> NetPollTask ──> ethernetif_poll ──> tcpip_thread
```

通信对象：

| 对象 | 类型 | 生产者 | 消费者 | 用途 |
|------|------|--------|--------|------|
| `xNetSemaphore` | Binary Semaphore | EXTI0 ISR | NetPollTask | CH390 中断通知 |
| `xUartRxNotify` | TaskNotification | UART IDLE ISR | UartRxTask | UART 接收通知 |
| `xTcpRxQueue` | Queue (16, route_msg_t*) | TCP 任务 | UartRxTask | TCP→UART 数据 |
| `xUartTxQueue` | Queue (8, route_msg_t*) | UartRxTask | TCP 任务 | UART→TCP 数据 |
| `xConfigQueue` | Queue (8, char*) | UartRxTask | ConfigTask | AT 文本 |

说明：
- TCP→UART 方向：TCP 任务调用 `netconn_recv` 获取数据，构造 `route_msg_t` 指针投递到 `xTcpRxQueue`，UartRxTask 取出后写入 UART DMA
- UART→TCP 方向：UartRxTask 从 UART DMA 读取数据，构造 `route_msg_t` 指针投递到 `xUartTxQueue`，对应 TCP 任务取出后调用 `netconn_write` 发送
- 路由逻辑内联在 UartRxTask 和各 TCP 任务中，不设独立 RouteTask

### 4.5 NetPollTask 实现

```c
void NetPollTask(void *argument)
{
    /* 初始化 CH390 + lwIP netif */
    ethernetif_init(&ch390_netif);
    /* 等待链路就绪后启动 TCP 任务 */
    /* ... */

    for (;;) {
        xSemaphoreTake(xNetSemaphore, pdMS_TO_TICKS(2));
        ethernetif_poll(&ch390_netif);
        ethernetif_check_link(&ch390_netif);
        /* sys_check_timeouts() 由 tcpip_thread 自动执行，此处不需要调用 */
    }
}
```

### 4.6 TcpSrvTask 实现模板

```c
void TcpSrvTask_S1(void *argument)
{
    struct netconn *conn = netconn_new(NETCONN_TCP);
    netconn_bind(conn, IP_ADDR_ANY, cfg->links[0].lport);
    netconn_listen(conn);

    for (;;) {
        struct netconn *newconn;
        if (netconn_accept(conn, &newconn) == ERR_OK) {
            /* 在本任务内处理唯一客户端 */
            tcp_server_worker(newconn, LINK_S1);
            netconn_close(newconn);
            netconn_delete(newconn);
        }
    }
}
```

### 4.7 TcpCliTask 实现模板

```c
void TcpCliTask_C1(void *argument)
{
    for (;;) {
        struct netconn *conn = netconn_new(NETCONN_TCP);
        ip_addr_t remote_ip;
        IP_ADDR4(&remote_ip, cfg->links[2].rip[0], ...);

        if (netconn_connect(conn, &remote_ip, cfg->links[2].rport) == ERR_OK) {
            tcp_client_worker(conn, LINK_C1);
        }
        netconn_close(conn);
        netconn_delete(conn);
        vTaskDelay(pdMS_TO_TICKS(cfg->links[2].reconnect_interval));
    }
}
```

### 4.8 UartRxTask 实现

```c
void UartRxTask(void *argument)
{
    for (;;) {
        ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10));
        /* 处理 UART2/UART3 DMA 接收数据 */
        /* MUX=0: 构造 route_msg_t 指针投递到 xUartTxQueue */
        /* MUX=1: 提取 MUX 帧，DSTMASK=0 投 xConfigQueue，否则投 xUartTxQueue */
        /* 从 xTcpRxQueue 取数据，写入 UART DMA 发送 */
    }
}
```

### 4.9 ConfigTask 实现

```c
void ConfigTask(void *argument)
{
    for (;;) {
        char *cmd;
        xQueueReceive(xConfigQueue, &cmd, portMAX_DELAY);
        config_process_at_cmd(cmd);
        /* 通过 UART1 发送响应 */
    }
}
```

## 五、最终协议实现模型

### 5.1 MUX 帧承载层

数据口启用 MUX 后，统一处理如下帧：

```text
SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
```

实现职责：

1. 识别帧边界
2. 解析长度字段
3. 提取 `SRCID`
4. 解析 `DSTMASK`
5. 按控制帧或数据帧分流

### 5.2 控制帧与数据帧分离

控制规则固定如下：

- `DSTMASK = 0x00`：系统控制帧
- `DSTMASK != 0x00`：业务数据帧

系统控制帧处理要求：

1. `PAYLOAD` 解释为 AT 文本
2. AT 文本必须以 `\r\n` 结束
3. 控制帧投递到 `ConfigTask`

业务数据帧处理要求：

1. `SRCID` 表示单一源端点
2. `DSTMASK` 表示目标端点集合
3. `RouteTask` 根据 `DSTMASK` 做多目标分发

### 5.3 统一端点编码

内部与外部文档统一使用以下端点编码：

| 端点 | 编码 |
|------|------|
| `C1` | `0x01` |
| `C2` | `0x02` |
| `UART2` | `0x04` |
| `UART3` | `0x08` |
| `S1` | `0x10` |
| `S2` | `0x20` |

实现要求：

- `SRCID` 为单值
- `DSTMASK` 为位图
- `DSTMASK=0x00` 仅保留为控制帧

## 六、配置层设计

### 6.1 MUX 记录

`MUX` 为全局记录，仅控制设备数据口是否进入 MUX 承载模式。

取值：

- `0`：普通透传
- `1`：MUX 透传

### 6.2 NET 记录

`NET` 为全局静态网络记录：

```text
IP,MASK,GW,MAC
```

说明：

- 设备只有一张网卡，因此不为每个实例单独配置本地 IP
- 当前实现目标中不包含 DHCP

### 6.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` 表示远端目标

## 七、模块职责

### 7.1 配置模块 `config.c/.h`

最终职责：

1. 解析 `AT+MUX`
2. 解析 `AT+NET`
3. 解析 `AT+LINK`
4. 加载与保存配置
5. 处理 `SAVE / RESET / DEFAULT`

### 7.2 UART 透传模块 `uart_trans.c/.h`

最终职责：

1. 保持 `USART2 / USART3` 的 `DMA + IDLE` 接收发送基线
2. 在 `MUX=0` 时执行普通透传
3. 在 `MUX=1` 时执行 MUX 帧收发
4. 将控制帧与业务数据帧分流

### 7.3 TCP Server / Client 模块（需重写）

原 `tcp_server.c` / `tcp_client.c` 基于 lwIP RAW API 回调模式，需重写为 netconn 阻塞模式：

1. 删除所有 `tcp_pcb` / `tcp_recv` / `tcp_accept` 回调代码
2. 改用 `netconn_new` / `netconn_bind` / `netconn_listen` / `netconn_accept`（Server）
3. 改用 `netconn_new` / `netconn_connect`（Client）
4. 收发改为 `netconn_recv` / `netconn_write` 阻塞调用
5. 内部 ring buffer 可取消（netconn 内部已有 pbuf 缓冲）
6. 每个 TCP 任务内直接处理路由，通过 Queue 指针传递数据

补充约束（当前实现口径）：

1. `Client` 链路保留固定 `LPORT` 配置语义，以满足产品侧对固定源端口的依赖
2. 在 `lwIP + netconn` 模型下，若 `Client` 继续使用优雅 `netconn_close()`，相同本地端口的快速重连会受 `TIME_WAIT` 影响
3. 因此当前工程对 `Client` 会话结束后的释放路径采用 abortive close（`tcp_abort` / RST）以立即释放 PCB 与本地端口
4. 该策略只针对 `Client` 固定端口重连路径，不扩展到 `Server` listener 或一般被动关闭场景
5. 该策略的已知代价是：对端可能看到 `RST`，且尾部未完成发送的数据不会再走优雅 `FIN/ACK` 收尾

### 7.4 FreeRTOS 初始化 `freertos.c`

CubeMX 生成的 FreeRTOS 初始化文件，职责：

1. 定义默认任务 `StartDefaultTask`
2. 用户在 `MX_FREERTOS_Init` 中创建自定义任务

### 7.5 FreeRTOS 配置 `FreeRTOSConfig.h`

关键配置项：

| 配置项 | 值 | 说明 |
|--------|-----|------|
| `configUSE_PREEMPTION` | 1 | 抢占式调度 |
| `configTICK_RATE_HZ` | 1000 | 1ms tick |
| `configMINIMAL_STACK_SIZE` | 128 | 最小栈（words） |
| `configTOTAL_HEAP_SIZE` | 10240 | FreeRTOS 堆大小 |
| `configMAX_PRIORITIES` | 7 | 最大优先级数 |
| `configUSE_MUTEXES` | 1 | 启用互斥锁 |
| `configUSE_COUNTING_SEMAPHORES` | 1 | 启用计数信号量 |
| `configUSE_RECURSIVE_MUTEXES` | 1 | 启用递归互斥锁 |
| `configCHECK_FOR_STACK_OVERFLOW` | 2 | 栈溢出检测方式 2 |
| `configUSE_MALLOC_FAILED_HOOK` | 1 | 内存分配失败钩子 |
| `configSUPPORT_DYNAMIC_ALLOCATION` | 1 | 动态内存分配 |

## 八、lwIP 配置（NO_SYS=0 + netconn）

### 8.1 lwIP 线程模型

由于采用 `NO_SYS=0`，lwIP 将运行以下线程：

1. `tcpip_thread`（优先级 6，栈 512 words）：lwIP 核心线程，处理所有协议栈内部事件
2. 应用任务通过 `netconn` API 与 lwIP 交互，由 `tcpip_thread` 消息机制保证线程安全
3. `LWIP_TCPIP_CORE_LOCKING=1`：允许应用任务在持有核心锁时直接调用 `netconn_write`，无需通过邮箱中转

### 8.2 lwIP 关键配置项（lwipopts.h）

| 配置项 | 值 | 说明 |
|--------|-----|------|
| `NO_SYS` | 0 | 启用 OS 抽象 |
| `LWIP_NETCONN` | 1 | 启用 netconn API |
| `LWIP_SOCKET` | 0 | 不使用 socket API，节省 RAM |
| `LWIP_TCPIP_CORE_LOCKING` | 1 | 允许直接发送，减少延时 |
| `MEM_SIZE` | 8192 | lwIP 堆大小 |
| `PBUF_POOL_SIZE` | 10 | pbuf 池数量 |
| `MEMP_NUM_NETCONN` | 8 | 2监听 + 4连接 + 2余量 |
| `MEMP_NUM_NETBUF` | 8 | netconn 缓冲 |
| `MEMP_NUM_TCP_PCB` | 4 | TCP 控制块 |
| `MEMP_NUM_TCP_PCB_LISTEN` | 2 | TCP 监听 |
| `MEMP_NUM_TCP_SEG` | 24 | TCP 段 |
| `MEMP_NUM_TCPIP_MSG_API` | 8 | API 消息池 |
| `MEMP_NUM_TCPIP_MSG_INPKT` | 8 | 入包消息池 |
| `TCP_MSS` | 536 | 保守 MSS |
| `TCP_SND_BUF` | 8×MSS=4288 | 发送缓冲 |
| `TCP_WND` | 8×MSS=4288 | 接收窗口 |
| `TCPIP_THREAD_STACKSIZE` | 512 | tcpip_thread 栈 |
| `TCPIP_THREAD_PRIO` | 6 (最高) | tcpip_thread 优先级 |
| `LWIP_DHCP` | 0 | 不使用 DHCP |
| `LWIP_UDP` | 0 | 不使用 UDP |

### 8.3 sys_arch 移植层

`Drivers/LwIP/port/sys_arch.c` 需实现 lwIP 到 FreeRTOS 的适配：

1. `sys_thread_new`：创建 lwIP 线程（即 `tcpip_thread`）
2. `sys_mbox_*`：消息邮箱（基于 FreeRTOS Queue，容量 `TCPIP_MBOX_SIZE=8`）
3. `sys_sem_*`：信号量（基于 FreeRTOS Semaphore）
4. `sys_mutex_*`：互斥锁（基于 FreeRTOS Mutex）
5. `sys_arch_protect / unprotect`：临界区保护（基于 `vPortEnterCritical / vPortExitCritical`）

### 8.4 lwIP 初始化流程

```c
/* 在 MX_FREERTOS_Init 或 NetPollTask 中调用 */
void lwip_init_task(void)
{
    /* tcpip_thread 在 lwip_init() 或第一个 sys_thread_new 时自动启动 */
    tcpip_init(NULL, NULL);
    
    /* 等待 tcpip_thread 就绪 */
    /* 添加网络接口 */
    netif_add(&ch390_netif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);
    netif_set_default(&ch390_netif);
    netif_set_up(&ch390_netif);
}
```

## 九、中断与 HAL 时间基准

### 9.1 HAL 时间基准

FreeRTOS 下 `SysTick` 被 FreeRTOS 占用，HAL 时间基准改用 `TIM4`：

- `TIM4` 配置为 1ms 中断（72MHz / (71+1) / (999+1) = 1kHz）
- `HAL_InitTick` 使用 `TIM4` 而非 `SysTick`
- `uwTick` 在 `TIM4_IRQHandler` 中递增

### 9.2 中断优先级规划

| 中断 | 优先级 | 说明 |
|------|--------|------|
| `SysTick` | 15（最低） | FreeRTOS tick |
| `PendSV` | 15（最低） | FreeRTOS 上下文切换 |
| `SVCall` | 0 | FreeRTOS 服务调用 |
| `TIM4` | 0 | HAL 时间基准 |
| `EXTI0` | 5 | CH390 中断 |
| `DMA1_Ch2~7` | 5 | UART DMA |
| `USART1/2/3` | 5 | UART 中断 |
| `SPI1` | 5 | SPI 中断 |

FreeRTOS 可管理的中断优先级必须 >= `configMAX_SYSCALL_INTERRUPT_PRIORITY`（本工程为 5）。

## 十、内存预算（路径 A 精确估算）

以 `STM32F103RCT6` 为目标（48KB SRAM）：

### 10.1 RAM 预算

| 项目 | 大小 | 说明 |
|------|------|------|
| 启动栈 (MSP) | 2,048 B | `startup_stm32f103xe.s` 定义 0x800 |
| FreeRTOS 堆 (heap_4) | 10,240 B | `configTOTAL_HEAP_SIZE` |
| 任务栈 (9 任务) | 13,312 B | 3,328 words × 4 (见上表) |
| lwIP 堆 (MEM_SIZE) | 8,192 B | |
| PBUF 池 (10 个) | ~6,000 B | 10 × ~600B |
| MEMP 池 | ~4,000 B | netconn/netbuf/tcpip_msg/pcb/seg |
| UART DMA+Ring 缓冲 | 2,304 B | 2 通道 × (256+256+512+384)/2 |
| 路由缓冲池 (4×512B) | 2,048 B | 零拷贝指针传递 |
| 配置结构 | 1,024 B | |
| **合计** | **~49,168 B** | |
| **余量 (RCT6 48KB)** | **~-928 B** | ⚠️ 超出，需优化或换 RDT6 |
| **余量 (RDT6 64KB)** | **~15,264 B** | ✅ 充裕 |

### 10.2 优化空间（RCT6 下）

若坚持使用 RCT6，可通过以下措施压缩到 48KB 以内：

| 优化项 | 节省 |
|--------|------|
| 取消 TCP 任务的 ring buffer（netconn 内部有 pbuf 缓冲） | -2,048 B |
| `configTOTAL_HEAP_SIZE` 降至 8KB | -2,048 B |
| `MEM_SIZE` 降至 6KB | -2,048 B |
| TcpCliTask 栈降至 192 words × 2 | -512 B |
| **优化后合计** | **~42,504 B** |
| **RCT6 余量** | **~5,464 B** |

### 10.3 备选 MCU

当 RAM 最终不够用时，切换为 `STM32F103RDT6`（pin-to-pin 替代）：

| 项目 | RCT6 | RDT6 |
|------|------|------|
| Flash | 256 KB | 384 KB |
| SRAM | 48 KB | 64 KB |
| 引脚 | LQFP64 | LQFP64（完全兼容） |
| 启动文件 | `startup_stm32f103xe.s` | `startup_stm32f103xe.s` |
| 宏定义 | `STM32F103xE` | `STM32F103xE` |
| Flash 算法 | `STM32F10x_HD` | `STM32F10x_HD`（相同） |
| SRAM 大小 | `0xC000` | `0x10000` |
| Flash 大小 | `0x40000` | `0x60000` |

### 10.4 Flash 预算

| 项目 | 估计值 | 说明 |
|------|--------|------|
| FreeRTOS 内核 | ~8 KB | 含 CMSIS-RTOS V2 |
| HAL 驱动 | ~20 KB | GPIO/UART/SPI/DMA/IWDG/TIM |
| lwIP 协议栈 | ~50 KB | core + api + ipv4 + netif + sys_arch |
| CH390 驱动 | ~4 KB | |
| 应用代码 | ~20 KB | config/uart_trans/tcp_server/tcp_client |
| **合计** | ~102 KB | RCT6 预留 154 KB，RDT6 预留 282 KB |

## 十一、硬件资源

### 11.1 MCU

- 型号：`STM32F103RCT6`
- Flash：`256 KB`
- SRAM：`48 KB`
- 主频：`72 MHz`

### 11.2 主要外设

- `SPI1`：连接 `CH390D`
- `USART1`：配置串口
- `USART2`：数据透传串口
- `USART3`：数据透传串口
- `DMA1`：3 路 UART 收发 DMA
- `EXTI0`：CH390 中断输入
- `IWDG`：独立看门狗
- `TIM4`：HAL 时间基准（替代 SysTick）

### 11.3 引脚分配

| 引脚 | 功能 | 用途 |
|------|------|------|
| PA2 | USART2_TX | 数据透传串口 |
| PA3 | USART2_RX | 数据透传串口 |
| PA4 | SPI1_NSS | CH390D 片选 |
| PA5 | SPI1_SCK | CH390D SPI 时钟 |
| PA6 | SPI1_MISO | CH390D SPI 数据输入 |
| PA7 | SPI1_MOSI | CH390D SPI 数据输出 |
| PA9 | USART1_TX | 配置串口 |
| PA10 | USART1_RX | 配置串口 |
| PB0 | EXTI0 | CH390D INT |
| PB1 | GPIO_Output | CH390D RESET |
| PB10 | USART3_TX | 数据透传串口 |
| PB11 | USART3_RX | 数据透传串口 |
| PC13 | GPIO_Output | 状态 LED |
| PD0/PD1 | HSE | 8MHz 外部晶振 |

## 十二、实现边界

1. 保持单网卡静态网络模型
2. 不实现 DHCP
3. 不实现旧 `S1... / C1...` 外部协议字段
4. 不在文档中保留兼容层描述
5. 所有 AT 文本控制统一要求 `\r\n` 结束
6. FreeRTOS 堆管理使用 `heap_4.c`
7. HAL 时间基准使用 `TIM4` 而非 `SysTick`

## 十三、文档一致性要求

后续实现、联调、测试与代码注释必须遵守以下统一口径：

1. 对外协议只使用 `MUX / NET / LINK`
2. 控制帧只使用 `DSTMASK=0x00`
3. MUX 帧格式固定为 `SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL`
4. AT 手册、需求说明、技术实现三份文档不得再出现历史展开式字段

## 十四、路径 A 实现清单

### 14.1 必须重写的模块

| 模块 | 原实现 | 目标实现 | 说明 |
|------|--------|----------|------|
| `tcp_server.c/.h` | lwIP RAW API 回调 | netconn 阻塞任务 | `tcp_new` → `netconn_new`，回调 → 阻塞循环 |
| `tcp_client.c/.h` | lwIP RAW API 回调 | netconn 阻塞任务 | 同上 |
| `sys_arch.c/.h` | NO_SYS=1 空壳 | FreeRTOS 移植层 | `sys_mbox`、`sys_sem`、`sys_mutex`、`sys_thread` |
| `lwipopts.h` | NO_SYS=1 | NO_SYS=0 + netconn | 已更新 |
| `main.c` | while(1) 轮询 | FreeRTOS 任务创建 | `App_Poll()` → 各任务函数 |
| `stm32f1xx_it.c` | 裸机 ISR | FreeRTOS ISR | `xxFromISR` API |

### 14.2 可复用（需适配）的模块

| 模块 | 适配内容 |
|------|----------|
| `uart_trans.c/.h` | 添加 `xTaskNotifyFromISR` 替代 poll 模式 |
| `config.c/.h` | 添加 `xQueueReceive` 替代 `config_poll()` |
| `flash_param.c/.h` | 无需修改 |
| `CH390` 驱动 | `ethernetif_poll` 改为 NetPollTask 调用，SPI 加 Mutex 保护 |
| `ethernetif.c` | 添加 `netif_add` 的 `tcpip_input` 回调 |

### 14.3 无需修改的模块

| 模块 | 说明 |
|------|------|
| `FreeRTOSConfig.h` | 已更新（任务优先级宏、堆大小） |
| `startup_stm32f103xe.s` | 已适配 RCT6 |
| `TCP2UART.ioc` | 已适配 RCT6 + FreeRTOS + TIM4 |
| `MDK-ARM/TCP2UART.uvprojx` | 已适配 RCT6 + xE 宏 |
| MUX 帧编解码 | 协议逻辑与 RTOS 无关 |

### 14.4 新增模块

| 模块 | 职责 |
|------|------|
| `route_msg.c/.h` | 零拷贝路由消息池管理 |
| `task_tcp_server.c/.h` | netconn Server 任务模板 |
| `task_tcp_client.c/.h` | netconn Client 任务模板 |
| `task_net_poll.c/.h` | CH390 poll + link check 任务 |
