Compare commits
4 Commits
db714471b8
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 51b174d330 | |||
| 58361589d8 | |||
| 0681b8bbe4 | |||
| 3cb49fc4f8 |
+23
-13
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## 1. 文档范围
|
## 1. 文档范围
|
||||||
|
|
||||||
本文档定义 `TCP2UART` 项目的最终 AT 外部协议。
|
本文档定义 `TCP2UART` 项目的 AT 外部协议。
|
||||||
|
|
||||||
本文档只描述最终协议模型,不保留任何历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。
|
本文档只描述当前代码实现支持的外部命令,不保留历史展开式实例字段,不包含测试记录,不讨论旧版兼容命令。项目结构、任务模型和数据流请阅读 `README.md` 与 `项目代码阅读指南.md`。
|
||||||
|
|
||||||
适用对象:
|
适用对象:
|
||||||
|
|
||||||
@@ -19,6 +19,13 @@
|
|||||||
- 配置口:`USART1`
|
- 配置口:`USART1`
|
||||||
- 数据口:`USART2`、`USART3`
|
- 数据口:`USART2`、`USART3`
|
||||||
|
|
||||||
|
### 2.1 固件版本线
|
||||||
|
|
||||||
|
- FreeRTOS + lwIP 版本线从 `V2.0.0` 开始。
|
||||||
|
- 裸机版本线从 `V1.0.0` 开始。
|
||||||
|
- 当前 FreeRTOS 固件基线 release:`TCP2UART RTOS V2.0.0`。
|
||||||
|
- 固件下载:`https://git.furtherverse.com/gaoro-xiao/TCP2UART/releases/tag/V2.0.0`
|
||||||
|
|
||||||
职责划分:
|
职责划分:
|
||||||
|
|
||||||
- `USART1`:AT 配置口
|
- `USART1`:AT 配置口
|
||||||
@@ -49,12 +56,12 @@ SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
|||||||
|
|
||||||
字段定义:
|
字段定义:
|
||||||
|
|
||||||
- `SYNC`:帧起始标记,建议固定为 `0x7E`
|
- `SYNC`:帧起始标记,固定为 `0x7E`
|
||||||
- `LEN_H / LEN_L`:`PAYLOAD` 长度,高字节在前
|
- `LEN_H / LEN_L`:`PAYLOAD` 长度,高字节在前
|
||||||
- `SRCID`:单字节源端点 ID
|
- `SRCID`:单字节源端点 ID
|
||||||
- `DSTMASK`:单字节目标端点位图
|
- `DSTMASK`:单字节目标端点位图
|
||||||
- `PAYLOAD`:负载数据
|
- `PAYLOAD`:负载数据
|
||||||
- `TAIL`:帧结束标记,建议固定为 `0x7F`
|
- `TAIL`:帧结束标记,固定为 `0x7F`
|
||||||
|
|
||||||
规则:
|
规则:
|
||||||
|
|
||||||
@@ -92,7 +99,7 @@ SYNC | LEN_H | LEN_L | SRCID | DSTMASK | PAYLOAD | TAIL
|
|||||||
```text
|
```text
|
||||||
AT\r\n
|
AT\r\n
|
||||||
AT+MUX?\r\n
|
AT+MUX?\r\n
|
||||||
AT+NET=192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
|
AT+NET=192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00\r\n
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6.2 持久化规则
|
### 6.2 持久化规则
|
||||||
@@ -121,9 +128,11 @@ MUX = 0
|
|||||||
### 7.2 NET 默认值
|
### 7.2 NET 默认值
|
||||||
|
|
||||||
```text
|
```text
|
||||||
NET = 192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01
|
NET = 192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00
|
||||||
```
|
```
|
||||||
|
|
||||||
|
默认 MAC 为全 0,表示 Flash 中不固化板卡 MAC;运行时使用 `CH390D` 内部 MAC。`AT+?` 与 `AT+NET?` 回显的是当前生效 MAC。
|
||||||
|
|
||||||
### 7.3 LINK 默认值
|
### 7.3 LINK 默认值
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -171,7 +180,7 @@ AT+QUERY\r\n
|
|||||||
推荐返回格式:
|
推荐返回格式:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+NET:IP=192.168.1.100,MASK=255.255.255.0,GW=192.168.1.1,MAC=02:00:00:00:00:01
|
+NET:IP=192.168.31.100,MASK=255.255.255.0,GW=192.168.31.1,MAC=<当前生效MAC>
|
||||||
+LINK:S1,EN=1,LPORT=8080,RIP=0.0.0.0,RPORT=0,UART=U0
|
+LINK:S1,EN=1,LPORT=8080,RIP=0.0.0.0,RPORT=0,UART=U0
|
||||||
+LINK:S2,EN=0,LPORT=8081,RIP=0.0.0.0,RPORT=0,UART=U1
|
+LINK:S2,EN=0,LPORT=8081,RIP=0.0.0.0,RPORT=0,UART=U1
|
||||||
+LINK:C1,EN=1,LPORT=9001,RIP=192.168.1.200,RPORT=9000,UART=U1
|
+LINK:C1,EN=1,LPORT=9001,RIP=192.168.1.200,RPORT=9000,UART=U1
|
||||||
@@ -213,7 +222,7 @@ OK
|
|||||||
#### 设置 NET
|
#### 设置 NET
|
||||||
|
|
||||||
```text
|
```text
|
||||||
AT+NET=192.168.1.100,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
|
AT+NET=192.168.31.100,255.255.255.0,192.168.31.1,00:00:00:00:00:00\r\n
|
||||||
```
|
```
|
||||||
|
|
||||||
字段顺序:
|
字段顺序:
|
||||||
@@ -231,13 +240,13 @@ AT+NET?\r\n
|
|||||||
返回示例:
|
返回示例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
+NET:IP=192.168.1.100,MASK=255.255.255.0,GW=192.168.1.1,MAC=02:00:00:00:00:01
|
+NET:IP=192.168.31.100,MASK=255.255.255.0,GW=192.168.31.1,MAC=<当前生效MAC>
|
||||||
OK
|
OK
|
||||||
```
|
```
|
||||||
|
|
||||||
**MAC 设置说明:**
|
**MAC 设置说明:**
|
||||||
|
|
||||||
当MAC设置为全0时,固件将使用硬件MAC地址,此时通过AT+?查询到的MAC地址即为当前生效的硬件MAC地址。
|
当 MAC 设置为全 0 时,固件将使用 `CH390D` 内部 MAC 地址。此时 Flash 内仍保存全 0,不会把内部 MAC 写回 Flash;`AT+?` 和 `AT+NET?` 查询到的 MAC 地址为当前运行时生效的硬件 MAC 地址。
|
||||||
|
|
||||||
### 8.5 LINK 类命令
|
### 8.5 LINK 类命令
|
||||||
|
|
||||||
@@ -357,7 +366,7 @@ OK: Defaults restored
|
|||||||
## 11. 推荐配置流程
|
## 11. 推荐配置流程
|
||||||
|
|
||||||
```text
|
```text
|
||||||
AT+NET=192.168.1.123,255.255.255.0,192.168.1.1,02:00:00:00:00:01\r\n
|
AT+NET=192.168.31.123,255.255.255.0,192.168.31.1,00:00:00:00:00:00\r\n
|
||||||
AT+LINK=S1,1,10001,0.0.0.0,0,U1\r\n
|
AT+LINK=S1,1,10001,0.0.0.0,0,U1\r\n
|
||||||
AT+LINK=S2,1,10003,0.0.0.0,0,U1\r\n
|
AT+LINK=S2,1,10003,0.0.0.0,0,U1\r\n
|
||||||
AT+LINK=C1,1,20001,192.168.1.201,10002,U0\r\n
|
AT+LINK=C1,1,20001,192.168.1.201,10002,U0\r\n
|
||||||
@@ -385,9 +394,10 @@ AT+RESET\r\n
|
|||||||
1. `AT+SAVE\r\n`
|
1. `AT+SAVE\r\n`
|
||||||
2. `AT+RESET\r\n`
|
2. `AT+RESET\r\n`
|
||||||
|
|
||||||
## 13. 相关文件
|
## 13. 相关文档与文件
|
||||||
|
|
||||||
|
- 项目入口:`README.md`
|
||||||
|
- 代码结构和数据流:`项目代码阅读指南.md`
|
||||||
- AT 命令实现:`App/config.c`
|
- AT 命令实现:`App/config.c`
|
||||||
- 配置结构与默认值:`App/config.h`
|
- 配置结构与默认值:`App/config.h`
|
||||||
- FreeRTOS 任务定义:`Core/Src/freertos.c`
|
- FreeRTOS 任务定义:`Core/Src/freertos.c`
|
||||||
- 调试指导:`工程调试指南.md`
|
|
||||||
|
|||||||
+2
-1
@@ -15,6 +15,7 @@
|
|||||||
#include "route_msg.h"
|
#include "route_msg.h"
|
||||||
|
|
||||||
#define TCP_CLIENT_CONNECT_TIMEOUT_MS 500
|
#define TCP_CLIENT_CONNECT_TIMEOUT_MS 500
|
||||||
|
#define TCP_CLIENT_RECONNECT_INTERVAL_MS 3000u
|
||||||
#define TCP_CLIENT_STOP_POLL_MS 50u
|
#define TCP_CLIENT_STOP_POLL_MS 50u
|
||||||
|
|
||||||
static BaseType_t tcp_client_stop_requested(void)
|
static BaseType_t tcp_client_stop_requested(void)
|
||||||
@@ -162,7 +163,7 @@ static void tcp_client_task(uint8_t link_index)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
delay_ms = (cfg->reconnect_interval_ms == 0u) ? 3000u : cfg->reconnect_interval_ms;
|
delay_ms = TCP_CLIENT_RECONNECT_INTERVAL_MS;
|
||||||
|
|
||||||
if (first_connect_deferred != 0u) {
|
if (first_connect_deferred != 0u) {
|
||||||
first_connect_deferred = 0u;
|
first_connect_deferred = 0u;
|
||||||
|
|||||||
@@ -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`
|
|
||||||
@@ -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 后,再继续做最小化的下一步判别。
|
|
||||||
```
|
|
||||||
+8
-2
@@ -76,9 +76,15 @@ void MX_GPIO_Init(void)
|
|||||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||||
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
||||||
|
|
||||||
/* EXTI interrupt init*/
|
/* EXTI interrupt init
|
||||||
|
* Keep CH390 INT masked during early boot. PB0 may already be asserted at
|
||||||
|
* power-on, while the FreeRTOS semaphore is not created until
|
||||||
|
* MX_FREERTOS_Init(). The network driver enables EXTI0 after CH390 and the
|
||||||
|
* RTOS objects are ready.
|
||||||
|
*/
|
||||||
|
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
|
||||||
|
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
|
||||||
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
|
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
|
||||||
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "stm32f1xx_it.h"
|
#include "stm32f1xx_it.h"
|
||||||
|
|
||||||
#include "FreeRTOS.h"
|
#include "FreeRTOS.h"
|
||||||
|
#include "semphr.h"
|
||||||
#include "task.h"
|
#include "task.h"
|
||||||
|
|
||||||
#include "app_runtime.h"
|
#include "app_runtime.h"
|
||||||
@@ -101,10 +102,13 @@ void EXTI0_IRQHandler(void)
|
|||||||
|
|
||||||
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
|
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
|
||||||
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
|
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
|
||||||
|
if ((xNetSemaphore != NULL) &&
|
||||||
|
(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)) {
|
||||||
xSemaphoreGiveFromISR(xNetSemaphore, &xHigherPriorityTaskWoken);
|
xSemaphoreGiveFromISR(xNetSemaphore, &xHigherPriorityTaskWoken);
|
||||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SPI1_IRQHandler(void)
|
void SPI1_IRQHandler(void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -188,6 +188,8 @@ void ch390_interrupt_init(void)
|
|||||||
/* EXTI0 is configured in CubeMX for PB0 */
|
/* EXTI0 is configured in CubeMX for PB0 */
|
||||||
/* NVIC priority should be >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY */
|
/* NVIC priority should be >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY */
|
||||||
/* for FreeRTOS compatibility */
|
/* for FreeRTOS compatibility */
|
||||||
|
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
|
||||||
|
__HAL_GPIO_EXTI_CLEAR_IT(CH390_INT_PIN);
|
||||||
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
|
HAL_NVIC_SetPriority(EXTI0_IRQn, 6, 0);
|
||||||
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`。
|
||||||
@@ -1,208 +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. 当前构建真值应查看:
|
|
||||||
- `MDK-ARM/build_capture.txt`
|
|
||||||
- `MDK-ARM/TCP2UART/TCP2UART.build_log.htm`
|
|
||||||
- `MDK-ARM/TCP2UART/TCP2UART.map`
|
|
||||||
4. 最近一次 Keil 构建示例:
|
|
||||||
- `Code=84560`
|
|
||||||
- `RW-data=432`
|
|
||||||
- `ZI-data=47056`
|
|
||||||
- `0 Error(s), 0 Warning(s)`
|
|
||||||
|
|
||||||
### 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 后,再继续做最小化的下一步判别。
|
|
||||||
```
|
|
||||||
@@ -1,469 +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`
|
|
||||||
|
|
||||||
### 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 关闭路径
|
|
||||||
+449
@@ -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` | 网络初始化、轮询和恢复任务 |
|
||||||
@@ -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 任务 |
|
|
||||||
@@ -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 任务无死锁、无栈溢出、无优先级反转
|
|
||||||
Reference in New Issue
Block a user