0%
✍️ Insight

首页那只机械臂,是怎么够到你的鼠标的

从转动一个手肘,到让三个关节轮流让步。五张可以摸的插图,拆开首页那只机械臂背后的几何,也看看它没有替真实机器人解决什么。

在首页挪动鼠标,那只机械臂会弯弯手肘,把夹爪伸过去。

看起来像「知道你想让它去哪」。但鼠标只交给它一个点,没有告诉它肩膀转几度、手肘朝哪边、腕部该不该跟着动。这个简单动作的难处,就藏在位置已经知道,姿势还没决定里面。

不妨把首页拆成五张小图。每张只问一件事:先转关节,再挪目标,最后看看那几行程序是怎么轮流劝动三个关节的。

给角度,像沿着地址找过去

先只留肩膀和手肘两个关节。

从肩膀朝某个方向走完上臂,再顺着前臂的方向走完前臂,落脚处就是指尖。只要长度和角度都知道,这趟路没有岔口。

先拖夹爪,只转手肘。然后拖手肘上的圈,改转肩膀。看指尖留下的圆弧,圆心会搬家。

只动手肘,指尖绕手肘画圆;只动肩膀,整条手臂绕肩膀画圆。

这叫正运动学:从关节姿态算出末端位置。名字听起来比动作严肃,其实就是顺着两根杆走过去。

若肩角是 θ1\theta_1,肘相对于上臂的转角是 θ2\theta_2,两节长度是 l1,l2l_1,l_2,那么以肩为原点:

x=l1cos⁡θ1+l2cos⁡(θ1+θ2),x=l_1\cos\theta_1+l_2\cos(\theta_1+\theta_2), y=l1sin⁡θ1+l2sin⁡(θ1+θ2).y=l_1\sin\theta_1+l_2\sin(\theta_1+\theta_2).

前臂用的是 θ1+θ2\theta_1+\theta_2,不是单独的肘角。肩膀已经转过一次,手肘是在那个方向上继续转。正文公式按数学课本:y 朝上、逆时针为正。SVG 的 y 朝下,代码内部角度符号相反;图里的角度读数已经翻回正文约定。

给位置,却像一个地址有两条路

现在把问题倒过来:指尖必须到十字靶,两个关节该怎么摆?

这叫逆运动学。先别急着求解:把肩、肘、目标连成一个三角形。上臂和前臂是两条固定边,肩到目标的距离是第三条边。

同一个三角形可以往两边翻。下面的实线和虚线,就是朝两边翻的两只肘。

拖动目标,看两个解一起移动;再把目标放到外圈,或放进内圈。

只指定指尖位置,通常有两个姿势。贴着圆环边缘时,三角形被压平,两个姿势合成一个;出了圆环,就没有姿势能做到。

这张图的杆长是 100 和 65。最远够到 165,这不奇怪;最近却只能到 35。两节不一样长,折到最紧也不能完全抵消。太近和太远,都可能够不到。

把三角形翻译成公式

设目标与肩的距离为 r。余弦定理给出肘角:

cos⁡θ2=r2−l12−l222l1l2.\cos\theta_2=\frac{r^2-l_1^2-l_2^2}{2l_1l_2}.

可达时,θ2=±arccos⁡(⋯ )\theta_2=\pm\arccos(\cdots) 对应两支解。再用目标方位角减掉三角形在肩部张开的角:

θ1=atan2⁡(y,x)−atan2⁡(l2sin⁡θ2,l1+l2cos⁡θ2).\theta_1=\operatorname{atan2}(y,x)-\operatorname{atan2}(l_2\sin\theta_2,l_1+l_2\cos\theta_2).

如果 r 不在 [∣l1−l2∣,l1+l2][|l_1-l_2|,l_1+l_2] 内,就别硬把它塞进反余弦。图中明确标出无解,并画出最近姿态。两节等长、目标恰好在肩上的情况更特殊:完全折叠后,肩可以朝任意方向,有无穷多个解。

多一个关节,多出一点自由

首页有三节连杆。如果仍然只要求指尖到一个平面位置,就比刚才多了一点「姿势上的余地」。

把下面的指尖钉住,再慢慢改变肩角。肩后的关节点换了位置,剩下两节重新解一次三角形,仍能接回同一个目标。

滑块限制在可行肩角范围内。肩转动,后两节配合,指尖误差保持在数值精度附近。

现在不只是两条路,而是一段连续的姿势。多出来的自由度,叫冗余。

