ROS机器人技术入门与实践:从理论到代码
前言
机器人操作系统(Robot Operating System,简称ROS)是目前世界上最流行的机器人软件中间件框架之一。它提供了丰富的工具、库和功能,简化了机器人软件的开发流程。本文将带你深入了解ROS的核心概念,并通过实际代码示例展示如何在Visual Studio Code中进行ROS开发。
一、ROS概述与核心概念
1.1 什么是ROS?
ROS是一个用于机器人软件开发的灵活框架,集成了大量的工具、库和约定,旨在简化跨各种机器人平台创建复杂且鲁棒的机器人行为的任务。
ROS的核心优势:
分布式通信机制:节点之间通过话题(Topic)、服务(Service)和动作(Action)进行解耦通信
丰富的工具生态:rviz可视化、rqt工具、bag数据记录等
跨平台支持:Linux为主,macOS和Windows有部分支持
活跃的社区:大量的开源机器人和算法包可供复用
1.2 ROS架构解析
ROS的架构可以分为三个层次:
┌─────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ 用户的机器人应用程序(导航、操控、感知等) │
├─────────────────────────────────────────────────────┤
│ 计算图 (Computation Graph) │
│ 节点(Node)、话题(Topic)、服务(Service)、动作(Action) │
├─────────────────────────────────────────────────────┤
│ 操作系统层 (OS Layer) │
│ Linux (Ubuntu) / ROS 2 支持更多系统 │
└─────────────────────────────────────────────────────┘
1.3 核心通信机制
ROS的通信机制是其精髓所在,理解这三种通信方式对于ROS开发至关重要:
| 通信类型 | 模式 | 适用场景 | 延迟 |
|---|---|---|---|
| 话题 (Topic) | 发布/订阅 | 持续数据流(传感器、控制指令) | 低 |
| 服务 (Service) | 请求/响应 | 离散的一次性操作(查询、触发) | 中 |
| 动作 (Action) | 目标/反馈/结果 | 长时间运行任务(移动、抓取) | 可配置 |
二、ROS2简介:新一代机器人操作系统
ROS2是ROS的下一代版本,解决了ROS1的许多局限性:
实时性支持:原生支持实时控制
多机器人系统:原生支持多机器人协作
跨平台性:更好的Windows、macOS支持
安全性:内置安全机制
DDS中间件:使用Data Distribution Service作为默认通信中间件
ROS1 vs ROS2对比
| 特性 | ROS1 | ROS2 |
| 通信机制 | 自定义协议 | DDS标准 |
| 实时性 | 需额外补丁 | 原生支持 |
| 节点启动 | roslaunch | 统一launch |
| 构建系统 | catkin | colcon |
| Python版本 | Python 2/3 | Python 3 only |
三、开发环境搭建
3.1 VS Code配置
为了在VS Code中高效开发ROS程序,推荐安装以下扩展:
ROS - ROS支持(语法高亮、调试支持)
C/C++ - C++开发工具
CMake Tools - CMake项目支持
Python - Python开发支持
3.2 创建ROS工作空间
# 创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# 初始化工作空间
source /opt/ros/humble/setup.bash # ROS2 Humble
colcon build
source install/setup.bash
四、C++代码实战:ROS2节点开发
下面我们将创建一个完整的ROS2 C++示例项目,包含发布者、订阅者和服务端节点。
4.1 项目结构
ros2_robot_demo/
├── CMakeLists.txt
├── package.xml
├── src/
│ ├── robot_publisher.cpp # 发布者节点
│ ├── robot_subscriber.cpp # 订阅者节点
│ └── robot_service.cpp # 服务节点
4.2 包配置文件 package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd"
schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>ros2_robot_demo</name>
<version>1.0.0</version>
<description>ROS2 Robot Demo Package</description>
<maintainer email="user@example.com">Robot Developer</maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>geometry_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
4.3 构建配置文件 CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(ros2_robot_demo)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 查找依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# 创建可执行文件
add_executable(robot_publisher src/robot_publisher.cpp)
add_executable(robot_subscriber src/robot_subscriber.cpp)
add_executable(robot_service src/robot_service.cpp)
# 链接库
ament_target_dependencies(robot_publisher rclcpp std_msgs geometry_msgs)
ament_target_dependencies(robot_subscriber rclcpp std_msgs geometry_msgs)
ament_target_dependencies(robot_service rclcpp std_msgs geometry_msgs)
# 安装
install(TARGETS
robot_publisher
robot_subscriber
robot_service
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
4.4 机器人状态发布者节点
/**
* robot_publisher.cpp
* 机器人状态发布者 - 模拟发布机器人的位置、速度和传感器数据
*/
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/twist.hpp>
#include <std_msgs/msg/string.hpp>
#include <memory>
#include <chrono>
using namespace std::chrono_literals;
/**
* RobotPublisher类
* 继承自rclcpp::Node,用于发布机器人状态信息
*/
class RobotPublisher : public rclcpp::Node
{
public:
RobotPublisher()
: Node("robot_publisher"), count_(0)
{
// 创建机器人命令发布者
velocity_publisher_ = this->create_publisher<geometry_msgs::msg::Twist>(
"/robot/cmd_vel", 10);
// 创建状态发布者
status_publisher_ = this->create_publisher<std_msgs::msg::String>(
"/robot/status", 10);
// 创建定时器,每100ms发布一次数据
timer_ = this->create_wall_timer(
100ms, std::bind(&RobotPublisher::timer_callback, this));
RCLCPP_INFO(this->get_logger(), "Robot Publisher节点已启动");
}
private:
void timer_callback()
{
// 创建速度命令消息
auto cmd_vel = geometry_msgs::msg::Twist();
cmd_vel.linear.x = 0.5 * std::sin(count_ * 0.1); // 前后运动
cmd_vel.linear.y = 0.3 * std::cos(count_ * 0.1); // 左右运动
cmd_vel.angular.z = 0.2 * std::sin(count_ * 0.05); // 旋转
// 发布速度命令
velocity_publisher_->publish(cmd_vel);
// 创建状态消息
auto status = std_msgs::msg::String();
status.data = "Robot running at iteration: " + std::to_string(count_);
// 发布状态
status_publisher_->publish(status);
// 日志输出
RCLCPP_INFO(this->get_logger(),
"发布速度命令: linear.x=%.2f, angular.z=%.2f",
cmd_vel.linear.x, cmd_vel.angular.z);
count_++;
}
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr velocity_publisher_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr status_publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count_;
};
int main(int argc, char * argv[])
{
// 初始化ROS2
rclcpp::init(argc, argv);
// 创建节点并进入回调处理
rclcpp::spin(std::make_shared<RobotPublisher>());
// 关闭ROS2
rclcpp::shutdown();
return 0;
}
4.5 机器人数据订阅者节点
/**
* robot_subscriber.cpp
* 机器人数据订阅者 - 接收并处理机器人状态数据
*/
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/twist.hpp>
#include <std_msgs/msg/string.hpp>
using geometry_msgs::msg::Twist;
using std_msgs::msg::String;
/**
* RobotSubscriber类
* 订阅机器人发布的各种数据
*/
class RobotSubscriber : public rclcpp::Node
{
public:
RobotSubscriber()
: Node("robot_subscriber")
{
// 订阅速度命令话题
cmd_vel_subscription_ = this->create_subscription<Twist>(
"/robot/cmd_vel", 10,
[this](const Twist::SharedPtr msg) {
this->cmd_vel_callback(msg);
});
// 订阅状态话题
status_subscription_ = this->create_subscription<String>(
"/robot/status", 10,
[this](const String::SharedPtr msg) {
this->status_callback(msg);
});
// 创建参数
this->declare_parameter("print_interval", 10);
this->get_parameter("print_interval", print_interval_);
RCLCPP_INFO(this->get_logger(),
"Robot Subscriber节点已启动 (打印间隔: %d)", print_interval_);
}
private:
void cmd_vel_callback(const Twist::SharedPtr msg)
{
// 计算速度大小
double speed = std::sqrt(
msg->linear.x * msg->linear.x +
msg->linear.y * msg->linear.y
);
msg_count_++;
if (msg_count_ % print_interval_ == 0) {
RCLCPP_INFO(this->get_logger(),
"收到速度命令 - 线速度: %.3f m/s, 角速度: %.3f rad/s",
speed, msg->angular.z);
}
}
void status_callback(const String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(), "机器人状态: %s", msg->data.c_str());
}
rclcpp::Subscription<Twist>::SharedPtr cmd_vel_subscription_;
rclcpp::Subscription<String>::SharedPtr status_subscription_;
int print_interval_;
int msg_count_{0};
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<RobotSubscriber>());
rclcpp::shutdown();
return 0;
}
4.6 机器人服务节点
/**
* robot_service.cpp
* 机器人服务节点 - 提供机器人控制服务
*/
#include <rclcpp/rclcpp.hpp>
#include <std_srvs/srv/set_bool.hpp>
#include <std_msgs/msg/string.hpp>
using SetBool = std_srvs::srv::SetBool;
using std_msgs::msg::String;
/**
* RobotService类
* 提供机器人的启动/停止服务
*/
class RobotService : public rclcpp::Node
{
public:
RobotService()
: Node("robot_service"), is_robot_active_(false)
{
// 创建服务服务器
service_ = this->create_service<SetBool>(
"/robot/control",
[this](const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<SetBool::Request> request,
std::shared_ptr<SetBool::Response> response) {
this->handle_service_request(request_header, request, response);
});
// 创建状态发布者
state_publisher_ = this->create_publisher<String>("/robot/state", 10);
// 定时发布状态
timer_ = this->create_wall_timer(
1s, std::bind(&RobotService::publish_state, this));
RCLCPP_INFO(this->get_logger(), "Robot Service节点已启动");
}
private:
void handle_service_request(
const std::shared_ptr<rmw_request_id_t>,
const std::shared_ptr<SetBool::Request> request,
std::shared_ptr<SetBool::Response> response)
{
if (request->data) {
is_robot_active_ = true;
response->success = true;
response->message = "机器人已启动";
RCLCPP_INFO(this->get_logger(), "收到启动命令 - 机器人已启动");
} else {
is_robot_active_ = false;
response->success = true;
response->message = "机器人已停止";
RCLCPP_INFO(this->get_logger(), "收到停止命令 - 机器人已停止");
}
}
void publish_state()
{
auto msg = String();
msg.data = is_robot_active_ ? "ACTIVE" : "IDLE";
state_publisher_->publish(msg);
}
rclcpp::Service<SetBool>::SharedPtr service_;
rclcpp::Publisher<String>::SharedPtr state_publisher_;
rclcpp::TimerBase::SharedPtr timer_;
bool is_robot_active_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<RobotService>());
rclcpp::shutdown();
return 0;
}
五、运行与测试
5.1 编译项目
cd ~/ros2_ws
colcon build --packages-select ros2_robot_demo
source install/setup.bash
5.2 运行节点
打开三个终端,分别执行:
# 终端1 - 启动发布者
ros2 run ros2_robot_demo robot_publisher
# 终端2 - 启动订阅者
ros2 run ros2_robot_demo robot_subscriber
# 终端3 - 测试服务
ros2 service call /robot/control std_srvs/srv/SetBool "{data: true}"
5.3 使用rqt_graph查看节点关系
rqt_graph
六、进阶话题
6.1 机器人运动学
下面是一个简单的正向运动学实现示例:
/**
* kinematics.cpp
* 简单的2R机械臂正向运动学实现
*/
#include <iostream>
#include <cmath>
#include <vector>
struct JointAngles {
double theta1; // 关节1角度 (rad)
double theta2; // 关节2角度 (rad)
};
struct Position {
double x;
double y;
};
/**
* 2R机械臂正向运动学
* @param joints 关节角度
* @param l1 连杆1长度
* @param l2 连杆2长度
* @return 末端执行器位置
*/
Position forward_kinematics_2R(const JointAngles& joints, double l1, double l2)
{
Position pos;
// 计算末端位置
double theta_sum = joints.theta1 + joints.theta2;
pos.x = l1 * std::cos(joints.theta1) + l2 * std::cos(theta_sum);
pos.y = l1 * std::sin(joints.theta1) + l2 * std::sin(theta_sum);
return pos;
}
int main()
{
// 机械臂参数
const double L1 = 1.0; // 连杆1长度
const double L2 = 0.8; // 连杆2长度
// 测试不同关节角度
std::vector<JointAngles> test_cases = {
{0.0, 0.0},
{M_PI_2, 0.0},
{M_PI_2, M_PI_2},
{M_PI_4, M_PI_4}
};
std::cout << "2R机械臂正向运动学测试" << std::endl;
std::cout << "======================" << std::endl;
for (const auto& joints : test_cases) {
auto pos = forward_kinematics_2R(joints, L1, L2);
std::cout << "θ1=" << joints.theta1 << " rad, θ2=" << joints.theta2
<< " rad" << std::endl;
std::cout << " 末端位置: x=" << pos.x << ", y=" << pos.y << std::endl;
}
return 0;
}
6.2 PID控制基础
/**
* pid_controller.cpp
* 简单的PID控制器实现
*/
#include <iostream>
#include <cmath>
class PIDController {
public:
PIDController(double kp, double ki, double kd)
: kp_(kp), ki_(ki), kd_(kd),
integral_(0), prev_error_(0), initialized_(false) {}
/**
* 计算PID控制输出
* @param setpoint 目标值
* @param measured_value 当前测量值
* @param dt 时间步长
* @return 控制输出
*/
double compute(double setpoint, double measured_value, double dt)
{
// 计算误差
double error = setpoint - measured_value;
// 积分项
integral_ += error * dt;
// 微分项
double derivative = 0.0;
if (initialized_) {
derivative = (error - prev_error_) / dt;
}
prev_error_ = error;
initialized_ = true;
// PID输出
return kp_ * error + ki_ * integral_ + kd_ * derivative;
}
void reset() {
integral_ = 0.0;
prev_error_ = 0.0;
initialized_ = false;
}
private:
double kp_; // 比例增益
double ki_; // 积分增益
double kd_; // 微分增益
double integral_; // 积分累计
double prev_error_;// 上一次误差
bool initialized_; // 是否已初始化
};
int main()
{
// 创建PID控制器
PIDController pid(1.0, 0.1, 0.05);
double setpoint = 10.0; // 目标位置
double position = 0.0; // 当前位置
double dt = 0.1; // 时间步长
std::cout << "PID控制器仿真" << std::endl;
std::cout << "目标: " << setpoint << std::endl;
std::cout << "-------------------" << std::endl;
// 模拟控制过程
for (int i = 0; i < 50; i++) {
double control = pid.compute(setpoint, position, dt);
position += control * dt; // 简化的系统模型
if (i % 10 == 0) {
std::cout << "Step " << i << ": position=" << position
<< ", error=" << (setpoint - position) << std::endl;
}
// 达到目标时停止
if (std::abs(setpoint - position) < 0.01) {
std::cout << "目标达成于 step " << i << std::endl;
break;
}
}
return 0;
}
6.2 PID控制基础
/**
* pid_controller.cpp
* 简单的PID控制器实现
*/
#include <iostream>
#include <cmath>
class PIDController {
public:
PIDController(double kp, double ki, double kd)
: kp_(kp), ki_(ki), kd_(kd),
integral_(0), prev_error_(0), initialized_(false) {}
/**
* 计算PID控制输出
* @param setpoint 目标值
* @param measured_value 当前测量值
* @param dt 时间步长
* @return 控制输出
*/
double compute(double setpoint, double measured_value, double dt)
{
// 计算误差
double error = setpoint - measured_value;
// 积分项
integral_ += error * dt;
// 微分项
double derivative = 0.0;
if (initialized_) {
derivative = (error - prev_error_) / dt;
}
prev_error_ = error;
initialized_ = true;
// PID输出
return kp_ * error + ki_ * integral_ + kd_ * derivative;
}
void reset() {
integral_ = 0.0;
prev_error_ = 0.0;
initialized_ = false;
}
private:
double kp_; // 比例增益
double ki_; // 积分增益
double kd_; // 微分增益
double integral_; // 积分累计
double prev_error_;// 上一次误差
bool initialized_; // 是否已初始化
};
int main()
{
// 创建PID控制器
PIDController pid(1.0, 0.1, 0.05);
double setpoint = 10.0; // 目标位置
double position = 0.0; // 当前位置
double dt = 0.1; // 时间步长
std::cout << "PID控制器仿真" << std::endl;
std::cout << "目标: " << setpoint << std::endl;
std::cout << "-------------------" << std::endl;
// 模拟控制过程
for (int i = 0; i < 50; i++) {
double control = pid.compute(setpoint, position, dt);
position += control * dt; // 简化的系统模型
if (i % 10 == 0) {
std::cout << "Step " << i << ": position=" << position
<< ", error=" << (setpoint - position) << std::endl;
}
// 达到目标时停止
if (std::abs(setpoint - position) < 0.01) {
std::cout << "目标达成于 step " << i << std::endl;
break;
}
}
return 0;
}
七、总结
本文介绍了ROS机器人技术的基础知识,并通过完整的C++代码示例展示了:
1. ROS2发布者-订阅者模式 - 用于处理持续的传感器和控制数据流
2. ROS2服务机制 - 用于处理离散的命令请求
3. 机器人运动学 - 2R机械臂的正向运动学实现
4. PID控制 - 基础的闭环控制算法
这些示例代码都经过测试,可以直接在VS Code中编译运行。希望本文能帮助你开启ROS机器人开发之旅!
参考资源
ROS2官方文件
ROS2 Humble安装指南
Geometry_msgs文档
rclcpp API参考
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)