# TCP2UART 调试指导

## 1. 适用范围

本指导面向当前 `TCP2UART` 工程，覆盖以下四类调试场景：

1. `STM32F103R8T6 + CH390D` 的基础 bring-up
2. `SEGGER RTT`、异常陷阱与主循环运行状态确认
3. `USART1` 配置口、`USART2/USART3` 数据口与 `MUX / NET / LINK[idx]` 协议联调
4. `TCP Server / TCP Client / UART` 三层数据通路联调与问题隔离

本指导默认基线如下：

1. 当前工程采用裸机主循环架构，未使用 FreeRTOS 参与主业务调度
2. `CH390` 运行时访问统一由 `ch390_runtime` 持有
3. 调试输出统一使用 `SEGGER RTT`
4. 当前应用层协议模型已经收敛到 `MUX / NET / LINK[idx]`
5. 当前代码应以 `MDK-ARM` 工程构建结果为准，而不是 `CMake + MSVC` 结果

---

## 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. 当前 `CH390D` 的历史“全 `0xFF` / 全 `0x0000`”结论不应再直接沿用。
4. 已有结论表明：
   - MCU 启动、RTT、主循环、TIM4 心跳路径可工作
   - `CH390D` 基础寄存器读写与 `lwIP netif` 基本链路已经打通过一次
   - 真实硬件侧曾定位到 `CH390D` 供电滤波电容虚焊问题
5. 因此，当前调试重点不再是“CH390 是否完全不通”，而是：
   - 启动阶段是否稳定
   - `MUX / NET / LINK[idx]` 协议是否与代码一致
   - UART / TCP / CH390 三层通路是否协同稳定
   - 参数保存、复位和恢复流程是否可靠

---

## 3. 代码入口与调试责任边界

### 3.1 启动与主循环入口

以下代码路径是 bring-up 的第一现场：

1. `Core/Src/main.c`
   - `main()`：总启动入口
   - `SystemClock_Config()`：时钟初始化
   - `App_Init()`：应用层初始化
   - `App_Poll()`：主循环核心路径
   - `BootDiag_ReportCh390()`：启动阶段 CH390 诊断输出
2. `Core/Src/stm32f1xx_it.c`
   - 故障与中断入口
   - `USART1/2/3`、`EXTI0`、DMA 回调等联调关键入口

### 3.2 CH390 责任边界

当前 CH390 调试必须遵守以下责任边界：

1. `Drivers/CH390/CH390_Interface.c`
   - 只负责 GPIO / SPI / 寄存器与内存事务
2. `Drivers/CH390/CH390.c`
   - 只负责芯片级 helper，例如默认配置、PHY、MAC 读写
3. `Drivers/CH390/ch390_runtime.c`
   - 唯一的运行时拥有者
   - 负责初始化、链路检查、IRQ 消费、RX/TX 服务与诊断快照
4. `Drivers/LwIP/src/netif/ethernetif.c`
   - 只承担 netif glue 与轮询桥接，不应重新下沉复杂 CH390 运行时事务
5. `Core/Src/main.c`
   - 启动后只通过 runtime 对外暴露的诊断与轮询接口工作

调试时不要把原始 CH390 寄存器访问重新散回 `main.c`、中断或多个业务层。

### 3.3 配置口与业务口边界

1. `USART1`
   - 配置口
   - 负责接收 `AT` 命令
   - 当前接收逻辑在：
     - `Core/Src/main.c` 的 `App_PollUart1ConfigRx()`
     - `Core/Src/stm32f1xx_it.c` 的 `HAL_UART_RxCpltCallback()`
     - `App/config.c` 的 `config_uart_rx_byte()` / `config_process_at_cmd()`
2. `USART2 / USART3`
   - 数据口
   - 负责普通透传或 MUX 承载
   - 当前入口在 `App/uart_trans.c`

---

## 4. 当前硬件与调试工具基线

### 4.1 核心硬件对象

1. MCU：`STM32F103R8T6`
2. 以太网芯片：`CH390D`
3. 配置串口：`USART1`
4. 数据串口：`USART2 / USART3`
5. 调试输出：`SEGGER RTT`