冗余不是多余的零件。真实手臂可以用它绕开障碍、避开关节极限,或让动作更顺。但它也把问题从「求一个答案」变成了「在许多答案里,挑哪个」。

不一次想明白,先劝离手最近的那个关节

首页选择了一种很朴素的策略:每次只转一个关节。

先看最末端的腕关节。从它画两条线,一条指向夹爪,一条指向目标。把第一条往第二条转一点,夹爪就沿着这个关节的圆周靠近目标。然后换肘,再换肩。

一次只转一个关节。圈出的是下一次要动的位置,蓝线指向指尖,橙线指向目标。

从腕到肩扫完一遍,通常还没到,便再扫一遍。它叫 CCD,循环坐标下降;这里的「坐标」是轮流调整的关节角,不是把 x、y 轮流改掉。

它没有先列出全部姿势,也不要求每一步就到目标。每个关节只处理眼前的夹角,许多小让步累积起来,把指尖挪过去。固定目标、没有关节限位、每次取最短转角且比例在 0 到 1 之间时,这种转动不会增加末端到目标的距离。但「不变远」不保证一定能到:目标可能不可达,也可能卡在不再进展的姿态。

首页那只手,只藏了两个手感旋钮

首页三节杆长是 82、86、50,底座在 SVG 的 (80, 252)。每次不是把夹角全转完,而是转 60%;每个动画帧从腕到肩扫 6 遍。

下面保留同样的杆长、初姿态和求解循环。先用默认参数挪一个目标,再恢复初姿态,把比例调到 0.1,只算一帧。相同的计算次数,指尖会留下更大的距离。

同一个 CCD,改变每次转多少、每帧扫几遍。可以算一帧,也可以有限时长地连续播放。

比例小,单次步子小;多扫几遍,可以补一些进度。比例 1 在固定目标下未必会抖,反而常常更快——别把「阻尼让动作温和」误记成「不加阻尼就一定不稳定」。鼠标还在移动时,追踪的手感才是另一层比较。

首页真正计算关节角的部分就是这些。以下保留了原来的循环,只省去 TypeScript 的参数类型:

function solve(t, iters = 6, gain = 0.6) {
  for (let iter = 0; iter < iters; iter++) {
    for (let i = 2; i >= 0; i--) {
      const pts = forward(ang);
      const pivot = pts[i], end = pts[3];
      const a1 = Math.atan2(end.y - pivot.y, end.x - pivot.x);
      const a2 = Math.atan2(t.y - pivot.y, t.x - pivot.x);
      let d = a2 - a1;
      d = Math.atan2(Math.sin(d), Math.cos(d));
      for (let k = i; k < 3; k++) ang[k] += d * gain;
    }
  }
}

atan2(sin(d), cos(d)) 把夹角换成一圈以内的最短转向。最后一行给 i 后面的角度都加上同一个量,因为代码存的是每节杆的绝对朝向:转肘时,前臂和腕后的杆都得一起跟过去。

正文前面的公式用关节相对角,代码这里用连杆绝对角。这不是两种物理,只是两种记账方式。漏掉这个区别,很容易写出「肘转了,夹爪那节却留在原地」的怪手臂。

能够到,还没有获得开机许可

首页那只手不承重,也不接电机。它没有关节限位、没有碰撞检测,也没有规定夹爪必须朝哪个方向。

把目标挪到底座附近,它可能折出真实机械臂根本做不到的姿势。这不是再调一下 gain 就能补好的:运动学解,只回答了几何上怎样摆,不回答能否安全地走过去。

真实机器人还要选解、避障、规划一条连续路径,再让控制器执行。伸直附近还会碰到奇异位形:某些方向很难靠微小关节转动产生位移。那是继续学习雅可比矩阵的好入口,但不必为了看懂首页,今天就把整本书读完。

这五张图的数学集中在 arm.js,检验文件会把逆解代回正运动学,检查指尖有没有真到目标。它们只是浏览器里的几何,不连接真实硬件。

下次回首页挪动鼠标,你看到的就不只是一只跟手的小动画了。那是三个关节在轮流让一点,把一个还没想明白的姿势,慢慢让到你指的地方。

Modern RoboticsLynch 与 Park 的开放教材配套资料:从刚体运动继续走向运动学、规划与控制。modernrobotics.northwestern.edu Robotics, Vision and Control 工具Peter Corke 的 Robotics Toolbox:把纸上的运动学继续放进工具里试。petercorke.com