2026/9/6 17:08:01

模拟消防灭火小车设计实战:STM32控制、火焰检测与差速转向实现

模拟消防灭火小车设计实战:STM32控制、火焰检测与差速转向实现 简介面向计算机毕业设计与嵌入式课程设计的模拟消防灭火小车资料包围绕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两端还要并一个续流二极管泵是感性负载断电瞬间会产生反向电动势不加续流二极管管子容易被击穿这个二极管可以就近并在水泵两个接线端子间阴极接电源正极。功能引脚说明火焰传感器左/中/右 AOPC0/PC1/PC2ADC输入读取模拟量L298N IN1~IN4PB8~PB11电机方向控制L298N ENA/ENBPA0/PA1TIM2 CH1/CH2 PWM调速水泵MOSFET栅极PA2TIM2 CH3 PWM软启动舵机信号线PB6TIM4 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太大会在火源前左右摆头甚至原地打转。我最终用的KP1.6BASE_SPEED650。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做开关栅极接单片机的PA2TIM2_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读取和差速转向小车就能稳定完成灭火动作。希望这篇实录能帮你少走点弯路。最后提醒一句调试火焰传感器请使用安全光源比如蜡烛或打火机远离窗帘和其他可燃物做实验安全永远排在第一位。本文还有配套的精品资源点击获取