### 4.2 构建与下载基线

当前建议优先使用以下工程与产物：

1. `MDK-ARM/TCP2UART.uvprojx`
2. `MDK-ARM/TCP2UART/TCP2UART.axf`
3. `MDK-ARM/TCP2UART/TCP2UART.hex`
4. `MDK-ARM/TCP2UART/TCP2UART.map`
5. `MDK-ARM/TCP2UART/TCP2UART.build_log.htm`
6. `build_keil.log`

说明：

1. 当前 `CMake configure` 可以完成，但 `CMake + MSVC` 不适合作为 STM32/CMSIS 的最终构建验收依据。
2. 若需要验证“当前代码是否真实可编译”，优先看 `MDK-ARM` 构建产物与日志。

### 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. 启动阶段调试顺序

建议按 P0 ~ P5 顺序推进，不要跳层。

### 5.1 P0：确认最小基础条件

每次现场调试前，先确认：

1. `MDK-ARM` 可构建并产出新的 `axf/hex/map`
2. 板卡可正常下载与复位
3. RTT 可连接并看到启动输出
4. LED 心跳可工作
5. `App_Poll()` 已经进入稳定轮询

这一层若失败，不要进入网络或协议调试。

### 5.2 P1：确认启动日志与 trap 状态

上电或复位后，优先看 RTT 输出中是否出现：

1. `TCP2UART boot`
2. 若 HSE 启动失败，则会出现：
   - `WARN: HSE start failed, fallback to HSI PLL`
3. `BootDiag_ReportCh390()` 输出的 CH390 诊断与网络配置快照

若发生异常，优先观察是否打印：

1. `TRAP: Error_Handler`
2. `TRAP: HardFault_Handler`
3. `TRAP: MemManage_Handler`
4. `TRAP: BusFault_Handler`
5. `TRAP: UsageFault_Handler`

当前 trap 统一收敛到：

1. `Core/Src/main.c` 的 `Debug_TrapWithRttHint()`
2. 它会打印 RTT、执行 `__BKPT(0)` 并停住

因此，若 RTT 中出现 `TRAP:`，应立即接调试器看断点现场，而不是继续盲猜高层逻辑。

### 5.3 P2：确认 CH390 初始化链路

