跳到主要内容

1. 课程导论与机器人系统全景

机器人系统是怎么组织的?ROS2 在里面扮演什么角色?这一讲从整体视角看机器人系统,理解各层怎么协作,以及为什么要先学运动学再学 MoveIt 2。


前言:你将构建什么?

在这门课程结束时,你将能够:

  1. 用 URDF 描述一个两连杆机械臂的结构
  2. 用 ROS2 发布它的坐标系(tf2)
  3. 用 MoveIt 2 对它进行运动规划
  4. 在 RViz2 中看到机械臂从 A 点运动到 B 点的完整仿真

这不是玩具 demo,而是真实机器人系统的完整缩影。


1.1 机器人系统是怎么组织的?

1.1.1 从一个具体场景出发

想象你让机械臂"把桌上的杯子拿起来放到盒子里"。

这句话背后,机器人需要完成以下事情:

步骤做什么对应系统层
用相机看到杯子在哪获取环境信息感知层
建立杯子和机器人的空间关系坐标系转换建模层
规划一条从当前位置到杯子的路径路径规划规划层
把路径转换成每个关节的角度指令逆运动学 + 控制控制层
电机按指令转动,手臂真正移动执行硬件层
所有模块之间互相通信、协调中间件系统层

这就是机器人系统的六层结构。

机器人系统六层结构

1.1.2 六层结构详解

第一层:感知层(Perception)

职责:让机器人"看到"世界

常见传感器:

  • RGB 相机:获取彩色图像,用于目标检测
  • 深度相机(RGBD):如 Intel RealSense,同时获取颜色和距离
  • 激光雷达(LiDAR):发射激光束,测量周围障碍物距离,输出点云
  • IMU(惯性测量单元):测量加速度和角速度,用于姿态估计

输出:原始数据 → 点云、图像、姿态角

类比:感知层就是机器人的"眼睛和耳朵"。没有感知层,机器人是盲的。


第二层:建模层(Modeling)

职责:让机器人"理解"自己和世界的空间关系

包含三个核心概念:

① 机器人模型(URDF)

URDF(Unified Robot Description Format)是用 XML 描述机器人结构的文件。它定义了:

  • 有哪些连杆(link)
  • 连杆之间用什么关节(joint)连接
  • 每个部分的形状、质量、惯量
<!-- 一个简单的关节定义示例 -->
<joint name="joint1" type="revolute">
<parent link="base_link"/>
<child link="link1"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="100" velocity="1.0"/>
</joint>

② 坐标系系统(TF / tf2)

机器人身上每个关节、每个传感器都有自己的坐标系。tf2 负责管理这些坐标系之间的变换关系。

例如:相机看到杯子在"相机坐标系"下的位置是 (0.3, 0, 0.5),但机械臂需要知道杯子在"机械臂基座坐标系"下的位置,这个转换就由 tf2 完成。

③ 环境地图

移动机器人需要地图来导航,机械臂需要知道工作空间中的障碍物位置。

输出:统一的空间表达,所有模块都能"说同一种语言"


第三层:规划层(Planning)

职责:决定机器人"怎么动"

  • 路径规划(Path Planning):找到从 A 到 B 的无碰撞路径
  • 运动规划(Motion Planning):在路径上加入速度、加速度约束,生成平滑轨迹
  • 任务规划(Task Planning):更高层的决策,如"先拿杯子,再放到盒子里"

输出:轨迹(一系列带时间戳的位姿点)


第四层:控制层(Control)

职责:把轨迹转换成电机能执行的指令

核心工作:

  • 逆运动学(IK):已知末端目标位置,求各关节角度
  • 轨迹跟踪:实时计算当前误差,调整控制量
  • PID 控制:最常用的反馈控制算法

输出:关节角度指令、力矩指令


第五层:执行层(Hardware)

职责:真正让机器人动起来

  • 电机驱动器接收控制指令
  • 编码器反馈当前关节角度
  • 末端执行器(夹爪、吸盘)执行抓取动作

第六层:系统层(System / Middleware)

职责:让以上所有层能协同工作

