判错一段,指尖弹出 37 毫米——两个编码器装进机器人手指,大多数人只用了一个半

首发于公众号「如是有为」 · 查看原文

判错一段,指尖弹出 37 毫米——两个编码器装进机器人手指,大多数人只用了一个半

·判错一段,关节跳 24°、指尖弹出 37 毫米——而这只手的标称精度是 ±0.2 毫米。

·容错窗 ±12°、噪声只有 ±0.15°、余量 80 倍;但边界是硬判决,路过边界时余量就是 0。

·加了迟滞窗也没用:腱一变形,边界就漂——你在用一个常数,去追一个变量。

我见过一个机器人关节的驱动代码。

光是压一根毛刺的 if-else,写了两百多行。

加一个迟滞窗。

加一个"跳变超过阈值就丢弃"。

加一个"连续 N 帧确认才换段"。

加一个"低速时不换段"的例外。

再给这个例外,加一个例外。

写这段代码的工程师,每改一版都要重新标一遍阈值。换个负载、换个温度、换个速度——全部过期,从头再来。

而他压的那根毛刺,根本不该存在。这篇讲的是:为什么两个编码器装进去了,大多数人只用了一个半。

01 先看清这两个的性格

在腱驱动的手指关节上,通常会有两个角度来源。

关节侧编码器:粗,但诚实。

它直接看关节。它说手指在哪,手指就在哪。

但它粗——空间只有十几毫米,转得又慢,低速下噪声地板压不下来。角度在抖。电机侧编码器:细,但不诚实。

电机转 N 圈手指才转 1 圈,所以它的刻度被白白细分了 N 倍。分辨率是白捡的。但它和关节之间,隔着一根会变形的腱。

腱有弹性、有预张力、会蠕变,摩擦还是方向相关的。

你抓得越用力,它偏得越多。

(腱为什么不是齿轮、那个偏差有多大,我另写了一篇细讲,这里不展开。)

02 于是大多数人这么用

做法一:二选一

信关节侧? 低速下它在抖。

信电机侧? 你一使劲它就骗你。

选来选去,两边都不对。

做法二:高位拿一个,低位拿另一个,拼起来

这是更"聪明"的一种:

高位(整数段)从关节侧来——它绝对,负责"手指大致在哪"。

低位(细分)从电机侧来——它精细,负责"这一段里的哪个位置"。

一拼,看起来又绝对又精细。然后毛刺就来了。

03 这根毛刺有多大?算给你看

光说"跳一整段"没有感觉。上数字。(下面是一组典型量级的示例参数,不对应任何具体产品。)设定:

  • 传动比 N = 15

  • 电机侧编码器:14 位单圈绝对 → 分辨率 0.022°

  • 指尖到关节的力臂:约 90 mm

第一步:电机侧折算到关节侧,精细到什么程度?

0.022° ÷ 15 = 0.0015° (电机侧折算到关节侧)

精细得离谱。 这就是为什么大家舍不得不用它。

第二步:但它有一个致命的短板——多圈歧义。电机转一圈,关节才转多少?

360° ÷ 15 = 24° (电机转一圈 = 关节转的角度 = 段宽)

电机侧那个 14 位编码器,只知道自己在"这一圈里的哪个位置"。它不知道自己转到第几圈了。所以关节侧编码器的唯一任务是:判断现在是第几段。每一段,24°。第三步:判错一段,会怎么样?关节角,直接跳 24°。折算到指尖(力臂 90 mm):

90 mm × 24° × π/180 ≈ 37 mm (判错一段 → 指尖弹出多远)

指尖,瞬间弹出去三厘米多。而这只手的标称定位精度,是 ±0.2 mm。差了将近 200 倍。

04 可是判错的窗口,明明宽得很啊

这正是最坑的地方。理论上的容错窗 = 半个段 = ±12°。而关节侧编码器的噪声,撑死 ±0.15°(低速、小磁铁、旁边全是电机的杂散场)。

±0.15° 对 ±12°——余量 80 倍。看起来安全得不能再安全。但边界是一个硬判决。当手指恰好走到 24°、48°、72° 这些边界上的时候——±0.15° 的噪声,就足以让判决在 k 和 k+1 之间来回翻。平时余量 80 倍。但每次路过边界,余量就是 0。于是输出反复跳 24°。指尖反复弹 37 mm。这就是那根毛刺。

05 于是打补丁:加一个迟滞窗

最自然的想法:别一到边界就换段,等它走过头 2° 再换。迟滞窗 ±2°。在台架上一试——±0.15° 的噪声,被 ±2° 的窗口稳稳挡住。不跳了。 ✅

工程师松了一口气,提交代码。

06 然后,你去拧了一个瓶盖

满载抓握的时候,腱被拉长了。

按另一篇里算的量级:腱伸长 0.2 mm、腱轮半径 6 mm →

Δθ_joint ≈ 0.2 / 6 = 0.033 rad ≈ 1.9° (腱伸长 → 关节角偏移)

