简介:面向机器人控制与工业自动化方向的学习者,这份资料聚焦六轴机械臂在平面内的位置规划,采用简化几何解法把复杂空间运动拆解为控制竖直与水平方向的关键关节运动,降低计算复杂度并提升规划直观性。压缩包共5个文件,以3个.m脚本和2个.mat数据文件为主,脚本承载几何解算与运动规划逻辑,数据文件用于存放轨迹或中间计算结果;整体仅3KB,体量轻便,便于快速查阅和二次修改。已有1329人学习下载,说明其在相关课程设计、算法练习与工程入门场景中具有一定参考价值。读者可结合七段S曲线速度规划思路,理解加减速平滑、抑制振动以及竖直与水平联动的基本方法,并依据工作环境、负载或速度要求替换数据、调整参数,逐步搭建从几何建模到位置规划验证的完整流程,适合作为机械臂运动规划入门与课程实践的辅助材料。
1. 六轴机械臂的位置规划:为什么工业现场宁愿用几何解法
六轴机械臂的位置规划里,最反直觉的一条经验是:数值迭代明明能解任意构型,工业现场却仍然优先用简化几何解法。原因不在精度,而在确定性——几何解法给出的八组封闭解是「可枚举」的,迭代法给出的只是「这一次恰好收敛到」的那一组。位置规划真正需要的不是一个解,而是一族能按当前工况挑选的解;轨迹规划真正需要的也不是漂亮曲线,而是每一拍都能在毫秒内算完、且不会突然跳到另一个臂型的逆解。这两条约束叠在一起,几何解法就成了默认答案。搞懂它怎么把六维问题拆成三次平面几何,比背公式有用得多。
2. 六轴解耦:为什么球腕让逆解从六元方程组退化成两次平面几何
六自由度逆解本质上是一个六元非线性方程组。直接硬解,要么用牛顿迭代猜初值,要么消元到十六次多项式——两条路都不适合在伺服周期里跑。简化几何解法之所以能成立,靠的是一个结构上的先决条件:腕部三个轴交于一点。满足这个条件,位置和姿态就解耦了,六维问题立刻变成「三轴定位 + 三轴定向」两个独立的小问题。
2.1 球腕假设的真正含义:后三轴交于一点
绝大多数工业六轴的第四、五、六轴轴线交于同一点,这个交点习惯上叫腕心。腕心有一个非常关键的性质:无论 θ4、θ5、θ6 怎么转,腕心在基座坐标系下的位置都不变。因为这三根轴都穿过它,绕穿过的轴旋转不会移动轴上的一点。
把这件事写成矩阵就一目了然。末端位姿是 T06,腕心是坐标系 3 的原点,于是
p_tool = p_wrist + R06 · t_tool
其中 t_tool 是工具中心点相对腕心的偏移。当工具只沿工具坐标系 z 轴伸出 d6 时,t_tool = [0, 0, d6]ᵀ,上式退化成 p_wrist = p_tool − d6 · a,a 就是旋转矩阵的第三列,也就是接近向量。这一步是整个解法的分水岭:它把「末端要到哪」翻译成了「腕心要到哪」,而后者只跟 θ1、θ2、θ3 有关。
如果工具在 x 或 y 方向也有偏移,比如做过 TCP 标定、装了偏置法兰,那就不能只减 d6·a,必须老老实实做 p_wrist = p_tool − R06 · t_tool。这是实际工程里最常见的错误来源之一,后面还会提到。
2.2 腕心求解:逆解的第一步不是解方程,是减法
先把腕心算出来,这一步只有一次矩阵乘法和一次向量减法,没有任何迭代:
import numpy as np def wrist_center(T06, t_tool): """由末端位姿反推腕心位置。 T06 : 4x4 末端位姿矩阵(基座系 -> 工具系) t_tool : 工具中心点在腕心系下的偏移,纯 z 向伸出时为 [0, 0, d6] """ R = T06[:3, :3] p = T06[:3, 3] # 关键:用完整旋转矩阵补偿工具偏移,而不是只减 d6 * 接近向量 p_wrist = p - R @ np.asarray(t_tool, dtype=float) return p_wrist这里的参数含义值得单独说清楚。T06 必须是与逆解模型严格一致的位姿,包括基座安装矩阵;t_tool 是标定出来的常量,不是理论值。代码里之所以写成 R @ t_tool 而不是标量乘法,就是因为它天然兼容任意方向的工具偏移——换刀、换夹具之后只需要改 t_tool,求解链路一行都不用动。
这一步做完,六维问题已经被砍掉一半:腕心位置只由前三个关节决定,而前三个关节又恰好构成一个「底座回转 + 平面二连杆」的经典结构。剩下的就是纯粹的平面几何。
2.3 一旦腕部有偏置,几何解法为什么立刻失效
解耦成立的充要条件是后三轴共点。如果第四轴相对第五轴有轴向偏置,或者第五、第六轴不相交,腕心的位置就会同时依赖 θ4 甚至 θ5。这时候 p_wrist 的表达式里混进了后三个角,位置方程和姿态方程再也拆不开,封闭解需要消元到高次多项式,系数还极其敏感。
这也是为什么简化几何解法在教科书里总是绑定「球形腕」这个前提。工程上判断能不能用,只要看两条:后三轴轴线是否近似交于一点;交点到法兰的偏移是否能用单一向量 t_tool 描述。两条都满足,几何解法就是又快又稳的最优选择;任何一条不满足,就该老老实实上数值迭代或者查表,硬套公式只会得到一组看起来能跑、实际末端偏差几毫米的解。
3. 从腕心到六个关节角:几何降维的完整推导链
腕心算出来之后,求解顺序是固定的:先用腕心的水平投影定 θ1,再用竖直平面内的两个距离定 θ2 和 θ3,最后用剩下的旋转矩阵定 θ4、θ5、θ6。每一步都只解一个或两个未知数,没有任何联立。
3.1 底座角 θ1:atan2 的象限陷阱与肩部偏置
最简情形下,肩关节就在基座轴线上,θ1 直接就是腕心水平投影的方位角:
theta1 = np.arctan2(pw[1], pw[0]) # 无肩部偏置用 arctan2 而不是 arctan 是硬性要求。arctan 的值域只有 (−π/2, π/2),腕心落到基座后方时它会给出一个差 180° 的角,机械臂会朝着完全相反的方向甩过去——这是最典型的翻车方式之一。
有肩部横向偏置 d2 时,腕心不在手臂所在的竖直平面内,需要先做一次三角修正。设水平距离 R = hypot(x, y),则手臂平面相对腕心方位偏了一个角度:
R = np.hypot(pw[0], pw[1]) if R < abs(d2): raise ValueError("腕心落在肩部偏置圆内,该目标不可达") # 偏置带来的夹角,符号随 DH 约定,两种都可能 delta = np.arctan2(d2, np.sqrt(R * R - d2 * d2)) theta1_a = np.arctan2(pw[1], pw[0]) - delta theta1_b = np.arctan2(pw[1], pw[0]) + delta # 肩前 / 肩后两个分支这两个分支就是「肩前」和「肩后」两组解的来源。它们都合法,差别在于大臂是伸向目标前方还是后方,通常用「离当前位形更近」来选。可达性判断必须放在前面:R < |d2| 时开方会出现负数,如果不拦截,后面一路 NaN。
偏置修正之后,手臂平面内的水平伸展距离是 r = sqrt(R² − d2²),相对肩关节的高度是 s = z − d1,接下来就是标准的两连杆问题。
3.2 平面二连杆:余弦定理给出的两个肘部构型
前臂那一段往往不是一根直杆,而是 a3 加一段腕部偏置 d4 构成的直角折线。几何上它等价于一根长度为 L2 = hypot(a3, d4)、相对原方向偏了 beta = atan2(d4, a3) 的等效连杆。把这两件事处理掉,剩下的就是初中几何:
def solve_arm(r, s, a2, a3, d4): """平面二连杆逆解,返回两组肘部构型 [(θ2, θ3), ...]""" L1 = a2 L2 = np.hypot(a3, d4) # 前臂等效长度 beta = np.arctan2(d4, a3) # 腕部偏置引入的固定折角 c = (r * r + s * s - L1 * L1 - L2 * L2) / (2 * L1 * L2) if abs(c) > 1.0 + 1e-9: # 超出工作空间 return [] c = np.clip(c, -1.0, 1.0) # 浮点误差保护,必须做 phi = np.arccos(c) # 肘部内夹角 sols = [] for sign in (+1, -1): # 肘上 / 肘下 t3 = sign * phi - beta t2 = np.arctan2(s, r) - np.arctan2(L2 * np.sin(sign * phi), L1 + L2 * np.cos(sign * phi)) sols.append((t2, t3)) return sols几个细节值得掰开说。c 的裁剪不是可选项,acos 的参数只要超出 [−1, 1] 哪怕 1e−15,返回的就是 NaN,而且会静默地污染后面全链路。beta 这一项容易被漏掉:如果直接把 L2 当成 a3 来算,求解出来的肘部角会差一个固定偏角,机械臂仍然能到目标附近,但末端姿态会带着几度的系统偏差——这种错误不会报错,只会让标定结果一直对不上。
至于为什么有两组解:余弦定理只约束了三角形三条边,没有约束三角形的朝向,肘关节可以在连线两侧对称分布。这就是「肘上」和「肘下」,配合 θ1 的肩前肩后,位置部分一共产生四组解。
3.3 腕部 ZYZ 反解与 θ5 趋零时的自由度塌缩
前三个角确定后,正运动学可以算出 R03,剩下的腕部旋转就是 R36 = R03ᵀ · R06。球腕的 R36 是一个标准的 Z-Y-Z 欧拉角旋转,反解有闭式:
def solve_wrist(R03, R06, theta4_prev=0.0): """Z-Y-Z 欧拉角反解,返回 θ4、θ5、θ6""" R36 = R03.T @ R06 s5 = np.hypot(R36[0, 2], R36[1, 2]) theta5 = np.arctan2(s5, R36[2, 2]) if s5 < 1e-6: # 腕部奇异:θ4 与 θ6 的转轴共线,只有 θ4+θ6 可确定 theta4 = theta4_prev # 沿用上一拍的值,避免跳变 theta6 = np.arctan2(-R36[0, 1], R36[1, 1]) - theta4 else: theta4 = np.arctan2(R36[1, 2], R36[0, 2]) theta6 = np.arctan2(R36[2, 1], -R36[2, 0]) return theta4, theta5, theta6θ5 这一支永远取正,对应 θ5 ∈ [0, π]。另一支 θ5 取负会得到镜像的腕部构型,也就是「腕翻转」,它是八组解里的最后那个 2 倍。
奇异分支是这段代码里唯一需要人工干预的地方。s5 趋近 0 意味着第四轴和第六轴变成了一根轴,此时 θ4 和 θ6 只有和(或差)能被确定,单独任何一个都不可观测——数学上解不唯一,物理上那个中间连杆的朝向本来就没有意义。常见的实现思路是冻结 θ4、反算 θ6,让这一拍的解在数值上连续。如果直接让 atan2 自由发挥,相邻两个伺服周期里 θ4 可能从 0 跳到 3.1,θ6 同步跳到 −3.1,末端位置纹丝不动,但两个关节会以极高的速度对冲——这是最容易被误判成「玄学抖动」的故障。
4. 位置规划与轨迹规划:几何解法只负责点位,过程交给谁
到这一步,给定一个位姿,能返回最多八组关节角。但位置规划的输出不是这八组解,而是其中的一组;轨迹规划的输出更不是解,而是一串随时间变化的关节角序列。把这两层混在一起写,是很多自研控制器的通病。
4.1 位置规划的输出是离散点位,不是曲线
位置规划要回答的问题只有一个:末端要去的那个位姿,对应哪一组关节角。它的输入是目标位姿和当前位形,输出是六个角度值。夹取一个工件、对准一个孔位、回到一个示教点,都属于这一类。
这里有个容易被忽略的点:几何解法给出的是「瞬时解」,它不包含任何关于怎么过去的信息。如果目标点在工作空间边界附近,解存在但邻域很窄,规划层要额外判断这段路径上是否处处可解,而不是只判断终点可解。常见的做法是先在笛卡尔空间按固定步长采样,逐点调用逆解,全部有解才认为这段可规划。
4.2 关节空间插补与笛卡尔空间插补的边界
拿到起止两组关节角之后,中间怎么走是插补层的事。两种方式的差别不在实现难度,而在末端轨迹的形状:
| 维度 | 关节空间插补 | 笛卡尔空间插补 |
|---|---|---|
| 插值对象 | 六个关节角分别插值 | 末端位姿插值,逐点逆解 |
| 末端路径 | 曲线,不可预知 | 直线或圆弧,可控 |
| 逆解调用 | 只在起止点各一次 | 每个插补周期一次 |
| 奇异点风险 | 低,路径绕开了关节空间突变 | 高,直线可能穿过腕部奇异面 |
| 典型场景 | 快速定位、回原点、大范围转场 | 涂胶、焊接、直线装配 |
几何解法的性能优势在这一列体现得最直接:笛卡尔插补下,每 4 毫秒就要完整跑一遍腕心计算加两次平面几何加一次欧拉角反解,单次耗时通常在微秒量级,普通工控机绰绰有余。换成数值迭代,同样的周期内要跑几十次雅可比求逆,还得处理不收敛的兜底,实时性一下子就成了瓶颈。
4.3 时间参数化:梯形速度曲线与同步缩放
插补出几何路径之后,还要给它配时间。最常用的是梯形速度曲线:加速段、匀速段、减速段。多关节同时运动时,必须保证六个关节在同一时刻到达,否则末端会走出一条扭曲的轨迹。做法是先找出位移最大的那个主导关节,按它的速度、加速度上限算出总时间,其余关节按比例放慢:
def sync_trapezoid(dq, v_max, a_max): """多关节同步梯形规划。 dq : 各关节的角度增量(弧度) v_max : 主导关节的速度上限 a_max : 主导关节的加速度上限 返回总时间、加速时间、峰值速度、各关节缩放系数 """ d = np.abs(np.asarray(dq, dtype=float)) j = int(np.argmax(d)) # 主导关节 t_acc = v_max / a_max d_acc = 0.5 * a_max * t_acc ** 2 if 2 * d_acc >= d[j]: # 距离太短,加速段还没到 v_max 就要减速,曲线退化成三角形 t_acc = np.sqrt(d[j] / a_max) v_peak = a_max * t_acc t_total = 2 * t_acc else: v_peak = v_max t_total = d[j] / v_max + t_acc scale = d / max(d[j], 1e-12) # 其余关节等比放慢,保证同时到位 return t_total, t_acc, v_peak, scale这段代码里有两个参数必须有物理来源,不能拍脑袋:v_max 和 a_max 要取六个关节里最保守的那一组,因为减速器的额定转速和转动惯量各不相同。scale 的作用是把每个关节的位移统一映射到同一个归一化时间轴上,这样只需要维护一套时间曲线,每个周期按归一化进度查表即可,省掉了六套独立规划器的同步逻辑。
三角形分支不能省。短距离点位运动在产线上占比极高,如果强行按梯形走,加速段还没结束就已经该减速了,规划器会给出一个倒挂的时间序列,表现出来就是「一动就抖」。
5. 避坑与排查:几何解法落地时最容易翻车的五处
公式推对了不代表能跑。下面这几条都是实际调试里反复出现的,每一条都值得在代码里留一道显式检查。
5.1 构型选错:解是对的,姿态却撞上底座
现象:末端能准确到达目标点,但大臂在运动途中扫过了底座或者夹具,轻则触发碰撞检测停机,重则撞坏工件。
原因:位置部分有肩前/肩后、肘上/肘下四组解,八组里每一组在数学上都成立。如果每次都用求解器返回的第一个解,机械臂就会在两个相差极大的臂型之间反复横跳。
解决:不要按返回顺序取解,用代价函数挑选。以「与上一拍关节角的加权距离」为主项,再叠加一条「是否跨越臂型」的惩罚项。跨越臂型意味着大范围重构,代价远高于几个关节多转十几度。加权系数里肘部那一项的权重通常要调高,因为肘部翻转最容易碰到工装。
5.2 奇异点附近:θ4 与 θ6 在相邻两拍之间互换
现象:末端轨迹完全正常,但示波器上 θ4 和 θ6 出现幅度接近 π 的等幅反向跳变,驱动器报过流或者跟随误差超限。
原因:腕部奇异面上 θ4 与 θ6 只有和可确定,求解器每一拍独立解算,浮点误差决定了它这一拍偏向哪一边,于是两个关节互相抵消着跳。
解决:在 s5 小于阈值时冻结 θ4、反算 θ6,让解在数值上连续;同时在上层做奇异规避,让插补路径绕开 θ5 接近 0 的区域。阈值不要设得太小,1e−3 量级比 1e−8 更实用,因为越接近奇异,θ4 和 θ6 的数值条件越差。
5.3 数值越界:acos 的参数差一点点就返回 NaN
现象:大部分点位正常,偶尔某个点解出来全是 NaN,往上一路查发现是 arccos 收到了 1.0000000002。
原因:c 的理论值域是 [−1, 1],但浮点累加之后会稍微越界一点,尤其是在工作空间边界附近。有些数学库直接返回 NaN,有些抛异常,行为还不一致。
解决:进入 arccos 之前一律 clip。同时在 clip 之后判断原始值是否超出容差,超了说明目标点在工作空间外,应该上报不可达而不是硬算。这个检查放在工作空间边界附近尤其重要,因为那里的 c 恒等于 ±1,浮点误差必然出现。
5.4 工具偏移漏算:末端差出一个 d6
现象:所有点位都能解、都能到,但换刀之后末端位置整体偏了几十毫米,且偏差方向随姿态变化。
原因:工具中心点不是腕心,工具在 x 或 y 方向有偏移时,p_wrist = p − d6·a 这个简化公式就错了,必须用 p − R06 · t_tool。这个错误在末端姿态接近竖直时几乎看不出来,姿态一倾斜就暴露。
解决:把 t_tool 做成标定参数而不是硬编码常量,每次换刀重新标定;求解入口处断言 t_tool 的模长与理论值一致,防止参数文件读错。另外,验证环节一定要用正运动学回代,比较末端位姿的残差,而不是只比较关节角。
5.5 验证缺失:解完不回代,错了也不知道
现象:整条链路跑得挺顺,实机上偶尔出现毫米级偏差,排查几天找不到原因。
原因:逆解算完就直接下发,没有任何反馈校验。几何解法涉及多次开方和反三角,任一步的符号约定和 DH 参数不一致,误差都会以毫米级潜伏下来。
解决:每个点位解完之后强制跑一次正运动学,比较位置残差和旋转矩阵残差,超过阈值就标记异常。位置残差用欧氏距离,姿态残差不要直接比矩阵元素,用 trace(R_desᵀ·R_act) 反算的角度更稳。这道检查的代价是几微秒,能省掉几天的排查。
6. 几何解法的边界:什么时候该退回数值迭代
几何解法不是万能的。它的优势完全建立在「球腕 + 结构参数已知 + 目标在工作空间内」这三个前提上,一旦前提破了,硬套公式的代价比直接迭代高得多。下面是实践中常用的判断表:
| 条件 | 用几何解法 | 用数值迭代 |
|---|---|---|
| 腕部三轴共点 | 首选,微秒级 | 不必 |
| 腕部有轴向偏置 | 不适用 | 唯一选择 |
| 目标点靠近工作空间边界 | 可用,但要处理好 acos 裁剪 | 收敛性差,需阻尼 |
| 目标点靠近腕部奇异 | 可用,需冻结分支 | 雅可比病态,需伪逆 |
| 伺服周期要求 < 1 ms | 合适 | 需要预规划或查表 |
| 结构参数在线辨识 | 每次改参数要重推公式 | 只需更新模型 |
真正的取舍在于「可预测性」和「通用性」之间。几何解法的每一组解都能被枚举、被编号、被代价函数排序,控制器知道自己在哪一个臂型上;数值迭代每一拍给出的是局部收敛的结果,没有全局编号,一旦初值选偏就可能收敛到完全不同的构型。对于需要保证确定性的产线设备,这种可预测性比通用性值钱得多。
反过来,如果结构不满足球腕假设,或者需要在线辨识连杆参数、支持多种末端工具快速切换,那几何解法的维护成本会迅速上升——每改一次结构参数,公式就得重推一遍。这时候用带阻尼的最小二乘迭代,再配一张离线打好的种子表,反而更省心。
验证方法上有一个我一直在用的笨办法:随机撒一万个可达位姿,对每个位姿跑逆解再正运动学回代,统计位置残差和姿态残差的最大值、99 分位。如果最大残差集中在工作空间边界,那是数值裁剪的问题;如果残差分布没有规律,多半是某个 DH 参数的符号写反了。这个测试跑一遍不到一分钟,比在实机上试错高效得多。
从那以后我每次接六轴逆解的活,都强制先把「算腕心 → 平面几何 → 欧拉角 → 正解回代」这条链路单独写成可测函数,配一个随机位姿的批量验证脚本,跑通了再接伺服。血泪经验是:跳过这一步省下的半天,后面要用一周来还。希望帮到你。
本文还有配套的精品资源,点击获取