1. 基于RTOS的直线插补任务框架搭建

任务目标

在RTOS环境下搭建直线插补任务框架:创建指令接收任务与插补执行任务,通过消息队列传递运动指令,实现任务间通信与调度。插补执行任务按给定起点、终点与速度,周期性计算并输出各轴目标位置。

环境依赖

本案例基于 FreeRTOS(V10.0+),硬件无关,可在PC模拟器或任意支持FreeRTOS的MCU上运行。需要:

  • FreeRTOS内核源码(含 queue.h、task.h)
  • C编译器(GCC/Keil/IAR等)
  • 串口或调试器用于观察输出

核心实现

定义运动指令结构体,包含目标坐标与插补周期数。接收任务从输入源(此处模拟为键盘/串口)获取指令并发送至队列;插补任务接收指令后,按周期计算直线插补位置并输出。使用vTaskDelayUntil保证插补周期恒定。

```c #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include #include // 运动指令结构 typedef struct { float start_x, start_y; // 起点 float end_x, end_y; // 终点 float speed; // 速度(单位/秒) } MotionCmd_t; // 消息队列句柄 QueueHandle_t xMotionQueue; // 插补周期(ms) #define CYCLE_MS 10 // 模拟输入:生成一条从(0,0)到(100,50)的指令 static void vGetUserInput(MotionCmd_t *cmd) { cmd->start_x = 0; cmd->start_y = 0; cmd->end_x = 100; cmd->end_y = 50; cmd->speed = 100; // 每秒100单位 } // 指令接收任务 static void vCmdReceiverTask(void *pvParameters) { MotionCmd_t cmd; for (;;) { vGetUserInput(&cmd); if (xQueueSend(xMotionQueue, &cmd, pdMS_TO_TICKS(100)) != pdPASS) { printf("队列发送失败\r\n"); } vTaskDelay(pdMS_TO_TICKS(2000)); // 每2秒发送一次 } } // 直线插补执行任务 static void vInterpTask(void *pvParameters) { MotionCmd_t cmd; BaseType_t received; float dx, dy, dist, step_x, step_y; float cur_x, cur_y; int total_steps, step; for (;;) { received = xQueueReceive(xMotionQueue, &cmd, portMAX_DELAY); if (received != pdPASS) continue; // 计算总距离与步数 dx = cmd.end_x - cmd.start_x; dy = cmd.end_y - cmd.start_y; dist = sqrtf(dx*dx + dy*dy); if (dist < 0.001f) continue; // 周期时间(s) = CYCLE_MS/1000 float step_dist = cmd.speed * (CYCLE_MS / 1000.0f); total_steps = (int)ceilf(dist / step_dist); if (total_steps < 1) total_steps = 1; step_x = dx / total_steps; step_y = dy / total_steps; cur_x = cmd.start_x; cur_y = cmd.start_y; printf("开始插补: (%.1f,%.1f)->(%.1f,%.1f), 步数=%d\r\n", cmd.start_x, cmd.start_y, cmd.end_x, cmd.end_y, total_steps); // 按周期输出插补点 TickType_t xLastWakeTime = xTaskGetTickCount(); for (step = 0; step <= total_steps; step++) { printf("步骤%3d: X=%.2f Y=%.2f\r\n", step, cur_x, cur_y); cur_x += step_x; cur_y += step_y; vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(CYCLE_MS)); } printf("插补完成\r\n"); } } int main(void) { // 创建队列(容量1,足够缓冲一条指令) xMotionQueue = xQueueCreate(1, sizeof(MotionCmd_t)); // 创建任务 xTaskCreate(vCmdReceiverTask, "Receiver", 256, NULL, 1, NULL); xTaskCreate(vInterpTask, "Interp", 256, NULL, 2, NULL); // 启动调度器 vTaskStartScheduler(); // 正常不会到达此处 for (;;); return 0; } ```

运行与验证

编译运行:将代码与FreeRTOS源码一同编译,在支持FreeRTOS的平台上运行(如QEMU模拟STM32、ESP32开发板或Windows模拟器)。确保堆栈大小足够(本案例每任务256字)。

预期输出:程序启动后约2秒内,串口输出插补开始信息,随后每10ms输出一个插补点,从(0,0)逐步递增至(100,50)。总步数约为ceil(111.8 / (100*0.01)) = 112步。最后输出“插补完成”。

通过判定标准:

  • 两个任务均被创建并正常运行,无崩溃或看门狗复位;
  • 接收任务成功通过队列将指令传递给插补任务;
  • 插补点坐标从起点线性递增至终点,相邻点间距均匀(约1单位);
  • 输出节奏稳定,每行间隔约10ms(可用示波器或时间戳验证)。

常见问题

  • 队列接收不到数据:检查队列创建是否成功(返回非NULL),以及发送时阻塞时间是否足够。
  • 插补点跳动不均匀:确认vTaskDelayUntil使用正确,且任务未被更高优先级任务抢占导致周期抖动。
  • 浮点printf输出异常:若MCU未启用浮点打印支持,改用整数定点数表示坐标(如放大100倍)。