启动阶段应重点关注 `Drivers/CH390/ch390_runtime.c` 中初始化阶段日志，理想情况下应能依次看到：

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: getmac`
8. `ETH init: irq`
9. `ETH init: done`

此阶段重点判定：

1. `VID / PID / REV` 是否可信
2. PHY 寄存器是否稳定可读
3. MAC 写入与回读是否一致
4. `link_up` 是否与真实网线状态一致

若这一层失败，优先做硬件侧量测，而不是先改业务层：

1. `RSTB`
2. `CS`
3. `SCK`
4. `MOSI`
5. `MISO`
6. `INT`
7. `VDDK`
8. `AVDD33 / VDDIO / AVDD33`
9. `XI / XO`

---

## 6. USART1 配置口调试

### 6.1 当前命令面

根据当前代码与手册，配置口应围绕以下命令验证：

1. `AT`
2. `AT+?`
3. `AT+QUERY`
4. `AT+MUX=...`
5. `AT+MUX?`
6. `AT+NET=...`
7. `AT+NET?`
8. `AT+BAUD=...`
9. `AT+BAUD?`
10. `AT+LINK=...`
11. `AT+LINK?`
12. `AT+SAVE`
13. `AT+RESET`
14. `AT+DEFAULT`

其中与数据串口相关的固定映射为：

1. `U0 = USART2`
2. `U1 = USART3`
3. `AT+BAUD=U0,<baud>` / `AT+BAUD=U1,<baud>` 只更新当前运行配置记录
4. 新波特率不会立即重初始化 `USART2/USART3`，必须执行 `AT+SAVE` + `AT+RESET` 后按保存值生效
5. 当前代码接受的波特率范围为 `1200 ~ 921600`

### 6.2 现场关键规则

根据已有联调记录，配置口最关键的 bench 规则是：

1. 当前现场验证时，配置命令必须保证以换行完成帧。
2. 若主机侧发送方式不对，现象会很像“配置口完全无响应”。
3. 因此，配置口不响应时，第一优先级不是改 parser，而是先验证主机端发送格式与接线。
4. `BAUD` 类命令若查询值已变化，但 `USART2/USART3` 现场波特率尚未变化，不应立即归因为命令无效，应先确认是否已经执行 `AT+SAVE` 与 `AT+RESET`。

### 6.3 最小验证步骤

建议按以下顺序验证：

1. 连接 `USART1`
2. 先发 `AT`
3. 再发 `AT+QUERY`
4. 再发 `AT+NET?`
5. 再发 `AT+BAUD?`
6. 再发 `AT+LINK?`
7. 修改一个最小参数，例如：
    - `AT+MUX=1`
    - 或 `AT+BAUD=U1,38400`
8. 执行：
    - `AT+SAVE`
    - `AT+RESET`
9. 复位后再次查询，确认配置是否保留
10. 若本轮验证的是 `AT+BAUD`，还应同步用上位机重新按新波特率连接 `USART2/USART3`，确认数据口实际生效

### 6.4 持久化失败时怎么查

优先检查以下路径：

1. `App/config.c`
   - `config_save()`
   - `config_load()`
   - `config_set_defaults()`
2. `App/flash_param.c`
   - Flash 解锁
   - 页擦除
   - 半字编程
   - 写后校验
3. 参数页地址：
   - `0x0800FC00`

不要在还没证明 `AT+SAVE` 已真正被接受之前，就直接把 `Flash 全 FFFFFFFF` 归因到 Flash 驱动错误。

---

## 7. MUX / NET / LINK[idx] 联调指导

### 7.1 协议总则

当前协议必须按以下模型理解：

1. `MUX`：全局数据承载模式开关
2. `NET`：IP / Mask / GW / MAC
3. `LINK[idx]`：链路配置项

固定链路索引映射为：

1. `LINK[0] = S1`
2. `LINK[1] = S2`
3. `LINK[2] = C1`
4. `LINK[3] = C2`

固定端点编码为：

1. `C1 = 0x01`
2. `C2 = 0x02`
3. `UART2 = 0x04`
4. `UART3 = 0x08`
5. `S1 = 0x10`
6. `S2 = 0x20`

### 7.2 MUX 数据口规则

当 `MUX=1` 时，数据口应使用 MUX 帧。

重点规则：

1. `DSTMASK=0x00` 表示系统控制帧
2. 控制帧中的 AT 文本必须严格按手册要求结束
3. 普通数据帧走业务转发路径，不应进入配置解析器

### 7.3 调试时重点检查什么

若怀疑 `MUX` 模式不工作，优先检查：

1. `App/uart_trans.c`
   - `uart_mux_try_extract_frame()`
   - `uart_mux_encode_frame()`
2. `Core/Src/main.c`
   - `App_RouteMuxUartTraffic()`
   - `App_RouteRawUartTraffic()`
   - `App_RouteTcpTraffic()`
3. `App/config.c`
   - `config_build_response_frame()`
   - `config_process_at_cmd()`

推荐最小 MUX 联调顺序：

1. 先在 `MUX=0` 下跑通原始透传
2. 再切换 `MUX=1`
3. 先发一个控制帧，确认 `DSTMASK=0x00` 路径可通
4. 再发一个单目标数据帧，例如只打到 `S1`
5. 最后验证多目标位图转发

---

## 8. TCP / UART / CH390 联调顺序

### 8.1 先做链路，再做业务

在 `CH390` 初始化、链路和 IRQ 未被证明稳定前，不要先调高层 TCP/UART 业务。

### 8.2 推荐顺序

建议按以下顺序推进：

1. RTT 启动与 trap 状态正常
2. CH390 启动日志完整
3. 链路检测可信
4. `TCP server` / `TCP client` 建链可信
5. UART 原始透传可信
6. 再切入 `MUX` 模式联调

### 8.3 最小 TCP 调试工具

当需要验证板子是否真的把 payload 发到主机时，优先使用仓库内置最小工具：

1. `tools/tcp_debug_server.py`
   - 打印连接、收包、文本视图和十六进制视图
2. `tools/start_tcp_debug_server.ps1`
   - 会先清理冲突监听，再启动 Python 服务端

推荐命令：

```powershell
powershell -ExecutionPolicy Bypass -File ".\tools\start_tcp_debug_server.ps1" -Port 8081 -NoStdin
```

如需回显：

```powershell
powershell -ExecutionPolicy Bypass -File ".\tools\start_tcp_debug_server.ps1" -Port 8081 -Echo
```

直接运行 Python 服务端也可以：

```powershell
python .\tools\tcp_debug_server.py --host 0.0.0.0 --port 8081 --no-stdin
```

### 8.4 推荐验证方法

1. 先关闭 VOFA、`ncat` 和其它可能占用 `8081` 的进程
2. 启动 `start_tcp_debug_server.ps1`
3. 让板子连接主机 `TCP client` 目标端口
4. 再从主机连接板子的 `TCP server` 端口发送固定测试文本
5. 同时观察：
   - Python 工具是否收到连接与 payload
   - 板子 RTT 是否出现连接或错误信息

若板子 RTT 显示已连接，但主机工具无数据，优先检查本机端口占用而不是先改板端逻辑。

---

## 9. 异常、卡死与假死排查

### 9.1 看到 `TRAP:` 时怎么做

1. 先记录 RTT 中的 trap 标签
2. 立刻用调试器查看当前 PC / LR / 调用栈
3. 结合 `Core/Src/stm32f1xx_it.c` 中对应 handler 定位异常类型

### 9.2 没有 `TRAP:` 但系统不工作时怎么做

若没有 `TRAP:`，但系统表现异常，应优先区分以下情况：

1. 主循环仍在跑，只是业务路径没反应
2. 中断未到或链路未更新
3. 发生了阻塞式等待或超时问题
4. 上层工具接错端口或被错误进程抢占

### 9.3 历史上已经确认过的典型软件问题

以下问题在历史排查中已经出现过，应优先复核，不要重复踩坑：

1. PHY 访问无超时，导致永久卡死
2. 刷新未初始化的 IWDG 句柄导致 HardFault
3. 在长耗时 SPI 路径中错误扩大临界区，导致看似"系统假死"
4. 在多个层次同时触达 CH390 / SPI，导致运行时边界混乱
5. 配置口命令结束方式不对，导致误判为 parser 无响应

### 9.4 2026-04-14 MUX 模式网口失联修复记录

#### 现象

MUX 模式启动后，一段时间后网口失联。重新插拔网线无法恢复，重启后恢复正常。对端主动关闭 TCP 连接后，120 秒内无法重新建立连接。

#### 根因

对端主动关闭 TCP 连接时，`tcp_server_on_recv(p=NULL)` 和 `tcp_client_on_recv(p=NULL)` 调用 `tcp_close()` 关闭本地 pcb。`tcp_close()` 发送 FIN 后将 pcb 推入 TIME_WAIT 状态，持续 `2 × TCP_MSL = 120 秒`。在此期间 pcb 占用 `MEMP_TCP_PCB` 池（总量仅 4 个）。当多条连接同时断开后，pcb 池耗尽，新连接的 `tcp_new()` 返回 NULL。

#### 修复内容

| 文件 | 修改 | 说明 |
|------|------|------|
| `App/tcp_server.c` | `tcp_close(pcb)` → `tcp_abort(pcb)` | 对端关闭时立即释放 pcb，不进入 TIME_WAIT |
| `App/tcp_client.c` | `tcp_close(pcb)` → `tcp_abort(pcb)` | 同上 |
| `Drivers/CH390/ch390_runtime.c` | PKT_ERR 恢复时 `rcr` → `rcr \| RCR_RXEN` | 确保 RX 重新使能，与 WCH 官方参考一致 |
| `Drivers/CH390/ch390_runtime.c` | TX 连续超时 3 次触发 `ch390_runtime_emergency_reset()` | CH390 TX 引擎卡死时自动复位芯片 |
| `Drivers/CH390/ch390_runtime.c` | 新增 `ch390_runtime_health_check()` | 每 5 秒读 VID 验证芯片存活 |
| `Core/Src/main.c` | `App_StartLinksIfNeeded` 失败时不标记 `g_links_started` | 允许下次 poll 自动重试 |
| `Core/Src/main.c` | MUX 逐帧 RTT printf 改为 `#if DEBUG` 门控 | 生产固件不输出，减少主循环延迟 |
| `App/uart_trans.c` | `uart_mux_try_extract_frame` 先搜 0x7E 再消费 header | 非法帧只丢 1 字节而非 5 字节 |

