尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从STM32到ROS2再到Home Assistant:扫地机器人全栈开发实战

从STM32到ROS2再到Home Assistant:扫地机器人全栈开发实战 1. 项目缘起为什么我要把一台扫地机器人拆到“底裤都不剩”扫地机器人这个品类市面上从几百块到几千块的机器我都拆过、修过、也自己攒过。但真正让我产生“把它完整讲一遍”冲动的是去年帮一个学弟做毕业设计——他想做一台能自主建图、能避障、能通过手机控制的扫地机器人结果卡在ROS2和STM32的通信上整整两周。我问他卡在哪他说“网上教程要么只讲ROS2仿真要么只讲STM32点灯没人告诉我这两东西怎么接在一起。”这句话点醒了我。一台扫地机器人本质上就是一套完整的机器人工程课程感知层有超声波、红外、碰撞、陀螺仪、编码器控制层有STM32跑电机闭环、跑传感器融合决策层有ROS2跑SLAM建图、路径规划、状态机交互层有Home Assistant做统一接入和自动化。这四个层次恰好对应了机器人工程从底层到上层的全部核心知识点。所以这篇文章我不打算只讲“怎么装ROS2”或者“怎么配STM32的PWM”而是把一台开源扫地机器人从硬件选型、嵌入式固件、ROS2节点设计、到Home Assistant集成的全栈链路按我实际踩过的坑和验证过的方案完整拆一遍。适合谁看如果你正在做机器人相关的课程设计、毕业设计或者想从纯嵌入式转向机器人系统开发再或者你只是好奇“我家那台扫地机到底怎么工作的”这篇都能给你一条能直接抄作业的路径。注意本文涉及的所有代码和配置均基于我实际跑通的环境ROS2 Humble STM32F407 Home Assistant OS但不同硬件平台可能需要微调我会在关键位置标注“这里容易出问题”。2. 整体架构设计四层分离各司其职2.1 为什么不做“一块板子全搞定”我见过不少开源扫地机器人项目为了省成本把电机控制、传感器采集、SLAM计算全塞在一颗树莓派或者一块STM32上。结果就是树莓派跑SLAM已经吃满CPU再让它实时响应电机编码器中断直接卡死STM32算力又不够跑建图算法最后做成一个“能动但很蠢”的玩具。我的方案是四层分离层级硬件载体核心职责实时性要求感知执行层STM32F407电机闭环、传感器采集、紧急避障硬实时微秒级通信层串口/UART上下行数据打包解包软实时毫秒级决策规划层树莓派4B / Jetson NanoROS2节点、SLAM、路径规划软实时10ms~100ms交互应用层Home Assistant状态展示、远程控制、自动化非实时秒级这个分层的核心逻辑是让实时的事情归实时让计算的事情归计算。STM32负责“反射弧”树莓派负责“大脑”Home Assistant负责“遥控器”。每一层通过定义良好的接口通信任何一层出问题都不会直接拖垮其他层。2.2 通信协议选型为什么是串口而不是WiFi或CANSTM32和树莓派之间的通信我试过三种方案WiFiESP8266/ESP32延迟不稳定丢包后电机控制会抖动而且调试时抓包麻烦。CAN总线抗干扰强但树莓派原生不支持CAN需要额外加MCP2515模块增加硬件复杂度。串口UART最简单、最稳定、调试最方便115200波特率下每10ms发一帧20字节的数据完全够用。最终我选了串口但加了一个自定义帧协议来保证可靠性// STM32端发送帧格式共16字节 // 帧头(2) 数据类型(1) 数据载荷(10) 校验和(1) 帧尾(2) typedef struct { uint8_t header[2]; // 0xAA 0x55 uint8_t data_type; // 0x01传感器数据 0x02电机状态 0x03心跳 uint8_t payload[10]; // 具体数据 uint8_t checksum; // 前面所有字节异或 uint8_t tail[2]; // 0x0D 0x0A } UART_Frame;实操心得串口通信最容易出问题的地方是地线没共地和波特率误差。STM32F407在168MHz主频下115200波特率的误差约0.16%完全可接受。但如果你用内部RC振荡器误差可能超过2%必须换外部晶振。2.3 ROS2节点划分别把所有东西塞进一个节点ROS2 Humble里我最初把电机控制、传感器读取、SLAM全写在一个节点里结果调试时改一行代码要重新编译整个包而且一个地方崩溃全盘皆输。后来拆成四个独立节点base_controller负责和STM32串口通信发布/odom和/sensor_data订阅/cmd_vel。slam_node用slam_toolbox建图发布/map和/tf。navigation_node用Nav2做路径规划订阅/map和/odom发布/cmd_vel。battery_monitor单独监控电池电压低电量时发布/battery_low警告。这样拆的好处是每个节点可以独立启动、独立调试、独立重启。比如SLAM建图效果不好我只重启slam_node不影响底盘控制。3. 嵌入式端实操STM32F407的电机闭环与传感器融合3.1 电机选型与驱动五线四相步进电机怎么接扫地机器人常用的电机有两种直流有刷电机编码器和步进电机。我选的是五线四相步进电机28BYJ-48原因是它自带减速箱、扭矩大、控制简单而且成本极低。但它的缺点也很明显转速慢、效率低。如果你追求性能建议换带编码器的直流电机。五线四相步进电机的接线方式线色功能接STM32引脚红公共端VCC接5V橙A相PA0黄B相PA1粉C相PA2蓝D相PA3驱动我用的是ULN2003达林顿管阵列每相电流约200mASTM32的GPIO直接驱动能力不够必须加驱动芯片。// 步进电机八拍控制序列A-AB-B-BC-C-CD-D-DA const uint8_t step_seq[8] {0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09}; void stepper_step(uint8_t dir, uint16_t speed) { static uint8_t phase 0; if (dir FORWARD) { phase (phase 1) % 8; } else { phase (phase 7) % 8; } GPIOA-ODR (GPIOA-ODR 0xFFF0) | step_seq[phase]; delay_us(speed); }注意步进电机在低速时会有明显振动建议用细分驱动或者加减速曲线。我实测在200步/秒以下振动明显超过400步/秒后反而平稳。3.2 编码器测速定时器捕获模式的正确配置如果你用的是带编码器的直流电机测速是必须的。STM32F407的定时器有编码器模式可以直接读取正交编码器的脉冲数和方向不需要额外中断。配置步骤开启TIM3和TIM4的时钟。配置PA6/PA7为TIM3_CH1/CH2PB6/PB7为TIM4_CH1/CH2。设置TIM3和TIM4为编码器模式计数范围0~65535。每10ms读取一次计数值计算差值得到速度。int16_t get_encoder_speed(TIM_TypeDef* TIMx) { static int16_t last_cnt[2] {0, 0}; int16_t cur_cnt (int16_t)TIMx-CNT; int16_t diff cur_cnt - last_cnt[TIMx TIM3 ? 0 : 1]; last_cnt[TIMx TIM3 ? 0 : 1] cur_cnt; return diff; // 正数正转负数反转 }踩过的坑编码器模式下的CNT寄存器是16位的如果10ms内脉冲数超过32767会溢出。我的电机在最大转速下10ms约2000个脉冲完全安全。但如果你用高分辨率编码器要么缩短采样周期要么用32位定时器。3.3 超声波避障为什么我放弃了HC-SR04HC-SR04是入门级超声波模块便宜、好用但有两个致命问题盲区大小于2cm测不到和易受干扰多个超声波同时工作会互相干扰。扫地机器人需要贴边清扫盲区大意味着撞墙。我换成了VL53L0X激光测距模块I2C接口最远2米精度±3%盲区仅3cm。虽然贵一点约15元/个但可靠性提升明显。// VL53L0X读取距离简化版 uint16_t read_tof_distance(void) { uint8_t buf[2]; HAL_I2C_Mem_Read(hi2c1, 0x52, 0x1E, 1, buf, 2, 100); return (buf[0] 8) | buf[1]; }实操心得VL53L0X的I2C地址默认是0x52如果你要接多个需要用XSHUT引脚逐个使能并修改地址。我用了3个分别改成0x52、0x54、0x56。3.4 陀螺仪与姿态融合MPU6050的DMP用法MPU6050是经典的六轴传感器但直接读原始数据噪声很大。我建议用它的DMP数字运动处理器直接在模块内部做姿态解算输出四元数。// 初始化DMP mpu6050_init(); dmp_load_motion_driver_firmware(); dmp_set_orientation(0); dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP); dmp_set_fifo_rate(100); // 100Hz mpu_set_dmp_state(1); // 读取四元数 float q[4]; dmp_read_fifo(NULL, NULL, q, NULL, NULL);注意DMP的固件需要从InvenSense官方获取网上有现成的inv_mpu.c和inv_mpu_dmp_motion_driver.c直接移植即可。但要注意STM32的GBK转UTF8问题——这些文件里可能有中文注释Keil默认GBK编码直接编译会报错。解决方法用Notepad打开转成UTF-8无BOM格式。4. ROS2端实操从串口数据到SLAM建图4.1 ROS2 Humble安装那些官方教程没告诉你的坑ROS2 Humble的安装官方教程看起来很简单但实际执行时大概率会遇到公钥验证失败的问题获取:1 http://packages.ros.org/ros2/ubuntu jammy InRelease [4,682 B] 错误:1 http://packages.ros.org/ros2/ubuntu jammy InRelease 由于没有公钥无法验证下列签名NO_PUBKEY F42ED6FBAB17C654解决方法# 重新导入公钥 sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg # 如果curl也失败用wget wget -qO - https://raw.githubusercontent.com/ros/rosdistro/master/ros.key | sudo apt-key add -踩过的坑如果你在国内网络环境下raw.githubusercontent.com可能访问不稳定。建议提前下载好ros.key文件用U盘拷过去。安装完成后别忘了echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc4.2 串口通信节点Python还是CROS2支持Python和C写节点。串口通信这种对性能要求不高的场景我选了Python因为开发快、调试方便。但要注意Python的pyserial库在读取串口时是阻塞的必须用多线程或者异步IO。import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry import serial import threading class BaseController(Node): def __init__(self): super().__init__(base_controller) self.ser serial.Serial(/dev/ttyUSB0, 115200, timeout0.01) self.cmd_vel_sub self.create_subscription(Twist, /cmd_vel, self.cmd_vel_callback, 10) self.odom_pub self.create_publisher(Odometry, /odom, 10) self.read_thread threading.Thread(targetself.read_serial) self.read_thread.start() def cmd_vel_callback(self, msg): # 将线速度和角速度转换为左右轮速度 v msg.linear.x w msg.angular.z v_left v - w * 0.1 # 0.1为轮距的一半 v_right v w * 0.1 # 打包发送给STM32 frame self.pack_frame(v_left, v_right) self.ser.write(frame) def read_serial(self): while rclpy.ok(): if self.ser.in_waiting 16: data self.ser.read(16) odom self.parse_frame(data) if odom: self.odom_pub.publish(odom)实操心得/dev/ttyUSB0这个设备名可能会变建议用udev规则固定# /etc/udev/rules.d/99-robot.rules KERNELttyUSB*, ATTRS{idVendor}1a86, ATTRS{idProduct}7523, SYMLINKrobot_base这样无论插拔多少次设备名始终是/dev/robot_base。4.3 SLAM建图slam_toolbox的配置与调参ROS2 Humble里slam_toolbox是官方推荐的SLAM包。安装sudo apt install ros-humble-slam-toolbox启动建图ros2 launch slam_toolbox online_async_launch.py但默认参数建出来的图往往重影严重需要调这几个关键参数参数默认值建议值说明max_laser_range10.05.0扫地机激光雷达一般只有5米resolution0.050.03分辨率越高地图越精细但计算量越大minimum_travel_distance0.50.2扫地机移动慢降低阈值minimum_travel_heading0.50.2同上loop_search_maximum_distance3.01.0小场景回环检测范围要小踩过的坑如果你的机器人原地旋转时地图扭曲大概率是陀螺仪零偏没校准。MPU6050上电后需要静止2秒做零偏校准否则角速度积分会漂移。4.4 八叉树地图与导航Nav2的配置要点建好图之后导航用Nav2。但Nav2默认用的是2D代价地图如果你的机器人有高度信息比如能上斜坡可以用八叉树地图OctoMap。sudo apt install ros-humble-octomap-server配置octomap_serveroctomap_server: ros__parameters: resolution: 0.05 frame_id: map base_frame_id: base_footprint sensor_model: max_range: 5.0 min_range: 0.1注意八叉树地图计算量大树莓派4B跑起来会比较吃力。如果只是平地清扫用2D代价地图就够了。5. Home Assistant集成让扫地机融入智能家居5.1 为什么选Home Assistant而不是自己写APP自己写APP当然可以但你要处理用户登录、设备发现、状态同步、自动化规则、语音助手接入……这些Home Assistant全帮你做好了。而且Home Assistant支持MQTTROS2端只要把数据发到MQTTHome Assistant就能自动发现。5.2 MQTT桥接ROS2到Home Assistant的桥梁安装MQTT Broker用Mosquittosudo apt install mosquitto mosquitto-clients sudo systemctl enable mosquittoROS2端用mqtt_client库发布状态import paho.mqtt.client as mqtt client mqtt.Client() client.connect(localhost, 1883, 60) def publish_status(odom): payload { x: odom.pose.pose.position.x, y: odom.pose.pose.position.y, battery: get_battery_voltage() } client.publish(robot/status, json.dumps(payload))Home Assistant配置mqtt: sensor: - name: Robot Position X state_topic: robot/status value_template: {{ value_json.x }} - name: Robot Battery state_topic: robot/status value_template: {{ value_json.battery }} unit_of_measurement: V实操心得MQTT的retain标志很重要。如果你希望Home Assistant重启后还能看到最后状态发布时加retainTrue。5.3 自动化场景低电量自动回充Home Assistant的自动化引擎可以轻松实现“低电量自动回充”automation: - alias: Low Battery Return to Charge trigger: platform: numeric_state entity_id: sensor.robot_battery below: 20 action: - service: mqtt.publish data: topic: robot/command payload: return_to_baseROS2端订阅robot/command收到return_to_base后调用Nav2的NavigateToPose目标点设为充电桩坐标。6. 常见问题与排查技巧实录6.1 串口通信丢包怎么办现象ROS2端偶尔收不到STM32的数据或者收到乱码。排查思路检查共地STM32和树莓派必须共地否则电平参考不一致。检查波特率双方必须完全一致STM32用外部晶振。检查缓冲区Python的serial.Serial默认缓冲区可能太小设置buffer_size1024。加校验和我用的异或校验能过滤99%的乱码。6.2 SLAM建图重影严重现象地图上墙壁出现双层甚至多层。排查思路里程计漂移检查编码器线数是否正确轮径参数是否准确。时间同步ROS2的/tf时间戳必须和激光雷达时间戳对齐用message_filters做时间同步。激光雷达安装位置base_link到laser的TF变换必须准确用卷尺量。6.3 Nav2规划失败现象机器人原地转圈不执行路径。排查思路代价地图检查/global_costmap和/local_costmap是否正常发布。膨胀半径inflation_radius太小会导致机器人贴墙走太大则无法通过窄道。恢复行为Nav2默认有spin、backup、wait三种恢复行为检查是否被触发。6.4 Home Assistant无法发现MQTT设备现象MQTT消息发出去了但Home Assistant里看不到实体。排查思路主题匹配Home Assistant的state_topic必须和发布主题完全一致。JSON格式value_template里的字段名必须和JSON key一致。MQTT Broker地址如果Home Assistant和ROS2不在同一台机器localhost要改成实际IP。独家避坑技巧我习惯在ROS2端加一个心跳包每5秒发一次robot/heartbeatHome Assistant用expire_after设置超时。这样如果机器人离线Home Assistant能自动标记为unavailable而不是显示过期的旧数据。7. 后续扩展这台扫地机还能怎么玩这套架构的扩展性很强。如果你想加视觉识别可以在树莓派上接一个USB摄像头跑YOLO做障碍物分类如果你想加语音控制Home Assistant原生支持多种语音助手如果你想做多机协同ROS2的DDS天然支持多机通信只要配置好ROS_DOMAIN_ID。我目前正在做的扩展是农业病虫害识别——把扫地机的移动底盘加上摄像头和补光灯在温室里巡检作物。核心代码复用这套ROS2STM32架构只是把SLAM换成了视觉导航把避障换成了病虫害检测。如果你对这个方向感兴趣可以关注后续的更新。最后分享一个我在调试过程中总结的最小可行调试顺序先让STM32单独跑通电机和传感器再用串口助手确认数据帧正确然后跑ROS2的base_controller确认/odom正常接着启动slam_toolbox建图最后接入Home Assistant。每一步都确认无误再进入下一步比一次性全启动再排查要快得多。
返回列表