这就是 ROS2 的核心价值所在:

  • 各模块之间的通信(Topic / Service / Action)
  • 统一的时间戳和坐标系管理
  • 参数配置、日志记录
  • 可视化工具(RViz2)

1.2 运动学——连接建模与规划的桥梁

1.2.1 机械臂的本质

机械臂本质上是一串"关节 + 连杆"的串联系统:

机械臂的本质示意图
基座 → 关节1 → 连杆1 → 关节2 → 连杆2 → ... → 末端执行器

每个关节有一个自由度(通常是旋转),关节角度的组合决定了末端的位置和姿态。

一个 6 自由度机械臂(如 UR5)有 6 个关节,可以让末端到达工作空间内任意位置和姿态。


1.2.2 正运动学(Forward Kinematics,FK)

问题:已知每个关节的角度,求末端在哪里?

平面两连杆机械臂示意

关节角度怎么定义? 本例与上图一致,采用常见的 相对关节角 约定(与后面逆运动学代码一致):

  • θ1:第一根连杆相对 基座坐标系(通常取 x 轴正方向)的转角,可理解为「第一节在世界系里的朝向」。
  • θ2:第二根连杆相对 第一根连杆的指向 的转角,不是相对世界 x 轴。因此第二根连杆在世界系里的绝对朝向是 θ1 + θ2,公式里第二项才会出现 cos(θ1 + θ2)sin(θ1 + θ2)
  • 转角正方向一般取 逆针向为正(与图中常见画法一致);若你的实物或 URDF 定义相反,只需在实现里统一符号即可。

Python 实现:

import numpy as np

def forward_kinematics(theta1_deg, theta2_deg, L1=1.0, L2=1.0):
"""
两连杆平面机械臂正运动学
参数:
theta1_deg, theta2_deg: 关节角度(度);θ2 为相对上一节连杆的相对角
L1, L2: 连杆长度(米),默认 1.0
返回:
(x, y): 末端在基座平面坐标系中的位置
"""
# NumPy 的 cos/sin 以「弧度」为自变量;日常更习惯用「度」输入,故先换算
theta1 = np.radians(theta1_deg)
theta2 = np.radians(theta2_deg)

# 第一节末端:(L1*cos(θ1), L1*sin(θ1));第二节沿世界系朝向 θ1+θ2 再伸 L2
x = L1 * np.cos(theta1) + L2 * np.cos(theta1 + theta2)
y = L1 * np.sin(theta1) + L2 * np.sin(theta1 + theta2)

return x, y

# 示例:θ1=45°, θ2=30°, L1=1m, L2=0.5m
x, y = forward_kinematics(45, 30, L1=1.0, L2=0.5)
print(f"末端位置: x = {x:.4f} m, y = {y:.4f} m")
# 输出: 末端位置: x = 0.8365 m, y = 1.1899 m

本质:关节空间 → 笛卡尔空间,有唯一解,计算简单


1.2.3 逆运动学(Inverse Kinematics,IK)

问题:已知末端目标位置,求各关节角度应该是多少?

为什么 IK 更难?

同一个末端位置,可能有两种关节角度组合:

两连杆平面机械臂:同一末端位置的肘下解与肘上解(逆运动学双解示意)

图:末端位置相同,肘下与肘上两种构型对应两组关节角。

怎么看下面这段代码? 重点是看懂流程:先判断可达性,再用几何关系求 θ2,最后代回求 θ1。能说出「每一块在解决什么问题」即可,无需背诵细节。

参考代码(含双解,读懂思路即可):

import numpy as np

def inverse_kinematics(x, y, L1=1.0, L2=1.0):
"""
两连杆平面机械臂逆运动学
参数:
x, y: 目标末端位置
L1, L2: 连杆长度(米),默认 1.0
返回:
dict,包含两组解:
{
"elbow_down": (theta1_deg, theta2_deg), # 肘下解,theta2 > 0
"elbow_up": (theta1_deg, theta2_deg), # 肘上解,theta2 < 0
}
如果目标不可达,抛出 ValueError
注意:返回角度单位为度
"""
# 检查目标是否可达
dist = np.sqrt(x**2 + y**2)
if dist > L1 + L2:
raise ValueError(f"目标点 ({x}, {y}) 超出工作空间,最大可达距离为 {L1+L2:.3f} m")
if dist < abs(L1 - L2):
raise ValueError(f"目标点 ({x}, {y}) 太近,最小可达距离为 {abs(L1-L2):.3f} m")

