Files
TCP2UART/项目技术实现.md

647 lines
21 KiB
Markdown
Raw Permalink 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` 项目基于 `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 任务设计(路径 Anetconn + 多 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 buffernetconn 内部有 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 KBRDT6 预留 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 任务 |