逐点比较法(一):基本原理与判别函数、进给方向与偏差计算、终点判别方法
各位同学,今天咱们正式进入插补算法的核心内容。逐点比较法,这个名字听起来有点绕,但说白了,它就像你走路时每走一步都要低头看看脚下——「我走偏了没有?该往哪边调整?」
我在刚入行做数控系统时,第一个接触的插补算法就是它。那时候没有现在这么多高级芯片,全靠逻辑电路硬算。你想想看,一个简单的直线插补,CPU 要在一个中断周期内完成偏差计算、方向判断、脉冲输出……嗯,逐点比较法就是那个时代的经典产物。
基本原理:走一步,看一步
逐点比较法的核心思想只有八个字:边运动,边比较,边修正。
具体来说,刀具每走一步,系统就计算一下当前位置与理想轨迹之间的偏差。如果偏差为正,说明刀具偏在轨迹的一侧;如果偏差为负,说明偏在另一侧。然后根据偏差符号,决定下一步往哪个方向走。
核心逻辑闭环:
- 偏差判别:当前点偏离理想轨迹了吗?
- 进给控制:该往哪个方向走一步?
- 偏差计算:走完这一步,新偏差是多少?
- 终点判断:到终点了没?没到就回到第1步。
这四步循环往复,直到刀具到达终点。说白了,就是「走一步看一步」的闭环控制。我当年调试第一台三轴雕刻机时,就是用这个逻辑手写了一段直线插补,结果跑出来的轨迹锯齿感很明显——后来才发现是终点判断的精度没处理好。这个坑后面我会细说。
判别函数:如何量化「偏了」?
要判断刀具是否偏离轨迹,首先得有个数学工具。对于直线插补,我们引入一个判别函数 F。
假设我们要插补一条从原点 O(0,0) 到终点 A(xe, ye) 的直线。当前刀具位置为 P(xi, yi)。那么判别函数定义为:
F = x_e * y_i - y_e * x_i
这个公式怎么理解?其实它就是向量 OP 与 OA 的叉积。叉积的正负,反映了 P 点在 OA 的哪一侧。
- F = 0:刀具正好在直线上(理想情况)
- F > 0:刀具在直线上方(或左侧,取决于坐标系定义)
- F < 0:刀具在直线下方(或右侧)
你可能会问:「为什么非要用叉积?用距离不行吗?」
原因很简单——叉积只有乘法和减法,没有开方,没有除法。在早期的数控系统里,CPU 算个开方要几十个周期,而乘减运算一个周期就能搞定。这就是工程上的取舍:用计算量换精度。
进给方向:偏差说了算
有了判别函数 F,进给方向就很好定了。以第一象限直线为例:
| 判别函数 F | 进给方向 | 说明 |
|---|---|---|
| F ≥ 0 | +X 方向 | 刀具偏上,向 X 正方向拉回 |
| F < 0 | +Y 方向 | 刀具偏下,向 Y 正方向拉回 |
为什么 F≥0 时走 +X?你想想看,当 F>0 时,刀具在直线上方,说明 Y 方向走多了,下一步就该多走 X,把偏差拉回来。这就是逐点比较法的精髓——每次只修正一个方向,用阶梯折线逼近理想直线。
我的经验: 实际项目中,进给方向判断要特别注意象限切换。第一象限和第三象限的符号逻辑是相反的。我曾经在写四象限通用插补时,漏了一个符号取反,结果刀具在第三象限直接往反方向跑……那次差点把工件干废。
偏差计算:递推才是王道
每次走完一步,都要重新计算 F 值。但如果每次都按原始公式 F = xe * yi - ye * xi 硬算,那就太笨了——每次都要做两次乘法,太浪费。
这里有个漂亮的优化:递推公式。
假设当前偏差为 Fi,走一步后的新偏差为 Fi+1:
- 如果走 +X 方向:xi+1 = xi + 1,y 不变
Fi+1 = xe * yi - ye * (xi + 1)
= (xe * yi - ye * xi) - ye
= Fi - ye - 如果走 +Y 方向:yi+1 = yi + 1,x 不变
Fi+1 = xe * (yi + 1) - ye * xi
= (xe * yi - ye * xi) + xe
= Fi + xe
看到了吗?递推之后,每次只需要做一次加法或减法!乘法消失了。这在早期的 8 位单片机上是质的飞跃。
// 第一象限直线插补递推核心代码
int F = 0; // 初始偏差
int x = 0, y = 0; // 当前位置
int xe = 100, ye = 80; // 终点坐标
while (!isFinished(x, y, xe, ye)) {
if (F >= 0) {
// 走 +X
x++;
F -= ye; // 递推:F_new = F_old - ye
outputPulseX();
} else {
// 走 +Y
y++;
F += xe; // 递推:F_new = F_old + xe
outputPulseY();
}
}
注意: 递推公式只适用于单位步长(每次走 1 个脉冲当量)。如果你的系统步长不是 1,记得把 xe、ye 换成对应的脉冲数。我曾经见过有人直接把物理坐标代进去,结果递推出来的偏差完全不对——因为单位没统一。
终点判别:别走过头了
终点判别看似简单,但坑不少。常用的方法有三种:
| 方法 | 原理 | 优缺点 |
|---|---|---|
| 总步数法 | 总步数 N = xe + ye,每走一步减 1 | 简单可靠,最常用 |
| 坐标比较法 | 判断 x == xe && y == ye | 直观,但容易因累积误差无法精确到达 |
| 偏差归零法 | 判断 F == 0 且到达终点附近 | 精度高,但实现复杂 |
我个人最推荐总步数法。为什么?因为它在原理上就保证了「走够步数就停」,不会因为累积误差而多走或少走。你想想看,如果终点是 (100, 80),总步数就是 180 步。不管中间怎么偏,走完 180 步,刀具一定在终点附近——误差最多一个脉冲当量。
终点判别的避坑指南:
- 总步数法要小心整数溢出——如果 xe+ye 超过 65535(16 位),记得用 32 位变量。
- 坐标比较法要加一个「容差窗口」:|x - xe| ≤ 1 && |y - ye| ≤ 1,防止因舍入误差永远到不了。
- 偏差归零法只适用于直线终点恰好在网格点上时,否则 F 永远不会归零。
完整流程:一张图看懂
下面我用一张 SVG 流程图,把逐点比较法的完整循环展示出来。你可以把它当作调试时的「路线图」。
这张图把整个循环串起来了。你调试的时候,可以对照这个流程,一步步看变量怎么变。我当年调试时,就是在纸上画了这么一张图,然后手动模拟了十几步,才彻底搞懂递推的规律。
小结:逐点比较法的灵魂
回顾一下,逐点比较法的核心就三件事:
- 判别函数:用叉积判断刀具在轨迹的哪一侧
- 递推计算:用加减法代替乘法,大幅降低计算量
- 终点判别:总步数法最稳妥,别在终点判断上省变量