FishBot ROS2 项目五大模块解析
写在前面
本文用通俗易懂的方式,帮你快速理解 FishBot 机器人项目的代码结构。不需要深厚的 ROS2 基础,看完就能明白每个模块是干嘛的。
模块总览
┌─────────────────────────────────────────────────────────────┐
│ src 目录结构 │
├─────────────────────────────────────────────────────────────┤
│ fishbot_description → 机器人长什么样 │
│ fishbot_interfaces → 机器人用什么语言交流 │
│ fishbot_bringup → 机器人开机启动器 │
│ fishbot_cartographer → 机器人绘图师(建图) │
│ fishbot_navigation2 → 机器人导航系统 │
└─────────────────────────────────────────────────────────────┘
模块一:fishbot_description —— 机器人的"身份证"
一句话解释
告诉系统:FishBot 长什么样、有多高、雷达装在哪里。
核心文件
urdf/fishbot.urdf —— 机器人的"三维简历"
里面定义了什么
| 部件 | 形状 | 尺寸 | 安装位置 |
|---|---|---|---|
| 底盘(base_link) | 圆柱体 | 高12cm,半径10cm | 离地7.6cm |
| 激光雷达(laser_link) | 圆柱体 | 高2cm,半径2cm | 底盘上方7.5cm |
| 雷达坐标系(laser_frame) | 圆柱体 | 同上 | 同上 |
为什么要这个模块
想象你要给朋友介绍你的车:
- 车长4米、宽1.8米
- 雷达装在车顶正中央
- 轮胎在四个角落
没有这些信息,导航软件连你的车能不能通过某个窄路都不知道。
实际作用
- 可视化:在 RViz 里能看到机器人的 3D 模型
- 碰撞检测:导航时知道机器人边界在哪里
- 坐标转换:激光雷达的数据要转换到机器人中心坐标系才能用
模块二:fishbot_interfaces —— 机器人的"方言词典"(消息实体类)
一句话解释
定义 FishBot 专用的通信格式,就像微信里的表情包,只有懂的人才能理解。
核心文件
1. 消息文件 msg/MyCustomMessage.msg
bool bool_test → 布尔值(是/否)
float32 float32_test → 小数(如温度 25.5)
int32 int32_test → 整数(如电量 80)
...(还有 byte、char、double 等各种类型)
这是干嘛的?
ROS2 自带很多标准消息(比如表示位置、速度的消息),但有时候不够用。比如你想发一个"机器人状态"消息,包含电量、温度、错误码,就可以自定义一个。
2. 服务文件 srv/FishBotConfig.srv
string key → 配置项名称(如"max_speed")
string value → 配置值(如"0.5")
---
string key → 返回的配置项
string value → 返回的配置值
这是干嘛的?
消息是"广播"(我说大家听),服务是"一对一问答"(你问我答)。
比如你想修改机器人的最大速度:
- 你问:把 max_speed 改成 0.5
- 机器人答:好的,max_speed 现在是 0.5
为什么要这个模块
ROS2 是一个分布式系统,不同节点之间要"说话"。这个模块就是定义"词汇表",确保大家说的是同一种语言。
模块三:fishbot_bringup —— 机器人的"开机键"
一句话解释
一键启动所有硬件驱动,就像电脑开机时自动加载显卡、声卡驱动一样。
核心文件
launch/bringup.launch.py —— 启动脚本
启动时干了什么(按顺序)
第1步:urdf2tf
→ 把机器人模型发布到 TF 坐标系
→ 让系统知道各个部件的相对位置
第2步:odom2tf
→ 订阅里程计话题 /odom
→ 把里程计数据转换成 TF 坐标变换
→ 告诉系统:机器人现在在哪里
第3步:micro_ros_agent
→ 通过 UDP 端口 8888 连接底层单片机
→ 接收底层传感器数据,发送控制指令
第4步:ros_serail2wifi
→ 串口转 WiFi 桥接
→ 让激光雷达的数据能通过网络传输
第5步:ydlidar(延迟5秒启动)
→ 启动激光雷达驱动
→ 开始扫描周围环境,发布 /scan 话题
核心代码解析
src/odom2tf.cpp 干了什么:
// 订阅 /odom 话题(里程计数据)
odom_subscribe_ = this->create_subscription<nav_msgs::msg::Odometry>(
"odom", ...);
// 收到数据后,转换成 TF 并广播出去
tf_broadcaster_->sendTransform(transform);
里程计是什么?
就像汽车的里程表,记录走了多远。机器人通过编码器计算:
- 我向前走了 1 米
- 我向左转了 30 度
把这些信息转换成坐标,系统就能追踪机器人位置。
为什么要这个模块
没有它,你需要手动启动五六个程序,还要确保顺序正确。这个模块就是"一键启动"的解决方案。
模块四:fishbot_cartographer —— 机器人的"绘图师"
一句话解释
让机器人边走路边画地图,就像你第一次进陌生大楼时边走边记路。
核心文件
launch/cartographer.launch.py—— 启动脚本config/fishbot_2d.lua—— 算法配置文件
建图原理(简化版)
激光雷达扫描
│
▼
┌─────────────────────┐
│ 看到障碍物在3米处 │
│ 看到障碍物在5米处 │
│ 看到墙在2米处 │
└─────────────────────┘
│
▼
结合里程计(我走了1米)
│
▼
┌─────────────────────┐
│ 原来那个障碍物 │
│ 现在离我2米了 │
│ → 更新地图 │
└─────────────────────┘
关键配置解读
fishbot_2d.lua 中的重要参数:
-- 使用2D SLAM(不是3D)
MAP_BUILDER.use_trajectory_builder_2d = true
-- 激光雷达有效范围:10厘米 ~ 3.5米
trajectory_builder_2d.min_range = 0.10
trajectory_builder_2d.max_range = 3.5
-- 使用里程计数据辅助建图
use_odometry = true
-- 开启回环检测(识别"这里我来过")
trajectory_builder_2d.use_online_correlative_scan_matching = true
建图时启动了哪些节点
| 节点 | 作用 |
|---|---|
| cartographer_node | 核心算法,处理激光数据,构建地图 |
| cartographer_occupancy_grid_node | 把地图转换成栅格格式(黑白图) |
| rviz_node | 可视化工具,实时显示建图过程 |
为什么要这个模块
机器人需要知道环境长什么样才能导航。这个模块就是"探索新环境"时用的。
使用场景:
- 新办公室第一次部署机器人
- 家里买了机器人,先让它"逛一圈"建图
- 环境布局变了,重新建图
模块五:fishbot_navigation2 —— 机器人的"导航系统"
一句话解释
给定目标点,自动规划路线、避开障碍、到达目的地。就像手机地图导航,但机器人真的在走。
核心文件
launch/navigation2.launch.py—— 启动脚本config/nav2_params.yaml—— 导航参数配置maps/room.yaml+room.pgm—— 预存地图
导航系统架构
┌─────────────────────────────────────────────────────────────┐
│ Navigation2 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AMCL │ │ Map Server │ │ Planner │ │
│ │ (定位) │ │ (地图服务)│ │ (全局规划) │ │
│ │ │ │ │ │ │ │
│ │ "我在哪?" │ │ "环境长啥样?"│ │ "整体路线?" │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ BT Navigator │ │
│ │ (行为树导航) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Controller │ │ Local Costmap│ │ Behavior │ │
│ │ (局部控制) │ │ (局部地图) │ │ (异常恢复) │ │
│ │ │ │ │ │ │ │
│ │ "下一步怎走?"│ │ "附近有障碍?"│ │ "卡住怎么办?"│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
各组件详解
1. AMCL(自适应蒙特卡洛定位)
作用:确定机器人在地图中的位置
原理:
- 发射一堆"虚拟粒子"在地图上
- 每个粒子猜测:"我可能在这里"
- 根据激光雷达数据,排除不可能的猜测
- 最终收敛到真实位置
配置参数:
min_particles: 500 # 最少粒子数
max_particles: 2000 # 最多粒子数
laser_model_type: "likelihood_field" # 激光模型
2. Planner(全局规划器)
作用:计算从起点到终点的最优路径
算法:NavFn(基于 Dijkstra/A*)
类比:就像地图 App 给你规划的蓝色路线
3. Controller(局部控制器)
作用:沿着全局路径走,同时避开突然出现的障碍
算法:DWB(Dynamic Window Approach)
原理:
- 考虑当前速度和加速度限制
- 在可行的速度范围内采样
- 选择"最像沿着路径走、又不会撞墙"的速度指令
速度限制:
max_vel_x: 0.26 # 最大前进速度 0.26 m/s
max_vel_theta: 1.0 # 最大旋转速度 1.0 rad/s
acc_lim_x: 2.5 # 最大加速度
4. Costmap(代价地图)
作用:标记哪里可以走、哪里不行
两种地图:
- 全局代价地图:基于预存地图,包含静态障碍(墙、家具)
- 局部代价地图:基于实时激光数据,包含动态障碍(人、移动的箱子)
膨胀层(Inflation):
- 在障碍物外围加"缓冲圈"
- 机器人不会贴着墙走,留安全距离
5. Behavior Server(行为服务器)
作用:处理异常情况
支持的行为:
- Spin:原地旋转(用于脱困或调整方向)
- BackUp:后退(前面有障碍时)
- Wait:等待
- AssistedTeleop:人工辅助遥控
6. BT Navigator(行为树导航)
作用:协调所有组件,决定"现在该干什么"
行为树逻辑示例:
如果 目标点更新了?
→ 重新规划路径
否则如果 路径被阻挡?
→ 尝试恢复行为(旋转/后退)
否则如果 到达目标?
→ 停止,报告成功
否则
→ 继续沿着路径走
导航流程示例
用户:去会议室!
│
▼
AMCL:我在大厅,坐标 (10, 20)
│
▼
Planner:规划路径 → 大厅 → 走廊 → 会议室
│
▼
Controller:第一步,向前0.5米,速度0.2m/s
│
▼
突然!有人走过
│
▼
Local Costmap:检测到动态障碍
│
▼
Controller:减速,绕行
│
▼
到达会议室!
为什么要这个模块
这是机器人的"大脑"。前面的模块让机器人"能看、能走",这个模块让机器人"知道要去哪、怎么去"。
五个模块协作流程
┌─────────────────────────────────────────────────────────────────────┐
│ 完整工作流程 │
└─────────────────────────────────────────────────────────────────────┘
阶段一:准备(只需一次)
══════════════════════════════════════════════════════════════════════
fishbot_description
│
▼
定义机器人模型(长什么样)
│
▼
fishbot_interfaces
│
▼
定义通信格式(说什么语言)
阶段二:开机(每次启动)
══════════════════════════════════════════════════════════════════════
fishbot_bringup
│
├── 加载机器人模型
├── 连接底层硬件(单片机)
├── 启动激光雷达
└── 启动里程计转换
│
▼
机器人现在:能感知(激光)、能定位(里程计)
阶段三:建图(新环境)
══════════════════════════════════════════════════════════════════════
fishbot_cartographer
│
├── 接收激光数据
├── 融合里程计
├── 实时构建地图
└── 保存地图文件
│
▼
得到:room.pgm(地图图片)+ room.yaml(地图信息)
阶段四:导航(日常使用)
══════════════════════════════════════════════════════════════════════
fishbot_navigation2
│
├── 加载预存地图
├── AMCL 定位当前位置
├── 接收目标点
├── 规划路径
├── 实时避障
└── 到达目标
常用命令速查
# 1. 编译项目
colcon build
# 2. 启动基础功能(bringup)
ros2 launch fishbot_bringup bringup.launch.py
# 3. 启动建图
ros2 launch fishbot_cartographer cartographer.launch.py
# 4. 保存地图
ros2 run nav2_map_server map_saver_cli -f ~/map
# 5. 启动导航
ros2 launch fishbot_navigation2 navigation2.launch.py
# 6. 查看 TF 坐标系
ros2 run tf2_tools view_frames
# 7. 可视化
ros2 run rviz2 rviz2
总结
| 模块 | 核心职责 | 类比 |
|---|---|---|
| description | 定义机器人外形 | 身份证照片 |
| interfaces | 定义通信语言 | 方言词典 |
| bringup | 启动硬件驱动 | 电脑开机 |
| cartographer | 构建环境地图 | 探险家绘图 |
| navigation2 | 自主导航避障 | 手机导航 |
这五个模块层层递进:
- 先定义"我是谁"
- 再定义"我说什么语言"
- 然后"启动我的身体"
- 接着"学会画地图"
- 最终"自主行走"
希望这份文档能帮你快速理解项目结构!有问题随时问。
评论区