11 KiB
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 代码。
必读文档(按优先级)
项目技术实现.md— 首要参考,第四节"FreeRTOS 任务设计"包含完整的 9+1 任务架构、通信机制、实现模板项目需求说明.md— MUX/NET/LINK 协议需求AT固件使用手册.md— AT 命令定义工程调试指南.md— 调试方法Keil工程配置说明.txt— 工程结构
架构核心决策(不可更改)
- lwIP:
NO_SYS=0,netconn API,LWIP_SOCKET=0,LWIP_TCPIP_CORE_LOCKING=1 - tcpip_thread: lwIP 自建,优先级 6,栈 512 words
- 每条 TCP 连接独立任务: S1/S2 各一个 Server 任务,C1/C2 各一个 Client 任务
- NetPollTask: 独立任务调
ethernetif_poll(),不由 tcpip_thread 做 - 零拷贝 Queue: 传递
route_msg_t*指针(含 src_id, dst_mask, len, conn_type, *data) - 路由内联: 不设独立 RouteTask,路由逻辑分别在 UartRxTask 和各 TCP 任务内
- HAL 时间基准: TIM4(非 SysTick,SysTick 被 FreeRTOS 占用)
- 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.c(lwIP 移植层)
文件: Drivers/LwIP/port/sys_arch.c
这是最关键的基础模块,没有它 tcpip_thread 无法启动。必须实现:
sys_init()— 空函数即可sys_thread_new(name, thread, arg, stacksize, prio)— 调用xTaskCreatesys_mbox_new(mbox, size)/sys_mbox_free/sys_mbox_post/sys_mbox_trypost/sys_arch_mbox_fetch/sys_arch_mbox_tryfetch— 基于 FreeRTOSxQueueCreate/xQueueSend/xQueueReceivesys_sem_new/sys_sem_free/sys_arch_sem_wait/sys_sem_signal— 基于 FreeRTOSxSemaphoreCreateBinary等sys_mutex_new/sys_mutex_free/sys_mutex_lock/sys_mutex_unlock— 基于 FreeRTOSxSemaphoreCreateMutexsys_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) 轮询架构,需要改为:
- 保留所有
MX_xxx_Init()外设初始化 - 删除
App_Init()/App_Poll()及所有App_Route*函数 - 删除
StackGuard_*(FreeRTOS 有自己的栈溢出检测) - 保留
SystemClock_Config()、Debug_TrapWithRttHint()、Error_Handler() 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 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
核心改动:
- 删除
uart_trans_poll()函数(不再需要轮询) - UART IDLE 中断回调中调用
xTaskNotifyFromISR(xUartRxTaskHandle, 1, eSetBits, &xHigherPriorityTaskWoken) - UART TX 完成中断回调中触发 DMA 继续发送(保持现有逻辑)
- 保持 MUX 帧编解码 (
uart_mux_try_extract_frame,uart_mux_encode_frame) 不变
第五优先级:config.c 适配
文件: App/config.c / App/config.h
改动较小:
- 删除
config_poll()函数 - ConfigTask 通过
xQueueReceive(xConfigQueue, ...)阻塞等待 AT 文本 - AT 命令解析逻辑保持不变
flash_param.c无需改动
第六优先级:stm32f1xx_it.c 适配
文件: Core/Src/stm32f1xx_it.c
改动:
EXTI0_IRQHandler(CH390 中断): 调用xSemaphoreGiveFromISR(xNetSemaphore, &xHigherPriorityTaskWoken)+portYIELD_FROM_ISRUSART2_IRQHandler/USART3_IRQHandler: IDLE 中断中调用xTaskNotifyFromISR通知 UartRxTaskTIM4_IRQHandler: 保持 HAL 时间基准(调用HAL_TIM_IRQHandler)- DMA 中断保持现有逻辑
- 删除
SysTick_Handler(FreeRTOS 接管 SysTick,TIM4 做 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)
约束
- 不修改以下文件:TCP2UART.ioc, MDK-ARM/TCP2UART.uvprojx, startup_stm32f103xe.s, FreeRTOSConfig.h, lwipopts.h
- 不引入新依赖,不使用 DHCP、DNS、UDP
- FreeRTOS 堆管理用
heap_4.c(已在 FreeRTOSConfig.h 中配置) - 调试输出统一使用
SEGGER_RTT - ISR 中只调用
FromISR后缀的 FreeRTOS API - FreeRTOS 可管理的中断优先级 >= 5(configMAX_SYSCALL_INTERRUPT_PRIORITY)
- 所有任务栈用
uxTaskGetStackHighWaterMark(NULL)监控 - 所有
.c文件使用#include "xxx.h"而非相对路径 - Keil 工程文件(uvprojx)中已有所有源文件组,如果新建文件需要在 Keil 中添加
验证目标
完成编码后,应能通过以下验证:
- MDK-ARM 编译 0 Error / 0 Warning
- RTT 输出显示所有 9 个任务成功创建(
vTaskList输出) - CH390 初始化成功(
ETH init: done) - TCP Server 在配置端口监听(外部工具可连接)
- TCP Client 连接远端成功
- UART 数据通过 TCP 双向透传
- MUX 帧模式下路由正确
- AT 命令通过 UART1 正常响应
- FreeRTOS 堆剩余 > 1KB(
xPortGetFreeHeapSize()) - 所有任务栈 highwatermark > 20% 余量
注意事项
- 如果编译时发现 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()