1. 从“指哪打哪”到“怎么打哪”:为什么我们需要逆运动学

上次我们聊了aubo-i5机械臂的正运动学,说白了就是“我告诉你每个关节转了多少度,你来算算它的‘手’(末端执行器)现在在哪儿”。这就像你开车,知道方向盘打了多少、油门踩了多深,就能算出车的位置。这个“正”的过程,是机械臂控制里最基础的一步。

但实际干活的时候,我们脑子里想的往往是反过来的。比如,我想让机械臂的“手”去抓取桌子上那个坐标为 (X, Y, Z) 的杯子,并且让“手”的姿态(是平着抓还是竖着抓)也摆好。这时候,我脑子里先有的是“手”的最终位置和姿态,然后我头疼的问题是:我那六个关节,每个到底该转多少度,才能让“手”精准地到达那个位置并摆好那个姿势?

这个过程,就是逆运动学。如果说正运动学是“因”到“果”的推导,那逆运动学就是从“果”反推“因”。对于aubo-i5这样的六轴机械臂,这个问题尤其有趣也尤其复杂。因为理论上,同一个末端位姿,可能对应多组、甚至无数组关节角度解(专业点叫“多解性”)。这就像你想从北京到上海,可以坐高铁、飞机、开车,甚至骑车(虽然不现实),路径不止一条。

我刚开始玩aubo-i5的时候,就卡在这儿了。正运动学的代码跑得挺顺,感觉自己已经掌控了机械臂。结果一到实际规划路径,想让机械臂画个圆或者走个复杂轨迹,立刻就傻眼——我知道“手”要经过的每一个点,但我不知道每个点对应的关节角度是多少。没有这些角度,我就没法给机械臂下命令。所以,逆运动学是机械臂从“能动”到“能干精细活”的关键一跳,是路径规划、轨迹跟踪、力控等高级应用的地基。

2. 解谜的钥匙:理解逆运动学的核心思路

逆运动学求解,听起来很高深,但我们可以把它想象成一个解多元方程的过程。还记得正运动学那个齐次变换矩阵 H 吗?它是一个4x4的矩阵,包含了末端执行器相对于基座标系的位置(3个值)和姿态(3个值,通常用旋转矩阵的9个元素表示,但有约束)。这个 H 矩阵,是六个关节角度 θ1, θ2, ..., θ6 的函数。

逆运动学问题就是:给定一个目标 H 矩阵,求解出满足 H = f(θ1, θ2, ..., θ6) 的关节角度 θ

对于aubo-i5这种结构比较经典(腕部三个轴线相交于一点,这叫球形腕)的六轴机械臂,业内通常采用一种叫几何法与代数法相结合的方法来求解。这也是最主流、最有效的方法。它的核心思想是“分而治之”:

  1. 分离位置和姿态:利用球形腕的特点,我们可以先把问题拆开。机械臂末端的位置,实际上只由前三个关节(大臂、小臂等)决定。腕部关节(后三个关节)主要控制末端的姿态旋转。这样,我们可以先不管姿态,只根据末端想要到达的位置点,反解出前三个关节的角度。
  2. 求解位置子问题:这步是难点。我们需要根据末端执行器中心点(即腕部中心点)的目标坐标 (X, Y, Z),解出 θ1, θ2, θ3。这个过程常常需要利用机械臂的几何关系,比如在平面内投影,利用余弦定理求解三角形等。aubo-i5的连杆参数(DH参数)决定了这个几何模型。
  3. 求解姿态子问题:一旦知道了前三个关节的角度,前三个连杆的位姿就确定了。此时,腕部中心点的位置和从基座到腕部的旋转矩阵 R03 就已知了。我们的目标末端姿态旋转矩阵是 R。那么,腕部三个关节需要提供的旋转矩阵就是 R36 = (R03)^T * R。对于这种结构,R36 有标准的解算公式,可以相对容易地解出 θ4, θ5, θ6

听起来还是有点绕?我给你打个比方:你想用你的手臂(肩、肘、腕)去摸墙上的一个点,并且让手心朝某个方向。

  • 第一步(位置):你大脑会先计算,肩关节和肘关节该怎么动,才能让你的手腕中心点贴到那个墙上的目标点。这个过程可能有多解:你可以伸直手够,也可以弯着手够。
  • 第二步(姿态):手腕中心点到位后,你再转动手腕,让手心朝向目标方向。