# 用余弦定理求 θ2
cos_theta2 = (x**2 + y**2 - L1**2 - L2**2) / (2 * L1 * L2)
cos_theta2 = np.clip(cos_theta2, -1, 1) # 防止浮点误差

theta2_down = np.arccos(cos_theta2) # 肘下解:θ2 > 0
theta2_up = -theta2_down # 肘上解:θ2 < 0

def solve_theta1(theta2):
k1 = L1 + L2 * np.cos(theta2)
k2 = L2 * np.sin(theta2)
return np.arctan2(y, x) - np.arctan2(k2, k1)

return {
"elbow_down": (np.degrees(solve_theta1(theta2_down)), np.degrees(theta2_down)),
"elbow_up": (np.degrees(solve_theta1(theta2_up)), np.degrees(theta2_up)),
}

# 示例
solutions = inverse_kinematics(0.836, 1.190, L1=1.0, L2=0.5)
for name, (t1, t2) in solutions.items():
print(f"{name}: θ1 = {t1:.2f}°, θ2 = {t2:.2f}°")

再次强调:上面代码是建立直觉用的示例,真正重要的是理解 IK 可能双解、可能无解,以及系统里谁会替你算。

本质:笛卡尔空间 → 关节空间,可能有多个解(甚至无解)


1.2.4 在系统中的位置

用户指定目标位置 (x, y, z)

逆运动学(IK)

各关节角度 θ1, θ2, ...

控制器执行

机器人运动

反向验证(用于可视化):

当前关节角度 θ1, θ2, ...

正运动学(FK)

末端当前位置 → 发布到 RViz2 显示

1.3 为什么必须学 ROS2?

1.3.1 没有 ROS2 会怎样?

假设你要自己写一个机械臂控制系统,你需要:

  1. 自己写相机数据读取程序
  2. 自己写激光雷达数据处理程序
  3. 自己设计这两个程序之间的通信协议(用 socket?共享内存?)
  4. 自己写坐标系转换代码
  5. 自己写可视化界面
  6. 自己管理所有程序的启动顺序和崩溃重启
  7. ...

每一项都需要大量工作,而且不同团队写的代码无法复用。

1.3.2 ROS2 解决了什么?

ROS2(Robot Operating System 2)提供了一套标准化的机器人软件框架:

下面是 ROS2 五个核心能力的速览。命令和代码片段不需要现在动手,第一遍读下来知道 ROS2 能做什么就够了,后续每一讲都会把对应能力讲透。

核心能力一:分布式节点系统

每个功能模块是一个独立的节点(Node),可以:

  • 独立运行、独立重启
  • 运行在不同的机器上(分布式)
  • 用不同语言编写(Python / C++)
# 查看当前运行的所有节点
ros2 node list

# 查看某个节点的详细信息
ros2 node info /robot_state_publisher

核心能力二:标准化通信机制

Topic(话题):发布/订阅模式,适合持续的数据流

# 查看所有话题
ros2 topic list

# 实时查看关节状态数据
ros2 topic echo /joint_states

# 查看相机图像话题的发布频率
ros2 topic hz /camera/image_raw

Service(服务):请求/响应模式,适合一次性操作

# 调用一个布尔服务
ros2 service call /enable_motor std_srvs/srv/SetBool "{data: true}"

Action(动作):适合长时间任务,支持中途反馈和取消

# 发送运动目标(MoveIt 2 内部使用 Action)
ros2 action list
ros2 action info /move_action

核心能力三:tf2 坐标系统

tf2 维护一棵"坐标系树",任意两个坐标系之间的变换都可以自动计算。

# 生成坐标系树的 PDF 图(需要 graphviz)
ros2 run tf2_tools view_frames

# 实时查询两个坐标系之间的变换
ros2 run tf2_ros tf2_echo base_link end_effector

