
1. 从一台扫地机看懂整套机器人工程扫地机器人这个品类很多人第一反应是不就是个会跑的吸尘器。但如果你真正拆过一台尤其是带激光雷达、能建图导航的那种你会发现它本质上是一台完整的移动机器人有感知、有决策、有执行、有电源管理、有通信总线还有一套跑在Linux上的机器人操作系统。把这台机器拆开从底层电机驱动一路看到上层导航算法等于把机器人工程专业里最核心的几门课串了一遍。我最近花了两周时间把一台开源扫地机器人的软硬件全栈过了一遍。硬件侧是STM32做主控负责电机、传感器、电源这些实时性要求高的活软件侧是ROS2跑在Ubuntu上负责建图、定位、路径规划这些算力密集的活。两边通过串口通信各司其职。这套架构不是某一家公司的独创而是整个移动机器人行业的主流做法理解了它你再看AGV、无人配送车、巡检机器人底层逻辑都是一样的。这篇文章适合三类人一是正在学STM32但不知道拿什么项目练手的嵌入式方向同学二是刚接触ROS2、想找个完整项目把话题、服务、动作、TF、导航栈串起来理解的机器人方向同学三是想了解移动机器人整体架构、为面试或转行做准备的工程师。我会从整体设计思路讲起然后逐层拆解硬件选型、通信协议、ROS2节点设计、导航栈配置最后把我踩过的坑和排查经验整理出来。内容偏实操代码和配置都会给到你可以直接抄作业。2. 整体架构设计与方案选型2.1 为什么是STM32 ROS2这种双脑架构移动机器人最核心的矛盾是实时性和算力不可兼得。电机控制要求微秒级响应PID环跑在1kHz以上一旦抖动大了轮子就会顿挫而SLAM建图、路径规划这些活算力需求大但允许几十毫秒的延迟。你不可能用一颗芯片同时满足这两个极端需求所以行业里普遍采用下位机 上位机的双脑架构。下位机选STM32理由很直接它便宜、生态成熟、实时性可控、外设丰富。一个STM32F4系列就能同时跑多路PWM、多路ADC、多个串口、编码器接口还能上FreeRTOS做任务调度。上位机选ROS2是因为它把机器人开发里最烦的通信、坐标变换、导航算法都封装好了你不用从零写一个A*也不用自己实现TF树。提示不要试图用一块树莓派同时干电机控制和导航。树莓派跑的是非实时LinuxGPIO抖动能到毫秒级电机控制会很难受。双脑架构不是过度设计是刚需。2.2 硬件选型的几个关键决策先说主控。我用的STM32F407VET6168MHz主频192KB RAM512KB Flash。为什么不用F103因为F103的定时器资源和串口数量在扫地机这种多外设场景下会捉襟见肘。扫地机至少要3个定时器两路电机PWM 编码器捕获、2个串口一个接上位机、一个接调试或蓝牙、多路ADC电池电压、电流检测F407的资源余量更舒服。电机选型上扫地机常用两种方案有刷直流电机 编码器或者无刷电机 霍尔。有刷方案便宜、驱动简单用一颗TB6612或DRV8833就能驱动适合入门。无刷方案效率高、寿命长但需要FOC驱动复杂度上一个台阶。我这次用的是有刷方案配合霍尔编码器做速度闭环性价比最高。传感器方面扫地机的核心是激光雷达。我用的是一款2D单线激光雷达串口输出10Hz扫描频率360度覆盖。它负责给SLAM提供环境点云。另外还有几个必备的MPU6050做IMU提供角速度和加速度几个红外或超声波做悬崖检测和碰撞检测电流传感器做堵转保护。模块型号接口作用主控STM32F407VET6-实时控制核心电机驱动TB6612GPIOPWM双路电机驱动编码器霍尔式定时器编码器模式速度反馈激光雷达2D单线UART环境感知IMUMPU6050I2C姿态估计上位机树莓派4B / x86小主机UARTROS2运行2.3 通信协议的设计考量上下位机之间用串口通信波特率我设的115200。为什么不更高因为再高在长线缆上误码率会上升而且115200对扫地机的数据量已经够用。协议格式我用的是经典的帧头 长度 功能码 数据 校验结构。帧头用0xAA 0x55两个字节避免和普通数据混淆。长度字段标明数据区字节数。功能码区分是上报传感器数据还是接收速度指令。校验用CRC8比简单累加和更可靠。这个协议设计不复杂但足够稳实测连续跑几小时没出现过丢帧错帧。注意串口通信一定要做超时处理。上位机发速度指令后如果下位机200ms没收到新指令应该自动停车。这是安全底线防止通信中断后机器人失控。3. STM32下位机的核心细节与实操3.1 电机速度闭环的PID实现扫地机的轮子要能精确控制速度靠的是编码器反馈 PID闭环。编码器接在定时器的编码器模式上硬件自动计数不占CPU。我用的定时器编码器模式四倍频计数每转一圈能拿到几千个脉冲分辨率足够。PID参数整定是个体力活。我的经验是先把Kp从小往大调调到轮子开始等幅振荡然后取振荡临界值的60%作为Kp。接着加Ki消除稳态误差但Ki不能太大否则会积分饱和导致超调。Kd在速度环里作用有限可以给个很小的值或者干脆不给。typedef struct { float Kp, Ki, Kd; float integral; float prev_error; float output_max; } PID; float PID_Compute(PID *pid, float target, float actual) { float error target - actual; pid-integral error; // 积分限幅防止饱和 if (pid-integral pid-output_max) pid-integral pid-output_max; if (pid-integral -pid-output_max) pid-integral -pid-output_max; float derivative error - pid-prev_error; pid-prev_error error; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; if (output pid-output_max) output pid-output_max; if (output -pid-output_max) output -pid-output_max; return output; }这段代码里积分限幅是关键。我一开始没做限幅结果机器人卡在墙角时积分项疯狂累积一脱困就猛冲出去差点撞墙。加上限幅后这个问题就没了。3.2 五线四相步进电机的驱动细节有些扫地机的边刷或者升降机构会用步进电机。五线四相步进电机是常见型号五根线里一根是公共端另外四根是四个相。驱动方式有单四拍、双四拍、八拍三种。八拍步距角最小运行最平滑但扭矩略低。我一般用八拍。STM32驱动步进电机可以用GPIO直接推也可以用定时器产生脉冲配合驱动器。直接推的话用定时器中断按节拍切换GPIO状态。关键是节拍表要写对// 八拍正转节拍表 const uint8_t step_table[8] { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09 };每个节拍对应四个相的不同通电组合。切换频率决定了转速频率太高电机会失步太低会抖动。实测这个型号在1kHz左右的切换频率下运行最稳。3.3 ADC多通道切换的坑扫地机要监测电池电压、电机电流至少两路ADC。STM32的ADC多通道采集有个经典坑切换通道后第一次转换的数据不可信需要丢弃。原因是采样保持电容需要时间建立。我的做法是用DMA 定时器触发让ADC自动轮流采集多个通道数据直接搬到内存CPU完全不干预。配置时把ADC设成扫描模式DMA设成循环模式定时器触发频率设成1kHz。这样每毫秒就有一组新鲜的电压电流数据。提示ADC参考电压一定要稳。如果直接用VDDA做参考电机一启动电压就波动采集出来的电池电压会跳。条件允许的话用外部基准芯片比如REF3033。3.4 串口通信与数据打包下位机每10ms向上位机上报一次数据包括编码器计数、IMU原始数据、电池电压、电流、碰撞状态。数据打包成前面说的帧格式CRC8校验。void pack_and_send(void) { uint8_t buf[32]; uint8_t idx 0; buf[idx] 0xAA; buf[idx] 0x55; buf[idx] 0x10; // 长度 buf[idx] 0x01; // 功能码传感器上报 // 填充数据... memcpy(buf[idx], sensor_data, sizeof(sensor_data)); idx sizeof(sensor_data); buf[idx] crc8(buf, idx); idx; HAL_UART_Transmit_DMA(huart1, buf, idx); }用DMA发送不阻塞主循环。接收侧用空闲中断 DMA一帧数据收完触发中断解析后更新指令。这套收发机制实测很稳CPU占用极低。4. ROS2上位机的节点设计与导航实现4.1 ROS2环境搭建与常见报错处理上位机我用的是Ubuntu 22.04 ROS2 Humble。安装过程本身不复杂但有几个报错几乎人人都会遇到。最典型的是添加软件源时的公钥验证失败报错信息大概是由于没有公钥无法验证下列签名。解决办法是手动导入公钥sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg另一个常见问题是网络下载慢导致apt更新超时。这个只能换时间段或者配置镜像源没有别的捷径。安装完成后记得source环境source /opt/ros/humble/setup.bash echo source /opt/ros/humble/setup.bash ~/.bashrc验证安装是否成功跑一个talker和listener就知道了。如果这两个能正常通信说明ROS2核心没问题。4.2 话题、服务、动作的合理使用ROS2的三种通信机制用错了会让系统很别扭。我的划分原则是高频、单向、持续的数据用话题低频、需要应答的配置用服务耗时、可取消的任务用动作。在扫地机项目里传感器数据上报、速度指令下发、激光雷达点云全部走话题。因为它们是高频持续的。比如/cmd_vel话题接收速度指令/scan话题发布激光数据/odom话题发布里程计。服务用在什么地方比如开始建图、保存地图、切换工作模式这种一次性指令。动作则用在导航到某个目标点这种可能耗时几十秒、中途可能需要取消的任务上。注意话题的QoS配置很关键。传感器数据用SensorDataQoS保证最新数据优先控制指令用Reliable保证不丢。配错了会出现数据收不到或延迟很大的诡异现象。4.3 里程计计算与TF树发布里程计是导航的基础。下位机上报编码器计数上位机换算成轮子走过的距离再结合两轮差速模型算出机器人的位姿变化。两轮差速的运动学很简单设左右轮速度分别为vL和vR轮距为L则机器人线速度v (vL vR) / 2角速度ω (vR - vL) / L。对时间积分就得到位姿。def update_odom(self, vL, vR, dt): v (vL vR) / 2.0 omega (vR - vL) / self.wheel_base self.x v * math.cos(self.theta) * dt self.y v * math.sin(self.theta) * dt self.theta omega * dt算出来的位姿要发布成TF变换从odom坐标系到base_link坐标系。同时激光雷达的安装位置也要发布TF从base_link到laser_frame。TF树建对了RViz里才能正确显示所有数据。4.4 SLAM建图与导航栈配置SLAM我用的是slam_toolbox它是ROS2里比较成熟的2D SLAM方案。配置主要是调几个参数激光雷达的扫描范围、地图分辨率、回环检测的阈值。地图分辨率设0.05米也就是每个像素代表5厘米这个精度对扫地机够用。导航栈用Nav2。配置分几块全局代价地图、局部代价地图、全局规划器、局部控制器、行为树。全局规划器我用NavFn局部控制器用DWB。代价地图的膨胀半径要根据机器人实际尺寸设设小了会贴着墙走设大了会卡在窄通道进不去。local_costmap: inflation_layer: inflation_radius: 0.25 cost_scaling_factor: 3.0这个膨胀半径0.25米是实测出来的。机器人半径约0.17米留8厘米安全余量既能贴边清扫又不会撞。4.5 八叉树地图在三维感知中的角色2D SLAM够用但如果你想做三维避障比如识别桌腿、门槛就得上八叉树地图。八叉树把三维空间递归八等分每个节点存储占据概率能高效表达稀疏的三维环境。在ROS2里用octomap_server把深度相机或三维激光的点云转成八叉树。配置时注意分辨率别设太高5厘米足够再高内存吃不消。八叉树地图主要用在路径规划的三维碰撞检测上让机器人知道哪些空间是空的、哪些是障碍。5. 常见问题与排查技巧实录5.1 通信类问题速查现象可能原因排查方法上位机收不到数据串口设备名不对ls /dev/tty*确认数据乱码波特率不匹配两端统一115200偶发丢帧线缆干扰或过长缩短线缆加屏蔽CRC校验失败帧同步丢失检查帧头加超时重同步权限不足用户不在dialout组sudo usermod -aG dialout $USER串口权限这个问题我踩过。第一次跑的时候一直报Permission denied查了半天才发现当前用户不在dialout组。加组之后要重新登录才生效这个细节很容易忽略。5.2 电机控制类问题电机不转先查三样PWM有没有输出、方向引脚电平对不对、使能引脚有没有拉高。用示波器或者逻辑分析仪看一眼PWM波形比盲猜快得多。电机抖动或者异响多半是PID参数太激进。把Kp降一半试试。如果降了还抖检查编码器接线有没有接反A/B相搞反了会导致速度反馈符号错误PID直接发散。堵转保护一定要做。我一开始没做机器人卡在沙发底下电机堵转电流飙到3A几分钟后驱动芯片烫得能煎鸡蛋。后来加了电流检测超过阈值就停安全多了。5.3 ROS2导航类问题机器人原地打转或者走不直先看里程计准不准。把机器人手动推一米看RViz里位姿变化是不是也接近一米。差太多就是轮径参数或者编码器分辨率设错了。导航时机器人贴着墙走或者卡住调代价地图的膨胀半径。还有可能是激光雷达的安装TF不对导致障碍物位置偏移。在RViz里看激光点和实际环境对不对得上一眼就能判断。建图时地图重影或者错位通常是里程计漂移太大。可以降低移动速度或者调SLAM的回环检测参数。如果激光雷达安装不水平扫描面倾斜建出来的图也会歪这个要用水平仪校准。5.4 编码与字符集问题STM32工程里如果用了中文注释从GBK转UTF8的时候容易乱码。我的做法是统一用UTF8编辑器设好编码避免混用。Keil默认是GBK用VSCode或者CLion的话记得在设置里改。printf重定向到串口也是个高频需求。重写_write或者fputc函数就行但要注意别在中断里调用printf它是阻塞的会拖慢中断响应。int _write(int fd, char *ptr, int len) { HAL_UART_Transmit(huart1, (uint8_t*)ptr, len, HAL_MAX_DELAY); return len; }5.5 几个独家避坑经验第一STM32的JTAG引脚和普通GPIO复用。如果你用了PA13、PA14、PA15、PB3、PB4这些引脚做普通IO一定要先禁用JTAG。否则这些引脚不受控调试半天找不到原因。第二FreeRTOS里写Flash要注意。Flash写入期间CPU会阻塞如果这时候有高优先级任务要跑会出问题。我的做法是把Flash操作放到低优先级任务或者写之前先挂起调度器。第三ROS2的节点命名别用中文或者特殊字符虽然理论上支持但实际用起来各种工具会出幺蛾子。全用英文小写加下划线省心。第四激光雷达的安装高度很讲究。太低会扫到地面反光太高会漏掉矮障碍物。我最后定在离地15厘米兼顾两者。6. 从这台机器延伸出去的学习路径把这台扫地机跑通之后你会发现很多知识是相通的。STM32那套电机控制、编码器采集、串口通信换到两轮差速小车上完全一样。ROS2那套话题、TF、导航栈换到UR5机械臂或者无人车上框架也没变。如果你想继续深入几个方向可以选一是把2D SLAM升级成3D SLAM加深度相机做三维建图二是把导航栈换成更高级的比如加动态避障、多目标点巡航三是把下位机换成无刷电机 FOC驱动提升效率和静音性四是加物联网网关功能让扫地机接入云端做远程监控。我个人在实际操作中的体会是机器人工程最难的不是某个单点技术而是把感知、决策、执行、通信这几层有机地串起来让它们稳定协同工作。这台扫地机就是个很好的练兵场麻雀虽小五脏俱全。你把它吃透了再去看更复杂的机器人系统心里就有底了。最后分享一个小技巧调试的时候一定要做数据记录。串口数据存成CSVROS2的bag包录下来出问题的时候回放分析比现场盯着看效率高十倍。我现在的习惯是每次测试必录bag事后用PlotJuggler一画曲线什么问题都藏不住。