尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32室内空气质量监测系统设计:DHT11与甲醛/CO2传感器仿真与实现

STM32室内空气质量监测系统设计:DHT11与甲醛/CO2传感器仿真与实现 简介基于STM32的室内空气质量监测系统设计资料面向嵌入式初学者、电子竞赛备赛者以及物联网项目开发者提供了一套完整的温湿度、甲醛与二氧化碳浓度检测方案。系统以DHT11采集温湿度配合独立甲醛和二氧化碳检测模块数据实时显示于LCD并支持阈值设定与超标报警可直接借鉴其传感器驱动和报警逻辑。资源包共172个文件约27.79MB主要包含C/H源码、Keil工程文件uvprojx/uvoptx、Proteus仿真工程pdsprj/pdsbak、编译生成的HEX固件及汇编启动文件另有MP4演示视频和批量清理脚本便于对照仿真与实际运行效果。当前已有230人学习下载适合作为课程设计或竞赛项目的参考模板尤其有助于快速理解STM32标准外设库配置、多传感器协同采集和LCD显示流程节省从零搭建环境的时间。1. 室内空气质量监测真正难的不是加传感器而是把数据变成可动作的信号装修完的房子、密闭的会议室、长时间开空调的卧室大家在意的都是甲醛和二氧化碳但多数方案只给一个数字没有用。用户要的是“超标了怎么办、阈值设多少、传感器漂移了怎么校准”。我最近拆了一个基于STM32的温湿度与空气质量监测实例DHT11加独立甲醛和二氧化碳模块带LCD显示、阈值报警附带完整的Proteus仿真工程和Keil工程源码。下面按我从看资料到跑通仿真的顺序把代码和仿真里值得注意的边界梳理一遍适合正在做环境监测毕业设计、或者想快速把多传感器接入STM32的开发者。2. 系统方案与选型从DHT11到甲醛/CO2传感器把“采样点”变成“系统”先看清楚骨架。工程里能看到stm32f10x_tim.c、stm32f10x_flash.c、stm32f10x_adc.c用的是标准外设库主控是STM32F103系列。接入三类信号DHT11温湿度、甲醛检测模块、二氧化碳检测模块再加上LCD和蜂鸣器。数据流非常直白传感器 → MCU判断 → 显示与报警没上RTOS靠主循环轮询加定时器中断就能满足实时性。2.1 整体架构与数据流传感器输出分两类DHT11是单总线数字协议只需一个GPIO引脚甲醛和二氧化碳模块大多提供模拟电压输出走ADC采样。推荐引脚划分如下表具体以你的板子为准。外设接口方式STM32引脚DHT11单总线 GPIOPA0LCD1602并行 GPIOPB0~PB2, PB10~PB15蜂鸣器GPIO 输出PB3甲醛模块ADC 模拟输入PA1CO2模块ADC 模拟输入PA2调试串口USART1PA9/PA10这种分配的好处是ADC通道集中在PA1/PA2可配置为规则组的两个通道用DMA或定时触发采样避免主循环里逐个等转换结果。系统采用“定时器调度主循环业务”的结构。定时器产生100ms时间片主循环里用一个状态机决定当前时间片做哪件任务读DHT11、读ADC、刷新LCD、检查报警。这个设计比单轮大循环更可靠因为它把慢设备DHT11的40bit时序和快设备ADC隔离开来不会让ADC采样被温湿度读取卡成锯齿。2.2 传感器选型精度、响应时间与成本DHT11精度是±2℃、±5%RH响应较慢大约1秒刷新一次。对室内环境监测勉强够用但如果做研发测试建议直接换SHT30I2C接口精度高一档代码复杂度和DHT11差不多。甲醛模块常见两种电化学型如ZE08-CH2O输出化学气体浓度对应电压响应时间约30~50秒适合长期慢速监测半导体型如WSP2110成本低但受温度和湿度影响大必须做温度补偿。二氧化碳模块优先选红外非色散型的MH-Z14量程400~5000ppm能自校准劣势是体积和功耗如果电池供电要给它单独做电源控制。关于阈值甲醛室内标准通常取0.08mg/m³二氧化碳健康阈值取1000ppm。不要一开始就把报警阈值定得太低因为传感器在开机前十几分钟会有预热漂移尤其电化学传感器冷启动输出偏高加一个“上电15分钟不报警”的软启动逻辑能省掉很多误报。2.3 开发环境搭建芯片包、Keil5和Proteus怎么配合代码工程是Keil5的UV5项目第一件事装STM32F10x芯片包。装过C51开发板的机器上容易遇到Keil5只认C51不认STM32的情况。常见做法是到Keil官网下载Keil.STM32F1xx_DFP.x.x.x.pack双击后Pack Installer会自动关联然后在Project → Manage → Device里能看到STM32F103C8即可。仿真用Proteus 8不需要额外装MCU模型但传感器模型要注意版本。DHT11、LCD1602和虚拟示波器在Proteus元件库里的名字分别是DHT11、LM016L烧录文件则需要在MCU属性里指定工程生成的.hex。如果不指定外部hexProteus会把看不见的内部固件当空ROM一运行什么都没反应这个坑后面第4章再细说。这套系统不依赖外部Flash工程里出现stm32f10x_flash.c是因为标准外设库默认把Flash驱动编译进来实际应用中没有写入除STM32固件外的数据。stm32f10x_tim.c则用于生成系统节拍我给定时器配的是1ms中断累加计数ADC的采样触发也用同一个时基这样报警比较器的扫描频率就是确定的。3. 温湿度与气体采集代码实现GPIO、ADC和中断的协作从代码层面看整个项目最核心的是三块DHT11的时序读取、多通道ADC的连续采样、阈值触发。这里不是简单地调用HAL库函数而是要处理好时序约束和传感器噪声。3.1 DHT11时序读取与错误处理DHT11的单总线协议要求MCU先拉低至少18ms发出启动信号再释放总线传感器应答后连续输出40bit数据。常见实现是直接操作GPIO翻转和延时等待。我一般会在每次读取前关闭中断避免定时器中断打扰微秒级的电平采样。以下代码是去掉注释后可以直接嵌入main.c的版本uint8_t dht11_read(uint8_t *temp, uint8_t *humi) { uint8_t buf[5] {0}; uint8_t i, j; GPIOA-CRL 0xFFFFFFF0; GPIOA-CRL | 0x00000003; // PA0 推挽输出 GPIOA-BRR (uint16_t)1; delay_us(20000); // 至少 18ms取 20ms 余量 GPIOA-BSRR (uint16_t)1; delay_us(30); GPIOA-CRL 0xFFFFFFF0; GPIOA-CRL | 0x00000004; // 切回浮空输入 while ((GPIOA-IDR 1) 1) { if (--i 0) return 1; // 等应答低电平 } while ((GPIOA-IDR 1) 0) { /* 等拉高 */ } while ((GPIOA-IDR 1) 1) { /* 等第一个数据位开始 */ } for (i 0; i 5; i) { for (j 0; j 8; j) { while ((GPIOA-IDR 1) 0) { /* 跳过 50us 低电平 */ } delay_us(40); buf[i] (buf[i] 1); if (GPIOA-IDR 1) buf[i] | 1; // 高电平超过 40us 视为 1 while ((GPIOA-IDR 1) 1) { /* 等位结束 */ } } } if ((buf[0] buf[1] buf[2] buf[3]) ! buf[4]) return 2; *humi buf[0]; *temp buf[2]; return 0; }这段代码用寄存器操作而不是HAL函数原因是DHT11的位周期是微秒级函数调用开销会导致采样错位。关键参数是延时40us的判断窗口DHT11数据位“0”的高电平持续约26~28us“1”约70us所以在高电平开始后等40us再读引脚能稳定区分二者。很多移植失败的案例是直接抄别人延时函数不同主频下delay_us不准确。验证方法是在PA0接逻辑分析仪看波形高位宽度是不是26us和70us两档。3.2 多通道ADC与传感器标定换算甲醛和二氧化碳模块输出0~3.3V或0~5V模拟信号。STM32的ADC是12位通过配置规则组顺序采样PA1和PA2。我通常开启扫描模式和连续转换用DMA把结果搬运到内存避免在中断里频繁读DR寄存器。配置代码void adc_init(void) { RCC-APB2ENR | RCC_APB2ENR_ADC1EN; RCC-APB2ENR | RCC_APB2ENR_IOPAEN; GPIOA-CRL ~(0xFF 4); // PA1/PA2 模拟输入 RCC-CFGR ~(7 14); RCC-CFGR | RCC_CFGR_ADCPRE_2; // PCLK2 6分频ADC时钟约 12MHz ADC1-CR2 ~ADC_CR2_CONT; ADC1-CR2 | ADC_CR2_ADON; ADC1-SMPR2 | (7 (3 * 1)) | (7 (3 * 2)); // PA1/PA2 采样周期最大 ADC1-SQR1 (1 20); // 规则组 2 个转换 ADC1-SQR3 (1 0) | (2 5); // 第1个通道 PA1第2个 PA2 ADC1-CR2 | ADC_CR2_CAL; while (ADC1-CR2 ADC_CR2_CAL) {} }注意ADC采样时间和输入阻抗的匹配。甲醛模块内部等效输出阻抗通常在1kΩ到10kΩSTM32数据手册推荐采样周期12MHz下不低于1.5us但如果模块输出带RC滤波可以把采样周期设到55.5us以上。实际操作中我给PA1/PA2各对地加一个100nF电容采样值抖动可以从±30mV降到±5mV。这比在软件里做十次平均更直接。3.3 LCD显示与阈值报警联动LCD1602在这里只显示两行第一行温度与湿度第二行甲醛和CO2读数。刷新频率不必太高每秒刷一次即可因为DHT11本身每秒才能响应一次。阈值判断放在主循环的1s时间片里void check_alarm(void) { if (temp_value temp_limit || humi_value humi_limit || hcho_mg_100ml hcho_limit || co2_ppm co2_limit) { BEEP_GPIO-BSRR BEEP_PIN; // 开启蜂鸣器 alarm_report(ALARM_SOURCE); // 通过串口输出当前超标项 } else { BEEP_GPIO-BRR BEEP_PIN; } }很多初版设计只给了温度阈值忽略湿度阈值。但霉菌和闷热感主要和湿度耦合所以我习惯在液晶屏上用闪烁的字符标出“ALARM:TEMP”而不是只响蜂鸣器。只有蜂鸣器无法说明是哪个指标超标现场调试时你必须逐个去看变量效率很低。加上报警源字符串维护成本会小很多。4. 用Proteus做仿真验证把hex跑起来再谈“仿真与实物的差异”仿真不是摆设它解决了“没有实物板也能调逻辑”的问题。但这套工程里有些坑Proteus模型和真实芯片不完全一样比如DHT11模型不会提供精确到微秒的波形蜂鸣器模型也只是高低电平控制一个发声符号。4.1 用Proteus搭出可运行的仿真图我没有重建原理图直接打开项目附带的.pdsprj。如果你要自己搭步骤固定从元件库拖入与hex匹配的STM32F103系列型号例如F103C8、DHT11、LM016L、两个电压源和一个可调电阻用可调电阻模拟甲醛和CO2模块的模拟电压。然后双击MCU在Program File里选择Keil生成的Template.hex设置晶振为8MHz勾选外部时钟。加载后点击运行LCD应该立刻出现DHT11的温湿度数据转动可调电阻第二行浓度变化超过阈值后蜂鸣器开始响。Proteus里没有直接叫“甲醛传感器”的元件常见的做法是电压源配滑动变阻器。把电压范围设置为0~3.3V对应模块的0~满量程。比如你的CO2模块输出0~2.5V对应400~5000ppm就在Proteus里设可变电阻0~2.5V然后将ADC值代入第3章的换算公式即可。4.2 仿真中容易踩的坑ADC输入范围与采样时序第一个坑是不检查代码里的ADC参考电压。Proteus默认STM32的VREF接VDD 3.3V如果你的传感器输出超过3.3V电压会被钳位到3.3V看不出真实超标。同样是5V供电的电化学模块需要先用分压电阻把输出缩放到0~3.3V。第二个坑是DHT11的应答时序Proteus模型对启动信号要求较严格延时20ms不够就拉不起应答把初始延时改到30ms仿真实测通过。如果遇到LCD显示乱码先量一下LCD两条对比度电压引脚。在Proteus里LM016L的3脚直接接地即可不用像真实芯片那样接10k电位器。另一个坑是蜂鸣器仿真模型驱动能力很弱PB3引脚直接驱动可能不响加一个NPN三极管就能解决。这个现象和实物一致MCU引脚的驱动电流有限所以代码没变硬件电路变了。4.3 从仿真到实物保留调试串口和日志点仿真通过后真正移植到实物板时最好把USART1调试串口留着。我在代码里加了一个“上报模式”每5秒输出一行T26.3 H58.0 HCHO0.04 CO21250 ALARMGAS。因为Proteus的虚拟串口可以和真实串口工具对接你可以在仿真阶段就检查这行日志的格式在实物阶段接USB转TTL看数据。这个习惯能让你快速发现传感器接线松动和电压漂移省掉拿着万用表逐个点测的时间。5. 进阶技巧以基线校准和自动校准替代固定阈值项目到第四步已经能跑仿真但真实部署时一个容易忽略的问题是传感器漂移。DHT11在90%RH高湿环境中长时间工作湿度读数会慢慢偏高几个百分点电化学甲醛传感器使用6个月后零点可能从0.00变成0.05mg/m³。如果一直用固定阈值会产生持续误报。5.1 开机基线校准和实时零点跟踪我一般会在代码里预留“上电基线校准”系统启动后前60秒内不计算报警只采样传感器输出取平均值作为零点偏移量。因为洁净空气中甲醛模块的理论输出应接近0mg/m³二氧化碳接近400ppm。将实测值减去这个基线再进入显示和报警判断。代码如下typedef struct { uint16_t raw_hcho; float offset_hcho; uint16_t raw_co2; float offset_co2; } sensor_cal_t; sensor_cal_t cal; void baseline_cal(void) { uint8_t i; uint32_t sum_hcho 0, sum_co2 0; for (i 0; i 60; i) { sum_hcho read_adc(1); sum_co2 read_adc(2); delay_ms(1000); } cal.offset_hcho (float)sum_hcho / 60.0f; cal.offset_co2 (float)sum_co2 / 60.0f; }注意这个校准不是每个上电周期都无条件执行。如果是持续运行的系统在开机后用户正在室内此时空气中的甲醛浓度不一定是零用开机均值当零点会把正在产生的污染“校掉”。正确做法是只在电池首次上电、或人工按校准键时执行平时依靠慢漂移跟踪每小时取最低值的90%作为参考点。5.2 用两点校准修正传感器的“斜率”传感器老化不仅让零点偏移也会让输出斜率产生变化。更完善的方案是两点校准用一瓶标准气比如1000ppm CO2和一瓶高浓度气5000ppm测出实际ADC输出再计算线性系数。这个值可以存储在STM32内部Flash的某个扇区利用工程里已经编译的stm32f10x_flash.c避免每次校准后重新编译固件。校准参数用结构体组织写入前先擦除整个扇区再按半字写入。#define CAL_SECTOR_ADDR 0x0803F000 void save_cal_param(sensor_cal_t *params) { FLASH_Unlock(); FLASH_ErasePage(CAL_SECTOR_ADDR); FLASH_ProgramWord(CAL_SECTOR_ADDR, *(uint32_t *)params-offset_hcho); FLASH_ProgramWord(CAL_SECTOR_ADDR 4, *(uint32_t *)params-offset_co2); FLASH_Lock(); }注意Flash写入前必须确保该页没有正在执行的代码读取数据否则总线卡死。把校准函数放在RAM里执行或者在擦写期间禁止中断可以将风险降到最低。这里使用最后一页作为参数区避开主程序空间如果使用更高容量的STM32地址要按实际Flash大小修改。本文还有配套的精品资源点击获取
返回列表