Files
TCP2UART/CODING_PROMPT.md
T

11 KiB
Raw Blame History

TCP2UART FreeRTOS 编码任务提示词

项目位置

D:\code\STM32Project\TCP2UART,当前在 master 分支。

背景

本项目是一个 TCP↔UART 双向透传设备,之前在 baremetal-r8 分支上以裸机 + lwIP RAW API 方式实现并已调通。现在需要在 master 分支上将其迁移为 FreeRTOS + lwIP netconn API 架构。

工程配置文件(IOC、Keil、启动文件、lwipopts.h、FreeRTOSConfig.h)已全部更新完毕,不需要你改。你的任务是编写/重写 C 代码。


必读文档(按优先级)

  1. 项目技术实现.md首要参考,第四节"FreeRTOS 任务设计"包含完整的 9+1 任务架构、通信机制、实现模板
  2. 项目需求说明.md — MUX/NET/LINK 协议需求
  3. AT固件使用手册.md — AT 命令定义
  4. 工程调试指南.md — 调试方法
  5. Keil工程配置说明.txt — 工程结构

架构核心决策(不可更改)

  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(非 SysTickSysTick 被 FreeRTOS 占用)
  8. MCU: STM32F103RCT6 (256KB/48KB),若 RAM 不够换 RDT6 (384KB/64KB) pin-to-pin

任务清单(9 个 FreeRTOS 任务)

任务 优先级 栈(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 1000ms freertos.c CubeMX 生成

文件级操作指南

第一优先级:sys_arch.clwIP 移植层)

文件: Drivers/LwIP/port/sys_arch.c

这是最关键的基础模块,没有它 tcpip_thread 无法启动。必须实现:

  • 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()

参考 FreeRTOSConfig.h 中已定义的兼容宏:

#define sys_arch_protect()       vPortEnterCritical()
#define sys_arch_unprotect(x)    vPortExitCritical()
#define sys_now()                ((uint32_t)xTaskGetTickCount())

参考: 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 中定义的常量:

#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:

// 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

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 buffernetconn 内部有 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_IRQHandlerCH390 中断): 调用 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_HandlerFreeRTOS 接管 SysTickTIM4 做 HAL tick

第七优先级:新建路由消息模块

新建文件: App/route_msg.c / App/route_msg.h

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 可管理的中断优先级 >= 5configMAX_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 堆剩余 > 1KBxPortGetFreeHeapSize()
  10. 所有任务栈 highwatermark > 20% 余量

注意事项

  • 如果编译时发现 RAM 溢出(48KB),优先压缩:取消 TCP ring buffernetconn 自带 pbuf)、降低 MEM_SIZE 到 6KB、降低 FreeRTOS 堆到 8KB
  • 如果仍然不够,告知用户切换 STM32F103RDT664KB SRAM),仅需改 startup/宏/SRAM 大小
  • baremetal-r8 分支的代码是已验证可工作的,可以作为逻辑参考,但不要照搬 RAW API 调用
  • 路由逻辑参考 baremetal-r8App_RouteRawUartTraffic()App_RouteMuxUartTraffic()