ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32的实验室消防预警系统:从硬件到仿真的完整开源方案

基于STM32的实验室消防预警系统:从硬件到仿真的完整开源方案 1. 项目缘起与整体设计思路实验室安全这件事没出事的时候谁都不在意一出事就是大事。我所在的实验室之前就遇到过一次小险情——楼道里有人用大功率设备烟雾起来了等保安发现的时候已经过了快十分钟。那次之后我就琢磨着能不能用STM32做一套成本可控、能本地报警、还能把数据传出去的消防预警系统。这个项目从画原理图到写代码再到仿真验证前后折腾了大概三周现在把完整的东西开源出来包括代码、原理图和仿真工程希望能帮到做类似题目的同学或者实验室想自己搭一套预警装置的朋友。这套系统核心解决三个问题第一实时监测环境里的烟雾浓度和温度超过阈值立刻本地声光报警第二把数据通过串口传到上位机方便值班人员远程查看第三预留继电器输出接口可以联动排风扇或者切断电源。整个方案围绕STM32F103C8T6这颗经典芯片展开外围搭配DHT11温湿度传感器和MQ-2烟雾传感器显示用0.96寸OLED报警用蜂鸣器加LED通信走USB虚拟串口。选这套组合的原因很简单成本低、资料多、社区支持好而且F103C8T6的资源和性能应付这种多传感器轮询加逻辑判断的场景绰绰有余。适合谁来参考这个项目呢如果你是嵌入式方向的学生正在找毕业设计或者课程设计的题目这套东西从硬件到软件到仿真全都有直接拿来改改就能用如果你是实验室管理员或者小企业负责安全的同事想低成本搭一套预警原型这里面的电路和代码也能直接落地哪怕你只是刚学完STM32基础外设想找个综合项目练手这个项目的复杂度也刚好合适——涉及GPIO、ADC、定时器、串口、I2C等多个外设的协同但又不至于复杂到让人放弃。提示这个项目定位是“预警”而不是“消防联动控制”所以没有涉及喷淋、气体灭火这些执行机构。如果你要做完整的消防系统还需要在此基础上增加更多的安全冗余和认证环节。2. 硬件架构与原理图设计拆解2.1 核心器件选型与理由先说说为什么选这几个关键器件。主控用STM32F103C8T648引脚LQFP封装72MHz主频64KB Flash20KB RAM。有人会问为什么不选F407或者G系列理由很直接这个项目不需要浮点运算不需要以太网不需要USB HostF103的资源和外设完全够用而且价格便宜、供货稳定、教程遍地。对于实验室预警这种场景稳定可靠比性能过剩重要得多。温湿度传感器选DHT11而不是SHT30或者DHT22原因有两个一是DHT11单总线协议简单时序虽然要求严格但资料极其丰富新手容易上手二是实验室环境对湿度精度要求不高DHT11的±5%RH精度足够判断是否异常。当然如果你追求更高精度可以直接换成SHT30I2C接口代码改动量不大。烟雾检测用MQ-2这个传感器对液化气、丙烷、氢气、烟雾都有响应模拟输出可以直接接STM32的ADC引脚。它的加热丝需要5V供电预热时间大概20秒到1分钟这个特性后面在代码里会专门处理。MQ-2的缺点是输出不是线性的而且受温湿度影响但对于“有没有烟雾”这种定性判断完全够用。显示部分用0.96寸OLEDSSD1306驱动I2C接口128x64分辨率。选它是因为I2C只需要两根线节省IO口而且显示内容清晰可以同时显示温度、湿度、烟雾浓度和报警状态。报警输出用有源蜂鸣器加红色LED有源蜂鸣器给高电平就响驱动简单不需要PWM调制。2.2 原理图关键模块分析整个原理图可以分成五个模块来看电源模块、主控最小系统、传感器接口、显示与报警、通信接口。电源部分实验室通常有5V的USB供电所以直接用USB Type-C接口引入5V然后通过AMS1117-3.3稳压到3.3V给STM32和OLED供电。MQ-2的加热丝直接吃5V它的模拟输出经过一个10K电阻和0.1uF电容组成的RC低通滤波后接入STM32的PA0引脚ADC1_IN0。这里加RC滤波很有必要因为MQ-2的输出会随着加热丝的开关产生纹波不加滤波的话ADC读数会跳得厉害。主控最小系统部分8MHz晶振加两个22pF电容构成HSE时钟源复位电路用10K上拉电阻加100nF电容BOOT0通过10K电阻下拉到地保证从Flash启动。SWD调试接口引出SWDIO和SWCLK方便用ST-Link下载和调试。这里有个细节NRST引脚上并了一个100nF电容到地这是为了滤除复位线上的毛刺实际调试中发现不加这个电容偶尔会出现上电不启动的情况。传感器接口部分DHT11的数据线接PB12加上4.7K上拉电阻。MQ-2的模拟输出接PA0数字输出DO其实也可以接一个GPIO做阈值比较但本项目只用模拟输出因为阈值可以在软件里灵活调整。OLED的SCL接PB6SDA接PB7这是STM32F103的I2C1默认引脚加上4.7K上拉电阻。蜂鸣器接PB5通过一个S8050三极管驱动因为蜂鸣器的工作电流可能超过GPIO的驱动能力。LED接PB4串联一个1K限流电阻。通信接口部分用STM32自带的USB外设做虚拟串口CDC这样只需要一根USB线就能同时供电和通信不需要额外的CH340或者CP2102。USB的D和D-分别接PA12和PA11D上拉一个1.5K电阻到3.3V这是USB全速设备的标志。注意画原理图的时候MQ-2的加热丝电源和STM32的3.3V要严格分开走线加热丝的电流波动会通过地线耦合到ADC参考地上导致烟雾读数不稳。我在第一版PCB上就踩了这个坑后来把加热丝的地单独走回电源入口问题才解决。2.3 原理图设计中的几个关键决策第一个决策是ADC参考电压的选择。STM32F103的VREF默认接VDDA也就是3.3V。但实际测量中发现USB供电的3.3V会有几十毫伏的波动导致ADC读数有±2个LSB的跳动。为了改善这个问题我在VREF引脚上并了一个10uF钽电容加一个100nF陶瓷电容效果很明显读数稳定了很多。第二个决策是DHT11的上拉电阻阻值。官方数据手册推荐4.7K到10K我实际测试下来4.7K在长导线超过20cm情况下波形上升沿更陡通信成功率更高。如果你把DHT11放在离主板较远的地方建议用4.7K甚至更小但不要低于2.2K否则功耗会增加。第三个决策是蜂鸣器的驱动方式。一开始我直接用一个NPN三极管做开关基极电阻用1K结果发现蜂鸣器启动瞬间会把3.3V拉低一下导致OLED闪屏。后来在蜂鸣器两端并了一个100uF电解电容并且在基极电阻上并了一个0.1uF电容加速关断问题才解决。这个经验告诉我任何感性负载蜂鸣器、继电器、电机在开关瞬间都会产生电源扰动必须做好去耦和储能。3. 软件架构与核心代码实现3.1 整体软件流程设计软件部分我采用前后台架构没有上RTOS因为任务数量少、实时性要求不高。主循环里依次做四件事读取DHT11温湿度、读取MQ-2烟雾ADC值、更新OLED显示、检查报警条件并执行报警动作。定时器2用来做1ms的系统滴答给DHT11的微秒级延时和任务调度提供时基。为什么不用RTOS因为这个项目的任务都是周期性的而且相互之间没有复杂的同步关系。用RTOS反而会增加内存开销和调试复杂度。当然如果你后续要加WiFi模块、要跑MQTT协议那上FreeRTOS会更合适。目前这个版本前后台架构足够稳定。报警逻辑是这样的温度超过45度、湿度超过85%、烟雾ADC值超过200012位ADC满量程4095任意一个条件满足就进入报警状态。报警状态下蜂鸣器间歇鸣叫响0.5秒停0.5秒LED常亮OLED显示报警原因。如果三个条件都恢复正常并持续10秒才退出报警状态。这个10秒的迟滞是为了防止临界值附近反复触发报警。3.2 DHT11驱动代码详解DHT11的单总线协议对时序要求很严格特别是微秒级的延时。我用TIM2做微秒延时配置成1MHz的计数频率也就是每计数一次1微秒。下面是关键代码// 微秒级延时函数 void delay_us(uint32_t us) { uint32_t start TIM2-CNT; while ((TIM2-CNT - start) us); } // DHT11起始信号 void DHT11_Start(void) { GPIO_InitTypeDef GPIO_InitStruct; // 配置PB12为推挽输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_12; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); GPIO_ResetBits(GPIOB, GPIO_Pin_12); // 拉低 delay_us(18000); // 保持至少18ms GPIO_SetBits(GPIOB, GPIO_Pin_12); // 拉高 delay_us(30); // 等待20-40us }读取数据的流程是主机发送起始信号后DHT11会拉低总线80us作为响应然后拉高80us准备发送数据。接着连续发送40位数据每一位以50us低电平开始高电平的持续时间决定数据是0还是1——26-28us表示070us表示1。代码里通过判断高电平持续时间来区分// 读取一位数据 uint8_t DHT11_ReadBit(void) { uint8_t retry 0; while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) retry 100) { retry; delay_us(1); } retry 0; while (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) retry 100) { retry; delay_us(1); } delay_us(40); // 等待40us后采样 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)) return 1; else return 0; }实操心得DHT11的读取间隔不能小于1秒否则数据会出错。我在主循环里用了一个计数器每2秒读一次这样既保证了实时性又不会因为读取太频繁导致传感器发热影响精度。另外读取失败的时候不要立刻重试等下一个周期再读连续重试反而容易一直失败。3.3 MQ-2烟雾传感器ADC采集与滤波MQ-2的输出接在PA0用ADC1的通道0。STM32的ADC是12位的参考电压3.3V所以分辨率是3.3V/4096≈0.8mV。配置ADC的时候要注意几点采样时间设为55.5个周期因为MQ-2的输出阻抗比较高采样时间太短会导致采样电容充不满连续转换模式关掉用单次转换加软件触发避免DMA占用资源。// ADC初始化 void MQ2_ADC_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; ADC_InitTypeDef ADC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟72M/612MHz GPIO_InitStruct.GPIO_Pin GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStruct); ADC_InitStruct.ADC_Mode ADC_Mode_Independent; ADC_InitStruct.ADC_ScanConvMode DISABLE; ADC_InitStruct.ADC_ContinuousConvMode DISABLE; ADC_InitStruct.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStruct.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStruct); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); // ADC校准 ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); }读取的时候我用了滑动平均滤波连续读10次去掉最大值和最小值剩下的8个取平均。这样处理之后读数波动从±30降到了±5以内。另外MQ-2上电后需要预热我在初始化之后加了一个60秒的预热等待期间OLED显示“预热中”不进行报警判断。这个预热时间可以根据实际传感器调整有些MQ-2模块标称20秒就够了但我实测下来60秒后读数才真正稳定。3.4 OLED显示与USB虚拟串口OLED用SSD1306驱动I2C地址0x78。显示内容分四行第一行显示“实验室消防预警”第二行显示温度和湿度第三行显示烟雾ADC值和电压值第四行显示系统状态正常/预热/报警。刷新率控制在5Hz左右太快了没必要还增加I2C总线负载。USB虚拟串口这部分STM32F103的USB外设配合ST的CDC库可以实现。配置的时候要注意USB时钟必须精确配置为48MHzF103的USB外设对时钟精度要求很高偏差超过0.25%就会枚举失败。系统时钟72MHzUSB预分频1.5得到48MHz。代码里我用了ST提供的USB-FS-Device_Lib初始化之后直接调用CDC_Transmit_FS就可以发送数据。// 发送数据到上位机 void USB_SendData(float temp, float humi, uint16_t smoke) { uint8_t buf[64]; sprintf((char*)buf, T:%.1fC H:%.1f%% S:%d\r\n, temp, humi, smoke); CDC_Transmit_FS(buf, strlen((char*)buf)); }上位机我用Python写了一个简单的串口接收程序用pyserial读取数据然后用matplotlib实时绘制曲线。这样值班人员可以在电脑上看到温湿度和烟雾浓度的变化趋势比只看OLED屏幕直观得多。4. 仿真验证与实物调试4.1 Proteus仿真工程搭建仿真这部分我用的是Proteus 8.13主要验证三个东西DHT11的时序能不能被正确读取、MQ-2的模拟输出能不能被ADC正确采集、报警逻辑在临界值附近的行为是否符合预期。在Proteus里搭建电路的时候STM32F103C8T6的模型可以直接从库里面找DHT11和MQ-2也有现成的仿真模型。需要注意的是Proteus里的DHT11模型响应速度比实物快所以仿真的时候要把读取间隔调小一点否则看不到数据变化。MQ-2的仿真模型可以通过一个电位器来模拟输出变化转动电位器就相当于改变烟雾浓度。仿真工程里我设置了三个测试场景场景一是正常环境温湿度正常烟雾ADC值在500左右系统显示正常场景二是温度逐渐升高到50度观察报警是否触发场景三是烟雾ADC值突然跳到3000观察蜂鸣器和LED的动作。三个场景都跑通之后我才开始画PCB打样。提示Proteus仿真不能完全替代实物调试特别是DHT11的时序和MQ-2的预热特性仿真和实物差异比较大。仿真主要用来验证逻辑实物调试才是真正的考验。4.2 实物调试中踩过的坑第一个坑是DHT11读取失败率很高。一开始我以为是自己时序写错了用逻辑分析仪抓波形才发现问题出在GPIO模式切换上。DHT11在响应之后会拉低总线这时候主机必须把GPIO从输出模式切换到输入模式而且切换要足够快。我原来的代码在切换之前多执行了几条指令导致错过了响应信号。后来把模式切换放在拉高总线之后立刻执行问题解决。第二个坑是MQ-2的读数在蜂鸣器响的时候会跳变。用示波器看电源纹波发现蜂鸣器启动瞬间3.3V上有一个大约200mV的跌落持续时间大概10ms。这个跌落通过ADC参考电压耦合到了读数上。解决办法是在蜂鸣器电源脚就近加一个100uF电解电容同时在软件里做处理蜂鸣器响的时候不进行ADC采样等蜂鸣器停了再采样。第三个坑是USB枚举偶尔失败。现象是插上USB线之后电脑有时候能识别有时候不能。排查下来发现是USB的D上拉电阻时序问题。STM32的USB库默认是在初始化之后才拉高D但有些电脑的USB主机在枚举超时之前就放弃了。后来我在USB初始化之前先把D拉高让主机更早检测到设备问题就很少出现了。4.3 常见问题速查表问题现象可能原因排查方法解决方案DHT11读数一直为0时序不对或上拉电阻缺失用逻辑分析仪抓PB12波形检查延时函数精度确认4.7K上拉电阻已焊接MQ-2读数跳动大电源纹波或预热不足示波器看3.3V和5V纹波加去耦电容延长预热时间到60秒OLED不显示I2C地址错误或上拉电阻未接用I2C扫描程序检测地址确认地址0x78检查PB6/PB7上拉USB无法识别时钟配置错误或D上拉时序检查SystemInit里USB时钟确认USB预分频1.5D提前拉高蜂鸣器不响三极管驱动电路问题测量PB5电平和蜂鸣器两端电压检查三极管型号和基极电阻加续流电容报警后无法退出迟滞时间设置过短观察串口输出的传感器值增加退出报警的持续正常时间到10秒5. 开源资料说明与二次开发建议5.1 开源内容清单这次开源的东西包括三部分硬件原理图Altium Designer格式和PDF导出、STM32完整工程Keil MDK5工程包含所有驱动代码和主程序、Proteus仿真工程Proteus 8.13格式。原理图里所有器件的封装都标注清楚了可以直接拿去打样。代码里关键部分都有注释特别是DHT11时序和ADC滤波部分注释写得比较详细方便新手理解。代码工程的结构是这样的Hardware文件夹放外设驱动dht11.c、mq2.c、oled.c、usb_cdc.cUser文件夹放main.c和中断处理Library文件夹放ST的标准外设库。编译环境是Keil MDK5需要安装STM32F1的器件支持包。如果你用STM32CubeMX重新生成工程也可以但要注意I2C和ADC的配置参数要和我这边保持一致。5.2 二次开发方向如果你想在这个基础上继续扩展有几个方向比较实用。第一个是加WiFi模块比如ESP8266或者ESP32把数据传到云平台或者手机APP上。这个改动主要在软件层面硬件上只需要把WiFi模块的串口接到STM32的USART2上然后在主循环里增加数据打包和发送的逻辑。第二个方向是增加继电器输出联动排风扇或者切断电源。原理图上我已经预留了继电器接口PB3只需要加一个继电器模块和驱动三极管就行。代码里增加一个GPIO控制函数在报警状态下拉高PB3继电器吸合。第三个方向是增加数据存储功能用SD卡或者SPI Flash记录历史数据。这个对于事故追溯很有价值可以知道报警发生前后的环境变化趋势。SD卡用SPI接口接在PB13-PB15上代码里用FatFS文件系统写入CSV格式的数据。注意如果你要增加继电器控制强电设备一定要做好强弱电隔离继电器触点侧和线圈侧要保持足够的爬电距离。实验室安全无小事任何涉及强电的改动都要格外谨慎。5.3 成本与物料清单整个系统的物料成本大概在50到60元之间具体清单如下STM32F103C8T6最小系统板约12元DHT11约5元MQ-2模块约8元0.96寸OLED约10元USB Type-C接口和AMS1117约3元蜂鸣器、LED、电阻电容等杂项约5元PCB打样5片约10元。如果批量做的话成本还能更低。焊接的时候建议先焊电源部分测好3.3V和5V电压正常之后再焊主控和传感器。MQ-2模块最好用排针插座不要直接焊死因为它的加热丝寿命有限坏了方便更换。OLED的排线比较脆弱焊接的时候烙铁温度不要太高300度左右快速焊接避免烫坏排线。我在实际使用中发现这套系统连续运行一周之后MQ-2的基准值会稍微漂移从最初的500左右变成550左右。这是金属氧化物传感器的正常老化现象解决办法是在软件里加一个自动基线校准每次上电预热完成后把当前ADC值作为基线报警阈值设为基线加1500。这样即使传感器老化报警灵敏度也能保持一致。这个技巧在常规文档里很少提到但实际用起来非常有效。
返回列表