ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

树莓派ROS2串口控制STM32小车:从/cmd_vel到电机驱动

2026/9/4 6:46:17 拓冰建站 浏览量
树莓派ROS2串口控制STM32小车:从/cmd_vel到电机驱动 搞过轮式小车项目的朋友应该都有体会真正费时间的往往不是电机驱动也不是某个传感器而是“树莓派里的 ROS2 话题数据怎么可靠地送到 STM32 去控制电机”。ROS2 侧/cmd_vel用起来很方便但串口另一头的单片机就像一个黑盒帧格式、校验、粘包半包、电机方向、超时停车每个环节都可能埋坑。这篇文章围绕“树莓派运行 ROS2、通过串口控制 STM32 小车”这套完整链路展开从系统架构讲到通信协议再到两端代码和联调排错。内容偏向一套可以直接照做的闭环教程适合 ROS2 初学者、STM32 玩家以及正在做竞赛小车或课程设计轮式机器人的人。1. 项目背景与整体架构1.1 为什么是树莓派 ROS2 STM32很多入门小车方案是“STM32 直接控制电机”代码简单但缺少上层调度能力也见过“树莓派直接接电机驱动板”接线省事但受 Linux 实时性和 GPIO 数量的限制。把两者组合起来是比较主流且适合做扩展的架构树莓派负责“大脑”工作运行 ROS2、接收手柄/键盘指令、跑导航算法、处理摄像头、做路径规划。STM32 负责“小脑”工作接收串口指令、产生 PWM、控制电机方向、读取编码器、做底层速度闭环。两者通过串口UART通信。ROS2 话题里的速度消息经过协议打包后发送给 STM32STM32 解析后驱动电机。这个组合的优点是分工明确。Linux 生态丰富开发效率高STM32 实时性好适合直接操作定时器、GPIO 和中断。后续你想加激光雷达、IMU、摄像头都只需要在树莓派这端扩展 ROS2 功能包底盘端不必反复改。1.2 系统模块与数据流整个小车系统的数据流可以概括为键盘/手柄/Rviz2 ↓ /cmd_vel 话题 (geometry_msgs/Twist) ↓ 树莓派 ROS2 串口桥接节点 serial_bridge ↓ 串口协议帧 USB 转 TTL / 板载 UART ↓ STM32 串口接收解析 ↓ 运动学解算v、w 换算左右轮速度 ↓ TIM 定时器 PWM 输出 GPIO 方向控制 ↓ 电机驱动模块TB6612 / L298N ↓ 直流减速电机带动轮子从 ROS2 的角度看最核心的是如何将/cmd_vel话题中的Twist消息转换成一段适合串口传输的二进制协议。STM32 端不需要关注 ROS2 是什么只需要能正确解析协议帧并执行控制。1.3 本文覆盖的内容本文不会把 ROS2 和 STM32 的所有细节从头讲一遍而是把两端的衔接点讲透。读完你可以掌握树莓派上 ROS2 环境与工作空间的搭建思路。/cmd_vel话题的含义和使用方式。一套适合 STM32 解析的串口帧协议设计。树莓派端桥接节点的 Python 实现。STM32 端串口接收状态机、PWM 输出的核心逻辑。两端联调时需要使用的命令和排查手段。2. 环境准备与版本规划2.1 树莓派系统与 ROS2 版本树莓派 4B 和树莓派 5 都可以运行 ROS2。本文以 Ubuntu 22.04 64 位系统加 ROS2 Humble 为例这也是目前资料最丰富、遇到问题最容易搜到解决方案的组合。如果你手里是树莓派 5同样可以安装 Ubuntu 22.04 或 Ubuntu 24.04。需要留意的是ROS2 版本和 Ubuntu 版本有对应关系不能随便混用。Ubuntu 版本对应 ROS2 LTS 版本Ubuntu 22.04ROS2 HumbleUbuntu 24.04ROS2 Jazzy版本差异会影响 apt 安装包名和部分 API但本文的核心思路是通用的。无论你装的是 Humble 还是 Jazzycmd_vel、串口协议、STM32 控制逻辑基本一致。ROS2 的安装建议直接参考官方二进制安装流程。安装完成后在终端中执行apt-cache search ros-humble | head -n 5如果能搜索到大量 ROS2 功能包说明源已经配置好了。然后安装桌面版sudo apt update sudo apt install ros-humble-desktop -y对于纯命令行调试的树莓派也可以安装ros-humble-ros-base之后按需装rviz2等可视化工具。安装完成后将 ROS2 环境写入 shell 配置echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc如果下载速度不理想可以先把 Ubuntu 的 apt 源更换为速度较好的镜像源再去安装 ROS2。务必注意镜像源要与系统版本严格匹配不要混用不同版本的源。2.2 STM32 开发环境STM32 端推荐使用 STM32CubeMX 生成初始化工程再配合 Keil MDK 或 STM32CubeIDE 编写业务代码。无论你用哪一套 IDE核心思路都相同串口 USART1用于接收树莓派指令。定时器 TIM2 / TIM3输出 PWM分别控制左右电机。GPIO控制电机驱动模块的方向引脚。可选编码器接口后续做速度闭环时使用。MCU 型号以 STM32F103 系列为例。如果你用的是 STM32F407、STM32G4 等型号底层库函数会有差异但串口解析状态机和 PWM 控制思路完全一致。2.3 通信硬件准备树莓派与 STM32 之间最常见、最稳定的连接方式是 USB 转 TTL 串口模块树莓派 USB 口 ↓ USB 转 TTL 模块 ↓ TXD RXD GND STM32 的 USART1注意交叉连接USB 转 TTL 的 TXD 接 STM32 的 RXRXD 接 STM32 的 TXGND 必须共地。如果使用 STM32 板载的 USB 转串口芯片树莓派端看到的设备名通常是/dev/ttyACM0如果使用独立 CH340/CP2102 模块则通常是/dev/ttyUSB0。也可以直接连接树莓派的 GPIO UART 引脚但需要先关闭系统串口控制台功能还要注意电平匹配问题。新手阶段建议优先使用 USB 转 TTL既省事又便于排查。3. ROS2 话题与通信协议设计3.1 /cmd_vel 话题说明在 ROS2 机器人系统中速度控制指令一般通过/cmd_vel话题发布消息类型是geometry_msgs/msg/Twist。它包含两部分linear线速度单位 m/s。轮式小车通常只使用linear.x。angular角速度单位 rad/s。差速小车通常只使用angular.z。下面命令可以手动发布一个“直行 0.2 m/s”的指令ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist \ {linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}按下Ctrl C停止发布后小车并不会自动刹车。因此实际控制中必须做“超时停车”处理否则树莓派端程序异常退出后STM32 还在执行最后一条速度指令这是非常危险的行为。3.2 串口通信帧格式设计ROS2 的Twist消息是 float 类型而单片机解析浮点并不方便而且不同平台浮点存储格式也存在隐患。因此上位机先将速度转换成整数再通过二进制帧发送。这里给出一种简单可靠的帧格式| 0xAA | 0x55 | cmd | data_len | data ... | checksum |字段说明字段长度说明0xAA1 字节帧头 10x551 字节帧头 2cmd1 字节命令字例如 0x01 表示运动控制data_len1 字节数据段长度 NdataN 字节数据内容checksum1 字节校验和等于前面所有字节累加后取低 8 位以运动控制命令为例0xAA 0x55 0x01 0x04 [VX_L] [VX_H] [WZ_L] [WZ_H] CHECKSUM其中线速度VX的单位是 mm/s角速度WZ的单位是 0.01 rad/s都使用 int16 小端字节序。为什么要放大因为 ROS2 中linear.x可能是 0.23 这样的浮点数串口传输整数更高效、更容易调试所以在树莓派端放大 1000 或 100STM32 端再缩小还原。校验和计算示例假设发送linear.x 0.2 m/sangular.z 0 rad/sVX 200 mm/s十六进制为0xC8 0x00WZ 0十六进制为0x00 0x00此时 CHECKSUM (0xAA 0x55 0x01 0x04 0xC8 0x00 0x00 0x00) 0xFF。这套协议的好处是结构简单可以在命令字上继续扩展比如增加舵机控制命令、编码器回传命令、蜂鸣器命令等。3.3 从 Twist 到左右轮速度差速小车底盘有两个主动轮ROS2 下发的是车体中心线速度 v 和角速度 w需要先换算成左右轮速度v_left v - w * wheel_base / 2 v_right v w * wheel_base / 2其中wheel_base是两个驱动轮之间的轮距。做这一步换算可以在 STM32 端也可以放在树莓派端。放在 STM32 端的优势是STM32 直接控制电机后续加入编码器闭环时不依赖上层。本文示例采用在 STM32 端做运动学换算的方案。3.4 稳定通信的三个原则第一控制周期稳定。ROS2 端不需要每次收到话题都立刻发串口帧而是用一个固定周期定时器发送最新速度值例如 50ms 发送一次防止话题发布频率忽高忽低。第二两端都要做超时保护。STM32 如果超过 500ms 没有收到新指令就立即停车。这是最后一道安全防线。第三程序调试阶段要分级。先把 PC 串口助手跑通再对接树莓派先把固定速度指令跑通再接键盘遥控。4. 树莓派 ROS2 节点实现4.1 创建 ROS2 工作空间与功能包先创建 ROS2 工作空间mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ros2 pkg create car_bringup --build-type ament_python不过ros2 pkg create自动生成的文件不够完整需要手动补充。建议结构如下~/ros2_ws/src/car_bringup/ ├── package.xml ├── setup.py ├── setup.cfg ├── launch/ │ └── car_launch.py ├── resource/ │ └── car_bringup └── car_bringup/ ├── __init__.py └── serial_bridge.py在setup.py中声明节点入口entry_points{ console_scripts: [ serial_bridge car_bringup.serial_bridge:main, ], },在package.xml中添加运行依赖exec_dependrclpy/exec_depend exec_dependgeometry_msgs/exec_depend exec_dependpython3-serial/exec_depend还需要安装 PySerial 库sudo apt install python3-serial -y如果使用虚拟环境也可以使用pip install pyserial。4.2 串口桥接节点完整代码文件路径~/ros2_ws/src/car_bringup/car_bringup/serial_bridge.py。#!/usr/bin/env python3 import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from rclpy.qos import QoSProfile, ReliabilityPolicy import serial import struct import math class SerialBridgeNode(Node): def __init__(self): super().__init__(serial_bridge) # 参数可以通过 launch 文件或命令行覆盖 self.declare_parameter(port, /dev/ttyUSB0) self.declare_parameter(baudrate, 115200) self.declare_parameter(send_hz, 20.0) self.declare_parameter(stop_timeout, 0.5) port self.get_parameter(port).value baudrate self.get_parameter(baudrate).value send_hz self.get_parameter(send_hz).value self.stop_timeout self.get_parameter(stop_timeout).value # 打开串口 try: self.ser serial.Serial(port, baudrate, timeout0.05) self.get_logger().info( f串口已打开: {port} {baudrate} ) except Exception as e: self.get_logger().fatal(f无法打开串口 {port}: {e}) raise SystemExit(1) # 订阅/cmd_vel qos QoSProfile( depth10, reliabilityReliabilityPolicy.BEST_EFFORT ) self.subscription self.create_subscription( Twist, /cmd_vel, self.cmd_vel_callback, qos ) # 保存最新速度指令 self.current_vx 0.0 self.current_wz 0.0 self.last_cmd_time self.get_clock().now() # 固定周期发送 self.send_period