Ubuntu+ROS:六关节机械臂从零搭建到运动控制实战
1. 环境准备与ROS安装
六关节机械臂的开发之旅从搭建稳定的软件环境开始。Ubuntu系统作为ROS的官方推荐平台,提供了完美的开发基础。我推荐使用Ubuntu 20.04 LTS搭配ROS Noetic,这是目前最稳定的长期支持组合。
安装ROS前需要先配置软件源。国内用户建议使用清华或中科大的镜像源加速下载。打开终端,依次执行以下命令:
sudo sh -c 'echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
sudo apt update
安装完整版ROS桌面环境:
sudo apt install ros-noetic-desktop-full
安装完成后需要初始化rosdep,这个工具用于处理ROS包的依赖关系:
sudo rosdep init
rosdep update
最后设置环境变量,让系统找到ROS命令:
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
验证安装是否成功:打开新终端,输入roscore,如果看到ROS master启动信息,说明安装成功。我建议同时安装一些常用工具:sudo apt install python3-rosinstall python3-rosinstall-generator python3-wstool build-essential,这些在后续开发中会很有用。
2. 硬件连接与驱动配置
六关节机械臂通常通过USB或串口与工控机连接。首先确认硬件连接:检查每个关节模组的电源线和数据线是否牢固。我用的是Dynamixel XM系列舵机,这类智能舵机通常采用菊花链方式串联,最后通过一个USB2Dynamixel转换器连接到电脑。
驱动安装是关键步骤。对于串口设备,需要设置正确的权限:
sudo usermod -a -G dialout $USER
重新登录后,检查设备是否被识别:
ls /dev/ttyUSB*
如果看到类似/dev/ttyUSB0的设备,说明系统已经识别了转换器。对于其他类型的关节模组,可能需要安装特定驱动。比如某些步进电机控制器需要专门的SDK:
git clone https://github.com/motor-manufacturer/sdk.git
cd sdk
mkdir build && cd build
cmake .. && make
sudo make install
测试通信是否正常:使用rosrun运行相应的测试节点,查看是否能收到关节模组的反馈数据。如果出现权限问题,记得使用chmod命令给设备文件添加读写权限。
3. 创建机械臂URDF模型
URDF(统一机器人描述格式)是ROS中描述机器人模型的标准方式。创建一个名为my_arm的机械臂模型,首先建立工作空间:
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_create_pkg my_arm_description urdf
cd my_arm_description
mkdir urdf meshes launch config
在urdf文件夹中创建主模型文件my_arm.urdf。先从基座开始定义:
<?xml version="1.0"?>
<robot name="my_arm">
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.1" radius="0.2"/>
</geometry>
<material name="blue">
<color rgba="0 0 1 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder length="0.1" radius="0.2"/>
</geometry>
</collision>
<inertial>
<mass value="5"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/>
</inertial>
</link>
接着定义第一个关节,使用旋转关节类型:
<joint name="joint1" type="revolute">
<parent link="base_link"/>
<child link="link1"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="100" velocity="2.0"/>
</joint>
<link name="link1">
<visual>
<geometry>
<box size="0.1 0.1 0.3"/>
</geometry>
<material name="red">
<color rgba="1 0 0 1"/>
</material>
</visual>
</link>
重复类似过程定义其余五个关节。每个关节都需要明确定义父连杆、子连杆、原点位置、旋转轴和运动限制。完整的六关节模型大约需要200行XML代码。使用Xacro宏可以简化重复代码:
<xacro:macro name="arm_joint" params="name parent child length *origin">
<joint name="${name}" type="revolute">
<parent link="${parent}"/>
<child link="${child}"/>
<xacro:insert_block name="origin"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="100" velocity="2.0"/>
</joint>
</xacro:macro>
最后用RViz验证模型:roslaunch my_arm_description display.launch,如果能看到完整的机械臂模型,说明URDF文件正确。
4. ROS控制节点开发
控制节点是机械臂的大脑,负责将高级指令转换为关节运动命令。创建控制包:
cd ~/catkin_ws/src
catkin_create_pkg my_arm_control roscpp std_msgs sensor_msgs
在src目录下创建主控制节点arm_control_node.cpp:
#include <ros/ros.h>
#include <std_msgs/Float64.h>
#include <sensor_msgs/JointState.h>
class ArmControlNode {
public:
ArmControlNode() {
// 初始化发布器
joint1_pub = nh.advertise<std_msgs::Float64>("/joint1_position_controller/command", 10);
joint2_pub = nh.advertise<std_msgs::Float64>("/joint2_position_controller/command", 10);
// ... 其他关节发布器
// 订阅关节状态
joint_state_sub = nh.subscribe("/joint_states", 10, &ArmControlNode::jointStateCallback, this);
}
void moveToPosition(const std::vector<double>& positions) {
if(positions.size() != 6) {
ROS_ERROR("需要6个关节角度");
return;
}
std_msgs::Float64 angle;
angle.data = positions[0];
joint1_pub.publish(angle);
// 发布其他关节角度
}
private:
void jointStateCallback(const sensor_msgs::JointState::ConstPtr& msg) {
// 处理关节状态反馈
current_positions = msg->position;
}
ros::NodeHandle nh;
ros::Publisher joint1_pub, joint2_pub, joint3_pub, joint4_pub, joint5_pub, joint6_pub;
ros::Subscriber joint_state_sub;
std::vector<double> current_positions;
};
int main(int argc, char** argv) {
ros::init(argc, argv, "arm_control_node");
ArmControlNode node;
// 测试:移动到初始位置
std::vector<double> init_position = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
node.moveToPosition(init_position);
ros::spin();
return 0;
}
编译前需要配置CMakeLists.txt:
add_executable(arm_control_node src/arm_control_node.cpp)
target_link_libraries(arm_control_node ${catkin_LIBRARIES})
编译并运行节点:
cd ~/catkin_ws
catkin_make
source devel/setup.bash
rosrun my_arm_control arm_control_node
5. 运动规划与轨迹控制
简单的点位运动不能满足复杂任务需求,需要实现连续轨迹控制。ROS中的moveit是首选方案,但这里我们先手动实现一个简单的轨迹生成器。
创建轨迹控制节点trajectory_controller.cpp:
#include <ros/ros.h>
#include <trajectory_msgs/JointTrajectory.h>
class TrajectoryController {
public:
TrajectoryController() {
traj_pub = nh.advertise<trajectory_msgs::JointTrajectory>("/arm_controller/command", 10);
}
void executeTrajectory(const std::vector<std::vector<double>>& points,
const std::vector<double>& durations) {
if(points.size() != durations.size()) {
ROS_ERROR("点数和持续时间数不匹配");
return;
}
trajectory_msgs::JointTrajectory traj;
traj.joint_names = {"joint1", "joint2", "joint3", "joint4", "joint5", "joint6"};
ros::Time start_time = ros::Time::now();
for(size_t i = 0; i < points.size(); ++i) {
trajectory_msgs::JointTrajectoryPoint point;
point.positions = points[i];
point.time_from_start = ros::Duration(durations[i]);
traj.points.push_back(point);
}
traj_pub.publish(traj);
}
private:
ros::NodeHandle nh;
ros::Publisher traj_pub;
};
实现一个直线轨迹生成函数:
std::vector<std::vector<double>> generateLinearTrajectory(
const std::vector<double>& start,
const std::vector<double>& end,
int steps) {
std::vector<std::vector<double>> trajectory;
for(int i = 0; i <= steps; ++i) {
double t = static_cast<double>(i) / steps;
std::vector<double> point;
for(size_t j = 0; j < start.size(); ++j) {
point.push_back(start[j] + t * (end[j] - start[j]));
}
trajectory.push_back(point);
}
return trajectory;
}
在主函数中使用轨迹生成器:
int main(int argc, char** argv) {
ros::init(argc, argv, "trajectory_controller");
TrajectoryController controller;
std::vector<double> start = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
std::vector<double> end = {1.57, 0.5, -0.5, 0.3, 0.2, 0.1};
auto trajectory = generateLinearTrajectory(start, end, 50);
std::vector<double> durations(trajectory.size(), 0.1);
controller.executeTrajectory(trajectory, durations);
ros::spin();
return 0;
}
6. 系统集成与调试
将各个模块集成到统一的启动文件中。创建launch/arm_bringup.launch:
<launch>
<!-- 加载URDF模型 -->
<param name="robot_description"
command="$(find xacro)/xacro '$(find my_arm_description)/urdf/my_arm.urdf'" />
<!-- 发布关节状态 -->
<node name="robot_state_publisher" pkg="robot_state_publisher"
type="robot_state_publisher" />
<!-- 启动控制节点 -->
<node name="arm_control_node" pkg="my_arm_control"
type="arm_control_node" output="screen" />
<!-- 启动轨迹控制器 -->
<node name="trajectory_controller" pkg="my_arm_control"
type="trajectory_controller" output="screen" />
<!-- 启动RViz -->
<node name="rviz" pkg="rviz" type="rviz"
args="-d $(find my_arm_description)/config/arm.rviz" />
</launch>
调试过程中常用的工具命令:
查看节点关系图:rqt_graph
实时查看关节角度:rostopic echo /joint_states
监控系统状态:rosrun rqt_console rqt_console
常见问题排查:如果机械臂不动,首先检查硬件连接,然后使用rostopic list确认控制话题是否正常发布。使用rosservice call /controller_manager/list_controllers检查控制器状态。
性能优化建议:调整控制频率,通常100Hz足够;优化轨迹插值算法减少计算开销;使用硬件加速处理逆运动学计算。
7. 高级功能扩展
基础运动控制实现后,可以添加更多高级功能。实现一个简单的逆运动学求解器:
#include <kdl_parser/kdl_parser.hpp>
#include <kdl/chainiksolverpos_nr.hpp>
class InverseKinematicsSolver {
public:
InverseKinematicsSolver(const std::string& urdf_param = "/robot_description") {
if(!kdl_parser::treeFromParam(urdf_param, tree)) {
ROS_ERROR("Failed to construct kdl tree");
return;
}
tree.getChain("base_link", "end_effector", chain);
ik_solver.reset(new KDL::ChainIkSolverPos_NR(chain, fk_solver, ik_solver_vel, 100, 1e-6));
}
bool solve(const KDL::Frame& target, std::vector<double>& joint_angles) {
KDL::JntArray q_init(chain.getNrOfJoints());
KDL::JntArray q_result(chain.getNrOfJoints());
int ret = ik_solver->CartToJnt(q_init, target, q_result);
if(ret < 0) return false;
joint_angles.assign(q_result.data.data(),
q_result.data.data() + chain.getNrOfJoints());
return true;
}
private:
KDL::Tree tree;
KDL::Chain chain;
KDL::ChainFkSolverPos_recursive fk_solver;
KDL::ChainIkSolverVel_pinv ik_solver_vel;
std::unique_ptr<KDL::ChainIkSolverPos_NR> ik_solver;
};
添加碰撞检测功能:
#include <moveit/collision_detection/collision_common.h>
class CollisionChecker {
public:
bool checkCollision(const std::vector<double>& joint_angles) {
// 设置关节状态
robot_state::RobotState state(robot_model);
state.setJointGroupPositions("arm_group", joint_angles);
// 检查自碰撞
collision_detection::CollisionRequest req;
collision_detection::CollisionResult res;
collision_checker->checkSelfCollision(req, res, state);
return res.collision;
}
};
实现一个完整的抓取任务:
void executePickAndPlace() {
// 1. 移动到观察位置
moveToObservationPose();
// 2. 识别目标物体
auto object_pose = detectObject();
// 3. 规划抓取轨迹
auto approach_trajectory = planGraspApproach(object_pose);
executeTrajectory(approach_trajectory);
// 4. 执行抓取
closeGripper();
// 5. 移动到放置位置
auto place_trajectory = planPlaceTrajectory();
executeTrajectory(place_trajectory);
// 6. 释放物体
openGripper();
// 7. 返回初始位置
moveToHomePosition();
}
这些扩展功能让机械臂从简单的运动控制升级为能完成实际任务的智能系统。每个功能模块都可以单独测试,然后集成到主系统中。
更多推荐
所有评论(0)