Matlab六自由度机械臂正逆运动学仿真与轨迹规划实践
简介:基于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++时只需要按同一结构体定义配置项即可。
更多推荐
所有评论(0)