3. 逐点比较法(二):圆弧插补的偏差计算与进给方向

上一章我们聊了直线插补,说白了就是让刀具走一条笔直的线。但实际加工中,圆弧才是最常见的轮廓——倒角、圆孔、弧形曲面,哪个都离不开它。逐点比较法处理圆弧的思路,其实和直线很像,但偏差计算要绕个弯子。

我个人习惯把圆弧插补看作「动态调整半径误差」的过程。你想想看,刀具每走一步,我们都要问一句:现在这个点,是在圆弧里面、外面,还是正好在圆弧上?

3.1 圆弧偏差的计算公式

假设我们要加工第一象限的逆圆弧。起点是 A(x₀, y₀),终点是 B(xₑ, yₑ),圆心在原点。圆弧半径 R 是已知的。

对于任意动点 P(xᵢ, yⱼ),它到圆心的距离平方是 xᵢ² + yⱼ²。我们定义偏差函数:

Fᵢⱼ = xᵢ² + yⱼ² - R²

这个公式很直观:

  • Fᵢⱼ = 0:点在圆弧上,完美。
  • Fᵢⱼ > 0:点在圆弧外侧,需要向圆内方向修正。
  • Fᵢⱼ < 0:点在圆弧内侧,需要向圆外方向修正。

核心要点:圆弧插补的偏差计算,本质上是在比较「当前点到圆心的实际距离」与「理想半径」的平方差。用平方是为了避免开方运算,提高实时性。

3.2 进给方向的确定

偏差算出来了,下一步就是决定往哪个方向走。这里有个容易混淆的地方——不同象限、顺圆还是逆圆,进给方向都不一样。

先看第一象限逆圆弧的情况:

  • F ≥ 0:点在圆外或圆上,向 -X 方向走一步(靠近圆心)。
  • F < 0:点在圆内,向 +Y 方向走一步(远离圆心)。

为什么会这样?你画个第一象限的逆圆弧就明白了。圆弧是逆时针走的,外侧点需要往左拉,内侧点需要往上推。我在项目中遇到过有人把方向搞反了,结果刀具直接切到了工件外面……嗯,那场面挺尴尬的。

3.3 不同象限的圆弧插补处理

实际加工中,圆弧可能出现在任何象限。每个象限的进给方向都需要单独处理。我整理了一张表,方便你对照:

象限 圆弧方向 F ≥ 0 进给 F < 0 进给
第一象限 逆圆 -X +Y
第一象限 顺圆 -Y +X
第二象限 逆圆 +Y -X
第二象限 顺圆 +X +Y
第三象限 逆圆 +X -Y
第三象限 顺圆 +Y -X
第四象限 逆圆 -Y +X
第四象限 顺圆 -X -Y

我的小技巧:记不住没关系。你只要记住一个原则——F ≥ 0 时向圆心方向走,F < 0 时远离圆心方向走。然后根据当前象限和圆弧方向,判断哪个坐标轴方向是「向圆心」或「离圆心」即可。

3.4 偏差计算的递推公式

每次计算 x² + y² - R² 虽然准确,但涉及乘法运算,在早期数控系统里效率不高。所以实际工程中,我们用的是递推公式——根据上一步的偏差和进给方向,快速算出下一步的偏差。

假设当前动点为 (xᵢ, yⱼ),偏差为 Fᵢⱼ:

  • 向 -X 方向走一步:新点 (xᵢ - 1, yⱼ)
    Fᵢ₊₁,ⱼ = (xᵢ - 1)² + yⱼ² - R² = Fᵢⱼ - 2xᵢ + 1
  • 向 +Y 方向走一步:新点 (xᵢ, yⱼ + 1)
    Fᵢ,ⱼ₊₁ = xᵢ² + (yⱼ + 1)² - R² = Fᵢⱼ + 2yⱼ + 1
  • 向 +X 方向走一步:新点 (xᵢ + 1, yⱼ)
    Fᵢ₊₁,ⱼ = (xᵢ + 1)² + yⱼ² - R² = Fᵢⱼ + 2xᵢ + 1
  • 向 -Y 方向走一步:新点 (xᵢ, yⱼ - 1)
    Fᵢ,ⱼ₋₁ = xᵢ² + (yⱼ - 1)² - R² = Fᵢⱼ - 2yⱼ + 1

你看,递推公式只用到了加减法和移位操作,没有乘法。这在老式的 8 位单片机时代简直是救命的设计。即使现在用 ARM 处理器,递推也比直接算平方快得多。

注意:递推公式中的坐标值 xᵢ 和 yⱼ 是当前动点的绝对坐标,不是相对坐标。每次进给后,记得更新坐标值,否则后续递推会出错。我曾经调试一个圆弧插补程序,发现偏差越算越离谱,查了半天才发现是坐标没更新——这种低级错误,犯过一次就再也不会忘了。

3.5 实例分析

我们用一个具体例子走一遍流程。加工第一象限逆圆弧,起点 A(4, 0),终点 B(0, 4),半径 R = 4。

初始状态:x₀ = 4, y₀ = 0, F₀ = 4² + 0² - 4² = 0

步数 当前点 (x, y) 偏差 F 进给方向 新坐标 新偏差(递推)
0 (4, 0) 0 -X (3, 0) 0 - 2×4 + 1 = -7
1 (3, 0) -7 +Y (3, 1) -7 + 2×0 + 1 = -6
2 (3, 1) -6 +Y (3, 2) -6 + 2×1 + 1 = -3
3 (3, 2) -3 +Y (3, 3) -3 + 2×2 + 1 = 2
4 (3, 3) 2 -X (2, 3) 2 - 2×3 + 1 = -3
5 (2, 3) -3 +Y (2, 4) -3 + 2×3 + 1 = 4
6 (2, 4) 4 -X (1, 4) 4 - 2×2 + 1 = 1
7 (1, 4) 1 -X (0, 4) 1 - 2×1 + 1 = 0

走到第 7 步时,坐标到达终点 (0, 4),偏差归零,插补结束。你看,整个过程只用了加减法,没有一次乘法运算。

3.6 代码实现

下面是我写的一个 C 语言实现,支持第一象限逆圆弧插补。代码风格偏工程化,注释写得很详细:

#include <stdio.h>

// 圆弧插补状态结构体
typedef struct {
    int x;      // 当前 X 坐标
    int y;      // 当前 Y 坐标
    int F;      // 当前偏差
    int x_end;  // 终点 X
    int y_end;  // 终点 Y
} ArcInterpolator;

// 初始化圆弧插补器
void arc_init(ArcInterpolator *arc, int x0, int y0, int xe, int ye) {
    arc->x = x0;
    arc->y = y0;
    arc->F = x0 * x0 + y0 * y0 - x0 * x0 - y0 * y0;  // 初始偏差为 0
    arc->x_end = xe;
    arc->y_end = ye;
}

// 执行一步插补,返回 1 表示到达终点,0 表示继续
int arc_step(ArcInterpolator *arc) {
    // 检查是否到达终点
    if (arc->x == arc->x_end && arc->y == arc->y_end) {
        return 1;
    }

    // 第一象限逆圆弧的进给逻辑
    if (arc->F >= 0) {
        // 向 -X 方向进给
        arc->F = arc->F - 2 * arc->x + 1;
        arc->x--;
    } else {
        // 向 +Y 方向进给
        arc->F = arc->F + 2 * arc->y + 1;
        arc->y++;
    }

    return 0;
}

int main() {
    ArcInterpolator arc;
    arc_init(&arc, 4, 0, 0, 4);

    printf("起点: (%d, %d)\n", arc.x, arc.y);
    int step = 0;
    while (!arc_step(&arc)) {
        step++;
        printf("第 %d 步: (%d, %d), F = %d\n", step, arc.x, arc.y, arc.F);
    }
    printf("到达终点: (%d, %d)\n", arc.x, arc.y);

    return 0;
}

工程建议:实际项目中,我会把象限判断和圆弧方向判断封装成查表函数,而不是写一堆 if-else。这样代码更简洁,也更容易扩展。另外,终点判断不要只比较坐标相等,最好加一个「剩余步数」计数器,防止因累积误差导致无限循环。

3.7 知识体系总览

下面这张图把圆弧插补的核心逻辑串起来了,从偏差计算到进给决策,再到递推优化,一目了然:

圆弧插补逐点比较法知识体系 起点/终点/半径 偏差计算 F = x² + y² - R² F ≥ 0(圆外/圆上) 向圆心方向进给 F < 0(圆内) 远离圆心方向进给 象限判断 确定具体进给轴方向 象限判断 确定具体进给轴方向 递推更新偏差 Fₙ₊₁ = Fₙ ± 2x/y + 1

从这张图可以清楚看到,圆弧插补是一个闭环迭代过程:先算偏差,再判方向,然后根据象限决定具体进给轴,最后用递推公式更新偏差,直到到达终点。每一步都是确定的,没有浮点运算,非常适合嵌入式实时控制。