Matlab的强大之处就在于,它能帮我们把复杂的几何关系和三角函数计算,用清晰、可调试的代码实现出来。我们不需要徒手去解那一堆可怕的方程,而是把求解逻辑步骤化。

3. 动手之前:准备好你的“地图”DH参数表

工欲善其事,必先利其器。解逆运动学,我们手里必须有一份准确的aubo-i5机械臂的DH参数表。这是描述机械臂几何结构的“地图”,正逆运动学都离不开它。这里我强调一下,必须使用和正运动学求解时完全同一套DH参数(包括是标准DH还是改进DH),否则你的正逆解会对不上,机械臂会“精神分裂”。

根据aubo-i5的官方技术文档和常见的建模方式,我们通常采用改进DH参数(Modified DH Parameters)。这里我给出我验证过的一份aubo-i5的改进DH参数表,你可以直接拿去用:

连杆 iα_{i-1} (弧度)a_{i-1} (米)d_i (米)θ_i (弧度,变量)
1000.1215θ1
2-π/200θ2
300.4080θ3
4-π/20.3760θ4
5π/200θ5
6-π/200.1025θ6

注意:这份表中的 θ_i 是关节变量。d_i 中的 0.1215 和 0.1025 分别是基座到关节1的偏移,以及腕部中心到末端法兰盘的长度(通常计入连杆6)。a_{i-1} 中的 0.408 和 0.376 分别对应大臂和小臂的长度。这些数值是核心,务必核对准确。

在Matlab里,我们可以先把这个表定义好:

% aubo_i5 改进DH参数表 [alpha, a, d, theta_offset]
% 注意:这里的theta列存放的是关节零位偏移,实际关节角 q = theta_offset + 输入关节变量
aubo_DH = [
    0,          0,      0.1215,  0;      % 连杆1
    -pi/2,      0,      0,       0;      % 连杆2
    0,          0.408,  0,       0;      % 连杆3
    -pi/2,      0.376,  0,       0;      % 连杆4
    pi/2,       0,      0,       0;      % 连杆5
    -pi/2,      0,      0.1025,  0;      % 连杆6
];

这里有个关键点需要理解:DH表里的 θ 这一列,我通常用来存放关节的零位偏移。什么意思呢?机械臂的“零位”姿态是厂家定义的一个初始姿态,此时各个关节的编码器读数不一定是0。我们在做正运动学时,输入的关节角度 q 应该是 (DH表中的theta_offset)+ (你想要的关节转动角度)。在逆运动学求解时,我们求解出的 θ 也是相对于这个DH模型坐标系的角度,最终发给实际机械臂的控制指令,可能还需要减去这个零位偏移。这一点非常容易混淆,是很多初学者调试不通的“坑”。我在代码里会通过注释特别强调。

4. 庖丁解牛:一步步实现aubo-i5的逆运动学求解

好了,地图有了,思路也清楚了,我们现在就用Matlab把逆运动学算法实现出来。我会把整个过程拆解成几个关键函数,并配上详细的注释。

4.1 第一步:定义目标位姿

首先,我们要明确想让机械臂末端达到什么样的位置和姿态。在Matlab中,我们通常用一个4x4的齐次变换矩阵 T_target 来表示。

% 示例:定义目标末端位姿
% 假设我们希望末端执行器到达位置 [0.5, 0.2, 0.3] 米处
desired_position = [0.5; 0.2; 0.3]; % 单位:米

% 定义目标姿态:这里用一个绕Z轴旋转45度的旋转矩阵为例
desired_rotation = rotz(45); % rotz是Matlab Robotics Toolbox的函数,生成绕Z轴旋转的矩阵
% 如果你没有Robotics Toolbox,可以手动定义:
% angle = deg2rad(45);
% desired_rotation = [cos(angle), -sin(angle), 0;
%                     sin(angle),  cos(angle), 0;
%                     0,           0,          1];

% 组合成齐次变换矩阵
T_target = [desired_rotation, desired_position;
            0, 0, 0, 1];

4.2 第二步:求解关节1的角度(θ1)

对于aubo-i5这类结构,关节1是绕基座Z轴旋转的。从几何上看,末端腕部中心点 P_wrist(注意,不是末端点,是腕部中心,即连杆4、5、6的交点)在XY平面上的投影点,决定了关节1需要转多少度才能让大臂对准这个方向。

