Files
TCP2UART/工程调试指南.md
T

12 KiB
Raw Blame History

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_IRQHandlerHAL 时间基准
    • USART1/2/3EXTI0、DMA 回调等联调关键入口

3.2 CH390 责任边界

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

  1. Drivers/CH390/CH390_Interface.cGPIO / SPI / 寄存器与内存事务
  2. Drivers/CH390/CH390.c:芯片级 helper
  3. Drivers/CH390/ch390_runtime.c:唯一的运行时拥有者
  4. Drivers/LwIP/src/netif/ethernetif.cnetif glue 与轮询桥接
  5. SPI 访问由 xSpiMutex 保护,避免多任务竞争

3.3 配置口与业务口边界

  1. USART1AT 配置口,接收 AT 命令
  2. USART2 / USART3:数据口,普通透传或 MUX 承载

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

4.1 核心硬件对象

  1. MCUSTM32F103RCT6256KB 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 获取所有任务运行状态:

char buf[512];
vTaskList(buf);
SEGGER_RTT_WriteString(0, buf);

输出格式:

任务名        状态  优先级  剩余栈  编号
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,溢出时自动调用:

void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    SEGGER_RTT_printf(0, "STACK OVERFLOW: %s\n", pcTaskName);
    __BKPT(0);
}

5.3 堆内存失败检测

已启用 configUSE_MALLOC_FAILED_HOOK,分配失败时自动调用:

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 OVERFLOWMALLOC 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->payloadARP 头 开始,而不是从 Ethernet 头开始
  3. 因此如果没有先执行:
pbuf_remove_header(p, SIZEOF_ETH_HDR)

则 ARP 包虽然被收到了,但会在 etharp_input() 的早期校验中被静默丢弃,最终表现为:

  1. Wireshark 能看到 PC 发来的 ARP request
  2. 板子侧底层收包计数在增长
  3. 但设备始终不回 ARP reply
  4. ping 也自然不会成功

当前应保留的正确处理方式如下:

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 okARP 帧计数在涨链路已 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 OVERFLOWMALLOC 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