4. 多轴直线插补的同步控制
任务目标
在RTOS环境下实现三轴直线插补,核心要求是三个轴同时启动、同步运动并同时到达终点。本案例使用FreeRTOS作为同步机制,通过二进制信号量实现三轴任务的同步启动,确保各轴按同一插补周期推进,最终同时到达目标位置。
实现原理
三轴直线插补采用逐点比较法:每个插补周期内,各轴根据当前位置与终点距离独立计算步进方向,但所有轴共享同一个插补周期节拍。同步控制的关键在于:三个轴任务在收到启动信号量后同时开始运动,每个周期内各轴完成一步后等待下一周期节拍,从而保证各轴步进次数相同、运动时间一致,实现同时到达终点。
完整代码
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/* 三轴目标位置与当前位置 */
#define AXIS_COUNT 3
int target_pos[AXIS_COUNT] = {100, 150, 80}; /* 各轴目标步数 */
int current_pos[AXIS_COUNT] = {0, 0, 0}; /* 各轴当前位置 */
int step_done[AXIS_COUNT] = {0, 0, 0}; /* 各轴完成标志 */
/* 同步信号量:用于三轴同时启动 */
SemaphoreHandle_t start_sem;
/* 插补周期节拍信号量:由定时器或主循环释放 */
SemaphoreHandle_t tick_sem;
/* 各轴运动任务 */
void axis_task(void *param) {
int axis_id = *(int *)param;
/* 等待启动信号,实现三轴同步开始 */
xSemaphoreTake(start_sem, portMAX_DELAY);
/* 插补主循环 */
while (1) {
/* 等待插补周期节拍 */
xSemaphoreTake(tick_sem, portMAX_DELAY);
/* 当前轴尚未到达终点时,前进一步 */
if (current_pos[axis_id] < target_pos[axis_id]) {
current_pos[axis_id]++;
printf("Axis %d: pos=%d\n", axis_id, current_pos[axis_id]);
}
/* 检查是否到达终点 */
if (current_pos[axis_id] >= target_pos[axis_id]) {
step_done[axis_id] = 1;
printf("Axis %d REACHED target=%d\n", axis_id, target_pos[axis_id]);
}
/* 三轴全部到达后停止 */
if (step_done[0] && step_done[1] && step_done[2]) {
printf("ALL AXES DONE\n");
vTaskDelete(NULL);
}
}
}
/* 插补周期定时器回调:每10ms释放一次节拍信号量 */
void tick_timer_callback(TimerHandle_t xTimer) {
/* 释放节拍信号量,唤醒三个轴任务各执行一步 */
xSemaphoreGive(tick_sem);
}
int main(void) {
/* 创建信号量 */
start_sem = xSemaphoreCreateBinary();
tick_sem = xSemaphoreCreateCounting(3, 0); /* 计数信号量,允许3个任务各取一次 */
/* 创建三个轴任务 */
int axis_ids[AXIS_COUNT] = {0, 1, 2};
for (int i = 0; i < AXIS_COUNT; i++) {
xTaskCreate(axis_task, "axis", 128, &axis_ids[i], 1, NULL);
}
/* 创建插补周期定时器,10ms周期 */
TimerHandle_t tick_timer = xTimerCreate("tick", pdMS_TO_TICKS(10), pdTRUE, NULL, tick_timer_callback);
xTimerStart(tick_timer, 0);
/* 启动调度器前先启动定时器,然后释放启动信号量 */
xTimerStart(tick_timer, 0);
vTaskDelay(pdMS_TO_TICKS(20)); /* 等待任务就绪 */
/* 释放启动信号量,三轴同时开始运动 */
xSemaphoreGive(start_sem);
/* 启动调度器 */
vTaskStartScheduler();
/* 正常不会执行到这里 */
return 0;
}
运行与验证
环境依赖:FreeRTOS内核(版本10.x及以上),支持多任务和信号量机制。在桌面模拟环境(如FreeRTOS Windows模拟器)或嵌入式平台(如STM32 + FreeRTOS)均可运行。
编译运行:
# 以FreeRTOS模拟器为例
make
./rtos_simulator
验证方法:观察串口或控制台输出,满足以下条件即判定通过:
- 三个轴任务几乎同时打印第一条位置信息(时间差小于一个插补周期10ms);
- 各轴按相同节拍递增,每10ms各轴各前进一步;
- 最终输出顺序为:各轴依次打印"REACHED"(因目标步数不同,先到终点的轴先打印),最后打印"ALL AXES DONE";
- 三轴到达终点的时间差不超过一个插补周期(10ms),即同步到达。
预期输出示例(节选,与代码逻辑严格一致):
Axis 0: pos=1
Axis 1: pos=1
Axis 2: pos=1
Axis 0: pos=2
Axis 1: pos=2
Axis 2: pos=2
...
Axis 2 REACHED target=80
Axis 0 REACHED target=100
Axis 1 REACHED target=150
ALL AXES DONE
关键说明
本实现使用计数信号量作为插补节拍,容量设为3,确保每个周期三个轴任务各取一次信号量后同步推进。启动信号量保证三轴在就绪后同时开始第一个插补周期。若需实际驱动电机,将current_pos[axis_id]++替换为对应轴的步进脉冲输出函数即可,同步逻辑不变。