简介:这是一个面向机器人控制学习者的MATLAB机械臂控制仿真源码,重点演示多自由度机械臂在复杂障碍物环境中完成避障与动作规划,覆盖机械臂动力学建模、Simulink仿真环境搭建、PID闭环控制、逆动力学解算与路径规划等核心环节。适合自动化、机器人方向的在校学生与工程师用来验证控制算法、理解关节空间与工作空间的转换关系。资源为zip压缩包,内部仅含1个m文件,大小约5KB,代码集中且注释清晰,便于直接阅读、修改和调试。目前已有185人学习下载。通过该源码,可以掌握多自由度机械臂的关节坐标设置、重力/摩擦力等物理效应补偿方式、基于距离传感器的避障算法以及三维动态轨迹可视化方法,还能根据仿真中实时反馈的关节响应调整控制参数,为进一步扩展自由度或优化控制性能提供了可运行的基础模板。
1. 自由度机械臂控制仿真的本质:穿过障碍物不是画一根直线
第一次在 MATLAB 里做自由度机械臂控制仿真的人,最容易看见的现象是“穿模”:末端执行器轨迹画得笔直,大臂和小臂却把障碍物扎了进去。原因是默认轨迹插值只约束末端位置,没有约束其他连杆,自由度越多的机械臂,这种假通过越难被发现。这篇文章按真实项目里最常见的一套做法,讲清楚如何用 MATLAB 完成六自由度机械臂建模、避障路径规划、碰撞检测和动态显示,并给出能直接改、直接跑的源码片段。适合做机械臂抓取、搬运和障碍穿越仿真的学生、算法工程师,以及现场调试机械臂的机器人工程师。
2. 用 MATLAB 建六自由度机械臂模型:DH 参数、正逆解与 qlim 设置
2.1 六自由度机械臂 DH 参数表:自由度到底写在哪个变量里
六自由度机械臂的仿真源码,第一步永远是运动学建模。在 Robotics Toolbox for MATLAB 里,一个旋转关节只需要四个参数决定:a是相邻关节轴的公垂线长度,alpha是绕 X 轴的连杆扭角,d是沿 Z 轴的偏置,qlim才是真正让“自由度”发挥限制作用的变量——每个关节允许旋转的范围。
下面这组参数是一套用于演示的 6R 关节型机械臂,不精确对应任何一家厂商的产品,但结构足够用来做避障仿真:
| 连杆 | d(m) | a(m) | alpha(rad) | theta offset(rad) |
|---|---|---|---|---|
| 1 | 0.333 | 0 | pi/2 | 0 |
| 2 | 0 | 0.19 | 0 | -pi/2 |
| 3 | 0 | 0.216 | pi/2 | 0 |
| 4 | 0.15 | 0 | -pi/2 | 0 |
| 5 | 0.1 | 0 | pi/2 | 0 |
| 6 | 0.1 | 0 | 0 | 0 |
写进 MATLAB 时,每个 Link 一行,qlim不要乱填,它决定后面模拟机械臂穿过障碍物时关节空间的可达范围:
% dof6_model.m % Link 参数按顺序写入:d, a, alpha, offset, qlim L(1) = Link('d', 0.333, 'a', 0, 'alpha', pi/2, 'offset', 0, 'qlim', [-pi pi]); L(2) = Link('d', 0, 'a', 0.19, 'alpha', 0, 'offset', -pi/2, 'qlim', [-pi pi]); L(3) = Link('d', 0, 'a', 0.216, 'alpha', pi/2, 'offset', 0, 'qlim', [-pi pi]); L(4) = Link('d', 0.15, 'a', 0, 'alpha', -pi/2, 'offset', 0, 'qlim', [-pi pi]); L(5) = Link('d', 0.1, 'a', 0, 'alpha', pi/2, 'offset', 0, 'qlim', [-pi pi]); L(6) = Link('d', 0.1, 'a', 0, 'alpha', 0, 'offset', 0, 'qlim', [-pi pi]); robot = SerialLink(L, 'name', 'dof6-arm');这里的offset会被叠加到关节角上,模拟机械臂源码里常见的零点标定。很多仿真发散问题不是出现在算法层,而是 DH 参数里某个alpha符号写反了,导致肩关节镜像,逆解收敛到另一个完全不同的构型。
2.2 用 SerialLink 建模型并验证正逆解
模型建好以后,先做一次正逆解闭环,确认 DH 参数写对了,再开始做避障路径。正运动学是把关节角变成末端位姿,逆运动学正好相反,都是机械臂控制仿真的地基:
% fk_ik_check.m q0 = [0, -pi/4, 0, pi/3, -pi/4, 0]; % 初始关节角 T_end = robot.fkine(q0); % 正运动学 q_ik = robot.ikcon(T_end, q0); % 带初值迭代逆解 fprintf('位置误差: %.3e m\n', norm(T_end.t - robot.fkine(q_ik).t));ikcon是迭代逆解,不需要机械臂满足球腕结构,比ikine6s适用范围更广。代价是它对初值敏感:同一个末端位姿对应多组逆解,如果初值落在另一个“分支”上,得到的关节角可能跨越很大的角度,表现在动态效果里就是机械臂路径突然跳动。
验证时看两点:一是位置误差能否到 1e-6 量级,二是q_ik是否落在qlim内。前者说明 DH 参数和正解一致,后者说明这一组逆解在机械臂的真实工作范围里。
2.3 关节空间与笛卡尔空间的插值选择:自由度约束在哪里起作用
穿过障碍物的路径通常不是一条直线,而是绕障碍物走的曲线。生成曲线后有两种插值方式,我一般先用关节空间插值,因为它不会因为腕部奇异产生关节速度突变:
% joint_traj_example.m q_start = [0, -pi/4, 0, pi/3, -pi/4, 0]; q_end = [0, pi/6, -pi/3, pi/4, pi/6, pi/3]; tvec = 0:0.05:2; qtraj = jtraj(q_start, q_end, tvec); % 五次多项式关节插补对应地,笛卡尔空间插值会直接生成末端位姿序列:
T1 = robot.fkine(q_start); T2 = robot.fkine(q_end); cpath = ctraj(T1, T2, length(tvec));| 插值对象 | 优势 | 风险 |
|---|---|---|
| 关节空间 | 轨迹平滑,不会因为奇异点爆炸 | 末端路径不可控,可能绕远路 |
| 笛卡尔空间 | 末端走直线或圆弧,工艺上好理解 | 接近奇异时逆解跳变,仿真发散最常见 |
实际做障碍穿越时,我建议把势场法生成的路径先放到笛卡尔空间检查一次,确认末端没有扎进障碍,再转成关节轨迹做动态仿真。检查用到的碰撞检测,就是下一章的核心。
3. 机械臂避障路径在 MATLAB 里的实现:势场规划与连杆碰撞检测
3.1 碰撞检测先于插值:把六自由度机械臂退化成连杆线段
“模拟机械臂穿过障碍物的动态效果”里,“穿过”有两个意思:一个是末端从障碍物旁边通过,另一个是整条机械臂都不和障碍物相交。后者才是真实的避障。把机械臂每个连杆看成一段线段,是 MATLAB 仿真里最常用的简化做法。
function pts = linkPoints(robot, q) % 把旋转关节状态 q 转换为连杆线段端点 pts = zeros(robot.n + 1, 3); T = robot.base; pts(1,:) = T.t'; % 基座位置 for i = 1:robot.n T = T * robot.links(i).A(q(i)); % 逐个关节累乘 pts(i+1,:) = T.t'; % 第 i 个关节坐标系原点 end endlinkPoints返回从基座到末端一共 7 个点,每两个相邻点组成一段连杆。之后每一段都和障碍物球体做距离判断:
function hit = checkCollision(p1, p2, center, r) % p1 p2: 连杆两端点, center: 球心, r: 球半径 v = p2 - p1; w = center - p1; t = dot(w, v) / dot(v, v); t = min(1, max(0, t)); % 投影点限制在线段内 closest = p1 + t * v; hit = norm(closest - center) < r + 0.02; % 0.02 是连杆半径补偿 end参数0.02是连杆半径和额外安全距离的总和,实际机械臂越粗,这个值应该越大。只检查末端会漏掉大臂,只检查两个点在球外也会漏掉整段穿入,所以必须遍历所有连杆段并计算投影点距离。这种算法比导入三维网格再求交快得多,足够支撑实时动态显示。
3.2 人工势场法生成穿过障碍物的中间节点
拿到碰撞检测函数之后,还需要生成一条不撞障碍物的路径。人工势场法是视觉仿真里最直观的方案:目标点产生引力,障碍物产生斥力,路径沿着合力方向前进。
function path = planAttRep(start, goal, obs) % obs = [x y z r] 球心坐标和半径 path = start(:)'; pos = start(:); step = 0.03; % 末端步长 k_att = 1.0; % 引力系数 k_rep = 0.3; % 斥力系数 d0 = 0.18; % 斥力影响半径 for iter = 1:3000 dist_g = norm(pos - goal(:)); if dist_g < 0.02, break; end F_att = k_att * (goal(:) - pos) / dist_g; dist_o = norm(pos - obs(1:3)); F_rep = zeros(3, 1); if dist_o < d0 F_rep = k_rep * (1/dist_o - 1/d0) ... / (dist_o^3) * (pos - obs(1:3)); end pos = pos + step * (F_att + F_rep); path = [path; pos(:)']; end end这个算法的缺点是会有局部极小点,比如障碍物正好挡在目标点前方且斥力大于引力时,机械臂会停在原地抖动。解决方法是把势场法结果作为初始路径,再做一次碰撞检查。实际工程里更稳的是 RRT 或 RRTConnect,但势场法胜在代码短、动态效果好,适合先跑通整个仿真链路。
生成笛卡尔路径后,还需要把每个点位姿转成关节角,然后用linkPoints检查整机是否碰撞:
% plan_and_check.m T1 = robot.fkine(q_start); T2 = robot.fkine(q_end); path_xyz = planAttRep(T1.t, T2.t, obs); q_path = zeros(size(path_xyz, 1), 6); q_current = q_start; for i = 1:size(path_xyz, 1) T_target = eye(4); T_target(1:3, 1:3) = T2.R; % 保持目标姿态 T_target(1:3, 4) = path_xyz(i, :); q_current = robot.ikcon(T_target, q_current); q_path(i, :) = q_current; end这里每一步逆解都用上一步的关节角作为初值,能在一定程度上保持关节空间连续性。
3.3 碰撞检测放在逆解之前还是之后:一个容易漏检的顺序问题
很多新手把碰撞检测放在逆解之前,只判断末端位置是否在障碍物外面。这当然更快,但对六自由度机械臂来说不够,因为中间四个关节完全可能扫过障碍物。
| 检测时机 | 优点 | 缺点 |
|---|---|---|
| 只检测末端轨迹 | 计算量小 | 大臂小臂全部漏检 |
| 末端位置加连杆线段检测 | 能覆盖典型工况 | 无法处理复杂形状物体 |
| 完整三维网格碰撞 | 精度高 | 仿真速度慢,不适合实时动画 |
我一般把碰撞检测放在逆解之后,构成“逆解-正解-检测-修正”的闭环:
function feasible = isPathFeasible(robot, q_path, obs) feasible = true; for i = 1:size(q_path, 1) pts = linkPoints(robot, q_path(i, :)); for j = 1:size(pts, 1) - 1 if checkCollision(pts(j, :), pts(j+1, :), obs(1:3), obs(4)) feasible = false; return; end end end end如果返回false,就减小step重新规划,或者调整斥力系数。这样写出来的源码,把运动学、碰撞检测、路径规划三个模块分开,后面换 DH 参数或者换障碍物半径都不用动主框架。
4. 模拟机械臂穿过障碍物的动态效果:动画、参数表与仿真发散排查
4.1 动态显示:用 bot.plot 把机械臂路径变成可录制动画
有了q_path,下一步是让它动起来。这时候直接robot.plot(q_path)会有两个问题:路径点太疏,看起来像瞬移;障碍物没有画出来,无法判断是否真实穿越。先补一个画球函数:
function drawSphere(center, radius) [X, Y, Z] = sphere(30); surf(center(1) + radius * X, center(2) + radius * Y, ... center(3) + radius * Z, 'FaceAlpha', 0.35, ... 'EdgeColor', 'none', 'FaceColor', [0.8 0.4 0.2]); end然后用jtraj在路径点之间加密,生成平滑的关节轨迹:
q_smooth = q_path(1, :); for i = 2:size(q_path, 1) q_smooth = [q_smooth; jtraj(q_path(i-1, :), q_path(i, :), 10)]; end figure('Color', 'w'); hold on; axis equal; drawSphere(obs(1:3), obs(4)); robot.plot(q_smooth, 'fps', 30, 'trail', 'r-');需要保存成源码级演示视频时,可以用getframe逐帧写进 MPEG-4 文件:
wr = VideoWriter('pass_obstacle.mp4', 'MPEG-4'); wr.FrameRate = 30; open(wr); for i = 1:size(q_smooth, 1) robot.plot(q_smooth(i, :), 'fps', 30); writeVideo(wr, getframe(gcf)); end close(wr);getframe会抓当前 axes 内容,所以drawSphere画的障碍物会一起录进视频。此时看到的“动态效果”已经不是末端直线,而是整条机械臂在势场路径引导下绕过球体的完整运动。
4.2 穿越仿真里最常调的 3 个参数
参数调不好,最常见结果就是仿真发散或者穿模。下面这张表是速查项:
| 参数 | 默认值 | 调整方向 |
|---|---|---|
| step | 0.03m | 步长过大容易漏检,过小路径点太多、动画变慢 |
| 0.02m | 连杆半径补偿 | 越接近真实机械臂外径越好,但必须留安全余量 |
| k_rep / d0 | 0.3 / 0.18 | 障碍物挡路时增大;路径震荡时减小 |
| qlim | [-pi, pi] | 收敛不到路径时放宽;真机限位根据数据表缩窄 |
仿真发散出现时,先不要改控制器,先看相邻两帧的关节角是否发生跳变。逆解多解切换会让某个关节角瞬间变化超过 pi 弧度,表现为机械臂从画面下方闪到上方。代码里可以加一个检测:
dq = diff(q_smooth); dq_wrapped = atan2(sin(dq), cos(dq)); % 映射到 [-pi, pi] if max(abs(dq_wrapped(:))) > pi/2 warning('关节角发生跳变,检查逆解初值或路径规划'); end4.3 机械臂偏差来源:逆解失败和碰撞漏检的区别
机械臂偏差这个词在仿真里常被混用。一类是逆解误差:ikcon迭代没收敛到目标位姿,末端静态误差达到毫米甚至厘米级,体现在动画里是末端在目标点附近漂移;另一类是动态路径偏差:路径规划或插值没有覆盖真实机械臂运动,导致连杆扫过障碍物。
前者要回到 2.2 节的正逆解闭环,把位置误差打印出来。如果误差大于 1e-3,先检查qlim和alpha。后者则要把碰撞检测从末端扩展到全部连杆段,再用isPathFeasible对q_smooth做一次离线批量检查。动态穿越通过,不等于动态效果安全,只有离线逐帧碰撞检测也通过,才能在后续接真机。
5. 进阶验证:从关节轨迹到 Simulink 仿真和 ROS 机械臂开发
验证关节轨迹最直接的办法,是把q_smooth输出成timeseries,交给 Simulink 的 From Workspace 模块。在 MATLAB 工作区执行:
Q = timeseries(q_smooth, tvec);在 Simulink 里添加 From Workspace 模块,Data 参数填Q,输出就是按时间排列的六维关节角。把输出接给 Simscape 机械臂模型,或者接给一个简单的六自由度关节驱动模块,就能对比原始控制量和实际机械臂位姿的偏差。这样做的意义在于:机械臂穿过障碍物的动画由 MATLAB 图形引擎渲染,Verification 则由 Simulink 的数值积分完成,两者互相验证。
如果项目后续走 ROS 机械臂开发,常见做法是先把关节轨迹存成 CSV,再写一个 ROS 节点发布joint_states。在 RVIZ 里加载同 URDF 模型后,逐帧查看连杆是否和障碍物 Marker 相交。URDF 的几何模型比linkPoints的线段模型精确,所以能发现 0.02m 补偿半径不够的情况。我一般把 MATLAB 仿真结果当作快速原型,ROS 和 gazebo 仿真作为二次验证。
最后一层验证是在物理真机上做。无论是六自由度还是七自由度机械臂,都要在低速模式下先空跑一遍路径,观察电机电流和关节跟随误差。仿真里 0.02m 的安全余量,真机上至少放大到 0.05m,因为 DH 标定误差和控制器延迟会让实际轨迹滞后于规划轨迹。调试时把这个余量暴露成输入参数,而不是写死在碰撞检测函数里,后续换机械臂型号只需要改一个变量。
本文还有配套的精品资源,点击获取