4. ROS 2与Jetson集成开发
在Jetson上做机器人感知与自主系统,ROS 2不是“装好就能跑”的框架,而是一套需要与硬件资源、实时性、网络拓扑共同设计的通信中间件。本节把节点、话题、服务、动作、TF2、Launch、QoS、DDS调优和Jetson资源约束串成一条可落地的开发主线:先理解通信语义,再决定用哪种机制,最后用Launch和QoS/DDS参数把系统稳定在Jetson的算力、内存和功耗边界内。
一、节点、话题、服务、动作:先选对通信语义
ROS 2的计算图由节点组成,节点通过话题、服务、动作和参数交互。话题是异步、多对多、单向的数据流,适合传感器流、图像、点云、里程计等连续数据;服务是同步请求-响应,适合“查询一次、立即返回”的短操作,如查询电池状态、切换模式;动作是带反馈和结果的长时任务,适合导航到目标点、机械臂抓取等可能耗时数秒到数分钟的任务。选择错误会直接导致Jetson上出现阻塞、丢帧或CPU空转。
以Jetson Orin Nano(8GB)为例,一个典型感知节点订阅 /camera/image_raw(1280×720@30fps),发布 /detections。若用话题传输图像,单帧约2.7MB,30fps即约81MB/s,DDS序列化和拷贝会占用大量CPU和内存带宽。此时应优先使用零拷贝(loan message)或共享内存传输,并限制发布频率。若把“查询检测结果”做成服务,每次调用都会阻塞等待,反而破坏流水线;正确做法是让检测结果持续发布为话题,服务只用于“切换检测模型”这类低频控制。
二、TF2:坐标变换是感知与自主的骨架
TF2维护机器人各坐标系之间的时间戳变换树。Jetson上常见链路为:map → odom → base_link → camera_link → camera_optical_frame。感知算法输出的目标位置通常在相机光学坐标系下,必须通过TF2变换到 map 或 base_link 才能用于导航和避障。TF2的关键约束是时间同步:查询变换时必须指定时间戳,若缓存中没有对应时刻的变换,会抛出 LookupException 或 ExtrapolationException。Jetson算力有限时,建议将静态变换用 static_transform_publisher 发布,动态变换由里程计和IMU融合节点以固定频率发布,并设置合理的 cache_time(如10s),避免频繁查询失败。
三、Launch:把节点、参数、命名空间和QoS编排在一起
Launch文件是Jetson上多节点系统的入口。一个典型的感知+导航Launch应包含:相机驱动节点、检测节点、TF发布节点、导航节点,以及每个节点的参数和QoS覆盖。ROS 2 Launch支持Python、XML、YAML三种写法,推荐Python,因为可以条件判断和动态计算。例如,根据Jetson型号选择不同的检测模型和分辨率:
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
use_small = LaunchConfiguration('use_small', default='true')
return LaunchDescription([
DeclareLaunchArgument('use_small', default_value='true'),
Node(
package='perception', executable='detector',
name='detector', namespace='perception',
parameters=[{'model': 'yolov8n', 'input_size': 640,
'use_small': use_small}],
remappings=[('/camera/image_raw', '/camera/image_raw')],
output='screen'
),
Node(
package='tf2_ros', executable='static_transform_publisher',
name='camera_tf',
arguments=['0.1','0','0.2','0','0','0','base_link','camera_link']
)
])
注意:Launch中不要把所有节点都塞进一个进程,Jetson上建议按功能分组,用 ComposableNode 将高频通信的节点组合进同一进程,减少DDS发现和序列化开销。但组合过多会导致单进程崩溃影响全局,需权衡。
四、QoS与DDS调优:让通信匹配Jetson的物理边界
QoS是ROS 2区别于ROS 1的核心。传感器数据应使用 BEST_EFFORT + KEEP_LAST(1),因为丢一帧图像比阻塞等待重传更可接受;控制指令和状态应使用 RELIABLE + KEEP_LAST(10);地图和静态配置可用 TRANSIENT_LOCAL + RELIABLE,让晚加入的节点也能收到。若发布者和订阅者QoS不兼容,连接不会建立,且不会报错,只会“收不到数据”,这是Jetson调试中最常见的坑之一。
DDS层面,Jetson上常用 rmw_fastrtps_cpp 或 rmw_cyclonedds_cpp。Fast DDS默认使用共享内存传输,适合同一台Jetson内的节点通信;跨设备通信则走UDP。若发现CPU占用过高,可调整 FASTRTPS_DEFAULT_PROFILES_FILE 中的 participant 线程数和内存池大小。例如,将发送队列从默认的无限改为有限,避免内存无限增长:
<participant profile_name="jetson_participant">
<rtps>
<builtin>
<discovery_config>
<leaseDuration>20</leaseDuration>
</discovery_config>
</builtin>
<allocation>
<total_participants>4</total_participants>
<total_readers>20</total_readers>
<total_writers>20</total_writers>
</allocation>
</rtps>
</participant>
五、Jetson资源约束下的工程实践
Jetson与桌面ROS 2开发最大的区别是资源受限。Orin Nano 8GB在运行相机、检测、导航和TF时,内存和CPU会迅速逼近上限。建议:第一,用 taskset 或 cgroups 把感知节点绑定到特定CPU核心,避免与DDS线程争抢;第二,图像话题使用 image_transport 的 compressed 或 raw 时,优先选择零拷贝的 loan 机制;第三,rosbag2录制时限制队列和分片大小,避免写满eMMC;第四,监控 /rosout 和 ros2 topic hz,若发现话题频率低于预期,先检查QoS兼容性和CPU占用,再检查DDS配置。
ros2 topic info /topic --verbose 查看发布者和订阅者的QoS配置,对比 Reliability、Durability、History 三项是否兼容。若不一致,连接不会建立,但节点不会报错。最后,TF2的时间同步与Jetson的功耗模式直接相关。当Jetson从25W降到15W时,CPU降频可能导致TF发布频率下降,进而使导航节点查询变换超时。建议在Launch中为TF发布节点设置较高的优先级,并监控 /tf 的实际发布频率。若使用 robot_localization 融合IMU和轮式里程计,需确保两个输入的时间戳对齐,否则融合结果会出现漂移。
总结来说,ROS 2与Jetson的集成开发不是简单的API调用,而是通信语义、QoS策略、DDS传输和硬件资源四者的联合设计。先选对话题/服务/动作,再用Launch编排,用QoS匹配数据特性,用DDS参数控制资源,最后用Jetson的算力、内存和功耗边界验证系统是否稳定。这套方法可以直接迁移到你的机器人项目中,减少“能跑但跑不稳”的问题。