拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

开源扫地机器人:从零搭建ROS2与STM32移动机器人全栈

开源扫地机器人:从零搭建ROS2与STM32移动机器人全栈

1. 从一台扫地机器人说起:为什么它是最好的机器人工程教材

扫地机器人这个品类,很多人家里都有,但大部分人只把它当成一个会自己跑的家电。直到你把它翻过来,拆掉底壳,看到里面那一堆传感器、电机驱动板、主控板和电池管理模块,才会意识到这东西本质上就是一台完整的移动机器人。它同时包含了感知、决策、控制、执行四个机器人核心环节,而且每一个环节都被压缩到了一个你能拿在手里的体积里。

我接触过不少做机器人开发的朋友,大家有个共识:学机器人最痛苦的不是某个单点技术,而是不知道怎么把一堆零散的知识串成一个能跑起来的系统。你在课上学了PID,学了卡尔曼滤波,学了路径规划,但这些东西怎么在一个真实产品里协同工作,课本不会告诉你。开源扫地机器人恰好补上了这个缺口——它把一整套机器人工程课程塞进了一台会扫地的机器里。

这篇文章面向的是想从零搭建一台完整移动机器人的开发者,不管你是电子专业的学生、嵌入式工程师转行做机器人,还是ROS2初学者想找一个能落地的项目,这台开源扫地机器人都是一个极佳的载体。我会从整体架构讲到每个模块的实现细节,包括ROS2和STM32的分工、传感器选型和数据处理、电机控制与里程计标定、导航栈配置,以及我在实际调试中踩过的坑和总结出来的排查方法。

2. 整体架构设计:ROS2与STM32怎么分工

2.1 为什么不是一颗芯片全包

初学者最容易犯的错误是想用一颗STM32把所有事情都干了——跑SLAM、做路径规划、控制电机、读传感器。理论上不是不行,但实际做下来你会发现两个问题:一是算力不够,STM32跑个简单的滤波还行,跑SLAM或者代价地图基本没戏;二是实时性冲突,导航算法需要大量浮点运算,会挤占电机控制的CPU时间,导致控制周期抖动,机器人走起来一顿一顿的。

所以开源扫地机器人的标准做法是双核架构:一颗STM32做底层实时控制,一台跑Linux的开发板(树莓派、Jetson Nano或者RK3588之类)跑ROS2做上层决策。两者之间通过串口或者USB转串口通信,跑一个自定义的通信协议。

这个分工的逻辑很清晰:STM32负责“反射弧”——读编码器、读IMU、读碰撞传感器、输出PWM控制电机、做PID闭环,这些任务对实时性要求高但计算量小;ROS2这边负责“大脑”——建图、定位、路径规划、任务调度,这些任务计算量大但对实时性要求相对宽松。

2.2 通信协议的设计要点

STM32和ROS2之间的通信协议是整个系统的血管。我见过很多人随便定义一个简单的帧格式就开始用,结果调试的时候数据对不上、丢包、粘包,排查起来非常痛苦。这里说几个关键设计点。

帧头帧尾必须有,而且要用不容易在数据区出现的字节组合。常见做法是帧头用0xAA 0x55两个字节,帧尾用校验和加0x0D 0x0A。数据区采用小端序还是大端序要统一,我建议统一用大端序,因为网络字节序就是大端,ROS2这边处理起来更自然。

校验方式推荐CRC16而不是简单的累加和。累加和对于字节顺序不敏感,两个字节交换位置校验值不变,这在调试时会造成误判。CRC16虽然计算量稍大,但STM32跑起来毫无压力。

通信频率方面,底层上报里程计和IMU数据的频率建议在50Hz左右,太高了串口带宽吃紧,太低了ROS2那边的odom和tf会断断续续。控制指令下发的频率可以低一些,20Hz就够用了,因为路径规划的输出本身就不会变化那么快。

注意:串口通信一定要加超时机制。STM32超过500ms没收到上位机的控制指令,必须自动停车。这个安全机制在调试时可能觉得碍事,但真跑起来的时候能救你的机器人好几次。

2.3 供电与电源管理

扫地机器人的供电系统比大多数人想象的复杂。它需要同时给电机(12V或24V)、开发板(5V)、传感器(3.3V或5V)供电,而且电池是充放电频繁的锂电池。我的建议是分三路做电源:电机驱动一路直接从电池取电,经过大电容缓冲;开发板和传感器一路经过DC-DC降压,做好滤波;STM32和它的外设单独用一颗LDO供电,避免电机启停时电压波动导致MCU复位。

