资讯动态

基于STM32的智能灭火小车:超声波测距与状态机控制实践

发布时间:2026/9/9 0:08:24 来源:尧图企业网站定制
简介一份基于STM32的超声波智能灭火小车完整工程包面向嵌入式系统学习者、电子设计竞赛参赛者及机器人爱好者用于快速掌握智能车自主避障与火焰检测灭火的实现方法。项目以STM32F103C8T6为主控采用超声波传感器感知障碍物并规划绕过路径搭配L293D电机驱动模块控制运动方向通过火焰传感器识别火源整体方案可扩展至仓库自主安全巡逻等场景。压缩包约47.02MB内部文件涵盖程序源文件、原理图PCB、参考论文及参考程序流程图等类型便于对照硬件设计、代码逻辑与论文思路进行系统学习。已有1576人学习下载是一份难度适中、链路完整的实战参考资料。通过阅读程序与流程图可理清多传感器融合调度流程参考原理图能帮助完成硬件焊接与排错论文部分则提供了项目背景与设计论证适合用于课程设计、毕业设计或竞赛作品的知识储备与二次开发。1. 整体方案怎么做主控、驱动与执行器的选型思路1.1 为什么主控选STM32F103而不是Arduino或51拿到“基于STM32超声波智能灭火小车”这个项目包的时候我第一反应不是急着解压代码而是先想清楚一件事为什么这套方案遍地都是却依然值得自己搭一遍。答案其实很实在——STM32F103C8T6这颗芯片几乎是大学生做课程设计和毕业设计时性价比最高的选择。先说资源。F103C8T6有64KB Flash、20KB RAM主频72MHz片上外设包括定时器、ADC、USART、I2C、SPI以及多路PWM输出。做一台灭火小车要用到的资源无非是一路超声波测距GPIO定时器输入捕获、两路电机PWM、一路风扇PWM可能再加一个火焰传感器ADC采集。这些对F103来说完全够用甚至还有富余。再说生态。你打开任何一个技术社区搜“STM32 灭火小车”能找到几十篇风格不同的实现。芯片手册、标准库源码、HAL库例程、Keil5工程模板全都公开遇到问题基本一搜就有答案。这一点比Arduino强在“更接近工业开发”比51强在“性能足够、外设丰富”。还有一个很多人忽略的选型理由手感和简历。用Arduino做灭火小车代码可能一天就写完了但面试官看了基本没什么感觉。STM32版本不仅能讲清楚GPIO、定时器、中断、PWM这些嵌入式基本功还能聊一聊电机驱动、传感器滤波、低功耗设计这些才是毕业设计和招聘需求里真正看中的东西。1.2 底盘与电机驱动TB6612和L298N到底选哪个小车底盘我建议直接用两轮差速底盘加万向轮也就是最常见的“两轮差速小车”结构。左右两个直流减速电机分别驱动靠两侧转速差实现转向转弯半径小控制逻辑直观。热词里正好有“两轮差速小车stm32控制”说明这条路已经被很多人验证过了。电机驱动模块的选择是个容易纠结的点。TB6612FNG和L298N是两款最常用的驱动板我的推荐是TB6612理由有三条。第一效率。TB6612是MOSFET驱动的内部导通压降小发热明显低于L298N。L298N用的是三极管压降大同样电压下电机实际获得的电压偏低跑起来没劲还容易烫手。灭火小车要带着风扇和电池跑能量利用效率很关键。第二体积和接线。TB6612模块只有拇指大小引脚间距和排针标准直接插面包板或者杜邦线连接都很方便。L298N那个大蓝板子不仅要占很多底盘空间还经常需要外接5V供电给逻辑电路接线一多出问题的概率就高。第三PWM频率适应性。TB6612对PWM频率不敏感10kHz以下都能正常工作。L298N虽然也能接受PWM调速但频率过高时开关损耗变大电机反而嗡嗡响、转速不线性。当然L298N也不是一无是处。如果你手头只有L298N或者需要驱动电流特别大的电机比如带负载重一点的底盘它那种简单粗暴的H桥结构反而更耐用。但单纯做灭火小车这种轻负载场景TB6612是更合适的答案。1.3 超声波测距和灭火风扇的执行链路设计这套小车的核心逻辑一句话就能说清楚用超声波测距判断小车与火源的位置距离合适就停车然后打开风扇把火吹灭。所以执行链路由三个环节组成传感器感知距离、主控决策动作、执行器完成灭火。超声波模块选HC-SR04这个基本没有争议。价格便宜测距范围2cm到400cm精度在±3mm左右对室内灭火场景完全够用。模块上有四个引脚VCC、GND、Trig、Echo其中Trig是输入触发脚Echo是回声输出脚。风扇执行模块我见过两种做法一种是直接用三极管或MOS管驱动小风扇另一种是再挂一个继电器控制220V大风扇。考虑到这是课程设计安全第一我建议用5V直流涡轮风扇加上一个N-MOS管比如AO3400实现PWM调速。这样既安全还能通过改变占空比调节风力大小效果比继电器开关式好很多。灭火方式还有另一种方案是“喷水”但喷水涉及水泵、水箱、防水处理复杂度和故障率都高一大截。用风扇吹灭蜡烛是绝大多数智能灭火小车的默认做法——蜡烛火焰容易吹灭演示效果好评委和老师也认可。所以看到压缩包名字里的“灭火小车”三个字基本就能猜到风扇是执行器了。2. 超声波测距与灭火状态机别急着写代码2.1 HC-SR04测距原理以及那个神秘的“58”先把超声波测距的原理掰开揉碎讲清楚因为后面所有决策都依赖这个距离值。HC-SR04的工作流程是这样的主控给Trig引脚一个至少10us的高电平脉冲模块内部就会发出8个40kHz的超声波脉冲同时把Echo引脚拉高。超声波遇到障碍物后会反射回来模块收到回波后把Echo引脚拉低。Echo高电平持续的时间就是超声波从发射到接收的总飞行时间。有了时间距离就好算了。声速在空气中大约是340m/s也就是0.034cm/us。假设Echo高电平持续时间为T单位us那么超声波走的总路程是 0.034 * T cm但这是往返距离实际距离要除以2。所以公式是距离(cm) T * 0.034 / 2 T * 0.017。但更常见的写法是“距离 T / 58”这个58是怎么来的因为用整数运算更高效1 / 0.017 ≈ 58.82约等于58。也就是说Echo高电平持续58us就代表障碍物距离1cm。这个近似的误差在厘米级别用来判断灭火距离完全够用。有一点必须提醒声速会随温度变化。0℃时声速约331m/s而常温25℃时约346m/s算下来误差能有4%左右。如果只做灭火小车这个误差不影响判断但如果你以后要用超声波做更精确的测量可以考虑加一个温度传感器修正声速这也是答辩时一个很好的加分项。2.2 灭火逻辑状态机距离驱动的“决策树”写代码之前先把决策逻辑画成状态图。灭火小车最核心的状态有三个搜索火源、接近火源、执行灭火。搜索状态小车在场地内前进超声波持续测距火焰传感器或遥控指令确认火源存在后进入接近状态。接近状态小车一边前进一边测距距离大于某个阈值比如30cm就继续走距离小于等于阈值就停车进入灭火状态。灭火状态小车停稳风扇以较大占空比启动持续吹3到5秒然后重新测距确认火源是否熄灭。如果熄灭则停止一切动作如果还在就再吹一轮。这个状态机的好处是逻辑清晰、便于调试。你不需要把“测距、判断、决策、执行”揉在一个几百行的while循环里而是拆成可独立测试的小模块。实际开发中我发现很多新手写灭火小车代码失败的原因不是不会测距也不是不会驱动电机而是把逻辑全写在了一个循环里状态一多就乱套。用状态机实现后每个分支都是独立逻辑出现Bug时可以用串口打印当前状态值一眼就能定位到问题环节。2.3 PWM控制风扇转速参数这样调更合理风扇转速控制要用PWM。STM32的定时器可以输出多路PWM比如TIM2的CH1和CH2分别给左右电机TIM3的CH1给风扇。配置PWM的时候顺便把定时器分频和重载值算清楚。以F103为例TIM3挂在APB1总线上时钟为72MHz。如果要产生20kHz的PWM频率预分频PSC设为72-1这样计数时钟变成1MHz重载值ARR设为50-1那么PWM频率就是1MHz / 50 20kHz。20kHz的PWM驱动风扇正好听不到明显噪声电机也不会因为频率太低而抖动。占空比由CCR寄存器决定。假设ARR49CCR40占空比就是40/49≈82%风扇转速最高档。我实际测试下来对普通5V涡轮风扇占空比超过70%之后风量已经足够吹灭30cm外的蜡烛没必要一直满转。低一点还能省电、减少噪音。电机PWM频率同理但通常用10kHz左右。频率太低电机会出现“咔哒咔哒”的步进感太高则驱动芯片发热。10kHz是直流减速电机比较均衡的工作点。// 以TIM3 CH1输出PWM到风扇为例标准库写法 void FAN_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 50 - 1; // ARR TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // PSC TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, TIM_OCInitStructure); TIM_Cmd(TIM3, ENABLE); }3. 实操记录从建工程到小车跑通3.1 Keil5环境搭建和芯片包安装避坑拿到压缩包之后第一步不是解压代码而是先把开发环境理顺。这个项目大概率是用Keil5开发搭配STM32F1系列芯片包和标准外设库。如果你电脑上还没有Keil5装完主程序后第一件事是安装器件支持包也就是“STM32F1xx Device Family Pack”。很多人明明双击了项目却能打开编译的时候却报“Target not created”或者找不到芯片型号基本都是因为没装这个包。安装方法不算复杂打开Keil5点击“Pack Installer”图标在搜索框输入“STM32F1”找到对应系列点击Install就行。芯片包装好之后新建工程还有几个容易踩的坑。一个是启动文件选择F103C8T6属于高密度HD小容量芯片连接文件选择stm32f10x_hd.s但如果你用的是F103C6或F103R8就要对应选择md或ld选错直接编译报错。另一个坑是宏定义。在C/C选项卡里的Define一栏必须手动填写STM32F10X_HD否则标准库里的芯片型号判断会出错导致外设时钟初始化失败。这个宏和启动文件必须配套而且是手动加Keil不会自动帮你补。最后是下载器配置。用ST-Link的话在Options for Target - Debug里选ST-Link Debugger然后点Settings把Flash Download里的Reset and Run勾上。不勾这个的话每次烧录完程序不会自动运行你还得手动按一下复位键调试起来特别烦。3.2 核心代码骨架状态机的一版可运行实现整个工程文件很多但核心逻辑可以浓缩在主函数和几个外设驱动文件里。下面是我简化过的可运行骨架保留了最重要的状态机结构和关键函数调用。#include stm32f10x.h #include delay.h #include usart.h #include motor.h #include fan.h #include hc_sr04.h #define SEARCH_DISTANCE 30 // 距离小于30cm视为到达灭火点 uint16_t g_distance 0; uint8_t g_fire_exist 0; enum { STATE_SEARCH 0, STATE_APPROACH, STATE_EXTINGUISH, STATE_STOP } car_state; void Car_Stop(void) { Motor_SetSpeed(0, 0); FAN_SetDuty(0); } void Car_Forward(uint8_t speed) { Motor_SetSpeed(speed, speed); FAN_SetDuty(0); } void Car_Extinguish(void) { Motor_SetSpeed(0, 0); FAN_SetDuty(85); // 大部分情况下85%占空比足够吹灭30cm外蜡烛 } int main(void) { delay_init(); USART1_Init(115200); Motor_Init(); FAN_Init(); HC_SR04_Init(); g_fire_exist 1; // 实际项目中由火焰传感器或上位机指令更新 car_state STATE_SEARCH; while (1) { g_distance HC_SR04_GetDistance(); switch (car_state) { case STATE_SEARCH: if (g_fire_exist) { car_state STATE_APPROACH; } break; case STATE_APPROACH: if (g_distance SEARCH_DISTANCE) { Car_Forward(50); } else { car_state STATE_EXTINGUISH; } break; case STATE_EXTINGUISH: Car_Extinguish(); delay_ms(3000); g_fire_exist 0; car_state STATE_STOP; break; case STATE_STOP: Car_Stop(); break; default: car_state STATE_STOP; break; } printf(distance%dcm, state%d\r\n, g_distance, car_state); delay_ms(50); } }这份代码把上一节说到的状态机落到了实处。关键是HC_SR04_GetDistance()这个函数没有被阻塞得太久它是通过定时器输入捕获来计算Echo高电平时间的测距一次大约几十毫秒不影响主循环对电机的实时响应。实际测试中我用串口把距离值和状态值同时打印出来能看到小车从“搜索”切换到“接近”再切换到“灭火”的完整过程。这样调试验证状态机特别直观哪里不对一眼就能看出来。3.3 烧录与串口调试实录一边看距离一边调动作真机调试那天我先把小车架起来让轮子悬空只通电不落地这样可以安全地测试电机和风扇动作。然后接好ST-Link打开串口助手波特率设115200。第一次烧录后串口输出显示距离值一直在跳从10cm到80cm乱飘。我第一反应是超声波模块的供电问题——HC-SR04虽然号称5V供电但如果直接接在STM32开发板的5V引脚上电机一转电源波动一大Echo回波就会出错。解决办法是给超声波模块单独用一个AMS1117-5V稳压模块供电或者至少把模块的地线和小车电源地接在一起减小环路干扰。稳定之后我把手放在超声波探头前方30cm处串口输出稳定显示在29cm到31cm之间。这时候我把灭火距离阈值设成30cm手慢慢靠近小车从前进状态切到灭火状态风扇全速转起来整个过程一气呵成。有一个细节特别值得说因为delay_ms(3000)在灭火状态里是阻塞的如果你在串口助手里连续发指令会看到状态机卡在灭火状态3秒不动。这在演示时是没问题的但如果以后要加入遥控中断或者传感器中断就要把这种长延时改成非阻塞计时否则中断响应会被耽误。4. 常见问题与排查技巧实录4.1 下载时报“no stm32 target found”怎么办玩STM32的人十有八九都见过这句话error: no stm32 target found! if your product embeds debug authentication, pl...。烧录的时候弹出来第一反应往往是芯片坏了其实大部分情况不是。先说最常见的几种原因。排第一的是连接线问题。ST-Link和开发板之间用的是SWD接口四根线分别是SWDIO、SWCLK、GND、3.3V。很多人用杜邦线连接稍微松动一下就会报找不到目标。处理办法是把杜邦线换成短一点的、质量好一点的或者干脆用排线固定好。我曾经因为一根杜邦线内部接触不良排查了半小时最后换线直接好。排第二的是目标板供电不足。ST-Link的3.3V输出电流很小如果板子上还有超声波模块和电机驱动在抢电你会发现单独供电能识别一接外设就报错。解决办法是开发板用USB或者电池单独供电ST-Link只负责下载和调试不要指望ST-Link给整个系统供电。排第三的是SWD引脚被程序禁用。某些工程为了省引脚会把PA13、PA14复用成普通GPIO这恰好是SWD下载引脚导致第二次烧录就报错。遇到这种情况可以按住开发板复位键在Keil点击下载的瞬间松开复位利用“下载前复位”的时间窗口抢烧一版正常的程序进去。如果还不行就把BOOT0引脚拉高进入ISP模式用串口把Flash擦除再把BOOT0拉低恢复正常。debug authentication这句提示现在越来越常见主要是新款芯片加了调试保护功能。F103C8T6基本没有这个门禁如果遇到基本就是连接问题。4.2 超声波数据乱跳、delay死循环的解决思路超声波测距数据乱跳是高频问题尤其当电机和风扇都在转的时候。现象是串口打印的距离值偶尔冒出一个大数比如障碍物明明30cm突然跳成200cm或者偶尔变成0。第一个排查方向是电源。电机启动瞬间电流很大会拉低整个电源电压超声波模块的Echo信号就可能失真。解决办法是在电源正负极并联一个大电解电容470uF或1000uF再并联一个0.1uF的陶瓷电容滤高频给电机驱动供电传感器和主控尽量用单独稳压出来的电源轨。第二个排查方向是测距逻辑。HC-SR04如果没收到回波Echo引脚会一直保持高电平直到超时约200ms才拉低。如果你用的延时方式是while循环等Echo变低一旦没抓到回波就会卡死这就是热词里“stm32延时函数delay卡死”的典型场景。正确做法是给Echo等待加一个超时判断比如用定时器计数超过30ms还没有回波就认为测距失败返回上次的有效值或者一个大数。我实测下来对超声波做“连续采样3次取中值”的滤波数据稳定性提升非常明显。中值滤波的好处是能剔除突然冒出的异常值比如一个有效的30cm一个乱跳的200cm一个正常的31cm取中值就是31cm稳得很。4.3 小车走不直、风扇一转就复位的几个原因小车走不直是两轮差速底盘的老问题。原因可能是两侧电机转速不一致也可能是摩擦力不同。排查方式很简单给两个电机设相同的PWM占空比悬空测试看两个轮子的空转转速是否一致。如果明显不一致先检查左右电机驱动信号有没有接反再检查两个电机的减速比是否一样。还有一种情况是代码的问题。如果你在转弯或者调占空比的时候用了直接给CCR赋值的方式两个电机的死区电压不一样会导致低速时一边转一边不转。我的习惯是给电机调速加一个最小启动占空比比如30%小于这个值直接给0避免电机在临界区“哆嗦”。风扇一转系统就复位这个故障我调试时遇到一次。原因是电池是两节18650串联标称7.4V空载时电压还行但风扇一启动瞬间电流把电压拉到欠压保护值以下主控复位。解决办法是换更大放电倍率的电池或者选择工作电压更宽的主控供电方案。如果手头只有普通电池可以用一个大功率二极管把电机和风扇电源跟主控电源隔离再在主控电源端加大电容储能稳住电压。还有一个容易忽略点风扇属于感性负载关断瞬间会产生反向电动势如果不加续流二极管可能会反向击穿MOS管或者干扰主控。驱动直流风扇时一定要在风扇两端反并联一个1N5819或者SS34肖特基二极管这一步能省很多麻烦。5. 一些个人建议这样调车能少走弯路这个项目我前前后后做了两遍第一遍是为了交差第二遍是真心想把每个模块吃透。两次做下来最大的体会是先分模块调通再整机联调能省至少一半时间。具体做法是先把超声波单独在开发板上跑通用串口打印距离再把电机的正反转和PWM调速跑通最后把风扇驱动跑通。三个模块各自确认无误后再组合成完整逻辑。千万别人还没到齐就先搭好全套车出了问题根本不知道从哪里查起。调试时串口是你的眼睛状态机的每个状态切换都要打印出来。我在做这个项目时吃过“看起来没反应”的亏后来发现是状态一直卡在某个分支没出来。加了几行printf之后问题立刻暴露。最后分享一个答辩能用到的小技巧在灭火状态机里加入“灭火确认”环节——风扇吹完3秒后重新用超声波测一次距离用火焰传感器或光敏电阻采集一次环境亮度判断火源是否真的熄灭。这个小细节在课程设计和毕业设计答辩中非常加分因为评委看到的不是“风扇转了就当灭了”而是一个有闭环反馈的完整灭火过程。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价