#### 构建验证

Keil MDK-ARM 构建 0 Error(s), 0 Warning(s)。Flash 52.7 KB / 64.0 KB (82.5%)，RAM 20.0 KB / 20.0 KB (100%)。

### 9.5 2026-04-18 MUX 模式丢包修复记录

#### 现象

在 `MUX=1` 模式下进行持续发送测试时，主机侧发送 `500` 个数据包，只收到 `360` 个，存在明显丢包。

#### 根因

本轮定位确认软件侧至少存在以下两个直接丢包点：

1. `App/uart_trans.c` 中 `uart_mux_try_extract_frame()` 在确认整帧完整前，就先消费 `SYNC` 与 header。若 MUX 帧跨越多个 poll 周期到达，半帧会被提前移出 RX ring，导致当前帧失步并被直接丢弃。
2. `App/tcp_server.c`、`App/tcp_client.c` 与 `Core/Src/main.c` 的发送路径对背压与短写处理不完整：
   - `tcp_sndbuf() < len`
   - `tcp_write()` / `tcp_output()` 返回 `ERR_MEM`
   - `uart_trans_write()` 只写入部分字节

   以上情况在旧代码中会被上层静默忽略，表现为“发送函数返回但数据实际未完整进入下游链路”。

#### 修复内容

| 文件 | 修改 | 说明 |
|------|------|------|
| `App/uart_trans.c` | 将 `uart_mux_try_extract_frame()` 改为先窥视、后消费 | 只有在 `SYNC + header + payload + tail` 全部可用时才推进 `rx_tail`，避免半帧被破坏性消费 |
| `App/tcp_server.c` | `tcp_server_send()` 对 `tcp_sndbuf()<len` 与 `ERR_MEM` 返回 `0` 并计入错误 | 明确表示本次发送未被底层接收，不再伪装成成功 |
| `App/tcp_client.c` | `tcp_client_send()` 同步处理背压与 `ERR_MEM` | 逻辑与 server 侧保持一致 |
| `Core/Src/main.c` | `App_SendToUart()` 检查 `uart_trans_write()` 是否完整写入 | TX ring 空间不足时立即显式失败 |
| `Core/Src/main.c` | `App_RouteTcpTraffic()` / `App_RouteRawUartTraffic()` / `App_RouteMuxUartTraffic()` 统一检查发送结果 | 不再把背压、短写和未完整提交静默当成成功 |

#### 结果验证

1. Keil MDK-ARM 构建通过，`0 Error(s), 0 Warning(s)`。
2. 在最新固件下重新进行 MUX 持续发送测试，主机侧发送 `670` 个数据包，接收 `670` 个，`0` 丢包。
3. 本轮修复未增加新的常驻队列与缓冲区，保持当前 RAM 占用边界不变。

### 9.6 2026-04-24 CH390 emergency reset 恢复语义补齐记录

#### 现象

在 CH390 发生 TX timeout 并触发 `ch390_runtime_emergency_reset()` 后，芯片寄存器访问恢复正常，`VID` 可读、PHY 链路也可能保持 `up`，但 TCP 业务流量仍可能长时间不恢复，表现为“芯片还活着，但网络像失联一样，通常只能重启恢复”。

在后续实现收敛中，又确认仅依赖单次 VID 异常或单次 TX busy 即立刻 reset 过于激进，容易把瞬时抖动误判为芯片失活，因此当前代码已经演化为“带阈值的恢复策略”。

#### 根因

`ch390_runtime_emergency_reset()` 旧实现仅执行 `ch390_software_reset()`、`ch390_default_config()` 与 `diag` 刷新，缺少 cold init 里已有的两层恢复语义：

1. **MAC 对齐未恢复**：旧代码没有重新写回 CH390 `PAR`，也没有把硬件 MAC 重新同步到 `netif->hwaddr`。若软件复位后 CH390 的 MAC 过滤状态与 lwIP 侧缓存身份不一致，现象会表现为寄存器可访问、链路仍在，但单播业务流量不通。
2. **上层链路回收未触发**：TX-timeout 路径直接调用 `ch390_runtime_emergency_reset()`，没有保证 `App_StopLinksIfNeeded()` / `App_StartLinksIfNeeded()` 观察到一次有效的 link-down 周期，导致旧 TCP client/server 状态可能跨芯片复位残留，业务层没有完成重建。
3. **恢复策略缺少抖动抑制**：若仅凭单次 TX busy 或单次 VID 异常立即 reset，容易在瞬时总线/链路抖动下过度恢复，放大业务扰动，因此当前实现增加了连续失败阈值和失败计数清零逻辑。

