2. 系统架构设计:感知-规划-控制闭环,模块间通信机制(ROS 2 / LCM),时间同步与延迟补偿
好,咱们进入第二章。这一章我打算聊聊整个系统的骨架——架构设计。
你想想看,一个机器人要动起来,从看到东西到做出动作,中间要经历多少步?我当年刚入行时,总觉得把感知、规划、控制三个模块拼起来就行了。结果呢?跑起来各种奇怪的问题——明明感知到了障碍物,规划却绕了个大弯;控制指令发下去了,执行器却慢了半拍。说白了,这三个模块不是简单的串联,它们之间需要一套精密的通信机制来协调。
2.1 感知-规划-控制闭环:不只是串起来
先说说这个闭环。我习惯把它理解成一个「感知-决策-执行」的循环,但实际工程中,它更像一个多速率、多数据流的网状结构。
核心闭环流程:
- 感知层:接收传感器数据(激光雷达、相机、IMU),输出环境状态(障碍物位置、自车位姿、速度)。
- 规划层:基于感知结果,生成运动轨迹(路径+速度曲线)。
- 控制层:将轨迹转化为底层执行指令(油门、刹车、转向)。
但这里有个坑——我早期做项目时,把感知和规划放在同一个线程里跑。结果感知一卡顿,规划也跟着停摆。后来我学乖了,每个模块独立成节点,用异步通信解耦。嗯,这里要注意:解耦不是不管,而是通过消息队列来管理依赖关系。
我的经验: 感知模块通常跑在10-20Hz,规划模块5-10Hz,控制模块50-100Hz。不同频率的模块之间,一定要做时间戳对齐,否则你拿到的「当前状态」其实是100ms前的旧数据。
2.2 模块间通信机制:ROS 2 vs LCM
通信机制这块,我踩过不少坑。目前主流方案就两个:ROS 2 和 LCM。我分别说说我的看法。
2.2.1 ROS 2:生态强大,但别滥用
ROS 2 的好处不用多说——发布-订阅模式、服务调用、参数服务器,一套组合拳下来,模块间通信很舒服。我个人习惯用 ROS 2 来做原型验证和中小型系统。
但要注意,ROS 2 的 DDS 实现(比如 Fast DDS、Cyclone DDS)在实时性上是有代价的。我曾经在一个项目中,用 ROS 2 的默认配置跑控制指令,结果延迟抖动达到了 5-10ms。对于高速移动的机器人,这个抖动足以让控制失效。
避坑指南: 如果你用 ROS 2 做实时控制,一定要配置 DDS 的 QoS 策略。把 reliability 设为 BEST_EFFORT,durability 设为 VOLATILE,可以显著降低延迟。我曾经因为没配这个,被延迟问题折磨了两周。
2.2.2 LCM:轻量级,适合高性能场景
LCM(Lightweight Communications and Marshalling)是我在另一个项目中接触到的。它比 ROS 2 更轻,延迟更低(通常 < 1ms),而且没有 DDS 那么重的依赖。
什么时候用 LCM?我建议:
- 对实时性要求极高(比如控制指令 < 1ms 延迟)
- 系统资源有限(嵌入式设备)
- 不想引入 ROS 2 的庞大生态
但 LCM 的缺点也很明显——没有服务调用、没有参数服务器、调试工具少。说白了,它就是个「裸」的发布-订阅库,你需要自己搭上层逻辑。
| 特性 | ROS 2 | LCM |
|---|---|---|
| 延迟 | 1-10ms(取决于 QoS) | < 1ms |
| 生态 | 丰富(工具、可视化、仿真) | 简陋(只有基本库) |
| 实时性 | 中等(需配置) | 高(原生支持) |
| 适用场景 | 原型、中型系统 | 高性能、嵌入式 |
我的建议: 如果你刚开始做项目,先用 ROS 2 搭起来。等系统稳定了,再把关键的控制链路换成 LCM。我现在的项目就是 ROS 2 + LCM 混用——感知和规划用 ROS 2,控制指令走 LCM。
2.3 时间同步与延迟补偿:别让数据「穿越」
这一节我要重点讲。时间同步,说白了就是让所有数据都有一个统一的时间基准。延迟补偿,就是知道数据「老了多久」,然后想办法修正。
2.3.1 时间同步:统一时钟是关键
我见过最蠢的错误——激光雷达用电脑时间,相机用相机内部时间,IMU 用另一个时钟。结果三个传感器的时间戳差了 50ms,融合出来的位姿完全是乱的。
解决方案很简单:
- 硬件同步:用 PTP(IEEE 1588)或 GPS 授时,让所有设备共享一个时钟。
- 软件同步:如果硬件不支持,就在 ROS 2 里用
message_filters做时间戳对齐。
我个人习惯用硬件同步。虽然成本高一点,但省心。软件同步总会有误差,尤其是在高动态场景下。
代码示例:ROS 2 时间同步(message_filters)
#include <message_filters/subscriber.h>
#include <message_filters/time_synchronizer.h>
#include <sensor_msgs/msg/point_cloud2.hpp>
#include <sensor_msgs/msg/image.hpp>
message_filters::Subscriber<sensor_msgs::msg::PointCloud2> lidar_sub(node, "lidar_points");
message_filters::Subscriber<sensor_msgs::msg::Image> camera_sub(node, "camera_image");
message_filters::TimeSynchronizer<sensor_msgs::msg::PointCloud2, sensor_msgs::msg::Image> sync(lidar_sub, camera_sub, 10);
sync.registerCallback(&callback);
2.3.2 延迟补偿:预测未来
延迟补偿,说白了就是「数据到了,但已经过时了,怎么办?」
举个例子:感知模块检测到障碍物,花了 50ms 处理,然后发给规划模块。规划模块又花了 20ms 计算轨迹,再发给控制模块。控制模块收到指令时,这个障碍物已经移动了 70ms。如果你不做补偿,控制指令就是基于「过去」的状态,结果就是撞上去。
我的做法是:
- 测量延迟:在每个消息里加上发送时间戳,接收时计算延迟。
- 状态预测:用卡尔曼滤波或简单的运动模型,把状态「推」到当前时刻。
- 控制补偿:在控制指令里加入前馈项,抵消延迟带来的相位滞后。
避坑指南: 我曾经在延迟补偿里用了线性插值,结果在急转弯时完全失效。后来换成二阶运动模型(考虑加速度),效果好了很多。记住:延迟补偿的精度,取决于你对系统动态的理解。
2.4 实战中的架构选择
最后,我分享一下我现在的项目架构。它不一定适合所有人,但你可以参考:
- 感知层:ROS 2 节点,10Hz,输出障碍物列表和自车位姿。
- 规划层:ROS 2 节点,5Hz,输出轨迹(路径点+速度)。
- 控制层:LCM 节点,50Hz,接收轨迹并输出控制指令。
- 时间同步:所有传感器通过 PTP 同步,时间戳精度 < 1ms。
- 延迟补偿:在控制层做,用二阶运动模型预测状态。
你想想看,这个架构的好处是什么?感知和规划用 ROS 2,方便调试和可视化;控制用 LCM,保证实时性。时间同步和延迟补偿,则把整个系统的「时间误差」控制在可接受范围内。
嗯,这一章就到这里。下一章我会讲感知模块的具体实现——从传感器选型到特征提取,咱们一步步来。