1. 项目概述
最近在机器人仿真领域完成了一个无人超市自动售货系统的仿真项目,核心是通过V-REP(现更名为CoppeliaSim)与Matlab联合控制UR5机械臂和移动底盘组成的自动化系统。这个项目看似简单,但在实际调试过程中遇到了不少挑战,特别是机械臂路径规划和底盘协同控制方面的问题。
这个仿真系统主要由三部分组成:UR5六轴机械臂、可移动底盘和二指夹爪。系统需要完成从货架识别商品、规划抓取路径、控制机械臂运动到底盘协同移动等一系列复杂操作。整个控制逻辑通过Matlab编写,与V-REP进行实时通信和数据交换。
提示:虽然项目中使用的是V-REP,但该软件已更名为CoppeliaSim,新用户建议直接使用最新版本。不过由于API兼容,本文中的代码仍然适用。
2. 仿真环境搭建
2.1 场景构建要点
在V-REP中搭建无人超市仿真场景时,有几个关键点需要注意:
-
物体命名规范:所有可控制的关节、夹爪和轮子都需要设置清晰易懂的标签,如
UR5_joint1、gripper_motor等。这能极大简化后续Matlab控制代码的编写。 -
碰撞体设置:货架、商品和机械臂之间需要正确设置碰撞体,否则会出现"穿模"现象。建议为每个商品单独设置碰撞体,而不是使用整个货架的碰撞体。
-
坐标系对齐:确保机械臂基座、底盘和世界坐标系正确对齐,否则运动控制会出现难以排查的问题。
2.2 Matlab-VREP通信初始化
通信初始化是项目的基础,以下代码展示了如何建立连接并获取对象句柄:
matlab复制% 加载V-REP远程API工具箱(安装V-REP时会自动提供)
vrep = remApi('remoteApi');
% 建立连接,参数说明:
% '127.0.0.1' - 本地主机
% 19999 - V-REP默认端口
% true - 等待直到连接建立
% true - 不持续尝试重新连接
% 2000 - 超时时间(ms)
% 5 - 线程周期(ms)
clientID = vrep.simxStart('127.0.0.1', 19999, true, true, 2000, 5);
if clientID < -1
error('无法连接到V-REP仿真环境,请检查V-REP是否正在运行');
end
% 获取机械臂关节句柄
jointHandles = zeros(1,6);
for i = 1:6
[~, jointHandles(i)] = vrep.simxGetObjectHandle(clientID, ['UR5_joint', num2str(i)], vrep.simx_opmode_blocking);
end
% 获取夹爪和底盘轮子句柄
[~, gripperHandle] = vrep.simxGetObjectHandle(clientID, 'gripper_motor', vrep.simx_opmode_blocking);
[~, leftWheel] = vrep.simxGetObjectHandle(clientID, 'left_wheel', vrep.simx_opmode_blocking);
[~, rightWheel] = vrep.simxGetObjectHandle(clientID, 'right_wheel', vrep.simx_opmode_blocking);
注意事项:初始化阶段务必使用
simx_opmode_blocking阻塞模式,确保所有句柄都正确获取后再进行后续操作。非阻塞模式可能导致句柄获取失败但程序继续执行,造成难以排查的错误。
3. 机械臂运动控制
3.1 逆运动学求解
机械臂运动控制的核心是逆运动学(IK)求解,即根据末端执行器的目标位置和姿态,计算机械臂各关节的角度。本项目采用V-REP内置的逆运动学求解器,相比在Matlab中自行计算有以下优势:
- 避免奇异点问题
- 求解速度更快
- 结果更稳定可靠
matlab复制function jointAngles = computeInverseKinematics(vrep, clientID, targetPos, targetEuler)
% targetPos: 目标位置[x,y,z] (相对于底盘坐标系)
% targetEuler: 目标欧拉角[α,β,γ]
% 调用V-REP中的Lua脚本计算逆解
[res, jointAngles] = vrep.simxCallScriptFunction(clientID, 'UR5',...
vrep.sim_scripttype_childscript, 'computeIK', [], targetPos, targetEuler, [], vrep.simx_opmode_blocking);
if res ~= vrep.simx_return_ok
error('逆运动学求解失败,目标位置可能不可达');
end
end
3.2 关节角度设置
获取逆解后,需要将各关节角度平滑地设置到机械臂上:
matlab复制function setJointAngles(vrep, clientID, jointHandles, angles, duration)
% angles: 目标关节角度数组(弧度)
% duration: 运动持续时间(秒)
if length(angles) ~= length(jointHandles)
error('关节角度数量与关节数量不匹配');
end
steps = max(round(duration / 0.05), 10); % 至少10步
currentAngles = zeros(1, length(jointHandles));
% 获取当前关节角度
for i = 1:length(jointHandles)
[~, currentAngles(i)] = vrep.simxGetJointPosition(clientID, jointHandles(i), vrep.simx_opmode_blocking);
end
% 插值运动
for t = 0:1/steps:1
interpAngles = currentAngles + t * (angles - currentAngles);
for i = 1:length(jointHandles)
vrep.simxSetJointTargetPosition(clientID, jointHandles(i), interpAngles(i), vrep.simx_opmode_oneshot);
end
pause(duration/steps);
end
end
实操心得:关节运动不宜过快,否则会导致机械臂抖动甚至仿真崩溃。建议单次运动时间不少于0.5秒,复杂路径可以分段执行。
4. 移动底盘控制
4.1 基本运动控制
底盘移动采用差速驱动方式,通过控制左右轮速度实现前进、后退和转向:
matlab复制function moveBase(vrep, clientID, leftWheel, rightWheel, distance, maxSpeed)
% distance: 移动距离(米),正数为前进,负数为后退
% maxSpeed: 最大轮速(rad/s),建议不超过1.0
if abs(distance) < 0.001
return; % 距离过小不移动
end
speed = sign(distance) * min(maxSpeed, 1.0);
vrep.simxSetJointTargetVelocity(clientID, leftWheel, speed, vrep.simx_opmode_streaming);
vrep.simxSetJointTargetVelocity(clientID, rightWheel, speed, vrep.simx_opmode_streaming);
% 简单计时控制,实际项目建议使用里程计反馈
wheelRadius = 0.1; % 轮子半径,根据实际模型调整
moveTime = abs(distance) / (wheelRadius * abs(speed));
pause(moveTime);
% 停止
vrep.simxSetJointTargetVelocity(clientID, leftWheel, 0, vrep.simx_opmode_streaming);
vrep.simxSetJointTargetVelocity(clientID, rightWheel, 0, vrep.simx_opmode_streaming);
end
4.2 机械臂-底盘协同控制
移动底盘时,机械臂需要实时调整姿态以避免碰撞:
matlab复制function moveToShelf(vrep, clientID, jointHandles, leftWheel, rightWheel, targetDistance)
% 初始化参数
stepDistance = 0.1; % 每次移动步长(米)
remainingDistance = targetDistance;
safetyHeight = 1.0; % 安全高度阈值(米)
while remainingDistance > 0
% 计算本次移动距离
moveDist = min(stepDistance, remainingDistance);
% 获取当前夹爪高度
[~, gripperPos] = vrep.simxGetObjectPosition(clientID, jointHandles(end), -1, vrep.simx_opmode_blocking);
currentHeight = gripperPos(3);
% 如果高度超过安全阈值,先调整机械臂
if currentHeight > safetyHeight
adjustArmHeight(vrep, clientID, jointHandles, safetyHeight - currentHeight);
end
% 移动底盘
moveBase(vrep, clientID, leftWheel, rightWheel, moveDist, 0.5);
% 更新剩余距离
remainingDistance = remainingDistance - moveDist;
end
end
5. 夹爪控制与抓取策略
5.1 夹爪力度控制
二指夹爪的控制需要考虑夹持力度和位置双重因素:
matlab复制function controlGripper(vrep, clientID, gripperHandle, action, gripForce)
% action: 'open'或'close'
% gripForce: 夹持力(N)
switch action
case 'close'
% 先设置夹持力
vrep.simxSetJointForce(clientID, gripperHandle, gripForce, vrep.simx_opmode_blocking);
% 然后设置目标位置
vrep.simxSetJointTargetPosition(clientID, gripperHandle, 0.02, vrep.simx_opmode_oneshot);
case 'open'
vrep.simxSetJointTargetPosition(clientID, gripperHandle, 0.1, vrep.simx_opmode_oneshot);
otherwise
error('无效的夹爪动作');
end
% 等待动作完成
pause(0.5);
end
5.2 完整抓取流程
结合机械臂运动和夹爪控制的完整抓取流程:
matlab复制function grabItem(vrep, clientID, jointHandles, gripperHandle, itemPos)
% itemPos: 物品位置[x,y,z] (世界坐标系)
% 1. 预抓取位置(物品上方10cm)
preGrabPos = itemPos + [0, 0, 0.1];
preGrabAngles = computeInverseKinematics(vrep, clientID, preGrabPos, [0, pi, 0]);
setJointAngles(vrep, clientID, jointHandles, preGrabAngles, 1.0);
% 2. 下降到抓取位置
grabAngles = computeInverseKinematics(vrep, clientID, itemPos, [0, pi, 0]);
setJointAngles(vrep, clientID, jointHandles, grabAngles, 0.5);
% 3. 闭合夹爪
controlGripper(vrep, clientID, gripperHandle, 'close', 15);
% 4. 抬起到预抓取位置
setJointAngles(vrep, clientID, jointHandles, preGrabAngles, 0.5);
% 5. 检查是否成功抓取
[~, gripState] = vrep.simxGetObjectFloatParameter(clientID, gripperHandle, 2012, vrep.simx_opmode_blocking);
if gripState < 0.01
error('抓取失败,物品可能未被夹住');
end
end
调试技巧:抓取力度需要根据物品重量和摩擦系数调整。可以先从较小的力度开始测试,逐步增加直到能稳定抓取物品。
6. 路径规划与避障
6.1 螺旋上升轨迹
对于需要绕过障碍物的场景,可以采用螺旋上升轨迹:
matlab复制function spiralTrajectory(vrep, clientID, jointHandles, startPos, endPos, numTurns, heightGain)
% startPos: 起始位置[x,y,z]
% endPos: 终点位置[x,y,z]
% numTurns: 螺旋圈数
% heightGain: 总高度增益
% 生成螺旋路径点
theta = linspace(0, 2*pi*numTurns, 100);
radius = linspace(0.3, 0.1, length(theta)); % 螺旋半径逐渐减小
x = startPos(1) + radius .* cos(theta);
y = startPos(2) + radius .* sin(theta);
z = linspace(startPos(3), startPos(3) + heightGain, length(theta));
% 添加直线段到终点
x = [x, linspace(x(end), endPos(1), 20)];
y = [y, linspace(y(end), endPos(2), 20)];
z = [z, linspace(z(end), endPos(3), 20)];
% 执行轨迹
for i = 1:length(x)
targetPos = [x(i), y(i), z(i)];
targetEuler = [0, pi, 0]; % 保持末端水平
try
jointAngles = computeInverseKinematics(vrep, clientID, targetPos, targetEuler);
setJointAngles(vrep, clientID, jointHandles, jointAngles, 0.05);
catch ME
warning('路径点%d不可达: %s', i, ME.message);
% 尝试调整高度后继续
targetPos(3) = targetPos(3) + 0.05;
jointAngles = computeInverseKinematics(vrep, clientID, targetPos, targetEuler);
setJointAngles(vrep, clientID, jointHandles, jointAngles, 0.05);
end
end
end
6.2 避障策略
在实际操作中,还需要考虑动态避障策略:
- 实时碰撞检测:通过V-REP的碰撞检测API监控机械臂与环境的碰撞状态
- 备选路径规划:当主路径受阻时,自动切换到备选路径
- 速度调整:接近障碍物时自动降低运动速度
matlab复制% 检查碰撞状态
[~, collisionState] = vrep.simxReadCollision(clientID, collisionHandle, vrep.simx_opmode_buffer);
if collisionState
% 触发避障策略
currentAngles = zeros(1, length(jointHandles));
for i = 1:length(jointHandles)
[~, currentAngles(i)] = vrep.simxGetJointPosition(clientID, jointHandles(i), vrep.simx_opmode_blocking);
end
% 回退到安全位置
safeAngles = currentAngles;
safeAngles(3) = safeAngles(3) - 0.2; % 调整第三个关节
setJointAngles(vrep, clientID, jointHandles, safeAngles, 0.5);
% 重新规划路径
replanPath();
end
7. 系统集成与调试技巧
7.1 主控制流程
将各个模块集成为完整的自动售货系统:
matlab复制function runAutomaticStore(clientID)
% 初始化系统
[vrep, jointHandles, gripperHandle, wheelHandles] = initializeSystem();
try
% 1. 移动到目标货架
moveToShelf(vrep, clientID, jointHandles, wheelHandles(1), wheelHandles(2), 1.5);
% 2. 识别目标商品(仿真中直接指定位置)
itemPos = [0.8, -0.3, 1.1]; % 假设这是可乐罐的位置
% 3. 抓取商品
grabItem(vrep, clientID, jointHandles, gripperHandle, itemPos);
% 4. 返回销售位置
moveToShelf(vrep, clientID, jointHandles, wheelHandles(1), wheelHandles(2), -1.5);
% 5. 放置商品到出货口
placeItem(vrep, clientID, jointHandles, gripperHandle, [0, 0.5, 0.8]);
disp('自动售货流程完成');
catch ME
disp(['运行出错: ' ME.message]);
% 紧急停止所有运动
emergencyStop(vrep, clientID, jointHandles, wheelHandles, gripperHandle);
end
end
7.2 调试经验分享
在实际调试过程中,积累了一些宝贵经验:
-
通信问题排查:
- 如果Matlab无法连接V-REP,首先检查V-REP是否开启了远程API服务
- 确保使用的端口号与V-REP设置一致(默认19999)
- 防火墙可能会阻止通信,必要时添加例外规则
-
机械臂异常姿态处理:
- 当机械臂出现奇异姿态时,不要强行发送新的目标位置
- 先记录当前各关节角度,然后设计一条安全的回退路径
- 可以在代码中添加姿态检查函数,提前预防奇异点
-
性能优化建议:
- 减少不必要的仿真数据交换,合理使用阻塞和非阻塞模式
- 复杂计算(如路径规划)尽量在Matlab中完成
- 使用
simx_opmode_buffer模式读取频繁变化的数据
-
仿真加速技巧:
- 在调试阶段可以适当提高仿真时间步长
- 关闭不必要的可视化效果能提升仿真速度
- 使用
simxSetBooleanParameter关闭动态计算,仅在需要时开启
8. 项目扩展与改进方向
虽然当前系统已经实现了基本功能,但仍有多个可以改进的方向:
-
视觉识别集成:
- 添加摄像头模型,通过图像处理识别商品位置
- 实现基于视觉伺服的控制策略
-
多机械臂协同:
- 扩展系统支持多个机械臂同时工作
- 设计任务分配和避碰算法
-
数字孪生应用:
- 将仿真模型与实际硬件对接
- 开发监控和预测性维护功能
-
智能路径规划:
- 引入机器学习算法优化路径
- 实现动态环境下的实时重规划
-
用户交互界面:
- 开发更友好的控制界面
- 添加仿真场景编辑功能
在实际开发中,建议先通过仿真验证核心算法,然后再逐步添加高级功能。仿真系统的优势在于可以快速迭代和测试各种想法,而不用担心硬件损坏或安全问题。