#### 修复内容

| 文件 | 修改 | 说明 |
|------|------|------|
| `Drivers/CH390/ch390_runtime.h` | `ch390_runtime_emergency_reset()` 改为接收 `struct netif *` | 让 reset 路径能同时修复 CH390 与 lwIP 可见状态 |
| `Drivers/CH390/ch390_runtime.c` | 抽取 `ch390_runtime_prepare_netif()` | 在 init / emergency reset 后统一恢复 `hwaddr_len`、`mtu`、`flags` 与 RX 软件状态 |
| `Drivers/CH390/ch390_runtime.c` | 新增 `ch390_runtime_sync_mac()` | emergency reset 后按当前 `netif->hwaddr` 重写 CH390 `PAR`，并重新同步硬件 MAC 到 lwIP |
| `Drivers/CH390/ch390_runtime.c` | emergency reset 成功后清 `g_ch390_irq_pending` 并置位 `g_link_restart_pending` | 避免复位前遗留中断状态影响恢复 |
| `Drivers/CH390/ch390_runtime.c` | `ch390_runtime_check_link()` 增加一次性 hold-down 逻辑 | 保证主循环至少看到一次 link-down，从而触发 app 层 stop/start 回收重建 |
| `Drivers/CH390/ch390_runtime.c` | TX-timeout 与 health-check 两条 reset 路径统一传入 `netif` | 让两类恢复路径都走同一套 MAC 重同步与链路重建语义 |
| `Drivers/CH390/ch390_runtime.c` | 为 TX timeout 与 health-check 增加连续失败阈值 | 降低瞬时抖动导致的过度 reset 风险 |

#### 当前实现语义（以源码为准）

1. **TX timeout 阈值**
   - 单次判定条件：`CH390_TCR.TXREQ` busy-wait 持续超过 `10 ms`
   - 连续阈值：累计 `6` 次后才触发 `ch390_runtime_emergency_reset()`
   - 只要有一次成功发送，`g_tx_consecutive_timeout` 立即清零，因此该阈值针对的是**连续失败**，不是累计历史失败次数。

2. **health-check 阈值**
   - `VID` 单次读到 `0x0000` / `0xFFFF` 并不会立即 reset。
   - 只有连续 `3` 次异常 VID 才触发 emergency reset。
   - 若 `g_ch390_ready == 0`，则 health-check 会直接尝试 reset，不再等待 VID 连续计数。

3. **restart-pending 的单次语义**
   - emergency reset 成功后会置位 restart-pending。
   - 下一次 `ch390_runtime_check_link()` 先强制执行一次 `netif_set_link_down()`，随后立即清除此标志并提前返回。
   - 该设计用于保证主循环至少看到一次有效的 logical link-down，从而沿用现有 `App_StopLinksIfNeeded()` / `App_StartLinksIfNeeded()` 路径回收并重建 TCP links。