“电机角 ÷ 15"和真实关节角之间,差了 1.9°。这意味着什么?段边界,整体挪了 1.9°。而你那个迟滞窗,是在空载下标的,钉死在原来的位置。现在算一下它还剩多少:一边:2.0° − 1.9° = 0.1°

±0.15° 的噪声,又能把它翻过去了。 ❌ 毛刺回来了。另一边:2.0° + 1.9° = 3.9°

该换段的时候不换——角度就一直错着 24°,直到手指多走 3.9° 才纠正过来。 ❌

一边太紧,还在抖。另一边太松,错得更久。这就是"用固定阈值,追一个随张力实时移动的边界"的具体含义。你调紧一点?空载下又开始抖。你调松一点?满载下错得更久。你去查表?那张表得是负载、温度、速度、磨损的四维函数。它不可能收敛。因为你在用一个常数,去追一个变量。

07 所以这套 if-else 的问题,不是丑

第一,毛刺没了,误差还在。

你只是把跳变藏起来了——藏在"我强行不让它换段"里面。

该换的时候不换,角度就一直是错的。只是不跳了。你没让它对,你只是让它不难看。第二,所有阈值都是在某一个工况下拍出来的。 换负载、换温度、换速度——全部过期。第三,最要命的:腱一变形,边界就漂。而你的阈值不会跟着漂。

08 错在最开始那一步:你不该"拼接”

回头看,高位一个、低位一个 这个动作本身,就已经错了。因为它默认了一件事:这两个读数之间的关系,是固定的。而它不固定。

它们中间隔着一根会随负载变形的腱。

拼接,是假装这个偏差不存在。if-else,是在偏差已经把角度顶跳了之后,回头去压那个跳。你压的是症状。

09 那个偏差不是噪声——它是信号

这是整篇文章唯一想让你记住的一句话。

两个读数之间那个差值,不是要被过滤掉的干扰。它就是腱变形了多少。它是一个可以被观测的物理量。你一旦开始盯着它,if-else 就不需要了。

而且你会立刻发现,它有两副面孔:慢的那一副:蠕变、磨损、温度。以小时、以天、以几百万次循环为单位在变。快的那一副:弹性变形。跟着张力走——而张力随每一次抓握,在毫秒内剧变。你去拿一个纸杯,和你去拧一个瓶盖,腱上的张力差着一两个数量级。这两副面孔,必须分开对付。慢的那一半,可以被在线跟踪、慢慢吸收——这正是"在线自校准"这类技术擅长的事:不需要外部的高精度标定台架,就能持续追一个慢变的系统偏差。

快的那一半,慢慢追是追不上的——它需要高带宽的观测或补偿。

(双编码器差值、张力检测、状态观测器、模型前馈……路子不止一条。关键不是用哪一种,而是:不能只靠慢速的参数更新,去追一个毫秒级变化的弹性变形。)

10 所以你到底用了几个编码器

回到开头那两百行 if-else。

装了两个编码器,是为了拿到"绝对"和"精细"两样东西。但如果你只是把它们拼起来,然后在接缝处压毛刺——你其实只用了一个半。关节侧那个,被你砍成了"高位";电机侧那个,被你砍成了"低位";而它们中间那个最有信息量的东西——那个偏差——被你当成噪声压掉了。你有两个编码器。但你只用了一个半。剩下的那半个,全花在写 if-else 上了。

11 下次评审,问这三个问题

  1. 两个编码器是怎么合的?

二选一?高位低位拼接?如果是拼接——那些压毛刺的阈值,是在什么负载、什么温度下拍的?

  1. 那个偏差,是被当成毛刺压掉了,还是被当成信号跟踪了?

如果是前者——每一次用力抓握,你的角度都会错。 在近似线性弹性区,这个偏差随负载增大。

  1. 这个偏差的慢变和快变,分开处理了吗?

慢的(蠕变)可以在线跟踪;快的(弹性)必须有高带宽的观测或补偿。 只用一个慢速滤波去追两者,两个都追不上。最后一句:别急着写下一个 else if。先问问自己:我压的到底是噪声,还是我自己看不懂的信号。边界说明文中第三到第六节的那组数字(传动比 15、电机侧 14 位、力臂 90 mm、噪声 ±0.15°、迟滞窗 ±2°、腱伸长 0.2 mm)是一组典型量级的示例参数,用于把推理链走通——它不对应任何具体产品,也不是实测值。 换一组参数,段宽、毛刺幅度和迟滞窗的余量都会变,但"用固定阈值追一个随负载移动的边界"这个结构性问题不会变。

本文讨论的是腱驱动关节里"关节侧 + 电机侧"双编码器的通用工程问题,不针对任何具体厂商的产品。腱的顺应性、摩擦和蠕变会拖垮"仅靠电机编码器估关节位置"的精度——这是机器人手领域的公认问题,双编码器也是文献里早已存在的路线之一。文中"两百行 if-else"是我见过的真实代码规模,不指向任何特定客户。

知识卡片

判错一段,指尖弹出 37 毫米——两个编码器装进机器人手指,大多数人只用了一个半

◆ ◆ ◆

应作如是观
公众号「如是有为」二维码

关注公众号「如是有为」

微信扫一扫,新文章先在公众号发出。