docs: add debug handoff package
This commit is contained in:
+42
-247
@@ -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:<filepath>` 查看裸机版本(已验证可工作):
|
||||
- `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()`
|
||||
|
||||
Reference in New Issue
Block a user