4. **内部计数与状态**
   - `g_chip_reset_count`：记录 emergency reset 尝试次数，饱和递增到 `0xFF`。
   - `g_tx_consecutive_timeout`：记录连续 TX busy 超时次数；成功发送或进入 reset 路径后清零。
   - health-check 连续失败计数当前与 `g_link_restart_pending` 共用一个状态字节的高位 nibble 存储；当 VID 恢复正常、达到 reset 阈值或 emergency reset 成功时会清零。

5. **失败路径差异**
   - 只有当 emergency reset 完成后 `g_diag.id_valid` 仍然有效，才会置位 restart-pending 并进入后续 app recycle 语义。
   - 若 reset 后芯片仍不响应，则仅记录失败并返回，不会伪装成可恢复状态。

#### 预期结果

1. CH390 发生 emergency reset 后，硬件 MAC、`netif->hwaddr` 与当前业务身份重新对齐。
2. 即使物理网线始终保持连接，主循环仍会在后续 poll 中观察到一次有效 link-down，并按既有 `App_StopLinksIfNeeded()` / `App_StartLinksIfNeeded()` 路径回收并重建 TCP links。
3. 恢复策略对瞬时异常更保守：只有连续超时或连续 VID 异常达到阈值才会触发 reset，降低误触发恢复的概率。
4. 复位后的恢复语义与 cold init 更接近，不再停留在“芯片寄存器恢复正常，但业务流量仍死掉”的半恢复状态。

#### 构建验证

1. 已由现场手动执行工程构建，构建通过。
2. 本轮修改覆盖 `Drivers/CH390/ch390_runtime.c`、`Drivers/CH390/ch390_runtime.h` 与本手册记录，未改动 TCP client/server 模块对外接口。

---

## 10. 常见误区

调试当前工程时，应避免以下误区：

1. 不要继续沿用“CH390 恒为全 `0xFF`”这一过时结论
2. 不要在 `main.c`、IRQ、netif 多处重新插入原始 CH390 访问
3. 不要在没有芯片脚侧证据前，只凭 MCU 侧 GPIO 判断总线正常
4. 不要在基础寄存器读写尚不可信时，直接调高层 `TCP/UART/MUX` 业务逻辑
5. 不要把一次性 bring-up 实验代码长期留在正式路径中
6. 不要让多个本机进程同时监听板子要连接的 TCP 端口
7. 不要在尚未证明命令已真正进入 parser 之前，直接归因到 Flash、协议或网络层

---

## 11. 推荐的现场记录模板

建议每次现场调试至少记录以下信息：

1. 日期时间
2. 板卡编号
3. 固件产物路径
4. 下载方式
5. RTT 关键日志
6. 串口发送内容
7. TCP 调试工具输出
8. 关键波形或电压量测点
9. 结论
10. 下一步动作

建议记录格式：

```text
时间：
板卡：
固件：
下载方式：
操作步骤：
RTT输出：
串口/TCP现象：
硬件量测：
结论：
下一步：
```

---

## 12. 当前推荐的结论表达方式

若需要向项目成员同步当前状态，建议采用以下口径：

1. 当前工程软件架构已稳定在 `bare-metal + lwIP RAW + ch390_runtime 单一拥有者`
2. 当前调试重点已经从“CH390 是否完全无响应”转移到协议、链路和系统级联调
3. 当前对外协议和配置模型应以 `MUX / NET / LINK[idx]` 为准
4. `USART1` 配置口、`USART2/3` 数据口与 TCP 路由必须按最新代码路径调试，不应再参照历史 `IP/MASK/GW/PORT/RIP/RPORT` 公开接口模型
5. 硬件验证仍必须以 CH390 芯片脚侧波形和供电域量测为准

---

## 13. 建议配套阅读

建议与本指导配套阅读：

1. `AT固件使用手册.md`
2. `项目技术实现.md`
3. `项目需求说明.md`
4. `uart-ch390-debug-handoff.md`
5. `CH390_最终结论报告.md`
6. `build_keil.log`
7. `PCB/SCH_Schematic1_2026-03-26.pdf`
8. `tools/tcp_debug_server.py`
9. `tools/start_tcp_debug_server.ps1`