核心能力四:Launch 系统

一个 launch 文件可以同时启动多个节点,并配置参数:

# my_robot/launch/bringup.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
return LaunchDescription([
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': open('my_robot.urdf').read()}]
),
Node(
package='rviz2',
executable='rviz2',
arguments=['-d', 'config/view_robot.rviz']
),
])
# 一键启动整个系统
ros2 launch my_robot bringup.launch.py

核心能力五:RViz2 可视化

RViz2 是 ROS2 的标准可视化工具,可以实时显示:

  • 机器人模型(URDF)
  • 坐标系(TF)
  • 传感器数据(点云、图像)
  • 规划路径
  • 碰撞检测结果
# 启动 RViz2
rviz2

# 带配置文件启动
rviz2 -d my_config.rviz

1.3.3 一句话理解 ROS2

ROS2 = 机器人系统的"操作系统级框架"

就像 Linux 为应用程序提供进程管理、文件系统、网络通信一样, ROS2 为机器人模块提供节点管理、数据通信、坐标系统。


1.4 课程学习路线

1.4.1 整体路线

1. 系统全景认知(本讲)

2-3. 运动学基础(坐标系、齐次变换、正/逆运动学)

4-7. ROS2 工程基础(节点、Topic、Service、Action、Launch)

8. tf2 坐标系统(核心中的核心)

9. URDF 机器人建模

10-11. MoveIt 2 运动规划

12. 完整项目整合

1.4.2 每讲的学习方式

本课程采用"概念 → 代码 → 运行验证"的三步法:

  1. 概念:用类比和图示理解原理
  2. 代码:写出最小可运行的示例
  3. 运行验证:在终端或 RViz2 中看到实际效果

1.5 环境准备

1.5.1 本课程使用的软件版本

软件版本说明
Ubuntu24.04 LTS推荐操作系统
ROS2Jazzy对应 Ubuntu 24.04 的 LTS 版本
Python3.12Ubuntu 24.04 系统自带
MoveIt 22.x (Jazzy)运动规划框架

如果你使用 Ubuntu 22.04,对应安装 ROS2 Humble,包名把 jazzy 替换成 humble 即可,核心概念完全一致。


1.5.2 安装 ROS2 Jazzy

第一步:确认 locale

Ubuntu 24.04 默认已配置,通常无需操作,验证一下即可:

locale # 确认输出包含 en_US.UTF-8

第二步:安装依赖并添加 GPG Key

sudo apt update && sudo apt install -y curl gnupg lsb-release

sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg

国内网络说明raw.githubusercontent.com 在国内经常被墙,上面的命令可能会卡住不动。遇到这种情况,Ctrl+C 终止后,改用 Gitee 镜像:

sudo curl -sSL https://gitee.com/ohhuo/rosdistro/raw/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg

第三步:写入 ROS2 软件源

重要:不要用 echo 命令写入。在终端粘贴多行命令时,换行符会被写入文件,导致 apt update 报错 Malformed entry ([option] unparsable)。用 vim 直接写入可完全避免此问题。

sudo vim /etc/apt/sources.list.d/ros2.list

在 vim 中输入以下内容(只有一行):

