From 51b174d3304efe06c76a9cf4be65ead057f05501 Mon Sep 17 00:00:00 2001 From: xiao Date: Wed, 10 Jun 2026 11:00:28 +0800 Subject: [PATCH] docs: reorganize project documentation --- AT固件使用手册.md | 13 +- CH390_RX_PBUF泄漏修复复盘.md | 245 ------------- CODING_PROMPT.md | 66 ---- README.md | 105 ++++++ 交接清单.md | 212 ------------ 工程调试指南.md | 474 ------------------------- 项目代码阅读指南.md | 449 ++++++++++++++++++++++++ 项目技术实现.md | 646 ----------------------------------- 项目需求说明.md | 191 ----------- 9 files changed, 561 insertions(+), 1840 deletions(-) delete mode 100644 CH390_RX_PBUF泄漏修复复盘.md delete mode 100644 CODING_PROMPT.md create mode 100644 README.md delete mode 100644 交接清单.md delete mode 100644 工程调试指南.md create mode 100644 项目代码阅读指南.md delete mode 100644 项目技术实现.md delete mode 100644 项目需求说明.md diff --git a/AT固件使用手册.md b/AT固件使用手册.md index 4e54a4f..65f21a6 100644 --- a/AT固件使用手册.md +++ b/AT固件使用手册.md @@ -2,9 +2,9 @@ ## 1. 文档范围 -本文档定义 `TCP2UART` 项目的最终 AT 外部协议。 +本文档定义 `TCP2UART` 项目的 AT 外部协议。 -本文档只描述最终协议模型,不保留任何历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。 +本文档只描述当前代码实现支持的外部命令,不保留历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。项目结构、任务模型和数据流请阅读 `README.md` 与 `项目代码阅读指南.md`。 适用对象: @@ -56,12 +56,12 @@ SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL 字段定义: -- `SYNC`:帧起始标记,建议固定为 `0x7E` +- `SYNC`:帧起始标记,固定为 `0x7E` - `LEN_H / LEN_L`:`PAYLOAD` 长度,高字节在前 - `SRCID`:单字节源端点 ID - `DSTMASK`:单字节目标端点位图 - `PAYLOAD`:负载数据 -- `TAIL`:帧结束标记,建议固定为 `0x7F` +- `TAIL`:帧结束标记,固定为 `0x7F` 规则: @@ -394,9 +394,10 @@ AT+RESET\r\n 1. `AT+SAVE\r\n` 2. `AT+RESET\r\n` -## 13. 相关文件 +## 13. 相关文档与文件 +- 项目入口:`README.md` +- 代码结构和数据流:`项目代码阅读指南.md` - AT 命令实现:`App/config.c` - 配置结构与默认值:`App/config.h` - FreeRTOS 任务定义:`Core/Src/freertos.c` -- 调试指导:`工程调试指南.md` diff --git a/CH390_RX_PBUF泄漏修复复盘.md b/CH390_RX_PBUF泄漏修复复盘.md deleted file mode 100644 index ce05eb0..0000000 --- a/CH390_RX_PBUF泄漏修复复盘.md +++ /dev/null @@ -1,245 +0,0 @@ -# CH390 / lwIP 固定次数 ping 失败问题修复复盘 - -## 1. 问题现象 - -在 TCP2UART 固件运行后,设备初期可以正常 ARP 和 ping,但连续 ping 一段时间后不再响应。 - -典型现象: - -- 设备 IP:`192.168.31.100` -- 设备 MAC:`02:00:00:00:00:01` -- 对端/网关 IP:`192.168.31.1` -- 对端/网关 MAC:`00:e0:4c:28:1e:60` -- 失败后设备仍持续发送 TCP SYN/RST 或 client timeout 相关流量,说明 TX、任务调度和应用层并未整体死机。 -- 失败后对端继续向设备 MAC 发送 ICMP/ARP,但设备不再回复。 - -关键抓包: - -- `WiresharkLog/04290150.pcapng` - - `seq=1884..1891` 共 8 次 ping reply 正常。 - - 第 9 次 `seq=1892` 开始无 reply。 -- `WiresharkLog/04290206.pcapng` - - 曾把 `PBUF_POOL_SIZE` / `MEMP_NUM_TCPIP_MSG_INPKT` 从 8 临时扩大到 16。 - - 成功 ping 从 8 次变为 `seq=1900..1915` 共 16 次。 - - 第 17 次 `seq=1916` 开始无 reply。 - -这个“成功次数随池大小移动”的现象证明:问题不是 CH390 随机丢包,也不是 PHY/TX 死掉,而是每次成功处理 ping 后都有某个 pbuf 引用没有释放,最终耗尽 `PBUF_POOL`。 - -## 2. 排查过程中的重要结论 - -### 2.1 CH390 RX 读包路径曾存在风险,但不是最终根因 - -早期排查时发现 CH390 RX 路径与参考驱动存在若干不一致,已修正: - -- `ch390_receive_packet()` 按参考序列读取 RX ready:先读 `MRCMDX` dummy,再读 `MRCMDX1`。 -- 校验 RX header 的 `Head` 字节必须为 `CH390_PKT_RDY`。 -- CH390 RX SRAM 中的 `rx_len` 包含 Ethernet FCS,交给 lwIP 前需要减去 4 字节。 -- `ch390_rx_reset()` 显式写 `MPTRCR_RST_RX` 复位 RX memory pointer。 - -这些修正确保 CH390 RX FIFO 读包更接近参考实现,但无法解释“固定 8 次/16 次后失败”。 - -### 2.2 扩大 lwIP 池只能延迟问题 - -曾临时将如下配置从 8 提到 16: - -```c -#define PBUF_POOL_SIZE 16 -#define MEMP_NUM_PBUF 16 -#define MEMP_NUM_TCPIP_MSG_INPKT 16 -``` - -结果成功 ping 次数也从 8 变成 16。这说明扩大池子只是延迟耗尽,不能作为根修复。 - -最终已恢复为 8: - -```c -#define PBUF_POOL_SIZE 8 -#define MEMP_NUM_PBUF 8 -#define MEMP_NUM_TCPIP_MSG_INPKT 8 -``` - -### 2.3 `tcpip_input()` 异步队列不是最终根因 - -项目启用了 lwIP core locking。为避免每个 RX 包占用 `MEMP_TCPIP_MSG_INPKT`,配置已改为同步输入: - -```c -#define LWIP_TCPIP_CORE_LOCKING 1 -#define LWIP_TCPIP_CORE_LOCKING_INPUT 1 -``` - -这样 `tcpip_input()` 会在 core lock 下同步调用 `ethernet_input()`,不再通过 `TCPIP_MSG_INPKT` 邮箱异步排队。 - -但用户后续验证仍然固定 8 次后停止,且每次成功 ping 都已经有 reply,因此说明 RX 包确实已经进入 ICMP 处理路径,问题更可能是 reply 输出路径增加了 pbuf 引用但未释放。 - -## 3. 最终根因 - -最终根因位于: - -```text -Drivers/LwIP/src/netif/ethernet.c -``` - -原 `ethernet_output()` 实现: - -```c -q = pbuf_alloc(PBUF_RAW_TX, SIZEOF_ETH_HDR, PBUF_RAM); -if (q == NULL) { - LINK_STATS_INC(link.memerr); - LINK_STATS_INC(link.drop); - return ERR_MEM; -} - -pbuf_chain(q, p); - -ethhdr = (struct eth_hdr *)q->payload; -SMEMCPY(ðhdr->dest, dst, sizeof(struct eth_addr)); -SMEMCPY(ðhdr->src, src, sizeof(struct eth_addr)); -ethhdr->type = lwip_htons(eth_type); - -return netif->linkoutput(netif, q); -``` - -问题在 `pbuf_chain(q, p)`。 - -lwIP 的 `pbuf_chain()` 会执行: - -```c -pbuf_ref(t); -``` - -也就是给被挂接的原始 pbuf `p` 引用计数加 1。 - -ICMP echo reply 路径会复用 RX pbuf: - -```text -ethernetif_poll() - -> tcpip_input() - -> ethernet_input() - -> ip4_input() - -> icmp_input() - -> ip4_output_if() - -> etharp_output() - -> ethernet_output() -``` - -`icmp_input()` 末尾本身会 `pbuf_free(p)`,这部分是正确的。但在原实现中,`ethernet_output()` 通过 `pbuf_chain(q, p)` 给 `p` 额外加了一次引用,却没有在 `linkoutput()` 返回后释放临时 header pbuf `q`。 - -因此每次 ping 的引用计数变化是: - -```text -RX pbuf 初始 ref = 1 -pbuf_chain(q, p) 后 ref = 2 -icmp_input() 末尾 pbuf_free(p) 后 ref = 1 -=> p 永远没有回到 0,PBUF_POOL 泄漏 1 个 -``` - -所以: - -- `PBUF_POOL_SIZE=8` 时,8 次 ping reply 后耗尽。 -- 临时扩大到 16 时,16 次 ping reply 后耗尽。 - -## 4. 修复方案 - -修复 `ethernet_output()`,在同步 `linkoutput()` 完成后释放临时 header pbuf 链: - -```c -err_t ethernet_output(struct netif *netif, - struct pbuf *p, - const struct eth_addr *src, - const struct eth_addr *dst, - u16_t eth_type) -{ - struct pbuf *q; - struct eth_hdr *ethhdr; - err_t err; - - LWIP_ASSERT("netif != NULL", netif != NULL); - LWIP_ASSERT("p != NULL", p != NULL); - LWIP_ASSERT("src != NULL", src != NULL); - LWIP_ASSERT("dst != NULL", dst != NULL); - - q = pbuf_alloc(PBUF_RAW_TX, SIZEOF_ETH_HDR, PBUF_RAM); - if (q == NULL) { - LINK_STATS_INC(link.memerr); - LINK_STATS_INC(link.drop); - return ERR_MEM; - } - - pbuf_chain(q, p); - - ethhdr = (struct eth_hdr *)q->payload; - SMEMCPY(ðhdr->dest, dst, sizeof(struct eth_addr)); - SMEMCPY(ðhdr->src, src, sizeof(struct eth_addr)); - ethhdr->type = lwip_htons(eth_type); - - err = netif->linkoutput(netif, q); - pbuf_free(q); - return err; -} -``` - -为什么这里可以释放 `q`: - -- 本项目 `low_level_output()` 是同步发送。 -- 它会立即遍历 pbuf 链,把数据复制到 `s_tx_buffer`。 -- 随后调用 `ch390_runtime_send_packet()` 把连续 buffer 发给 CH390。 -- `low_level_output()` 返回后不再持有 pbuf 指针。 - -因此 `ethernet_output()` 在 `linkoutput()` 返回后释放 `q` 是正确的。 - -`pbuf_free(q)` 会同时: - -- 释放临时 Ethernet header pbuf `q`; -- 解除 `pbuf_chain()` 对原始 RX pbuf `p` 增加的引用; -- 之后 `icmp_input()` 末尾的 `pbuf_free(p)` 可以真正把 RX pbuf 归还 `PBUF_POOL`。 - -## 5. 不要做的错误修复 - -### 5.1 不要在 `netif->input()` 成功后手动释放 pbuf - -驱动层当前逻辑是正确的: - -```c -input_err = ch390_netif.input(p, &ch390_netif); -if (input_err != ERR_OK) { - pbuf_free(p); -} -``` - -`netif->input()` 返回 `ERR_OK` 时,pbuf ownership 已经交给 lwIP。此时驱动不能再 `pbuf_free(p)`,否则会造成 double-free 或 use-after-free。 - -### 5.2 不要只扩大 `PBUF_POOL_SIZE` - -扩大池子只会让失败次数从 8 变 16、32……不会修复泄漏。 - -### 5.3 不要继续优先怀疑 CH390 PHY/TX - -抓包中失败后设备仍持续发送 TCP SYN/RST,说明 TX 和任务仍活着。固定次数失败更符合 pbuf 引用泄漏。 - -## 6. 验证结果 - -修复后 Keil 构建通过: - -```text -"TCP2UART\TCP2UART.axf" - 0 Error(s), 0 Warning(s). -Program Size: Code=93376 RO-data=2768 RW-data=456 ZI-data=56032 -``` - -用户烧录验证后确认问题已修复。 - -## 7. 后续排查建议 - -如后续再次出现固定次数网络停止,优先检查: - -1. 是否存在 `pbuf_chain()` / `pbuf_ref()` 后没有配对 `pbuf_free()` 的路径。 -2. 是否有 ARP pending queue 长时间持有 pbuf。 -3. 是否有 TCP `recvmbox` / 应用桥接队列背压长期持有 pbuf。 -4. 是否有人在 `netif->input()` 成功后错误释放 pbuf,导致内存破坏。 - -推荐排查点: - -- `Drivers/LwIP/src/netif/ethernet.c` -- `Drivers/LwIP/src/core/ipv4/icmp.c` -- `Drivers/LwIP/src/core/ipv4/etharp.c` -- `Drivers/LwIP/src/core/pbuf.c` -- `Drivers/LwIP/src/netif/ethernetif.c` diff --git a/CODING_PROMPT.md b/CODING_PROMPT.md deleted file mode 100644 index e34f65d..0000000 --- a/CODING_PROMPT.md +++ /dev/null @@ -1,66 +0,0 @@ -# TCP2UART 当前交接 Prompt - -## 1. 用途 - -本文件不再承担“项目从零编码任务说明”的职责,而是作为**当前工程的交接入口 Prompt**使用。 - -长期有效的工程知识、调试经验和现状说明,已经分别固化到其它文档,不再全部堆在本文件中。 - ---- - -## 2. 接手后先读什么 - -请按以下顺序阅读: - -1. `交接清单.md` —— 当前状态、接下来要做什么、怎么做 -2. `工程调试指南.md` —— 已固化的调试经验与当前工程真实边界 -3. `项目技术实现.md` —— 架构、任务模型、协议模型 -4. `项目需求说明.md` -5. `AT固件使用手册.md` - ---- - -## 3. 当前工程一句话状态 - -当前项目已从早期 bring-up 阶段推进到 full-task 运行期调试阶段;`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前在 `STM32F103RCT6` 上的 RAM/heap 余量过低,已被认定为调试噪声的主要来源之一,因此推荐下一阶段先切到 pin2pin 的 `STM32F103RDT6` 再继续分析。 - ---- - -## 4. 下一位 agent 的当前目标 - -请不要把当前任务理解成“立刻继续加逻辑修补”。当前更重要的是: - -1. 完成 `STM32F103RCT6 -> STM32F103RDT6` 目标切换 -2. 使用真实 Keil 日志重新确认构建成功 -3. 在新器件上复测当前代码基线 -4. 比较: - - 故障是否消失 - - 是否明显后移 - - 是否仍停在相同 enabled path -5. 只有拿到新器件上的第一轮 RTT / heap / HWM 证据后,再决定下一步最小化改动 - ---- - -## 5. 工作约束 - -1. 构建真值以 `MDK-ARM/build_capture.txt`、`TCP2UART.build_log.htm`、`.map` 为准 -2. 不要再把 viewer 当作当前构建真值 -3. 不要忽视 `DIAG_TASK_ISOLATION=1 正常、=0 异常` 这个前提 -4. 在新器件的第一轮复测前,避免继续做大范围代码改动 -5. 每次只做一个能明显改变故障边界的最小改动,并保留 RTT 证据 - ---- - -## 6. 可直接复制给下一位 agent 的起始 Prompt - -```text -请先阅读:`交接清单.md`、`工程调试指南.md`、`项目技术实现.md`。 - -当前项目是 STM32F103 + FreeRTOS + lwIP + CH390 的 TCP↔UART 透传工程。此前在 `STM32F103RCT6` 上调试时,`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前最关键的资源事实是:在 `RCT6` 上 full-task 创建完四个 TCP 任务后,FreeRTOS heap 只剩约 944 bytes,静态 RAM 也已逼近物理上限,因此当前推荐先切换到 pin2pin 的 `STM32F103RDT6`,保持现有代码基线基本不变,先完成第一轮换片复测,再根据新器件上的 RTT、free/min heap 和 enabled `S1/C1` 行为决定下一步。 - -你的当前目标不是立刻修完所有问题,而是: -1. 完成 `RCT6 -> RDT6` 目标切换; -2. 用真实 Keil 日志确认构建通过; -3. 在新器件上复测当前代码,判断故障是否消失、后移或保持原状; -4. 仅在拿到新器件上的第一轮 RTT 后,再继续做最小化的下一步判别。 -``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..31b33a6 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# TCP2UART 项目说明 + +`TCP2UART` 是一个基于 `STM32F103RCT6 + CH390D` 的 TCP 与双串口透传固件。系统运行在 `FreeRTOS` 上,网络协议栈使用 `lwIP NO_SYS=0 + netconn API`,通过 `SEGGER RTT` 输出调试日志,工程入口为 Keil MDK 项目 `MDK-ARM/TCP2UART.uvprojx`。 + +本仓库当前文档已经收敛为两类: + +1. 面向开发者的代码阅读与系统理解文档。 +2. 面向上位机、测试和联调人员的 AT 命令接口文档。 + +## 硬件与软件基线 + +| 项目 | 内容 | +|------|------| +| 主控 | `STM32F103RCT6` | +| 以太网芯片 | `CH390D` | +| 配置串口 | `USART1` | +| 数据串口 | `USART2`、`USART3` | +| RTOS | `FreeRTOS` | +| TCP/IP | `lwIP`,`NO_SYS=0`,启用 `netconn` API | +| 调试输出 | `SEGGER RTT` | +| 构建工程 | `MDK-ARM/TCP2UART.uvprojx` | + +`USART1` 只承担 AT 配置面。`USART2/USART3` 是业务数据口,可工作在普通透传模式或 MUX 帧模式。网络侧提供 2 路 TCP Server 实例和 2 路 TCP Client 实例,统一使用 `LINK` 配置模型管理。 + +## 当前文档地图 + +| 文档 | 读者 | 内容 | +|------|------|------| +| `README.md` | 所有人 | 项目总览、文档入口、快速理解路径 | +| `项目代码阅读指南.md` | 固件开发、调试、交接人员 | 代码目录、启动流程、任务模型、数据流、阅读顺序、调试线索 | +| `AT固件使用手册.md` | 上位机、测试、联调人员 | AT 命令、参数模型、默认值、配置流程、常见错误 | + +旧的需求说明、技术实现、调试交接、Prompt 和单点问题复盘文档已经合并到上述文档中。临时调试结论不再作为根目录入口,避免把某一轮现场状态误认为长期项目事实。 + +## 一句话架构 + +```text +远端 TCP 连接 + | + v +TcpSrvTask_S1/S2 或 TcpCliTask_C1/C2 + | + v +route_msg_t + FreeRTOS Queue + | + v +UartRxTask / uart_trans + | + v +USART2 / USART3 +``` + +反向数据流也走同一套路由对象:数据口收到字节后由 `UartRxTask` 判断普通透传或 MUX 帧,再投递到对应 `xLinkTxQueues[]`,最终由 TCP 任务调用 `netconn_write()` 发回网络。 + +## 快速阅读顺序 + +首次接手建议按下面顺序阅读: + +1. `README.md`:确认硬件、软件、文档入口。 +2. `项目代码阅读指南.md` 的“总体架构”和“业务数据流示例”:先建立整体图。 +3. `Core/Src/main.c`:理解上电初始化顺序。 +4. `Core/Src/freertos.c`:理解队列、信号量和任务创建。 +5. `App/config.c`、`AT固件使用手册.md`:理解配置模型和 AT 命令。 +6. `App/tcp_server.c`、`App/tcp_client.c`、`App/uart_trans.c`、`App/route_msg.c`:理解 TCP 与 UART 如何互相转发。 +7. `App/task_net_poll.c`、`Drivers/CH390/*`、`Drivers/LwIP/src/netif/ethernetif.c`:需要定位网络底层问题时再深入。 + +## 核心配置模型 + +固件内部把所有链路抽象为 4 条 `LINK`: + +| 角色 | 内部索引 | 端点编码 | 含义 | +|------|----------|----------|------| +| `S1` | `CONFIG_LINK_S1` | `0x10` | TCP Server 1 | +| `S2` | `CONFIG_LINK_S2` | `0x20` | TCP Server 2 | +| `C1` | `CONFIG_LINK_C1` | `0x01` | TCP Client 1 | +| `C2` | `CONFIG_LINK_C2` | `0x02` | TCP Client 2 | +| `UART2` / `U0` | `LINK_UART_U0` | `0x04` | 数据串口 0 | +| `UART3` / `U1` | `LINK_UART_U1` | `0x08` | 数据串口 1 | + +每条 `LINK` 记录包含:启用状态、本地端口、远端 IP、远端端口、绑定的数据串口。外部配置命令见 `AT固件使用手册.md`。 + +## 常用开发入口 + +| 目的 | 文件 | +|------|------| +| 上电初始化 | `Core/Src/main.c` | +| 任务和队列创建 | `Core/Src/freertos.c` | +| AT 命令与 Flash 参数 | `App/config.c`、`App/flash_param.c` | +| 路由消息池 | `App/route_msg.c` | +| UART DMA/IDLE、普通透传、MUX 帧 | `App/uart_trans.c` | +| TCP Server | `App/tcp_server.c` | +| TCP Client | `App/tcp_client.c` | +| 网络初始化和轮询 | `App/task_net_poll.c` | +| CH390 与 lwIP netif | `Drivers/CH390/*`、`Drivers/LwIP/src/netif/ethernetif.c` | +| RTT 日志封装 | `Core/Src/debug_log.c` | + +## 调试提示 + +1. 启动阶段先看 RTT 中的 `debug_log_boot()` 里程碑,例如 `hal-init`、`clock-config`、`peripherals-ready`、`tasks-created`、`scheduler-start`。 +2. 网络阶段重点看 `NetPollTask` 的 `tcpip-init`、`netif-init`、`netif-ready`、`post-ready free/min heap` 日志。 +3. TCP 或 UART 透传异常时,优先检查 `route_send()` 返回的 `pool`、`queue`、`invalid`,它们能区分消息池耗尽、队列满和参数错误。 +4. HardFault、MemManage、BusFault、UsageFault 会进入 `Debug_TrapWithRttHint()`,应先保留 RTT 现场再修改代码。 +5. `STM32F103RCT6` RAM 余量有限;如果 full-task 模式下问题呈现强资源相关特征,应优先核对堆、栈、水位和 lwIP 池配置,不要只看业务逻辑。 + +更详细的阅读和调试路线见 `项目代码阅读指南.md`。 diff --git a/交接清单.md b/交接清单.md deleted file mode 100644 index 3857af4..0000000 --- a/交接清单.md +++ /dev/null @@ -1,212 +0,0 @@ -# TCP2UART 调试交接清单 - -## 1. 文档目的 - -本清单用于把当前 `TCP2UART` 工程的调试状态、已验证结论、后续动作建议一次性交接给下一位 agent 或开发者。 - -这份文档本身就可以当作下一位 agent 的工作 prompt 使用。 - ---- - -## 2. 先读哪些文档 - -接手本工程后,推荐按以下顺序阅读: - -1. `交接清单.md` —— 当前状态、下一步、禁止回退到哪些旧假设 -2. `工程调试指南.md` —— 已固化的调试经验与真实工程边界 -3. `项目技术实现.md` —— 架构、任务模型、协议模型 -4. `项目需求说明.md` —— 用户需求与协议要求 -5. `AT固件使用手册.md` —— AT 命令与配置面 - ---- - -## 3. 当前工程状态(交接时刻) - -### 3.1 当前平台与构建状态 - -1. 当前 Keil 工程目标仍是 `STM32F103RC` -2. 当前代码可以真实构建通过 -3. 当前 FreeRTOS + lwIP 固件版本线从 `V2.0.0` 开始,裸机固件版本线从 `V1.0.0` 开始 -4. 当前 RTOS 基线 release:`https://git.furtherverse.com/gaoro-xiao/TCP2UART/releases/tag/V2.0.0` -5. Release 附件: - - `TCP2UART-RTOS-V2.0.0.hex` - - `TCP2UART-RTOS-V2.0.0.elf` -6. 当前构建真值应查看: - - `MDK-ARM/build_capture.txt` - - `MDK-ARM/TCP2UART/TCP2UART.build_log.htm` - - `MDK-ARM/TCP2UART/TCP2UART.map` -7. `V2.0.0` release 构建验证: - - `"TCP2UART\TCP2UART.axf" - 0 Error(s), 0 Warning(s).` - - `TCP2UART-RTOS-V2.0.0.hex` 用于烧录 - - `TCP2UART-RTOS-V2.0.0.elf` 用于符号/调试 - -### 3.2 当前调试结论摘要 - -1. `DIAG_TASK_ISOLATION=1` 稳定 -2. `DIAG_TASK_ISOLATION=0` 仍会卡死 -3. 卡死边界已经从更早的启动阶段被推进到更靠后的 enabled `netconn_*` 路径 -4. 在 `RCT6` 上,四个 TCP task 创建后 `FreeRTOS heap` 仅剩约 `944 bytes` -5. 这说明当前 `RCT6` 上的资源余量已经严重干扰调试判断 -6. 因此当前推荐策略是:**先换到 pin2pin 的 `STM32F103RDT6`,再继续 full-task 调试** - -### 3.3 已做过并有信息量的改动/观察 - -以下工作已经做过,不要在没有新理由的情况下重复一遍: - -1. 清理与恢复 `DIAG_TASK_ISOLATION=0` -2. 用真实 Keil 日志替代 viewer 作为构建真值 -3. staged creation:将四个 TCP task 延后到 `netif-ready` 后创建 -4. `lwIP netif` 初始化、post-init、post-ready 关键 RTT 日志 -5. CH390 TX bounded wait / timeout 观察 -6. TCP task 栈从 `256` 提高到 `384 words` -7. TCP task 入口 `hwm` 日志 -8. 对 `C1` 增加 one-shot first-connect defer discriminator - -这些动作都让故障边界后移了,但仍未在 `RCT6` 上把问题彻底消灭。 - ---- - -## 4. 当前最可信的判断 - -当前最可信的判断不是“某一行代码单点必错”,而是: - -1. `RCT6` 上的静态 RAM 占用与 FreeRTOS heap 余量都已经接近极限 -2. disabled 的 task 可以持续打印,说明调度器与基础日志路径未整体死亡 -3. enabled 的 `S1 / C1` 一旦进入真实 `netconn_*` 路径,就更容易触发新的运行期问题 -4. 因此如果继续在 `RCT6` 上做更多 discriminator,很容易一直被资源边界噪声带偏 - -换句话说,**先把“内存极限平台”这个干扰项拿掉,再看逻辑问题还剩多少,是当前性价比最高的路线。** - ---- - -## 5. 下一位 agent 现在应该做什么 - -### 5.1 第一目标 - -先完成 `STM32F103RCT6 -> STM32F103RDT6` 的工程切换,然后在**尽量不再改业务逻辑**的前提下复现当前版本。 - -### 5.2 为什么先这样做 - -因为下一位 agent 最先需要回答的,不是“立刻怎么修”,而是: - -1. 换片后 full-task 模式是否还挂 -2. 如果还挂,挂点是否后移 -3. 换片后 `free/min heap` 是否显著改善 -4. enabled `S1 / C1` 是否能进入更深的 `netconn_*` 路径 - ---- - -## 6. 下一位 agent 的推荐工作步骤 - -### Step 1:切换目标器件到 `STM32F103RDT6` - -建议动作: - -1. 更新 Keil target 里的 device 选择 -2. 对齐 Flash / RAM 容量描述 -3. 确认 linker / scatter / startup 相关目标描述与新器件一致 -4. 再次真实构建,确认 `0 Error(s), 0 Warning(s)` - -### Step 2:保持当前代码基线,直接上板复测 - -要求: - -1. 不要一换片就继续改业务逻辑 -2. 使用当前代码基线直接验证 -3. 仍以 `build_capture.txt` 和 RTT 为主证据 - -### Step 3:收集第一轮换片后的关键证据 - -至少记录: - -1. `netif-ready` 后是否仍卡死 -2. `free/min heap` 变化 -3. enabled `S1 / C1` 的新 RTT 行为 -4. `C1 first-connect defer` 是否仍然影响故障边界 -5. LED 心跳和 IWDG 表现是否变化 - -### Step 4:根据换片结果分流 - -#### 情况 A:换片后明显稳定 / 不再挂 - -说明: - -1. RAM 压力是主要阻碍项之一 -2. 后续需要回头做“资源预算收敛”,而不是继续把当前临时参数直接当最终方案 - -#### 情况 B:换片后仍挂,但更晚 / 更深 - -说明: - -1. 当前逻辑问题仍在 -2. 但已经去掉了最主要的资源噪声 -3. 这时再围绕 `S1 / C1` 的真实 `netconn_new / bind / connect / listen / accept` 做最小日志/判别,会更有信息量 - -#### 情况 C:换片后几乎同点位仍挂 - -说明: - -1. 主问题不再是单纯 RAM -2. 应优先检查 enabled 路径的 API 使用、阻塞行为、线程间交互与资源释放 - ---- - -## 7. 不要重复的方向 - -下一位 agent 接手后,除非有新证据,否则不要优先回到以下旧方向: - -1. 单纯怀疑 startup/init 早期 bring-up -2. 把 viewer 当构建真值 -3. 继续只靠加大 TCP task 栈来解释所有现象 -4. 默认把 CH390 TX timeout 当成当前一号嫌疑 -5. 在 `RCT6` 上继续大量增加日志、队列、栈或临时 buffer - ---- - -## 8. 关键文件 - -### 构建/目标 - -1. `MDK-ARM/TCP2UART.uvprojx` -2. `MDK-ARM/build_capture.txt` -3. `MDK-ARM/TCP2UART/TCP2UART.build_log.htm` -4. `MDK-ARM/TCP2UART/TCP2UART.map` - -### 任务与运行期 - -1. `Core/Inc/FreeRTOSConfig.h` -2. `Core/Src/freertos.c` -3. `App/task_net_poll.c` -4. `App/tcp_server.c` -5. `App/tcp_client.c` - -### 网络与驱动 - -1. `Drivers/LwIP/src/netif/ethernetif.c` -2. `Drivers/CH390/CH390.c` -3. `Drivers/CH390/CH390.h` - -### 文档 - -1. `工程调试指南.md` -2. `项目技术实现.md` -3. `交接清单.md` -4. `CODING_PROMPT.md` - ---- - -## 9. 可直接给下一位 agent 的 Prompt - -下面这段文字可以直接作为下一位 agent 的起始 prompt: - -```text -请先阅读:`交接清单.md`、`工程调试指南.md`、`项目技术实现.md`。 - -当前项目是 STM32F103 + FreeRTOS + lwIP + CH390 的 TCP↔UART 透传工程。此前在 `STM32F103RCT6` 上调试时,`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前最关键的资源事实是:在 `RCT6` 上 full-task 创建完四个 TCP 任务后,FreeRTOS heap 只剩约 944 bytes,静态 RAM 也已逼近物理上限,因此当前推荐先切换到 pin2pin 的 `STM32F103RDT6`,保持现有代码基线基本不变,先完成第一轮换片复测,再根据新器件上的 RTT、free/min heap 和 enabled `S1/C1` 行为决定下一步。 - -你的当前目标不是立刻修完所有问题,而是: -1. 完成 `RCT6 -> RDT6` 目标切换; -2. 用真实 Keil 日志确认构建通过; -3. 在新器件上复测当前代码,判断故障是否消失、后移或保持原状; -4. 仅在拿到新器件上的第一轮 RTT 后,再继续做最小化的下一步判别。 -``` diff --git a/工程调试指南.md b/工程调试指南.md deleted file mode 100644 index b093af8..0000000 --- a/工程调试指南.md +++ /dev/null @@ -1,474 +0,0 @@ -# TCP2UART 调试指导 - -## 1. 适用范围 - -本指导面向当前 `TCP2UART` 工程,覆盖以下四类调试场景: - -1. `STM32F103RCT6 + CH390D` 的基础 bring-up -2. `SEGGER RTT`、异常陷阱与 FreeRTOS 任务运行状态确认 -3. `USART1` 配置口、`USART2/USART3` 数据口与 `MUX / NET / LINK[idx]` 协议联调 -4. `TCP Server / TCP Client / UART` 三层数据通路联调与问题隔离 - -本指导默认基线如下: - -1. 当前工程采用 `FreeRTOS` 任务调度架构 -2. `CH390` 运行时访问由 `xSpiMutex` 保护,`NetworkTask` 持有主要访问权 -3. 调试输出统一使用 `SEGGER RTT` -4. 当前应用层协议模型已经收敛到 `MUX / NET / LINK[idx]` -5. 当前代码应以 `MDK-ARM` 工程构建结果为准 - ---- - -## 2. 当前工程边界与真实状态 - -在进入现场调试前,先统一以下工程边界: - -1. 当前项目的主要软件路径已经切换为: - - `NET`:网络基础参数 - - `LINK[idx]`:链路配置记录 - - `MUX`:数据口承载模式 -2. 对外 AT 配置面应只围绕以下命令展开: - - `AT` / `AT+?` / `AT+QUERY` - - `AT+MUX` / `AT+NET` / `AT+LINK` - - `AT+SAVE` / `AT+RESET` / `AT+DEFAULT` -3. 已有结论表明: - - MCU 启动、RTT、FreeRTOS 调度、TIM4 心跳路径可工作 - - `CH390D` 基础寄存器读写与 `lwIP netif` 基本链路已经打通过一次 - - 真实硬件侧曾定位到 `CH390D` 供电滤波电容虚焊问题 -4. 当前调试重点是: - - FreeRTOS 任务是否正常创建与调度 - - `MUX / NET / LINK[idx]` 协议是否与代码一致 - - UART / TCP / CH390 三层通路是否协同稳定 - - 参数保存、复位和恢复流程是否可靠 - ---- - -## 3. 代码入口与调试责任边界 - -### 3.1 启动与 FreeRTOS 入口 - -以下代码路径是 bring-up 的第一现场: - -1. `Core/Src/main.c` - - `main()`:总启动入口 - - `SystemClock_Config()`:时钟初始化 - - `MX_FREERTOS_Init()`:FreeRTOS 任务创建(在 `freertos.c` 中实现) -2. `Core/Src/freertos.c` - - `StartDefaultTask()`:默认任务(LED 心跳 + 看门狗) - - `MX_FREERTOS_Init()`:用户任务创建入口 -3. `Core/Src/stm32f1xx_it.c` - - 故障与中断入口 - - `TIM4_IRQHandler`:HAL 时间基准 - - `USART1/2/3`、`EXTI0`、DMA 回调等联调关键入口 - -### 3.2 CH390 责任边界 - -当前 CH390 调试必须遵守以下责任边界: - -1. `Drivers/CH390/CH390_Interface.c`:GPIO / SPI / 寄存器与内存事务 -2. `Drivers/CH390/CH390.c`:芯片级 helper -3. `Drivers/CH390/ch390_runtime.c`:唯一的运行时拥有者 -4. `Drivers/LwIP/src/netif/ethernetif.c`:netif glue 与轮询桥接 -5. SPI 访问由 `xSpiMutex` 保护,避免多任务竞争 - -### 3.3 配置口与业务口边界 - -1. `USART1`:AT 配置口,接收 `AT` 命令 -2. `USART2 / USART3`:数据口,普通透传或 MUX 承载 - ---- - -## 4. 当前硬件与调试工具基线 - -### 4.1 核心硬件对象 - -1. MCU:`STM32F103RCT6`(256KB Flash / 48KB SRAM) -2. 以太网芯片:`CH390D` -3. 配置串口:`USART1` -4. 数据串口:`USART2 / USART3` -5. 调试输出:`SEGGER RTT` - -### 4.2 构建与下载基线 - -1. `MDK-ARM/TCP2UART.uvprojx` -2. 启动文件:`startup_stm32f103xe.s` -3. 目标器件:`STM32F103RC` -4. 预处理器宏:`USE_HAL_DRIVER, STM32F103xE` -5. FreeRTOS + lwIP 固件版本线从 `V2.0.0` 开始;裸机固件版本线从 `V1.0.0` 开始。 -6. 当前 RTOS release:`https://git.furtherverse.com/gaoro-xiao/TCP2UART/releases/tag/V2.0.0` -7. Release 附件命名: - - `TCP2UART-RTOS-V2.0.0.hex` - - `TCP2UART-RTOS-V2.0.0.elf` - -### 4.3 常用调试工具 - -1. `Keil MDK-ARM` -2. `ST-Link / J-Link` -3. `SEGGER RTT Viewer` -4. `PowerShell` -5. `tools/start_tcp_debug_server.ps1` -6. `tools/tcp_debug_server.py` - ---- - -## 5. FreeRTOS 专项调试 - -### 5.1 任务状态检查 - -使用 `vTaskList` 获取所有任务运行状态: - -```c -char buf[512]; -vTaskList(buf); -SEGGER_RTT_WriteString(0, buf); -``` - -输出格式: - -```text -任务名 状态 优先级 剩余栈 编号 -NetworkTask R 4 120 1 -UartTask B 4 200 2 -ConfigTask B 3 150 3 -RouteTask R 3 180 4 -DefaultTask B 1 80 5 -IDLE R 0 100 6 -Tmr Svc B 2 90 7 -``` - -状态码:`R`=Ready, `B`=Blocked, `S`=Suspended, `D`=Deleted, `I`=Invalid - -### 5.2 栈溢出检测 - -已启用 `configCHECK_FOR_STACK_OVERFLOW = 2`,溢出时自动调用: - -```c -void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) -{ - SEGGER_RTT_printf(0, "STACK OVERFLOW: %s\n", pcTaskName); - __BKPT(0); -} -``` - -### 5.3 堆内存失败检测 - -已启用 `configUSE_MALLOC_FAILED_HOOK`,分配失败时自动调用: - -```c -void vApplicationMallocFailedHook(void) -{ - SEGGER_RTT_printf(0, "MALLOC FAILED: Free heap = %u\n", xPortGetFreeHeapSize()); - __BKPT(0); -} -``` - -### 5.4 常见 FreeRTOS 调试陷阱 - -1. **优先级反转**:使用 Mutex(含优先级继承)而非 Binary Semaphore 保护共享资源 -2. **死锁**:多 Mutex 场景确保所有任务按相同顺序获取 -3. **中断优先级**:FreeRTOS 可管理的 ISR 优先级必须 >= `configMAX_SYSCALL_INTERRUPT_PRIORITY`(本工程 5) -4. **栈不足**:每个任务定期调用 `uxTaskGetStackHighWaterMark(NULL)` 检查剩余栈 -5. **禁止在中断中调用阻塞 API**:必须使用 `FromISR` 后缀版本 - ---- - -## 6. 启动阶段调试顺序 - -建议按 P0 ~ P5 顺序推进,不要跳层。 - -### 6.1 P0:确认最小基础条件 - -1. `MDK-ARM` 可构建并产出新的 `axf/hex/map` -2. 板卡可正常下载与复位 -3. RTT 可连接并看到启动输出 -4. FreeRTOS 任务创建成功,`DefaultTask` LED 心跳可工作 -5. `TIM4` 1ms tick 正常运行 - -### 6.2 P1:确认 FreeRTOS 调度正常 - -上电或复位后,优先确认: - -1. `StartDefaultTask` 是否进入运行 -2. `vTaskList` 输出是否显示所有预期任务 -3. `xPortGetFreeHeapSize()` 返回值是否合理 -4. 无 `STACK OVERFLOW` 或 `MALLOC FAILED` 输出 - -### 6.3 P2:确认 CH390 初始化链路 - -启动阶段应重点关注 `NetworkTask` 中初始化日志: - -1. `ETH init: gpio` -2. `ETH init: spi` -3. `ETH init: reset` -4. `ETH init: probe` -5. `ETH init: default` -6. `ETH init: mac` -7. `ETH init: done` - -### 6.4 P3:确认 TCP 链路 - -1. lwIP `tcpip_thread` 是否正常运行 -2. TCP Server 是否在指定端口监听 -3. TCP Client 是否成功连接远端 - -### 6.5 P3.5:确认 ARP / ICMP 基础网络可达 - -在继续 TCP 联调前,建议先把 `ARP + ping(ICMP)` 跑通。对于当前 `CH390 + lwIP + FreeRTOS` 架构,这一步不是可选项,而是 TCP 可达之前必须成立的网络基线。 - -#### 6.5.1 推荐最小验证顺序 - -1. 先确认板卡 IP、掩码、MAC 与 PC 所在网段一致 -2. 上电后先观察 RTT,确认 `ETH init: done` 已出现 -3. 在 PC 侧执行一次 `ping <板卡IP>`,同时开启 Wireshark 抓包 -4. 先看是否出现发往板卡 IP 的 ARP request,再看设备是否回 ARP reply -5. ARP 正常后,再看是否出现 ICMP echo request / echo reply 成对出现 - -#### 6.5.2 当前工程推荐观察点 - -如果网络基础链路有疑问,建议按以下分层观察,不要只看某一层: - -1. **raw RX 层**:CH390 是否确实收到了以太网帧 -2. **Ethernet demux 层**:`ethernet_input()` 是否识别到 `ETHTYPE_ARP` / `ETHTYPE_IP` -3. **协议处理层**:`etharp_input()` / `ip_input()` 是否真正进入 -4. **协议发包层**:lwIP 是否已经生成待发送的 ARP reply / ICMP reply -5. **驱动发送层**:`low_level_output()` / CH390 TX 是否真正把帧送出 - -这次 bring-up 证明,`raw RX 正常` 并不等于 `lwIP 已真正处理该帧`。如果只看到底层收到了包,就直接假设协议栈一定会回复,通常会把排查方向带偏。 - -#### 6.5.3 这次 ARP / ICMP bring-up 的关键结论 - -本轮调试中,最终根因位于: - -- `Drivers/LwIP/src/netif/ethernet.c` - -问题本质是: - -1. `ethernet_input()` 在 `ETHTYPE_ARP` 分支中,曾直接调用 `etharp_input(p, netif)` -2. 但 `etharp_input()` 要求 `p->payload` 从 **ARP 头** 开始,而不是从 Ethernet 头开始 -3. 因此如果没有先执行: - -```c -pbuf_remove_header(p, SIZEOF_ETH_HDR) -``` - -则 ARP 包虽然被收到了,但会在 `etharp_input()` 的早期校验中被静默丢弃,最终表现为: - -1. Wireshark 能看到 PC 发来的 ARP request -2. 板子侧底层收包计数在增长 -3. 但设备始终不回 ARP reply -4. ping 也自然不会成功 - -当前应保留的正确处理方式如下: - -```c -case ETHTYPE_ARP: - if (netif->flags & NETIF_FLAG_ETHARP) { - if (pbuf_remove_header(p, SIZEOF_ETH_HDR)) { - pbuf_free(p); - return ERR_OK; - } - etharp_input(p, netif); - } else { - pbuf_free(p); - } - return ERR_OK; -``` - -#### 6.5.4 为什么这类问题容易漏看 - -这类问题常见但隐蔽,原因通常有三点: - -1. 根因非常小,外在表现却像“整个发送链路都坏了” -2. 多个低层信号可能同时正常,容易误导为 SPI / TX / CH390 初始化问题 -3. `rx ok`、`ARP 帧计数在涨`、`链路已 up` 都不代表协议层一定接受了该帧 - -因此,后续遇到“收得到包但就是不回”的问题时,优先检查: - -1. 传给上层协议处理函数时,`pbuf->payload` 是否已经对齐到正确协议头 -2. glue-layer 是否和 lwIP 原生调用约定一致 -3. 观察点是否已经覆盖到 `demux -> protocol handler -> linkoutput` 这一整条链 - -#### 6.5.5 建议保留的最小验收标准 - -在认定网络基线“已经打通”之前,至少应满足: - -1. Keil 工程可稳定构建通过 -2. 上电后可稳定看到网络初始化完成日志 -3. Wireshark 中能看到设备对本机 ARP request 做出 reply -4. PC 对设备 `ping` 时,能看到 ICMP echo request / reply 成对出现 -5. RTT 中无 `STACK OVERFLOW`、`MALLOC FAILED`、异常 trap 等故障信号 - ---- - -## 7. MUX / NET / LINK[idx] 联调指导 - -### 7.1 协议总则 - -与裸机版本完全一致,参见 `AT固件使用手册.md`。 - -### 7.2 推荐最小 MUX 联调顺序 - -1. 先在 `MUX=0` 下跑通原始透传 -2. 再切换 `MUX=1` -3. 先发一个控制帧,确认 `DSTMASK=0x00` 路径可通 -4. 再发一个单目标数据帧 -5. 最后验证多目标位图转发 - ---- - -## 8. 异常、卡死与假死排查 - -### 8.1 看到 `TRAP:` 时怎么做 - -1. 先记录 RTT 中的 trap 标签 -2. 立刻用调试器查看当前 PC / LR / 调用栈 -3. 结合 `Core/Src/stm32f1xx_it.c` 中对应 handler 定位异常类型 - -### 8.2 FreeRTOS 任务卡死时怎么做 - -1. 使用 `vTaskList` 检查各任务状态 -2. 如果某个任务始终 `B`(Blocked),检查其等待的队列/信号量 -3. 检查是否有 Mutex 被持有但从未释放 -4. 使用调试器暂停,查看各任务的调用栈 - -### 8.3 常见 FreeRTOS 陷阱 - -1. 在 ISR 中误调用阻塞 API(如 `xQueueSend` 而非 `xQueueSendFromISR`) -2. 中断优先级低于 `configMAX_SYSCALL_INTERRUPT_PRIORITY` 但调用了 FreeRTOS API -3. Mutex 持有期间任务被删除导致 Mutex 永不释放 -4. 栈溢出导致邻近变量被破坏 - ---- - -## 9. 常见误区 - -1. 不要继续沿用"CH390 恒为全 `0xFF`"过时结论 -2. 不要在多个任务中直接访问 CH390 SPI(必须通过 Mutex 保护) -3. 不要在没有芯片脚侧证据前,只凭 GPIO 判断总线正常 -4. 不要在基础寄存器读写尚不可信时,直接调高层业务 -5. 不要在 ISR 中执行复杂 SPI 事务或调用阻塞 API -6. 不要忽视 `configCHECK_FOR_STACK_OVERFLOW` 报告 -7. 不要把“底层已经收到 ARP 包”等同于“lwIP 一定已经正确处理 ARP 包” -8. 不要忽略 glue-layer 对 `pbuf->payload` 起始位置的约定,特别是 `Ethernet header -> ARP/IP header` 的切换 -9. 不要在 ARP / ICMP 还没闭环前,就直接怀疑 TCP Server / TCP Client 逻辑 -10. 不要在没有抓包和分层观测点的情况下,只凭单一日志就断言故障位于 TX 或 SPI 层 - ---- - -## 10. 推荐配套阅读 - -1. `AT固件使用手册.md` -2. `项目技术实现.md` -3. `项目需求说明.md` -4. `Keil工程配置说明.txt` - ---- - -## 11. 近期调试经验固化(2026-04) - -本节用于固化这一轮 `FreeRTOS + lwIP + CH390` 联调过程中已经验证过的经验,后续调试默认以本节为前提,不要反复回到已排除的旧假设。 - -### 11.1 构建结果的真值来源 - -当前工程的构建真值必须以 `Keil` 实际构建日志为准,而不是辅助 viewer。 - -推荐优先级如下: - -1. `MDK-ARM/build_capture.txt` -2. `MDK-ARM/TCP2UART/TCP2UART.build_log.htm` -3. `MDK-ARM/TCP2UART/TCP2UART.map` - -说明: - -1. `keil-build-viewer.exe` 只能辅助观察内存占用或旧构建快照 -2. viewer 未刷新时,不代表当前代码没有变化,往往只是最新构建没成功或 viewer 数据滞后 -3. 后续任何“编译通过 / RAM 变化 / 目标器件切换”的判断,都必须引用以上三类 Keil 真实产物 - -### 11.2 近期已验证的事实 - -以下结论已经被本轮调试反复验证: - -1. `DIAG_TASK_ISOLATION=1` 时,系统可以稳定运行 -2. `DIAG_TASK_ISOLATION=0` 时,full-task 模式仍会卡死 -3. 启动阶段 `lwip_netif_init()`、deferred `xTaskCreate()`、`netif-ready` 等关键日志已经真实跑通 -4. 之前通过加日志、分阶段创建 TCP 任务、调整栈大小后,卡死边界会继续后移,但问题不会完全消失 -5. 这说明当前问题不是单一固定点故障,而是“逻辑路径 + 极低资源余量”共同作用的结果 - -### 11.3 已排除或降级优先级的方向 - -以下方向目前不再应作为一号假设: - -1. **startup/init 失败** - - 当前日志已能稳定走到 `netif-ready` - - 因此主要问题不再是最初的 bring-up 链路本身 -2. **单纯 CH390 TX 无限等待是当前主因** - - 已为 TX 路径增加过 bounded wait / timeout 观察点 - - 最新故障没有先落到 `[ETH] tx timeout` 分支 -3. **只靠继续增大 TCP task 栈就能解决问题** - - `256 -> 384 words` 后,故障边界虽然继续后移,但系统仍会卡死 - - 栈确实曾是问题的一部分,但不是唯一剩余问题 - -### 11.4 当前最重要的资源压力结论 - -当前 `STM32F103RCT6`(48KB SRAM)上的真实资源压力已经高到会直接干扰调试判断: - -1. 真实构建中,`ZI-data` 已接近物理 RAM 上限(约 95%+) -2. 在 `DIAG_TASK_ISOLATION=0` 下,创建完四个 TCP 任务后,`xPortGetFreeHeapSize()` 只剩约 `944 bytes` -3. 这个余量不足以让后续 `netconn_*`、semaphore、mailbox、任务栈回旋空间保持清晰边界 -4. 因此当前在 `RCT6` 上继续做 discriminator 时,结果会持续混入“资源边界噪声” - -这也是为什么本轮调试后半段已经把重点从“继续在 RCT6 上做更多小修补”切换到“先换更大 RAM 的 pin2pin 器件再继续分析”。 - -### 11.5 当前推荐的硬件调试策略 - -当前推荐的下一阶段策略如下: - -1. 先从 `STM32F103RCT6` 切换到 pin2pin 的 `STM32F103RDT6` -2. 在切换器件后,尽量保持当前代码基线不做大改 -3. 先复测当前版本的 RTT 与运行边界,看故障是否: - - 消失 - - 明显后移 - - 仍然停在相同 enabled path -4. 再根据新器件上的表现,区分: - - 资源压力主导 - - 逻辑 / 时序 / API 使用问题仍然存在 - -### 11.6 换片后第一轮调试目标 - -切换到 `RDT6` 后,第一轮调试不追求立刻修复,而是优先回答下面几个问题: - -1. 当前 full-task 模式是否仍然卡死 -2. `free/min heap` 是否明显高于 `RCT6` 版本 -3. enabled 的 `S1 / C1` 是否能够继续进入更深的 `netconn_new / bind / connect / listen / accept` 路径 -4. 之前为了定位问题而加入的 discriminator(例如 `C1 first-connect defer`)是否仍然影响故障边界 - -如果这些问题不先回答,后续继续改代码的结论可信度会很差。 - -### 11.7 当前建议保留的调试原则 - -后续 agent 或开发者继续接手本工程时,建议遵守以下原则: - -1. 不要在 `RCT6` 上继续大量加日志、加栈、加 queue 深度后再试图解释现象 -2. 不要把 viewer 当作当前构建真值 -3. 不要忽略 `DIAG_TASK_ISOLATION=1 正常、=0 异常` 这个前提 -4. 不要一次性修改 `C1/S1/CH390/lwIP` 多个方向,避免再次失去因果关系 -5. 每次只做一个能明显改变故障边界的最小改动,并用 RTT + Keil build 结果交叉验证 - -### 11.8 固定 Client 端口重连策略(TIME_WAIT 取舍) - -当前工程的 `Client` 链路保留固定 `LPORT` 配置语义,默认 `C1/C2` 均使用明确的本地源端口。 - -在 `lwIP + netconn` 路径下,如果仍沿用优雅 `netconn_close()`,则相同 `LPORT` 的快速重连会受到 TCP `TIME_WAIT` 影响,表现为一段时间内重复 `bind/connect` 失败。 - -结合本项目的约束,当前版本固化如下取舍: - -1. 不取消 `Client` 固定 `LPORT` 语义 -2. 不依赖扩大 PCB 池作为主修复手段 -3. 不通过降低 `TCP_MSL` 改写全局 TCP 保守语义 -4. 对 `Client` 主动断开后的释放路径采用 abortive close(RST),以立即释放 PCB 与本地端口 - -使用该策略时应明确接受以下副作用: - -1. 对端可能看到 `RST` 或“连接被重置” -2. 连接尾部未完成发送的数据不会再走优雅关闭路径 -3. 该策略仅用于固定 `Client` 端口快速重连场景,不应直接推广到所有 TCP 关闭路径 diff --git a/项目代码阅读指南.md b/项目代码阅读指南.md new file mode 100644 index 0000000..8b1cf30 --- /dev/null +++ b/项目代码阅读指南.md @@ -0,0 +1,449 @@ +# TCP2UART 项目代码阅读指南 + +## 1. 文档目的 + +本文面向接手 `TCP2UART` 的固件开发、联调和调试人员,目标是说明当前代码如何组织、启动后如何运行、TCP 与 UART 数据如何流动,以及应该按什么顺序阅读源码。 + +本文只描述当前工程的长期有效结构。历史调试交接文档中的某些“下一步动作”属于当时现场状态,不再作为当前项目入口。 + +## 2. 系统边界 + +### 2.1 硬件边界 + +- MCU:`STM32F103RCT6` +- 以太网芯片:`CH390D` +- 配置口:`USART1` +- 数据口:`USART2`、`USART3` +- 调试输出:`SEGGER RTT` + +### 2.2 软件边界 + +- 工程:`MDK-ARM/TCP2UART.uvprojx` +- 调度:`FreeRTOS` +- 网络:`lwIP NO_SYS=0 + netconn API` +- 网络输入:`tcpip_thread` + `ethernetif` + CH390 驱动 +- 业务链路:2 路 TCP Server,2 路 TCP Client,2 路 UART 数据口 +- 配置协议:`AT`、`MUX`、`NET`、`LINK`、`BAUD`、`SAVE`、`RESET`、`DEFAULT` + +### 2.3 目录结构 + +```text +App/ + app_runtime.h 全局任务、队列、信号量声明 + config.c/.h AT 命令、运行配置、默认值、Flash 保存 + flash_param.c/.h Flash 参数读写与 CRC + route_msg.c/.h 固定消息池和路由消息封装 + task_net_poll.c/.h lwIP/CH390 初始化、netif ready、网络重启 + tcp_server.c/.h S1/S2 TCP Server 任务 + tcp_client.c/.h C1/C2 TCP Client 任务 + uart_trans.c/.h USART2/3 业务数据接收、发送、MUX 编解码 + +Core/Inc, Core/Src/ + main.c 上电入口和外设初始化顺序 + freertos.c FreeRTOS 队列、信号量、任务创建 + stm32f1xx_it.c 中断入口,尤其是 UART IDLE 和 CH390 EXTI + usart.c/dma.c/... STM32CubeMX 生成的外设初始化 + debug_log.c/.h RTT 日志和异常提示 + +Drivers/CH390/ + CH390.c/.h 芯片级寄存器/辅助操作 + CH390_Interface.c SPI/GPIO 与 CH390 事务封装 + +Drivers/LwIP/ + src/include/arch/lwipopts.h 当前 lwIP 配置 + src/netif/ethernetif.c CH390 与 lwIP netif 胶水层 + port/sys_arch.c lwIP 在 FreeRTOS 上的 sys_arch 适配 +``` + +## 3. 总体架构 + +```text ++------------------------------------------------------+ +| AT / Control Plane | +| USART1 IDLE DMA -> ConfigTask -> config_process_at_cmd| ++------------------------------------------------------+ +| Configuration Model | +| MUX / NET / LINK[S1,S2,C1,C2] / BAUD | ++------------------------------------------------------+ +| Routing Layer | +| route_msg fixed pool + xTcpRxQueue + xLinkTxQueues[] | ++------------------------------------------------------+ +| Data Tasks | +| UartRxTask + TcpSrvTask_S1/S2 + TcpCliTask_C1/C2 | ++------------------------------------------------------+ +| Network Runtime | +| NetPollTask + tcpip_thread + ethernetif + CH390 | ++------------------------------------------------------+ +| HAL / DMA / IRQ | +| USART1/2/3 DMA+IDLE, SPI1, EXTI0, TIM4 timebase | ++------------------------------------------------------+ +``` + +这套架构的核心思想是:TCP 任务和 UART 任务不直接互相调用,而是通过 `route_msg_t` 和 FreeRTOS 队列传递数据。这样可以把“从哪里来”“发到哪里去”“数据多长”统一表示,便于普通透传和 MUX 模式共用同一套路由机制。 + +## 4. 启动流程 + +启动入口在 `Core/Src/main.c` 的 `main()`。 + +1. `HAL_Init()` 初始化 HAL、Flash 接口和基础 tick。 +2. `debug_log_init()` 初始化 RTT 日志,并输出 `hal-init`。 +3. `SystemClock_Config()` 配置系统时钟。 +4. 初始化外设:`MX_GPIO_Init()`、`MX_DMA_Init()`、`MX_USART1_UART_Init()`。 +5. `config_init()` 从 Flash 读取配置;读取失败则 `config_set_defaults()`。 +6. `ApplyConfiguredUartBaudrates()` 根据配置设置 `USART2/USART3` 波特率。 +7. 初始化 `USART2`、`USART3`、`SPI1`。 +8. 初始化 LED 并执行 `CH390_HardwareReset()`。 +9. `osKernelInitialize()` 初始化 RTOS 内核。 +10. `MX_FREERTOS_Init()` 创建队列、信号量和基础任务。 +11. `osKernelStart()` 启动调度器。 + +调试启动问题时,优先按 RTT boot 日志确认流程卡在哪个里程碑。 + +## 5. FreeRTOS 对象和任务 + +`Core/Src/freertos.c` 是理解运行时结构的关键文件。 + +### 5.1 全局对象 + +| 对象 | 类型 | 用途 | +|------|------|------| +| `xNetSemaphore` | Binary Semaphore | `EXTI0` 通知网络轮询任务处理 CH390 事件 | +| `xTcpRxQueue` | Queue | TCP 任务收到网络数据后投递给 `UartRxTask` | +| `xConfigQueue` | Queue | `USART1` AT 命令或 MUX 控制帧投递给 `ConfigTask` | +| `xLinkTxQueues[4]` | Queue | UART 收到的数据投递给指定 S1/S2/C1/C2 TCP 任务 | +| `route_msg` pool | 固定池 | 避免每包动态分配,最大载荷见 `ROUTE_MSG_MAX_PAYLOAD` | + +### 5.2 基础任务 + +`MX_FREERTOS_Init()` 固定创建: + +| 任务 | 入口 | 职责 | +|------|------|------| +| `defaultTask` | `StartDefaultTask()` | LED 心跳、看门狗 | +| `NetPoll` | `NetPollTask()` | 初始化 lwIP/netif,轮询或响应 CH390 中断,网络 ready 后启动 TCP 任务 | +| `UartRx` | `UartRxTask()` | 处理 USART2/3 RX,执行普通透传或 MUX 路由 | +| `Config` | `ConfigTask()` | 处理 AT 命令并回复 | + +当 `DIAG_TASK_ISOLATION` 打开时,`UartRx` 和 `Config` 会被隔离,这只用于调试,不代表正常产品形态。 + +### 5.3 网络任务 + +网络任务不是在 `MX_FREERTOS_Init()` 里立即全部创建,而是在 `NetPollTask()` 完成 `tcpip_init()`、`lwip_netif_init()` 并设置 `g_netif_ready = pdTRUE` 后,由 `app_start_network_tasks()` 按配置创建: + +| 任务 | 条件 | 职责 | +|------|------|------| +| `TcpSrvS1` | `LINK[S1].enabled` | 监听 S1 本地端口,收发 Server 数据 | +| `TcpSrvS2` | `LINK[S2].enabled` | 监听 S2 本地端口,收发 Server 数据 | +| `TcpCliC1` | `LINK[C1].enabled` | 主动连接 C1 远端,断线重连 | +| `TcpCliC2` | `LINK[C2].enabled` | 主动连接 C2 远端,断线重连 | + +这种延迟创建能避免 TCP 任务在 netif 尚未就绪时进入 `netconn_*` 路径。 + +## 6. 配置模型 + +配置结构定义在 `App/config.h` 的 `device_config_t`。 + +### 6.1 端点编码 + +| 端点 | 代码宏 | 编码 | +|------|--------|------| +| `C1` | `ENDPOINT_C1` | `0x01` | +| `C2` | `ENDPOINT_C2` | `0x02` | +| `UART2` / `U0` | `ENDPOINT_UART2` | `0x04` | +| `UART3` / `U1` | `ENDPOINT_UART3` | `0x08` | +| `S1` | `ENDPOINT_S1` | `0x10` | +| `S2` | `ENDPOINT_S2` | `0x20` | + +`SRCID` 是单一源端点;`DSTMASK` 是目标端点位图。`DSTMASK=0x00` 专用于系统控制帧,最终进入 AT 解析路径。 + +### 6.2 LINK 模型 + +4 条链路固定为: + +| 角色 | 内部索引 | 默认作用 | +|------|----------|----------| +| `S1` | `CONFIG_LINK_S1` | TCP Server 1 | +| `S2` | `CONFIG_LINK_S2` | TCP Server 2 | +| `C1` | `CONFIG_LINK_C1` | TCP Client 1 | +| `C2` | `CONFIG_LINK_C2` | TCP Client 2 | + +每条 `LINK` 包含: + +```text +EN,LPORT,RIP,RPORT,UART +``` + +`UART` 取 `U0` 或 `U1`,分别对应 `USART2` 和 `USART3`。 + +### 6.3 默认配置 + +当前默认值由 `config_set_defaults()` 写入: + +- `MUX=0`,即普通透传模式。 +- `NET=192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00`。 +- `UART2/USART2` 和 `UART3/USART3` 默认波特率为 `115200`。 +- `reconnect_interval_ms=3000`。 + +完整 AT 命令格式以 `AT固件使用手册.md` 为准。 + +## 7. AT 配置数据流 + +AT 配置口固定使用 `USART1`。 + +```text +上位机 AT 文本 + -> USART1 DMA 接收 + -> USART1 IDLE 中断 + -> config_uart_idle_handler() + -> route_send_from_isr(xConfigQueue, ROUTE_CONN_UART1, ...) + -> ConfigTask + -> config_process_at_cmd() + -> config_respond_to_uart() + -> USART1 DMA 发送响应 +``` + +`config_process_at_cmd()` 当前支持: + +- `AT` +- `AT+?` / `AT+QUERY` +- `AT+SAVE` +- `AT+RESET` +- `AT+DEFAULT` +- `AT+MUX?` / `AT+MUX=0|1` +- `AT+NET?` / `AT+NET=IP,MASK,GW,MAC` +- `AT+LINK?` / `AT+LINK=ROLE,...` / `AT+LINK=ROLE` +- `AT+BAUD?` / `AT+BAUD=U0|U1,baudrate` + +修改网络、链路、MUX 或波特率后,代码会返回 `AT_NEED_REBOOT`,`ConfigTask` 会追加提示:`Use AT+SAVE then AT+RESET to apply changes`。 + +## 8. 业务数据流示例:无协议透传 + +本节用一个最常见场景说明数据如何流动。 + +### 8.1 场景配置 + +假设现场需要“电脑 TCP 客户端连到设备,数据直接从 `USART2` 输出;`USART2` 收到的数据再原样回到 TCP 客户端”。可以配置: + +```text +AT+MUX=0 +AT+LINK=S1,1,8080,0.0.0.0,0,U0 +AT+SAVE +AT+RESET +``` + +含义: + +- 普通透传模式,不使用 MUX 帧。 +- 启用 `S1` TCP Server。 +- `S1` 监听本地 `8080` 端口。 +- `S1` 绑定 `U0`,也就是 `USART2`。 + +### 8.2 TCP 到 UART + +当远端 TCP 客户端连接 `S1:8080` 并发送字节,例如 `01 02 03`: + +```text +远端 TCP 客户端 + -> CH390D 收包 + -> ethernetif / lwIP + -> TcpSrvTask_S1 + -> tcp_server_worker() + -> netconn_recv() + -> route_send(xTcpRxQueue, src=S1, dst=UART2, data=01 02 03) + -> UartRxTask + -> uart_trans_send_buffer(UART_CHANNEL_U0, data) + -> USART2 DMA TX + -> 外部串口设备收到 01 02 03 +``` + +关键代码位置: + +- `App/tcp_server.c`:`tcp_server_worker()` 从 `netconn_recv()` 取 `netbuf`,再投递到 `xTcpRxQueue`。 +- `App/uart_trans.c`:`UartRxTask()` 从 `xTcpRxQueue` 取消息,按 `dst_mask` 发送到 `USART2/USART3`。 + +### 8.3 UART 到 TCP + +当外部串口设备向 `USART2` 发送 `A1 B2 C3`: + +```text +外部串口设备 + -> USART2 DMA RX + -> USART2 IDLE 中断 + -> uart_trans_notify_rx_from_isr(UART_CHANNEL_U0) + -> UartRxTask + -> uart_route_raw_channel(U0) + -> 查找所有 enabled 且绑定 U0 的 LINK + -> route_send(xLinkTxQueues[S1], src=UART2, dst=S1, data=A1 B2 C3) + -> TcpSrvTask_S1 + -> xQueueReceive(xLinkTxQueues[S1]) + -> netconn_write() + -> lwIP / CH390D + -> 远端 TCP 客户端收到 A1 B2 C3 +``` + +普通透传模式下,`uart_route_raw_channel()` 会把一个 UART 上收到的数据复制给所有“已启用且绑定该 UART”的链路。因此如果 `S1` 和 `C2` 都绑定 `U0` 且都启用,`USART2` 的一段输入会分别投递到 `xLinkTxQueues[S1]` 和 `xLinkTxQueues[C2]`。 + +## 9. MUX 模式数据流 + +MUX 模式由 `AT+MUX=1` 开启。帧格式在 `App/uart_trans.c` 中由 `uart_mux_try_extract_frame()` 和 `uart_mux_encode_frame()` 实现: + +```text +SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL +0x7E | len high | len low | source | destinations | bytes | 0x7F +``` + +处理规则: + +1. `DSTMASK=0x00`:系统控制帧,`PAYLOAD` 作为 AT 文本进入 `xConfigQueue`。 +2. `DSTMASK` 包含 `S1/S2/C1/C2`:投递到对应 `xLinkTxQueues[]`。 +3. `DSTMASK` 包含另一个 UART:编码成新的 MUX 帧并转发到另一个数据口。 +4. TCP 到 UART 时,如果目标是 UART 且当前处于 MUX 模式,会带上源端点并编码成 MUX 帧输出。 + +MUX 模式适合多个 TCP 实例共享一个数据口,或者上位机需要明确指定数据发往哪个逻辑端点的场景。 + +## 10. 网络初始化和 CH390 路径 + +网络运行入口是 `App/task_net_poll.c` 的 `NetPollTask()`: + +1. 调用 `tcpip_init(NULL, NULL)` 创建 lwIP 内核线程。 +2. 按 `config_get()` 中的 `NET` 参数构造 `ipaddr/netmask/gateway`。 +3. 调用 `lwip_netif_init()` 初始化 netif 和 CH390 glue。 +4. 初始化成功后设置 `g_netif_ready = pdTRUE`。 +5. 调用 `app_start_network_tasks()` 创建启用的 TCP Server/Client 任务。 +6. 主循环中等待 `xNetSemaphore` 或周期轮询,驱动 `ethernetif_poll()`,并响应网络重启请求。 + +底层路径主要在: + +- `Drivers/CH390/CH390_Interface.c`:SPI、CS、寄存器和 FIFO 访问。 +- `Drivers/CH390/CH390.c`:芯片级 helper。 +- `Drivers/LwIP/src/netif/ethernetif.c`:CH390 与 lwIP netif 的桥接。 +- `Drivers/LwIP/src/include/arch/lwipopts.h`:lwIP 池、线程、core locking 配置。 + +当前关键 lwIP 配置包括: + +- `NO_SYS=0` +- `LWIP_NETCONN=1` +- `LWIP_TCPIP_CORE_LOCKING=1` +- `LWIP_TCPIP_CORE_LOCKING_INPUT=1` +- `PBUF_POOL_SIZE=8` +- `MEMP_NUM_PBUF=8` +- `MEMP_NUM_TCPIP_MSG_INPKT=8` +- `LWIP_NETCONN_SEM_PER_THREAD=1` + +## 11. 推荐阅读路线 + +### 11.1 只想理解系统怎么跑 + +1. `README.md` +2. 本文第 3、4、5、8 节 +3. `Core/Src/main.c` +4. `Core/Src/freertos.c` + +### 11.2 要改 AT 命令或默认参数 + +1. `AT固件使用手册.md` +2. `App/config.h`:结构体、默认值、端点编码。 +3. `App/config.c`:解析、保存、响应。 +4. `App/flash_param.c`:Flash 存储。 + +### 11.3 要改 TCP 或串口透传 + +1. 本文第 8、9 节。 +2. `App/route_msg.c`:先理解消息生命周期。 +3. `App/uart_trans.c`:UART RX/TX、普通透传、MUX。 +4. `App/tcp_server.c` 和 `App/tcp_client.c`:网络收发。 + +### 11.4 要查网络底层问题 + +1. `App/task_net_poll.c` +2. `Drivers/LwIP/src/netif/ethernetif.c` +3. `Drivers/CH390/CH390_Interface.c` +4. `Drivers/LwIP/src/include/arch/lwipopts.h` +5. RTT 日志和抓包结果一起看,不要只看单侧现象。 + +## 12. 调试指南 + +### 12.1 启动阶段 + +先看 RTT boot 日志: + +```text +hal-init +clock-config +peripherals-ready +config-ready +uart-trans-init +tasks-created +freertos-init +scheduler-start +``` + +如果停在 `scheduler-start` 前,优先看外设初始化、CH390 reset、RTOS 对象创建断言。如果进入任务后异常,再看 `NetPollTask`、`ConfigTask`、`UartRxTask` 的 task-entry 日志。 + +### 12.2 网络阶段 + +关键日志点: + +- `[NET] tcpip-init enter/exit` +- `[NET] netif-init enter/exit` +- `[NET] post-init ok=... hwm=... free=... min=...` +- `[NET] start-network-tasks call` +- `[NET] netif-ready` + +如果 netif 未 ready,不要先查 TCP 业务任务;应先查 CH390、SPI、netif 初始化和 lwIP 配置。 + +### 12.3 路由阶段 + +`route_send_result_to_str()` 的返回值很重要: + +| 返回 | 含义 | 常见方向 | +|------|------|----------| +| `invalid` | 参数或长度非法 | 检查 `dst_mask`、payload 长度 | +| `pool` | `route_msg` 固定池耗尽 | 检查消费者任务是否卡住、队列是否积压 | +| `queue` | 目标队列满 | 检查对应 TCP/UART 任务是否还在运行 | + +### 12.4 资源约束 + +`STM32F103RCT6` 的 SRAM 余量有限,而 FreeRTOS、lwIP、多个 TCP 任务、UART ring buffer 和消息池都会消耗 RAM。遇到 full-task 模式下的非确定性问题时,应同时记录: + +- `xPortGetFreeHeapSize()` +- `xPortGetMinimumEverFreeHeapSize()` +- `uxTaskGetStackHighWaterMark()` +- lwIP pbuf/memp 池配置 +- 哪些 `LINK` 被启用 + +如果问题随任务数量、池大小或启用链路数量明显移动,先按资源问题分析,不要急着给业务路径加补丁。 + +### 12.5 历史 CH390/lwIP pbuf 泄漏教训 + +历史上曾出现“设备能成功 ping 固定次数,随后不再回应”的问题。现象随 `PBUF_POOL_SIZE` 从 8 改到 16 而从 8 次移动到 16 次,最终定位到 lwIP 输出路径中 pbuf 引用计数未释放这一类问题。 + +这个案例的长期价值不是记住某个临时修改,而是调试方法: + +1. 如果失败次数精确跟池大小相关,优先怀疑引用计数或释放路径。 +2. 扩大池只能延迟问题,不能当根修复。 +3. 抓包、RTT、lwIP 统计和代码引用计数要一起看。 + +## 13. 修改代码时的边界 + +1. 不要绕过 `route_msg` 和队列直接让 TCP/UART 任务互相调用。 +2. ISR 中只做通知或投递,不做阻塞等待。 +3. `netconn_*` 只在网络任务语境中使用,注意每个线程的 `netconn_thread_init()` / `netconn_thread_cleanup()`。 +4. 改 AT 命令时同步更新 `AT固件使用手册.md`。 +5. 改 MUX 帧格式或端点编码时,同步检查 `App/config.h`、`App/uart_trans.c`、`AT固件使用手册.md`。 +6. 改 lwIP 池大小时,同步记录 RAM、heap、水位和实际业务链路数量。 + +## 14. 术语速查 + +| 术语 | 含义 | +|------|------| +| `U0` | `USART2` 数据口 | +| `U1` | `USART3` 数据口 | +| `S1/S2` | TCP Server 实例 | +| `C1/C2` | TCP Client 实例 | +| `MUX=0` | 普通透明透传 | +| `MUX=1` | 带 `SRCID/DSTMASK` 的帧化透传 | +| `xTcpRxQueue` | TCP 到 UART 的队列 | +| `xLinkTxQueues[]` | UART 到 TCP 的队列数组 | +| `xConfigQueue` | AT 命令队列 | +| `NetPollTask` | 网络初始化、轮询和恢复任务 | diff --git a/项目技术实现.md b/项目技术实现.md deleted file mode 100644 index 3487d4d..0000000 --- a/项目技术实现.md +++ /dev/null @@ -1,646 +0,0 @@ -# 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, ðernetif_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 任务 | diff --git a/项目需求说明.md b/项目需求说明.md deleted file mode 100644 index a094c8e..0000000 --- a/项目需求说明.md +++ /dev/null @@ -1,191 +0,0 @@ -# TCP2UART 项目需求说明 - -## 一、项目目标 - -本项目基于 `STM32F103RCT6` 与 `CH390D` 实现一台多实例 TCP 与双串口数据透传设备。 - -最终对外协议模型固定为: - -1. `MUX`:控制串口侧是否采用 MUX 承载 -2. `NET`:全局静态网络配置 -3. `LINK[idx]`:按实例索引组织的链路配置 - -系统必须支持: - -- `2` 路 TCP Server 实例 -- `2` 路 TCP Client 实例 -- `UART1` 作为 AT 配置口 -- `UART2 / UART3` 作为业务数据口 - -## 二、硬件与软件边界 - -### 2.1 硬件边界 - -- 主控:`STM32F103RCT6`(256KB Flash / 48KB SRAM) -- 备选主控:`STM32F103RDT6`(384KB Flash / 64KB SRAM),pin-to-pin 兼容,当 RAM 不够时直接替换 -- 以太网芯片:`CH390D` -- 网卡数量:`1` -- 配置口:`UART1` -- 数据口:`UART2`、`UART3` - -### 2.2 软件边界 - -- 执行模型:`FreeRTOS` -- 网络协议栈:`lwIP NO_SYS=0 + netconn API`(线程安全,每连接独立任务) -- 调试输出:`SEGGER RTT` -- 采用 `FreeRTOS` 任务调度 -- TCP 连接使用 `netconn` 阻塞 API(`netconn_accept` / `netconn_recv` / `netconn_write`) -- 每条 TCP 连路(S1/S2/C1/C2)独立一个任务 -- 不包含 DHCP 协议支持 - -## 三、最终协议需求 - -### 3.1 MUX 帧格式 - -所有 MUX 数据承载必须使用如下格式: - -```text -SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL -``` - -要求: - -- `DSTMASK != 0x00`:业务数据帧 -- `DSTMASK = 0x00`:系统控制帧 -- 系统控制帧承载 AT 文本命令 -- AT 文本命令必须以 `\r\n` 结尾 - -### 3.2 统一端点编码 - -系统必须使用统一端点编码,同时覆盖 UART 与 TCP 逻辑实例: - -| 端点 | 编码 | -|------|------| -| `C1` | `0x01` | -| `C2` | `0x02` | -| `UART2` | `0x04` | -| `UART3` | `0x08` | -| `S1` | `0x10` | -| `S2` | `0x20` | - -要求: - -- `SRCID` 为单值 -- `DSTMASK` 为位图 -- `DSTMASK=0x00` 仅保留给系统控制帧 - -## 四、AT 接口需求 - -### 4.1 命令分类 - -AT 协议必须收敛为以下三类命令: - -1. `AT+MUX` -2. `AT+NET` -3. `AT+LINK` - -不再保留历史展开式实例字段命令。 - -### 4.2 MUX 命令需求 - -- `AT+MUX=0/1`:设置全局 MUX 模式 -- `AT+MUX?`:查询当前 MUX 模式 - -### 4.3 NET 命令需求 - -`NET` 必须统一表达以下静态网络参数: - -```text -IP,MASK,GW,MAC -``` - -说明: - -- 设备只有一张网卡,因此本地 IP 不按实例拆分 -- DHCP 不属于协议需求范围 - -### 4.4 LINK 命令需求 - -`LINK[idx]` 必须统一表达如下字段: - -```text -EN,LPORT,RIP,RPORT,UART -``` - -要求: - -- `idx` 固定映射四个实例:`0=S1`、`1=S2`、`2=C1`、`3=C2` -- `Server` 与 `Client` 共用同一条 `LINK` 配置模型 -- `LPORT` 必须可配置 -- `RIP / RPORT` 必须可配置 -- `UART` 必须可配置 - -## 五、功能需求 - -### 5.1 TCP 功能 - -- 支持 `2` 路 Server -- 支持 `2` 路 Client -- 每个实例通过 `LINK[idx]` 配置其本地端口、对端地址、对端端口和串口路由 - -### 5.2 串口透传功能 - -- `UART2 / UART3` 支持普通透传模式与 MUX 透传模式 -- 当需要多实例共享数据口时,必须启用 MUX 模式 -- 业务数据流向由 `SRCID / DSTMASK` 决定 - -### 5.3 系统控制功能 - -- 系统控制帧由 `DSTMASK=0x00` 表示 -- 系统控制帧进入 AT 解析路径 -- 控制文本必须以 `\r\n` 结束 - -### 5.4 参数保存功能 - -- 参数修改后支持 `SAVE` -- 支持 `RESET` 后按保存配置启动 -- 支持恢复默认配置 - -## 六、FreeRTOS 任务架构需求 - -### 6.1 任务划分 - -系统至少应包含以下 FreeRTOS 任务: - -| 任务 | 优先级 | 职责 | -|------|--------|------| -| tcpip_thread | 6 (最高) | lwIP 内核线程(自动创建) | -| NetPollTask | 5 | CH390 事件轮询 + 链路检测 | -| TcpSrvTask_S1 | 4 | S1 netconn_accept + 收发 | -| TcpSrvTask_S2 | 4 | S2 netconn_accept + 收发 | -| TcpCliTask_C1 | 4 | C1 netconn_connect + 收发 | -| TcpCliTask_C2 | 4 | C2 netconn_connect + 收发 | -| UartRxTask | 4 | UART DMA/IDLE 接收 + MUX 帧提取 + 路由 | -| ConfigTask | 2 | AT 命令解析与响应 | -| DefaultTask | 1 | LED 心跳 + 看门狗 | - -### 6.2 任务间通信 - -- 使用 `Queue` 传递指针 + 元数据描述符(零拷贝路由消息) -- 使用 `Binary Semaphore` 同步 CH390 中断事件 -- 使用 `TaskNotification` 通知 UART IDLE 事件 -- 预分配静态缓冲池,避免动态分配 - -## 七、非功能需求 - -1. 满足 `STM32F103RCT6` 的 `256KB Flash / 48KB SRAM` 约束,若 RAM 不足可切换 `STM32F103RDT6`(pin-to-pin,64KB SRAM) -2. 工程可在 `MDK-ARM` 下构建 -3. 调试输出统一使用 `SEGGER RTT` -4. 不引入 DHCP、DNS、UDP 等当前非目标协议 -5. FreeRTOS 堆使用 `heap_4.c`,总堆大小建议 `10KB` -6. 所有任务栈通过 `uxTaskGetStackHighWaterMark` 监控 - -## 八、验收口径 - -验收时以下几点必须同时成立: - -1. 文档只使用 `MUX / NET / LINK` 作为最终协议模型 -2. 文档不再出现历史 `S1... / C1...` 外部字段 -3. 串口控制文本统一规定为 `\r\n` 结束 -4. MUX 帧格式与端点编码在需求、手册、技术实现三份文档中表述一致 -5. FreeRTOS 任务无死锁、无栈溢出、无优先级反转