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 知识体系总览
下面这张图把圆弧插补的核心逻辑串起来了,从偏差计算到进给决策,再到递推优化,一目了然:
从这张图可以清楚看到,圆弧插补是一个闭环迭代过程:先算偏差,再判方向,然后根据象限决定具体进给轴,最后用递推公式更新偏差,直到到达终点。每一步都是确定的,没有浮点运算,非常适合嵌入式实时控制。