目录
[一、ROS2 消息对象的常见写法](#一、ROS2 消息对象的常见写法)
[1.1 创建消息对象的基本格式](#1.1 创建消息对象的基本格式)
[1.2 以 Twist 为例创建速度消息](#1.2 以 Twist 为例创建速度消息)
[1.3 给消息字段赋值](#1.3 给消息字段赋值)
[二、ROS2 发布器 Publisher 的写法](#二、ROS2 发布器 Publisher 的写法)
[2.1 发布器的基本定义方式](#2.1 发布器的基本定义方式)
[2.2 创建发布器](#2.2 创建发布器)
[2.3 发布消息](#2.3 发布消息)
[2.4 完整发布器示例代码](#2.4 完整发布器示例代码)
[2.5 发布器代码重点解释](#2.5 发布器代码重点解释)
[三、ROS2 订阅器 Subscriber 的写法](#三、ROS2 订阅器 Subscriber 的写法)
[3.1 订阅器的基本定义方式](#3.1 订阅器的基本定义方式)
[3.2 创建订阅器](#3.2 创建订阅器)
[3.3 回调函数中接收消息](#3.3 回调函数中接收消息)
[3.4 完整订阅器示例代码](#3.4 完整订阅器示例代码)
[3.5 发布端和订阅端访问字段的区别](#3.5 发布端和订阅端访问字段的区别)
摘要
本篇是 ROS2 话题通信保姆级教程专题 的第二篇文章。
上一篇文章主要讲解了 ROS2 中常见的消息类型,例如 std_msgs、geometry_msgs、nav_msgs、sensor_msgs,并重点说明了 geometry_msgs::msg::Twist、Pose、PoseStamped、TransformStamped 等消息的含义。
为了方便大家按照顺序学习,ROS2 话题通信专题文章整理如下:
第一篇:ROS2 常见消息类型保姆级教程
ROS2 常见消息类型保姆级教程-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/161693378?spm=1001.2014.3001.5501第三篇:ROS2 常用命令保姆级教程:node、topic、interface、run、launch、param 一文搞懂
史上最全 ROS2 常用命令保姆级教程:节点、话题、消息结构、参数、服务和动作一篇讲透-CSDN博客
https://blog.csdn.net/m0_58954356/article/details/161726502?spm=1001.2014.3001.5501这一篇开始进入 ROS2 话题通信实战,重点讲清楚几个问题:
1. ROS2 中如何创建消息对象?
2. Publisher 发布器怎么写?
3. Subscriber 订阅器怎么写?
本文仍然以移动机器人常用的速度控制消息:
geometry_msgs::msg::Twist
为例,带大家从代码层面真正看懂 ROS2 的话题发布、话题订阅和消息字段查看方法,为后续学习 ros2 topic、ros2 node、ros2 interface 等常用调试命令打好基础。

一、ROS2 消息对象的常见写法
1.1 创建消息对象的基本格式
ROS2 中创建消息对象,一般采用下面这种格式:
包名::msg::消息类型 变量名;
例如:
std_msgs::msg::String msg;
geometry_msgs::msg::Twist vel_cmd;
nav_msgs::msg::Odometry odom;
sensor_msgs::msg::LaserScan scan;
这和普通 C++ 定义变量非常像。
普通 C++ 中可以这样定义变量:
int age;
double speed;
std::string name;
ROS2 中定义消息对象则是:
geometry_msgs::msg::Twist vel_cmd;
其中:
geometry_msgs::msg::Twist 消息类型
vel_cmd 消息对象名
也就是说,vel_cmd 是一个变量,只不过它的类型不是 int、double,而是 ROS2 中的 Twist 消息类型。
1.2 以 Twist 为例创建速度消息
Twist 常用于 /cmd_vel 速度控制话题
移动机器人控制中,经常会看到:
geometry_msgs::msg::Twist vel_cmd;
这句代码的意思是:
创建一个 Twist 类型的速度消息对象,名字叫 vel_cmd。
Twist 里面主要包含两部分:
Vector3 linear; // 线速度
Vector3 angular; // 角速度
完整字段如下:
vel_cmd.linear.x;
vel_cmd.linear.y;
vel_cmd.linear.z;
vel_cmd.angular.x;
vel_cmd.angular.y;
vel_cmd.angular.z;
对于大多数地面移动机器人来说,最常用的是:
vel_cmd.linear.x; // 前进 / 后退速度
vel_cmd.angular.z; // 左转 / 右转角速度

1.3 给消息字段赋值
(1)让机器人向前运动
例如:
geometry_msgs::msg::Twist vel_cmd;
vel_cmd.linear.x = 0.2;
vel_cmd.angular.z = 0.0;
含义是:
机器人以 0.2 m/s 的速度向前运动,不旋转。
这里:
linear.x = 0.2
表示沿机器人前方方向前进。
angular.z = 0.0
表示不绕 z 轴旋转。
(2)让机器人原地旋转
如果写成:
vel_cmd.linear.x = 0.0;
vel_cmd.angular.z = 0.5;
含义是:
机器人不前进,只原地旋转。
其中:
angular.z = 0.5
表示绕 z 轴旋转,单位通常是:
rad/s
也就是弧度每秒。
(3)让机器人停止
如果想让机器人停止,一般写成:
vel_cmd.linear.x = 0.0;
vel_cmd.angular.z = 0.0;
并且发布出去:
pub_vel->publish(vel_cmd);
需要注意的是,机器人停止时,最好不是简单地"不发消息",而是主动发布一个零速度消息。
也就是:
linear.x = 0.0
angular.z = 0.0
这样底盘控制节点才能明确知道机器人需要停止。
二、ROS2 发布器 Publisher 的写法

2.1 发布器的基本定义方式
Publisher 需要指定消息类型
如果要发布 Twist 消息,发布器一般这样定义:
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel;
可以拆开理解:
rclcpp::Publisher<geometry_msgs::msg::Twist>
表示这是一个发布 Twist 消息的发布器。
SharedPtr
表示智能指针类型。
pub_vel
是发布器对象的名字。
完整意思就是:
定义一个发布 geometry_msgs::msg::Twist 消息的发布器,名字叫 pub_vel。
2.2 创建发布器
使用 create_publisher 创建发布器
在 ROS2 节点类里面,通常使用:
create_publisher
创建发布器。
例如:
pub_vel = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);
这句代码可以拆成:
this->create_publisher<geometry_msgs::msg::Twist>
表示创建一个发布 Twist 消息的发布器。
"/cmd_vel"
表示发布到 /cmd_vel 话题。
10
表示队列深度。
所以这句代码整体含义是:
创建一个发布器,这个发布器向 /cmd_vel 话题发布 Twist 类型消息,队列深度为 10。
2.3 发布消息
先创建消息,再赋值,最后 publish
发布消息的基本流程是:
1. 创建消息对象
2. 给消息字段赋值
3. 调用 publish 发布出去
代码如下:
cpp
geometry_msgs::msg::Twist vel_cmd;
vel_cmd.linear.x = 0.1;
vel_cmd.angular.z = 0.0;
pub_vel->publish(vel_cmd);
其中:
pub_vel->publish(vel_cmd);
表示把 vel_cmd 这条速度消息发布出去。
如果 pub_vel 发布的是 /cmd_vel 话题,那么底盘控制节点订阅到这个话题后,就可以控制机器人运动。
2.4 完整发布器示例代码
周期发布 /cmd_vel 速度消息
下面是一个简单的 ROS2 C++ 发布器节点,功能是周期性发布速度消息,让机器人向前运动。
cpp
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
using namespace std::chrono_literals;
class CmdVelPublisher : public rclcpp::Node
{
public:
CmdVelPublisher() : Node("cmd_vel_publisher")
{
pub_vel = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);
timer = this->create_wall_timer(
500ms,
std::bind(&CmdVelPublisher::timer_callback, this)
);
}
private:
void timer_callback()
{
geometry_msgs::msg::Twist vel_cmd;
vel_cmd.linear.x = 0.1;
vel_cmd.linear.y = 0.0;
vel_cmd.linear.z = 0.0;
vel_cmd.angular.x = 0.0;
vel_cmd.angular.y = 0.0;
vel_cmd.angular.z = 0.0;
pub_vel->publish(vel_cmd);
RCLCPP_INFO(
this->get_logger(),
"Publishing: linear.x = %.2f, angular.z = %.2f",
vel_cmd.linear.x,
vel_cmd.angular.z
);
}
private:
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel;
rclcpp::TimerBase::SharedPtr timer;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<CmdVelPublisher>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
2.5 发布器代码重点解释
(1)包含头文件
cpp
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
其中:
rclcpp/rclcpp.hpp
是 ROS2 C++ 客户端库头文件。
geometry_msgs/msg/twist.hpp
是**Twist 消息类型对应的头文件。**
只要代码里使用:
geometry_msgs::msg::Twist
就需要包含:
#include "geometry_msgs/msg/twist.hpp"
(2)创建发布器
cpp
pub_vel = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);
这句表示创建 /cmd_vel 话题发布器。
消息类型是:
geometry_msgs::msg::Twist
话题名字是:
/cmd_vel
(3)创建定时器
timer = this->create_wall_timer(
500ms,
std::bind(&CmdVelPublisher::timer_callback, this)
);
这句表示每隔 500ms 执行一次:
timer_callback()
所以这个节点会周期性发布速度消息。
(4)发布速度消息
pub_vel->publish(vel_cmd);
这句才是真正把速度消息发布出去。
如果没有这句,前面只是创建了消息、给消息赋值,并没有真正发到话题中。
三、ROS2 订阅器 Subscriber 的写法

3.1 订阅器的基本定义方式
Subscriber 也需要指定消息类型
如果要订阅 Twist 消息,订阅器一般这样定义:
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr sub_vel;
可以拆成:
rclcpp::Subscription<geometry_msgs::msg::Twist>
表示这是一个订阅 Twist 消息的订阅器。
SharedPtr
表示智能指针类型。
sub_vel
是订阅器对象名。
完整含义是:
定义一个订阅 geometry_msgs::msg::Twist 消息的订阅器,名字叫 sub_vel。
3.2 创建订阅器
使用 create_subscription 创建订阅器
创建订阅器一般这样写:
cpp
sub_vel = this->create_subscription<geometry_msgs::msg::Twist>(
"/cmd_vel",
10,
std::bind(&CmdVelSubscriber::cmd_vel_callback, this, std::placeholders::_1)
);
其中:
geometry_msgs::msg::Twist
表示订阅的消息类型。
"/cmd_vel"
表示订阅的话题名字。
10
表示队列深度。
std::bind(&CmdVelSubscriber::cmd_vel_callback, this, std::placeholders::_1)
表示收到消息后,执行 cmd_vel_callback 回调函数。
3.3 回调函数中接收消息
回调函数一般这样写:
cpp
void cmd_vel_callback(const geometry_msgs::msg::Twist::SharedPtr msg)
{
RCLCPP_INFO(
this->get_logger(),
"Received: linear.x = %.2f, angular.z = %.2f",
msg->linear.x,
msg->angular.z
);
}
这里需要特别注意:
msg
是一个指针,所以访问字段时使用:
msg->linear.x
msg->angular.z
而不是:
msg.linear.x
msg.angular.z
3.4 完整订阅器示例代码
订阅 /cmd_vel 并打印速度
下面代码订阅 /cmd_vel 话题,并打印收到的速度信息。
cpp
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
class CmdVelSubscriber : public rclcpp::Node
{
public:
CmdVelSubscriber() : Node("cmd_vel_subscriber")
{
sub_vel = this->create_subscription<geometry_msgs::msg::Twist>(
"/cmd_vel",
10,
std::bind(&CmdVelSubscriber::cmd_vel_callback, this, std::placeholders::_1)
);
}
private:
void cmd_vel_callback(const geometry_msgs::msg::Twist::SharedPtr msg)
{
RCLCPP_INFO(
this->get_logger(),
"Received: linear.x = %.2f, angular.z = %.2f",
msg->linear.x,
msg->angular.z
);
}
private:
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr sub_vel;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<CmdVelSubscriber>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
3.5 发布端和订阅端访问字段的区别
(1)发布端通常使用点号
发布端一般是普通对象:
geometry_msgs::msg::Twist vel_cmd;
所以访问字段使用:
vel_cmd.linear.x
vel_cmd.angular.z
(2)订阅端通常使用箭头
订阅端回调函数中收到的 msg 通常是智能指针:
const geometry_msgs::msg::Twist::SharedPtr msg
所以访问字段使用:
msg->linear.x
msg->angular.z
可以简单记成:
- 普通对象用 .
- 指针对象用 ->
对比如下:
| 场景 | 写法 | 说明 |
|---|---|---|
| 发布端消息对象 | vel_cmd.linear.x |
vel_cmd 是普通对象 |
| 订阅端消息指针 | msg->linear.x |
msg 是指针 |

