ARTICLE DETAIL

资讯详情

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

基于STM32最小系统板打造扫地机器人:状态机与传感器融合实战

基于STM32最小系统板打造扫地机器人:状态机与传感器融合实战 简介这是一份基于STM32最小系统板的扫地机器人完整项目方案面向电子、嵌入式方向的学生与开发者尤其适合毕业设计、期末大作业和课程设计场景。项目实现了自动清扫、超声波避障、自动回充、自适应巡航及覆盖清扫等核心功能代码包含详细注释新手也能快速读懂并部署运行。资源包共153个文件压缩后约5.1MB以h头文件、c源代码文件为主辅以uvprojx/uvoptx等Keil工程配置、s汇编启动文件以及crf编译中间文件目录结构清晰便于查阅和二次开发。核心代码覆盖STM32外设驱动、定时器PWM控制、ADC采样等知识模块可直接导入Keil编译烧录。目前已有562人浏览学习项目经作者本人手打并获得98分的高分认定导师认可度较高。下载后简单配置硬件即可使用也可作为嵌入式综合开发的参考模板帮助理解机器人导航、避障和电源管理的完整实现思路。1. 一块最小系统板能把扫地机器人做到什么程度一块几十元的蓝板 STM32F103C8T6 最小系统板不画底板、不重做 PCB直接飞线就能搭出一台带自动清扫、超声波避障、自动回充、自适应巡航和覆盖清扫的扫地机器人。这个结论听起来反直觉但实际做下来会发现这些功能没有一个是靠算力堆出来的全部依赖状态机的合理划分和传感器数据的正确融合。STM32 在这个项目里更像一个调度核心而不是计算核心主要工作是把电机转速、超声波距离、充电电流这些信号按时序处理干净。做这类项目的难点通常不在硬件本身而在于五个功能互相穿插时的任务切换——正在弓字形巡扫时突然遇到障碍要避让、低电量时要从任意位置回到充电座、回充对接失败后还要重新巡航。把这些状态切换理顺比把某一项功能做完美更关键。适合的人群主要是做毕设或课设的学生以及想从点灯进阶到完整机电项目的嵌入式开发。读完你会清楚每个模块该怎么选型、参数怎么设、联调时优先看哪个信号。2. 最小系统板选型与硬件搭建先定 IO 和电源再写代码2.1 STM32F103C8T6 够用的边界在哪里在 STM32 系列里做扫地机器人最常见的选择是 STM32F103C8T6 最小系统板。72MHz 主频、64KB Flash、20KB SRAM不带地图和视觉可以跑得很轻松。热词里出现的 STM32F407VGT6 最小系统板是另一个量级的选择168MHz 主频配 512KB Flash适合跑 FreeRTOS 加灰度传感器栅格地图融合的复杂方案。但纯超声波避障加红外回充的扫地机F103C8T6 的定时器资源和 IO 数量完全覆盖多花成本买 F407 反而要处理更复杂的电源和引脚映射。选型时还要注意最小系统板的实际差异。刷机用 ST-LINKSWD 接口占用 PA13/PA14写代码时这两个引脚不要分配外设。晶振和 BOOT 配置基本不用动但 3.3V 和 GND 引脚要确认清楚——便宜的蓝色板子丝印偶尔会标反上电前用万用表量一遍比什么都有用。2.2 电机驱动、编码器与超声波传感器的引脚分配扫地机器人本体一般用两轮差速底盘两个直流减速电机配霍尔编码器前侧或斜前方装 2 到 3 个 HC-SR04 超声波传感器。电机驱动选 TB6612 而不用 L298N原因在于 TB6612 的 MOSFET 导通压降小4 节镍氢电池或 2 节 18650 串联供电时能留更多电压给电机。每路电机需要 3 个控制信号两个方向脚加一个 PWM 速度脚。功能模块信号STM32 引脚定时器/外设说明左电机 TB6612AIN1/AIN2PB12/PB13GPIO 输出控制正反转左电机 TB6612PWMAPA0TIM2_CH1PWM 调速默认 20kHz右电机 TB6612BIN1/BIN2PB14/PB15GPIO 输出控制正反转右电机 TB6612PWMBPA1TIM2_CH2PWM 调速左电机编码器A/B 相PB6/PB7TIM4_CH1/CH2编码器模式直读转速右电机编码器A/B 相PB8/PB9TIM3_CH1/CH2编码器模式超声波 1前Trig/EchoPB0/PB1GPIO 输出/输入捕获测距 2cm-400cm超声波 2左前Trig/EchoPB10/PB11GPIO 输出/输入捕获避障扫描用电压检测ADCPA4ADC1_IN4分压后采样电池电压引脚分配的一条实用原则是PWM 和编码器必须绑在硬件定时器上GPIO 模拟 PWM 会占死 CPU影响超声波测距的时序。上面的表里 TIM2 做电机 PWMTIM4/TIM3 做编码器接口三者互不冲突。超声波 Echo 引脚如果不用输入捕获改成外部中断加定时器计时也可以但输入捕获更省心不用在中断里反复翻转引脚。2.3 电源方案与电压检测的分压计算电源是整个项目里最容易埋雷的部分。电机堵转瞬间电流可能冲到 1A 以上如果逻辑供电和电机供电共用一路单片机会在堵转瞬间复位。常见做法是两节 18650 串联标称 7.4V直接给 TB6612 的 VM 供电再通过一颗 AMS1117-3.3 稳压给 STM32 和传感器单独供电电机和逻辑地在电池负极处单点汇合。电压检测用于低电量判断不能直接接到 PA4要用电阻分压。取 R110K、R24.7K电池满电 8.4V 时分压输出 8.4×4.7/(104.7)≈2.87V低于 3.3V 参考电压。ADC 采到的原始值换算成实际电压的公式// adc_value 是 12 位 ADC 的原始值VREF3.3V float Vbat; Vbat (float)adc_value / 4095.0f * 3.3f; // 分压点电压 Vbat Vbat * (10.0f 4.7f) / 4.7f; // 还原电池端真实电压 // 电压低于 6.6V 时切换回充状态机 if (Vbat 6.6f) { robot_state STATE_RETURN_DOCK; }这里的关键点在于分压电阻的精度直接决定回充触发电压的准确性建议用 1% 精度的电阻。另外采样时刻要避开电机启动瞬间软件里对连续 5 次采样取均值间隔 10ms 一次滤掉 PWM 开关造成的毛刺。镍氢电池方案则要重新计算分压比因为满电电压只有 5.2V 左右。3. 两轮差速运动控制与超声波避障让车先走直再谈避障3.1 差速模型的速度解算与转向策略扫地机器人底盘大多是两轮差速结构控制核心是左右轮速度的分配。线速度 v 和角速度 ω 与左右轮轮速 vL、vR 的关系是v(vLvR)/2ω(vR−vL)/L其中 L 是轮距。实际写代码时不需要用这个公式反推每个动作的精确速度更实用的做法是直接定义动作原语直行是 vLvR设定速度原地左转是 vL−vR前进左转是 vL0.3v、vRv。// 动作原语定义speed 是单位 mm/s转向半径由左右轮速差决定 void motion_go(float speed) { set_motor(LEFT, speed); set_motor(RIGHT, speed); } void motion_rotate(float angle) { // angle0 逆时针0 顺时针原地旋转时两轮速度相反 set_motor(LEFT, -ROTATE_SPEED); set_motor(RIGHT, ROTATE_SPEED); // 旋转角度由编码器累计换算轮距 L160mm转一圈周长π*L // 目标累计编码器脉冲 angle/360 * π * L / (轮周长/减速比/编码器线数) } void motion_stop(void) { set_motor(LEFT, 0); set_motor(RIGHT, 0); }motion_rotate 的注释里提到编码器脉冲换算这个参数每个底盘都不样。霍尔编码器常见配置是电机减速比 30:1、编码器线数 13那么电机轴转一圈输出 13×2AB 相×30780 个脉冲。轮径 65mm 的轮子走一毫米大约对应 780/(π×65)≈3.8 个脉冲。先把实测的脉冲数标定出来再写转向逻辑否则角度会越偏越多。3.2 编码器闭环速度 PID 的调参顺序开环 PWM 调速的问题很明显——电池电压从 8.4V 掉到 7.2V 时同样占空比下电机转速会掉百分之十几直线就走歪。解决方式是加编码器 PID 闭环控制周期取 20ms。增量式 PID 的计算量小适合 F103C8T6 这种资源紧张的场景typedef struct { float kp, ki, kd; float err_last, err_prev; float out; } pid_t; float pid_update(pid_t *pid, float target, float actual) { float err target - actual; float p_out pid-kp * err; float i_out pid-ki * (err pid-err_last pid-err_prev); // 实际实现里积分项单独累加这里用简化的增量形式 float d_out pid-kd * (err - pid-err_last); float inc p_out i_out d_out; // 输出限幅防止 PWM 饱和后恢复慢 if (pid-out inc 999) pid-out 999; else if (pid-out inc 0) pid-out 0; else pid-out inc; pid-err_prev pid-err_last; pid-err_last err; return pid-out; }PID 参数不要一套抄完就完事按 Kp、Ki、Kd 的顺序逐个试。先把 Ki 和 Kd 设 0Kp 从 0.5 开始加观察电机是否能快速响应目标转速且不振荡。响应慢就加 Kp出现持续振荡就减 Kp。然后再加 Ki 消除稳态误差最后用很小的 Kd 抑制超调。常见问题是积分项累积过大导致 PWM 饱和堵转解除后电机猛冲限幅必须加。串口调试 PID 参数时把目标转速和实际转速都打印出来跑一段直线看两条曲线贴合程度。3.3 超声波避障状态机多方向扫测代替单点测距HC-SR04 单次测距的逻辑是拉高 Trig 10us 以上然后测量 Echo 高电平持续时间距离等于时间除以 58单位厘米。但扫地机器人用的超声波不能只测正前方否则侧向的障碍物会直接撞上。常见方案是前向和左前两个超声波交替采样避障时先减速然后原地旋转探测两侧空间的可用距离选择空旷的一侧转向。// 一次测距返回厘米超时返回 -1 int16_t us_distance(sonar_t *s) { HAL_GPIO_WritePin(s-trig_port, s-trig_pin, GPIO_PIN_SET); delay_us(12); HAL_GPIO_WritePin(s-trig_port, s-trig_pin, GPIO_PIN_RESET); // 用输入捕获测量 Echo 脉宽超时 30ms 认为无回波 if (s-echo_timeout) return -1; return s-echo_width_us / 58.0f; } // 避障动作选择前方障碍 25cm 时比较左前和右前可用距离 void avoid_obstacle(void) { motion_stop(); int16_t left_dist sonar_measure(LEFT_FRONT); int16_t right_dist sonar_measure(RIGHT_FRONT); if (left_dist right_dist) { motion_rotate(90); // 空地大的一侧优先 } else { motion_rotate(-90); } motion_go(CRUISE_SPEED_LOW); }注意一个细节HC-SR04 的 Echo 引脚不能接在普通 GPIO 上干等干等会卡死主循环。输入捕获配合定时器是一劳永逸的方案Echo 上升沿和下降沿都触发捕获差值直接读出脉宽。实测中超声波在软性障碍物上经常测不到回波返回 -1 要按无障碍处理否则机器人会对窗帘这类障碍物产生迷之转向。还有一种情况是超声波串扰两个探头同时测距会互相干扰软件上错开 30ms 触发即可。4. 覆盖清扫与自适应巡航算法小内存也能做栅格覆盖4.1 弓字形覆盖从行为模式到栅格标记扫地机器人能不能扫干净核心指标是覆盖率。随机碰撞式清扫实现最简单但重复路径多看起来在工作实际效率不高。弓字形覆盖是一种更实用的路线策略机器人沿直线清扫碰到边界后原地旋转 180 度侧移一个清扫宽度继续反向清扫如此往复覆盖整个区域。STM32F103C8T6 没有 IMU航向角靠编码器积分得出长时间运行会积累误差。更可靠的做法是不依赖绝对坐标只在地图上记录相对位置。把地图划分成 30×20 的栅格每个格子约 10cm×10cm用一个 uint32_t 数组就能存下 960 个栅格的状态30*20600 位一个 32 位数组就够了#define GRID_W 30 #define GRID_H 20 uint32_t grid_map[(GRID_W * GRID_H 31) / 32]; void grid_mark(int x, int y) { if (x 0 || x GRID_W || y 0 || y GRID_H) return; int idx y * GRID_W x; grid_map[idx / 32] | (1u (idx % 32)); } int grid_is_marked(int x, int y) { if (x 0 || x GRID_W || y 0 || y GRID_H) return 1; // 边界视为已覆盖 int idx y * GRID_W x; return (grid_map[idx / 32] (idx % 32)) 1u; }坐标更新用编码器里程计累积每个控制周期根据左右轮脉冲数计算位移和转角更新当前栅格坐标并标记。弓字形巡扫时每条直线的目标长度由障碍物位置决定当超声波在前方 X 距离处探测到边界时本地图当前行结束机器人转向并进入下一行侧移量取清扫宽度的一半到三分之二保证相邻两行之间有重叠覆盖。4.2 自适应巡航速度调度按障碍密度调整功率自适应巡航可以理解为根据当前环境的障碍物密度动态调节清扫速度开阔区域高速巡航拥挤区域低速慢扫。速度的反馈信号直接取最近一段时间内的避障次数。维护一个环形计数器记录过去 60 秒内的避障触发次数超过阈值就降挡长时间没有避障则升挡。typedef enum { SPEED_LOW 10, SPEED_MED 18, SPEED_HIGH 26 } cruise_speed_t; cruise_speed_t g_speed SPEED_MED; void cruise_tune_speed(uint8_t avoid_count_1min) { if (avoid_count_1min 6) { g_speed SPEED_LOW; // 狭窄区域低速提高避障成功率 } else if (avoid_count_1min 2) { g_speed SPEED_HIGH; // 开阔区域高速提升单位时间覆盖面积 } else { g_speed SPEED_MED; // 默认中等速度 } // 速度切换时建议做斜坡处理每 100ms 调整一次避免电机电流突变 motion_set_target_speed(g_speed); }速度切换的斜坡很重要。如果直接从 26mm/s 跳到 10mm/s电机的 PWM 突变会让机身顿挫编码器计数瞬间丢步里程积分也跟着出错。常见做法是目标速度改变时每次控制周期只允许调整 PWM 比较值不超过 30让加减速平滑。低速挡还应该配合提高超声波避障的触发距离因为速度越低传感器采样间隔越短反应距离可以缩短到 15cm 而不会撞上。4.3 清扫电量预算与结束条件覆盖清扫不是无限跑下去的要预留足够的电量给回充过程。电压采样值降到一个阈值比如 6.8V时先进入返航状态如果继续降到 6.4V则强制停止清扫直接断电保护电池。除此之外清扫时间上限用 30 分钟比较合适——按正常清扫速度30 分钟覆盖一个十平方米的房间已经足够。时间上限的作用是兜底防止机器在复杂地形里来回绕圈浪费电量。// 主循环中的清扫结束判断优先级从高到低 if (Vbat 6.4f) { // 严重低电立刻停车 motion_stop(); enter_state(STATE_POWER_OFF); } else if (Vbat 6.8f) { // 低电进入回充 enter_state(STATE_RETURN_DOCK); } else if (sweep_time SWEEP_MAX_30MIN) { enter_state(STATE_RETURN_DOCK); }覆盖清扫的完成判断不依赖时间而是看栅格地图连续 20 秒没有出现新的未覆盖栅格且当前行的障碍物已经触发过两次转向就认为这一区域清扫完毕。要注意判断条件里必须有一个时间下限否则机器人刚启动时地图全是空的任何一瞬间都满足“无新增栅格”的条件。5. 自动回充实现与整机调试技巧用充电电流锁定对接成功5.1 红外引导对接与状态机自动回充的常见方案是充电座发射两束红外光机器人左右两侧各装一个红外接收管根据两路信号的强弱差调整航向差额缩小说明正对充电座。这个过程的本质是一个比例引导差值大时转大角度差值小时微调直行。typedef enum { DOCK_SEARCH, // 原地旋转找红外信号 DOCK_APPROACH, // 对准基座缓慢前进 DOCK_CONTACT, // 极片接触等待充电电流 DOCK_CHARGING // 充电中电压回升后继续 } dock_state_t; void dock_control(void) { switch (g_dock_state) { case DOCK_SEARCH: motion_rotate(10); // 持续慢转直到任一接收管有信号 if (ir_left_detect() || ir_right_detect()) g_dock_state DOCK_APPROACH; break; case DOCK_APPROACH: int diff ir_left_value() - ir_right_value(); // diff 越大表示偏左向右修正 set_motor(LEFT, DOCK_SPEED diff * 0.8f); set_motor(RIGHT, DOCK_SPEED - diff * 0.8f); if (charge_current_detect()) g_dock_state DOCK_CONTACT; break; case DOCK_CONTACT: motion_stop(); if (charge_current_detect()) g_dock_state DOCK_CHARGING; // 电流连续存在 1 秒才算对接成功 else g_dock_state DOCK_SEARCH; // 虚接抖动重新搜索 break; } }充电电流检测不能只靠电压变化判断。接触瞬间电池电压会被充电座拉高但没接触好的话电压几秒后就回落。更可靠的判断是串在充电回路里的采样电阻两端电压用运放放大后送到 STM32 的 ADC 引脚。连续 1 秒检测到充电电流才切换为充电状态能有效过滤极片弹跳引起的误判。对接失败时不要原地重试后退一段距离再重新搜索红外信号同时更新地图上充电座的方位信息下次回充优先朝那个方向走。5.2 整机验证的关键串口日志五个功能全部接好后验证时最容易出问题的是状态机跳变太快肉眼跟不上。建议用串口把状态切换和设备状态按固定格式打出来波特率 11520010Hz 频率输出一行。串口打印用的是编程中的标准方法通过 UART 发送格式化字符串PC 端用串口调试助手或 Vofa 观看波形。// 主循环末尾打印状态格式为 CSV 方便画图 printf(STATE:%d,V:%d.%dV,L:%d,R:%d,US:%d,OC:%d\r\n, g_robot_state, Vbat_int, Vbat_frac, encoder_left_speed, encoder_right_speed, sonar_front_cm, grid_covered_count);对照日志检查三个指标直线行驶时左右编码器速度差是否小于 5%超声波触发避障到电机停转的响应时间是否小于 100ms每次回充对接时 DOCK_SEARCH 到 DOCK_CONTACT 的停留时间是否稳定。延时函数卡死的问题也经常出现在这个阶段最常见原因是超声波测距里用了阻塞式 delay_us 等待 Echo 信号导致定时器中断无法响应。排查时优先怀疑所有阻塞延时全部改成非阻塞的超时检查。最后一类隐藏问题出现在 ST-LINK 烧录后的首次运行时各模块共用一个 3.3V 电源电流超出 LDO 能力会导致芯片反复复位用示波器看 3.3V 引脚波形能立刻确认。本文还有配套的精品资源点击获取
返回列表