☰
RDK X5与ROS2机器人实战:从硬件选型到BPU模型部署与Nav2导航
2026/9/28 23:21:54 网站建设 项目流程

我用RDK X5搭过两版ROS2机器人,第一次是纯跑官方镜像当“能开机”用,第二次才算是真正把底盘、雷达、算法串成一条整链路。这中间踩了很多文档上没有写明的地方,尤其是BPU模型转换和串口权限这类细节,官方资料经常一句话带过,但实操起来能卡你一整天。这篇文章就当作一份完整的搭建记录,从硬件选型到ROS2环境、底盘驱动、传感器接入、模型部署、Nav2建图导航,一条线走到底,适合手里有RDK X5、想做一台能感知能自主移动的ROS2机器人的朋友。

1. 为什么选RDK X5当ROS2机器人主控

先说选型逻辑。机器人主控的活儿,不是“能跑Linux”就行。它要同时扛住三件事:实时收发传感器数据、跑感知和决策算法、控制电机执行动作。树莓派4性能偏弱,跑个YOLO基本就吃满CPU,Jetson Nano算力尚可但价格不友好,更重要的是它的IO口和机器人常用的外设对接没有RDK X5顺手。RDK X5这块板子最吸引我的地方是它板载了BPU(脑处理单元,Bayesian Processing Unit),10TOPS级别的算力,推理一个YOLOv8s模型跑个几十毫秒很轻松,功耗还低,小车用电池带得动。

再就是它的接口配置。一个板子能不能当机器人主控,接口齐全度很关键。RDK X5带了双千兆网口、USB 3.0、M.2接口,还有40Pin GPIO,机器人常用的I2C、UART、PWM、CAN都能直接引出来,省掉一堆转接板。我实际接的激光雷达走串口、IMU走I2C、电机驱动板走串口,一个40Pin排针全部搞定,不用额外扩展。

软件生态方面,地瓜官方提供了适配好的Ubuntu 22.04镜像和ROS2 Humble版本TROS工具链,Humble是ROS2里最稳定的发行版之一,官方支持到2027年。相比自己从零编译ROS2,这个省了很多时间。RDK X5的CPU是8核Arm A55,跑ROS2的节点和通信中间件毫无压力,我把激光雷达、IMU、三个视觉节点、导航节点全开,CPU占用也就一半左右。

还值得说的是,RDK X5的地平线工具链把模型转换的门槛降了不少。ONNX模型通过hobot_model_encoder工具转成BPU可执行的bin模型,再用hobot_dnn订阅图像话题发布推理结果,整个流程比较顺。对于没有太多嵌入式AI经验的ROS2开发者来说,这块学习曲线要比自己手撸NPU驱动友好得多。

当然它也有短板,比如GPU能力弱,跑GPU版CUDA程序基本别指望,图像渲染之类的任务别压在它身上。但作为一台移动机器人的主控,它定位很准确,就是“感知决策执行闭环”。

2. 系统烧录与ROS2 Humble环境搭建

2.1 镜像烧录:选对的镜像版本

RDK X5出厂不带系统,需要自己烧录。官方提供的是基于Ubuntu 22.04的Desktop或Server镜像,我建议直接上Desktop版,毕竟开发调试阶段需要可视化界面,ROS2的工具链很多也要靠图形界面来配。

烧录工具用balenaEtcher就行,准备好一张至少32GB的SD卡。插卡、选镜像、烧录、插入板子、通电,第一次开机系统会自动扩展分区,耐心等两三分钟。

注意:RDK X5的电源最好用官方配套的USB-C电源,至少5V 3A起步。电源功率不足会导致板子在跑AI推理时突然重启,这种问题排查起来相当隐蔽。

2.2 ROS2 Humble的安装方式选择

系统起不来先别高兴,接下来是环境痛点。RDK X5官方镜像里其实已经预装了一部分ROS2组件,但版本可能不完全。我的建议是全部重新装一遍,保证环境一致性。

ROS2安装方式有几个选择,我用的是鱼香ROS一键安装脚本,它在国内网络环境下省事很多:

wget http://fishros.com/install -O fishros && . fishros

脚本执行后选择ROS2 Humble,再选基础版ROS2,脚本会自动配置apt源并安装全套核心包。如果你不习惯用第三方脚本,也可以走官方apt源安装:

sudo apt update && sudo apt install curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update && sudo apt install ros-humble-desktop

