From deecc8f1c8073b844548c4c279f35dd70de8425b Mon Sep 17 00:00:00 2001 From: xiao Date: Sat, 18 Apr 2026 05:29:47 +0800 Subject: [PATCH] docs: add debug handoff package --- CODING_PROMPT.md | 289 +++++++---------------------------------------- 交接清单.md | 208 ++++++++++++++++++++++++++++++++++ 工程调试指南.md | 92 +++++++++++++++ 3 files changed, 342 insertions(+), 247 deletions(-) create mode 100644 交接清单.md diff --git a/CODING_PROMPT.md b/CODING_PROMPT.md index 3a78a6e..e34f65d 100644 --- a/CODING_PROMPT.md +++ b/CODING_PROMPT.md @@ -1,271 +1,66 @@ -# TCP2UART FreeRTOS 编码任务提示词 +# TCP2UART 当前交接 Prompt -## 项目位置 +## 1. 用途 -`D:\code\STM32Project\TCP2UART`,当前在 `master` 分支。 +本文件不再承担“项目从零编码任务说明”的职责,而是作为**当前工程的交接入口 Prompt**使用。 -## 背景 - -本项目是一个 TCP↔UART 双向透传设备,之前在 `baremetal-r8` 分支上以裸机 + lwIP RAW API 方式实现并已调通。现在需要在 `master` 分支上将其迁移为 **FreeRTOS + lwIP netconn API** 架构。 - -**工程配置文件(IOC、Keil、启动文件、lwipopts.h、FreeRTOSConfig.h)已全部更新完毕,不需要你改。你的任务是编写/重写 C 代码。** +长期有效的工程知识、调试经验和现状说明,已经分别固化到其它文档,不再全部堆在本文件中。 --- -## 必读文档(按优先级) +## 2. 接手后先读什么 -1. `项目技术实现.md` — **首要参考**,第四节"FreeRTOS 任务设计"包含完整的 9+1 任务架构、通信机制、实现模板 -2. `项目需求说明.md` — MUX/NET/LINK 协议需求 -3. `AT固件使用手册.md` — AT 命令定义 -4. `工程调试指南.md` — 调试方法 -5. `Keil工程配置说明.txt` — 工程结构 +请按以下顺序阅读: + +1. `交接清单.md` —— 当前状态、接下来要做什么、怎么做 +2. `工程调试指南.md` —— 已固化的调试经验与当前工程真实边界 +3. `项目技术实现.md` —— 架构、任务模型、协议模型 +4. `项目需求说明.md` +5. `AT固件使用手册.md` --- -## 架构核心决策(不可更改) +## 3. 当前工程一句话状态 -1. **lwIP**: `NO_SYS=0`, `netconn API`, `LWIP_SOCKET=0`, `LWIP_TCPIP_CORE_LOCKING=1` -2. **tcpip_thread**: lwIP 自建,优先级 6,栈 512 words -3. **每条 TCP 连接独立任务**: S1/S2 各一个 Server 任务,C1/C2 各一个 Client 任务 -4. **NetPollTask**: 独立任务调 `ethernetif_poll()`,不由 tcpip_thread 做 -5. **零拷贝 Queue**: 传递 `route_msg_t*` 指针(含 src_id, dst_mask, len, conn_type, *data) -6. **路由内联**: 不设独立 RouteTask,路由逻辑分别在 UartRxTask 和各 TCP 任务内 -7. **HAL 时间基准**: TIM4(非 SysTick,SysTick 被 FreeRTOS 占用) -8. **MCU**: STM32F103RCT6 (256KB/48KB),若 RAM 不够换 RDT6 (384KB/64KB) pin-to-pin +当前项目已从早期 bring-up 阶段推进到 full-task 运行期调试阶段;`DIAG_TASK_ISOLATION=1` 稳定,`DIAG_TASK_ISOLATION=0` 仍会卡死,但故障边界已被多轮 discriminator 推进到 enabled 的 `netconn_*` 路径。当前在 `STM32F103RCT6` 上的 RAM/heap 余量过低,已被认定为调试噪声的主要来源之一,因此推荐下一阶段先切到 pin2pin 的 `STM32F103RDT6` 再继续分析。 --- -## 任务清单(9 个 FreeRTOS 任务) +## 4. 下一位 agent 的当前目标 -| 任务 | 优先级 | 栈(words) | 阻塞方式 | 对应文件 | -|------|--------|-----------|----------|----------| -| tcpip_thread | 6 | 512 | mbox | lwIP 自动创建 | -| NetPollTask | 5 | 384 | BinarySem 2ms 超时 | 新建 `task_net_poll.c` | -| TcpSrvTask_S1 | 4 | 384 | netconn_accept 阻塞 | 重写 `tcp_server.c` | -| TcpSrvTask_S2 | 4 | 384 | netconn_accept 阻塞 | 重写 `tcp_server.c` | -| TcpCliTask_C1 | 4 | 256 | netconn_connect 阻塞 | 重写 `tcp_client.c` | -| TcpCliTask_C2 | 4 | 256 | netconn_connect 阻塞 | 重写 `tcp_client.c` | -| UartRxTask | 4 | 384 | TaskNotify 10ms 超时 | 适配 `uart_trans.c` | -| ConfigTask | 2 | 256 | QueueReceive 阻塞 | 适配 `config.c` | -| DefaultTask | 1 | 128 | vTaskDelay 500ms | `freertos.c` CubeMX 生成,LED 心跳 + IWDG 喂狗,保留不可删 | +请不要把当前任务理解成“立刻继续加逻辑修补”。当前更重要的是: + +1. 完成 `STM32F103RCT6 -> STM32F103RDT6` 目标切换 +2. 使用真实 Keil 日志重新确认构建成功 +3. 在新器件上复测当前代码基线 +4. 比较: + - 故障是否消失 + - 是否明显后移 + - 是否仍停在相同 enabled path +5. 只有拿到新器件上的第一轮 RTT / heap / HWM 证据后,再决定下一步最小化改动 --- -## 文件级操作指南 +## 5. 工作约束 -### 第一优先级:sys_arch.c(lwIP 移植层) +1. 构建真值以 `MDK-ARM/build_capture.txt`、`TCP2UART.build_log.htm`、`.map` 为准 +2. 不要再把 viewer 当作当前构建真值 +3. 不要忽视 `DIAG_TASK_ISOLATION=1 正常、=0 异常` 这个前提 +4. 在新器件的第一轮复测前,避免继续做大范围代码改动 +5. 每次只做一个能明显改变故障边界的最小改动,并保留 RTT 证据 -**文件**: `Drivers/LwIP/port/sys_arch.c` +--- -这是最关键的基础模块,没有它 tcpip_thread 无法启动。必须实现: +## 6. 可直接复制给下一位 agent 的起始 Prompt -- `sys_init()` — 空函数即可 -- `sys_thread_new(name, thread, arg, stacksize, prio)` — 调用 `xTaskCreate` -- `sys_mbox_new(mbox, size)` / `sys_mbox_free` / `sys_mbox_post` / `sys_mbox_trypost` / `sys_arch_mbox_fetch` / `sys_arch_mbox_tryfetch` — 基于 FreeRTOS `xQueueCreate` / `xQueueSend` / `xQueueReceive` -- `sys_sem_new` / `sys_sem_free` / `sys_arch_sem_wait` / `sys_sem_signal` — 基于 FreeRTOS `xSemaphoreCreateBinary` 等 -- `sys_mutex_new` / `sys_mutex_free` / `sys_mutex_lock` / `sys_mutex_unlock` — 基于 FreeRTOS `xSemaphoreCreateMutex` -- `sys_arch_protect()` / `sys_arch_unprotect()` — 基于 `vPortEnterCritical()` / `vPortExitCritical()` -- `sys_now()` — 返回 `xTaskGetTickCount()` -- `sys_thread_id()` — 返回 `xTaskGetCurrentTaskHandle()` +```text +请先阅读:`交接清单.md`、`工程调试指南.md`、`项目技术实现.md`。 -参考 FreeRTOSConfig.h 中已定义的兼容宏: -```c -#define sys_arch_protect() vPortEnterCritical() -#define sys_arch_unprotect(x) vPortExitCritical() -#define sys_now() ((uint32_t)xTaskGetTickCount()) +当前项目是 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 后,再继续做最小化的下一步判别。 ``` - -**参考**: `baremetal-r8` 分支的 `Drivers/LwIP/port/sys_arch.c` 是 NO_SYS=1 的空壳,需要完全重写。 - -### 第二优先级:main.c + freertos.c - -**文件**: `Core/Src/main.c` - -当前 main.c 是裸机 while(1) 轮询架构,需要改为: -1. 保留所有 `MX_xxx_Init()` 外设初始化 -2. 删除 `App_Init()` / `App_Poll()` 及所有 `App_Route*` 函数 -3. 删除 `StackGuard_*`(FreeRTOS 有自己的栈溢出检测) -4. 保留 `SystemClock_Config()`、`Debug_TrapWithRttHint()`、`Error_Handler()` -5. `main()` 最后调用 `MX_FREERTOS_Init()` 启动调度器(由 CubeMX 生成的 freertos.c 中实现) - -**文件**: `Core/Src/freertos.c` - -CubeMX 生成的框架文件,需要在 `MX_FREERTOS_Init()` 中创建所有自定义任务: -- NetPollTask -- TcpSrvTask_S1 / TcpSrvTask_S2 -- TcpCliTask_C1 / TcpCliTask_C2 -- UartRxTask -- ConfigTask - -任务优先级和栈大小使用 FreeRTOSConfig.h 中定义的常量: -```c -#define TASK_PRIORITY_NET_POLL 5 -#define TASK_STACK_NET_POLL 384 -// ... 等等,见 FreeRTOSConfig.h 底部 -``` - -### 第三优先级:tcp_server.c / tcp_client.c(重写) - -**文件**: `App/tcp_server.c` / `App/tcp_server.h` - -当前基于 RAW API (`tcp_pcb`, `tcp_recv` 回调),需完全重写为 netconn: - -```c -// Server 任务模板(每个实例一个任务) -void TcpSrvTask_S1(void *arg) { - 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) { - server_worker(newconn, LINK_S1); - netconn_close(newconn); - netconn_delete(newconn); - } - } -} - -void server_worker(struct netconn *conn, uint8_t link_idx) { - struct netbuf *buf; - while (netconn_recv(conn, &buf) == ERR_OK) { - // 构造 route_msg_t,投递到 xTcpRxQueue - // xQueueSend(xTcpRxQueue, &msg, pdMS_TO_TICKS(10)); - netbuf_delete(buf); - } -} -``` - -**文件**: `App/tcp_client.c` / `App/tcp_client.h` - -```c -void TcpCliTask_C1(void *arg) { - for (;;) { - struct netconn *conn = netconn_new(NETCONN_TCP); - ip_addr_t rip; - IP_ADDR4(&rip, cfg->links[2].rip[0], ...); - if (netconn_connect(conn, &rip, cfg->links[2].rport) == ERR_OK) { - client_worker(conn, LINK_C1); - } - netconn_close(conn); - netconn_delete(conn); - vTaskDelay(pdMS_TO_TICKS(reconnect_interval)); - } -} -``` - -**重要**: -- 删除内部 ring buffer(netconn 内部有 pbuf 缓冲) -- 删除 RAW API 回调函数 (`tcp_recv`, `tcp_sent`, `tcp_err` 等) -- Server 接受的连接在**本任务内**处理(不 spawn 新任务) -- Client 断连后需自动重连(delay 后重试) -- TCP→UART 数据通过 `xTcpRxQueue` 传递 route_msg_t 指针 - -### 第四优先级:uart_trans.c 适配 - -**文件**: `App/uart_trans.c` / `App/uart_trans.h` - -核心改动: -1. 删除 `uart_trans_poll()` 函数(不再需要轮询) -2. UART IDLE 中断回调中调用 `xTaskNotifyFromISR(xUartRxTaskHandle, 1, eSetBits, &xHigherPriorityTaskWoken)` -3. UART TX 完成中断回调中触发 DMA 继续发送(保持现有逻辑) -4. 保持 MUX 帧编解码 (`uart_mux_try_extract_frame`, `uart_mux_encode_frame`) 不变 - -### 第五优先级:config.c 适配 - -**文件**: `App/config.c` / `App/config.h` - -改动较小: -1. 删除 `config_poll()` 函数 -2. ConfigTask 通过 `xQueueReceive(xConfigQueue, ...)` 阻塞等待 AT 文本 -3. AT 命令解析逻辑保持不变 -4. `flash_param.c` 无需改动 - -### 第六优先级:stm32f1xx_it.c 适配 - -**文件**: `Core/Src/stm32f1xx_it.c` - -改动: -1. `EXTI0_IRQHandler`(CH390 中断): 调用 `xSemaphoreGiveFromISR(xNetSemaphore, &xHigherPriorityTaskWoken)` + `portYIELD_FROM_ISR` -2. `USART2_IRQHandler` / `USART3_IRQHandler`: IDLE 中断中调用 `xTaskNotifyFromISR` 通知 UartRxTask -3. `TIM4_IRQHandler`: 保持 HAL 时间基准(调用 `HAL_TIM_IRQHandler`) -4. DMA 中断保持现有逻辑 -5. **删除** `SysTick_Handler`(FreeRTOS 接管 SysTick,TIM4 做 HAL tick) - -### 第七优先级:新建路由消息模块 - -**新建文件**: `App/route_msg.c` / `App/route_msg.h` - -```c -typedef struct { - uint8_t src_id; - uint8_t dst_mask; - uint16_t len; - uint8_t conn_type; /* LINK_S1/S2/C1/C2 */ - uint8_t *data; /* 指向静态缓冲池 */ -} route_msg_t; - -#define ROUTE_BUF_COUNT 4 -#define ROUTE_BUF_SIZE 512 - -/* 从池中获取空闲缓冲 */ -uint8_t *route_buf_alloc(void); -/* 标记缓冲为可用 */ -void route_buf_free(uint8_t *buf); -/* 构造 route_msg_t 并发送到指定 Queue */ -void route_send(QueueHandle_t queue, uint8_t src_id, uint8_t dst_mask, - uint8_t conn_type, const uint8_t *data, uint16_t len); -``` - ---- - -## 可参考的 baremetal-r8 分支代码 - -通过 `git show baremetal-r8:` 查看裸机版本(已验证可工作): -- `App/config.c` — AT 命令解析(可直接复用大部分逻辑) -- `App/flash_param.c` — Flash 读写(完全不用改) -- `App/uart_trans.c` — UART DMA/IDLE + MUX 帧编解码(复用 + 适配) -- `Core/Src/main.c` — App_Poll / App_RouteRawUartTraffic / App_RouteMuxUartTraffic(路由逻辑参考) -- `Drivers/CH390/*` — CH390 驱动(复用 + 加 Mutex) -- `Drivers/LwIP/src/netif/ethernetif.c` — netif glue(复用 + 适配 tcpip_input) - ---- - -## 约束 - -1. **不修改**以下文件:TCP2UART.ioc, MDK-ARM/TCP2UART.uvprojx, startup_stm32f103xe.s, FreeRTOSConfig.h, lwipopts.h -2. **不引入**新依赖,不使用 DHCP、DNS、UDP -3. FreeRTOS 堆管理用 `heap_4.c`(已在 FreeRTOSConfig.h 中配置) -4. 调试输出统一使用 `SEGGER_RTT` -5. ISR 中只调用 `FromISR` 后缀的 FreeRTOS API -6. FreeRTOS 可管理的中断优先级 >= 5(configMAX_SYSCALL_INTERRUPT_PRIORITY) -7. 所有任务栈用 `uxTaskGetStackHighWaterMark(NULL)` 监控 -8. 所有 `.c` 文件使用 `#include "xxx.h"` 而非相对路径 -9. Keil 工程文件(uvprojx)中已有所有源文件组,如果新建文件需要在 Keil 中添加 - ---- - -## 验证目标 - -完成编码后,应能通过以下验证: - -1. MDK-ARM 编译 0 Error / 0 Warning -2. RTT 输出显示所有 9 个任务成功创建(`vTaskList` 输出) -3. CH390 初始化成功(`ETH init: done`) -4. TCP Server 在配置端口监听(外部工具可连接) -5. TCP Client 连接远端成功 -6. UART 数据通过 TCP 双向透传 -7. MUX 帧模式下路由正确 -8. AT 命令通过 UART1 正常响应 -9. FreeRTOS 堆剩余 > 1KB(`xPortGetFreeHeapSize()`) -10. 所有任务栈 highwatermark > 20% 余量 -11. DefaultTask(CubeMX 生成)保留,充实为 LED 心跳 + IWDG 喂狗,不可删除 - ---- - -## 注意事项 - -- 如果编译时发现 RAM 溢出(48KB),优先压缩:取消 TCP ring buffer(netconn 自带 pbuf)、降低 MEM_SIZE 到 6KB、降低 FreeRTOS 堆到 8KB -- 如果仍然不够,告知用户切换 STM32F103RDT6(64KB SRAM),仅需改 startup/宏/SRAM 大小 -- `baremetal-r8` 分支的代码是已验证可工作的,可以作为逻辑参考,但不要照搬 RAW API 调用 -- 路由逻辑参考 `baremetal-r8` 的 `App_RouteRawUartTraffic()` 和 `App_RouteMuxUartTraffic()` diff --git a/交接清单.md b/交接清单.md new file mode 100644 index 0000000..72de914 --- /dev/null +++ b/交接清单.md @@ -0,0 +1,208 @@ +# 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 后,再继续做最小化的下一步判别。 +``` diff --git a/工程调试指南.md b/工程调试指南.md index 3f9d8e1..6a7feeb 100644 --- a/工程调试指南.md +++ b/工程调试指南.md @@ -356,3 +356,95 @@ case ETHTYPE_ARP: 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 结果交叉验证