ARTICLE DETAIL

资讯详情

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

树莓派ROS2与STM32串口联动:智能小车上下位机控制全流程

树莓派ROS2与STM32串口联动:智能小车上下位机控制全流程 树莓派和 STM32 单独玩都不难一个是 Linux 小主机一个是经典单片机。但把两者组合成一台能跑 ROS2 的智能小车难点就变了不是点灯不是 Hello World而是树莓派上的 ROS2 节点怎么把速度指令可靠地发给 STM32STM32 又是怎么把编码器数据、电池电压这些信息实时上报给 ROS2。这篇文章不绕弯直接拆解“树莓派 ROS2 控制 STM32 小车”的完整链路。你会看到我推荐的上下位机架构、串口通信协议设计、树莓派端 ROS2 节点怎么写、STM32 端解析代码怎么写以及联调时最容易踩的坑。1. 树莓派 ROS2 控制 STM32 小车的整体架构做机器人小车第一件事不是写代码而是确定上下位机分工。常见方案有两种一种是树莓派直接控制电机驱动板另一种是树莓派只做决策把底层控制全部交给 STM32。1.1 上位机树莓派树莓派在 ROS2 架构里承担的是“大脑”角色跑 Ubuntu Server 或 Desktop 版本上面运行 ROS2 主节点、SLAM 建图、导航规划、视觉识别等计算密集型任务。常见型号是树莓派 4B 或 5内存建议 4GB 起步因为跑 Gazebo 仿真、rviz2 可视化、激光雷达驱动时内存占用会明显上升。树莓派的优势是生态完整几乎所有 ROS2 功能包都能直接用 apt 装不用自己交叉编译。缺点是 GPIO 输出的实时性不够稳定Linux 内核调度、后台进程、Wi-Fi 干扰都会让 PWM 波形抖动。这就是为什么电机控制这类硬实时任务必须放 STM32。1.2 下位机STM32STM32 承担“小脑”角色负责所有硬实时任务读取编码器、输出 PWM 控制电机、采集 IMU 和电池电压、处理超声波传感器。常用的型号是 STM32F103C8T6 或 STM32F407VE前者便宜够用后者性能更强、定时器和 ADC 资源更丰富。STM32 端不跑 ROS2因为 ROS2 需要 Linux 或 RTOS 环境而 STM32F103 裸机跑 ROS2 Client Library 很吃力。这里用串口作为桥接通道让 STM32 通过自定义通信协议和树莓派交换数据。1.3 整体数据流[键盘/手柄] -- [ROS2 teleop 节点] -- /cmd_vel 话题 | \|/ 树莓派 serial 节点 | \|/ 串口 UART (USB 或 GPIO) | \|/ STM32 串口中断接收并解析 | \|/ PWM 控制电机驱动板上行方向STM32 定时把编码器里程计数据、底盘状态封装成数据帧发回树莓派树莓派再发布到/odom话题供导航栈或 rviz2 使用。1.4 为什么不用树莓派 GPIO 直接控制电机从实现难度看树莓派 Python 操作 GPIO 输出 PWM 并不难几分钟就能让电机转起来。但放到 ROS2 导航场景就有几个问题第一树莓派没有硬件实时中断保证系统负载高时 PWM 周期会抖动导致小车速度不稳定。第二如果导航算法崩溃或进程卡死树莓派没有能力自动刹车小车会一直往前冲。STM32 可以设置看门狗超时没收到新指令就自动停车这在物理实验里是保命功能。第三轮式里程计需要高频读取编码器并计算位移把这个丢给 Linux 去做会占用大量 CPU而且时间精度不够。所以结论很清楚树莓派加 STM32 的分层架构不是炫技是机器人工程的基本功。2. 核心能力速览放在 CSDN 上读者第一眼需要看到的是这个方案能做什么、需要什么硬件、难度在哪里。直接整理成表格。能力项说明控制链路ROS2/cmd_vel话题 - 串口 - STM32 解析 - PWM 驱动电机上位机平台树莓派 4B/5Ubuntu 22.04 Server/DesktopROS2 Humble下位机平台STM32F103C8T6 或 STM32F407Keil MDK 或 STM32CubeIDE 开发通信方式UART 串口USB-TTL 或 GPIO 板载串口波特率典型值 115200里程计反馈STM32 读取编码器脉冲通过串口帧上报树莓派发布/odom启动方式树莓派登录后启动serial_bridge节点再启动 teleop 或导航批量能力不涉及批处理多机调试时可使用 ROS2 多机通信在同一局域网跑多台小车难度等级中高级需要同时掌握 Linux、ROS2、STM32 裸机开发、串口协议设计这套方案和“树莓派直接控制电机”相比初期工作量更大但后续扩展性完全不同。加了 STM32 之后你可以继续加编码器闭环、PID 调速、IMU 融合、超声波避障甚至把 ROS2 的 Nav2 导航栈完整跑起来。STM32 只负责执行树莓派只负责规划和感知职责清晰不容易出混乱。3. 适用场景与使用边界3.1 适合什么场景这套架构最适合的是 ROS2 机器人学习和小型科研平台验证。比如给学生讲 ROS2 话题通信时用键盘控制真实小车前进后退转弯比纯 Gazebo 仿真直观得多。对做毕业设计或竞赛的同学这个方案可以直接作为 Navigation2 导航、Cartographer 建图的底盘基础。另一个典型场景是算法验证。你想实验一个新的路径规划算法不需要关心底盘驱动细节只要让它往/cmd_vel话题发速度指令就行。底层运动控制已经由 STM32 帮你屏蔽掉了。3.2 不适合什么场景如果只是做一个最简单的蓝牙遥控小车不需要引入 ROS2也不需要树莓派STM32 加手机 App 就直接搞定。引入 ROS2 会增加系统复杂度和调试时间对纯遥控需求来说得不偿失。如果目标是工业级无人车涉及功能安全认证和量产树莓派加 STM32 的组合也不是最优选择需要考虑更严格的实时操作系统和冗余设计。3.3 使用边界与合规提醒做这类机器人项目时几个问题必须提前想清楚第一实验环境安全。小车带电机和电池启动前要把轮子架空或用支架撑起避免意外冲下桌沿伤人或损坏设备。第一次联调建议在桌面上架空测试确认时序正确再放地面跑。第二如果小车带摄像头并准备做人脸识别或行人检测涉及他人肖像信息。实验数据只能用于自己学习研究不能随意公开发布或商用。做语音交互的话还要注意录音数据的隐私保护。第三如果使用 ROS2 多机通信把树莓派和 PC 放在同一局域网时要注意网络访问控制。ROS2 默认的 DDS 通信在某些网络环境下会比较开放尽量只在可信局域网内实验不要直接暴露在公网环境。4. 上位机环境准备树莓派 ROS2 部署工欲善其事必先利其器。树莓派上跑 ROS2第一步是选一个和操作系统匹配的 ROS2 发行版。我这边按稳定路线推荐 Ubuntu 22.04 配 ROS2 Humble也可以用树莓派官方系统然后配 Docker 方式跑 ROS2但性能会有损耗不推荐作为主力方案。4.1 安装 Ubuntu 与 ROS2 Humble树莓派 5 上建议装 Ubuntu 22.04 Server 版或者带桌面的 Desktop 版。Server 版更省资源如果你不需要在树莓派上直接看 rviz2就选 Server。如果需要图形界面推荐在 PC 上装 rviz2 通过 ROS2 多机通信连接而不是让树莓派跑重型可视化。在终端执行以下命令安装 ROS2 Humble# 设置编码安装必要工具 sudo apt update sudo apt install -y locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALLen_US.UTF-8 LANGen_US.UTF-8 export LANGen_US.UTF-8 # 添加 ROS2 软件源 sudo apt install -y software-properties-common sudo add-apt-repository universe sudo apt update sudo apt install -y curl sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [archarm64 signed-by/usr/share/keyrings/ros-archive-keyring.gpg] \ http://packages.ros.org/ros2/ubuntu jammy main | \ sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update sudo apt install -y ros-dev-tools然后安装 ROS2 Humble 桌面版或基础版。树莓派上为了省空间可以装 ros-base再加自己需要的功能包sudo apt install -y ros-humble-ros-base sudo apt install -y ros-humble-teleop-twist-keyboard ros-humble-rclpy每次打开终端都要 source 环境echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc如果网络下载慢可以用镜像源加速。给树莓派换源时注意不是所有国内镜像都同步了 ROS2 的 arm64 包优先确认镜像源是否包含ros2仓库否则后面 apt 安装会报 404。4.2 创建 ROS2 工作区接下来建立一个 catkin 风格工作区用于放我们自己的串口桥接节点mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon buildcolcon 是 ROS2 的构建工具第一次执行会自动创建 build、install、log 目录。记住以后每次新建功能包都要回到~/ros2_ws/src目录下操作。4.3 验证树莓派串口设备把 USB-TTL 模块插到树莓派 USB 口另一端接 STM32 的串口引脚。先确认系统识别到了设备ls -l /dev/ttyUSB0如果没显示试一下ls /dev/tty*看看有哪些串口设备。树莓派板载 GPIO 串口对应的设备是/dev/ttyAMA0或/dev/serial0但要先做配置sudo raspi-config # 选择 Interface Options - Serial Port # 登录 shell 访问设为 No硬件串口设为 Yes无论使用 USB-TTL 还是板载串口当前用户都要加入 dialout 组否则没有权限访问串口设备sudo usermod -a -G dialout $USER改完权限后要重新登录或者直接重启树莓派否则权限不会生效。5. 串口通信协议设计上下位机之间传什么、怎么传是这套系统里最核心的工程问题。没有协议规范树莓派发的字节流和 STM32 解析的字节流就是对不齐时序一乱小车就会像喝醉一样抖动。5.1 协议帧格式建议协议设计原则是帧头固定、长度固定或带长度字段、带校验。我采用的是一种简单可靠的方案帧头(0xAA 0x55) | 数据长度(1字节) | 命令字(1字节) | 数据区(N字节) | 校验(1字节)具体字段帧头固定为0xAA 0x55接收端判断帧头后开始组帧。数据长度是数据区字节数加命令字字节数。命令字定义功能。0x01表示速度控制指令0x02表示查询里程计0x03表示底盘状态上报。数据区内容根据命令字不同而不同。校验算法可以直接用累加和简单够用。把帧头之外的所有字节累加取低 8 位。5.2 速度控制指令设计ROS2 的/cmd_vel话题发布的是geometry_msgs/Twist包含线速度linear.x和角速度angular.z。但 STM32 不关心线速度和角速度它需要的是左右轮的目标转速或目标 PWM。所以树莓派端先做运动学解算把线速度和角速度换算成左右轮速度。差速模型公式为v_left linear.x - angular.z * wheel_distance / 2 v_right linear.x angular.z * wheel_distance / 2注意公式里的符号和你的电机安装方向、轮子方向有关。测试时如果发现左转变右转把angular.z取负即可。下发速度指令时是发目标线速度还是发目标 PWM这是很多初学者犹豫的点。直接发 PWM 值实现简单但电池电压下降时同样 PWM 下实际速度会变慢。如果后续要做 SLAM 和里程计必须实现速度闭环让 STM32 编码器测量实际转速再通过 PID 调整 PWM 输出。因此串口协议里建议直接下发目标线速度。底盘要跑0.2 m/s就下发0.2 m/s由 STM32 闭环控制实现这样最合理。速度值在协议里还要考虑精度和传输效率。如果直接用 float 发 4 字节也行但做嵌入式通信更常用的方式是将速度乘以 1000 转成 int16。比如0.235 m/s变成235两个字节传输接收端再除以 1000 还原。这样避免 float 大小端问题也减少协议解析复杂度。速度帧格式可以这样定义0xAA 0x55 0x05 0x01 0x00 0xEB 0x01 0x81 0x6C含义说明0xAA 0x55 帧头 0x05 数据长度 0x01 速度控制命令 0x00 0xEB 左轮目标速度大端表示0x00EB 为 235即 0.235 m/s 0x01 0x81 右轮目标速度0x0181 为 385即 0.385 m/s 0x6C 累加和校验STM32 收到后校验通过就可以提取左右轮速度值交给 PID 控制器执行。5.3 上行里程计帧设计STM32 通过编码器可以测出左右轮实际走过的距离。在差速模型中小车的位移和转角计算公式为distance (distance_left distance_right) / 2 rotation (distance_right - distance_left) / wheel_distanceSTM32 周期性地比如每 50ms计算一次把速度、位移差值、转角差值打包上传。树莓派端收到这些数据后通过 ROS2 的nav_msgs/Odometry话题发布导航栈才能知道小车当前在哪里。里程计上报帧示例0xAA 0x55 0x0D 0x03 [左轮速度 2字节] [右轮速度 2字节] [位移 4字节] [转角 4字节] [校验 1字节]数据区比较长但对 STM32 来说用指针操作字节数组并不复杂关键是发送端和接收端的格式定义要一致包括大小端序、单位、小数点缩放。6. 树莓派 ROS2 串口桥接节点编写树莓派端需要写一个 ROS2 Python 节点负责三件事订阅/cmd_vel话题拿到 Twist 消息。把 Twist 消息中linear.x和angular.z转换为左右轮速度。按照协议拼帧通过串口发送给 STM32。同时这个节点还要周期性读取串口数据解析里程计帧发布到/odom话题。如果暂时不做导航可以先只实现发送部分。6.1 功能包创建在~/ros2_ws/src目录下创建功能包cd ~/ros2_ws/src ros2 pkg create --build-type ament_python stm32_serial_bridge功能包结构stm32_serial_bridge/ ├── package.xml ├── setup.py ├── setup.cfg └── resource/ └── stm32_serial_bridge创建后编辑器打开setup.py在entry_points中加入entry_points{ console_scripts: [ serial_bridge stm32_serial_bridge.serial_bridge:main, ], },6.2 串口发送节点实现这个节点里Serial 通信用的是 Python 的pyserial库。安装方式sudo apt install -y python3-serial完整实现代码#!/usr/bin/env python3 import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist import serial import struct class Stm32SerialBridge(Node): def __init__(self): super().__init__(stm32_serial_bridge) self.declare_parameter(port, /dev/ttyUSB0) self.declare_parameter(baudrate, 115200) port self.get_parameter(port).value baudrate self.get_parameter(baudrate).value self.ser serial.Serial(port, baudrate, timeout0.1) self.wheel_distance 0.35 # 单位: 米需按实际小车轮距修改 self.sub self.create_subscription( Twist, /cmd_vel, self.cmd_vel_callback, 10 ) self.get_logger().info(Serial bridge started, waiting for cmd_vel...) def cmd_vel_callback(self, msg): linear_x msg.linear.x angular_z msg.angular.z v_left linear_x - angular_z * self.wheel_distance / 2.0 v_right linear_x angular_z * self.wheel_distance / 2.0 # 限制最大速度防止协议溢出或电机飞车 max_speed 1.0 v_left max(-max_speed, min(max_speed, v_left)) v_right max(-max_speed, min(max_speed, v_right)) # 浮点转 int16单位 mm/s保留一位小数精度 left_speed int(v_left * 1000) right_speed int(v_right * 1000) self.send_speed_frame(left_speed, right_speed) self.get_logger().info( fLinear: {linear_x:.2f}, Angular: {angular_z:.2f}, fL: {left_speed}, R: {right_speed} ) def send_speed_frame(self, left_speed, right_speed): frame bytearray() frame.append(0xAA) frame.append(0x55) frame.append(5) # 数据长度: 命令字1字节 左轮2字节 右轮2字节 frame.append(0x01) # 速度控制命令 frame.extend(struct.pack(h, left_speed)) # 大端 int16 frame.extend(struct.pack(h, right_speed)) # 大端 int16 checksum sum(frame[2:]) 0xFF frame.append(checksum) self.ser.write(frame) def main(argsNone): rclpy.init(argsargs) node Stm32SerialBridge() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()代码里用了struct.pack(h)按大端序打包 int16 数据STM32 端解析时也要按大端处理。注意一个工程细节linear.x是浮点数但真机控制时如果上位机发来极端值比如导航算法异常输出1.0 m/s或更大速度直接下发给 STM32小车可能直接冲出去。所以节点里必须加限幅我在这里设置了最大速度 1.0 m/s你可以按自己小车的电机能力和测试环境调整得更保守。6.3 构建与测试回到工作区根目录编译cd ~/ros2_ws colcon build --packages-select stm32_serial_bridge source install/setup.bash先不接 STM32可以直接用一个假的串口测试节点是否能正常发布消息。不过这需要虚拟串口工具新手不推荐。更简单的方式是接上 STM32然后在树莓派上跑一个键盘控制节点ros2 run teleop_twist_keyboard teleop_twist_keyboard终端会提示按键说明。按i是前进按,是后退按j、l左右转。此时观察串口桥节点打印的日志确认左右轮速度在正确变化。7. STM32 端固件实现STM32 端用标准库或 HAL 库都可以重点放在串口中断接收、帧解析和电机驱动三个部分。功能代码是结构简单好调试的裸机版本。使用平台是 STM32F103C8T6开发环境为 Keil MDK。如果要用 STM32CubeIDE流程基本一致。7.1 硬件资源分配建议USART1PA9 为 TXPA10 为 RX连接 USB-TTL 模块与树莓派通信。TIM2输出两路 PWM控制左电机。PA0、PA1 或 PA2、PA3 根据驱动板决定。TIM3输出两路 PWM控制右电机。编码器接口使用 TIM4 的 CH1、CH2 接左电机编码器 A、B 相TIM3 的 CH1、CH2 接右电机编码器具体要看你的板子是否支持编码器模式。GPIO电机方向控制引脚连接电机驱动板的 IN1、IN2、IN3、IN4。7.2 串口接收缓冲区设计STM32 接收树莓派发来的速度帧核心问题是字节什么时候到、来多少个。不能靠主循环轮询等待因为主循环还要执行 PID 和电机控制一旦被串口阻塞整个系统实时性就崩了。推荐方案是串口空闲中断加 DMA或者串口接收中断加环形缓冲区。对于 F103 这种没有空闲中断的高级外设最可靠的办法是每接收到一个字节就触发中断把字节存入数组同时判断帧头状态机。定义一个简洁的接收状态机#define FRAME_HEAD1 0xAA #define FRAME_HEAD2 0x55 #define FRAME_MAX_LEN 32 uint8_t rx_buffer[FRAME_MAX_LEN]; uint8_t rx_index 0; uint8_t rx_state 0; uint8_t rx_expected_len 0;在串口中断服务函数里逐步处理void USART1_IRQHandler(void) { uint8_t data; if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { data USART_ReceiveData(USART1); switch (rx_state) { case 0: if (data FRAME_HEAD1) rx_state 1; break; case 1: if (data FRAME_HEAD2) rx_state 2; else rx_state 0; break; case 2: rx_expected_len data; rx_index 0; rx_state 3; break; default: if (rx_index FRAME_MAX_LEN) { rx_buffer[rx_index] data; if (rx_index rx_expected_len) { rx_state 4; // 完整帧接收完成 } } break; } } }注意这里帧头加数据长度一共占了 3 个字节第 4 个字节开始才是命令字和数据区。收到rx_state 4后主循环或定时器中断里做校验和解析。校验时要用frame[3]开始的字节累加与最后一个字节比对。校验通过后根据frame[3]的命令字执行对应操作。7.3 速度指令解析与电机控制收到速度帧后STM32 把两个 int16 转换为 float 速度值然后用 PID 输出 PWM 占空比控制电机转动。void process_speed_frame(uint8_t *frame, uint8_t len) { uint8_t checksum 0; for (int i 3; i len - 1; i) { checksum frame[i]; } if (checksum ! frame[len - 1]) { return; // 校验失败丢弃 } int16_t left_raw (frame[4] 8) | frame[5]; int16_t right_raw (frame[6] 8) | frame[7]; float left_speed left_raw / 1000.0f; float right_speed right_raw / 1000.0f; // 更新目标速度 pid_left.target left_speed; pid_right.target right_speed; // 看门狗计数清零表示收到新指令 cmd_watchdog 0; }什么时候启动 PID 循环用 TIM 定时器中断固定每 10ms 或 20ms 执行一次 PID 计算和 PWM 更新。周期太短占用 CPU 高太长响应慢10ms 对小车主控是平衡点。7.4 看门狗保护这是整辆车安全性的关键。树莓派运行 ROS2 时有各种意外导航节点崩溃、Wi-Fi 断开、程序卡死、USB 线松动。如果 STM32 还在按照最后一条速度指令让电机继续转小车会失去控制。STM32 主循环里维护一个计数器void SysTick_Handler(void) { cmd_watchdog; if (cmd_watchdog 50) // 500ms 没收到指令就停车 { pid_left.target 0; pid_right.target 0; motor_stop(); } }这个机制要求树莓派端即使没有速度变化也至少要周期性发送心跳帧。实现方式可以是在串口桥接节点加一个定时器以 5Hz 到 10Hz 频率发送当前速度。如果树莓派进程死了串口没有新数据STM32 自动刹车小车不会飞出去。8. 里程计数据采集与话题发布里程计是 ROS2 导航系统中一个重量级话题。有了它机器人才能构建环境地图、进行定位和路径规划。8.1 STM32 端编码器数据采集使用带编码器的直流减速电机常见的是霍尔编码器或光电编码器。编码器输出 A、B 两相脉冲通过相位差可以判断电机旋转方向。STM32 的定时器编码器模式可以自动完成正交解码不需要 CPU 中断去数脉冲。需要配置的是编码器倍频。一般情况下选择 4 倍频模式这样分辨率是电机原始编码器线数的 4 倍。比如电机厂商标称 13 线编码器4 倍频后每转产生 52 个脉冲。每次定时器中断读取编码器计数并计算实际速度int32_t encoder_left_raw; int32_t encoder_right_raw; // 编码器当前读数减去上次读数得到这一周期内的脉冲增量 int32_t delta_left TIM4-CNT - last_left_cnt; last_left_cnt TIM4-CNT; // 脉冲增量乘以 每脉冲对应的距离得到这一周期内轮子走的路程 float distance_left delta_left * meters_per_pulse; float distance_right delta_right * meters_per_pulse; // 累加总路程 odom_x (distance_left distance_right) / 2.0f * cos(theta); odom_y (distance_left distance_right) / 2.0f * sin(theta); theta (distance_right - distance_left) / wheel_distance;关键难点是里程计累计误差。编码器测量值本身不完美轮胎打滑、地面不平会引入额外误差。树莓派端如果不做视觉或激光雷达修正小车在长时间运行后位置估计会漂移这是正常现象不是代码写错了。发布数据时如果担心误差累加可以先只测试短距离的运动比如让小车前进 1 米看看/odom位置是否接近 1 米误差有多大。8.2 树莓派端里程计发布节点完整代码量较大这里给出核心片段的伪代码def read_serial_loop(self): while rclpy.ok(): if self.ser.in_waiting 20: frame self.ser.read(20) # 解析帧提取左轮速度、右轮速度、位移增量、转角增量 left_speed ... right_speed ... delta_distance ... delta_theta ... odom_msg Odometry() odom_msg.header.stamp self.get_clock().now().to_msg() odom_msg.header.frame_id odom odom_msg.child_frame_id base_link # 根据运动学模型计算 x, y, theta self.x delta_distance * cos(self.theta) self.y delta_distance * sin(self.theta) self.theta delta_theta q quaternion_from_euler(0, 0, self.theta) odom_msg.pose.pose.position.x self.x odom_msg.pose.pose.position.y self.y odom_msg.pose.pose.orientation q odom_msg.twist.twist.linear.x (left_speed right_speed) / 2.0 odom_msg.twist.twist.angular.z (right_speed - left_speed) / self.wheel_distance self.odom_pub.publish(odom_msg)这里要用到欧拉角转四元数。直接引入tf_transformations库或transforms3d都行树莓派上安装sudo apt install -y ros-humble-tf-transformations注意/odom话题发布时必须带时间戳而且坐标系的命名要规范。frame_id设为odomchild_frame_id设为base_link后续 Nav2 导航栈才能正确处理 TF 关系如果配错了rviz2 里地图和机器人位置会乱转。9. 联调流程与效果验证硬件联调是整个项目里最容易让人放弃的阶段。串口数据看不见摸不着问题可能出现在树莓派端、STM32 端、串口硬件甚至是供电不足。按下面顺序逐步验证可以把问题范围逐步缩小。9.1 第一阶段串口数据回环测试树莓派和 STM32 不通过电平转换芯片直接互连时注意电平一致性。STM32F103 是 3.3V 逻辑电平树莓派 GPIO 也是 3.3V可以直接连接。但是如果不小心接到了树莓派 5V 引脚会烧坏 STM32。稳妥做法是用 USB-TTL 模块它内部有电平转换而且隔离了 GPIO 电压风险。搭一个最简单的回环测试把 USB-TTL 的 TX 和 RX 短接然后在树莓派上用命令行发送数据如果原样收到说明树莓派和 USB-TTL 模块本身工作正常。sudo apt install -y python3-serial python3 -c import serial ser serial.Serial(/dev/ttyUSB0, 115200, timeout1) ser.write(bhello loopback) print(ser.read(20)) 如果发出去又收回来说明树莓派到 USB-TTL 这段链路没问题。这时候再接上 STM32在 STM32 代码里写一个回环测试接收到什么就发回什么。树莓派端如果也能收到说明 STM32 串口初始化和中断接收正常。9.2 第二阶段单条速度指令验证把 STM32 小车用支架撑起来轮子悬空。树莓派端写一个测试脚本手动发送一条固定速度的串口帧ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist \ {linear: {x: 0.1}, angular: {z: 0.0}}观察现象STM32 串口日志里显示收到数据。左右轮以低速平稳转动方向一致。树莓派端serial_bridge日志显示左右轮速度大约在 100mm/s 附近。如果轮子不转用示波器或万用表检查 PWM 输出引脚是否有波形。如果轮子反转交换电机驱动板的 IN1、IN2 引脚或在代码里取反方向逻辑。9.3 第三阶段键盘控制真车让轮子落地启动键盘控制节点ros2 run teleop_twist_keyboard teleop_twist_keyboard先按很小的速度测试。例如按i前进时观察小车是否走直线。如果小车明显偏向一侧说明左右电机特性不一致或者两个轮子直径差异较大。此时可以微调左右轮 PID 参数或者在树莓派端做一个角速度补偿修正轻微的跑偏。9.4 第四阶段里程计话题验证在一个平整地面上做短距离直线测试ros2 topic echo /odom让小车从起点出发前进约 1 米对比/odom中pose.pose.position.x的读数。如果差距在 5% 以内说明编码器标定和运动学模型基本准确。如果误差很大先检查meters_per_pulse这个常量的计算有没有问题。9.5 第五阶段rviz2 可视化验证有桌面的电脑上安装 rviz2和树莓派组成 ROS2 多机通信sudo apt install -y ros-humble-rviz2在两台机器上配置相同的ROS_DOMAIN_ID比如都设为 42。在 PC 上打开 rviz2添加Odometry显示话题选择/odom。同时添加TF显示。如果连上了你会看到机器人坐标系在随着实际运动移动。这是整个调试链路的最终验证能跑到这一步说明树莓派和 STM32 的主干链路已经打通。10. 接口 API 说明与扩展方向这里单独说一下树莓派 ROS2 端对外可能涉及的“接口能力”因为很多人把树莓派当成机器人上的小服务器后续还要扩展摄像头、雷达、语音模块等。树莓派上的 ROS2 节点本身就是一种分布式接口。/cmd_vel是输入接口/odom是输出接口。你的 PC 上可以运行ros2 run teleop_twist_keyboard通过网络直接控制小车不需要在树莓派上插键盘这就是 ROS2 多机通信带来的最直接能力。扩展接口时的建议激光雷达或深度相机的驱动节点一般跑在树莓派上通过话题向导航栈提供/scan或/depth数据。IMU 如果接在 STM32 上需要通过串口把姿态数据传给树莓派定义数据协议时把 IMU 的加速度计、陀螺仪数据也封装进去IMU 数据频率高如果串口负载跟不上可以用单独的 SPI 或 I2C 接树莓派。摄像头可以做视觉识别例如 YOLOv5 目标检测检测到的障碍物位置转换到base_link坐标系后发布话题。这一步需要相机内参标定和 TF 变换。USB 摄像头比 CSI 摄像头方便但 CSI 摄像头延迟更低。自己训练 YOLO 模型时先在 PC 上完成训练再把模型转换到树莓派可运行的格式部署。需要注意模型大小和推理帧率之间的平衡树莓派端推理速度明显低于 PC运行时要考虑整体功耗和散热。如果打算做自动导航Cartographer 建图和 Nav2 导航是 ROS2 生态里最经典组合。Cartographer 主要用于生成二维栅格地图Nav2 负责在地图上做路径规划。底盘部分只需要保证/odom准确、/cmd_vel响应及时即可剩下算法都可以用现成的 ROS2 包。这里有个容易犯的认知错误加了serial_bridge和 STM32 闭环之后NV2 导航的延时来源就不只是算法本身还包括串口通信周期。STM32 的 PID 周期是 10ms 时速度响应时间大约在 50ms 以内对室内小车完全够用。如果你的系统延迟很大先检查树莓派 CPU 占用和串口阻塞而不是去调 Nav2 参数。11. 资源占用与性能观察树莓派上跑 ROS2 Humble 加串口桥接节点系统负载主要来自 DDS 通信、话题序列化和日志输出。资源占用因树莓派型号和所跑功能不同差异很大所以这里给的是观察方法不是固定数值。观察 CPU 和内存占用top -p $(pgrep -f serial_bridge)启动serial_bridge后按q退出键盘控制然后观察节点 CPU。正常情况下 Python 写的串口桥节点 CPU 占用应该在个位数百分比到十几百分比之间。如果 CPU 占用长期超过 50%说明节点设计有问题可能是串口读取用了忙等循环或者日志打印太频繁。查看 DDS 通信负载和话题频率ros2 topic hz /cmd_vel ros2 topic hz /odom键盘控制时/cmd_vel只在按键时才发布。导航模式下Nav2 的速度规划器会以一定频率持续发布cmd_vel。/odom发布频率取决于 STM32 的上报周期10Hz 到 50Hz 都有可能。发布频率越高树莓派 CPU 占用越高精度不一定同步提升。显存概念在纯 ROS2 小车上不适用因为不涉及深度学习推理。装了摄像头和 YOLO 目标检测时树莓派 CPU/GPU 占用会显著增加此时要考虑加装主动散热避免芯片降频导致节点崩溃或话题延迟。降低资源占用的建议日志输出用debug级别而不是info。高频串口日志打印会占用大量 CPU。减小不需要的话题回传频率。比如树莓派远程调试时不需要 50Hz 的/odom降为 10Hz 会提升整体稳定性。用--ros-args -r重映射话题名在不改代码的情况下把不必要的话题屏蔽。12. 常见问题与排查方法联调小车的过程几乎每个星期都会遇到新问题。下面整理高频问题按现象、原因、排查手段和解决方案列出。问题现象可能原因排查方式解决方案树莓派无法打开/dev/ttyUSB0用户不在 dialout 组权限不足ls -l /dev/ttyUSB0看权限sudo usermod -a -G dialout $USER后重新登录STM32 收到乱码数据波特率不一致确认树莓派和 STM32 串口初始化波特率统一波特率典型值 115200STM32 收不到任何数据USB-TTL 模块损坏或接线错误短接 TX、RX 测试回环更换模块核对 TX 接 RX、RX 接 TX、共地电机不转PWM 通道配置错误或电机驱动板未使能用万用表测 PWM 引脚和驱动板使能引脚检查 TIM 输出通道驱动板 EN 引脚接高电平小车前进时跑偏左右电机特性不一致或轮径不同用键盘控制让小车低速前进观察轨迹偏移方向调 PID 参数或加左右轮速度偏置STM32 收到指令但偶尔丢帧中断接收处理不及时或帧被拆包在 STM32 端加 FIFO 缓冲区主循环轮询处理改用环形缓冲区或 DMA 接收树莓派端serial_bridge启动报错串口被其他进程占用sudo lsof /dev/ttyUSB0查看占用进程sudo fuser -k /dev/ttyUSB0杀掉占用进程/odom话题没有数据STM32 上行帧格式不匹配在树莓派端打印十六进制原始串口数据用串口助手对比解析代码的帧字节序ROS2 节点在树莓派上启动很慢树莓派内存不足或 DDS 配置不当free -h看内存ros2 doctor诊断关闭桌面版多余进程或换 Server 版系统小车轮子悬空正常但落地速度异常电机负载导致编码器读数异常检查编码器供电电压和信号线编码器需要独立 3.3V 或 5V 供电避免与电机共用电源断电重连后串口设备名从ttyUSB0变成ttyUSB1USB 设备枚举顺序改变ls /dev/tty*查看当前设备名创建 udev 规则固定设备名或启动节点时通过参数指定端口Ubuntu 24.04 装 ROS2 时找不到软件源包Ubuntu 24.04 默认对应 Jazzy与 Humble 不匹配lsb_release -a查看系统版本统一使用 Ubuntu 22.04 配 Humble或换 Jazzy 后重新适配13. 常见硬件接线避坑指南树莓派和 STM32 联调时的硬件问题比代码问题更容易隐性掉坑。13.1 串口电平匹配STM32F103C8T6 的串口 TX、RX 是 3.3V 电平。树莓派 GPIO 的 UART 也是 3.3V两者直连可以工作。但很多初学者不了解这一点误把树莓派的 5V 引脚当作串口电源或逻辑电源接入就会烧坏 STM32。如果你对电平匹配没有把握或者没有独立的 USB-TTL 模块最安全的选择是买一个带隔离的 USB-TTL 转换模块模块的 USB 口接树莓派或 PCTTL 端接 STM32。模块和 STM32 之间注意共地。13.2 供电问题树莓派和 STM32 如果各用各的电源调试时可能因为地电位不一致导致通信异常。USB-TTL 模块通过 USB 口和树莓派共地同时它的 TTL 端和 STM32 共地。只要保证树莓派、USB-TTL、STM32 三者共地串口信号才稳定。小车动力电源不要直接给树莓派供电。电机启动瞬间电流很大会造成电压跌落树莓派因此重启是常见故障。建议动力电源通过 DC-DC 降压模块输出稳定的 5V 给树莓派电机驱动板单独从动力电源取电。13.3 STM32 复位与串口下载冲突STM32 的 BOOT0 引脚配置决定程序是正常启动还是进入串口下载模式。调试时如果 BOOT0 被拉高程序不会跑小车自然没反应。这也是“代码没问题但电机不转”的一个隐藏原因。14. 最佳实践与后续扩展建议快速跑通一套树莓派控制 STM32 小车的方案并不复杂但要稳定可靠地用于 ROS2 导航、SLAM 或竞赛还需要一些工程化实践。14.1 开发阶段的工程规范树莓派端写 ROS2 功能包时尽量用Docker固定 ROS2 开发环境。换一张 SD 卡或换一台树莓派时同一套 Docker 镜像可以秒级恢复环境不用重新 apt install ROS2。但 Docker 容器访问串口设备时启动命令要加上--device/dev/ttyUSB0参数。STM32 代码建议使用 Git 管理固件版本号写在代码头文件里。联调时如果改了协议先同时更新树莓派节点和 STM32 固件避免旧固件用老协议解析新数据出现难以排查的偶发故障。串口协议文档单独维护一个 Markdown 文件和代码放在同一仓库字段有变动就同步更新。14.2 排查问题时的系统方法论串口联调时不要凭感觉改代码。用一个固定步骤定位问题第一步树莓派端用串口调试工具直接向 STM32 发送固定字节确认物理链路通。第二步STM32 端在串口中断里把接收到的每个字节通过另一个串口发到 PC 串口助手确认数据是否正确接收。第三步树莓派端打印解析后的左右轮速度确认 ROS2 话题转换逻辑没问题。第四步STM32 端把 PID 的目标值和实际值通过 OLED 屏幕或串口日志输出确认闭环控制正常。每一步能独立验证通过后再组合到一起联调问题就能迅速定位。14.3 后续扩展方向当前架构跑通后扩展方向很多。如果做扫地机器人类应用加激光雷达跑 Cartographer 建图和 Nav2 导航。如果做视觉跟随加摄像头跑 YOLO 检测把目标检测结果发布成坐标话题再用 PID 控制小车跟随目标。如果做多机器人编队利用 ROS2 的多机通信机制给每台小车分配不同命名空间实现多车协同。如果要实现远程监控在树莓派上跑 rosbridge_server把 ROS2 话题桥接到 Web 端在浏览器里实时看摄像头画面和小车状态。关于 Nav2 导航需要强调一点即使底层链路没问题Nav2 对底盘里程计精度要求也很高。如果你的 STM32 编码器没有做速度闭环里程计误差会快速累积Cartographer 建图会出现重影。所以这部分不是可选的是把树莓派和 STM32 小车升级成完整自主移动机器人之前必须跨过的一道坎。14.4 安全操作清单做机器人实验代码之外的安全意识更重要第一次通电和测试时始终把小车架在支架上让轮子悬空。给小车安装机械限位或物理挡块防止意外冲出实验桌。电机驱动板要选带过热保护的型号长时间满负载测试时注意散热。使用锂电池时不要过放不要短路充电时尽量有人值守。15. 总结与下一步上手建议“树莓派 ROS2 控制 STM32 小车”这套方案本质上不是在教你怎么点一个灯或者跑一个现成 demo而是在打通一条从 ROS2 算法到真实电机驱动的完整链路。链路里的每个环节都有独立的知识体系串起来之后你就有了一台可以持续扩展的移动机器人基础平台。最值得尝试的现象是什么就是你在 PC 端按下键盘方向键真实小车立刻响应同时 rviz2 里的小车模型跟着前进和转弯。那一刻你会发现ROS2 话题、串口协议、STM32 中断、PID 控制这些知识点都被一条速度流串了起来。最先应该验证的功能是/cmd_vel到电机转动的最小闭环不涉及里程计、不涉及导航、不涉及摄像头。跑通这一步后面的 SLAM 和 Nav2 才有意义。最容易踩的坑是什么我再说一遍串口协议字段对不齐、树莓派串口权限没配置、共地没接好、STM32 看门狗没做。这四个问题在调试阶段会轮流出来折磨你。后续按顺序扩展建议是先加编码器和/odom里程计再用激光雷达建图最后把 Nav2 导航跑起来。每一步之间都有明确的验证标准不会陷入“哪一步都没跑通”的混乱状态。如果你手上正好有树莓派、STM32 和一块电机驱动板建议直接动手。这篇文章提到的代码片段和协议设计可以直接复制作为起点剩下的就是在你自己的小车上修参数、调方向、补功能。建议收藏备用。
返回列表