4. 逐点比较法(三):圆弧插补原理,偏差计算公式推导,象限处理与过象限问题

好,咱们接着聊逐点比较法。前两章讲了直线插补,这一章轮到圆弧了。说实话,圆弧插补比直线要绕一点,但核心思路没变——还是那四个字:逐点比较。

你想想看,直线插补时,我们比较的是当前点与理想直线的偏离。圆弧呢?比较的是当前点到圆心的距离,跟理想半径比一比。说白了,就是看你是跑到了圆里面,还是圆外面。

核心思想:每走一步,算一次偏差,根据偏差决定下一步往哪个方向走。偏差为零,说明正好在圆弧上;偏差为正,说明在圆外;偏差为负,说明在圆内。

4.1 圆弧插补的偏差函数

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

圆弧上任意一点 (x, y) 满足:

x² + y² = R²

那么,对于当前刀具所在点 P(xᵢ, yⱼ),我们定义偏差函数:

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

这个 F 值就是判断依据:

  • F = 0:点在圆弧上
  • F > 0:点在圆弧外侧
  • F < 0:点在圆弧内侧

嗯,这里要注意:偏差的正负决定了进给方向。对于逆圆弧,F ≥ 0 时我们向 -X 方向走一步,F < 0 时向 +Y 方向走一步。为什么?因为要往圆弧靠拢嘛。

4.2 偏差递推公式推导

直接算 x² + y² - R² 当然可以,但每次都要算平方,计算量太大。我早期做嵌入式数控系统时,CPU 算力有限,平方运算简直是噩梦。所以必须用递推公式。

咱们分两种情况推导:

情况一:F ≥ 0,向 -X 方向走一步

新坐标:xᵢ₊₁ = xᵢ - 1,yⱼ 不变

新偏差:

Fᵢ₊₁,ⱼ = (xᵢ - 1)² + yⱼ² - R²
        = xᵢ² - 2xᵢ + 1 + yⱼ² - R²
        = (xᵢ² + yⱼ² - R²) - 2xᵢ + 1
        = Fᵢⱼ - 2xᵢ + 1

情况二:F < 0,向 +Y 方向走一步

新坐标:xᵢ 不变,yⱼ₊₁ = yⱼ + 1

新偏差:

Fᵢ,ⱼ₊₁ = xᵢ² + (yⱼ + 1)² - R²
        = xᵢ² + yⱼ² + 2yⱼ + 1 - R²
        = (xᵢ² + yⱼ² - R²) + 2yⱼ + 1
        = Fᵢⱼ + 2yⱼ + 1

你看,递推公式里只有加减法和移位操作,没有平方。这在老式单片机系统上跑起来飞快。我记得有一次调试一个 8 位机上的圆弧插补,用递推公式后,插补周期从 2ms 降到了 0.3ms,效果立竿见影。

个人习惯:我写圆弧插补时,会把初始偏差 F₀ 先算好。起点在圆弧上,所以 F₀ = 0。但如果起点不在圆弧上(比如从直线切过来),F₀ 就得老老实实算 x₀² + y₀² - R²。

4.3 象限处理

第一象限搞定了,其他象限呢?其实原理一样,只是进给方向变了。我整理了一张表,方便你对照:

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

你可能会问:这 8 种情况怎么记?我有个笨办法——记住第一象限逆圆,然后根据对称性推。但实际写代码时,我建议直接用查表法,把进给方向存成数组,运行时根据象限和方向索引。这样代码清晰,不容易出错。

4.4 过象限问题

过象限,是圆弧插补里最容易翻车的地方。什么叫过象限?就是圆弧从当前象限跨到相邻象限。比如从第一象限逆圆走到第二象限,中间要经过 90° 边界。

为什么会出问题?因为跨象限时,进给方向要变。如果程序没处理好,刀具会在边界上犹豫不决,甚至跑偏。

我曾经踩过的坑:有一次做雕刻机控制系统,加工一个整圆。第一象限跑得好好的,一到 90° 边界,电机突然抖了一下,工件上留下一道刀痕。查了两天才发现,是过象限时偏差递推公式里的坐标符号没更新,导致进给方向算错了。

解决过象限问题,通常有两种思路:

  1. 象限切换法:检测到当前点到达象限边界(x=0 或 y=0),立即切换象限标志,更新进给方向和偏差公式中的符号。
  2. 统一坐标法:把坐标取绝对值,始终按第一象限的规则算,进给方向由象限和圆弧方向共同决定。这种方法代码更简洁,但逻辑要小心。

我个人更推荐第二种。你看,不管在哪个象限,偏差递推公式的形式是一样的——F ± 2|x| + 1 或 F ± 2|y| + 1。区别只在于加减号和进给轴。把坐标取绝对值后,递推公式统一了,进给方向单独查表,逻辑分离,调试起来方便很多。

4.5 圆弧插补流程图

下面这张图是我当年画给团队培训用的,把整个流程串了一遍:

圆弧插补流程图(逐点比较法) 开始 初始化:起点(x₀,y₀),终点(xₑ,yₑ) 计算初始偏差 F₀ = 0,象限标志 q 是否到达终点? (xᵢ==xₑ && yⱼ==yₑ) 结束 是 否 判断偏差 Fᵢⱼ F≥0 或 F<0 F ≥ 0:沿进给方向走一步 更新坐标:xᵢ₊₁ = xᵢ ± 1 或 yⱼ₊₁ = yⱼ ± 1 F < 0:沿进给方向走一步 更新坐标:xᵢ₊₁ = xᵢ ± 1 或 yⱼ₊₁ = yⱼ ± 1 递推更新偏差 Fᵢ₊₁,ⱼ 或 Fᵢ,ⱼ₊₁ 检查象限边界,必要时切换象限 循环

流程其实不复杂:初始化 → 判终点 → 判偏差 → 走步 → 更新偏差 → 回环。关键就两点:一是偏差递推公式要写对,二是过象限时坐标符号和进给方向要同步切换。

4.6 代码示例

最后,给一段我常用的圆弧插补核心代码。这是第一象限逆圆的实现,其他象限加上象限判断逻辑即可:

// 第一象限逆圆插补
// 起点(x0,y0),终点(xe,ye),圆心在原点
void arc_interp_ccw_q1(int x0, int y0, int xe, int ye) {
    int x = x0, y = y0;
    int F = 0;  // 起点在圆弧上,偏差为0

    while (1) {
        // 判断是否到达终点
        if (x == xe && y == ye) break;

        if (F >= 0) {
            // 向 -X 方向走一步
            x--;
            F = F - 2*x - 1;  // 注意:这里 x 已经更新,所以是 -2*x - 1
        } else {
            // 向 +Y 方向走一步
            y++;
            F = F + 2*y - 1;  // 注意:这里 y 已经更新,所以是 +2*y - 1
        }

        // 输出脉冲或更新位置
        // output_step();
    }
}

一个小细节:递推公式里,F = F - 2x - 1 中的 x 是更新后的值。如果你先算 F 再更新坐标,公式要写成 F = F - 2*(x+1) + 1。两种写法等价,但容易搞混。我习惯先更新坐标再算 F,这样公式更直观。