2. 圆弧插补基础实现
任务目标
实现二维平面内的圆弧插补器,支持顺时针与逆时针方向,输入圆弧起点、终点、圆心及方向,输出沿圆弧路径各轴(X/Y)的步进脉冲序列。
实现原理
采用逐点比较法(偏差判别法)实现圆弧插补。核心思想:根据当前点与理想圆弧的偏差决定进给方向,每步仅沿X或Y轴移动一个脉冲当量,逐步逼近目标圆弧。偏差计算基于圆弧方程,通过起点坐标与圆心坐标确定初始偏差,每走一步后按公式递推更新偏差,直至到达终点。
关键代码
// arc_interpolator.h
#ifndef ARC_INTERPOLATOR_H
#define ARC_INTERPOLATOR_H
#include <stdint.h>
#include <stdbool.h>
typedef struct {
int32_t x0, y0; // 起点坐标(脉冲数)
int32_t x1, y1; // 终点坐标(脉冲数)
int32_t cx, cy; // 圆心坐标(脉冲数)
bool clockwise; // true=顺时针, false=逆时针
} ArcConfig;
typedef struct {
int32_t x, y; // 当前点坐标
int32_t f; // 偏差值
int32_t x_abs, y_abs; // 相对圆心的坐标
int32_t step_count; // 已走步数
int32_t total_steps; // 预估最大步数(防死循环)
bool finished;
} ArcInterpolator;
void arc_init(ArcInterpolator *it, const ArcConfig *cfg);
bool arc_step(ArcInterpolator *it, bool *step_x, bool *step_y, bool *dir_x, bool *dir_y);
#endif // ARC_INTERPOLATOR_H
// arc_interpolator.c
#include "arc_interpolator.h"
#include <stdlib.h>
static int32_t abs32(int32_t v) { return v < 0 ? -v : v; }
void arc_init(ArcInterpolator *it, const ArcConfig *cfg) {
it->x = cfg->x0;
it->y = cfg->y0;
it->x_abs = cfg->x0 - cfg->cx;
it->y_abs = cfg->y0 - cfg->cy;
// 初始偏差:F = (x-xc)^2 + (y-yc)^2 - R^2,起点在圆上,F=0
it->f = 0;
it->step_count = 0;
// 预估最大步数:沿两轴方向总脉冲数之和(保守上界)
int32_t dx = abs32(cfg->x1 - cfg->x0);
int32_t dy = abs32(cfg->y1 - cfg->y0);
it->total_steps = (dx + dy) * 4 + 1000; // 安全余量
it->finished = false;
}
bool arc_step(ArcInterpolator *it, bool *step_x, bool *step_y, bool *dir_x, bool *dir_y) {
if (it->finished) return false;
if (it->step_count++ > it->total_steps) {
it->finished = true; // 防死循环保护
return false;
}
// 判断是否到达终点
if (it->x == it->x1 && it->y == it->y1) {
it->finished = true;
return false;
}
*step_x = false;
*step_y = false;
*dir_x = false;
*dir_y = false;
int32_t x = it->x_abs;
int32_t y = it->y_abs;
int32_t f = it->f;
// 根据方向选择进给规则
if (it->clockwise) {
// 顺时针圆弧:F>=0 走 -X(或 +Y 取决于象限),此处用标准逐点比较法
// 简化实现:按第一象限顺时针推导,其他象限通过坐标变换处理
// 为保持代码简洁,此处采用通用判别法:
// 顺时针:F>=0 时走 -Y 方向(若在圆外),F<0 走 +X
// 实际需根据所在象限调整,这里给出完整四象限处理
if (x >= 0 && y >= 0) {
// 第一象限顺时针:F>=0 走 -X, F<0 走 +Y
if (f >= 0) {
*step_x = true; *dir_x = false; // -X
it->x -= 1;
it->x_abs -= 1;
it->f = f - 2*x + 1;
} else {
*step_y = true; *dir_y = true; // +Y
it->y += 1;
it->y_abs += 1;
it->f = f + 2*y + 1;
}
} else if (x < 0 && y >= 0) {
// 第二象限顺时针:F>=0 走 +Y, F<0 走 -X
if (f >= 0) {
*step_y = true; *dir_y = true;
it->y += 1;
it->y_abs += 1;
it->f = f + 2*y + 1;
} else {
*step_x = true; *dir_x = false;
it->x -= 1;
it->x_abs -= 1;
it->f = f - 2*x + 1;
}
} else if (x < 0 && y < 0) {
// 第三象限顺时针:F>=0 走 +X, F<0 走 -Y
if (f >= 0) {
*step_x = true; *dir_x = true;
it->x += 1;
it->x_abs += 1;
it->f = f + 2*x + 1;
} else {
*step_y = true; *dir_y = false;
it->y -= 1;
it->y_abs -= 1;
it->f = f - 2*y + 1;
}
} else {
// 第四象限顺时针:F>=0 走 -Y, F<0 走 +X
if (f >= 0) {
*step_y = true; *dir_y = false;
it->y -= 1;
it->y_abs -= 1;
it->f = f - 2*y + 1;
} else {
*step_x = true; *dir_x = true;
it->x += 1;
it->x_abs += 1;
it->f = f + 2*x + 1;
}
}
} else {
// 逆时针圆弧
if (x >= 0 && y >= 0) {
// 第一象限逆时针:F>=0 走 +Y, F<0 走 -X
if (f >= 0) {
*step_y = true; *dir_y = true;
it->y += 1;
it->y_abs += 1;
it->f = f + 2*y + 1;
} else {
*step_x = true; *dir_x = false;
it->x -= 1;
it->x_abs -= 1;
it->f = f - 2*x + 1;
}
} else if (x < 0 && y >= 0) {
// 第二象限逆时针:F>=0 走 -X, F<0 走 +Y
if (f >= 0) {
*step_x = true; *dir_x = false;
it->x -= 1;
it->x_abs -= 1;
it->f = f - 2*x + 1;
} else {
*step_y = true; *dir_y = true;
it->y += 1;
it->y_abs += 1;
it->f = f + 2*y + 1;
}
} else if (x < 0 && y < 0) {
// 第三象限逆时针:F>=0 走 -Y, F<0 走 +X
if (f >= 0) {
*step_y = true; *dir_y = false;
it->y -= 1;
it->y_abs -= 1;
it->f = f - 2*y + 1;
} else {
*step_x = true; *dir_x = true;
it->x += 1;
it->x_abs += 1;
it->f = f + 2*x + 1;
}
} else {
// 第四象限逆时针:F>=0 走 +X, F<0 走 -Y
if (f >= 0) {
*step_x = true; *dir_x = true;
it->x += 1;
it->x_abs += 1;
it->f = f + 2*x + 1;
} else {
*step_y = true; *dir_y = false;
it->y -= 1;
it->y_abs -= 1;
it->f = f - 2*y + 1;
}
}
}
return true; // 产生了一个脉冲
}
测试与验证
// test_arc.c
#include <stdio.h>
#include "arc_interpolator.h"
int main() {
// 测试1:第一象限1/4圆弧,圆心(0,0),起点(10,0),终点(0,10),逆时针
ArcConfig cfg = {10, 0, 0, 10, 0, 0, false};
ArcInterpolator it;
arc_init(&it, &cfg);
int steps = 0;
bool sx, sy, dx, dy;
while (arc_step(&it, &sx, &sy, &dx, &dy)) {
if (sx) printf("X%s ", dx ? "+" : "-");
if (sy) printf("Y%s ", dy ? "+" : "-");
steps++;
}
printf("\n逆时针圆弧步数: %d, 终点: (%d,%d)\n", steps, it.x, it.y);
// 测试2:顺时针半圆,圆心(0,0),起点(5,0),终点(-5,0)
ArcConfig cfg2 = {5, 0, -5, 0, 0, 0, true};
ArcInterpolator it2;
arc_init(&it2, &cfg2);
steps = 0;
while (arc_step(&it2, &sx, &sy, &dx, &dy)) {
if (sx) printf("X%s ", dx ? "+" : "-");
if (sy) printf("Y%s ", dy ? "+" : "-");
steps++;
}
printf("\n顺时针半圆步数: %d, 终点: (%d,%d)\n", steps, it2.x, it2.y);
return 0;
}
编译运行:gcc -o test_arc test_arc.c arc_interpolator.c && ./test_arc
验证标准
- 程序能正常编译运行,无死循环;
- 逆时针1/4圆弧从(10,0)走到(0,10),步数约为15~16步(半径10,每步1个脉冲当量,路径长度≈15.7);
- 顺时针半圆从(5,0)走到(-5,0),步数约为15~16步(半周长≈15.7);
- 输出脉冲序列中X/Y方向与圆弧走向一致,终点坐标与设定值误差不超过1个脉冲。
常见问题
- 若终点无法精确到达(圆弧半径非整数),插补器会在终点附近停止,误差不超过1个脉冲当量,属正常现象;
- 实际电机控制中,需将step_x/step_y脉冲信号接入定时器产生PWM,dir_x/dir_y接方向控制引脚;
- 本实现采用逐点比较法,适用于步进电机开环控制,若需更高精度可改用DDA或Bresenham圆弧算法。