简介:基于Matlab搭建的六自由度机械臂正逆运动仿真项目源码,面向计算机、电子信息工程、数学等相关专业大学生,可服务于课程设计、期末大作业或毕业设计等教学场景。压缩包内共六个文件,含五个脚本文件与一个图形界面文件;脚本部分围绕运动学正解、逆解、轨迹插值、坐标变换等核心算法展开,图形界面则用于可视化交互与结果展示。整体包体仅四十三KB,代码量精简,结构清晰,便于阅读和二次开发。当前已有274人学习使用,适合具备一定Matlab基础、能自行调试和扩展功能的开发者参考。通过学习该源码,可快速理解机械臂正逆运动学的建模思路与实现流程,借助GUI观察末端位姿和关节角变化,为后续机器人轨迹规划与控制研究提供可复用的代码基础。

1. 为什么在Matlab里先做六自由度机械臂正逆运动仿真

一台六自由度关节型机械臂的裸机价格通常在几万到几十万不等,更贵的是调试时间和撞机成本。在实物上验证正逆解,一旦某个关节角突变、轨迹穿过奇异位形,轻则电机过流报警,重则末端撞上夹具。所以比较稳妥的做法是:先在Matlab里把DH参数、正运动学、逆运动学、轨迹规划全部跑通,确认数值正确后再下发到真实控制器。这套流程解决的恰恰是“我的机械臂应该转多少度才能让末端到达目标位姿”和“给定关节角,末端到底在哪”这两个最基础、也最容易出错的问题。

这个标题适合三类人:做机械臂毕业设计的学生,需要快速看到仿真动画来演示算法;刚转入机器人控制方向的嵌入式工程师,想用Matlab验证自己写的C++逆解逻辑;以及做自动化产线集成、需要评估机械臂可达姿态的现场工程师。下面按正解、逆解、仿真工程、轨迹规划、验证这条线逐步展开,代码都可以直接复制修改。

2. 正运动学建模:先定DH参数,再写变换矩阵

2.1 六自由度机械臂DH参数表怎么读、怎么改

正运动学的核心是把每个关节的转动转化成末端位姿的矩阵连乘。最常见的建模方式是用DH参数,四个参数分别是:连杆长度a、连杆扭转角alpha、连杆偏距d、关节角theta。其中theta是变量,其余三个在机械结构确定后就是常量。

以常见的六轴关节型机械臂(类似UR10的量级)为例,改进DH参数表如下:

关节i a(mm) alpha(rad) d(mm) theta
1 0 0 151.9 q1
2 -425 -pi/2 0 q2
3 -392.25 0 0 q3
4 0 -pi/2 112.5 q4
5 0 pi/2 93.0 q5
6 0 -pi/2 82.5 q6

这张表里a2和a3是负值,这在六轴机械臂里很常见,表示相邻关节轴线在x轴负方向上有偏置,不是写错了。单位用mm,后面正解算出来的末端位置也是mm,画图时如果和STL模型混用要注意单位统一。theta列就是系统的关节变量,代码里直接用关节角向量q替代即可。

根据机械结构图确定DH参数时,最容易错的是alpha的符号。判断规则很简单:沿着x轴正方向看,如果z轴从当前坐标系转向下一个坐标系是逆时针,alpha为正,否则为负。从CAD模型里量参数时,优先找相邻两个关节轴线的公垂线距离a,再量沿z轴方向的偏移d。

2.2 用改进DH约定写正运动学核心函数

这里用改进DH(Craig约定)实现变换矩阵,对应的矩阵形式是:

function T = dh_transform_modified(a, alpha, d, theta)
    % 改进DH:RotX(alpha) * TransX(a) * RotZ(theta) * TransZ(d)
    ct = cos(theta);
    st = sin(theta);
    ca = cos(alpha);
    sa = sin(alpha);
    
    T = [ct,          -st,         0,  a;
         st*ca,        ct*ca,      -sa, -d*sa;
         st*sa,        ct*sa,       ca,  d*ca;
         0,            0,           0,  1];
