3. 多轴联动任务同步机制

任务目标

在RTOS环境中,多个轴各自运行独立的插补任务,但要求它们在同一时刻开始运动,并在整个插补过程中保持步调一致。本案例实现一个基于事件标志组的同步启动机制,以及基于任务通知的逐段协调机制,确保多轴联动时不会出现某一轴超前或滞后。

核心机制说明

同步启动使用FreeRTOS事件标志组:主任务置位启动位,所有轴任务阻塞等待该位后同时就绪,由调度器按优先级统一调度,实现“尽量同时”启动。协调运行采用任务通知作为轻量级栅栏:每个插补段完成后,轴任务通知协调任务,协调任务等待全部轴到达后,再统一释放下一段的执行许可。这样即使各轴插补计算耗时不同,也不会累积偏差。

完整实现(FreeRTOS + 模拟插补)

#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include <stdio.h>
#include <string.h>

#define AXIS_COUNT     3
#define SEGMENT_COUNT  5
#define START_BIT      (1 << 0)

typedef struct {
    int axis_id;
    int position;
    int segment_done;
} axis_state_t;

static EventGroupHandle_t start_event;
static TaskHandle_t axis_tasks[AXIS_COUNT];
static TaskHandle_t coordinator_task;
static axis_state_t axis_states[AXIS_COUNT];

/* 模拟单段插补计算,耗时随轴号递增,验证协调机制 */
static void compute_segment(int axis_id, int seg) {
    volatile int delay = 1000 * (axis_id + 1);
    while (delay-- > 0) { }
    axis_states[axis_id].position += seg * 10;
}

static void axis_task(void *param) {
    int id = (int)param;
    axis_states[id].axis_id = id;
    axis_states[id].position = 0;

    /* 等待同步启动 */
    xEventGroupWaitBits(start_event, START_BIT, pdFALSE, pdTRUE, portMAX_DELAY);

    for (int seg = 1; seg <= SEGMENT_COUNT; seg++) {
        compute_segment(id, seg);

        /* 通知协调任务本段完成 */
        xTaskNotify(coordinator_task, (1 << id), eSetBits);

        /* 等待协调任务释放下一段 */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    }
}

static void coordinator_task(void *param) {
    (void)param;
    uint32_t bits;

    for (int seg = 1; seg <= SEGMENT_COUNT; seg++) {
        /* 等待所有轴完成当前段 */
        uint32_t all_done = (1 << AXIS_COUNT) - 1;
        do {
            bits = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
            all_done &= ~bits;
        } while (all_done != 0);

        /* 所有轴已到达,统一释放下一段 */
        for (int i = 0; i < AXIS_COUNT; i++) {
            xTaskNotifyGive(axis_tasks[i]);
        }
    }

    /* 打印最终位置,验证同步性 */
    for (int i = 0; i < AXIS_COUNT; i++) {
        printf("Axis %d final position: %d\n",
               axis_states[i].axis_id, axis_states[i].position);
    }
    vTaskDelete(NULL);
}

int main(void) {
    start_event = xEventGroupCreate();

    xTaskCreate(coordinator_task, "coord", 256, NULL, 2, &coordinator_task);

    for (int i = 0; i < AXIS_COUNT; i++) {
        xTaskCreate(axis_task, "axis", 256, (void *)i, 1, &axis_tasks[i]);
    }

    /* 置位启动事件 */
    xEventGroupSetBits(start_event, START_BIT);

    vTaskStartScheduler();
    return 0;
}

运行与验证

环境依赖:FreeRTOS内核(需提供heap_4.c内存管理),编译器建议使用gcc-arm-none-eabi或桌面模拟版FreeRTOS(如Win32模拟器)。

编译运行:将上述代码加入FreeRTOS示例工程,确保configSUPPORT_DYNAMIC_ALLOCATION为1,编译后烧录或在本机模拟运行。

通过条件:串口输出三行最终位置,且三行数值完全一致(本案例中均为150)。若任一轴数值偏小,说明该轴未完成全部段,协调机制失效。可通过调整compute_segment中的延迟值(各轴不同)来验证:即使各轴单段耗时不同,最终仍同步到达。

关键点说明

  • 事件标志组只保证“同时就绪”,实际启动顺序由RTOS调度器决定,但所有轴均在置位后第一个时间片内开始执行。
  • 任务通知在此处用作计数型信号量(xTaskNotifyGive/ulTaskNotifyTake),比二进制信号量更轻量,且无需创建额外内核对象。
  • 协调任务优先级(2)高于轴任务(1),确保释放许可后轴任务能及时被调度。