电池管理这块,至少要有过放保护和电量检测。电量检测用简单的分压电阻加ADC就够了,但要注意分压电阻的阻值不能太小,否则会持续耗电。我一般用100k和10k分压,配合0.1uF的滤波电容,读数很稳定。

3. 底层硬件与STM32固件开发

3.1 主控选型与开发环境搭建

STM32的型号选择上,F103系列是最经典的入门选择,资源够用、资料多、价格便宜。但如果你的机器人要跑FreeRTOS做多任务调度,建议上F4系列,浮点运算能力和RAM都更充裕。我目前用的是STM32F405,跑FreeRTOS加三路PID闭环,CPU占用率不到40%。

开发环境我强烈推荐用VSCode加PlatformIO或者STM32CubeMX加Makefile的组合。Keil虽然经典,但代码补全和版本管理体验太差。用VSCode配置STM32开发环境的核心步骤是:安装Cortex-Debug插件,配置J-Link或ST-Link的调试参数,在c_cpp_properties.json里指定芯片包的头文件路径。这套环境搭好之后,开发效率比Keil高一个档次。

链接文件(.ld文件)这块很多人忽略,但它决定了你的内存布局。STM32F405的RAM是192KB,分成SRAM1(112KB)、SRAM2(16KB)和CCM(64KB)。CCM只能被CPU访问,DMA访问不了,所以DMA缓冲区必须放在SRAM1或SRAM2里。这个细节在写电机控制和串口DMA时非常关键,放错了地方DMA直接不工作。

3.2 电机驱动与编码器读取

扫地机器人一般用两个带编码器的直流减速电机做差速驱动。电机驱动芯片的选择上,DRV8323是集成度很高的方案,内置了电流采样和故障保护,但价格偏高。预算有限的话可以用TB6612或者L298N,但L298N的压降大、发热严重,不推荐。

编码器读取用STM32的定时器编码器模式最省事。把编码器的A相和B相接到定时器的CH1和CH2,配置成编码器模式,硬件自动计数,你只需要定期读CNT寄存器的值然后清零。这里有个坑:编码器模式下的CNT是16位的,正转到65535会溢出到0,反转从0会下溢到65535。处理方法是读到一个int16_t类型的变量里,让编译器自动处理符号。

int16_t encoder_left = (int16_t)TIM3->CNT; TIM3->CNT = 0;

这行代码看起来简单,但如果你用uint16_t去接,正转反转都会变成正数,里程计直接废掉。我当初在这个地方卡了半天,一直以为是编码器接线问题。

3.3 IMU数据读取与姿态解算

IMU是扫地机器人定位的核心传感器之一。常用的MPU6050或者ICM20602通过I2C或SPI接口和STM32通信。原始数据出来之后需要做姿态解算,得到机器人的偏航角(yaw),这个角度会和编码器的里程计融合,用于ROS2那边的定位。

姿态解算算法我推荐用Mahony或者Madgwick,计算量小,在STM32上跑绰绰有余。互补滤波也行,但参数调起来比较麻烦。解算出来的yaw角会随时间漂移,这是MEMS陀螺仪的通病,所以必须和编码器里程计做融合。融合的策略后面在ROS2那边讲。

实操心得:MPU6050的I2C地址是0x68,但如果你买到的模块上AD0引脚被拉高了,地址就变成0x69。读不到数据的时候先确认地址,别急着怀疑代码。

3.4 碰撞与悬崖传感器

扫地机器人的碰撞检测一般用微动开关或者红外接近传感器,悬崖检测用红外测距传感器朝下安装。这些传感器的信号处理很简单,但安装位置有讲究。碰撞开关要装在碰撞板的内侧,保证碰撞板被轻微挤压时就能触发;悬崖传感器要装在底盘边缘,离地高度控制在1到2厘米,太高了检测不到地面,太低了容易误触发。

STM32这边用外部中断或者定时轮询都可以。我建议用定时轮询,10ms一次,配合软件消抖。外部中断虽然响应快,但机械开关的抖动会导致中断频繁触发,反而增加CPU负担。

4. ROS2上层开发与导航实现

4.1 ROS2环境搭建与版本选择

ROS2的版本选择上,Humble是目前最稳定的LTS版本,支持到2027年。如果你用的是Ubuntu 22.04,直接装Humble就行。安装方式推荐用apt,比源码编译省心太多。安装完之后记得source一下setup.bash,或者直接写进.bashrc里。

sudo apt install ros-humble-desktop echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

装完之后用ros2 run demo_nodes_cpp talker和ros2 run demo_nodes_py listener测试一下,能通就说明环境没问题。如果遇到packages.ros.org的InRelease报错,大概率是网络问题或者源配置不对,检查一下sources.list.d里的ros2.list文件。