end

四个基础矩阵的顺序是固定的,不要把标准DH和改进DH混用。改进DH的平移部分分布在第四列,标准DH则是a cos(theta)和a sin(theta)在同一列,两者外观差得很远,混用时位姿会完全错误。

有了单关节变换矩阵,正运动学就是把六个矩阵按顺序连乘:

function T06 = forward_kinematics(dh_table, q)
    % dh_table: 6x4矩阵,每行为 [a, alpha, d, theta_offset]
    % q: 6x1关节角向量,单位rad
    T = eye(4);
    for i = 1:size(dh_table, 1)
        T = T * dh_transform_modified(dh_table(i,1), dh_table(i,2), ...
                                      dh_table(i,3), q(i));
    end
    T06 = T;
end

这十几行代码就是正运动学的全部。参数dh_table是按关节顺序排列的6x4矩阵,q是当前关节角。如果某个关节有零位偏置,把偏置值加在dh_table第4列上,不要直接改q,这样调零位时逻辑更清晰。函数最后返回的T06同时包含末端位置(第4列前3行)和姿态(左上3x3旋转矩阵)。

2.3 自检:用RTB的SerialLink和fkine做交叉验证

自己写的矩阵连乘可能因为DH约定、符号问题累积错误,所以常见做法是借助Matlab的Robotics Toolbox(RTB)做交叉验证。RTB是通用的开源工具箱,创建同一组DH参数的机器人模型只需要:

% 从dh_table创建改进DH模型
robot = SerialLink(dh_table(:,1:4), 'modified');

% 给定一组关节角,用工具箱算正解
q_test = [0.2, -0.5, 0.3, 0.1, -0.4, 0.6];
T_toolbox = robot.fkine(q_test);

% 用自己写的函数算正解
T_mine = forward_kinematics(dh_table, q_test);

% 两者之差小于1e-9就说明实现一致
disp(norm(T_toolbox.T - T_mine));

这里fkine返回的是SE3对象,要用.T取出矩阵。注意DH表列顺序必须是[a, alpha, d, theta],RTB对列顺序很敏感,填错后不会报错但位姿会乱。数值差在1e-9量级说明矩阵写法没问题,如果差很大,优先检查alpha符号和a的正负。

3. 逆运动学求解:解析法路径与雅可比数值迭代

3.1 逆解前先判断这组DH能不能用解析法

逆运动学比正解复杂得多,任何一组合法关节角都能算出唯一末端位姿,但反过来同一个末端位姿通常对应多组关节角。解析法能求出全部封闭解,前提是机械臂满足一定几何条件。

从上文的DH表可以看出,4、5、6三个关节的坐标系原点重合,这就是典型的球腕结构——后三个关节轴线相交于一点。满足Pieper准则(三个相邻关节轴交于一点或相互平行)时,可以把逆解拆成两部分:先由末端位姿解出腕心位置,再分别解前三关节和后三关节的角度。对于球腕结构,前三关节负责把腕心送到目标点,后三关节负责姿态,两部分解耦,推导复杂度大幅下降。

判断自己的机械臂是否满足这个条件,就看DH表里后三个关节的a值是否都是0,且相邻alpha的符号是否交替。如果都不满足,解析法的推导会非常痛苦,建议直接上数值法。

3.2 在Matlab里写阻尼最小二乘数值逆解

数值法不依赖特殊结构,通用性强。这里用阻尼最小二乘法,通过雅可比矩阵迭代逼近目标位姿。需要先实现数值雅可比:

function J = jacobian_numerical(dh_table, q)
    % 用前向差分近似几何雅可比矩阵
    n = length(q);
    T0 = forward_kinematics(dh_table, q);
    p0 = T0(1:3, 4);
    R0 = T0(1:3, 1:3);
    J = zeros(6, n);
    dq_eps = 1e-6;
    
    for i = 1:n
        q_plus = q;
        q_plus(i) = q_plus(i) + dq_eps;
        T1 = forward_kinematics(dh_table, q_plus);
        p1 = T1(1:3, 4);
        R1 = T1(1:3, 1:3);
        
        % 线速度部分:[vx, vy, vz]
        J(4:6, i) = (p1 - p0) / dq_eps;
        
        % 角速度部分:由旋转矩阵差值的反对称分量提取
        dR = (R1 - R0) / dq_eps;
        J(1:3, i) = [dR(3,2); dR(1,3); dR(2,1)];
    end
end

数值雅可比的核心思想是逐个关节加微小扰动,用正解结果差分出末端速度。J的行顺序是角速度在前、线速度在后,这样和下面的误差向量顺序才能对应上。dq_eps取1e-6比较合适,太小会引入浮点噪声,太大会让差分结果失真。

迭代求解逆解的主函数如下:

function q = inverse_kinematics_jacobi(dh_table, T_desired, q0, opts)
    % opts: 可选结构体,包含max_iter、tol、damp三个字段
    if isfield(opts, 'max_iter'), max_iter = opts.max_iter; else, max_iter = 50; end
    if isfield(opts, 'tol'),      tol      = opts.tol;      else, tol      = 1e-6; end
    if isfield(opts, 'damp'),     damp     = opts.damp;     else, damp     = 0.05; end
    
    q = q0(:);
    for k = 1:max_iter
        T_cur = forward_kinematics(dh_table, q);
        
        % 用李代数误差表示位姿偏差,避免欧拉角奇异
        err_mat = logm(inv(T_cur) * T_desired);
        e = [err_mat(3,2); err_mat(1,3); err_mat(2,1); err_mat(1,4); err_mat(2,4); err_mat(3,4)];
        
        if norm(e) < tol
            break;
        end
        
        J = jacobian_numerical(dh_table, q);
        % 阻尼最小二乘:JJ^T奇异时仍可迭代
        dq = (J' / (J * J' + damp^2 * eye(6))) * e;
        q = q + dq;
    end
end

误差向量e的前三行是旋转误差,来自logm的反对称矩阵分量;后三行是平移误差。这个顺序必须和J的行顺序严格一致。damp是阻尼因子,在接近奇异位形时JJ^T会接近奇异,正则项damp^2*eye(6)保证矩阵可逆。damp设太小则奇异时迭代发散,设太大则收敛变慢,0.01到0.1是常用区间。

这个迭代法的本质是:当前末端和目标末端差多少,就用雅可比伪逆把误差映射回关节空间,走一小步,再重新算正解。每一步都是局部线性化,所以初始值q0不能离目标解太远,否则可能收敛到错误的姿态分支或直接发散。实际使用时,把上一控制周期的关节角作为q0,轨迹跟踪场景下基本都能收敛。

3.3 解析法与数值法的取舍:多解、限位、收敛

逆解方法 计算速度 多解覆盖 适用条件 常见问题
解析法(Pieper) 微秒级 完整8组解 后三轴交于一点 推导周期长,改结构需重推
雅可比阻尼最小二乘 毫秒级 取决于初值 任意六自由度结构 可能收敛到限位内的次优解
牛顿法(位姿误差) 毫秒级 取决于初值 任意结构 奇异处雅可比秩亏

解析法适合给实时控制器用,数值法适合在仿真阶段快速验证算法逻辑。实际工程里一般是数值逆解 + 多初值 + 限位过滤组合使用:取多个不同的q0分别迭代,把结果投影到关节限位区间内,再按“关节位移最小”原则排序。关节位移可以简单算为关节角的加权平方和,权重按照各关节电机的最大速度倒着设,大关节权重小,末端关节权重大,这样选出的解最省时间。

还有一种改进做法是每次迭代后把q硬截断到限位区间内,再计算下一步误差。不截断的话,迭代过程中关节角可能跑到物理范围之外,虽然数值上能收敛,但轨迹真实性很差。

4. 把正解和逆解装进一个可跑的仿真工程

