基于STM32的厨房安全检测系统:从传感器到物联网报警的嵌入式实践
1. 项目概述:为什么厨房需要“智能哨兵”?
厨房,这个充满烟火气的地方,潜藏着不少安全隐患。燃气泄漏、火灾、水浸、甚至长时间无人看管导致的设备空转,都可能引发严重后果。传统的解决方案,比如独立的烟雾报警器或燃气报警器,功能单一,且无法实现远程预警和联动控制。这正是我们基于STM32打造一个集成化厨房安全检测系统的初衷——它就像一个不知疲倦的智能哨兵,7x24小时守护厨房安全。
这个项目的核心,是利用STM32微控制器作为大脑,整合多种传感器(如MQ系列气体传感器、火焰传感器、温湿度传感器、水浸传感器),实时监测厨房环境。一旦检测到异常,系统不仅能通过本地声光报警器发出警报,还能通过Wi-Fi或GSM模块将警情信息推送到你的手机,甚至能自动执行一些补救措施,比如联动电磁阀切断燃气。对于嵌入式开发者、电子爱好者,或是正在寻找有深度的毕业设计课题的学生来说,这是一个绝佳的练手项目。它涵盖了传感器数据采集、模数转换、外设驱动、通信协议、状态机设计乃至简单的上位机交互等多个嵌入式开发的核心知识点,实践性极强。
2. 系统整体设计与核心思路拆解
2.1 核心需求与功能定义
一个合格的厨房安全检测系统,需要应对多种风险场景。我们将其核心功能定义如下:
- 燃气泄漏监测:实时检测甲烷、丙烷等可燃气体浓度,超过阈值立即报警。
- 火灾预警:通过火焰传感器和温度传感器,双保险探测明火或异常高温。
- 水浸检测:防止水管破裂、忘关水龙头导致积水,损坏橱柜和地板。
- 环境监测:监测温湿度,辅助判断环境异常(如高温高湿易滋生细菌,或异常高温可能是火灾前兆)。
- 本地报警:高亮LED、高分贝蜂鸣器,确保室内人员能第一时间察觉。
- 远程通知:通过无线模块(如ESP8266 Wi-Fi或SIM800C GSM)向手机APP或云平台发送报警信息。
- 联动控制:在极端情况下,可输出控制信号,驱动继电器模块来切断燃气电磁阀或打开排风扇。
2.2 硬件架构选型与考量
硬件是系统的骨架,选型直接决定了系统的稳定性、成本和扩展性。
主控芯片:STM32F103C8T6(核心板)选择这款芯片的原因很实际:它属于STM32F1系列,资源丰富(72MHz主频,64KB Flash,20KB RAM),外设齐全(多路ADC、定时器、USART、I2C、SPI),社区资料和教程浩如烟海,成本低廉。对于这个多传感器、需处理通信协议的项目来说,性能完全够用且性价比极高。
传感器阵列:
- 气体传感器(MQ-2/MQ-5):MQ-2对液化气、丙烷、烟雾敏感;MQ-5对天然气、甲烷更敏感。根据你家主要气源选择。它们输出模拟电压信号,需要接入STM32的ADC引脚。
- 火焰传感器:采用红外接收管,对特定波长的火焰光敏感。输出数字开关量(有火低电平,无火高电平)或模拟量,我们通常选用数字接口,简单可靠。
- 温度传感器(DS18B20):单总线数字传感器,精度高,抗干扰好,一根线即可通信,节省IO口。用于精确测温。
- 水浸传感器:通常也是数字输出,当探针接触到水时,输出电平翻转。
- 温湿度一体传感器(DHT11):单总线通信,提供基本的温湿度数据。虽然精度一般,但胜在便宜易用。
报警与执行单元:
- 声光报警器:一个高亮RGB LED(可显示不同颜色的状态)和一个有源蜂鸣器。
- 继电器模块:用于控制220V的排风扇或燃气电磁阀(注意:控制燃气阀门涉及重大安全,务必使用符合安全规范的电磁阀,并确保继电器隔离良好,本项目仅作原理演示,实际家用请咨询专业人士)。
通信模块:
- ESP-01S(ESP8266):这是最经济实惠的Wi-Fi方案。STM32通过串口(USART)以AT指令与它通信,连接家庭路由器,将数据上报到云平台(如OneNET、阿里云)或直接推送到手机。
- SIM800C GSM/GPRS模块:在没有Wi-Fi的环境下,通过插入手机卡,以短信或GPRS流量方式报警,可靠性更高,但会产生持续的费用。
电源:整个系统可采用5V/2A的USB电源适配器供电,通过AMS1117等LDO芯片为STM32提供3.3V。
注意:硬件安全是第一位的。所有涉及220V市电的部分,必须做好强电弱电隔离,使用带有光耦隔离的继电器模块,接线时务必断电操作。燃气传感器不要安装在灶具正上方,避免油烟污染,应安装在靠近气源且空气流通的上部。
2.3 软件框架设计思路
软件上,我们采用“前后台”与“有限状态机(FSM)”结合的模式。
- 后台:主循环中,以固定的周期(如100ms)轮询所有传感器的状态。
- 前台:中断服务程序,用于处理紧急事件,如串口接收完成(收到Wi-Fi模块回复或上位机指令)、定时器超时等。
- 状态机:这是系统的逻辑核心。我们可以定义几个系统状态:
NORMAL(正常)、GAS_WARNING(燃气预警)、FIRE_ALARM(火警)、WATER_LEAK(漏水)等。每个状态下,系统对传感器数据的判断阈值、报警方式、执行动作都不同。例如,在NORMAL状态下,检测到气体浓度缓慢升高,可能先进入GAS_WARNING,仅点亮黄色LED;若浓度急剧升高或超过更高阈值,则立刻跳转到FIRE_ALARM,触发声光报警并发送远程通知。
这种设计使得程序逻辑清晰,易于维护和扩展。例如,未来想增加“长时间无人移动自动进入布防状态”的功能,只需增加一个ARMED状态和对应的迁移条件即可。
3. 核心模块电路与驱动解析
3.1 传感器接口电路设计要点
传感器的稳定工作是系统可靠性的基石。
1. MQ系列气体传感器:MQ传感器需要加热丝预热(通常需24-48小时初次稳定),工作时要提供5V电压(加热丝和传感器本身)。它的输出端(AOUT)是一个与气体浓度成正比的模拟电压(0-5V)。STM32的ADC只能接受0-3.3V输入,因此必须进行分压!一个简单的方案是使用一个10kΩ电阻和一個20kΩ电阻串联分压,将5V范围映射到约0-1.67V,既保护了STM32的IO口,又充分利用了ADC的量程。计算如下:V_adc = V_sensor * (R2/(R1+R2)) = 5V * (20k/(10k+20k)) ≈ 3.33V,为了更安全,可以适当增大R2,例如使用15k+15k,将最大输入限制在2.5V。
2. 火焰传感器(数字接口):其DO引脚直接连接STM32的GPIO输入引脚。模块上通常有一个电位器,用于调节灵敏度(检测距离)。接线时,注意其工作电压(常见为3.3V或5V),确保与STM32逻辑电平匹配。如果不匹配,需要电平转换电路。
3. DS18B20单总线电路:单总线意味着所有设备数据线、控制线、电源线都共用这一根线。必须接一个4.7kΩ的上拉电阻到3.3V,以保证总线在空闲时为高电平。STM32的GPIO需要配置为开漏输出模式,在读写时切换输入输出状态。驱动代码的关键在于精确的时序控制,DS18B20对微秒级延时非常敏感。
4. 继电器驱动电路:STM32的GPIO(3.3V)驱动能力不足以直接驱动继电器线圈。需要用一个三极管(如S8050)或MOS管作为开关。基极通过一个1kΩ电阻连接STM32的IO,集电极接继电器线圈和续流二极管(1N4148),发射极接地。继电器线圈另一端接5V。当IO输出高电平时,三极管导通,继电器吸合。续流二极管至关重要,用于吸收继电器线圈断电时产生的反向电动势,保护三极管。
3.2 STM32外设配置心得(基于HAL库)
使用STM32CubeMX进行初始化可以极大提升效率。关键配置如下:
- ADC(用于MQ传感器):配置为单次转换模式或扫描模式(如果接多个模拟传感器)。设置合适的采样周期(如239.5 Cycles),以提高精度。启用DMA传输可以解放CPU,但本项目数据量不大,轮询读取即可。注意:读取ADC值后,要根据分压比和传感器特性曲线,将其转换为实际的气体浓度(ppm)。这个曲线需要查阅传感器手册,并通过校准来修正。
- GPIO:
- 火焰传感器、水浸传感器的输入引脚:配置为上拉输入模式,这样默认读到的就是高电平(安全状态),当传感器触发时变为低电平。
- 蜂鸣器、LED、继电器控制引脚:配置为推挽输出模式。
- DS18B20/DHT11的引脚:配置为开漏输出模式,并在代码中动态切换输入输出。
- USART(用于调试和Wi-Fi通信):至少需要两个串口。USART1连接USB转串口芯片(CH340/CP2102)用于PC调试打印。USART2或USART3连接ESP8266,波特率通常设为115200。务必开启串口接收中断,用于异步接收Wi-Fi模块返回的大量数据。
- 定时器(TIM):用一个基本定时器(如TIM6)产生1ms的中断,作为系统的“心跳”,用于实现精准的延时、传感器轮询周期计时以及软件定时器功能(如报警后持续鸣响30秒)。
实操心得:ADC的稳定性。模拟传感器读数容易受电源噪声干扰。除了在硬件上增加滤波电容(如在MQ传感器的输出端对地接一个104电容),在软件上可以进行滑动平均滤波。例如,连续采样10次,排序后去掉最大最小值,再取平均,能有效消除毛刺。
4. 关键功能实现与代码剖析
4.1 多传感器数据采集与融合判断
单纯的阈值判断容易误报。我们需要更智能的判断逻辑。
// 伪代码示例:安全监控主循环中的判断逻辑 void Safety_Monitor_Task(void) // 每100ms执行一次 { static uint32_t gas_stable_counter = 0; static uint32_t temp_rise_counter = 0; // 1. 读取所有传感器数据 gas_ppm = Read_MQ5_ADC_And_Convert(); flame_detected = HAL_GPIO_ReadPin(FLAME_GPIO_Port, FLAME_Pin); temperature = DS18B20_ReadTemp(); water_leak = HAL_GPIO_ReadPin(WATER_GPIO_Port, WATER_Pin); // 2. 燃气泄漏判断(加入迟滞和持续判断) if(gas_ppm > GAS_ALARM_THRESHOLD_HIGH) { // 浓度超高,立即触发最高级别报警 System_State = STATE_FIRE_ALARM; Trigger_Alarm(ALARM_TYPE_FIRE, gas_ppm); } else if(gas_ppm > GAS_WARNING_THRESHOLD) { gas_stable_counter++; if(gas_stable_counter > 5) { // 浓度持续500ms超过预警值 if(System_State != STATE_GAS_WARNING) { System_State = STATE_GAS_WARNING; Trigger_Alarm(ALARM_TYPE_WARNING, gas_ppm); } } } else { gas_stable_counter = 0; // 浓度恢复正常,计数器清零 if(System_State == STATE_GAS_WARNING) { System_State = STATE_NORMAL; Clear_Alarm(ALARM_TYPE_WARNING); } } // 3. 火焰与温度协同判断 if(flame_detected == 0) { // 检测到火焰(假设低电平触发) System_State = STATE_FIRE_ALARM; Trigger_Alarm(ALARM_TYPE_FIRE, temperature); } else if(temperature > TEMP_ALARM_THRESHOLD) { temp_rise_counter++; if(temp_rise_counter > 20) { // 温度持续2秒超高,可能是有火但火焰传感器未对准 System_State = STATE_FIRE_ALARM; Trigger_Alarm(ALARM_TYPE_FIRE, temperature); } } else { temp_rise_counter = 0; } // 4. 水浸判断 if(water_leak == 0) { // 检测到水(假设低电平触发) if(System_State != STATE_WATER_LEAK) { System_State = STATE_WATER_LEAK; Trigger_Alarm(ALARM_TYPE_WATER, 0); } } }这段代码体现了几个关键思想:阈值分级(预警和报警)、迟滞判断(防止数值在阈值附近抖动导致状态频繁切换)、时间持续(短时波动不报警)以及传感器融合(火焰和温度相互佐证)。
4.2 基于ESP8266的无线通信实现
让系统“上网”是智能化的关键。我们使用AT指令操作ESP8266。
第一步:模块初始化。上电后,STM32需要发送一系列AT指令来配置ESP8266。
// 发送: AT\r\n 等待回复: OK // 测试模块是否就绪 // 发送: AT+CWMODE=1\r\n 等待回复: OK // 设置为Station模式(连接路由器) // 发送: AT+CWJAP="你的Wi-Fi名","密码"\r\n 等待回复: OK // 连接Wi-Fi,这一步可能耗时较长 // 发送: AT+CIPSTART="TCP","api.xxx.com",80\r\n 等待回复: CONNECT OK // 连接云平台服务器这个过程必须在代码中做好错误重试机制。比如,连接Wi-Fi可能失败,需要循环尝试几次。
第二步:数据上报。以向OneNET平台发送HTTP POST请求为例。
// 1. 准备数据。通常将传感器数据封装成JSON格式。 char json_data[128]; sprintf(json_data, "{\"gas\":%.2f,\"temp\":%.1f,\"flame\":%d,\"water\":%d}", gas_ppm, temperature, flame_detected?0:1, water_leak?0:1); // 2. 发送数据长度指令 char cmd[64]; int data_len = strlen(json_data); sprintf(cmd, "AT+CIPSEND=%d\r\n", data_len); // 告诉模块即将发送的数据长度 UART_SendString(&huart2, cmd); // 通过连接ESP8266的串口发送 Wait_For_Response(">", 1000); // 等待模块回复 '>' 提示符 // 3. 发送实际数据 UART_SendString(&huart2, json_data); // 等待服务器响应...第三步:处理接收。在串口中断服务程序中,将接收到的字符存入缓冲区,在主循环中解析是否包含“OK”、“ERROR”或服务器返回的特定信息。
避坑指南:AT指令的“回车换行”与缓冲区管理。
\r\n不能少:每条AT指令必须以回车换行结尾,这是协议规定。很多新手调试不通,就是因为只发了\n或忘了发。- 响应等待与超时:发送指令后,必须等待模块回复。不能连续狂发指令。需要实现一个带超时的等待函数,在指定时间内搜索预期的响应字符串(如“OK”、“ERROR”)。
- 大缓冲区:ESP8266在接收网络数据或状态信息时,可能会一下子返回很长一串字符(比如收到服务器推送)。你的串口接收缓冲区(
rx_buffer)要足够大(建议512字节以上),并妥善处理缓冲区溢出的情况。- 状态机解析:最好为ESP8266通信单独设计一个简单的状态机,状态包括“就绪”、“等待Wi-Fi连接”、“等待TCP连接”、“等待发送”、“等待响应”等,使流程更清晰。
4.3 报警策略与联动控制逻辑
报警不是简单地响一声就完事,需要有层次、可管理。
void Trigger_Alarm(AlarmType_t type, float value) { switch(type) { case ALARM_TYPE_WARNING: // 燃气预警:黄色LED慢闪,蜂鸣器不响或短促滴滴声 HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_RESET); // 黄=红+绿 HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_SET); Set_Buzzer_Beep(200, 1000); // 响200ms,停1000ms Send_Warning_Msg("GAS_WARNING", value); // 发送预警消息到手机 break; case ALARM_TYPE_FIRE: // 火警/燃气高危:红色LED快闪,蜂鸣器长鸣 HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET); // 持续响 Send_Alarm_Msg("FIRE_ALARM", value); // 联动控制:打开排风扇,切断燃气阀(需谨慎!) HAL_GPIO_WritePin(FAN_RELAY_GPIO_Port, FAN_RELAY_Pin, GPIO_PIN_RESET); // 继电器低电平触发 HAL_GPIO_WritePin(VALVE_RELAY_GPIO_Port, VALVE_RELAY_Pin, GPIO_PIN_RESET); break; case ALARM_TYPE_WATER: // 水浸报警:蓝色LED常亮,蜂鸣器间歇响 HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_RESET); Set_Buzzer_Beep(500, 500); Send_Alarm_Msg("WATER_LEAK", 0); break; } // 记录报警日志到内部Flash或EEPROM Write_Alarm_Log(type, value); }联动控制需要谨慎设计。例如,切断燃气阀是终极安全措施,一旦触发,可能需要手动复位才能恢复。可以在程序中设置,只有确认是持续性的、高浓度的燃气泄漏或明火时,才执行切断操作,并且通过远程通知明确告知用户。
5. 系统调试与常见问题实录
5.1 硬件调试:从乱码到稳定读数
问题1:串口打印乱码。
- 排查:这是最经典的问题。99%的原因是波特率不匹配。检查STM32CubeMX中USART的波特率设置、串口助手的波特率设置、以及代码中
printf重定向的波特率是否三者完全一致。另外,检查时钟树配置,确保系统主频(HCLK)正确,因为USART的波特率发生器依赖于APB总线时钟。 - 心得:先用最简单的代码测试串口,比如在主循环里每秒发送一个固定的字符串“Hello\r\n”。确保硬件连接(TX、RX是否接反)、电源稳定。
问题2:ADC读数跳动剧烈,像在“跳舞”。
- 排查:
- 电源噪声:用示波器看STM32的3.3V和ADC参考电压(VDDA)是否平稳。模拟部分和数字部分的电源最好用磁珠或0Ω电阻隔离,并增加去耦电容(10uF钽电容+0.1uF陶瓷电容靠近芯片引脚)。
- 传感器信号噪声:在MQ传感器的模拟输出端对地加一个0.1uF~10uF的电容滤波。
- 软件滤波:如前所述,实现滑动平均滤波或中值滤波。
- 采样周期:适当增加ADC的采样周期(
SAMPLETIME),让采样电容有更充分的充电时间,可以提高精度。
问题3:ESP8266无法连接Wi-Fi。
- 排查:
- 指令格式:确认AT指令的字符串末尾是否包含了
\r\n。 - 响应等待:发送
AT+CWJAP后,模块需要数秒时间进行握手连接,你的等待响应函数超时时间是否设置得太短(建议至少10秒)? - SSID和密码:是否包含特殊字符(如空格、中文)?尝试用最简单的数字密码测试。
- 电源:ESP8266在发射信号时瞬时电流可能超过200mA,你的5V电源能否提供足够电流?最好单独给ESP8266供电,或者使用质量好的稳压模块,并在模块的VCC和GND之间并联一个大电容(如220uF)。
- 指令格式:确认AT指令的字符串末尾是否包含了
5.2 软件逻辑调试:状态与预期不符
问题4:系统误报警,比如没火没烟却触发火警。
- 排查:
- 传感器初始状态:检查火焰、水浸传感器的默认输出电平。你的代码里是否将“低电平”错误地理解为“安全”?使用逻辑分析仪或万用表测量传感器在正常状态和触发状态下的实际输出。
- 阈值设置不合理:气体报警阈值是否设得太低?厨房炒菜时的油烟可能导致MQ-2的读数短暂升高。需要通过实验,在安全环境下(如点一根香靠近传感器)观察读数,设定一个合理的预警值和报警值。
- 判断逻辑漏洞:检查你的状态机迁移条件。是不是某个计数器没有及时清零,导致条件累积触发?在状态切换的地方多打一些日志出来分析。
问题5:无线通信时好时坏,容易断线。
- 排查:
- 服务器保活:TCP连接长时间无数据通信会被服务器断开。需要实现“心跳包”机制,每隔一段时间(如30秒)向服务器发送一个小的数据包或空指令来保持连接。
- AT指令流程冲突:确保同一时间只有一个AT指令流程在执行。设计一个通信队列,将待发送的指令加入队列,由后台任务依次执行,避免在等待上一个指令回复时又发起新的请求。
- 缓冲区溢出:检查串口接收中断服务函数,是否因为处理时间过长导致丢失数据。中断里只做最核心的“存入缓冲区”和“标记接收完成”操作,复杂的解析放到主循环中。
5.3 稳定性与抗干扰优化
问题6:系统运行一段时间后死机或重启。
- 排查:
- 看门狗:务必启用独立看门狗(IWDG)或窗口看门狗(WWDG)。在main函数的while(1)循环里定期“喂狗”。这样即使程序跑飞,也能自动复位。
- 堆栈溢出:如果使用了大量局部变量或递归,可能造成堆栈溢出。可以在启动文件(
startup_stm32f103xe.s)中适当增大堆栈大小。 - 中断嵌套与优先级:避免在中断服务程序中进行耗时操作(如软件延时、复杂的浮点运算)。给不同的中断(如串口接收、定时器)设置合理的优先级。
问题7:如何测试系统的可靠性?
- 压力测试:编写一个测试脚本,在PC上通过串口模拟各种传感器数据组合,连续数小时向STM32发送,观察系统响应和内存使用情况。
- 环境测试:在厨房实际环境中长期运行(至少一周)。记录下每天炒菜、烧水时传感器的数据变化,进一步优化阈值和判断逻辑,降低误报率。
- 断电测试:突然断电再上电,系统是否能正常自检并启动?EEPROM或Flash中存储的报警记录是否会丢失或错乱?
这个项目从构思到实现,是一个典型的嵌入式系统开发全流程实践。它不仅仅是将几个模块连起来,更涉及到硬件设计中的抗干扰、电源完整性,软件设计中的状态管理、通信可靠性、异常处理等工程化问题。调试过程中,逻辑分析仪和串口调试助手是你最得力的伙伴,而耐心和细致的观察则是解决问题的关键。当你最终看到系统稳定运行,并在手机上收到第一条来自自己亲手打造的“厨房哨兵”的报警信息时,那种成就感,是任何现成产品都无法替代的。