4.2 串口通信节点与协议解析

ROS2这边需要一个节点专门负责和STM32通信。这个节点要做的事情是:打开串口、按照协议解析STM32上报的数据、发布odom和imu话题、订阅cmd_vel话题并下发控制指令。

串口配置上,波特率建议用115200或者更高。如果用USB转串口,注意芯片型号,CH340在Linux下偶尔会有驱动问题,CP2102和FT232更稳定。串口权限问题用sudo usermod -aG dialout $USER解决,加完组要重新登录才生效。

协议解析的核心是状态机。收到0xAA进入帧头状态,收到0x55确认帧头,然后根据数据长度字段读取数据区,最后校验CRC。校验通过就发布数据,不通过就丢弃并重新寻找帧头。这个状态机的实现要健壮,不能因为一帧数据错误就卡死。

// 简化的状态机伪代码 switch(state) { case WAIT_HEADER1: if(byte == 0xAA) state = WAIT_HEADER2; break; case WAIT_HEADER2: if(byte == 0x55) state = READ_LEN; else state = WAIT_HEADER1; break; // ... 后续状态 }

4.3 里程计计算与tf变换发布

里程计的计算是ROS2这边最核心的工作之一。STM32上报的是左右轮的编码器计数,ROS2这边需要把它转换成机器人的位姿变化。

计算过程是这样的:先算左右轮的行驶距离,d_left = ticks_left * wheel_circumference / ticks_per_rev,d_right同理。然后计算机器人中心前进的距离d = (d_left + d_right) / 2,以及角度变化d_theta = (d_right - d_left) / wheel_base。最后更新位姿:x += d * cos(theta + d_theta/2),y += d * sin(theta + d_theta/2),theta += d_theta。

这个计算看起来简单,但有几个细节要注意。轮距(wheel_base)的测量要尽量准确,差个几毫米在长时间运行后会导致明显的角度累积误差。编码器每转的计数(ticks_per_rev)要算上减速比和四倍频,比如编码器线数11、减速比30、四倍频,那ticks_per_rev就是11×30×4=1320。

tf变换的发布用tf2_ros的TransformBroadcaster,发布odom到base_link的变换。注意时间戳要用ROS2的时钟,不要用系统时间,否则和传感器数据对不上。

4.4 传感器融合与定位

单纯的轮式里程计在扫地机器人上是不够用的,因为轮子打滑、地面不平都会导致累积误差。所以需要把IMU的yaw角和里程计融合。最简单的融合方式是互补滤波:theta_fused = alpha * (theta_prev + gyro_z * dt) + (1 - alpha) * theta_odom。alpha取0.98左右,陀螺仪的短期精度高,里程计的长期稳定性好,互补一下效果不错。

如果要更精确的定位,可以上EKF(扩展卡尔曼滤波)。ROS2里有robot_localization这个包,配置好参数就能用。但EKF的参数调起来比较费时间,建议先用互补滤波跑通,再考虑升级。

4.5 建图与导航栈配置

建图用slam_toolbox,这是ROS2里最成熟的2D SLAM方案。配置上主要调几个参数:max_laser_range设成雷达的实际量程,resolution设成0.05米,minimum_travel_distance设成0.2米左右。建图的时候让机器人慢慢走一圈,速度控制在0.2m/s以内,建出来的地图质量最好。

导航用Nav2,配置比ROS1的move_base复杂一些,但功能更强。关键配置文件是nav2_params.yaml,里面要配好代价地图、规划器、控制器、行为树。初学者最容易搞混的是global_costmap和local_costmap的坐标系,前者用map,后者用odom,搞反了导航直接不工作。

路径规划算法上,全局规划用NavFn或者Smac,局部规划用DWB或者TEB。DWB参数少、调起来快,适合入门;TEB路径更平滑,但参数多、容易调崩。我建议先用DWB跑通,再根据需求换TEB。

注意:Nav2的behavior tree配置文件很容易被忽略,但它是整个导航流程的骨架。如果机器人规划出路径但不走,或者走到一半停住,先检查behavior tree的配置。

5. 常见问题与排查技巧实录

5.1 通信类问题

串口通信是出问题最多的地方。常见症状和排查方法我整理了一个表:

症状可能原因排查方法
完全收不到数据串口设备名不对ls /dev/tty*确认设备名
数据乱码波特率不匹配两端统一波特率
偶尔丢帧串口缓冲区溢出降低发送频率或增大缓冲区
数据错位帧头识别错误检查状态机逻辑
权限拒绝用户不在dialout组sudo usermod -aG dialout $USER

