本文最后更新于44 天前,其中的信息可能已经过时,如有错误请发送邮件到suzhech@gmail.com
ROS 学习指南:从 Ubuntu 基础到机器人控制
Ubuntu 系统基础
要想学习 ROS,需要先安装 Ubuntu 系统。下面是 Ubuntu 系统(Linux 系统)的一些基本命令。
Linux 基础命令参考手册
1. 目录操作命令
# pwd = Print Work Directory (显示当前工作目录)
pwd
# cd = Change Directory (切换目录)
cd /path/to/directory # 切换到指定目录
cd .. # 返回上一级目录
cd ~ # 返回 home 目录
cd - # 返回上一次访问的目录
# ls = list (列出目录内容)
ls # 列出当前目录内容
ls -a # 显示所有文件(包括隐藏文件)
ls -l # 详细列表显示(权限、大小、时间)
ls -la # 显示所有文件的详细信息
# mkdir = make directory (创建目录)
mkdir dirname # 创建目录
mkdir -p parent/child # 创建多级目录
2. 文件操作命令
# rm = remove (删除文件或目录)
rm filename # 删除文件
rm -r dirname # 删除文件夹(递归删除)
rm -rf dirname # 强制删除(不询问)
# mv = move (移动或重命名文件)
mv filename folder/filename # 移动文件
mv oldname newname # 重命名文件
# cp = copy (复制文件或目录)
cp file1 file2 # 复制文件
cp -r dir1 dir2 # 复制目录
3. 文件查看命令
# cat = catenate (连续输出文件内容)
cat filename # 显示整个文件内容
# less = "比 more 更强" (分页查看文件)
less filename # 分页查看文件内容
# tail = 取文件"尾巴" (查看文件末尾内容)
tail filename # 查看文件末尾10行
tail -f filename # 实时跟踪文件变化
tail -n 20 filename # 查看文件末尾20行
4. 文本编辑器
# vi = visual interface (命令行文本编辑器)
# vim = vi improved (改进版 vi)
# gedit = GNOME 的图形化文本编辑器
# 编辑器使用示例:
gedit hello.cpp # 打开图形化编辑器(推荐新手用)
vim hello.cpp # 打开命令行编辑器
vi hello.cpp # 打开 vi 编辑器
使用说明:
- 在终端中直接输入命令即可执行
- 注意命令和参数之间的空格
- 谨慎使用
rm -rf命令
ROS 基础概念
ROS 重要概念介绍
ROS 基础工作空间(Workspace):存放工程开发相关文件的文件夹。类似一个 IDE(例如 Pycharm)新建一个工程,就是一个工作空间。包含 4 个文件夹:
| 空间名称 | 英文名称 | 主要用途与说明 |
|---|---|---|
| src | Source Space | 放置功能包代码 |
| build | Build Space | 编译过程中产生的中间文件,不用过多关注 |
| devel | Development Space | 放置编译生成的可执行文件、库、脚本 |
| install | Install Space | 存放可执行文件,与上面 devel 有一定重复 |
工作流程
- 先创建一个工作空间
- 再创建一个功能包:
catkin_create_pkg 包名 依赖包 - 编辑源文件
- 编辑配置文件
- 编译并执行
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.bash
cd src
catkin_create_pkg robot_control roscpp std_msgs geometry_msgs
ROS 代码编写
基础代码结构
#include "ros/ros.h" // 写入ROS头文件
#include "geometry_msgs/Twist.h" // 写入速度消息处理头文件
// linear.x / linear.y / linear.z(线速度)
// angular.x / angular.y / angular.z(角速度)
// arguement count/arguement vector 用于传参
int main(int argc, char** argv){
ros::init(argc, argv, "move_robot_node"); // 通过初始化一个节点,相当于在ROS Master系统中注册了一个节点用于通信和管理进程
ros::NodeHandle nh; // 定义一个节点句柄用于创建发布者和订阅者
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("cmd_vel", 10); // 用Publisher创建了一个叫pub的发布者,发布的消息类型为Twist,发布到的话题名称为cmd_vel,队列长度为10
}
Rate 函数和 Sleep 函数
ros::Rate define_rate_name(20); // 会定义一个"节拍器", 以一秒二十次的频率工作
为了保证系统在运行过程中的稳定性,通常会使用 sleep 函数,这个函数是为了保证 ROS 系统按照某一特定频率运行。
特殊地,对于一个循环而言,例如:
for(int i = 0; i < 30; ++i){
some command;
rate.sleep();
}
由于循环执行了三十次,我们又知道”节拍器”一秒钟运行三十次,也就相当于在这里我们执行了三秒,通过”节拍器”这一”睡眠特性”我们能够让整个 ROS 中所出现的代码以我们预想的秒数执行,达到稳定的状态。
运动控制函数
根据 sleep 的这一特性,结合最终考核任务让小车前进一段距离后逆时针旋转 60° 后做矩形运动,我们可以编写前进函数和旋转函数:
void move_forward(ros::Publisher pub, double second){
geometry_msgs::Twist t1;
ros::Rate rate(10); // 一秒十次,相当于0.1秒每次
int n = second / 0.1;
t1.linear.x = 1.0;
for(int i = 0; i < n; i++){
pub.publish(t1);
rate.sleep();
}
ROS_INFO("前进中");
}
void turn_function(ros::Publisher pub_name, double angle){
geometry_msgs::Twist t1;
ros::Rate rate(10);
t1.angular.z = 0.1; // 弧度制即0.1rad/s
double angle_rad = angle * M_PI / 180.0; // M_PI=3.1415926.... 这一步目的是将角度转化为弧度
int n = angle_rad / 0.1;
for(int i = 0; i < n; i++){
pub_name.publish(t1);
rate.sleep();
ROS_INFO("旋转中");
}
t1.angular.z = 0;
pub_name.publish(t1);
ROS_INFO("旋转完成");
}
改进的运动控制(带停止指令)
void move_forward(ros::Publisher pub_name, double second){
geometry_msgs::Twist t1;
ros::Rate rate(10); // 一秒十次,相当于0.1秒每次
int n = second / 0.1;
t1.linear.x = 0.1;
for(int i = 0; i < n; i++){
pub_name.publish(t1);
rate.sleep();
ROS_INFO("前进中");
}
// 用pub_name作参数名的原因是和pub区分,实际查阅过程中发现可以用指针来代替,避免直接传参。
for(int i = 0; i < 3; i++){
t1.linear.x = 0.0;
pub_name.publish(t1);
ROS_INFO("前进完成");
}
}
基于偏转角的精确控制
#include "ros/ros.h"
#include "geometry_msgs/Twist.h"
#include "tf/tf.h"
#include "nav_msgs/Odometry.h"
#include <cmath>
double yaw = 0;
void callback(const nav_msgs::Odometry::ConstPtr& msg){
tf::Quaternion test(
msg->pose.pose.orientation.x,
msg->pose.pose.orientation.y,
msg->pose.pose.orientation.z,
msg->pose.pose.orientation.w
);
tf::Matrix3x3 m(test);
double roll, pitch;
m.getRPY(roll, pitch, yaw);
}
void turn_function(ros::Publisher pub_name, double target_angle){
geometry_msgs::Twist t1;
ros::Rate rate(10); // 依然0.1秒每次
t1.angular.z = 0.3; // 弧度制即0.3rad/s
ros::spinOnce();
ros::Duration(0.1).sleep();
double start_rad = yaw; // 只会赋值一次
double target_rad = target_angle * M_PI / 180.0;
while(ros::ok()){
double delta = yaw - start_rad;
if (delta > M_PI) delta -= 2 * M_PI;
if (delta < -M_PI) delta += 2 * M_PI;
if(fabs(delta) >= target_rad){
break;
}
else {
pub_name.publish(t1);
ros::spinOnce();
rate.sleep();
}
}
t1.angular.z = 0;
pub_name.publish(t1);
ros::Duration(0.2).sleep();
ROS_INFO("旋转完成");
}
完整的主程序
int main(int argc, char** argv){
setlocale(LC_ALL, "");
ros::init(argc, argv, "Robot_Control");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
ros::Subscriber sub = nh.subscribe("/odom", 10, callback);
move_forward(pub, 10);
turn_function(pub, 60);
move_forward(pub, 10);
turn_function(pub, 90);
move_forward(pub, 6);
turn_function(pub, 90);
move_forward(pub, 10);
turn_function(pub, 90);
move_forward(pub, 6);
return 0;
}
环境配置
# 启动 Gazebo 仿真环境
roslaunch turtlebot3_gazebo turtlebot3_world.launch
# 设置机器人模型
echo "export TURTLEBOT3_MODEL=burger" >> ~/.bashrc
source ~/.bashrc
# 机器人型号说明:
# burger(最小型)
# waffle(带摄像头、激光雷达)
# waffle_pi(带树莓派摄像头)
# 启动 Gazebo 和 Rviz
roslaunch turtlebot3_gazebo turtlebot3_gazebo_rviz.launch
进阶任务:导航目标点
单点导航
#include <ros/ros.h>
#include <move_base_msgs/MoveBaseAction.h>
#include <actionlib/client/simple_action_client.h>
#include <tf/tf.h>
#include <cmath>
int main(int argc, char** argv){
ros::init(argc, argv, "nav_goal");
actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> MoveBaseClient;
MoveBaseClient client("move_base", true);
ROS_INFO("等待启动成功");
client.waitForServer(ros::Duration(5.0));
ROS_INFO("连接成功!");
move_base_msgs::MoveBaseGoal goal;
goal.target_pose.header.frame_id = "map";
goal.target_pose.header.stamp = ros::Time::now();
goal.target_pose.pose.position.x = 3.0;
goal.target_pose.pose.position.y = 2.0;
double yaw = M_PI;
tf::Quaternion q = tf::createQuaternionFromYaw(yaw);
goal.target_pose.pose.orientation.x = q.x();
goal.target_pose.pose.orientation.y = q.y();
goal.target_pose.pose.orientation.z = q.z();
goal.target_pose.pose.orientation.w = q.w();
ROS_INFO("发送导航目标...");
client.sendGoal(goal);
client.waitForResult();
if(client.getState() == actionlib::SimpleClientGoalState::SUCCEEDED)
ROS_INFO("到达目标点!");
else
ROS_WARN("未能到达目标点!");
return 0;
}
多点导航
#include <ros/ros.h>
#include <move_base_msgs/MoveBaseAction.h>
#include <actionlib/client/simple_action_client.h>
#include <tf/tf.h>
#include <cmath>
int main(int argc, char** argv){
setlocale(LC_ALL, "");
ros::init(argc, argv, "nav_goal");
typedef actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> MoveBaseClient;
MoveBaseClient client("move_base", true);
ROS_INFO("等待启动成功");
client.waitForServer(ros::Duration(5.0));
ROS_INFO("连接成功!");
double goals[][3] = {
{-1.5, 0.5, 0},
{2.05, 0.3, 0},
{3, 2, M_PI},
{1.06, 2.48, 0},
{-6.04, 5.04, 0},
{-6.5, -3.42, 0},
};
for(int i = 0; i < 6; ++i){
move_base_msgs::MoveBaseGoal goal;
goal.target_pose.header.frame_id = "map";
goal.target_pose.header.stamp = ros::Time::now();
goal.target_pose.pose.position.x = goals[i][0];
goal.target_pose.pose.position.y = goals[i][1];
double yaw = goals[i][2];
tf::Quaternion q = tf::createQuaternionFromYaw(yaw);
goal.target_pose.pose.orientation.x = q.x();
goal.target_pose.pose.orientation.y = q.y();
goal.target_pose.pose.orientation.z = q.z();
goal.target_pose.pose.orientation.w = q.w();
ROS_INFO("发送导航目标...");
client.sendGoal(goal);
client.waitForResult();
if(client.getState() == actionlib::SimpleClientGoalState::SUCCEEDED)
ROS_INFO("到达目标点!");
else
ROS_WARN("未能到达目标点!");
ros::Duration(1.0).sleep();
}
ROS_INFO("至此,终于完成了所有考核任务");
return 0;
}
这份指南涵盖了从 Ubuntu 基础命令到 ROS 机器人控制的完整流程,包括基础运动控制和高级导航功能。通过逐步学习和实践这些内容,您将能够掌握 ROS 机器人编程的核心概念和技能。
