简介:一份基于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寄存器决定。假设ARR=49,CCR=40,占空比就是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秒后,重新用超声波测一次距离,用火焰传感器或光敏电阻采集一次环境亮度,判断火源是否真的熄灭。这个小细节在课程设计和毕业设计答辩中非常加分,因为评委看到的不是“风扇转了就当灭了”,而是一个有闭环反馈的完整灭火过程。
本文还有配套的精品资源,点击获取