我遇到过一次很诡异的问题:STM32发出来的数据在示波器上看完全正确,但ROS2这边收到的就是乱的。查了半天发现是USB转串口线的质量问题,换了一根带屏蔽的线就好了。所以调试通信问题的时候,硬件本身也要怀疑。

5.2 电机控制类问题

电机不转或者转动异常,排查顺序是这样的:先确认电源电压是否正常,再确认PWM信号是否输出,然后确认驱动芯片的使能引脚是否拉高,最后确认电机线是否接对。我见过有人把电机的两根线接反了,结果机器人一直原地转圈,查了半天代码。

PID参数整定是另一个大坑。速度环的PID建议先用纯P控制,Kp从0.5开始慢慢加,加到电机能快速响应但不振荡为止。然后再加一点点I消除稳态误差,D一般不用加,因为编码器噪声会被D放大。

5.3 导航类问题

导航中最常见的问题是机器人原地打转或者撞墙。原地打转一般是角度控制的问题,检查一下cmd_vel的angular.z方向对不对,以及odom的yaw角是否和实际方向一致。撞墙一般是代价地图的问题,检查雷达数据是否正常发布,以及膨胀半径是否设置合理。

还有一种情况是机器人规划出了路径但不动。这时候先看cmd_vel话题有没有数据,有数据说明导航栈在工作,问题在底层;没数据说明导航栈卡住了,检查behavior tree和costmap的状态。

5.4 建图类问题

建图时地图重影或者漂移,核心原因是里程计精度不够。先检查编码器计数是否准确,用手推着机器人走一米,看odom显示的距离是不是一米。如果差得多,检查ticks_per_rev和轮径的参数。如果里程计没问题但地图还是漂,那就是雷达的安装位置和tf配置对不上,检查base_link到laser的tf变换。

6. 从能跑到好用:几个提升体验的细节

6.1 速度平滑与加减速控制

直接给电机发阶跃的速度指令,机器人会猛地一顿,体验很差。在STM32那边加一个速度斜坡函数,让目标速度逐渐变化,机器人走起来就顺滑多了。实现很简单,每个控制周期让当前速度向目标速度靠近一个固定步长,步长根据加速度需求来定。

6.2 低电量自动回充

这个功能涉及红外或者超声波引导,实现起来比较复杂,但思路可以分享一下。机器人在低电量时先导航到充电座附近,然后切换到红外引导模式,靠充电座发出的红外信号对准。红外接收用三个传感器,分别朝左前、正前、右前,根据哪个收到信号来调整方向。

6.3 数据记录与回放

调试的时候把关键数据记录下来,事后回放分析,效率比实时调试高很多。ROS2里用ros2 bag record就能录,把odom、imu、scan、cmd_vel都录下来,出问题的时候回放一遍,很快就能定位。

ros2 bag record /odom /imu /scan /cmd_vel -o my_bag ros2 bag play my_bag

我个人的习惯是每次调试都录一个bag,哪怕当时没问题,后面对比正常和异常的数据也很有用。

6.4 远程调试与可视化

RViz2是必备的可视化工具,把机器人模型、雷达点云、代价地图、路径都显示出来,一眼就能看出问题在哪。如果开发板没有显示器,可以用RViz2的远程模式,在PC上跑RViz2,通过ROS2的DDS通信连到机器人上。配置好ROS_DOMAIN_ID和ROS_LOCALHOST_ONLY,同一网络下就能互通。

提示:如果RViz2连不上机器人,先检查两边的ROS_DOMAIN_ID是否一致,再检查防火墙是否挡住了DDS的端口。DDS默认用7400到7500之间的UDP端口。

7. 这套系统还能怎么扩展

这台开源扫地机器人的架构其实是一个通用的移动机器人平台。把扫地模块拆掉,换上机械臂就是移动操作机器人;换上摄像头和深度学习模块就是视觉导航机器人;换上多线雷达就是3D SLAM平台。底层的STM32固件和ROS2的通信框架基本不用动,只需要在上层增加新的功能包。

我自己在这套系统上做过几个扩展:加了一个ESP32做WiFi网关,把机器人状态推到手机上看;加了一个超声波模块做更精确的避障;还把导航栈从2D换成了3D,用八叉树地图做三维路径规划。每次扩展都是一次很好的学习机会,因为底层的框架已经跑通了,你可以专注于新功能本身。

如果你也在做类似的项目,我的建议是先把最小系统跑通——一个电机能转、一个传感器能读、一条数据能从STM32传到ROS2——然后再逐步加功能。机器人开发最怕的就是一上来就想做全套,结果每个模块都半吊子,出了问题也不知道是哪里的锅。一步一步来,每加一个模块就充分测试,这样最终的系统才稳定可靠。

返回列表