# 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 关闭路径