两种方式我都试过,鱼香脚本在源的选择上更聪明一些,安装速度和稳定性都更好。装完记得把环境变量写进bashrc,不然后面每次开终端都要手动source:

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc

验证安装是否成功,跑一下经典小乌龟:

ros2 run turtlesim turtlesim_node

能弹出小乌龟窗口,说明ROS2核心环境正常。这里有个小细节,ros2 run能找到节点,是因为有ament包索引,如果提示找不到包,先检查环境变量有没有正确加载。

2.3 DDS与多机通信的选型

ROS2的底层通信是DDS(Data Distribution Service),RDK X5默认用的是Eclipse Cyclone DDS,这个选择是地瓜做过权衡的。Fast DDS对标准Linux桌面支持好,但其多播发现在嵌入式环境下偶尔有兼容问题;Cyclone DDS内存占用更小、实时代理调度更好,在单板环境里更稳。

如果你想修改DDS配置,在/etc/ros2/rmw.d/下新建一个rmw配置文件,选择Cyclone作为中间件:

export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

多机通信时候需要注意,RDK X5和工控机要在同一个网段,并且都启用同样的DDS发现协议。我遇到过RDK X5发布的话题工控机订阅不到的情况,最后发现是两台机器的DDS实现不一致,统一用CycloneDDS后问题就消失了。

3. 硬件接口配置:40Pin排针与底盘驱动打通

3.1 点亮电机驱动板:串口控制协议

我的底盘是两轮差速驱动,驱动板通过串口接收控制指令,控制协议是自定义的ASCII协议,格式大概是MOTOR LEFT_SPEED RIGHT_SPEED。RDK X5的40Pin排针引出多路UART,需要确认用的是哪一路,以及如何开启它。

RDK X5默认的调试串口和机器人用途的UART是不同的。设备树里通常有一个UART默认被分配为控制台,其他UART是空闲的。你需要修改设备树启用串口,这步比较关键。在RDK X5上可以通过/sys/kernel/debug/pinctrl/查看引脚复用情况,一般文档会说明某个UART对应的GPIO号。

我用的UART3,对应板载40Pin上的物理引脚8和10。启用UART3的操作是改/boot/firmware/config.txt(新版本镜像路径可能不同):

enable_uart=1 dtoverlay=uart3

修改后重启板子。然后检查串口设备是否出现:

ls /dev/ttyS* ls /dev/ttyUSB*

如果是USB转串口设备,会看到/dev/ttyUSB0;如果是板载串口,通常是/dev/ttyS0或/dev/ttyTHS0。我的驱动板通过USB转TTL模块连接到板子的USB口,所以是ttyUSB0。

然后是权限问题,这是个容易踩的坑。普通用户默认没有权限访问串口:

sudo usermod -aG dialout $USER

执行完需要重新登录,否则串口依然打不开。

3.2 底盘驱动节点:发布odom和接收cmd_vel

底盘驱动不能只写一个死循环去读串口。规范做法是写一个ROS2节点,订阅/cmd_vel话题获取速度指令,解析后通过串口发给驱动板;同时周期性地读取驱动板里的轮速编码器数据,计算出机器人的里程计信息,发布/odom话题和/tf坐标变换。

我的驱动节点核心代码结构大概是这样的:

import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry import serial class RobotBaseNode(Node): def __init__(self): super().__init__('robot_base_node') self.serial_port = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.1) self.cmd_sub = self.create_subscription(Twist, '/cmd_vel', self.cmd_callback, 10) self.odom_pub = self.create_publisher(Odometry, '/odom', 10) def cmd_callback(self, msg): left_speed = msg.linear.x + msg.angular.z * self.wheel_base / 2 right_speed = msg.linear.x - msg.angular.z * self.wheel_base / 2 command = f"MOTOR {left_speed:.2f} {right_speed:.2f}\n" self.serial_port.write(command.encode()) def publish_odom(self): # 读取编码器数据,计算位置,发布odom pass

有个核心点:时间戳。发布/odom时,时间戳用node.get_clock().now().to_msg()而不是系统当前时间直接转,因为ROS2的图要保证不同话题之间的时间同步,乱给时间戳会导致后续融合定位算法出问题。

3.3 TF树:让每个传感器知道自己在哪

传感器接入之前,先把TF树建立起来,不然后面所有算法都会报坐标找不到。我的机器人的TF树是:

map -> odom -> base_footprint -> base_link -> laser_link -> imu_link -> camera_link

在launch文件里用robot_state_publisher发布静态变换:

<node pkg="robot_state_publisher" exec="robot_state_publisher"> <param name="robot_description" value="$(command 'cat $(find my_robot_description)/urdf/robot.urdf')" /> </node> <node pkg="tf2_ros" exec="static_transform_publisher"> <param name="translation" value="0 0 0.15"/> <param name="rotation" value="0 0 0 1"/> <param name="frame_id" value="base_link"/> <param name="child_frame_id" value="laser_link"/> </node>

TF树设计有个原则:base_link是所有传感器坐标的根,轮式里程计推算的是odom到base_link的变换,激光雷达到车体中心是固定变换用静态TF发布即可。如果雷达装在车体中心偏前10厘米,就是x=0.1 y=0 z=0.15,这些都是静态值,一次性写进URDF里就行。

4. 传感器接入与数据流打通

4.1 激光雷达:串口雷达的连接与校验

我用的激光雷达是思岚A1,12米测距半径,360度扫描,对于室内机器人导航完全够用。A1通过串口输出,接法很简单,把雷达的TX/RX接到USB转TTL模块上,再插到RDK X5的USB口。

雷达驱动在ROS2里用现成的sllidar_ros2包:

sudo apt install ros-humble-sllidar ros2 launch sllidar_ros2 sllidar_a1_launch.py

启动后检查话题数据:

ros2 topic echo /scan --once

如果报错“Failed to open serial port”,大概率是串口权限问题,前面加的dialout用户组检查一下;如果扫描出来数据全是0,多半是波特率设置不对,A1默认是115200,检查驱动launch文件里的serial_baudrate参数。

雷达数据接入后,有一个非常关键的隐藏细节:雷达扫描数据里的角度范围是0到360度,但很多雷达由于物理安装原因,0度方向并不对着车头正前方。你需要测量出雷达0度方向和车体正前方的夹角,在后续建图时补偿这个角度偏移,否则建出来的地图看起来是歪的。

4.2 IMU:I2C接口的校准与数据融合

IMU我用的是BMI088,通过I2C接口连接,它提供三轴加速度和角速度。IMU对机器人导航的作用是补足轮式里程计在打滑场景下失效的问题,轮子空转时里程计会认为机器人还在前进,实际已经原地打滑,IMU能帮算法识别这种状态。

I2C设备的接入确认用i2cdetect:

sudo apt install i2c-tools sudo i2cdetect -y 2

在I2C总线上看到设备地址出现在列表中,说明IMU连接正常。IMU驱动在ROS2里可以用自定义的驱动包,或者用bmi088_ros2包。启动后发布的数据在/imu/data_raw话题上。

IMU数据需要进行静态校准,尤其需要校准陀螺仪零偏。把机器人放在完全水平静止的桌面上,采集一分钟的IMU角速度数据,取平均,这个值就是零偏,后续需要在驱动代码里减去。这点不少文档没强调,但直接用未校准的IMU数据做融合定位,yaw角会缓慢漂移,几分钟后偏到离谱。

校准完IMU后,建议用robot_localization包做轮式里程计和IMU的数据融合,比单纯用里程计定位稳定得多。配置ekf_node时的关键参数是:

EkfNode: ros__parameters: frequency: 30.0 odom0: /odom imu0: /imu/data_raw odom0_config: [true, true, false, false, false, false, false, false, false, false, false, false, false, false, false] imu0_config: [false, false, false, true, true, true, false, false, false, false, false, true, false, false, false]

odom0_config的含义是,前三个true代表接收x、y、yaw速度数据,后面代表其他速度/姿态分量不接收。IMU那边接收三个角速度和roll、pitch,但不叠加线性加速度。按这个思路配置,融合效果才不会互相打架。

4.3 摄像头:从采集到ROS2话题

视觉传感器我用的是USB摄像头,RDK X5支持UVC协议,插上就能识别。通过usb_cam包发布图像话题:

sudo apt install ros-humble-usb-cam ros2 run usb_cam usb_cam_node_exe --ros-args -p video_device:=/dev/video0 -p image_width:=640 -p image_height:=480

图像分辨率不要调太高,640x480足够算法用,调高反而拖慢整个感知链路。摄像头画面用RViz2订阅/image_raw话题可以实时查看。

5. BPU算法部署:从ONNX到端侧推理

5.1 模型准备与工具链安装

这一步是整个RDK X5搭建流程里最有门槛的地方。我们要在BPU上跑一个YOLOv8s目标检测模型,用来识别机器人前进方向上的障碍物。

