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倍)。