插补算法原理解析与性能优化 - 第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 是必然的。

这个方法可以节省一个比较器,但只适用于直线,不适用于圆弧。所以,通用性不如总步数法。

我把这三种方法的特点总结一下:

方法名称 实现方式 优点 缺点
总步数法 计数器递减 简单、可靠、通用 需要额外一个计数器
坐标比较法 比较当前坐标与终点 逻辑直观 需要同时比较两个值,速度略慢
长轴判别法 只判断长轴是否走完 节省资源 仅适用于直线

在实际工程中,我几乎只用总步数法。它就像一把瑞士军刀,简单、可靠,能应对绝大多数场景。

好了,以上就是逐点比较法直线插补的核心内容。从判别函数的定义,到递推公式的推导,再到终点判别的选择,每一步都有它的道理。把这些基础打牢,后面学圆弧插补、多轴联动,就会轻松很多。

逐点比较法直线插补核心逻辑 开始插补 初始化:F=0, N=|Xe|+|Ye| 判断 F 值 F ≥ 0 ? 是 (F≥0) 否 (F<0) +X 方向进给 +Y 方向进给 F' = F - Ye F' = F + Xe N = N - 1 N = N - 1 N == 0 ? 是 插补结束 否,继续循环 核心:判别函数决定方向,递推公式简化计算,总步数法判断终点