1. 一台扫地机为什么值得全栈拆解
扫地机器人这个品类,很多人第一反应是"不就是个会跑的吸尘器"。但如果你真正拆过一台,会发现它几乎是消费电子里集成度最变态的产品之一:移动底盘、传感器融合、路径规划、电机控制、电池管理、无线通信、人机交互,一个都不少。更关键的是,这些东西不是各自独立工作的,它们要在有限的算力和功耗预算下实时协同。这就意味着,一台扫地机天然就是一套完整的机器人工程教学平台。
我接触过不少做机器人方向的朋友,普遍有个困惑:学ROS2的时候跑仿真很顺,一到真机就抓瞎;学STM32的时候点灯、串口、PWM都能搞定,但不知道怎么把这些东西串成一个"能自主行动的系统"。开源扫地机器人恰好填上了这个断层——它把ROS2的上层决策和STM32的底层执行用一条清晰的链路连起来,让你能看到从"激光雷达出一帧点云"到"轮子实际转动"之间到底发生了什么。
这篇文章面向三类人:一是想找一个完整机器人项目练手的在校学生;二是做嵌入式但想往机器人方向转的工程师;三是已经会ROS2但没碰过真机底盘的开发者。我会按照"整体架构设计→核心模块拆解→实操落地→问题排查"的顺序,把一台开源扫地机器人从里到外讲透。涉及ROS2、STM32、传感器、电机驱动、通信协议这些关键词的地方,我都会给出具体的参数和操作思路,而不是泛泛而谈。
先说结论:扫地机器人的技术栈可以拆成"感知-决策-执行"三层,ROS2负责感知和决策,STM32负责执行和实时反馈,两者通过串口或CAN通信。理解了这条主线,后面所有细节都是挂在上面的。
2. 整体架构设计与方案选型思路
2.1 三层架构的划分逻辑
一台扫地机器人的软件系统,本质上是一个典型的分层控制架构。我把它分成三层来看:
- 感知层:激光雷达(LDS)、IMU、碰撞传感器、悬崖传感器、里程计。这一层负责把物理世界的信息转成数字信号。
- 决策层:跑在Linux主控(比如树莓派、Jetson Nano或RK3588)上的ROS2节点,负责SLAM建图、路径规划、任务调度。
- 执行层:STM32单片机,负责电机PID控制、传感器轮询、电池监测、与主控通信。
为什么这么分?核心原因是实时性要求不同。路径规划可以容忍几十毫秒的延迟,但电机控制如果延迟超过几毫秒,轮子就会抖动甚至失控。Linux系统因为调度机制的问题,做不到硬实时,所以必须把实时任务下沉到STM32。这是机器人系统设计里最基本的一条原则:该实时的地方用RTOS或裸机,该复杂计算的地方用Linux。
2.2 主控与MCU的选型考量
主控这边,常见的选择有几种。树莓派4B性价比高,ROS2 Humble跑起来没问题,但GPIO和实时性一般;Jetson Nano/NX算力强,适合跑视觉SLAM,但功耗和散热是个问题;RK3588是这两年的新宠,NPU算力足够,接口也丰富。如果只是做基础扫地机功能,树莓派4B加一个激光雷达就够用了。
STM32这边,我建议至少用STM32F103C8T6起步,如果要做更复杂的电机控制(比如FOC),可以上STM32F407或G431。F103的资源对于扫地机来说其实偏紧,但胜在便宜、资料多、社区成熟。我实测下来,用F103做三轮全向底盘的PID控制,配合超声波和红外传感器,CPU占用大概在60%左右,还有余量。
通信方式上,串口是最简单可靠的方案。ROS2这边通过ros2_serial或者自己写一个serial_node,STM32那边用UART中断收发。如果对实时性要求更高,可以用CAN总线,但调试复杂度会上升。我建议新手先用串口跑通,再考虑升级。
2.3 为什么选ROS2而不是ROS1
这个问题被问过很多次。ROS1在2025年已经停止维护了,ROS2是唯一的选择。但更重要的是,ROS2的几个特性对扫地机这种产品特别友好:
- DDS通信:去中心化,节点之间直接通信,不需要roscore。扫地机的主控和MCU之间如果都用ROS2,通信会更灵活。
- 实时性支持:ROS2支持实时内核和QoS配置,可以给电机控制话题设置更高的优先级。
- 生命周期管理:节点可以有序启动和关闭,这对扫地机的开机自检流程很重要。
ROS2的版本选择上,Humble是目前的LTS版本,支持到2027年,社区资源最丰富。Jazzy是最新版,但有些包还没跟上。我建议新手直接上Humble,等生态成熟了再迁移。
3. 核心模块拆解与关键技术点
3.1 激光雷达与SLAM建图
扫地机最核心的传感器就是激光雷达。常见的低成本方案是RPLIDAR A1或镭神N10,价格在几百块,扫描半径12米左右,精度够用。雷达通过串口或USB转串口接到主控,ROS2这边用sllidar_ros2或rplidar_ros2驱动包。
SLAM算法上,Cartographer和slam_toolbox是两个主流选择。Cartographer建图质量高,但配置复杂,对算力要求也高;slam_toolbox轻量,配置简单,适合树莓派。我实测在树莓派4B上跑slam_toolbox,建一个80平米的房间,CPU占用大概40%,内存占用500MB左右,可以接受。
建图过程中有个坑:雷达的安装位置会影响建图效果。如果雷达装得太低,会被家具腿遮挡;装得太高,又可能扫到天花板。一般建议雷达安装在离地10-15厘米的位置,视野尽量开阔。另外,雷达的扫描频率要和机器人的移动速度匹配,移动太快会导致点云稀疏,建图出现空洞。
3.2 STM32底层电机控制
电机控制是STM32的核心任务。扫地机一般用直流有刷电机或无刷电机,配合编码器做闭环控制。有刷电机驱动简单,用DRV8833或TB6612就行;无刷电机效率高,但需要FOC驱动,比如DRV8323。
PID控制是绕不开的。我一般用位置式PID做速度环,参数整定的顺序是:先调P,让轮子能响应但不过冲;再加I,消除稳态误差;最后加D,抑制振荡。具体参数上,P一般在0.5-2.0之间,I在0.01-0.1之间,D在0-0.5之间。这些值不是固定的,要根据电机特性和负载调整。
编码器读取上,STM32的定时器编码器模式是最方便的方案。把编码器的A、B相接到定时器的CH1和CH2,配置成编码器模式,硬件自动计数,CPU只需要定期读取计数值就行。注意编码器的线数和轮子直径要匹配,否则里程计会不准。计算公式是:每米脉冲数 = 编码器线数 × 减速比 / (轮子直径 × π)。
3.3 传感器融合与里程计
扫地机的里程计一般来自编码器,但编码器有累积误差,跑久了会漂。所以需要和IMU融合。IMU用MPU6050或ICM20602,通过I2C接到STM32,读取加速度和角速度,做姿态解算。
融合算法上,互补滤波是最简单的方案:角度 = α × (角度 + 角速度 × dt) + (1-α) × 加速度计角度。α一般取0.98。如果要更精确,可以用卡尔曼滤波或Mahony算法。我建议先用互补滤波跑通,再考虑升级。
ROS2这边,里程计通过odom话题发布,格式是nav_msgs/Odometry。STM32把编码器计数和IMU数据打包成自定义协议,通过串口发给主控,主控解析后发布odom话题。这里要注意时间戳同步,STM32的时间戳和主控的时间戳要对齐,否则TF变换会出错。
3.4 通信协议设计
STM32和主控之间的通信协议,我建议自定义一个简单的帧格式:
帧头(2字节) + 长度(1字节) + 命令(1字节) + 数据(N字节) + 校验(1字节) + 帧尾(2字节)帧头用0xAA 0x55,帧尾用0x0D 0x0A,校验用异或。命令字段区分是里程计数据、传感器数据还是控制指令。数据字段用小端序,浮点数用IEEE754格式。
为什么要自定义协议而不是用现成的?因为扫地机的数据量不大,但实时性要求高,自定义协议可以做到最小开销。另外,调试的时候可以用串口助手直接看原始数据,比解析ROS2消息方便。
4. 实操过程与核心环节实现
4.1 环境搭建:ROS2 Humble安装与配置
先说ROS2的安装。Ubuntu 22.04对应Humble,安装步骤如下:
# 设置locale sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 # 添加ROS2源 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 # 安装ROS2 sudo apt update sudo apt install ros-humble-desktop安装完成后,记得source /opt/ros/humble/setup.bash,并加到.bashrc里。如果遇到packages.ros.org连接错误,检查网络和DNS设置,或者换用国内镜像源。
STM32开发环境这边,我推荐VSCode + PlatformIO或者STM32CubeIDE。PlatformIO配置简单,跨平台,适合快速开发;CubeIDE是官方工具,调试功能强。如果要用J-Link下载,需要在VSCode里配置launch.json和tasks.json,指定J-Link的路径和芯片型号。
4.2 STM32端:电机控制与传感器轮询
STM32的主循环我一般这么设计:
while (1) { // 1. 读取编码器 encoder_left = TIM_GetCounter(TIM2); encoder_right = TIM_GetCounter(TIM3); // 2. 读取IMU MPU6050_Read_Accel(&accel); MPU6050_Read_Gyro(&gyro); // 3. 读取超声波和红外 ultrasonic_distance = Ultrasonic_Measure(); cliff_status = Read_Cliff_Sensors(); // 4. PID计算 pid_left = PID_Compute(&pid_left_ctx, target_left, encoder_left); pid_right = PID_Compute(&pid_right_ctx, target_right, encoder_right); // 5. 设置PWM Set_PWM(pid_left, pid_right); // 6. 发送数据到主控 Send_Data_To_Host(); // 7. 接收主控指令 Receive_Command_From_Host(); HAL_Delay(10); // 100Hz循环 }这个循环频率是100Hz,对于扫地机来说足够了。如果要做更精细的控制,可以提高到200Hz或500Hz,但要注意CPU占用。
超声波测距用定时器输入捕获模式,测量回响信号的高电平时间,然后换算成距离。公式是:距离 = 高电平时间 × 声速 / 2。声速取340m/s,注意温度补偿。我实测HC-SR04在室内环境下,误差在1-2厘米左右,够用。
4.3 ROS2端:节点设计与话题通信
ROS2这边的节点设计,我建议分成几个独立的节点:
- serial_node:负责和STM32通信,发布
odom和sensor_data话题,订阅cmd_vel话题。 - slam_node:跑slam_toolbox,订阅
scan话题,发布map话题。 - navigation_node:跑Nav2,订阅
map和odom,发布cmd_vel。 - robot_state_publisher:发布TF变换。
serial_node的核心逻辑是:
import rclpy from rclpy.node import Node from nav_msgs.msg import Odometry from geometry_msgs.msg import Twist import serial class SerialNode(Node): def __init__(self): super().__init__('serial_node') self.ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.1) self.odom_pub = self.create_publisher(Odometry, 'odom', 10) self.cmd_sub = self.create_subscription(Twist, 'cmd_vel', self.cmd_callback, 10) self.timer = self.create_timer(0.01, self.read_serial) def read_serial(self): if self.ser.in_waiting > 0: data = self.ser.read(self.ser.in_waiting) # 解析数据,发布odom odom = Odometry() # ... 填充odom self.odom_pub.publish(odom) def cmd_callback(self, msg): # 打包cmd_vel,发送到STM32 packet = self.pack_cmd(msg.linear.x, msg.angular.z) self.ser.write(packet)这里有个细节:串口的读写要放在不同的线程或定时器里,否则会阻塞。我一般用create_timer做读,用回调做写。
4.4 建图与导航的完整流程
建图流程是这样的:
- 启动雷达驱动:
ros2 launch sllidar_ros2 sllidar_launch.py - 启动serial_node:
ros2 run my_robot serial_node - 启动slam_toolbox:
ros2 launch slam_toolbox online_async_launch.py - 启动RViz2:
rviz2,添加LaserScan和Map显示 - 手动控制机器人走一圈:
ros2 run teleop_twist_keyboard teleop_twist_keyboard - 保存地图:
ros2 run nav2_map_server map_saver_cli -f my_map
导航流程:
- 加载地图:
ros2 run nav2_map_server map_server --ros-args -p yaml_filename:=my_map.yaml - 启动Nav2:
ros2 launch nav2_bringup navigation_launch.py - 在RViz2里设置初始位置和目标点
- 机器人自动规划路径并移动
这里有个坑:Nav2的代价地图参数需要根据机器人尺寸调整。robot_radius要设成机器人的实际半径,inflation_radius要设成半径的1.5倍左右,否则机器人会贴着墙走或者卡住。
5. 常见问题与排查技巧实录
5.1 通信类问题
问题1:串口数据乱码
这是最常见的问题。原因一般是波特率不匹配、数据位/停止位配置错误、或者地线没接好。排查步骤:先用串口助手看STM32发的原始数据,确认波特率正确;再检查ROS2这边的串口配置;最后检查硬件连线,特别是GND。
问题2:CAN通信突然连不上
CAN总线的问题一般是终端电阻没接、波特率不匹配、或者总线负载过高。排查步骤:用万用表测CAN_H和CAN_L之间的电阻,应该是60欧姆左右;检查所有节点的波特率是否一致;用CAN分析仪看总线负载,如果超过70%就要优化。
问题3:ROS2话题收不到数据
先ros2 topic list看话题是否存在,再ros2 topic echo /odom看有没有数据。如果没有数据,检查发布者是否启动;如果有数据但频率不对,检查定时器配置。另外,QoS配置不匹配也会导致收不到数据,发布者和订阅者的QoS要一致。
5.2 电机控制类问题
问题4:电机抖动或异响
一般是PID参数不合适。先降低P,如果抖动减轻,说明P太大;如果响应变慢,说明P太小。I太大会导致积分饱和,表现为电机突然加速;D太大会导致高频振荡。我一般用Ziegler-Nichols方法做初步整定,再手动微调。
问题5:里程计漂移严重
编码器分辨率不够、轮子打滑、或者地面不平都会导致漂移。解决办法:提高编码器线数;在轮子上加防滑材料;用IMU做融合。如果漂移是系统性的,可以标定轮子直径和轮距,修正里程计模型。
问题6:五线四相步进电机不转
五线四相步进电机的接线是:红线接VCC,其余四根接驱动器的输出。如果不动,检查驱动器是否使能、脉冲信号是否正常、相序是否正确。用万用表测每相的电阻,应该差不多。如果相序错了,电机会抖动但不转。
5.3 建图与导航类问题
问题7:建图出现重影或错位
一般是雷达数据和时间戳不同步。检查雷达驱动的时间戳是否用了系统时间;检查TF变换是否正确;检查机器人移动速度是否太快。另外,雷达的安装角度也要标定,如果雷达装歪了,建图会整体旋转。
问题8:导航时机器人卡住
先看代价地图,如果障碍物膨胀太大,机器人会认为通道太窄。调整inflation_radius和cost_scaling_factor。如果机器人原地打转,检查cmd_vel的角速度限制;如果机器人不动,检查cmd_vel是否被其他节点覆盖。
问题9:八叉树地图导航内存占用高
八叉树地图(OctoMap)的分辨率越高,内存占用越大。如果树莓派内存不够,可以降低分辨率(比如从0.05米降到0.1米),或者限制地图范围。另外,定期清理旧数据也能释放内存。
5.4 开发环境类问题
问题10:VSCode配置STM32开发环境失败
一般是路径配置错误。检查c_cpp_properties.json里的includePath是否包含了STM32的头文件目录;检查launch.json里的svdFile是否指向了正确的SVD文件;检查J-Link的驱动是否安装。如果还是不行,用STM32CubeIDE先跑通,再迁移到VSCode。
问题11:STM32芯片包安装失败
在Keil或CubeIDE里安装芯片包时,如果网络不好,可以手动下载.pack文件,然后离线安装。另外,芯片包的版本要和芯片型号匹配,比如STM32F1系列要用F1的包,不能用F4的。
问题12:ROS2安装教程里的命令报错
最常见的是apt update报错,一般是源的问题。检查/etc/apt/sources.list.d/ros2.list的内容是否正确;检查网络是否能访问packages.ros.org;如果用了代理,要配置apt的代理。另外,Ubuntu版本和ROS2版本要对应,22.04对应Humble,24.04对应Jazzy。
6. 从扫地机延伸到更广的机器人开发
一台开源扫地机器人跑通之后,你会发现很多技能是可以迁移的。比如ROS2的话题-服务-动作机制,在机械臂、无人机、自动驾驶上都是一样的;STM32的PID和传感器融合,在平衡车、云台、四足机器人上也是通用的。
如果你想继续深入,我建议几个方向:一是视觉SLAM,用RGB-D相机替代激光雷达,跑ORB-SLAM3或RTAB-Map;二是多机协同,用ROS2的DDS发现机制,让多台扫地机协同建图;三是强化学习,用Gazebo仿真环境训练路径规划策略,再迁移到真机。
最后分享一个我在实际项目中踩过的坑:不要一开始就追求全功能。我见过太多人一上来就想做激光雷达+视觉+AI导航,结果卡在串口通信上。正确的做法是先把最小闭环跑通——STM32控制轮子转,ROS2能发指令收里程计——然后再逐步加传感器和算法。这个顺序看起来慢,实际上是最快的。
另外,文档和注释比代码本身更重要。扫地机的代码量不大,但涉及硬件、通信、算法多个层面,过一个月再看,没有注释根本记不住当时为什么这么写。我现在的习惯是每个模块开头写清楚输入输出、依赖关系、已知问题,调试的时候能省很多时间。