先在PC上训练或者下载一个预训练好的YOLOv8s模型,导出为ONNX格式。导出时有两个关键参数需要注意:opset=11和dynamic_axes设置为固定尺寸,BPU不支持的动态shape会导致转换失败。

地瓜的模型转换工具链在ROM(RDK工具链)里,安装方式官方文档说得很详细,核心是hobot_model_encoder命令,它负责把ONNX模型转换成BPU可执行的bin格式:

hobot_model_encoder -d yolov8s.onnx -o yolov8s.bin

转换过程中有几个参数直接决定模型能不能高效跑在BPU上:量化策略(int8或fp16)、输入分辨率、归一化参数。RDK X5的BPU对int8量化支持最好,但直接转int8可能掉精度,最好先用校准集做量化感知训练,或者在转换工具里开启“混合量化”,让关键层保持fp16,其他层用int8。这个度需要根据实际场景测一下。

5.2 推理节点:hobot_dnn的使用

模型转换完成后,用hobot_dnn工具部署推理,它相当于一个通用的DNN推理节点,负责订阅图像话题、调用BPU推理、发布感知结果。用Python API的写法更灵活:

import numpy as np from hobot_dnn import pyeasy_dnn class YoloNode(Node): def __init__(self): super().__init__('yolo_node') self.model = pyeasy_dnn.load('../model/yolov8s.bin') ...

整个推理流程是:订阅/image_raw话题 -> 对图像做预处理(resize到640x640、归一化、通道转换)-> 送入BPU推理 -> 拿到输出tensor -> 解析检测框和类别 -> 发布vision_msgs/msg/Detection2DArray。

图像预处理有个容易出错的地方,RDK X5的BPU对输入数据的内存排布有要求,通常是NHWC格式、RGB通道、归一化值范围0到1。如果你的模型训练时用的是BGR通道、0到255归一化,那在Python里要先做转换,不然模型输出会完全混乱。

我的预处理核心代码:

img = cv2.resize(img, (640, 640)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 img = img.transpose(2, 0, 1) if model_input_channel_first else img

判断模型的输入格式,直接看转换工具生成的yaml描述文件就行,里面对输入输出有完整定义。

5.3 感知结果可视化与RViz2联动

推理得到的目标框信息,在RViz2里用Detection2DArray可视化比较麻烦,我建议发布成visualization_msgs/msg/MarkerArray,在RViz2里直接能显示3D框。核心是给每个检测结果生成一个长方体Marker,放到相机坐标系对应的位置。

要注意的是,单目相机检测出的目标框是2D的,没有深度信息,不能直接生成有效3D框。我的做法是结合激光雷达的点云数据,在图像上目标框中心对应的雷达距离值作为该目标的深度,这样标出来的3D框在空间里就比较可信了。

这个“2D框+雷达深度”的融合办法在导航场景里非常实用。它会直接作为障碍物信息送入代价地图,让导航算法知道前方几米处有障碍、这个障碍大概多大体积。单靠雷达点云判断障碍物很容易漏掉细小的目标比如桌腿、电线杆,有了视觉先验再结合雷达测距,准确率提升很多。

6. Nav2建图与导航实测

6.1 建图:SLAM Toolbox跑通旋转扫描

环境感知有了,接下来就是把地图建起来。我用的是slam_toolbox,它对二维激光SLAM在室内场景的表现比cartographer更稳定,参数调起来也直观。启动命令:

ros2 launch slam_toolbox online_async_launch.py

这里有个需要注意的地方:slam_toolbox默认只订阅/scan话题作为输入,并且要求坐标变换laser_link -> base_link已经发布。我用rviz2控制机器人旋转一圈,地图就开始逐步构建。建图时推进速度别太快,雷达扫描周期是10Hz,车体移动过快会导致帧间畸变,地图会有明显的“重影”现象。

建完地图后保存:

ros2 run nav2_map_server map_saver_cli -f my_map

生成的map.pgm和map.yaml保存好,后续导航直接加载。

6.2 Nav2导航:costmap配置与避障逻辑

导航用Nav2全家桶,这块的参数配置是ROS2机器人导航里最耗时间的活。核心是costmap的配置,分global_costmap和local_costmap,都包含几个layer:static layer加载静态地图、obstacle layer订阅激光雷达数据实时添加障碍物、inflation layer做膨胀处理让机器人贴着障碍物边缘走。

我的obstacle_layer配置:

obstacle_layer: plugin: "nav2_costmap_2d::ObstacleLayer" enabled: True observation_sources: scan scan: topic: /scan max_obstacle_height: 2.0 clearing: True marking: True

关于动态障碍物,普通costmap只能检测到雷达能扫到的障碍,对于视觉识别到但雷达还没扫到的情况,需要额外把视觉障碍信息作为一个单独的主题喂给obstacle layer。我写了一个桥接节点,把YOLO检测到的目标框中心点结合雷达距离,生成一个PointCloud2发布到/obstacles_vision,然后在这个话题上再加一个observation source。

Nav2的几个关键调参思路:

  • robot_radius:对圆形底盘设为0.2米,太小会导致规划路径贴上障碍物,太大容易造成路径不可达。
  • inflation_radius:这个值决定路径和障碍物保持多远的“安全距离”,室内紧凑空间设0.3~0.5,大空间设0.6~0.8。
  • planner_server:默认的NavFn就行,计算快且稳定。

6.3 实测过程与常见问题

整链路跑通后,在RViz2里点击“2D Goal Pose”下发一个目标点,让机器人自主规划并运动过去。整个过程分几个阶段:全局规划器会先计算出从当前位置到目标点的全局路径,然后局部规划器实时调整速度指令,避让过程中新发现的障碍物,底盘驱动节点把速度指令转成串口数据控制电机转动。

实测中几次比较典型的失败案例:

第一次跑导航时,机器人完全不动,打开终端才发现在报错“No valid plan found”,原因是我设置地图的时候map.yaml里的resolution参数和实际建图保存的不一致,导致导航规划空间坐标系彻底乱了。

第二次是机器人走了一条“贴墙又突然转向”的诡异路线,排查发现是IMU和里程计融合的yaw角在缓慢漂移,机器人认为自己已经转向到位,实际还差角度。校准IMU零偏之后这个问题就消失了。

第三次是激光雷达数据在导航过程中间歇性丢失,过几秒又恢复,排查下来是USB转TTL模块供电不稳,雷达在电流波动时自我复位。换了一根带磁环屏蔽的USB线,以及用独立电源给雷达供电,问题彻底解决。

7. 几个容易被问烂的坑位,提前踩给你看

RDK X5 + ROS2这套组合本身不算复杂,但把所有细节串起来,确实有不少坑位值得提前知道。

第一,系统镜像升级要谨慎。地瓜发布的镜像迭代挺快,不同版本之间设备树和默认配置有差异,如果你用的是老版本镜像,照着网上较新的教程去配置文件路径,可能压根找不到对应文件。遇到这种情况,先确认自己的镜像版本,再决定要不要升级。

第二,串口权限问题反复出现。不仅驱动板串口需要dialout权限,雷达串口、USB摄像头设备权限都可能涉及,最好一劳永逸地把所有相关用户组加好。另外如果插了多个USB转串口设备,/dev/ttyUSB0的编号可能漂移,最好写一个udev规则,根据设备ID固定映射到自定义名称。

第三,BPU模型转换时最容易犯的错是不看转换日志。每次转换都会输出各算子的支持情况和性能预估,一眼就能看出哪个算子被跑在CPU上了。如果某个算子显示“not supported”,尽量回到模型导出阶段,把这个算子替换掉,哪怕精度损失一点,换来的推理速度提升非常明显。

第四,排查ROS2问题时要善用几个基础工具。话题有数据没数据,ros2 topic hz /scan看频率;节点之间没连通,ros2 doctor做诊断;坐标变换看不到,ros2 run tf2_ros tf2_echo base_link laser_link。这些基础排查手段比盲目改代码高效得多。

第五,也是我觉得最重要的,整套系统的实时性取决于最弱的那个环节。我刚开始只优化感知节点的推理速度,模型从50ms优化到20ms,但回头发现底盘驱动节点串口通信的响应间隔是100ms,整个系统跟手的程度还是慢。后来排查发现是驱动节点用了阻塞式串口读,加了超时和非阻塞处理之后,整个控制链路流畅了很多。在机器人系统里,算法快不代表系统快,端到端的时延才是决定体验的关键指标。

RDK X5这套板子,加上ROS2生态,组装一台功能完整的自主移动机器人已经不是门槛很高的事。硬件接口现成、ROS2环境预适配、BPU推理工具有现成的链路,真正花时间的在于把每个环节的细节磨顺。这篇记录写到的内容,基本覆盖了我从零到整链路跑通全过程遇到的主要问题和处理思路,希望能给正在这条路上折腾的朋友省一些时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询