deb [arch=amd64 signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu noble main

这里使用清华镜像源。官方源 http://packages.ros.org/ros2/ubuntu 在国内速度极慢(约 8 KB/s),强烈推荐替换。

写入后验证(确认输出是完整的一行,不能有换行):

cat /etc/apt/sources.list.d/ros2.list
sudo apt update

第四步:安装 ROS2 Jazzy Desktop

# desktop 版包含 RViz2、示例程序等,推荐安装
sudo apt install -y ros-jazzy-desktop python3-colcon-common-extensions python3-rosdep

包体积较大,使用清华镜像约需数分钟。中断后重新运行同一命令可续传,已下载的包缓存在 /var/cache/apt/archives/,不会重新下载。

第五步:配置环境变量

# 每次打开终端自动加载 ROS2 环境
echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc
source ~/.bashrc

第六步:初始化 rosdep(用于自动安装依赖)

国内网络说明rosdep update 同样需要访问 raw.githubusercontent.com,在国内会报 Connection reset by peer 错误。推荐用 rosdepc 替代,功能完全相同,走国内镜像:

sudo pip install rosdepc
# 如果提示 "externally managed",改用:
# sudo pip install rosdepc --break-system-packages
sudo rosdepc init
rosdepc update

后续所有 rosdep 命令均可直接替换为 rosdepc

如果网络正常,也可以直接用原版:

sudo rosdep init
rosdep update

执行时可能出现 DeprecationWarning: pkg_resources is deprecated as an API,这是正常警告,不影响使用,忽略即可。另外,若提示 default sources list file already exists,说明已初始化过,跳过 init 直接执行 update 即可。


1.5.3 配置 Python 虚拟环境

ROS2 的 Python 包(rclpy 等)通过 apt 安装到系统 Python,不建议用 Anaconda,否则路径隔离会导致 import rclpy 失败。

推荐用 venv + --system-site-packages

前置依赖:Ubuntu 24.04 默认不带 venv 模块,若提示 ensurepip is not available,先安装:

sudo apt install python3.12-venv
# 创建虚拟环境,--system-site-packages 让 venv 能访问系统级 ROS2 包
python3 -m venv ~/ros2_venv --system-site-packages

# 激活(每次开新终端都需要执行)
source ~/ros2_venv/bin/activate

# 安装课程所需的科学计算库
pip install numpy scipy matplotlib

# 验证 ROS2 包仍然可以访问
python3 -c "import rclpy; print('rclpy OK')"

参数说明:

python3 -m venv ~/ros2_venv --system-site-packages
│ │ └─ 允许访问系统 site-packages(含 rclpy)
│ └─ 虚拟环境存放路径
└─ 调用内置 venv 模块创建虚拟环境

建议把激活命令也加入 .bashrc

echo "source ~/ros2_venv/bin/activate" >> ~/.bashrc
source ~/.bashrc

1.5.4 验证 ROS2 安装

# 终端1:
ros2 run demo_nodes_py talker

# 终端2:
ros2 run demo_nodes_py listener

如果看到 listener 打印出 talker 发送的 Hello World 消息,说明 ROS2 安装正常。

注意:ros2 --version 会报错 unrecognized arguments: --version,这是正常的,ROS2 不支持该参数,用 talker/listener 验证即可。


1.5.5 工作空间结构

本课程的代码组织方式:

ros2_ws/ # ROS2 工作空间根目录
├── src/ # 源代码目录
│ ├── my_robot_basics/ # 运动学函数、基础节点(3-8)
│ ├── my_robot_interfaces/ # 自定义 Service/Action 接口(6)
│ ├── my_robot_description/ # 机器人 URDF 模型(9)
│ ├── my_robot_moveit_config/ # MoveIt 2 配置包(10)
│ ├── my_robot_planner/ # 规划客户端节点(10-11)
│ └── my_robot_bringup/ # 一键启动 Launch 文件(11)
├── build/ # 编译输出(自动生成)
├── install/ # 安装目录(自动生成)
└── log/ # 日志目录(自动生成)

各包会在对应讲次中逐步创建,不需要现在手动建立。

创建工作空间:

本课程工作空间说明:课程源码统一存放在 $ROS_WS/ 下,与教程文档平行。后续所有命令均直接使用该路径。

# 创建工作空间目录
mkdir -p $ROS_WS/src

# 进入工作空间,执行首次构建
cd $ROS_WS
colcon build
source install/setup.bash

# 建议把 source 加入 .bashrc,避免每次手动执行
echo "source $ROS_WS/install/setup.bash" >> ~/.bashrc
source ~/.bashrc

1.6 常见误区(重点)

1.6.1 误区一:跳过运动学,直接学 MoveIt 2

很多人觉得运动学数学太难,想直接用 MoveIt 2 的 API。

后果

  • 规划失败时不知道为什么(是目标不可达?还是配置错误?)
  • 无法判断机械臂的工作空间范围
  • 遇到奇异点(singularity)时完全不知所措

正确做法:先用 Python 手写两连杆的正/逆运动学,理解原理,再用 MoveIt 2。


1.6.2 误区二:把 ROS2 当成"发消息的工具"

ROS2 不只是通信框架,它是:

  • 工程管理平台:管理节点生命周期、参数、日志
  • 标准化接口:让不同团队的代码能互相配合
  • 生态系统:大量现成的驱动、算法包可以直接使用

1.6.3 误区三:认为 MoveIt 2 = 运动规划算法

MoveIt 2 本身不是算法,而是算法集成框架。它:

  • 调用 OMPL(Open Motion Planning Library)做路径规划
  • 依赖 URDF 和 tf2 获取机器人模型和坐标系
  • 依赖 IK 求解器(如 KDL、IKFast)做逆运动学

理解这一点,才能在出问题时知道去哪里找原因。


1.7 本讲练习

1.7.1 练习 1:用自己的话描述六层结构

不看笔记,尝试向别人解释:

  • 感知层做什么?输出什么?
  • 建模层做什么?输出什么?
  • 规划层做什么?输出什么?
  • 控制层做什么?输出什么?

能流畅解释,说明你真正理解了。


1.7.2 练习 2:手算两连杆正运动学

给定:L1 = 1.0m,L2 = 0.8m,θ1 = 30°,θ2 = 45°

先手算,再用 Python 验证:

import numpy as np

L1, L2 = 1.0, 0.8
theta1 = np.radians(30)
theta2 = np.radians(45)

x = L1 * np.cos(theta1) + L2 * np.cos(theta1 + theta2)
y = L1 * np.sin(theta1) + L2 * np.sin(theta1 + theta2)

print(f"末端位置: x = {x:.4f} m, y = {y:.4f} m")
# 预期输出: x = 1.2071 m, y = 1.2243 m

1.7.3 练习 3:验证 ROS2 环境

# 打开两个终端,分别运行:
# 终端1
ros2 run demo_nodes_py talker

# 终端2
ros2 run demo_nodes_py listener

观察输出,确认通信正常后,用 Ctrl+C 停止。


本讲核心总结

概念一句话理解
机器人系统六层结构感知→建模→规划→控制→执行,系统层贯穿全部
正运动学已知关节角 → 求末端位置,有唯一解
逆运动学已知末端位置 → 求关节角,可能有多解或无解
ROS2机器人系统的标准化中间件框架
tf2管理所有坐标系变换的核心组件
URDF用 XML 描述机器人结构的标准格式
MoveIt 2运动规划算法的集成框架,不是算法本身

参考代码

本讲为概念导论,无对应源码文件。

参考代码目录结构(后续各讲逐步填充):

ros_ws/
├── scripts/ ← 2-3. 纯 Python 脚本,可直接运行
│ ├── lesson_02_transforms.py
│ └── lesson_03_two_link_kinematics.py
└── reference/src/ ← 各 ROS2 包的完整参考实现
├── my_robot_basics/ ← 4-8.
├── my_robot_interfaces/ ← 6.
├── my_robot_description/ ← 9.
├── my_robot_planner/ ← 10-11.
└── my_robot_bringup/ ← 11.

使用方式

  • scripts/ 下的脚本可直接 python3 运行,无需 ROS2 环境
  • reference/src/ 下的包是完整参考实现,用户走完 ros2 pkg create 流程后,可将参考文件 cp 到自己的包中对照或直接使用:
# 示例:把参考代码 cp 到自己的包
cp $ROS_WS/reference/src/my_robot_basics/my_robot_basics/hello_node.py \
$ROS_WS/src/my_robot_basics/my_robot_basics/

参考代码中的注释标注了关键逻辑和对应教程章节,建议先自己实现,遇到问题再对照。


下一讲预告

2. 运动学核心(工程版)

我们将从零推导两连杆机械臂的运动学,内容包括:

  • 坐标系的定义与变换
  • 齐次变换矩阵的几何意义
  • 用 Python 实现正运动学
  • 用 Python 实现逆运动学(含多解处理)
  • 可视化验证结果

全程代码驱动,不做复杂数学推导,只讲能用来写代码的核心。