4.1 源码工程的组织:参数、函数、主循环分开

正逆解只是核心算法,要让它变成一个能看动画、能换参数、能输出的仿真工程,代码结构比算法本身更影响效率。常见的组织方式是分成四个文件,各自只有单一职责:

文件 职责 关键内容
model_params.m 定义机械臂模型 DH表、关节限位、初始关节角、末端工具偏移
forward_kinematics.m 正运动学 dh_transform_modified、fkine封装
inverse_kinematics_jacobi.m 逆运动学 数值雅可比、阻尼最小二乘迭代
main.m 主循环与可视化 目标位姿生成、逆解、画图、轨迹存储

把DH参数单独抽出来好处很明显:换机械臂型号时只需要改model_params.m一个文件,不需要动算法逻辑。另加一个JointLimit结构体来存关节范围,多解排序和轨迹规划都会用到。初始化关节角尽量选一个远离奇异、且各关节在中位附近的姿态,能减少很多数值问题。

4.2 自写可视化:坐标系与连杆怎么画

RTB的plot命令虽然方便,但它是通过不断重绘整个画布实现的,在长轨迹仿真里帧率很低。一个更可控的做法是自己用plot3画连杆和坐标轴:

function plot_arm(dh_table, q, fignum)
    % 绘制机械臂连杆和关节坐标系
    figure(fignum); clf; hold on; grid on; axis equal;
    xlabel('x/mm'); ylabel('y/mm'); zlabel('z/mm');
    
    T = eye(4);
    p_prev = [0, 0, 0]';
    
    for i = 1:size(dh_table, 1)
        T = T * dh_transform_modified(dh_table(i,1), dh_table(i,2), ...
                                      dh_table(i,3), q(i));
        p_cur = T(1:3, 4);
        
        % 用线段连接相邻关节原点
        if i > 1
            plot3([p_prev(1), p_cur(1)], ...
                  [p_prev(2), p_cur(2)], ...
                  [p_prev(3), p_cur(3)], 'b-o', 'LineWidth', 2);
        end
        draw_frame(T, 60);  % 60mm长度的坐标轴
        p_prev = p_cur;
    end
end

function draw_frame(T, len)
    % 在变换矩阵T处画红绿蓝三色坐标系
    o = T(1:3, 4);
    x = o + len * T(1:3, 1);
    y = o + len * T(1:3, 2);
    z = o + len * T(1:3, 3);
    plot3([o(1) x(1)], [o(2) x(2)], [o(3) x(3)], 'r', 'LineWidth', 2);
    plot3([o(1) y(1)], [o(2) y(2)], [o(3) y(3)], 'g', 'LineWidth', 2);
    plot3([o(1) z(1)], [o(2) z(2)], [o(3) z(3)], 'b', 'LineWidth', 2);
end

把基座原点作为初始点,循环里每乘一个变换矩阵就画一段连杆,并画出当前坐标系的三个轴。这样能看到末端坐标轴的方向,判断姿态是否正确。axis equal必须加,否则三个轴的比例会被拉伸,关节角度接近时视觉上会出现假重叠。

这个版本每次调用都会clf重绘,帧率不高。做连续动画时改成预建图形句柄,在循环里更新XData和YData即可,能流畅很多。如果只是为了截图或者验证静态位姿,当前的写法完全够用。

4.3 用RTB的teach和plot快速验证算法

除了做交叉验证,RTB还提供了两个非常适合调试的工具。teach可以拖动滑块改变六个关节角,末端位姿实时显示,能直观理解“某个关节角变化对末端位置的影响”。plot配合轨迹矩阵可以快速看整段运动是否顺畅:

% 建立模型后,用滑块调试关节角
robot.teach([0, -0.8, 0.8, 0, 0.8, 0]);

% 绘制一段关节空间轨迹
q_traj = jtraj(q_start, q_end, 100);
robot.plot(q_traj, 'trail', 'r-');

teach模式下可以手动把机械臂转到奇异位形附近,观察末端速度的急剧变化,这对理解雅可比奇异非常直观。RTB建议只用来辅助验证,最终仿真和输出还是用自己的正逆解函数,因为真实控制器的C++代码需要把Matlab逻辑原样移植,自写算法更容易对照排查。

5. 轨迹规划与奇异规避:别让逆解在运动中途跳变

5.1 关节空间插值:jtraj与梯形速度

点位运动最简单的方案是直接把起始关节角线性插值到目标关节角,但线性插值在起点和终点速度突变,机械臂会有冲击。常见的做法是采用五次多项式或梯形速度曲线,让速度从零平滑升起再降回零。RTB的jtraj内置了这种平滑逻辑:

t = linspace(0, 3, 200);  % 3秒,200个路径点
[q_traj, qd_traj, qdd_traj] = jtraj(q_start, q_end, t);

jtraj返回关节位置、速度、加速度三组序列。速度曲线呈钟形,起止点速度均为零,直接用这组关节角驱动仿真主循环,末端轨迹通常不会出现突然折返。如果不用工具箱,自己实现梯形速度也不复杂,关键是加速度阶段的时间Tacc不能超过总时长的一半,否则速度三角形无法闭合,这部分在5.2节里会用到。

5.2 笛卡尔空间走圆:控制插补步长的常见坑

很多任务要求末端在笛卡尔空间走直线或圆弧,比如涂胶、焊接。这时需要把目标轨迹离散成一系列末端位姿,再逐点调用逆解。以下代码在基座x-y平面内走一个半径为0.3m的圆:

% 笛卡尔空间走圆参数
r = 300;                 % 半径mm
h = 500;                 % 圆心高度mm
p_center = [600, 0, h];  % 圆心位置
Tf = 4;                  % 总时长s
n = 200;                 % 路径点数量
tvec = linspace(0, Tf, n);

% 梯形速度曲线:s从0到1,表示轨迹进度
s = trapezoid_s(tvec, 0.8, Tf);

q_k_prev = q_home;
Q = zeros(n, 6);
for k = 1:n
    % 圆上的笛卡尔位置
    ang = 2 * pi * s(k);
    p = p_center + [r * cos(ang), r * sin(ang), 0];
    
    % 姿态保持固定,末端朝向不旋转
    T_desired = eye(4);
    T_desired(1:3, 1:3) = R_fixed;  % 预先设定好的3x3姿态矩阵
    T_desired(1:3, 4) = p;
    
    % 上一周期关节角作为迭代初值
    opts = struct('max_iter', 50, 'tol', 1e-6, 'damp', 0.05);
    Q(k, :) = inverse_kinematics_jacobi(dh_table, T_desired, q_k_prev, opts);
    q_k_prev = Q(k, :)';
end

trapezoid_s函数按梯形速度曲线把时间映射到轨迹比例s:

function s = trapezoid_s(t, Tacc, Tf)
    % 输入:时间向量t、加速时长Tacc、总时长Tf
    % 输出:轨迹进度s,范围[0,1]
    Tacc = min(Tacc, Tf / 2);
    A = 1 / (Tf - Tacc);          % 匀速段斜率
    s = zeros(size(t));
    
    c1 = t < Tacc;                        % 加速段
    s(c1) = 0.5 * (A / Tacc) * t(c1).^2;
    
    c2 = t >= Tacc & t <= Tf - Tacc;      % 匀速段
    s(c2) = A * t(c2) - 0.5 * A * Tacc;
    
    c3 = t > Tf - Tacc;                   % 减速段
    tr = Tf - t(c3);
    s(c3) = 1 - 0.5 * (A / Tacc) * tr.^2;
end

绝大多数人第一次写笛卡尔轨迹插补时,会直接让s均匀地从0变到1,也就是每一步的角度增量恒定。这会导致末端角速度在起点和终点瞬间从零跳到最大值,仿真图上看不明显,到真实电机会表现为明显的顿挫感。用梯形速度曲线后,圆周运动的速度在空间中平滑变化,贴合真实控制需求。

