简介:面向计算机毕业设计与嵌入式课程设计的模拟消防灭火小车资料包,围绕STM32单片机展开,从红外避障、火焰检测、电机驱动和LCD1602显示等模块入手,给出完整设计方案与实现思路。压缩包共包含1个docx文件,大小约2.75MB,文档以设计论文形式呈现,依次介绍系统总体结构、硬件模块选型、单片机引脚连接、灭火流程及关键代码,并附有调试要点;文档目录完整,从设计任务、功能需求到系统测试均有覆盖,便于边读边实践。目前已有35人浏览学习,适合单片机与嵌入式方向学生作为课程设计或毕业设计参考。借助该文档,可快速复现红外避障遇障停车、检测到火焰后启动灭火的状态逻辑,理解STM32外设配置与工程搭建方法,同时获得论文撰写和排版思路,对完成课设、毕设都有直接帮助。 这种课程设计题目我已经看过不下三十个版本,说句实话,大部分作品只能叫“能动”,离“能灭火”还有一段距离。模拟消防灭火小车看起来结构简单——底盘、传感器、小水泵、单片机,但真要把它做到现场稳定运行,火焰检测的可靠性、运动控制的精准度、电源的稳定性,三样缺一不可。这篇文章就是我完整实现一版模拟消防灭火小车设计(文档+源码)后的复盘,覆盖需求拆解、硬件选型、代码实现和联调排错,适合正在做课设的同学、刚入手嵌入式小车开发的初学者,以及想在答辩和展示时多拿点分的实操党。
1. 拆解需求:灭火小车不是“能动”那么简单
1.1 三个闭环:检测、逼近、灭火
很多人拿到题目第一反应是找底盘、买模块、拼代码。建议反过来,先拆功能。这个项目表面上看是“一辆小车”,本质上是三个串联的闭环:检测闭环负责发现火源,运动闭环负责把车送到合适的灭火位置,执行闭环负责完成灭火动作。三个闭环互相耦合,任何一个没做好,整体效果都上不去。
检测闭环的核心是火焰传感器。常见方案是红外火焰传感器模块,火焰燃烧时释放特定波段的红外辐射,模块里的红外光电管把它变成电信号,再经过比较器输出。这里最容易犯的错误是只看DO数字输出,不看AO模拟量。数字输出的跳变阈值是硬件电位器定的,环境光一变,无火也触发、有火也可能漏掉。很多人的失败在于分不清“检测到火”和“找到火的方位”是两码事,数字输出告诉你有没有火,模拟量加多路布局才能告诉你火在哪一边。
运动闭环就是两个直流电机。别小看这一步,“走直线”这个在桌面上看着简单的动作,真要让小车执行起来,左右轮的制造误差、电池电压波动、地面摩擦系数变化都会让它越走越偏。执行闭环则是水泵加舵机,水泵负责喷水,舵机负责控制喷头的仰角和水平扫射角度。
1.2 把工作流程画成状态机
三个闭环串起来就是一台状态机:搜索状态(原地旋转找火源),逼近状态(顺着火焰传感器信号最强的方向前进),灭火状态(停车、下压喷头、启动水泵扫射),灭火成功后回到待机或继续搜索。
我当时是在白板上先画状态转移图,标清楚每个状态做什么、什么条件跳到什么状态,然后才动手写代码。把跳转条件写在图上,写代码时就不容易遗漏边界情况。比如火源在小车侧后方时,搜索状态到底该往哪个方向转,这种细节在代码里很容易漏,但画在图上藏不住。用状态机的好处是逻辑清晰、容易调试,我最常看到的问题是把所有逻辑散写在主函数里,状态切换条件互相冲突,最后小车行为完全不可预测。
1.3 先定指标再动手
任何设计类项目,动手之前先定指标。我给自己定的是:室内正常照明下,正前方40厘米处的蜡烛火焰3秒内能检测到;从发现火源到小车停在距火焰15~25厘米范围内不超过5秒;水泵启动后10秒内熄灭模拟火源;直行一米偏差不超过5厘米。这些指标未必多先进,但它们直接决定了传感器档位、电机功率和水泵流量。
打个比方,如果指标是“明亮环境下50厘米检测距离”,传感器前面的聚光罩和安装角度就必须认真设计;如果只是“暗室3厘米能检测”,那随便装都能过。指标不同,方案复杂度完全不同。先把指标想清楚,再去画框图和原理图,每一步都有据可依,后面调试也有个可量化的验收标准。
2. 硬件选型:我踩过选型坑之后的最终方案
2.1 主控:为什么用STM32F103C8T6
主控常见三个选择:51、Arduino、STM32。51结构简单、教材多,但ADC和定时器资源紧张,做三路火焰传感器模拟量采集、两路电机PWM加一路舵机PWM,代码会绕得很别扭。Arduino开发快、库全,但如果课设事先没学过Arduino,答辩时很容易被老师追问底层实现。STM32F103C8T6外设资源刚好:多路ADC、多路定时器、足够多的GPIO,资料也丰富。最终我选了它,不是因为性能过剩,而是它一颗芯片就能把传感器、电机、舵机、水泵全管起来,电路干净,逻辑也干净。
| 主控方案 | 上手成本 | 外设资源 | 答辩友好度 | 我的建议 |
|---|---|---|---|---|
| 51单片机 | 低 | 偏少 | 一般 | 想练基础可以用,项目后期比较别扭 |
| Arduino | 很低 | 够用 | 有风险 | 快速验证想法可以,直接交付课设不推荐 |
| STM32F103C8T6 | 中 | 充裕 | 高 | 综合体验最好 |
2.2 火焰传感器的安装布局
火焰传感器模块原理不复杂:红外接收管加比较器,正对火焰时输出翻转。选型注意两点:一是要有AO模拟输出引脚,二是视角不能太宽。数字输出只能告诉你有没有火,模拟输出才能告诉你火偏哪边。我在车头并排装了三路,左侧45度、正前方、右侧45度,覆盖小车前半个圆弧。
这里有个成本几乎为零但效果显著的技巧:给每个传感器前面套一段黑色热缩管或短纸筒,长度15到20毫米,管口稍微向外张开。作用是限制接收视角,让传感器只看正前方一个较小圆锥,减少天花板灯、墙面反光这些非火源红外源的干扰。不改这一下,传感器在室内容易乱报;改完之后误报率下降非常明显。
2.3 电机、水泵与供电:电老虎是水泵
运动部分我用L298N驱动两个带减速箱的直流电机。L298N是老将,压降大但皮实,课设够用;想更高效可以换TB6612,体积更小、压降更低。电机供电用两节18650组成的7.4V电池,逻辑和传感器经5V稳压供电。
最需要单独拎出来说的是水泵。模拟灭火小车常用的3到6V直流小水泵,启动瞬间电流能冲到0.6到0.8A以上。如果水泵的电源是从电机电源直接稳压出来的,泵一启动,整个控制电路电压会被拉低到复位阈值附近,单片机当场重启。解决思路两个:一是把泵的电源与控制电路电源彻底分开,共地但不共电源;二是给泵做PWM软启动,并在电源输入端加1000uF以上大电容。两者配合最稳。MOSFET两端还要并一个续流二极管,泵是感性负载,断电瞬间会产生反向电动势,不加续流二极管,管子容易被击穿,这个二极管可以就近并在水泵两个接线端子间,阴极接电源正极。
| 功能 | 引脚 | 说明 |
|---|---|---|
| 火焰传感器左/中/右 AO | PC0/PC1/PC2 | ADC输入,读取模拟量 |
| L298N IN1~IN4 | PB8~PB11 | 电机方向控制 |
| L298N ENA/ENB | PA0/PA1 | TIM2 CH1/CH2 PWM调速 |
| 水泵MOSFET栅极 | PA2 | TIM2 CH3 PWM软启动 |
| 舵机信号线 | PB6 | TIM4 CH1 PWM |
所有负极端子必须可靠共地。信号地和功率地最后要单点汇合,别顺着电路板绕一大圈再回电源负极,否则电机启停在地线上产生的感应毛刺会让ADC跳动,传感器数据突然变得不可信。
3. 代码落地:ADC滤波、差速转向与主状态机
3.1 三路火焰模拟量的滤波与阈值
再次强调:用ADC读AO模拟量,不要只读DO数字量。数字比较器的阈值是电位器定的,代码里没法随环境光动态调;模拟量读取后可以做滤波、做左右相对判断、做滞回控制。我封装了一个去极值平均滤波函数:
#define FLAME_L_CH 0 #define FLAME_M_CH 1 #define FLAME_R_CH 2 uint16_t read_flame(uint8_t ch) { uint16_t buf[8]; uint16_t tmp; uint32_t sum = 0; for (int i = 0; i < 8; i++) { buf[i] = adc_read(ch); delay_us(200); } // 8次采样排序,去掉最大值和最小值,防毛刺 for (int i = 0; i < 8; i++) { for (int j = i + 1; j < 8; j++) { if (buf[j] < buf[i]) { tmp = buf[i]; buf[i] = buf[j]; buf[j] = tmp; } } } for (int i = 1; i < 7; i++) sum += buf[i]; return (uint16_t)(sum / 6); }火焰信号本身有波动,电机一转干扰更大,不做滤波直接拿去当控制量,小车会像喝醉一样左右乱晃。阈值不要拍脑袋定,在场地分别采集“无火”和“有火”的ADC基线,取中间值再留5%到10%余量。比如无火时3400,有火时降到1200,设1900当检测阈值。注意不同模块的数值方向可能相反,接好线先打印确认再写逻辑。
3.2 电机驱动函数
L298N每个通道三个控制脚:两个方向脚决定正反转,一个使能脚接PWM决定速度。方向脚当普通GPIO,使能脚用定时器PWM。我把左右电机各封装成一个函数,speed为正前进、为负后退、为零刹车:
void motor_left_set(int16_t speed) { if (speed > 0) { GPIO_SetBits(GPIOB, GPIO_Pin_8); GPIO_ResetBits(GPIOB, GPIO_Pin_9); } else if (speed < 0) { GPIO_ResetBits(GPIOB, GPIO_Pin_8); GPIO_SetBits(GPIOB, GPIO_Pin_9); } else { GPIO_ResetBits(GPIOB, GPIO_Pin_8); GPIO_ResetBits(GPIOB, GPIO_Pin_9); } TIM_SetCompare1(TIM2, abs_limit(speed, 1000)); }PWM占空比范围用了0到1000,对应TIM2的ARR设999。为什么不直接用百分比?因为时基寄存器本身就是计数比较用的,直接写比较值最直观,后面做差速控制做整数加法时不会遇到小数精度问题。右电机函数对称,控制PB10/PB11,使用TIM_SetCompare2。
3.3 差速转向:一个P控制器就够用
火在左边时,左传感器读数会比右边更敏感,让左轮慢一点、右轮快一点,小车就会自动转过去。用误差驱动转向:
int16_t err = flame_left - flame_right; int16_t steer = err * KP / 8; int16_t ls = BASE_SPEED + steer; int16_t rs = BASE_SPEED - steer; motor_left_set(ls); motor_right_set(rs);这里的符号必须根据实际接线的传感器方向和ADC数值方向确认。建议上电后用手电筒分别照左右传感器,打印三路ADC看数值变化方向,把正负号对着改,别靠猜。KP值现场调:先把BASE_SPEED固定在600左右,KP从0.6开始加,每次加0.2,观察小车接近火源时方向是否稳定收窄。KP太小,转弯反应慢,路径变成大弧线;KP太大会在火源前左右摆头甚至原地打转。我最终用的KP=1.6,BASE_SPEED=650。
3.4 主状态机:搜索、逼近、灭火串起来
把前面的滤波、电机、转向封装好之后,主循环就是一台状态机:
typedef enum { ST_SEARCH, ST_APPROACH, ST_EXTINGUISH, ST_STOP } CarState; CarState state = ST_SEARCH; while (1) { uint16_t fl = read_flame(FLAME_L_CH); uint16_t fm = read_flame(FLAME_M_CH); uint16_t fr = read_flame(FLAME_R_CH); switch (state) { case ST_SEARCH: // 原地慢转找火源 motor_left_set(300); motor_right_set(-300); if (fl < FLAME_ON || fm < FLAME_ON || fr < FLAME_ON) state = ST_APPROACH; break; case ST_APPROACH: { int16_t err = fl - fr; int16_t steer = err * KP / 8; motor_left_set(BASE_SPEED + steer); motor_right_set(BASE_SPEED - steer); // 前方足够近就停车进入灭火 if (fm < EXTINGUISH_DISTANCE) { motor_left_set(0); motor_right_set(0); state = ST_EXTINGUISH; } break; } case ST_EXTINGUISH: servo_set_angle(ANTI_FIRE_ANGLE); pump_pwm_start(); delay_ms(3000); pump_off(); if (fm > FLAME_OFF_THRESHOLD) { state = ST_STOP; } break; case ST_STOP: default: motor_left_set(0); motor_right_set(0); break; } delay_ms(50); }注意灭火后的火焰熄灭判断要加滞回。EXTINGUISH_DISTANCE原本是一个很小的ADC值,如果火焰熄灭,读数会在短时间内从很小跳回环境值,中间可能横跨判定线,不加滞回状态就会抖来抖去。上面代码用了FLAME_OFF_THRESHOLD,这个值设得比熄灭判定线更靠近环境本底,只有火焰确实消失才切换状态。
演示用的delay_ms(3000)在课设里够用,但更规范的做法是改用定时器或系统Tick做非阻塞计时,这样灭火过程中还能继续响应传感器,万一泵没喷准,也好及时修正。
4. 联调实录:三个让我差点熬夜的坑
4.1 环境光干扰:传感器误报的完整排查链路
第一个坑出现在室内联调。小车在场地里原地搜索时,转到某个朝向就会突然停下、调头朝窗户冲。一开始怀疑程序逻辑写错了,后来用串口把三路ADC实时打印出来,发现对着窗户时读数会掉到和蜡烛火焰差不多的水平。
再往下排查,关掉窗帘后误报消失;把传感器用不透明纸板挡住正面,读数恢复正常;用手电筒照射,同样触发。到这里根因就清楚了:火焰传感器实际上是一个宽谱段红外探测器,只负责感知红外辐射强度,不区分是火焰、太阳还是白炽灯。窗户透进来的太阳光包含大量红外成分,白炽灯、卤素灯同样如此。
修复做了三层。第一层硬件:传感器前加黑色热缩管限制视角。第二层软件:在无火状态下先采集三路基线BASE_L/BASE_M/BASE_R,判定条件从“读数小于固定阈值”改成“读数比基线低超过差值阈值”,环境整体亮度变化时不至于全盘误报。第三层:有条件的话加第四个朝上或朝侧面的传感器当环境参照,用相对差值判断。只靠一个固定阈值的数字量输出,在这个场景下基本没法稳定工作。
4.2 水泵一启动,单片机就重启
第二个坑更隐蔽。调试灭火动作时,只要一调用泵启动,整块板子就像被人按了复位键。第一次遇到时第一反应是程序卡死,但看串口输出,复位发生在泵启动的同一瞬间,基本可以锁定电源问题。
排查链路如下:先甩开电机和传感器,只留单片机和泵,故障依旧;用万用表电流档串进泵回路,启动瞬间电流冲到0.7A以上,稳定后也有0.4A;再测5V稳压输出,泵启动的几十毫秒里电压从4.9V掉到3.1V,已经把单片机最低工作电压击穿了。根因是泵和控制电路共用了同一个稳压器,启动电流超出稳压器维持能力,电压瞬间塌方。
修复方案:泵直接从7.4V电池取电,用N-MOSFET做开关,栅极接单片机的PA2(TIM2_CH3),这样就能用PWM软启动。软启动代码很朴素:
void pump_pwm_start(void) { for (uint16_t duty = 0; duty <= 1000; duty += 80) { TIM_SetCompare3(TIM2, duty); delay_ms(4); } TIM_SetCompare3(TIM2, 1000); }从一个很小的占空比慢慢加满,泵的启动电流被摊到几百毫秒里,电压不再塌方。同时我还在泵的电源输入端并联了一个1000uF电解电容,进一步吸收冲击。改完之后,连续触发几十次灭火动作都没再复位。
4.3 走不直:左右轮速差的标定
第三个坑发生在演示彩排。小车在搜索状态原地转圈问题不大,但一旦直线逼近目标,路径明显往一个方向偏,一米直线能偏出二十多厘米。最初怀疑是转向控制算错了,后来在桌面贴了卷尺,固定PWM值让小车直行,发现同一个PWM下两个电机转速就是不一样,左轮明显比右轮快。
原因是直流减速电机批量误差和左右轮胎摩擦差异。这个坑不用在硬件上大动干戈,软件标定就能压下来。我加了一组基准常量:
#define LEFT_PWM_BASE 690 #define RIGHT_PWM_BASE 710把原来直接用BASE_SPEED的地方,左右轮分别替换成LEFT_PWM_BASE和RIGHT_PWM_BASE,跑一米再测偏差,反复调几次偏差就压到5厘米以内了。如果想让小车在不同地面都稳定,可以给电机装霍尔编码器做闭环PID,但课设阶段先标定一组常量完全够用。
4.4 文档不是代码的附件,是答辩的弹药
回到标题里“文档+源码”这几个字。这个项目交付的其实有两样东西,代码只是其中一样。很多人写设计文档就是贴原理图、贴源码、贴个流程图,这很浪费。我建议把联调阶段踩过的坑完整写进调试记录:现象是什么、怎么定位的、根因是什么、最终怎么解决。答辩时老师最常问的就是“你遇到过什么问题、怎么解决的”,这些内容就是你现场讲的故事,也是评分里最能体现工作量的一部分。
5. 做完三版之后,还能往哪扩展
5.1 避障、蓝牙调试与编码器闭环
这个方案我前后做了三版,第一版全部用数字量输出,第二版改成三路ADC,第三版加了基线校准和泵的软启动。越往后越觉得项目难度不在某一个单点技术,而在工程化整合。如果课设做完还有余力,可以从这几个方向继续加码。
想增加避障能力,就在车头加一个HC-SR04超声波模块,距离小于某个值时进入临时避障状态,绕开后再回到搜索。想远程调试,加一个蓝牙串口模块,手机就能看三路火焰ADC和当前状态机状态,比拖着USB线调车舒服太多。想拼进阶,装带霍尔编码器的电机,把开环差速转向换成闭环PID速度控制,直行精度会上一个台阶。再往上,可以上FreeRTOS,把传感器任务、运动任务、逻辑任务分开写,那就完全是工程项目的写法了。
5.2 关于工程化的一点体会
我个人做了这么多轮下来最深的体会是:很多bug看起来是软件问题,最后根子都出在电源和布线;代码写得再漂亮,供电不稳一样白搭。反过来,只要电源理顺、传感器装对角度、主状态机写清楚,哪怕只用最朴素的ADC读取和差速转向,小车就能稳定完成灭火动作。希望这篇实录能帮你少走点弯路。最后提醒一句:调试火焰传感器请使用安全光源,比如蜡烛或打火机,远离窗帘和其他可燃物,做实验,安全永远排在第一位。
本文还有配套的精品资源,点击获取