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]++替换为对应轴的步进脉冲输出函数即可,同步逻辑不变。