ARTICLE DETAIL

资讯详情

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

树莓派+STM32+ROS2轮式小车实战:架构与串口通信全解析

树莓派+STM32+ROS2轮式小车实战:架构与串口通信全解析 用树莓派跑 ROS2、用 STM32 做小车底层控制是典型的“上层大脑 下层关节”架构。这个方案最大的价值不是硬件本身多高级而是能同时练到 ROS2 节点编写、串口通信协议、嵌入式 PWM 控制和轮式机器人运动学属于从零到能遥控小车之间性价比非常高的一条路线。如果你正在纠结“树莓派上到底跑什么”“STM32 要不要直接接电机”“两边怎么通信”那我先把结论摆出来不要用树莓派 GPIO 直接驱动电机也不要在 STM32 里跑复杂 ROS 节点。正确的做法是树莓派负责指令和上层逻辑STM32 通过串口接收目标速度再控制电机驱动器。下面按实际落地顺序拆一遍。1. 先把小车架构拆清楚树莓派做脑袋STM32 做手脚很多新手第一次接触这个项目都会把注意力放在“树莓派能跑哪些 AI 模型”上结果做到后面才发现真正让车动起来的其实是 STM32 这边的电机控制和串口解析。1.1 为什么不要用树莓派直接控制电机树莓派带 Linux 系统跑的是 ROS2系统进程非常多。你写一个 Python 节点去翻转 GPIO理论上没有问题但实际会出现调度抖动。也就是说PWM 波形不是那么精确。电机控制需要的是稳定的频率和占空比一旦 Linux 系统忙于日志、网络、摄像头解码GPIO 输出就会出现抖动小车上最容易看到的现象就是轮子速度忽快忽慢。另外一个更实际的问题是安全。树莓派系统崩溃、进程卡住、远程终端断掉都可能导致 GPIO 电平一直保持在高位电机停不下来。STM32 作为独立 MCU可以单独跑一个超时保护逻辑比如 300ms 没收到新的速度指令就自动清零 PWM。这个保护在树莓派方案里实现起来很麻烦但在 STM32 端很容易做。所以架构上建议这样拆分STM32 负责电机 PWM、方向引脚、编码器读取、串口接收、超时停车。树莓派负责ROS2 节点、话题收发、cmd_vel 转换、以后接摄像头或导航算法。1.2 树莓派到底负责哪一层树莓派在 ROS2 系统里更像一个“计算和通信中心”。最开始的遥控阶段它只做一件事订阅/cmd_vel话题把 Twist 消息里的线速度和角速度转成左右轮目标速度然后通过串口发给 STM32。等你想继续往下做树莓派才开始处理视觉、地图、导航这类任务。比如装 OV5647 摄像头模块做目标检测、跑 YOLOv5或者接激光雷达跑 Nav2 建图导航这些任务需要的算力和功能包非常多不适合塞进 STM32 里。树莓派正好可以把这些相对高层的任务统一调度起来再把最终的速度指令发给 STM32。这里有个容易走偏的地方很多人拿到树莓派第一件事就是装桌面版系统、装 Rviz2、装一堆图形工具。如果你的目标是控制小车树莓派端不需要一开始就跑 Rviz2。先跑一个轻量 ROS2 节点把串口链路打通让轮子转起来比界面好看重要得多。1.3 这套结构适合谁这套“树莓派 ROS2 STM32”组合比较适合下面几类人已经有点 Linux 基础想认真接触机器人操作系统的人。想把单片机开发和 ROS2 开发串起来但不希望一开始就搞太复杂的异构计算平台的人。手里已经有一台树莓派 4B 或树莓派 5又不想浪费它的性能同时想保留 STM32 底层控制能力的人。如果你只有树莓派没有 STM32硬要做也不是不行但后面加里程计、闭环控制、导航的时候会明显感觉吃力。如果只有 STM32也可以做小车但你就接触不到 ROS2 的话题机制和上层算法。建议第一版先做最简单的两轮差速小车不要一上来就搞麦克纳姆轮、机械臂或者全向底盘。差速底盘的运动学公式简单调试容易等你把速度控制和通信都跑通了再扩展也来得及。2. 起步前先把环境问题处理干净这个项目的失败案例里很大比例不是代码逻辑问题而是环境没准备好。树莓派系统版本不对、ROS2 装错版本、STM32 的 IDE 配置不对、串口权限不对都会让进度卡住。2.1 树莓派系统与 ROS2 的常见组合ROS2 不是每个 Linux 版本都有对应发行包。常见组合中树莓派 4B 搭配 Ubuntu Server 22.04 再装 ROS2 Humble 是比较稳的路线。Humble 是一个长期支持版本网上教程多问题也容易搜到。树莓派 5 也可以跑 Ubuntu 22.04 的 server 镜像但要注意树莓派 5 的硬件比较新部分镜像版本可能适配不完整。如果买的是树莓派 5装系统之前先确认你下载的镜像是否明确支持不要拿树莓派 4B 的老镜像硬刷。树莓派 3B 也不是不能跑但内存较小跑 ROS2 基础节点还行再叠加视觉、导航就会出现资源紧张。如果只是想尽快跑通串口和电机我建议先别装完整桌面版系统。安装 Ubuntu Server再手动安装 ROS2 的 ros-base 包就能满足 90% 的初期测试需求。只有在后续需要在本机跑 Rviz2 的时候才考虑装 desktop 相关包。需要特别提醒装 ROS2 之前先把系统源换掉。热词里一直有人搜“树莓派5 清华源 ubuntu22.04”说明这一步确实是卡点。换源的操作不复杂但很容易因为系统版本不同而改错。sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak # 然后根据你的 Ubuntu 版本编辑源文件 # Ubuntu 22.04 的新镜像可能使用 /etc/apt/sources.list.d/ubuntu.sources sudo apt update sudo apt upgrade这里的关键是要先备份。另外不要从网上直接复制一段和你系统版本不匹配的源内容否则更新时会出现一堆 404。换完源之后再执行 ROS2 官方源的添加步骤操作顺序不要颠倒。2.2 ROS2 基础依赖与常用工具装完 ROS2 后建议顺手装这几个东西python3-serial或pyserial树莓派串口通信要用。colconROS2 工作空间编译工具。teleop-twist-keyboard用键盘发速度指令后续测试非常方便。如果你用 Python 写 ROS2 节点还需要确认 Python 环境和rclpy能正常导入。很多情况下节点报找不到模块并不是依赖没装而是当前终端没有 source ROS2 的环境变量。source /opt/ros/humble/setup.bash这个命令每个新终端都要执行。如果不想每次都敲可以写进~/.bashrc但要注意如果你同时装了多个 ROS2 版本写进去之后可能会冲突。2.3 STM32 开发环境怎么选STM32 端的主流开发方式有两种标准库和 HAL 库。对这个项目我更推荐 HAL 库加 STM32CubeMX 生成初始化代码。原因很简单CubeMX 可以帮你配置时钟树、串口、定时器 PWM 和引脚复用大大减少看 datasheet 的时间。开发板不必追求高端。最常见的是 STM32F103C8T6 这种小蓝板价格低资料多。唯一要留意的是引脚和定时器数量。做两轮差速小车你需要至少两路 PWM分别控制左右电机。至少一路 UART和树莓派通信。如果以后要加编码器测速还需要额外的定时器通道。所以选型时不要只看芯片便宜要看你选的板子有没有引出足够多的定时器通道。IDE 可以用 STM32CubeIDE也可以用 Keil。Linux 用户直接用 STM32CubeIDE 更省事Windows 用户用 Keil 也能做。VSCode 加 CMake 的方案也可以但对新手来说配置成本偏高建议先用图形化 IDE 把代码跑通。2.4 硬件接线的几个基础原则树莓派和 STM32 通信最常见的是通过 USB 转 TTL 模块连接。接线遵循交叉连接树莓派 TX 接 STM32 RX树莓派 RX 接 STM32 TX然后 GND 必须接在一起。很多人漏接 GND导致串口乱码或者完全没数据。树莓派的物理串口可能是/dev/ttyAMA0或/dev/ttyS0也可能被系统控制台占用。为了避免这些麻烦我建议第一版直接用 USB 转 TTL 模块插在树莓派 USB 口上这样设备名一般是/dev/ttyUSB0比折腾物理串口简单很多。STM32 与电机驱动之间要注意主控电源和电机电源分开。电机启动瞬间电流很大如果主控从电机电源取电很容易造成电压跌落导致 STM32 复位。常见接法是电池给电机驱动供电ST-Link 或独立 5V 给 STM32 供电两边只共 GND不共用电源输出。3. 通信协议是所有问题的高发区树莓派和 STM32 中间这条链路代码量不大却能决定整个项目能不能稳定跑起来。通信协议如果只写“发一串十六进制过去”后面会遇到各种黏包、错位、速度不准的问题。3.1 为什么第一版首选串口 UART树莓派和 STM32 之间可以选 UART、CAN、SPI、USB 甚至网络。但对这种两轮差速小车UART 是最合适的硬件简单几乎每个 STM32 都有多个串口。树莓派端用 USB 转 TTL 模块就能接入。115200 波特率下传输几十字节的控制帧完全够用。调试方便可以用串口助手直接看数据。CAN 和 USB 的实时性、可靠性更好但要么需要额外收发器要么上位机协议复杂。SPI 不适合距离稍长的机箱内走线而且树莓派引脚和 STM32 引脚之间的电平匹配也要小心。对学习项目先把 UART 跑稳以后要升级再换 CAN 也不迟。3.2 一个能落地的帧格式通信之前必须定义好帧格式。建议用固定帧长这样 STM32 解析最简单。这里我给一个很常见的约定字段长度含义帧头2 字节0xAA 0x55用于同步指令1 字节0x01 表示左右轮速度控制左轮速度2 字节int16单位可以约定为 mm/s右轮速度2 字节int16单位可以约定为 mm/s校验1 字节对前面指令和速度字段做累加和整体帧长固定为 8 字节AA 55 01 左轮速度 2 字节 右轮速度 2 字节 校验 1 字节。为什么用固定帧长因为 STM32 在串口中断里不需要判断“长度字段在哪个位置”只要找到帧头后面 6 个字节就能一次性收完然后做校验。字节序建议统一为小端也就是 int16 的低字节在前。如果不好理解那就在树莓派端用 Python 的to_bytes(2, little, signedTrue)编码在 STM32 端按低字节拼接两边对应起来就可以。这里有一个容易忽视的问题速度单位。ROS2 里的/cmd_vel用的是 m/s但如果直接把这个小数转成字节浮点转字节很麻烦。更好做法是树莓派端把 m/s 换算成 mm/s再转 int16 发送。比如 0.2 m/s 转成 200然后按 200 发送。STM32 端收到 200直接理解为 200mm/s处理起来方便很多。3.3 STM32 端接收解析要分成两层STM32 的串口接收代码核心思路是“中断收字节主循环处理数据”。不要在串口中断里做复杂逻辑否则一个高优先级的串口中断会拖垮整个主循环。接收时最基础的做法是状态机switch (receive_state) { case STATE_WAIT_HEAD1: if (c 0xAA) receive_state STATE_WAIT_HEAD2; break; case STATE_WAIT_HEAD2: if (c 0x55) receive_state STATE_RECEIVE_CMD; else receive_state STATE_WAIT_HEAD1; break; // 后续字节按顺序存入缓冲区收满后做累加和校验 }状态机的优势是能自动跳过乱码。就算上电瞬间有噪声只要帧头不对就会被过滤掉。收完一帧后需要在主循环里判断校验是否通过。如果通过就把左右轮速度更新到全局变量里。如果校验失败直接丢弃不要写成一句“复位重来就算成功”的逻辑因为连续错误帧可能来自波特率不准或电源干扰。我这里还要补一个 STM32 端的坑如果发现 HAL_Delay 卡死先检查 SysTick 中断有没有被禁用或者中断优先级分组有没有被改掉。很多教程里的延时函数问题都出在中断优先级配置上和延时函数本身没有关系。3.4 一定要加超时停车STM32 收到速度指令后不能傻傻地一直执行最后一条。树莓派端节点崩溃、串口线松动、ROS2 话题停止发布都会导致没有新指令过来。如果不加保护小车可能会一直往前冲。在 STM32 主循环里记录一个时间戳每次成功收到有效速度帧就更新时间戳。每次更新 PWM 前检查当前时间和时间戳的差if (HAL_GetTick() - last_cmd_tick 300) { // 将 PWM 占空比清零 }这个 300ms 不是固定参数可以根据实际调试调整。原则是比树莓派发送周期的 3 到 5 倍大防止单个丢包就停车但也不能太大否则真出问题时反应太慢。4. ROS2 侧的节点编写思路ROS2 侧最核心的工作是写一个 bridge 节点它一边订阅/cmd_vel一边往串口写协议帧。任务不复杂但很容易写着写着把字符串拼接、话题名、坐标方向搞混。4.1 创建功能包在树莓派上创建一个 Python 功能包mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src ros2 pkg create stm32_bridge --build-type ament_python --dependencies rclpy geometry_msgs创建完功能包后先colcon build编译一次再 source 一下 local setupcd ~/ros2_ws colcon build source install/setup.bash4.2 订阅 cmd_vel 并转换成左右轮速度差速小车最常用的转换公式是v msg.linear.x w msg.angular.z wheel_base 0.2 # 实测两个轮子中心之间的距离 left_speed_mm (v - w * wheel_base / 2.0) * 1000 right_speed_mm (v w * wheel_base / 2.0) * 1000这里的wheel_base千万不要拍脑袋填一个值。拿尺子量一下两个驱动轮中点之间的实际距离单位是米。填错之后最典型的现象是直线还能走原地旋转时的半径完全不对。需要注意这个公式默认的是差速底盘常用的坐标方向。也就是说正面朝前线速度向前为正角速度绕 Z 轴逆时针为正。如果你的遥控器往左拨小车往右转先检查电机方向或者左右轮定义不要急着改公式里的正负号。4.3 串口封装和协议编码在 bridge 节点里我建议把串口逻辑单独拆成一个函数或类不要全堆在订阅回调里。订阅回调主要做两件事把 Twist 转成左右轮 mm/s然后编码成协议帧并写入串口。编码部分可以参考这样的思路import serial def cmd_to_bytes(left_speed_mm, right_speed_mm): data bytearray() data b\xAA\x55 data b\x01 data left_speed_mm.to_bytes(2, little, signedTrue) data right_speed_mm.to_bytes(2, little, signedTrue) checksum sum(data[2:]) 0xFF data.append(checksum) return bytes(data)这里把这个函数的返回值直接serial_port.write(...)就可以。查看 STM32 端是否能解析可以先把这个函数单独跑一次用串口助手看输出确认帧头、速度和校验都符合约定再接入 ROS2。串口读取端同样重要。STM32 如果每 100ms 回传一次状态ROS2 节点不能只是“读到就打印”建议先打印十六进制确认数据能回来再写解析逻辑。4.4 从话题发布到键盘遥控节点写好后先手动发布一个低速测试话题ros2 topic pub --rate 10 /cmd_vel geometry_msgs/msg/Twist {linear: {x: 0.1, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}这时小车应该缓慢前进。测试之前最好把小车架起来让轮子悬空避免速度设置得太大冲出桌面。话题测试通过后再装键盘遥控sudo apt install ros-humble-teleop-twist-keyboard ros2 run teleop_twist_keyboard teleop_twist_keyboard键盘遥控效率高但它的发布频率和话题类型不一定稳定如果发现小车反应不灵敏先检查 topic 发布频率再检查是否有多个节点同时发/cmd_vel。两个发布者同时往一个话题发数据会让小车收到混乱的指令。5. 从最小样例到完整遥控的验证步骤这个项目最容易出的问题不是某个环节不会做而是跳级。树莓派系统还没弄好就直接跑 Nav2结果不知道是地图问题还是底盘问题。正确方式是分级验证每级通过之后再进下一级。5.1 四级验证清单阶段怎么做成功标准失败优先排查第一级串口助手直接给 STM32 发速度帧轮子按预期转动检查接线、波特率、协议帧格式第二级用树莓派 Python 脚本发同一帧轮子转动检查串口权限、设备名第三级用 ROS2 的ros2 topic pub /cmd_vel控制轮子转动检查节点话题名、环境变量第四级键盘遥控能前进后退左右转检查 wheel_base 和电机方向这四级不要混着来。如果第一级就不通不要打开 Rviz2问题大概率在硬件接线或协议格式。5.2 反馈数据怎么验证如果你的 STM32 会回传当前状态比如当前速度或者心跳树莓派端可以用一个简单节点订阅并打印ros2 topic echo /stm32_status先不要设计复杂的 JSON 解析。最开始时STM32 端可以直接回传原始十六进制树莓派端打印出来人眼判断数据是否正常。等通信链路稳定了再在 STM32 端把编码器转速算好树莓派端再转成带语义的状态。很多教程会直接教你写 Odometry 和 TF但对于第一版来说这些会让人分心。先把“发指令、收到反馈、反馈能对上”这个闭环跑通再谈里程计。5.3 后续接入 Rviz2 和 Nav2 的路径速度控制跑通后常见的扩展方向是STM32 读取编码器并回传左右轮累计里程树莓派端算出 /odom 话题再配合激光雷达建立地图最后用 Nav2 导航。热词里很多人搜“rviz2安装使用”“ros2八叉树地图导航”说明大家对可视化导航很感兴趣。但从我的经验看导航真正卡住你的通常不是 Rviz2 不会装而是 odom 频率太低、TF 树不完整、底盘底盘回传的里程计有累计误差。如果你确实想在树莓派上跑 Rviz2要注意树莓派 4B 的 GPU 能力和内存都不算强。Rviz2 打开后可能比较卡尤其地图点云一多就更明显。折中做法是把树莓派当成“采集和通信节点”Rviz2 跑在台式机上两台机器通过局域网通信。5.4 如果想加摄像头和 YOLO热词里有“树莓派5上部署自己训练的yolov5模型”这也是一条常见路线。但要有个边界认知摄像头推理、目标检测、ros2 话题发布、底盘速度控制这些任务叠加在树莓派上CPU 和内存占用会明显上升。我更建议把摄像头视觉任务和底盘控制分成独立节点视觉节点发布检测结果底盘控制节点只负责速度转换。这样即使视觉节点卡住小车也不会立刻失去保护。树莓派的 CSI 摄像头模块比如 OV5647接线方式和分辨率都需要按对应镜像配置。如果板子版本不同先确认摄像头能不能在系统里正常出图再接入 ROS2不要上来就做联合推理。6. 最容易让人怀疑人生的坑与排查顺序到这里基础链路已经完整了。最后整理几个实际调试时很容易踩的坑按出现频率排序。6.1 树莓派串口端第一类问题是找不到串口设备。ls -l /dev/ttyAMA0 /dev/ttyUSB0如果只有/dev/ttyAMA0没有/dev/ttyUSB0说明系统可能没识别 USB 转 TTL 模块或者驱动没加载。如果是物理串口要确认板载串口有没有被蓝牙或系统 console 占用。第二类问题是权限不够。打开串口时报 Permission denied一般是因为当前用户不在 dialout 组sudo usermod -aG dialout $USER执行完需要重新登录。不要想着用sudo python3强行跑因为后面接 ROS2 时环境变量和权限管理会更乱。第三类问题是串口被占用。如果程序非正常退出串口可能没有被释放这时再启动节点会报 device busy。先看进程sudo lsof /dev/ttyUSB0找到占用进程后 kill 掉或者在代码里按 CtrlC 时调用串口关闭方法。6.2 STM32 端STM32 端最容易遇到的现象是“串口助手能看到树莓派发来的数据但电机不动”。先检查这几项电机驱动板的供电是否正常特别是独立供电没共 GND。PWM 使能函数有没有调用对应的定时器和通道是否正确。方向引脚和 PWM 引脚有没有接反。占空比初始值是不是 0有没有把__HAL_TIM_SET_COMPARE的通道写错。如果电机动作但声音很尖可能是 PWM 频率设置偏高或偏低。先从常用区间开始试比如 10kHz 到 20kHz听声音判断是否正常。电机啸叫不是模型问题往往是频率落到了人耳敏感区。如果出现 HAL_Delay 卡死先确认是不是在中断服务函数里调用延时。高位中断里出现阻塞延时很容易把整个系统卡住。你可以在主循环里用状态标志替代延时不要在中断里做“等待事件结束”的操作。6.3 ROS2 端最常见的 ROS2 问题是找不到命令。新打开的终端如果没 source任何ros2命令都会提示 command not found。解决方法是手动执行source /opt/ros/humble/setup.bash另一个问题是colcon build之后依然找不到功能包。这时要 source 当前工作空间的 install 路径source ~/ros2_ws/install/setup.bash如果两个 ROS2 设备要通信检查是否处于同一局域网并且ROS_DOMAIN_ID是否一致。Domain ID 不同话题永远互不可见。如果你发布/cmd_vel没反应优先用ros2 topic list ros2 topic info /cmd_vel ros2 topic echo /cmd_vel先确认话题存在再确认有没有多个发布者。话题名是全局的还是相对的在节点代码里也要统一。有时你发布cmd_vel节点订阅的是/cmd_vel表面上看差不多实际是不同话题。6.4 一个更稳妥的调参顺序如果你已经能遥控小车但速度控制不稳不要先怀疑 PID 或编码器。先检查左右轮在同一个速度指令下是否转得一样快。很多小车的电机参数不一致出厂差异会导致低速直线跑偏。先用低占空比或低速度测试确认左右轮基本一致后再加编码器闭环。如果要开闭环建议先让 STM32 端通过编码器做底层速度闭环不要老想着在树莓派端做高频控制。STM32 控制周期可以做到比较稳定树莓派只负责下发目标速度这样职责更清晰出现问题也更好排查。6.5 给新手的最终落地建议我的建议很简单先别管导航和视觉先让小车收到一条明确的速度指令后能按预期动起来再停下来。这条链路打通了后面加摄像头、加 Nav2、加地图都是在这条链路上叠新的数据源。如果一开始就把 ROS2、Rviz2、STM32 编码器、PID、YOLO 全部塞进一个项目出问题时你会分不清是哪个环节的问题。先固定一个可控的最小闭环然后逐步增加变量。踩过几次之后你会发现很多问题不是树莓派不行也不是 STM32 不行而是通信协议、单位换算、供电和权限这些基础工作没有处理干净。把这些基础打牢这套架构能支持你走的比想象中远得多。
返回列表