跑一个ROS2 机器人系统
先看最终目标:
ROS2机器人系统
│
ros2 launch
│
┌──────────┼──────────┐
↓ ↓ ↓
Node A Node B Node C
│ │ │
└────── Topic / Service ──────┘
│
数据和命令流动
│
┌──────────────┼──────────────┐
↓ ↓ ↓
TF2 URDF ros2_control
│ │ │
↓ ↓ ↓
坐标关系 机器人模型 控制器
│
↓
Hardware Interface
│
↓
EtherCAT/CAN
│
↓
电机
你现在先不要研究每一个节点怎么写。先搞清楚:
启动 → 数据 → 坐标 → 控制 → 硬件
这条主线。
1、第一层:Bringup
真实机器人 ROS2 项目里面,你以后经常看到:
robot_bringup
或者:
xxx_bringup
这个名字非常值得记。
它通常不是一个核心算法包,而是:
负责把整个机器人系统启动起来。
比如:
robot_bringup/
├── launch/
│ ├── robot.launch.py
│ ├── simulation.launch.py
│ └── hardware.launch.py
│
└── config/
├── controller.yaml
└── robot.yaml
于是:
ros2 launch robot_bringup robot.launch.py
可能一次启动:
robot_state_publisher
controller_manager
joint_state_broadcaster
robot_controller
RViz
hardware_interface
所以以后看到:
bringup
你脑子里直接翻译成:
系统启动入口。
2、第二层:URDF——机器人“长什么样”
现在假设机器人已经启动,ROS2 需要知道:
机器人有多少个关节?
每个关节连接什么?
每个 link 多大?
坐标系在哪里?
关节怎么运动?
这就是:
URDF
例如一个简单机械臂:
base_link
│
joint1
│
link1
│
joint2
│
link2
│
joint3
│
link3
URDF 描述的是:
Link
Joint
几何模型
惯量
碰撞模型
关节限制
真实机器人 URDF 往往很长。
例如几十个关节:
left_hip
left_knee
left_ankle
right_hip
right_knee
right_ankle
...
直接写 URDF 很麻烦。
所以工程里经常看到:
.xacro
你可以暂时把它理解成:
更方便生成 URDF 的模板语言。
因此以后看到:
urdf/
├── robot.urdf.xacro
├── leg.xacro
├── arm.xacro
└── sensor.xacro
不要懵。
本质上:
Xacro
↓
生成
↓
URDF
↓
机器人描述
3、第三层:robot_state_publisher
这是一个非常重要的 Node。
它的任务可以简单理解为:
根据机器人模型和关节状态,告诉 ROS2 每个 link 当前在哪里。
例如:
base_link
↓
left_hip
↓
left_thigh
↓
left_knee
↓
left_shank
如果膝关节转了:
joint position变化
↓
robot_state_publisher
↓
TF变化
↓
RViz里的机器人姿态变化
4、TF2:机器人坐标系的“关系网络”
你可以把 TF2 理解成:
机器人所有坐标系之间的关系管理系统。
例如人形机器人:
base_link
│
├── torso
│
├── left_leg
│ ├── left_thigh
│ ├── left_shin
│ └── left_foot
│
└── right_leg
├── right_thigh
├── right_shin
└── right_foot
相机:
head
│
└── camera_link
IMU:
torso
│
└── imu_link
TF2 就是在维护:
camera_link
↓
head
↓
torso
↓
base_link
这样的坐标关系。
5、ros2_control
到这里:
URDF
↓
机器人是什么样
TF2
↓
机器人各部分在哪里
但是还缺一个东西:
怎么让机器人动?
这就是:ros2_control
你可以先把它理解成:
ROS2 和真实机器人硬件之间的标准控制框架。
整体:
ROS2
│
Controller
│
↓
ros2_control
│
Hardware Interface
│
┌───────┴───────┐
↓ ↓
EtherCAT CAN
↓ ↓
电机 电机
所以:
Controller
负责:
我要机器人怎么运动。
而:
Hardware Interface
负责:
怎么把这个运动命令真正送给电机。
现在把整条链真正串起来,你现在应该能看懂下面这个架构:
robot_bringup
│
Launch启动
│
┌──────────────┼──────────────┐
↓ ↓ ↓
GMR robot_state controller
Node publisher manager
│ │ │
↓ ↓ ↓
/joint_command TF2 ros2_control
│
↓
Hardware Interface
│
┌───────┴───────┐
↓ ↓
MuJoCo EtherCAT
│ │
↓ ↓
仿真 真实机器人
旁边:
URDF/Xacro
↓
机器人模型
↓
robot_state_publisher
↓
TF2
而参数:
YAML
↓
Controller / GMR / Driver
最终:
Launch
↓
把所有东西启动起来
6、你现在看真实 ROS2 仓库,应该按照这个顺序
以后打开一个陌生机器人项目,不要从 .cpp 开始。按照:
① README
↓
② Workspace
↓
③ Package
↓
④ bringup
↓
⑤ launch
↓
⑥ config/YAML
↓
⑦ URDF/Xacro
↓
⑧ Node
↓
⑨ Topic
↓
⑩ TF2
↓
⑪ Controller
↓
⑫ ros2_control
↓
⑬ Hardware
这就是你现在最需要建立的读 ROS2 项目的方法论。
ROS2 为什么需要 URDF?
因为 ROS2 本身并不知道:
“你的机器人到底长什么样?”
比如宇树 G1:
head
│
torso
│
pelvis
/ \
left_leg right_leg
│ │
foot foot
ROS2 需要知道:
-
有哪些关节?
-
有哪些身体部件?
-
谁连接谁?
-
关节能不能转?
-
能转多少?
-
每个部件的坐标系在哪里?
-
碰撞模型是什么?
-
质量、惯量是什么?
这就是 URDF 主要解决的问题。
你只需要先记住:
URDF = 机器人结构说明书
它描述的是机器人本身。
例如:
机器人
│
├── base_link
│
├── pelvis
│
├── torso
│
├── left_arm
│
├── right_arm
│
├── left_leg
│
└── right_leg
这些叫:
Link
也就是机器人的“身体部件”。
7、Link 是什么?
非常简单:
Link = 一块刚体
例如:
大腿
小腿
脚
手臂
前臂
手
躯干
都可以看成 Link。
例如:
大腿
↓
小腿
↓
脚
可以理解成:
Link
↓
Joint
↓
Link
↓
Joint
↓
Link
8、Joint 是什么?
Joint 就是:
两个 Link 之间怎么连接、怎么运动。
例如机器人腿:
大腿
│
│ 髋关节
↓
小腿
│
│ 膝关节
↓
脚
对应:
thigh
│
hip_joint
│
shin
│
knee_joint
│
foot
所以:
Link 是东西,Joint 是东西之间的连接和运动关系。
这个一定记住。
Joint 有哪些类型?目前最需要知道三个:
1. Fixed
固定连接:
torso
│
fixed
│
camera
摄像头固定在头上,就可以是 Fixed。
2. Revolute
旋转关节。
机器人绝大部分关节都属于这一类。
例如:
大腿
│
髋关节 ↻
│
小腿
3. Prismatic
直线运动:
←────→
机器人传统机械臂里可能比较常见。
对于你现在的 humanoid 运控工作:
重点理解 Fixed + Revolute 就够了。
Xacro = 帮你生成 URDF 的模板语言
你可以理解成:
Xacro
↓
生成
↓
URDF
所以:
URDF 是最终机器人模型,Xacro 是更方便写 URDF 的方式。
9、robot_state_publisher
现在我们有了:
URDF
↓
知道机器人有哪些 Link / Joint
但是还有一个问题:
机器人现在的关节到底转到了哪里?
比如:
左膝关节 = 30°
那么小腿应该在哪里?这时候就需要:
robot_state_publisher
它的作用可以简单理解成:
URDF
+
Joint State
↓
计算
↓
各个 Link 的空间关系
↓
发布 TF
10、TF2
TF2 = 管理机器人各个坐标系之间空间关系的系统。
例如:
base_link
↓
pelvis
↓
torso
↓
head
↓
camera_link
TF2 就是在告诉 ROS2:
camera_link
相对于
head
在哪里?
head
相对于
torso
在哪里?
torso
相对于
base_link
在哪里?
| 机器人学 | ROS2 |
|---|---|
| 连杆 | Link |
| 关节 | Joint |
| 坐标系 | TF Frame |
| 坐标变换 | TF Transform |
| 机器人结构 | URDF |
| 参数化机器人模型 | Xacro |
| 根据关节状态计算 TF | robot_state_publisher |
这个地方非常重要。
Joint State
告诉你:
左膝:
position = 0.5 rad
velocity = ...
effort = ...
它描述的是:
关节状态
TF
告诉你:
left_foot
相对于
base_link
在哪里?
它描述的是:
坐标系之间的位置和姿态关系
所以:
Joint State
↓
关节转了多少
↓
robot_state_publisher
↓
TF
↓
各个坐标系在哪里
11、RViz
以后打开:
rviz2
你会看到机器人模型。
背后的逻辑其实就是:
URDF
↓
Robot Model
↓
TF
↓
RViz
↓
显示机器人
所以 RViz 不是什么“神奇的机器人软件”。
它主要是在:
把 ROS2 中的数据可视化出来。
URDF / Xacro
│
机器人结构模型
│
Link + Joint
│
↓
robot_state_publisher
│
↓
TF2
│
各坐标系之间的空间关系
│
↓
RViz
Joint State ───────┘
而控制链是另外一条:
GMR
↓
Joint Command
↓
Controller
↓
ros2_control
↓
Hardware Interface
↓
EtherCAT
↓
电机
两条线最后共同组成一个机器人系统。
12、Static TF 和 Dynamic TF
这个也不用学复杂。
Static TF
不会变化。
例如:
head
↓
camera_link
摄像头牢牢固定在头上。
那么:
head → camera_link
基本不会变化。
这就是静态 TF。
Dynamic TF
会随着机器人运动变化。
例如:
torso
↓
upper_arm
↓
forearm
↓
hand
手臂动起来以后:
torso → hand
一直在变化。所以这是动态 TF,以后调机器人系统,你会经常用。
查看 TF:
ros2 topic echo /tf
静态 TF:
ros2 topic echo /tf_static
查看节点:
ros2 node list
查看机器人状态:
ros2 topic echo /joint_states
查看 TF 树:
ros2 run tf2_tools view_frames
查看两个坐标系之间的关系:
ros2 run tf2_ros tf2_echo base_link camera_link
不用死记,以后真正调系统的时候再查命令即可。
13、ros2_control
ROS2
│
↓
Controller
│
↓
ros2_control
│
┌────────┴────────┐
↓ ↓
Command Interface State Interface
↓ ↑
└──── Hardware ───┘
│
↓
Hardware Interface
│
┌────────┴────────┐
↓ ↓
EtherCAT MuJoCo
↓ ↓
电机 仿真
上面
控制算法
↓
Controller
↓
ros2_control
↓
Hardware Interface
↓
具体硬件
所以:
上面的控制逻辑尽量不要关心底层电机到底是 EtherCAT、CAN 还是仿真。
底层通过 Hardware Interface 对接。
Controller
它不希望知道:
“下面到底是 EtherCAT 还是 MuJoCo?”
所以:
Controller
↓
ros2_control
↓
Hardware Interface
↓
具体硬件
例如:
真机
Controller
↓
ros2_control
↓
G1 Hardware Interface
↓
EtherCAT
↓
电机
仿真
Controller
↓
ros2_control
↓
Simulation Interface
↓
MuJoCo
上面的 Controller 可以保持基本一致。
这就是 ros2_control 很重要的意义。
14、 Hardware Interface
简单理解:
Hardware Interface = ROS2 和真实硬件之间的翻译层。
上面说:
左膝目标位置 = 0.5 rad
Hardware Interface 负责把这个东西变成底层硬件能理解的东西。
例如:
ROS2
↓
position command
↓
Hardware Interface
↓
EtherCAT PDO
↓
电机驱动器
↓
电机
然后电机反馈:
编码器
↓
EtherCAT
↓
Hardware Interface
↓
ROS2
↓
joint_state
于是形成闭环:
Command
↓
ROS2 → Hardware → 电机
↑
Feedback
Joint Command
↓
Controller
↓
ros2_control
↓
Hardware Interface
↓
EtherCAT
↓
Motor
↓
Encoder
↓
Hardware Interface
↓
Joint State
↓
ROS2
所以你会发现:
ROS2 并没有改变机器人控制的本质。
它主要是在把整个机器人软件系统组织起来。
| 名称 | 大白话 |
|---|---|
| Controller | 决定机器人应该怎么动 |
| ros2_control | 控制框架 |
| Hardware Interface | ROS2 和硬件之间的翻译层 |
| Command Interface | 给硬件的目标 |
| State Interface | 硬件反馈回来的状态 |
这五个概念搞明白,后面学习就会非常顺。
15、 Controller 到底怎么控制关节
你现在最需要搞明白的是这一条:
上层算法
↓
Controller
↓
Command Interface
↓
Hardware Interface
↓
电机
以及反馈:
编码器
↓
Hardware Interface
↓
State Interface
↓
Controller
Controller 不等于“电机控制器”,这是刚接触 ros2_control 很容易混淆的地方。
这里的 Controller 更接近:
ROS2 中负责产生关节控制目标的软件模块。
例如:
Joint Trajectory Controller
收到:
左膝 → 0.5 rad
右膝 → 0.6 rad
然后产生对应的关节命令。
真正和电机通信的是后面的:
Hardware Interface
所以:
Controller
= 算“我要什么”
Hardware Interface
= 想办法把这个要求送到硬件
16、 Command Interface
假设一个关节:
left_knee
可能存在:
position
velocity
effort
这就是不同的 Command Interface。
例如:
position command
意思:
“我希望这个关节到这个位置。”
或者:
velocity command
意思:
“我希望这个关节以这个速度运动。”
或者:
effort command
意思:
“我希望给这个关节这个力/力矩控制量。”
你可以把它理解成:
Controller
↓
“给左膝 0.5 rad”
↓
position command interface
17、 State Interface
反过来,机器人会告诉 ROS2:
left_knee position = 0.48
left_knee velocity = 0.12
left_knee effort = ...
这些就是:
State Interface
所以:
Command Interface
↓
输出
↓
Hardware
↑
反馈
↑
State Interface
这是 ros2_control 最核心的一组概念。
18、 Controller Manager
现在再加入一个东西:
Controller Manager
它可以理解成:
Controller 的管理者。
例如你的机器人可能同时有:
joint_state_broadcaster
arm_controller
leg_controller
head_controller
Controller Manager 负责:
-
加载 Controller
-
启动 Controller
-
停止 Controller
-
切换 Controller
-
管理 Controller 使用哪些接口
所以结构变成:
Controller Manager
/ | \
↓ ↓ ↓
Controller Controller Controller
↓
ros2_control
↓
Hardware Interface
假设控制频率:
500 Hz
一个周期大致可以理解成:
① 读取硬件反馈
↓
② 更新 State Interface
↓
③ Controller 读取状态
↓
④ Controller 计算
↓
⑤ 产生 Command Interface
↓
⑥ Hardware Interface 写入硬件
↓
⑦ 电机执行
↓
⑧ 编码器反馈
↓
回到①
形成闭环:
┌──────────────────────┐
│ ↓
反馈 → State → Controller → Command
↑ ↓
└──── Hardware ←──────────┘
这就是你之前学的:
闭环控制
在 ROS2 中的一个软件实现形式。
上层算法
↓
Joint Target
↓
Controller
↓
Command Interface
↓
ros2_control
↓
Hardware Interface
↓
EtherCAT/CAN
↓
电机
↑
Encoder
↑
State Interface
↑
ros2_control
Controller 是什么?
→ 产生控制目标的软件模块。
Command Interface 是什么?
→ 向硬件发送什么类型的控制命令。
State Interface 是什么?
→ 从硬件读取什么状态。
Controller Manager 是什么?
→ 管理 Controller 的加载、启动、停止和切换。
19、 Hardware Interface
你可以把它理解成一个“适配器”:
ROS2世界
↓
Command / State Interface
↓
Hardware Interface
↓
机器人硬件世界
上面只认识:
position
velocity
effort
下面可能是:
EtherCAT PDO
CAN
串口
SDK
MuJoCo
所以 Hardware Interface 的任务就是:
把 ROS2 的标准接口翻译成具体硬件能理解的通信。
read()
从机器人读取状态:
电机
↓
EtherCAT
↓
Hardware Interface
↓
read()
↓
State Interface
↓
ROS2
例如:
position = 0.52
velocity = 0.13
effort = ...
write()
把 ROS2 的目标写给机器人:
Controller
↓
Command Interface
↓
write()
↓
Hardware Interface
↓
EtherCAT
↓
电机
例如:
left_knee position command = 0.6
所以你可以直接记:
read = 从硬件读
write = 向硬件写
Controller
│
↓
Command Interface
│
↓
Hardware Interface
│
write
│
↓
EtherCAT
│
↓
电机驱动
│
↓
电机
│
编码器
│
↓
EtherCAT
│
read
│
↓
Hardware Interface
│
↓
State Interface
│
↓
Controller
20、 以后遇到机器人“不动”,你应该怎么想?
不要第一反应就怀疑电机。
按照链路检查:
① Controller启动了吗?
↓
② Controller有没有产生Command?
↓
③ Command Interface存在吗?
↓
④ Hardware Interface激活了吗?
↓
⑤ write()有没有执行?
↓
⑥ EtherCAT有没有发送?
↓
⑦ 驱动器有没有收到?
↓
⑧ 电机有没有执行?
反馈则反过来:
电机
↓
编码器
↓
EtherCAT
↓
read()
↓
State Interface
↓
ROS2
沿着数据链逐段定位,而不是一上来猜问题。
21、 Noitom和宇树G1完整机器人系统
先看最终全貌:
人体
↓
Motion Capture
/ \
Xsens Noitom
\ /
↓ ↓
MotionInput
↓
NoitomFrame
↓
GMR
↓
Joint Target
↓
Controller
↓
ros2_control
↓
Hardware Interface
↓
┌───────┴────────┐
↓ ↓
EtherCAT MuJoCo
↓ ↓
G1 真机 仿真
1. Motion Capture 层
Xsens / Noitom
↓
人体骨骼运动数据
例如:
人体左手在哪里?
人体膝盖怎么弯?
人体躯干怎么旋转?
这些数据本质上是:
人体运动状态。
2. MotionInput
你之前已经做过这个抽象:
XsensUdpSource
↓
NoitomFrame
或者:
NoitomMocapApiSource
↓
NoitomFrame
所以 GMR 不需要关心:
“数据到底来自 Xsens 还是 Noitom?”
它只需要:
NoitomFrame
这就是软件工程里面非常典型的:
接口抽象 + 多种实现。
3. GMR
然后:
NoitomFrame
↓
GMR
↓
Robot Joint Target
这里发生的是:
人体动作 → 机器人动作
例如:
人体膝盖角度
↓
机器人腿部目标关节角
所以 GMR 属于:
运动重定向 / IK / 运动映射层。
它不是:
-
EtherCAT
-
电机驱动
-
Controller
-
Hardware Interface
这些东西不要混。
4. Joint Target 到 Controller
GMR 算出来:
q_target =
[
q1,
q2,
q3,
...
]
这个东西进入控制系统。
可以理解为:
GMR
↓
“我希望机器人每个关节到这些位置”
然后 Controller 再负责:
目标
↓
控制逻辑
↓
输出控制命令
5. ros2_control
接下来:
Controller
↓
ros2_control
这里的核心作用已经学过:
把上层控制和底层硬件解耦。
于是上面不需要关心:
EtherCAT PDO
CAN
驱动器寄存器
编码器协议
这些交给下面。
6. Hardware Interface
继续:
ros2_control
↓
Hardware Interface
Hardware Interface 把:
position command
velocity command
effort command
转换成底层硬件需要的数据。
例如:
Command
↓
write()
↓
EtherCAT PDO
反馈:
EtherCAT PDO
↓
read()
↓
Joint State
7. 真机路线
于是完整真机链路就是:
人体
↓
动捕
↓
NoitomFrame
↓
GMR
↓
Joint Target
↓
Controller
↓
ros2_control
↓
Hardware Interface
↓
EtherCAT
↓
电机
↓
编码器
↓
EtherCAT
↓
Hardware Interface
↓
Joint State
↓
Controller
这就是一个完整闭环。
8. MuJoCo 路线
如果换成仿真:
人体
↓
动捕
↓
GMR
↓
Joint Target
↓
Controller
↓
ros2_control
↓
Simulation Hardware
↓
MuJoCo
因此你之前使用 MuJoCo 的经验现在就接上了。
你可以把:
真实 Hardware Interface
换成:
仿真 Hardware Interface
上层控制逻辑仍然可以保持一致。
9. 那么 ROS2 在整个系统中到底是什么?
ROS2 更像整个机器人软件系统的:
通信 + 节点组织 + 参数配置 + 启动管理 + 控制框架集成平台。
所以你可以把整个系统看成:
┌─────────────────────────────┐
│ ROS2 │
│ │
│ Motion Capture │
│ ↓ │
│ GMR │
│ ↓ │
│ Controller │
│ ↓ │
│ ros2_control │
│ ↓ │
│ Hardware Interface │
└────────────┬────────────────┘
↓
EtherCAT
↓
G1
22、 以后拿到一个陌生 ROS2 机器人项目怎么办?
这是这套学习路线最终要达到的能力。
不要一上来就看源码。
先找这几个东西:
第一:启动入口
找:
launch/
bringup/
问:
系统怎么启动?
第二:机器人模型
找:
urdf/
xacro/
问:
机器人结构是什么?
第三:控制器
找:
controller/
config/
yaml
问:
谁产生控制命令?
第四:硬件
找:
hardware/
hardware_interface/
问:
ROS2 怎么连接真实机器人?
第五:上层算法
找:
gmr/
ik/
motion/
planner/
问:
机器人为什么要这么运动?
第六:通信
找:
msg/
srv/
action/
topic
问:
数据怎么流动?
看到:
robot_bringup
robot_description
robot_controller
robot_hardware
gmr
xxx_msgs
你应该能大概判断:
robot_bringup
→ 系统启动
robot_description
→ URDF/Xacro
robot_controller
→ 控制器
robot_hardware
→ 硬件接口
gmr
→ 运动重定向
xxx_msgs
→ ROS2数据接口
然后再进入源码。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)