RuyiSDK Board Docs

RISC-V ROS2 机器人操作系统编程技术

源码仓库

ch01 · ROS 2 概述与架构设计

教材编程语言运行环境课程文档实验文档
RISC-VC++17SpacemiT K3 CoM260 Kit / Bianbu 4.0.6 / Humble,配合 x86 Ubuntu 22.04 / Humble / Harmonic 课程容器阅读课程开始实验
x86PythonUbuntu 22.04 / Humble 或 Ubuntu 24.04 / Jazzy阅读课程开始实验

第1章:ROS 2 概述与架构设计

课程:ROS 2 C++17 编程 章节:第1章
课时:2 课时(90 分钟)
教学方式:讲授 + 演示


1.1 ROS 2 发展背景与设计目标

知识点 1.1.1:从 ROS 1 到 ROS 2

ROS(Robot Operating System)最初由 Willow Garage 于 2007 年发布,旨在为机器人软件开发提供统一的分布式框架。ROS 1 在学术界和工业界获得了广泛采用,但随着应用场景从实验室扩展到工业级产品,ROS 1 的局限性逐渐暴露出来:首先是单点故障问题,Master 节点(roscore)是所有通信的中枢,一旦崩溃整个系统便陷入瘫痪;其次是实时性不足,基于 TCP 的通信无法满足硬实时控制的需求;再次是安全性缺失,系统没有内置的认证、授权和加密机制;最后是嵌入式支持薄弱,对资源受限的微控制器支持有限。正是针对这些痛点,2014 年 Open Robotics 启动了 ROS 2 项目,完全重新设计了通信架构。

知识点 1.1.2:ROS 2 版本演进

ROS 2 自 2017 年底发布首个版本以来,以约半年一次的节奏迭代,下表保留原课程涉及的版本、代号与发布日期。其中 Humble(2022 年 5 月发布)与 Jazzy(2024 年 5 月发布)为长期支持(LTS)版本,是当前工业界与学术界的主力选择。

版本名 代号 发布日期 状态
Ardent Apalone 2017.12 EOL
Bouncy Bolson 2018.07 EOL
Crystal Clemmys 2018.12 EOL
Dashing Diademata 2019.05 EOL
Eloquent Elusor 2019.11 EOL
Foxy Fitzroy 2020.06 EOL
Galactic Geochelone 2021.05 EOL
Humble Hawksbill 2022.05 LTS (推荐)
Iron Irwini 2023.05 EOL
Jazzy Jalisco 2024.05 LTS

本课程使用 ROS 2 Humble:C++ 节点在 COM260 Kit(Bianbu >= 4.0.1 / riscv64)运行,Gazebo/RViz 在 x86 的 Ubuntu 22.04/Humble/Harmonic 课程容器运行。连接与环境加载见 公共双端环境。


1.2 DDS 中间件架构

知识点 1.2.1:DDS 概念模型

DDS(Data Distribution Service)是 OMG 制定的分布式实时通信标准,ROS 2 采用它替换了 ROS 1 的自定义 TCP/UDP 协议。DDS 与 ROS 2 的核心概念之间存在清晰的对应关系:

DDS 概念          →    ROS 2 概念
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DomainParticipant →    Node (通过 context)
DataWriter        →    Publisher
DataReader        →    Subscription
Topic             →    Topic
Domain            →    ROS_DOMAIN_ID
图 1-1:DDS 去中心化架构
┌─────────────┐                    ┌─────────────┐
│   Node A    │                    │   Node B    │
│             │                    │             │
│ ┌─────────┐ │    DDS Domain     │ ┌─────────┐ │
│ │Publisher│ │◄════Topic════════►│ │Subscribe│ │
│ └─────────┘ │    (自动发现)      │ └─────────┘ │
└─────────────┘                    └─────────────┘
        │                                  │
        └────────── 去中心化 ───────────────┘
               (无Master节点)

从图 1-1 可以看出,ROS 2 使用 DDS 的自动发现机制,节点可直接对等通信,无需中心化的 Master 节点。

知识点 1.2.2:RMW 抽象层

ROS 2 通过 RMW(ROS MiddleWare)接口抽象不同的 DDS 实现,其分层结构如图 1-2 所示:最上层是用户直接调用的 rclcpp/rclpy API,其下依次是统一的 ROS Client Library(RCL)与中间件抽象层(RMW),再往下则是可以自由切换的 DDS 实现(如 Fast DDS、Cyclone DDS、RTI Connext),底层以 UDP 或共享内存(SHMEM)完成数据传输。

┌──────────────────┐
│  rclcpp / rclpy  │  ← 用户代码 API
├──────────────────┤
│       RCL        │  ← ROS Client Library
├──────────────────┤
│       RMW        │  ← 中间件抽象层 (可替换)
├──────────────────┤
│  Fast DDS /      │
│  Cyclone DDS /   │  ← DDS 实现 (可切换)
│  RTI Connext     │
├──────────────────┤
│   UDP / SHMEM    │  ← 传输层
└──────────────────┘

图 1-2:ROS 2 分层架构。RMW 层允许用户在不修改应用代码的情况下切换不同的 DDS 实现。

知识点 1.2.3:官方要点——分层架构与 DDS 中间件

官方 About ROS 2 文档将 ROS 2 定位为「面向机器人应用的开源中间件框架」,其与 ROS 1 最本质的架构差异在于采用 DDS(Data Distribution Service,数据分发服务)作为通信中间件。DDS 是一种基于发布-订阅模型的数据分发标准,底层使用 RTPS 协议完成数据的可靠传输与节点发现。ROS 2 通过 rmw(ROS 中间件接口)抽象层将上层 API 与具体的 DDS 实现(如 Fast DDS、Cyclone DDS)解耦,用户使用 rclpy/rclcpp 编写的代码可以无缝切换底层供应商。

由于 DDS 本身就是去中心化的,节点之间通过自动发现(Discovery)机制互相感知,不再需要 ROS 1 时代的中央 Master 节点。这一设计带来三个显著收益:其一,系统天然支持多机分布部署,机器人本体与上位机可以位于不同主机;其二,QoS(服务质量)策略内建于通信层,可在可靠传输与实时低延迟之间按需取舍;其三,协议栈符合实时通信标准,为机械臂控制、自动驾驶等硬实时场景提供了基础。


1.3 Quality of Service (QoS) 策略

知识点 1.3.1:QoS 策略维度

QoS 定义了数据传输的质量保证机制,ROS 2 提供以下核心策略维度:

策略维度 选项 含义
Reliability RELIABLE / BEST_EFFORT 可靠传输(重传) / 尽力传输(允许丢包)
Durability VOLATILE / TRANSIENT_LOCAL 不保存历史 / 保存历史给后来者
History KEEP_LAST(n) / KEEP_ALL 保留最后 n 条 / 保留所有
Depth 正整数 队列深度
Deadline 时间间隔 消息更新的最大间隔
Lifespan 时间间隔 消息的有效期
Liveliness AUTOMATIC / MANUAL 发布者存活检测

其中 Reliability 决定消息是否重传,Durability 决定后加入的订阅者能否收到历史消息,History 与 Depth 共同控制缓存队列的长度,Deadline、Lifespan 与 Liveliness 则分别约束消息的更新频率、有效时长与发布端存活检测。

知识点 1.3.2:预定义 QoS 配置文件

为了让常见场景开箱即用,ROS 2 在 rclcpp 中预置了若干 QoS 类型,例如传感器数据流使用高频允许丢包的配置,服务通信则使用可靠传输的配置;同时用户也可以显式构造 rclcpp::QoS 自定义每一维度的取值:

#include "rclcpp/rclcpp.hpp"
 
// Sensor data: BEST_EFFORT, KEEP_LAST(5).
auto qos_sensor = rclcpp::SensorDataQoS();
 
// Services: RELIABLE, KEEP_LAST(10).
auto qos_service = rclcpp::ServicesQoS();
 
// Custom QoS.
auto qos_custom = rclcpp::QoS(10).reliable().transient_local();

1.4 节点生命周期管理

知识点 1.4.1:生命周期状态机

ROS 2 为节点引入了标准化的生命周期状态机,其状态转换关系如图 1-3 所示:节点从 Unconfigured 状态出发,调用 configure() 进入 Inactive,再经 activate() 变为 Active 正常运行;反之可经 deactivate()、cleanup() 逐级退回,并且任意状态下都可以通过 shutdown() 进入 Finalized。

                              configure()
Unconfigured ─────────────────────────► Inactive
     ▲                                      │
     │                                      │ activate()
     │ cleanup()                            ▼
     │                                   Active
     │                                      │
     └────────────────── deactivate() ──────┘

           shutdown() (从任意状态进入 Finalized)

图 1-3:ROS 2 生命周期节点状态转换图。通过状态机管理节点的初始化和资源分配,确保确定的启动和关闭流程。

知识点 1.4.2:生命周期 C++ API

在 C++ 中实现生命周期节点只需继承 rclcpp_lifecycle::LifecycleNode,并按需覆写 on_configure、on_activate 等回调,每个回调通过返回 CallbackReturn::SUCCESS 表示迁移成功:

#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
 
using CallbackReturn =
  rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
 
class MyLifecycleNode : public rclcpp_lifecycle::LifecycleNode
{
public:
  MyLifecycleNode() : LifecycleNode("my_lifecycle_node") {}
 
  CallbackReturn on_configure(const rclcpp_lifecycle::State &) override
  {
    // Allocate resources and set parameters.
    RCLCPP_INFO(get_logger(), "on_configure() called");
    return CallbackReturn::SUCCESS;
  }
 
  CallbackReturn on_activate(const rclcpp_lifecycle::State &) override
  {
    // Start processing data.
    RCLCPP_INFO(get_logger(), "on_activate() called");
    return CallbackReturn::SUCCESS;
  }
 
  CallbackReturn on_deactivate(const rclcpp_lifecycle::State &) override
  {
    // Stop processing data.
    RCLCPP_INFO(get_logger(), "on_deactivate() called");
    return CallbackReturn::SUCCESS;
  }
 
  CallbackReturn on_cleanup(const rclcpp_lifecycle::State &) override
  {
    // Release resources.
    RCLCPP_INFO(get_logger(), "on_cleanup() called");
    return CallbackReturn::SUCCESS;
  }
};

知识点 1.4.3:官方要点——生命周期节点设计动机

官方 Concepts 文档中的 About Lifecycle Nodes 页面解释了生命周期(Lifecycle)节点的设计动机:机器人系统中的硬件驱动、导航算法等组件需要有序的初始化与关停流程,而非一启动就全速运行。生命周期节点将运行状态划分为未配置(Unconfigured)、未激活(Inactive)、激活(Active)与已停止(Shutdown)等状态,外部通过服务调用触发状态迁移。

例如导航系统加载代价地图时,需要先完成参数读取与地图订阅初始化(Unconfigured → Inactive),再一次性切换到 Active 开始运行。rclpy/rclcpp 官方示例包(ros2/examples)提供了完整的生命周期节点实现,与本章演示的生命周期状态机一一对应,可作为进阶实现参考。


1.5 安装与环境搭建

知识点 1.5.1:安装 ROS 2 Humble

COM260 使用 Bianbu 的 ROS 2 软件包及课程专用安装器,安装 ROS 2、colcon 和课程所需依赖;Gazebo/RViz 安装在 x86 课程容器中。安装前先预检并查看缺失项,具体双端准备步骤见 公共环境。

【COM260 板端,安装终端】

cd ~/ROS2_RISCV_COM260
bash setup_course_k3_com260_kit.sh --dry-run
bash setup_course_k3_com260_kit.sh --install-deps
bash setup_course_k3_com260_kit.sh --build

知识点 1.5.2:配置开发环境

每次新开终端加载 ROS 2 和课程工作空间环境后,才能使用其中的包和节点。COM260 安装器生成独立的课程环境文件;本课程使用已核对的显式依赖清单,本轮直接使用 Bianbu 软件包,未执行 rosdep 初始化。

【COM260 板端,各实验终端】

source ~/.config/ros2-course-com260/env.bash
source ~/ros2_course_com260_ws/install/setup.bash

知识点 1.5.3:工作空间与 colcon 构建

开发机器人功能包时,通常先在主目录下创建 ~/my_ros2_com260_ws 工作空间并建立 src 子目录存放源码,随后用 colcon 构建整个空间,构建完成后加载 install/setup.bash 即可运行其中的节点,最后用官方的 talker/listener 示例节点验证整个环境是否可用:

【COM260 板端,终端 1;已有工作空间内容保留】

# 创建工作空间
mkdir -p ~/my_ros2_com260_ws/src
cd ~/my_ros2_com260_ws
 
# 首次使用时创建练习包;已有包时保留原内容。
if [ ! -e src/my_first_pkg ]; then
  (cd src && ros2 pkg create my_first_pkg --build-type ament_cmake \
    --dependencies rclcpp std_msgs)
fi
 
# 编译工作空间
python3 -m colcon build --symlink-install
 
# 加载工作空间环境
source install/setup.bash
 
# 验证安装
ros2 run demo_nodes_cpp talker
# 在另一个终端:
ros2 run demo_nodes_cpp listener

知识点 1.5.4:官方要点——快速上手:turtlesim 与命令行工具

官方 Beginner: CLI Tools 系列教程以小乌龟仿真 turtlesim 为教学道具:在终端一运行 ros2 run turtlesim turtlesim_node 启动乌龟仿真器,终端二运行 ros2 run turtlesim turtle_teleop_key 即可用方向键控制乌龟运动。教程刻意引导读者用 ros2 node list --no-daemon --spin-time 10、ros2 node info、ros2 topic list、ros2 service list 等命令观察图形界面背后的节点、话题与服务实体,并演示节点重命名(--ros-args -r __node:=my_turtle)。

这一步与本章练习的仿真节点观察实验思路完全一致:从「黑盒」现象出发,借助命令行工具建立对 ROS 2 实体模型的直观认识。The Construct 的 5 天入门课也保留了这一「先跑仿真、再读命令」的教学法,Articulated Robotics 的视频系列则以更口语化的方式逐条演示这些命令,适合中文读者对照复习。

知识点 1.5.5:官方要点——工作区与 colcon 构建系统

官方 colcon 教程介绍了标准工作区布局:src/(源码)、build/(中间产物)、install/(安装产物)、log/(日志)四个目录。构建命令 python3 -m colcon build 会递归编译所有包,常用选项包括 --packages-select <pkg>(只编译指定包)、--symlink-install(适用文件以符号链接安装,C++ 源码修改后仍需重新编译)以及 --merge-install(合并安装,适合在嵌入式/容器中运行)。

Articulated Robotics 强调初学者应形成「修改源码 → python3 -m colcon build --symlink-install → source install/setup.bash → ros2 run」的日常循环,并提醒每次新开终端都要重新 source,否则会出现 Package 'x' not found 的错误——这是 ROS 2 初学者最高频的报错之一,根源正是环境变量(如 AMENT_PREFIX_PATH、LD_LIBRARY_PATH)未正确加载。


1.6 本章小结

本章围绕 ROS 2 的设计理念与核心机制展开。首先回顾了 ROS 1 在单点故障、实时性、安全性与嵌入式支持四方面的局限,以及 ROS 2 如何通过全新的通信架构解决这些问题。随后介绍了 DDS 中间件的去中心化自动发现机制:节点之间不再依赖中央 Master,而是通过对等发现直接通信,且底层 DDS 实现可以通过 RMW 抽象层自由切换。在通信质量层面,QoS 策略从可靠性、持久性、历史缓存、队列深度、截止时间、存活周期与存活检测等多个维度提供细粒度的保障。此外,生命周期节点通过标准状态机统一管理节点的配置、激活与关闭流程,保证系统启动和退出行为确定可控。在环境搭建方面,本章给出了 COM260 上 ROS 2 Humble 的安装入口与环境加载步骤,并介绍了以 colcon 为标准构建工具的工作空间管理方式。


1.7 练习题

练习 1.1:简述 ROS 1 的三个主要局限性和 ROS 2 对应的解决方案。

ros1依赖master,存在中心节点————ros2使用dds通信机制,节点之间可以自动发现彼此 ros1不太能实时控制并传输,通信策略单一————Qos通信,根据不同数据类型设置可靠性队列深度。。。 ros1多机器人、多实验环境相互干扰————通过ROS_DOMAIN_ID划分通信领域 练习 1.2:DDS 域 ID(ROS_DOMAIN_ID)的作用是什么?如果两台机器上的域 ID 不同,它们的节点能通信吗?

作用是隔离通信环境。id不同一般不会互相发现和通信 练习 1.3:以下 QoS 配置能否匹配通信?

  • Publisher: RELIABLE, VOLATILE
  • Subscriber: BEST_EFFORT, VOLATILE 可以。Publisher 使用 RELIABLE,提供可靠传输能力;Subscriber 使用 BEST_EFFORT,只要求尽力接收,因此发布者能力满足订阅者需求。两者 Durability 都为 VOLATILE,也能够匹配。 练习 1.4:查看当前 ROS_DOMAIN_ID 环境变量值,将其修改为 42 并验证。

COM260 ROS_DOMAIN_ID 修改为 42 并恢复课程域

COM260 实测 ROS_DOMAIN_ID=42;观察后恢复为课程使用的 Domain 0。

练习 1.5:创建一个 ROS 2 工作空间,使用 python3 -m colcon build 编译,并验证 setup.bash 加载。

COM260 在新练习目录创建 ament_cmake 包并编译通过,加载 setup.bash 后可查询 my_first_pkg。

COM260 在新练习目录创建 ament_cmake 包并编译通过,加载 setup.bash 后可查询 my_first_pkg。

练习 1.6:运行 talker/listener 示例节点,使用 ros2 node list --no-daemon --spin-time 10 和 ros2 topic list 查看运行状态。

COM260 talker、listener 与节点列表

COM260 查看 ROS 2 节点和话题;/chatter 可见,查询退出码为 0。

COM260 查看 ROS 2 节点和话题;/chatter 可见,查询退出码为 0。


仿真结合实例(当前仓库):Gazebo Harmonic 仿真中的 ROS 2 体系验证

目标与知识点对应

本实例把第1章的三个核心概念——DDS 分布式通信、去中心化自动发现、ROS_DOMAIN_ID 域隔离、QoS 策略——放到一个真实的 Gazebo 仿真中验证。当前验证使用 COM260 上的 ROS 2 Humble 与 x86 课程容器中的 ROS 2 Humble + Gazebo Sim Harmonic,移动机器人入口为 robot_sim_demo。通过观察一个仿真世界里的节点、话题和桥接关系,你能直观理解 DDS 中间件如何在节点之间自动建立连接。

运行命令

先按公共环境启动并进入 x86 课程容器;下列命令对应容器内仿真进程,已经启动时不要重复启动。

【x86 课程容器,仿真终端】

source /opt/ros/humble/setup.bash
source /workspace/install/setup.bash
 
# 终端 1:启动 Gazebo + 机器人 + 传感器桥(带 RViz)
ros2 launch robot_sim_demo gazebo2.launch.py gui:=true rviz:=true drive:=false

等待 Gazebo 3D 场景出现 TurtleBot3 Burger 机器人、RViz 显示机器人模型与激光雷达点云。随后在 COM260 独立终端(先加载课程环境)观察:

# 查看自动发现到的所有节点(DDS 去中心化发现)
ros2 node list --no-daemon --spin-time 10
 
# 查看传感器桥发布的话题及其 QoS 与类型
ros2 topic list
ros2 topic info /scan

需要观察到的现象

运行上述命令后应重点观察三类现象。其一,ros2 node list --no-daemon --spin-time 10 能看到 gazebo2_bridge、gazebo2_robot_state_publisher、camera_info_publisher 等多个节点,且它们没有中心 master,全部靠 DDS 自动发现(对应 1.1-1.2 节)。其二,ros2 topic info /scan 显示 Message type: sensor_msgs/msg/LaserScan,桥接发布者提供激光数据;可通过 ros2 topic info /scan -v 查看实际 QoS,再与订阅者对照。Lazy 桥控制有无订阅者时是否转发,与 DDS 的 Durability/History 是不同机制。其三,在另一个带 source 的终端重新指定一个 ROS_DOMAIN_ID 再执行 ros2 node list --no-daemon --spin-time 10,可以看到它与仿真图隔离,对应 1.2.2 节与练习 1.2。

源码与相关位置

本实例涉及的关键文件包括:启动入口位于 src_k3_pico_itx/robot_sim_demo/launch/gazebo2.launch.py,桥配置位于 src_k3_pico_itx/robot_sim_demo/config/gazebo2_bridge.yaml,相机内参发布器位于 src_k3_pico_itx/robot_sim_demo/src/camera_info_publisher.cpp,世界与模型文件则位于 src_k3_pico_itx/robot_sim_demo/worlds/museum.sdf 和 src_k3_pico_itx/robot_sim_demo/models/turtlebot3_burger/model.sdf。

本章结果来自 2026-09-16 COM260 实测;对应截图、终端记录和连续 GUI 录像见实际运行证据。

COM260 生命周期状态迁移与速度输出

COM260 实测完整生命周期迁移、cleanup 后计数重置、自动启动和停止归零;双端 /cmd_vel 查询通过,实际退出码和分轮记录见证据索引。


学习材料: