插补算法原理解析与性能优化 - 第2节
逐点比较法(一):基本原理与判别函数
各位同学,今天咱们来聊聊插补算法里最经典、也最直观的一种——逐点比较法。
我记得刚入行那会儿,带我的老工程师跟我说:“你先把逐点比较法吃透,后面那些复杂的插补算法,理解起来就顺了。” 当时我不太信,后来做了十几年运动控制,回头一看,还真是这么回事。
逐点比较法,说白了就是“走一步,看一步”。每走一步,都算算当前位置跟理想轨迹差了多少,然后决定下一步往哪走。它不需要复杂的三角函数计算,全是加减法,非常适合早期的硬件实现。即便在今天,在一些对成本敏感、对实时性要求高的嵌入式系统里,它依然有它的用武之地。
核心思想:步步为营,逼近目标
想象一下,你要在数控机床上铣出一条45度的斜线。刀具不能真的斜着走,它只能沿着X轴或Y轴方向,一步一步地移动。逐点比较法要解决的,就是“下一步该走X还是走Y”这个问题。
它的逻辑很简单:
- 比较:计算刀具当前位置与理想直线之间的偏差。
- 判断:根据偏差的正负,决定下一步的进给方向。
- 进给:沿判断出的方向走一步。
- 计算:更新新位置的偏差值。
- 循环:重复以上步骤,直到到达终点。
你想想看,这个过程像不像一个盲人走直线?他每走一步,都要用手杖探一下,看看自己是不是偏了,然后调整方向。逐点比较法就是那个“手杖”。
核心要点:逐点比较法的本质,是用折线去逼近直线。每一步的误差都不会超过一个脉冲当量(也就是一步的距离)。
判别函数:衡量偏差的标尺
要实现“比较”,首先得有个“标尺”。这个标尺就是判别函数。
假设我们要插补一条从原点 (0,0) 到终点 (Xe, Ye) 的直线。对于任意一个动点 P(x, y),我们怎么判断它在直线的哪一侧?
这里有个经典的数学工具:直线方程。
过原点的直线方程为:Y = (Ye / Xe) * X
把它改写一下,变成:Xe * Y - Ye * X = 0
这个等式左边,就是我们的判别函数 F:
F = Xe * y - Ye * x
这个 F 值,能告诉我们什么?
- F = 0:动点在直线上。
- F > 0:动点在直线上方(对于第一象限的直线而言)。
- F < 0:动点在直线下方。
嗯,这里要注意,这个“上方”和“下方”是相对于直线的方向而言的。对于不同象限的直线,进给规则会有所不同,但核心的判别逻辑是一样的。
个人经验:我在调试一个老式的步进电机系统时,发现判别函数的计算精度直接影响加工表面的粗糙度。当时用的是16位整数,结果在长距离插补时,累积误差把工件给干废了。后来我换成32位计算,问题才解决。所以,数据位宽的选择,是工程实现里的一个关键点。
直线插补的偏差递推公式推导
如果每走一步,都要重新算一遍 F = Xe*y - Ye*x,那计算量就太大了。毕竟,这里面有两次乘法呢。在早期的8位单片机时代,乘法可是个“奢侈”的操作。
所以,我们需要一个更聪明的办法——递推。
递推的思想很简单:利用上一步的偏差值,通过简单的加减法,计算出下一步的偏差值。
咱们分两种情况来推导:
情况一:沿 X 方向进给(+X)
假设当前动点在 P(x, y),偏差为 F。现在,我们沿 X 轴正方向走一步,到达新点 P'(x+1, y)。
新点的偏差 F' 为:
F' = Xe * y - Ye * (x + 1)
= (Xe * y - Ye * x) - Ye
= F - Ye
看到了吗?新偏差 = 旧偏差 - Ye。一次减法,搞定!
情况二:沿 Y 方向进给(+Y)
同样,从 P(x, y) 沿 Y 轴正方向走一步,到达 P'(x, y+1)。
新点的偏差 F' 为:
F' = Xe * (y + 1) - Ye * x
= (Xe * y - Ye * x) + Xe
= F + Xe
同样简单:新偏差 = 旧偏差 + Xe。
我把这两个公式整理一下:
| 进给方向 | 偏差递推公式 | 计算量 |
|---|---|---|
| +X 方向 | F' = F - Ye |
1 次减法 |
| +Y 方向 | F' = F + Xe |
1 次加法 |
这就是逐点比较法最迷人的地方——把复杂的乘法,变成了简单的加减法。我当年第一次推导出这个结果时,心里就一个感觉:“真漂亮!”
避坑指南:我曾经在一个项目中,直接把递推公式写成了 F = F - Ye。这在数学上没问题,但在C语言里,如果 F 和 Ye 都是 int 类型,而 Ye 比 F 大,结果可能会溢出。所以,一定要根据你的最大行程,选择合适的数据类型,或者做饱和处理。
终点判别方法
光知道怎么走还不够,还得知道什么时候停下来。这就是终点判别。
常用的终点判别方法有三种,我按推荐程度排个序:
方法一:总步数法(最常用)
这个方法最直观。直线从起点到终点,在 X 方向需要走 |Xe| 步,在 Y 方向需要走 |Ye| 步。那么,总步数就是:
N = |Xe| + |Ye|
每走一步,N 就减 1。当 N = 0 时,说明走完了。
我个人习惯用这个方法,因为它简单、可靠、没有累积误差。你只需要一个计数器,每进给一次就减一,直到归零。
方法二:坐标比较法
每走一步,都把当前的坐标 (x, y) 和终点坐标 (Xe, Ye) 比较一下。如果 x == Xe && y == Ye,就停止。
这个方法逻辑上很直接,但有个问题:如果插补过程中有扰动,或者坐标值很大,比较起来会慢一些。而且,它需要同时比较两个坐标值,在硬件实现上比总步数法稍微复杂一点。
方法三:长轴判别法
这个方法只适用于直线插补。它利用了一个特性:在插补过程中,走完步数较多的那个轴(长轴),另一个轴(短轴)也必然走完。
比如,对于从 (0,0) 到 (100, 50) 的直线,X 轴是长轴(需要走100步)。我们只需要判断 X 轴是否走到了 100 就行。Y 轴走到 50 是必然的。
这个方法可以节省一个比较器,但只适用于直线,不适用于圆弧。所以,通用性不如总步数法。
我把这三种方法的特点总结一下:
| 方法名称 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 总步数法 | 计数器递减 | 简单、可靠、通用 | 需要额外一个计数器 |
| 坐标比较法 | 比较当前坐标与终点 | 逻辑直观 | 需要同时比较两个值,速度略慢 |
| 长轴判别法 | 只判断长轴是否走完 | 节省资源 | 仅适用于直线 |
在实际工程中,我几乎只用总步数法。它就像一把瑞士军刀,简单、可靠,能应对绝大多数场景。
好了,以上就是逐点比较法直线插补的核心内容。从判别函数的定义,到递推公式的推导,再到终点判别的选择,每一步都有它的道理。把这些基础打牢,后面学圆弧插补、多轴联动,就会轻松很多。