基于STM32的室内空气质量检测系统设计与实现
1. 项目缘起与整体设计思路室内空气质量这件事很多人是搬进新装修的房子、或者家里添了新车之后才开始真正在意的。甲醛超标、二氧化碳浓度过高、温湿度失衡这三个问题凑在一起轻则让人头晕犯困、皮肤过敏重则影响长期健康。市面上的成品检测仪动辄几百上千功能还未必齐全于是拿一块单片机自己搭一套检测系统就成了很多电子爱好者和嵌入式学习者的首选练手项目。我这次做的这套系统核心目标很明确用一块STM32F103C8T6最小系统板做主控挂上DHT11温湿度传感器和MQ-138甲醛传感器再配一块OLED12864显示屏做本地显示同时预留CO2检测通道最后加上继电器控制换气扇和蜂鸣器报警。整套东西做下来成本不到一百块但功能覆盖了“检测—显示—判断—执行”这条完整链路拿来当课程设计、毕业设计或者个人练手项目都非常合适。为什么选STM32F103C8T6而不是51单片机这个问题我在动手之前也纠结过。51单片机便宜、资料多、上手快但它的ADC精度和运算能力有限跑多传感器融合和阈值判断的时候会比较吃力。STM32F103C8T6是ARM Cortex-M3内核72MHz主频自带12位ADC多个USART和I2C接口处理DHT11的时序、MQ-138的模拟量采集、OLED的I2C通信这几件事同时进行毫无压力。而且这块芯片的国产替代方案已经很成熟价格压得很低最小系统板十几块钱就能买到性价比极高。整个系统的设计思路可以概括为“采集层—处理层—执行层”三层结构。采集层负责把甲醛浓度、CO2浓度、温度和湿度这四个物理量转换成电信号处理层由STM32完成数据解析、阈值比较和状态判断执行层则根据判断结果驱动OLED显示、继电器换气和蜂鸣器报警。这个分层思路的好处是每一层都可以独立调试传感器坏了换传感器执行器不动作查驱动电路不会牵一发而动全身。提示如果你之前只玩过51单片机转到STM32时最大的门槛不是编程本身而是开发环境的搭建和库函数的使用。建议先用STM32CubeMX生成初始化代码再配合HAL库逐步添加功能不要一上来就啃寄存器手册。2. 核心器件选型与关键参数解析2.1 主控芯片STM32F103C8T6的引脚分配与资源规划STM32F103C8T6是LQFP48封装48个引脚里有37个可用GPIO对于这个项目来说绰绰有余。但引脚分配不能随便来得根据外设功能来规划。我把DHT11的数据线接在PA0因为PA0支持外部中断DHT11的单总线协议需要精确的微秒级延时用中断方式处理会更稳。MQ-138的模拟输出接PA1对应ADC1的通道1。OLED的I2C接口用PB6和PB7这是STM32F103的I2C1默认引脚硬件I2C比软件模拟更省CPU资源。继电器的控制引脚我放在PB0蜂鸣器放在PB1这两个都是普通推挽输出就行。CO2传感器如果用的是模拟输出的型号可以接PA2对应ADC1的通道2如果用的是UART输出的型号就接PA9和PA10用USART1接收数据。这里要特别注意STM32F103C8T6的PA9和PA10同时也是下载程序的串口引脚如果你用串口下载调试的时候可能会冲突建议CO2传感器优先选模拟输出型号。供电方面最小系统板一般有5V和3.3V输出。DHT11和OLED用3.3VMQ-138和继电器用5VCO2传感器看具体型号。这里有个坑MQ-138的加热丝电流比较大大概150mA左右如果和继电器同时工作5V电源的电流要留够余量建议用2A以上的电源适配器否则会出现传感器读数漂移或者继电器吸合不稳定的情况。2.2 DHT11温湿度传感器的时序要点与读取策略DHT11是一款性价比很高的数字温湿度传感器但它的单总线协议对时序要求非常严格。一次完整的读取过程包括主机拉低数据线至少18ms作为起始信号然后释放总线等待DHT11响应。DHT11会先拉低80微秒再拉高80微秒之后开始传输40位数据。每一位数据都以50微秒的低电平开始然后高电平的持续时间决定数据是0还是1——26到28微秒表示070微秒表示1。这个时序用STM32的HAL库实现时不能用HAL_Delay因为它的最小分辨率是1毫秒而我们需要的是微秒级延时。我的做法是用SysTick定时器做一个微秒级延时函数或者直接用__NOP()空指令做粗略延时。实测下来用SysTick的计数方式最稳误差能控制在1微秒以内。DHT11的采样周期不能太快官方建议两次读取之间至少间隔1秒实际上我建议间隔2秒以上。因为DHT11内部有个电阻式湿度敏感元件每次测量后需要一定时间恢复。如果你连续快速读取会发现数据基本不变或者出现校验错误。我在代码里设了一个2秒的定时器每2秒读一次DHT11读完之后做校验和判断校验失败就丢弃本次数据保留上一次的有效值。注意DHT11的湿度精度是±5%RH温度精度是±2℃这个精度对于室内空气质量监测来说够用但如果你要做精密控制建议换DHT22或者SHT30。另外DHT11的响应时间比较慢从上电到第一次有效读取大概需要1到2秒程序初始化后不要立刻读先延时2秒再开始。2.3 MQ-138甲醛传感器的预热与标定方法MQ-138是MQ系列气体传感器中专门针对甲醛检测的型号它的敏感材料是二氧化锡在加热状态下接触甲醛气体会导致电阻变化从而输出电压信号。这个传感器有两个关键特性一是需要预热二是输出是非线性的。预热时间官方给的是24小时老化但实际使用中每次上电后至少预热5到10分钟读数才会稳定。我实测下来刚上电的时候输出电压会从高往低缓慢下降大概8分钟左右趋于平稳。所以程序里我加了一个预热标志位上电后前10分钟OLED显示“预热中”不参与报警判断避免误报。标定是MQ-138最麻烦的地方。它输出的是模拟电压对应的是甲醛浓度的相对值不是绝对浓度。如果你要得到ppm级别的读数需要用标准气体或者专业的甲醛检测仪做对比标定。我的做法比较简单在已知清洁空气中读取一个基准电压V0然后在密闭空间里用甲醛溶液挥发制造一个已知浓度的环境读取电压V1用两点法拟合一条曲线。虽然精度有限但对于判断“是否超标”这个需求来说足够了。实际电路中MQ-138的输出需要接一个负载电阻RL典型值是10kΩ。输出电压Vout Vc × RL / (Rs RL)其中Rs是传感器的敏感电阻。通过测量Vout反推Rs再根据数据手册的曲线查浓度值。这个过程在STM32里用查表加线性插值实现比直接算公式更准。2.4 OLED12864显示屏的I2C驱动与界面布局OLED12864指的是0.96寸、128×64分辨率的OLED屏有I2C和SPI两种接口。I2C版本接线简单只需要SCL和SDA两根线但刷新率比SPI低。对于这个项目来说我们只需要显示四行数据加几个状态图标I2C完全够用。驱动芯片一般是SSD1306STM32的HAL库有现成的I2C接口但SSD1306的初始化命令序列比较长建议直接用现成的驱动库比如网上流传很广的oled.c和oled.h文件。移植的时候注意两点一是I2C地址一般是0x78或0x7A取决于SA0引脚的电平二是显示缓冲区的大小128×64分辨率需要1024字节的显存STM32F103C8T6有20KB RAM放一个显存缓冲区绰绰有余。界面布局我设计成四行第一行显示温度第二行显示湿度第三行显示甲醛浓度第四行显示CO2浓度和系统状态。状态用图标表示正常是绿色笑脸超标是红色感叹号。字体用6×8和8×16两种标题用大字体数据用小字体。刷新策略上不要每次循环都全屏刷新那样会有闪烁感。我的做法是只刷新变化的数据区域用OLED_Set_Pos定位到具体坐标再写数据。3. 硬件搭建与软件实现全流程3.1 电路原理图设计与嘉立创EDA实操画原理图我用的嘉立创EDA免费而且元件库比较全。整个电路分成四个模块主控最小系统、传感器接口、显示模块、执行器驱动。主控部分直接把STM32F103C8T6最小系统板当成一个元件放上去引出需要的引脚。传感器接口部分DHT11接3.3V和GND数据线接PA0并加一个4.7kΩ上拉电阻。MQ-138接5V和GND模拟输出接PA1负载电阻RL用10kΩ。OLED的VCC接3.3VGND接地SCL接PB6SDA接PB7I2C总线上各加一个4.7kΩ上拉电阻。执行器驱动部分继电器模块我用的是现成的5V继电器模块输入控制端接PB0模块内部已经有光耦隔离和续流二极管直接驱动就行。蜂鸣器用有源蜂鸣器接PB1串联一个1kΩ限流电阻。这里要注意有源蜂鸣器通电就响无源蜂鸣器需要PWM驱动买的时候看清楚。PCB布局的时候MQ-138的加热丝电流较大走线要加粗至少20mil。模拟信号线PA1尽量远离数字信号线避免干扰。OLED的I2C走线不要太长超过10厘米建议加屏蔽或者降低速率。3.2 STM32CubeMX初始化配置与HAL库代码框架用STM32CubeMX配置外设可以省去大量底层代码。具体配置如下系统时钟选HSE外部晶振主频拉到72MHz。ADC1配置为独立模式扫描模式关闭单次转换通道1和通道2轮流采集。I2C1配置为标准模式100kHz速率。USART1配置为115200波特率用于调试输出。GPIO方面PA0配置为输入模式带上拉PB0和PB1配置为推挽输出初始电平低。定时器我用TIM2做2秒定时中断在中断里置一个标志位主循环检测到标志位就执行一次传感器读取。这样比在主循环里用HAL_Delay更高效不会阻塞其他任务。代码框架上我建了这几个文件main.c负责主循环和任务调度dht11.c和dht11.c负责温湿度读取mq138.c负责甲醛采集和浓度换算oled.c负责显示驱动relay.c和buzzer.c负责执行器控制。每个模块都有独立的初始化和功能函数main函数里按顺序调用就行。// main.c 主循环片段 while (1) { if (timer2_flag) { timer2_flag 0; DHT11_Read(temp, humi); MQ138_Read(formaldehyde); CO2_Read(co2); OLED_Refresh(temp, humi, formaldehyde, co2); Check_Threshold(temp, humi, formaldehyde, co2); } }3.3 多传感器数据融合与阈值判断逻辑四个传感器读回来的数据格式不一样DHT11是整数加小数MQ-138是0到4095的ADC值CO2也是ADC值。我先把它们统一转换成工程量温度单位摄氏度湿度单位百分比甲醛单位mg/m³CO2单位ppm。阈值判断我设了三档正常、警告、危险。温度正常范围18到28℃警告范围10到18℃或28到35℃超出就是危险。湿度正常40%到70%甲醛正常低于0.08mg/m³CO2正常低于1000ppm。判断逻辑用if-else嵌套实现每个参数独立判断最后综合出一个系统状态。这里有个细节甲醛和CO2的浓度是渐变的如果阈值设得太死会在临界点附近频繁触发报警。我加了一个迟滞区间比如甲醛超过0.08报警但要降到0.06以下才解除报警这样避免继电器反复吸合。3.4 自动换气与声光报警的执行器驱动继电器控制换气扇的逻辑很简单系统状态为警告或危险时PB0输出高电平继电器吸合换气扇启动状态恢复正常后延时30秒再关闭确保空气充分交换。这个延时很有必要我试过立即关闭结果甲醛浓度很快又升上来反复启动对继电器寿命也不好。蜂鸣器报警分两种模式警告状态用间歇鸣叫响1秒停2秒危险状态用连续鸣叫。用TIM3做PWM输出控制蜂鸣器频率有源蜂鸣器直接给高低电平就行无源蜂鸣器需要2kHz到4kHz的方波。OLED显示同步更新正常状态显示绿色“OK”警告状态显示黄色“WARN”危险状态显示红色“DANGER”并闪烁。闪烁用定时器控制每500毫秒翻转一次显示状态。4. 调试过程中的典型问题与排查实录4.1 DHT11读取失败与校验错误的排查思路DHT11读不出来是最常见的问题表现是返回值为0或者校验和错误。排查步骤我总结了一个顺序先查硬件再查时序最后查电源。硬件方面数据线的上拉电阻不能省4.7kΩ到10kΩ都行不加上拉的话信号上升沿太慢STM32可能识别不到。供电电压要在3.3V到5.5V之间低于3V DHT11不工作。接线长度不要超过20厘米太长的话分布电容会影响时序。时序方面用示波器看数据线的波形最直观。起始信号的低电平要够18ms响应信号的80微秒低电平和80微秒高电平要清晰。如果波形畸变检查GPIO模式是不是配置成了开漏输出应该配置成推挽输出。电源方面DHT11对电源纹波比较敏感如果和继电器共用5V电源继电器吸合的瞬间可能导致DHT11复位。我的解决办法是在DHT11的VCC和GND之间并一个100微法的电解电容效果很明显。4.2 MQ-138读数漂移与预热时间不足的影响MQ-138的读数漂移问题困扰了我很久。刚上电的时候读数一路往下掉大概10分钟后才稳定。一开始我以为传感器坏了后来查资料才知道这是正常现象。二氧化锡敏感材料需要达到工作温度后电阻才会稳定这个预热过程不可避免。除了预热时间环境温湿度也会影响MQ-138的读数。湿度越高输出电压越低。我在代码里加了一个温湿度补偿系数根据DHT11读到的湿度值对甲醛浓度做修正。补偿公式是修正值 原始值 × (1 0.02 × (湿度 - 50))。这个系数是我实测拟合出来的不一定适用于所有场景但比不补偿强。还有一个坑是MQ-138的负载电阻选择。RL太大输出电压高但灵敏度低RL太小灵敏度高但输出范围窄。我试过4.7kΩ、10kΩ和20kΩ最后选了10kΩ兼顾灵敏度和量程。4.3 OLED显示乱码与I2C通信超时的解决OLED显示乱码一般是I2C通信出了问题。先检查地址对不对用I2C扫描程序扫一下总线上的设备地址确认是0x78还是0x7A。然后检查上拉电阻I2C总线必须有上拉STM32内部的上拉电阻比较弱建议外接4.7kΩ。如果I2C通信超时可能是总线被拉死了。这种情况通常是因为某个从机在通信过程中复位导致SDA线被持续拉低。解决办法是在初始化的时候手动发送9个时钟脉冲让从机释放总线。代码实现就是先把SCL配置成推挽输出翻转9次再重新配置成I2C模式。显示乱码还有一个可能是显存缓冲区没有正确初始化。SSD1306上电后显存内容是随机的必须先清屏再写数据。我的做法是在OLED_Init函数最后调用一次OLED_Clear把整个显存写0。4.4 继电器吸合干扰与电源稳定性处理继电器吸合的瞬间会产生反向电动势这个电动势会通过电源线传导到单片机和传感器导致复位或读数异常。我在继电器线圈两端并了一个续流二极管1N4148在继电器模块的电源输入端并了一个100微法电解电容和一个0.1微法陶瓷电容双管齐下。电源方面我用的是5V 2A的适配器单独给继电器和MQ-138供电STM32和OLED用另一路5V转3.3V供电。两路电源共地但不共正这样继电器的干扰不会串到单片机那边。实测下来继电器吸合时单片机不再复位DHT11读数也不跳变了。问题现象可能原因排查方法解决方案DHT11返回0上拉电阻缺失万用表测数据线电压加4.7kΩ上拉电阻DHT11校验错误时序偏差示波器看波形调整微秒延时函数MQ-138读数漂移预热不足记录上电后读数曲线预热10分钟再采集OLED乱码I2C地址错误I2C扫描程序确认地址0x78或0x7A继电器吸合导致复位电源干扰示波器看电源纹波加续流二极管和滤波电容CO2读数异常串口冲突检查PA9/PA10占用改用模拟输出传感器5. 系统优化与功能扩展的实操建议5.1 数据滤波与显示刷新率的平衡原始ADC读数会有波动直接显示会跳来跳去。我用了滑动平均滤波每采集10次取平均值这样读数平滑很多。但滑动平均会带来滞后对于甲醛这种需要快速响应的参数我改用一阶低通滤波系数取0.3兼顾平滑和响应速度。显示刷新率方面OLED全屏刷新一次大概需要20毫秒如果每2秒刷新一次CPU占用率很低。但如果你把刷新率提高到100毫秒一次CPU就有10%的时间花在刷屏上。我的建议是数据采集2秒一次显示刷新500毫秒一次只刷新变化的数据区域这样既流畅又不浪费资源。5.2 阈值可调与参数掉电保存的实现固定阈值不够灵活不同场景对空气质量的要求不一样。我加了一个按键短按切换设置项长按进入设置模式用另一个按键加减数值。设置好的阈值存在STM32内部的Flash里掉电不丢失。Flash读写要注意STM32F103的Flash擦写次数有限不要频繁写我是在设置完成后统一写一次。5.3 从本地报警到远程通知的扩展思路本地声光报警适合有人在家的场景但如果人不在家就需要远程通知。最简单的扩展是加一个ESP8266或ESP32模块通过串口和STM32通信把数据上传到云平台手机就能随时查看。再进一步可以加一个GSM模块超标时直接发短信。不过这些扩展会增加成本和复杂度建议先把基础功能调稳再考虑。5.4 外壳设计与传感器布局的注意事项外壳我用的3D打印材料是PLA。设计的时候要注意DHT11和MQ-138需要接触空气不能密封在壳子里。我在外壳上开了通风孔传感器用排线引出固定在通风孔附近。OLED显示屏开一个矩形窗口用热熔胶固定。传感器布局上MQ-138要远离发热元件因为它的读数受温度影响。继电器和电源模块放在另一侧用隔板隔开。整个外壳的尺寸大概是120×80×40毫米放在桌面上不占地方。提示如果你没有3D打印机用塑料收纳盒改造也行关键是保证传感器周围空气流通同时避免灰尘和油污直接落在传感器上。6. 写在最后的一些个人体会这套系统我从画原理图到调试完成前后花了大概两周时间其中大部分时间花在传感器标定和干扰排查上。硬件搭建本身不难难的是让四个传感器同时稳定工作互不干扰。我的经验是电源一定要分开供电模拟地和数字地单点接地这两个措施能解决80%的干扰问题。另外不要迷信数据手册上的典型电路实际调试中你会发现很多参数需要根据你的具体环境调整。比如MQ-138的负载电阻数据手册推荐10kΩ但在我这个电路里4.7kΩ的响应更快。多试几组参数用实测数据说话比照搬手册靠谱得多。最后说一个容易被忽略的点传感器的寿命。MQ-138的加热丝长期通电会老化DHT11的湿敏元件在潮湿环境下会漂移。建议每隔半年用标准仪器校准一次或者直接换新传感器。这东西不贵换一个比调半天省事。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →