4. 逐点比较法(二):圆弧插补原理、象限处理、终点判别

好,咱们接着聊逐点比较法。上一章我们把直线插补讲透了,这一章轮到圆弧了。

说实话,圆弧插补比直线要稍微绕一点。为什么?因为直线只有一个方向偏差,而圆弧的偏差跟半径、象限都有关系。我刚开始做数控系统那会儿,第一次写圆弧插补就栽了个跟头——四个象限的符号处理错了,结果画出来的圆像个被踩扁的易拉罐。

嗯,咱们今天就把这个坑填平。

4.1 圆弧插补的基本原理

先想一个问题:怎么判断一个点是在圆内、圆外,还是在圆上?

很简单,看它到圆心的距离跟半径比一下就行。

假设我们要插补第一象限的逆圆(逆时针方向),圆心在原点,半径为 R。当前刀具位置是 P(x, y),那么偏差函数就是:

F = x² + y² - R²
  • F = 0:点在圆上,完美
  • F > 0:点在圆外,需要向圆内方向走
  • F < 0:点在圆内,需要向圆外方向走

那问题来了:向哪个方向走?这取决于圆弧的方向——是逆圆还是顺圆。

核心口诀(我个人习惯这么记):

  • 逆圆:F ≥ 0 走 -X,F < 0 走 +Y
  • 顺圆:F ≥ 0 走 -Y,F < 0 走 +X

说白了,偏差大就往里收,偏差小就往外扩。方向反了,圆就画歪了。

4.2 象限处理——四个象限的符号规则

第一象限搞定了,那第二、第三、第四象限呢?

你想想看,刀具在不同象限,坐标的符号不一样,进给方向自然也要跟着变。我见过不少新手直接套第一象限的公式,结果画出来的圆在象限交界处断开了。

咱们先看逆圆四个象限的进给规则:

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

再看顺圆的规则:

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

我的小技巧:别死记硬背。你只需要记住第一象限的规则,然后根据坐标轴的对称性推导其他象限。比如逆圆在第二象限,X 是负的,Y 是正的,进给方向自然要跟着坐标符号翻转。我曾经在调试一台五轴机床时,就是用这个对称性快速排查了一个象限处理 bug。

4.3 终点判别——怎么知道圆画完了?

圆弧插补的终点判别比直线复杂一些。直线可以用总步数,但圆弧呢?

常用的方法有三种:

  1. 总步数法:计算从起点到终点需要走的总步数,每走一步减一,减到零就停。
  2. 坐标比较法:每次进给后判断当前坐标是否到达终点坐标。
  3. 偏差值法:当偏差值 F 回到某个阈值范围内时认为到达终点。

我个人最常用的是总步数法。为什么?简单、可靠、不依赖坐标精度。

总步数的计算公式:

n = |Xe - Xs| + |Ye - Ys|

其中 (Xs, Ys) 是起点,(Xe, Ye) 是终点。注意,这里用的是曼哈顿距离,不是欧氏距离。因为逐点比较法每一步只走一个轴,曼哈顿距离正好等于总步数。

注意:总步数法在跨象限时可能会出问题。比如从第一象限走到第二象限,步数算对了,但实际路径可能因为象限切换而多走或少走。我曾经在一个项目中遇到过这个问题——圆画到最后差了一个脉冲,查了半天才发现是终点判别没考虑象限切换。

解决方案:在象限切换时重新计算剩余步数,或者改用坐标比较法做双重校验。

4.4 圆弧插补的完整流程

把上面这些串起来,一个完整的圆弧插补流程是这样的:

  1. 输入起点、终点、圆心、圆弧方向(逆圆/顺圆)
  2. 计算初始偏差值 F = Xs² + Ys² - R²
  3. 计算总步数 n = |Xe - Xs| + |Ye - Ys|
  4. 判断当前象限
  5. 根据象限和 F 值决定进给方向
  6. 更新坐标和偏差值
  7. 步数减一,判断是否到达终点
  8. 如果没到,回到步骤 4;如果到了,结束

下面我用一个 SVG 流程图把整个过程可视化一下:

圆弧插补完整流程图 开始 输入起点、终点、圆心、方向 计算 R、初始 F、总步数 n 判断当前象限 根据象限和 F 值 决定进给方向 更新坐标、偏差值 F 步数 n = n - 1 n == 0 ? 结束 是 否

4.5 代码实现——C 语言版圆弧插补

光说不练假把式。下面我给出一段完整的 C 语言实现,注释写得很清楚,你可以直接拿去用:

// 圆弧插补函数
// 参数:起点 (xs, ys),终点 (xe, ye),圆心 (cx, cy),方向 dir(0=逆圆,1=顺圆)
void arc_interpolate(float xs, float ys, float xe, float ye,
                     float cx, float cy, int dir) {
    float x = xs - cx;  // 相对坐标
    float y = ys - cy;
    float R2 = x*x + y*y;  // 半径平方
    float F = x*x + y*y - R2;  // 初始偏差

    // 计算总步数
    int n = (int)(fabs(xe - xs) + fabs(ye - ys));

    while (n > 0) {
        // 判断象限
        int quad = 0;
        if (x >= 0 && y >= 0) quad = 1;
        else if (x < 0 && y >= 0) quad = 2;
        else if (x < 0 && y < 0) quad = 3;
        else quad = 4;

        // 根据象限和方向决定进给
        int step_x = 0, step_y = 0;
        if (dir == 0) {  // 逆圆
            switch (quad) {
                case 1: step_x = (F >= 0) ? -1 : 0; step_y = (F >= 0) ? 0 : 1; break;
                case 2: step_x = (F >= 0) ? 0 : -1; step_y = (F >= 0) ? 1 : 0; break;
                case 3: step_x = (F >= 0) ? 1 : 0; step_y = (F >= 0) ? 0 : -1; break;
                case 4: step_x = (F >= 0) ? 0 : 1; step_y = (F >= 0) ? -1 : 0; break;
            }
        } else {  // 顺圆
            switch (quad) {
                case 1: step_x = (F >= 0) ? 0 : 1; step_y = (F >= 0) ? -1 : 0; break;
                case 2: step_x = (F >= 0) ? -1 : 0; step_y = (F >= 0) ? 0 : -1; break;
                case 3: step_x = (F >= 0) ? 0 : -1; step_y = (F >= 0) ? 1 : 0; break;
                case 4: step_x = (F >= 0) ? 1 : 0; step_y = (F >= 0) ? 0 : 1; break;
            }
        }

        // 更新坐标和偏差
        x += step_x;
        y += step_y;
        F = x*x + y*y - R2;
        n--;

        // 输出当前点(实际项目中这里发脉冲给驱动器)
        printf("Step: (%f, %f)\n", x + cx, y + cy);
    }
}

使用建议:这段代码是教学版,实际工程中你需要加上象限切换检测和终点双重校验。我一般在生产代码里还会加一个超时保护——如果步数走完了但偏差还没归零,强制终止并报错。这个习惯帮我避免过好几次刀具撞工件的事故。

4.6 常见问题与避坑指南

  • 象限边界处理:当刀具正好在坐标轴上时,属于哪个象限?我建议归到相邻象限中偏差较小的那个,或者单独处理轴上的情况。
  • 圆弧过象限:如果圆弧跨越多个象限,终点判别要特别小心。我曾经用总步数法没处理好,结果圆在象限交界处多走了两步,工件直接报废。
  • 偏差累积:浮点数运算会有误差,长时间插补后偏差可能漂移。建议每走若干步做一次强制校正。

一个血的教训:有一次我在调试一台三轴雕刻机,圆弧插补在第三象限总是偏一点。查了两天,最后发现是象限判断的条件写成了 x > 0 而不是 x >= 0,导致坐标轴上的点被漏判了。就这一个等号,耽误了两天工期。所以,边界条件一定要覆盖全。