首先,我们需要从目标位姿中提取腕部中心点坐标。因为我们的DH参数中,d6=0.1025 是沿着连杆6的Z轴方向(腕部指向末端)的偏移。所以腕部中心点 P_wrist = P_effector - d6 * (R_target的第3列向量)R_targetT_target 的旋转矩阵部分。

function theta1_solutions = solve_theta1(T_target, d6)
    % 从目标变换矩阵提取位置和姿态
    P_eff = T_target(1:3, 4); % 末端执行器位置
    R_eff = T_target(1:3, 1:3); % 末端执行器旋转矩阵
    % 计算腕部中心位置 (Wrist Center)
    % R_eff(:,3) 是末端坐标系Z轴的单位向量(指向末端前进方向)
    P_wrist = P_eff - d6 * R_eff(:,3);

    x = P_wrist(1);
    y = P_wrist(2);
    % 关节1的解:atan2(y, x) 和 atan2(y, x) + pi (或 - pi)
    % 通常有两个可能解,相差180度,对应机械臂“左手”和“右手”构型。
    theta1_1 = atan2(y, x);
    theta1_2 = atan2(y, x) + pi; % 另一种构型
    % 将角度归一化到 [-pi, pi] 区间,方便后续处理
    theta1_solutions = [wrapToPi(theta1_1), wrapToPi(theta1_2)];
end

wrapToPi 是一个自定义函数,将角度约束在[-π, π]之间。Matlab Robotics Toolbox里有同名函数,没有的话可以自己写一个简单的:angle = mod(angle+pi, 2*pi) - pi;

4.3 第三步:求解关节2和关节3的角度(θ2, θ3)

知道了 θ1 和腕部中心点 P_wrist 的坐标后,我们可以将问题简化到由关节1、2、3所在的平面内。这本质上是一个平面二连杆机械臂(关节2和关节3)的逆运动学问题,已知连杆长度(a2, a3)和末端点(腕部中心投影到该平面的坐标),求两个关节角。

这里需要用到余弦定理。我们计算腕部中心点相对于关节2所在坐标系的位置。然后根据几何关系,可以解出 θ3θ2

function [theta2_sol, theta3_sol] = solve_theta2_theta3(P_wrist, theta1, a2, a3)
    % 将腕部中心坐标转换到关节1坐标系(绕Z轴旋转-theta1)
    R_z_inv = rotz(-theta1);
    P_1 = R_z_inv * P_wrist; % 现在P_1的x分量就是关节2到腕部中心的平面距离投影

    % 提取在关节1坐标系下,腕部中心在X-Z平面上的坐标(忽略Y,因为关节1已对齐)
    x = P_1(1);
    z = P_1(3) - d1; % d1是DH表中连杆1的d参数,即基座高度偏移

    % 计算腕部中心到关节2原点的平面距离
    r = sqrt(x^2 + z^2);

    % 使用余弦定理求解 theta3
    % cos(theta3) = (a2^2 + a3^2 - r^2) / (2*a2*a3)
    cos_theta3 = (a2^2 + a3^2 - r^2) / (2 * a2 * a3);
    % 由于数值计算误差,需要将cos值钳制在[-1,1]之间
    cos_theta3 = max(-1, min(1, cos_theta3));
    theta3_1 = acos(cos_theta3); % 肘部“向上”解
    theta3_2 = -theta3_1;        % 肘部“向下”解

    % 对应每个theta3,求解theta2
    % theta2 = atan2(z, x) - atan2(a3*sin(theta3), a2 + a3*cos(theta3))
    for i = 1:2
        theta3 = [theta3_1, theta3_2](i);
        phi = atan2(z, x);
        psi = atan2(a3 * sin(theta3), a2 + a3 * cos(theta3));
        theta2_sol(i) = phi - psi;
    end
    % 同样,将角度归一化
    theta2_sol = wrapToPi(theta2_sol);
    theta3_sol = [theta3_1, theta3_2];
end

这里 a2a3 对应DH表中的 a 参数(分别是0.408和0.376)。d1 是基座偏移(0.1215)。这段代码会为每一组 (θ1, 肘部构型) 产生对应的 θ2θ3

4.4 第四步:求解腕部关节角度(θ4, θ5, θ6)

当前三个关节的角度确定后,从基座到关节3的变换矩阵 T03 就可以通过正运动学计算出来(复用我们上一篇文章写的正运动学函数)。那么,从关节3到末端(关节6)所需的变换矩阵就是 T36 = inv(T03) * T_target

对于球形腕,T36 的旋转矩阵 R36 具有特定的形式,我们可以从中直接提取出 θ4, θ5, θ6。常用的方法是使用 atan2 函数来求解,避免象限判断错误。

function [theta4, theta5, theta6] = solve_spherical_wrist(R36)
    % R36 是从连杆3坐标系到连杆6坐标系的旋转矩阵
    % 对于常用的Z-Y-Z欧拉角(或类似)解算
    % 注意:解不唯一,通常有两组解(“腕部翻转”)
    
    % 方法:从旋转矩阵元素中提取角度
    % 假设机械臂后三轴符合常见的配置,我们可以用以下公式:
    % 注意:这里需要根据你的DH模型和坐标系定义来调整符号和公式顺序
    % 以下是一种常见情况的推导:
    
    % 求解 theta5
    theta5_1 = atan2(sqrt(R36(1,3)^2 + R36(2,3)^2), R36(3,3));
    theta5_2 = -theta5_1;
    
    % 对应每个theta5求解theta4和theta6
    for i = 1:2
        th5 = [theta5_1, theta5_2](i);
        if abs(sin(th5)) > 1e-6 % 避免奇异点(theta5接近0或pi)
            th4 = atan2(R36(2,3)/sin(th5), R36(1,3)/sin(th5));
            th6 = atan2(R36(3,2)/sin(th5), -R36(3,1)/sin(th5));
        else
            % 处于奇异位形,theta4和theta6不独立,通常设定一个为默认值,求解另一个
            th4 = 0; % 或当前角度
            th6 = atan2(R36(1,2), R36(1,1)) - th4;
        end
        theta4(i) = wrapToPi(th4);
        theta5(i) = wrapToPi(th5);
        theta6(i) = wrapToPi(th6);
    end
end

重要提示:腕部解算公式 solve_spherical_wrist与你的DH模型定义强相关的。上面的代码是一个示例框架。你必须根据自己从 T36 中推导出的确切公式来编写。推导过程需要耐心,建议在纸上画一下坐标系,根据 R = rotz(θ4)*roty(θ5)*rotz(θ6)(或你的顺序)的矩阵乘法,反解出每个角度的表达式。这是逆运动学编程中最需要细心和验证的部分。

4.5 第五步:整合与多解处理

现在我们把所有部分组合起来。对于aubo-i5,理论上最多有 8组解(2种肩部构型 × 2种肘部构型 × 2种腕部构型)。我们需要一个主函数来遍历这些组合,并调用上述子函数。

function all_solutions = aubo_ikine(T_target, DH_table)
    % 输入:目标齐次变换矩阵 T_target (4x4)
    % 输入:改进DH参数表 DH_table (6x4)
    % 输出:所有可能的关节角解 (N x 6),N <= 8
    
    % 提取DH参数
    d = DH_table(:, 3); % d参数列
    a = DH_table(:, 2); % a参数列
    d1 = d(1); d4 = d(4); d6 = d(6); % 常用的d参数
    a2 = a(3); a3 = a(4); % 常用的a参数 (注意索引,DH表a是a_{i-1})
    
    % 1. 求解 theta1
    theta1_options = solve_theta1(T_target, d6);
    
    all_solutions = []; % 存储所有有效解
    
    % 2. 对每个theta1解,求解theta2, theta3
    for t1 = theta1_options
        P_wrist = ... % 计算腕部中心(同上)
        [t2_opts, t3_opts] = solve_theta2_theta3(P_wrist, t1, a2, a3, d1);
        
        % 3. 对每组(t1, t2, t3),计算T03,并求解腕部关节
        for idx = 1:length(t2_opts)
            t2 = t2_opts(idx);
            t3 = t3_opts(idx);
            
            % 计算正运动学 T03 (使用前三个DH参数)
            T03 = ... % 调用你的正运动学函数,输入前三个关节角度
            
            % 计算 T36
            T36 = inv(T03) * T_target;
            R36 = T36(1:3, 1:3);
            
            % 求解腕部角度
            [t4_opts, t5_opts, t6_opts] = solve_spherical_wrist(R36);
            
            % 4. 组合所有解,并检查是否在关节限位内
            for w_idx = 1:length(t4_opts)
                one_solution = [t1, t2, t3, t4_opts(w_idx), t5_opts(w_idx), t6_opts(w_idx)];
                % 可选:检查关节角度是否在物理限位内
                if check_joint_limits(one_solution)
                    all_solutions = [all_solutions; one_solution];
                end
            end
        end
    end
    
    % 如果无解,返回空矩阵
    if isempty(all_solutions)
        warning('未找到逆运动学可行解!');
    end
end

这个主函数 aubo_ikine 就是我们的核心。它系统地组合了前面各个步骤的解,并最终输出所有在物理上可行的关节角度集合。在实际使用中,我们可能会从这多组解中,根据“最接近当前姿态”、“能量最小”、“避开奇异点”等原则,选择最优的一组发给机械臂。

5. 验证与调试:让你的算法真正可靠

代码写完了,千万别以为就大功告成了。逆运动学算法的调试,是真正考验耐心和细致的时候。下面是我总结的验证“三部曲”:

第一步:闭环验证(最重要) 这是最直接的验证方法。你随机生成(或在关节限位内选择)一组关节角度 q_test

  1. 用你的正运动学函数,根据 q_test 计算出末端位姿 T_computed
  2. 将这个 T_computed 作为目标,输入给你的逆运动学函数 aubo_ikine
  3. 在逆解得到的多组解中,寻找与原始 q_test 最接近的一组解(注意角度周期性,相差2π的应视为相同)。
  4. 比较两者的差异。理想情况下,误差应该在 1e-6 弧度或更小的量级。如果误差很大,说明你的逆解算法有bug。

第二步:可视化辅助 在Matlab中,你可以用简单的线条画出机械臂的连杆。对于每一组逆解,都用正运动学算出每个关节的位置,然后用 plot3 函数画出来。直观地看看,机械臂是不是真的以你期望的构型到达了目标点。这能帮你快速发现是肩部、肘部还是腕部的解选错了。

第三步:奇异点测试 机械臂在某些特殊位形下(比如手臂完全伸直,θ5 接近0),会失去一个或多个方向的自由度,这就是奇异点。在奇异点附近,逆运动学求解可能会数值不稳定(出现极大值或NaN)。你需要测试这些边界情况,确保你的代码能稳健处理(例如,在 solve_spherical_wrist 函数中,我们对 sin(theta5) 接近0的情况做了特殊处理)。

我自己的调试过程就踩过不少坑。最常见的是符号错误:DH参数中角度的正负号、atan2 函数的参数顺序、腕部解算公式的符号,任何一个错了,解出来的姿态都会莫名其妙。我的经验是,每写一个子函数,就立刻用几个简单例子测试一下。比如测试 solve_theta1 时,手动设定一个简单的 P_wrist,看看算出来的角度是不是你几何直观上期望的角度。

6. 从理论到实战:在路径规划中的应用

逆运动学求解本身不是目的,它是个工具。它的最大用武之地是笛卡尔空间路径规划。举个例子,你想让aubo-i5的末端画一个圆,或者走一条直线。

  1. 路径点生成:首先,你在笛卡尔空间(就是三维空间)里规划好路径。比如画圆,你可以用参数方程生成一系列圆上的点 [X(k), Y(k), Z(k)],以及每个点处末端的姿态(比如始终保持垂直向下)。
  2. 逆解计算:对于路径上的每一个目标位姿 T_target(k),调用 aubo_ikine 函数,从得到的多组逆解中,根据“与上一时刻关节角度最接近”的原则选择一组,得到对应的关节角度序列 q(k, :)。这个过程叫做逆解选择,它能保证机械臂运动连续、平滑,不会在两种构型间突然跳跃。
  3. 关节空间插值:得到了离散的关节角度序列 q(k, :) 后,你可以在关节空间进行插值(比如三次样条插值),生成更密集、更平滑的关节角度指令,最后发送给机械臂控制器执行。

这样,你就实现了让机械臂在三维空间中沿着预定轨迹运动。没有逆运动学,这一切都无法实现。我在做一个简单的拾放demo时,就是先示教了抓取点和放置点,然后在这两点间进行直线插值,对每一个插值点求逆解,从而让机械臂平稳地直线运动过去,效果非常棒。

最后,我想说,逆运动学的实现确实比正运动学复杂不少,会涉及到更多的几何和三角计算,调试也需要耐心。但一旦你亲手把它调通,看到机械臂精准地按照你的空间指令运动时,那种成就感是无与伦比的。这份Matlab代码不仅仅是一个算法实现,更是你理解和掌控机械臂空间运动能力的桥梁。建议你对照着aubo-i5的模型,一步步推导,一步步编码测试,遇到问题就画图分析,这才是学习机器人学最扎实的方式。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