ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

机器人系统架构与ROS2实战:从硬件选型到节点通信

机器人系统架构与ROS2实战:从硬件选型到节点通信 1. 机器人系统架构到底在解决什么问题很多人第一次接触机器人开发脑子里冒出来的画面是电影里那种人形机器但实际上你身边跑得最多的机器人是扫地机、AGV 小车、机械臂、巡检机器人这类东西。它们看起来形态各异但拆开来看底层要解决的问题高度一致感知环境、做出决策、驱动执行、保持稳定。这四件事要同时跑起来还得跑得协调靠的就是一套清晰的系统架构。机器人系统架构这个词听起来很虚但你可以把它理解成盖房子。硬件是地基和承重墙决定了这栋楼能盖多高、能扛多大重量软件是水电管网和房间布局决定了住起来顺不顺手而 ROS2 这类中间件就是那套标准化的接口和管线规范让不同厂家生产的水管、电线、开关能互相插得上。没有这套规范你每换一个传感器就得重写一遍通信代码项目根本没法维护。我见过太多团队在项目初期不重视架构设计硬件选型拍脑袋软件全写在一个大 main 函数里传感器数据用全局变量传来传去。结果就是加一个雷达要改三天代码换一个电机驱动要重新调一遍时序最后整个系统脆得像纸糊的谁都不敢动。所以这一讲的核心价值不是教你某个具体工具怎么用而是帮你建立一套分层思考、接口清晰、可替换可扩展的架构观。这篇文章适合谁看如果你是刚入门的机器人方向学生它能帮你把零散的知识点串成一条线如果你是转行过来的软件工程师它能告诉你机器人软件和普通后端软件的本质区别在哪如果你是硬件工程师想往系统层面走它能让你明白你的电路板在整个系统里处于什么位置、需要向上提供什么接口。ROS2 在这里扮演的角色是把硬件和软件粘合起来的那层胶水理解它你就理解了现代机器人系统的组织方式。2. 硬件层机器人的身体是怎么搭起来的2.1 计算平台选型大脑放哪里机器人硬件架构的第一层是计算平台也就是机器人的“大脑”。这个大脑通常不是一颗芯片而是一个计算集群因为不同任务对算力的需求差异极大。感知和规划需要跑神经网络、做点云配准、解优化问题这些吃 CPU 和 GPU而底层电机控制、传感器读取需要的是实时性和确定性对算力要求不高但对抖动极其敏感。常见的做法是异构计算一块高性能主控比如 x86 工控机或者 NVIDIA Jetson 系列负责上层感知决策一块或多块微控制器STM32、ESP32 这类负责底层实时控制。两者之间通过串口、CAN 总线或者以太网通信。为什么不用一块强芯片全包了因为通用操作系统Linux的调度抖动可能达到毫秒级甚至更高而电机控制环路往往要求微秒级的确定性响应这是操作系统层面保证不了的必须靠 MCU 的裸机或 RTOS 来兜底。选主控的时候我一般会看几个硬指标算力TOPS 或 GFLOPS、功耗、接口丰富度、散热条件、供货稳定性。举个例子Jetson Orin 系列算力强、功耗相对可控、自带 CSI 摄像头接口和千兆网口适合移动机器人而如果是固定式的机械臂控制柜空间和散热不是问题直接上带独立显卡的工控机更划算。这里没有绝对优劣只有场景匹配。注意算力不是越高越好。算力越高通常功耗越大、发热越猛移动平台上电池和散热都是瓶颈。我见过有人给小型 AGV 塞了张桌面级显卡结果续航从 6 小时掉到 1 小时散热风扇噪音比电机还大得不偿失。2.2 传感器与执行器感知和动作的物理边界传感器是机器人的感官。按测量对象分有测自身状态的编码器、IMU、电流传感器和测外部环境的激光雷达、摄像头、超声波、毫米波雷达。执行器则是电机、舵机、气缸这些能把电信号变成物理动作的部件。这里有个架构上的关键点传感器和执行器的接口标准化程度直接决定了你软件层的开发效率。如果每个传感器都走私有协议你的驱动代码会变成一团乱麻。所以实际项目中我会尽量选择支持标准接口的设备能走 CAN 的走 CAN能走以太网的走以太网能输出标准 ROS2 消息类型的优先。这样在软件层我只需要写一次订阅逻辑换设备时改改配置就行。以激光雷达为例市面上主流产品基本都提供 ROS2 驱动包输出sensor_msgs/LaserScan或sensor_msgs/PointCloud2类型的消息。你的上层导航算法只认这个消息类型不关心背后是哪个牌子的雷达。这就是接口标准化的威力——硬件可替换软件不用动。执行器这边电机驱动通常需要接收速度指令、返回编码器计数和电流值。常见的做法是 MCU 通过 PWM 驱动电机同时读取编码器做闭环然后通过串口把状态打包发给主控。主控这边写一个 ROS2 节点把串口数据解析成geometry_msgs/Twist或者自定义的关节状态消息。这个节点就是硬件和软件之间的翻译官。2.3 通信总线硬件之间的神经网络硬件之间怎么连是个容易被忽视但极其重要的问题。常见的总线有几种各有各的适用场景。总线类型典型速率适用场景特点UART115200bps~几MbpsMCU 与主控通信简单可靠点对点距离短CAN1Mbps经典CAN电机、电池管理、车载设备抗干扰强多主架构适合实时控制Ethernet100M~10G激光雷达、摄像头、主控互联带宽大支持多设备协议栈成熟I2C/SPI几百k~几十M板级传感器距离极短适合芯片间通信选总线的逻辑很简单看数据量和实时性要求。激光雷达每秒产生几十兆的点云数据必须走以太网电机控制指令数据量小但要求周期稳定CAN 或 UART 就够板载 IMU 走 I2C 最省事。我踩过的坑是早期用 USB 转串口连激光雷达数据量一大就丢包后来换成原生以太网接口才稳定。所以带宽要留足余量别卡着上限用。3. 软件层让硬件动起来的逻辑组织3.1 分层架构从驱动到应用机器人软件不是一坨代码而是分层的。我习惯把它分成四层驱动层、抽象层、功能层、应用层。驱动层直接跟硬件打交道负责读写寄存器、解析串口数据、收发 CAN 帧。这一层的代码跟具体硬件强绑定换硬件就得改。抽象层把驱动层的数据统一成标准消息格式比如把不同雷达的数据都转成PointCloud2。功能层实现具体算法比如 SLAM、路径规划、运动控制。应用层则是业务逻辑比如“巡检机器人按路线走一圈并拍照上传”。这样分层的好处是变更隔离。换雷达只改驱动层和抽象层功能层和应用层不动改导航算法只动功能层硬件相关代码不碰。我见过不分层的项目一个文件几千行改一个参数要在里面翻半天维护成本极高。ROS2 在这套分层里扮演什么角色它提供了节点间的通信机制让每一层可以做成独立的节点通过话题、服务、动作来交互。驱动层节点发布传感器数据功能层节点订阅并处理应用层节点发控制指令。节点之间松耦合一个节点崩了不至于整个系统挂掉当然关键节点要有监控和重启机制。3.2 ROS2 的核心概念Node、Topic、Service、ActionROS2 的几个核心概念我用生活化的方式解释一遍。Node节点就是一个独立运行的程序干一件具体的事。比如一个节点专门读雷达一个节点专门做定位一个节点专门控制电机。每个节点可以独立启动、停止、调试。这跟微服务架构的思路很像只不过跑在机器人内部。Topic话题是节点之间传数据的通道采用发布/订阅模式。一个节点往话题里发消息所有订阅了这个话题的节点都能收到。这是单向、异步、多对多的通信。传感器数据流最适合用话题因为它是持续不断的而且可能有多个消费者。Service服务是请求/响应模式客户端发一个请求服务端处理完返回结果。这是双向、同步的。适合那种“问一次答一次”的场景比如“查询当前地图列表”“切换控制模式”。Action动作是服务的高级版适合长时间运行的任务。比如“导航到目标点”这个任务可能跑几十秒中间要反馈进度还要能被取消。Action 就提供了目标、反馈、结果三件套。通信方式模式同步性典型场景Topic发布/订阅异步传感器数据、控制指令Service请求/响应同步参数查询、模式切换Action目标/反馈/结果异步可取消导航、抓取、充电选哪种通信方式核心看数据流向和任务时长。持续不断的数据流用 Topic一问一答用 Service长任务用 Action。这个判断标准我用了好几年基本没出过错。3.3 为什么是 ROS2 而不是 ROS1ROS1 在机器人圈子里用了很多年但它的架构有几个硬伤。最核心的是它依赖一个中心化的 Master 节点所有节点启动都要先找 Master 注册。Master 一挂整个系统就瘫了。而且 ROS1 的通信基于 TCP在 WiFi 这种不稳定网络下表现很差丢包重传会导致延迟飙升。ROS2 把这些问题都改了。它去掉了中心 Master改用 DDS数据分发服务作为通信底层。DDS 是工业界成熟的通信中间件支持去中心化发现、多种 QoS 策略、实时性保障。节点之间自动发现不需要中心协调。QoS 可以配置可靠性、持久性、历史深度等参数让你根据场景权衡实时性和可靠性。另一个重要变化是 ROS2 支持多平台Linux、Windows、macOS 都能跑还支持实时操作系统。这对工业场景很重要因为很多产线设备跑的不是 Linux。ROS2 还改进了构建系统ament/colcon、客户端库rclcpp/rclpy整体工程化程度高了不少。当然 ROS2 也不是没缺点。DDS 的配置比较复杂默认参数在 WiFi 下可能表现不佳需要调 QoS。生态方面ROS1 的一些老包还没完全迁移过来。但新项目我基本都推荐直接上 ROS2因为它是明确的方向ROS1 已经停止维护了。4. 实操从零搭一个 ROS2 节点4.1 环境安装与版本选择ROS2 的安装第一步是选版本。ROS2 的版本和 Ubuntu 版本是绑定的选错了装不上。截至目前常见的搭配是Ubuntu 22.04 配 HumbleUbuntu 24.04 配 Jazzy。Humble 是 LTS 版本支持到 2027 年生态最成熟新手首选。安装过程官方文档写得很清楚我补充几个实操要点。首先换源。默认的软件源在国内下载速度感人换成国内镜像源能快很多。其次locale 设置ROS2 对字符编码有要求UTF-8 是必须的否则编译时会报奇怪的错。最后环境变量每次开新终端都要 source 一下 setup 文件嫌麻烦就写进.bashrc。# 设置 locale sudo apt update sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALLen_US.UTF-8 LANGen_US.UTF-8 # 添加 ROS2 源以 Humble 为例 sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update sudo apt install curl -y 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 $(. /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注意ros-humble-desktop包含 GUI 工具RViz2、rqt体积较大。如果是服务器或者资源受限设备装ros-humble-ros-base就够需要 GUI 工具再单独装。装完之后验证一下source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp talker另开一个终端跑 listener能看到消息打印就说明装好了。4.2 创建第一个工作空间和节点工作空间是 ROS2 项目的组织单位。我习惯在~/ros2_ws下建结构清晰。mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build source install/setup.bash然后创建一个 Python 包cd ~/ros2_ws/src ros2 pkg create --build-type ament_python my_robot_pkg --dependencies rclpy这会在src下生成my_robot_pkg目录里面有package.xml、setup.py和一个同名的 Python 包目录。节点代码就写在那个目录里。一个最简单的发布者节点长这样import rclpy from rclpy.node import Node from std_msgs.msg import String class MinimalPublisher(Node): def __init__(self): super().__init__(minimal_publisher) self.publisher_ self.create_publisher(String, chatter, 10) timer_period 0.5 self.timer self.create_timer(timer_period, self.timer_callback) self.i 0 def timer_callback(self): msg String() msg.data fHello World: {self.i} self.publisher_.publish(msg) self.get_logger().info(fPublishing: {msg.data}) self.i 1 def main(argsNone): rclpy.init(argsargs) minimal_publisher MinimalPublisher() rclpy.spin(minimal_publisher) minimal_publisher.destroy_node() rclpy.shutdown() if __name__ __main__: main()这段代码做了几件事初始化 rclpy、创建节点、创建发布者话题名chatter队列深度 10、创建定时器每 0.5 秒触发一次回调、在回调里发消息并打印日志。rclpy.spin让节点保持运行不断处理回调。写完要在setup.py里注册入口点否则ros2 run找不到entry_points{ console_scripts: [ talker my_robot_pkg.minimal_publisher:main, ], },然后colcon build编译source 之后ros2 run my_robot_pkg talker就能跑了。4.3 话题通信实测与调试工具节点跑起来之后怎么确认它在正常工作ROS2 提供了一套命令行工具非常实用。# 查看当前所有话题 ros2 topic list # 查看某个话题的消息内容 ros2 topic echo /chatter # 查看话题的发布频率 ros2 topic hz /chatter # 查看话题的详细信息发布者、订阅者、QoS ros2 topic info /chatter --verboseros2 topic hz是我用得最多的命令。调试传感器时第一件事就是看数据频率对不对。雷达标称 10Hz实际跑出来 3Hz那肯定是哪里堵了。ros2 topic echo用来确认消息内容是否符合预期比如看看激光雷达的range_min和range_max是不是合理值。还有一个神器是rqt图形化工具集。rqt_graph能把当前所有节点和话题的连接关系画成图一眼就能看出谁在发谁在收。系统复杂了之后这张图能帮你快速定位通信断在哪里。实操心得调试多节点系统时我习惯先单独跑每个节点用ros2 topic echo确认它输出正常再逐步把节点连起来。这样出问题时能快速定位是哪个节点的问题而不是一上来就跑整个系统然后抓瞎。5. 硬件与软件如何对接一个完整案例5.1 场景描述差速驱动小车假设我们要做一台差速驱动的小车硬件配置是Jetson Orin Nano 做主控STM32 做底层控制一个 2D 激光雷达一个 IMU两个带编码器的直流电机。软件上要跑 ROS2实现遥控和里程计发布。这个案例麻雀虽小五脏俱全涵盖了硬件选型、通信设计、节点划分、消息定义等核心环节。5.2 硬件连接与通信协议设计硬件连接方案激光雷达走以太网接 JetsonIMU 走 I2C 接 STM32电机编码器接 STM32 的定时器编码器接口STM32 通过 UART 接 Jetson。为什么 IMU 接 STM32 而不是直接接 Jetson因为 IMU 数据需要和电机控制做时间同步放在同一个 MCU 上处理时序更可控。UART 通信协议我设计成固定长度的帧包含帧头、左右轮编码器计数、IMU 数据、校验和。帧头用两个字节的魔数防止数据错位。校验和用简单的累加取反够用且计算量小。字段长度字节说明帧头2固定值 0xAA 0x55左轮计数4int32累计脉冲数右轮计数4int32累计脉冲数IMU 加速度6三个 int16IMU 角速度6三个 int16校验和1前面所有字节累加取低 8 位帧尾1固定值 0x0D这个协议简单、解析快、不易出错。工业场景里很多自定义协议都是这个套路没必要上复杂的序列化框架除非数据量特别大或者结构特别复杂。5.3 ROS2 节点划分与消息流转软件这边我划分成四个节点serial_driver 节点负责跟 STM32 收发串口数据解析成自定义消息发布出去同时订阅速度指令下发给 STM32。odom_publisher 节点订阅编码器数据结合运动学模型计算里程计发布nav_msgs/Odometry。imu_publisher 节点把 IMU 原始数据转成sensor_msgs/Imu发布。teleop 节点接收键盘输入发布geometry_msgs/Twist速度指令。数据流是这样的teleop 发 Twist → serial_driver 订阅 Twist 并转成串口帧发给 STM32 → STM32 驱动电机并回传编码器数据 → serial_driver 解析后发布自定义编码器消息 → odom_publisher 订阅并计算里程计 → 发布 Odometry 供导航使用。这个划分的好处是每个节点职责单一serial_driver 只管串口odom_publisher 只管运动学计算。换串口协议只改 serial_driver换运动学模型只改 odom_publisher。5.4 里程计计算的关键参数差速小车的里程计计算核心是轮速到线速度和角速度的转换。公式如下v (v_left v_right) / 2 ω (v_right - v_left) / L其中L是左右轮间距v_left和v_right是左右轮的线速度。轮速又由编码器计数算出来v_wheel (Δcount / counts_per_rev) * 2π * r / Δtcounts_per_rev是电机转一圈编码器的计数要考虑减速比和编码器线数r是轮半径Δt是采样周期。这里有个坑轮半径和轮间距的标定。理论值往往和实际有偏差轮子压在地上会变形实际半径比标称小一点轮间距测量也有误差。这两个参数不准里程计会越跑越偏。我的做法是让小车直线走 5 米看里程计报了多少按比例修正轮半径再原地转 10 圈看角度报了多少修正轮间距。标定完精度能提升一个档次。注意里程计是相对定位误差会累积。跑得越远偏得越多。所以实际导航系统里里程计只用来做短时间内的运动估计长时间定位要靠激光雷达或者视觉做重定位。别指望纯里程计能跑完全程。6. 常见问题与排查技巧实录6.1 节点启动失败与依赖问题ROS2 节点启动失败最常见的原因是环境没 source。ROS2 的包路径靠环境变量找新开终端不 source 就找不到包。解决办法是写进.bashrc或者用source install/setup.bash手动加载。第二个常见原因是依赖缺失。Python 节点报ModuleNotFoundError说明某个包没装。用rosdep可以自动装依赖rosdep install --from-paths src --ignore-src -r -yC 节点编译报找不到头文件通常是package.xml里没声明依赖或者CMakeLists.txt里没find_package。6.2 通信异常排查思路话题收不到数据按这个顺序排查ros2 node list确认节点在跑ros2 topic list确认话题存在ros2 topic info /xxx --verbose看发布者和订阅者的 QoS 是否匹配ros2 topic hz /xxx看有没有数据流ros2 topic echo /xxx看数据内容QoS 不匹配是 ROS2 特有的坑。发布者设了reliable订阅者设了best_effort在某些 DDS 实现下可能连不上。默认情况下 ROS2 的传感器数据用best_effort控制指令用reliable搞清楚这个规律能省很多时间。现象可能原因排查方法节点找不到环境未 source检查ROS_DISTRO环境变量话题无数据发布者未启动或 QoS 不匹配ros2 topic info --verbose数据频率低网络拥塞或处理耗时ros2 topic hz对比标称值编译失败依赖缺失或版本冲突看编译日志用 rosdep 补依赖串口打不开权限不足或设备被占用ls -l /dev/ttyUSB*加 dialout 组6.3 实时性与性能优化经验机器人系统对实时性有要求但 Linux 不是实时操作系统。能做的优化有几种提高进程优先级用chrt或nice、绑定 CPU 核心taskset、用实时内核PREEMPT_RT 补丁。这些手段能降低抖动但不能完全消除。软件层面的优化更重要。避免在回调里做耗时操作回调应该尽快返回重活丢给单独的线程或节点。控制消息队列深度队列太深会导致延迟累积太浅会丢数据一般传感器数据队列深度设 1 到 10 之间。减少数据拷贝ROS2 支持零拷贝传输大块数据比如点云用共享内存能显著降低开销。我实测下来Jetson Orin Nano 上跑激光雷达 里程计 简单避障CPU 占用大概 30% 到 40%还有余量。但如果加上视觉 SLAM 或者深度学习推理就得精打细算了该用硬件加速的用硬件加速该降分辨率的降分辨率。7. 架构设计的几条个人经验做机器人系统这些年踩过的坑比走过的路还多有几条经验我觉得值得分享。第一接口先行。动手写代码之前先把节点划分和消息定义定下来。消息格式一旦定了上下游就能并行开发不用互相等。我习惯用.msg文件把自定义消息定义清楚这本身就是最好的接口文档。第二硬件选型留余量。算力留 30% 余量带宽留 50% 余量电源功率留 20% 余量。项目后期加功能是常态一开始就卡着上限选型后面必然捉襟见肘。第三日志和可视化要早做。ROS2 的rqt、rviz2、ros2 bag这些工具项目一开始就用起来。ros2 bag record能把话题数据录下来出问题了回放分析比在现场反复复现高效得多。第四别过度设计。架构要清晰但不是越复杂越好。小项目两三个节点能搞定的事别拆成十个节点。节点间通信有开销调试也麻烦。架构的复杂度应该跟项目规模匹配能跑起来、好维护、方便扩展就是好架构。最后说一个我自己的体会机器人系统架构这门手艺看书看文档只能学个大概真正的理解来自于动手搭、动手调、动手改。你搭过一遍踩过一遍坑那些概念才会变成你自己的东西。ROS2 的生态还在快速演进今天的最佳实践明天可能就过时了但分层、解耦、接口标准化这些底层原则是不会变的。
返回列表