位置用梯形速度,姿态也可以类似处理:用旋转矩阵的球面线性插值(RTB里是slertp或ctraj),或者直接把欧拉角按同一s曲线插值。固定姿态走圆时不需要姿态插值,但要注意R_fixed必须是合法旋转矩阵,行列式为1,否则逆解时误差永远收敛不到零。

5.3 用雅可比条件数在仿真中捕捉奇异

奇异类型 典型位形 现象 规避手段
肩部奇异 腕心位于J1轴线延长线上 关节1角速度骤增 路径点偏移x-y平面
肘部奇异 前臂与上臂完全伸直 关节2/3角速度骤增 修改途经点高度
腕部奇异 J4与J6轴线重合 J4/J6反向高速旋转 改用关节空间插值

每次调用逆解后计算雅可比的条件数,条件数过大说明当前位形接近奇异:

J = jacobian_numerical(dh_table, Q(k,:)');
condJ = cond(J);
if condJ > 1e4
    warning('轨迹点%03d接近奇异位形,条件数=%.2e', k, condJ);
end

条件数是雅可比最大奇异值与最小奇异值的比值,理想值是1,越大越趋近奇异。超过1e4通常意味着末端在一个方向上几乎失去运动能力,表现为某个关节角在相邻轨迹点间剧烈跳变。一旦检测到奇异,最省事的处理是让该段轨迹改用关节空间插值,绕过奇异的笛卡尔路径点。

6. 验证三件套:让正逆解经得起追问

逆解算法写完,下一步不是直接跑轨迹,而是做闭环验证。只验证目标位姿和最终位姿是否接近是不够的,因为恰好碰巧收敛的可能只是个别点位。常见做法是随机生成大量关节角,正解得到末端位姿,再用这个位姿作为逆解目标,对比解出的关节角和原始关节角:

rng(42);
n_test = 500;
err_trans = zeros(n_test, 1);
err_rot = zeros(n_test, 1);

opts = struct('max_iter', 50, 'tol', 1e-7, 'damp', 0.02);

for i = 1:n_test
    % 随机关节角,限制在中位附近避免碰限位
    q_true = (rand(6,1) - 0.5) * deg2rad(120);
    
    T_true = forward_kinematics(dh_table, q_true);
    q_ik = inverse_kinematics_jacobi(dh_table, T_true, zeros(6,1), opts);
    T_ik = forward_kinematics(dh_table, q_ik);
    
    % 平移误差,单位mm
    err_trans(i) = norm(T_true(1:3,4) - T_ik(1:3,4)) * 1000;
    
    % 旋转误差,单位deg
    R_err = T_true(1:3,1:3)' * T_ik(1:3,1:3);
    cos_angle = (trace(R_err) - 1) / 2;
    cos_angle = max(-1, min(1, cos_angle));
    err_rot(i) = acosd(cos_angle);
end

fprintf('平移误差中位数: %.4f mm\n', median(err_trans));
fprintf('旋转误差中位数: %.4f deg\n', median(err_rot));

500次随机样本里,平移误差中位数在0.01mm量级、旋转误差在0.01度量级,就说明实现可靠。若误差偏大,先检查阻尼因子是否过大导致迭代提前收敛,再检查logm误差向量和雅可比行顺序。

第二个验证手段是单轴单步法:固定其他五个关节,只给当前关节一个微小增量,观察末端位置是否沿以该关节轴线为中心的圆弧运动。这个测试能准确找出DH表中某一列参数的错误,比整体随机验证定位更精准。

第三,把末端绑定的目标位姿换成圆形路径,在轨迹上每个点记录逆解迭代收敛时的条件数和最大迭代次数。实用的调参技巧是把opts结构体提升为主脚本的可变参数,通过反复对比不同damp值下的收敛曲线,找到精度和稳定性平衡点。这样整套仿真验证完成后,移植到C++时只需要按同一结构体定义配置项即可。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