手把手教你能存储数据的环境监测系统:DHT11 + 烟雾 + 燃气 + OLED + AT24C02
这是一个 STM32F103C8T6 小项目,能同时监测环境温度、湿度、烟雾浓度和燃气浓度,本地 OLED 实时显示,超限蜂鸣器 + LED 声光报警,阈值掉电保存在 AT24C02 中。硬件简单、代码清晰,很适合作为单片机课程设计或毕业设计参考。
一、项目背景与功能
系统面向厨房、仓库、实验室等场景,核心需求如下:
- 温湿度监测:DHT11 采集温度和湿度。
- 烟雾监测:MQ-2 烟雾传感器检测烟雾浓度。
- 燃气监测:MQ-7 / 燃气传感器检测一氧化碳/燃气浓度。
- 本地显示:0.96 寸 IIC OLED 实时显示四路数据。
- 声光报警:温度、湿度、烟雾、燃气任一超过阈值,蜂鸣器和 LED 同时报警。
- 阈值可调:4 个独立按键设置温度/湿度/烟雾/燃气报警阈值。
- 掉电保存:阈值写入 AT24C02,重新上电自动恢复。
二、硬件清单
| 元器件 |
数量 |
说明 |
| STM32F103C8T6 最小系统板 |
1 |
主控 MCU,72MHz Cortex-M3 |
| 0.96 寸 OLED 显示屏 |
1 |
IIC 接口,128×64 分辨率 |
| DHT11 温湿度传感器 |
1 |
单总线协议 |
| MQ-2 烟雾传感器 |
1 |
模拟输出,接 PA0 |
| MQ-7 燃气传感器 |
1 |
模拟输出,接 PA1 |
| AT24C02 存储芯片 |
1 |
IIC 接口,掉电保存阈值 |
| 有源蜂鸣器 |
1 |
PA15 驱动 |
| LED |
1 |
PA12 驱动 |
| 独立按键 |
4 |
PB12~PB15,上拉输入 |
| DC-005 电源接口 |
1 |
5V 输入供电 |
IO 资源分配
三、硬件设计
3.1 原理图

原理图分为几个区域:
- 输入供电:DC-005 接口 + 330uF/100nF 滤波,5V 转 3.3V 供 MCU 和传感器。
- 主控开发板:STM32F103C8T6 最小系统,引出 ADC、IIC、GPIO 资源。
- 传感器接口:DHT11 接 PA11;MQ-2/MQ-7 模拟量分别接 PA0、PA1。
- OLED 屏幕:IIC 接口接 PB10/PB11。
- 存储模块:AT24C02 接 PB8/PB9,4.7K 上拉电阻。
- 蜂鸣器与光报警:PA15 驱动蜂鸣器,PA12 驱动 LED。
- 按键:4 个独立按键共用下拉方式,接 PB12~PB15。
四、软件设计
4.1 主循环
main.c 完成系统初始化、阈值读取和主循环等待:
#include "stm32f10x.h" #include "main.h" #include "delay.h" #include "key.h" #include "beep.h" #include "oled.h" #include "show.h" #include "dht11.h" #include "adc.h" #include "led.h" #include "timer1.h" #include "special_pin.h" #include "24cxx.h"
u8 show_mode; u8 baojing_status; u8 tiaozheng_status; u16 smooth_data, smooth_yuzhi = 1000; u16 ranqi_data, ranqi_yuzhi = 500; u8 temp, temp_yuzhi = 30; u8 humi, humi_yuzhi = 70; u8 cunchu_shuzu[6];
int main(void) { u8 datatemp[6]; delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); SPECIAL_PIN_Init();
LED_Init(); BEEP_Init(); Init_adc(); KEY_Init(); while (DHT11_Init()) { Delay_Ms(200); } AT24CXX_Init(); while (AT24CXX_Check()) { Delay_Ms(500); LED = !LED; }
AT24CXX_Read(0, datatemp, 6); temp_yuzhi = datatemp[0]; humi_yuzhi = datatemp[1]; smooth_yuzhi = datatemp[2] * 256 + datatemp[3]; ranqi_yuzhi = datatemp[4] * 256 + datatemp[5];
OLED_Init(); OLED_Clear(); page_one(); TIM1_Time_Init(99, 7199); while (1) { } }
|
4.2 ADC + DMA 双通道采集
烟雾和燃气传感器都是模拟输出,使用 ADC1 的通道 0 和通道 1,配合 DMA1 通道 1 循环传输:
__IO uint16_t ADC_ConvertedValue[2];
void Init_adc(void) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 2; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_NbrOfChannel = 2; ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5);
ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }
|
4.3 定时器中断:数据采集 + 按键 + 显示 + 报警
TIM1 每 10ms 进入一次更新中断,完成所有周期性任务:
void TIM1_UP_IRQHandler(void) { static u8 KEY_count = 0, ADC_count = 0; static u8 TEMP_count = 0, MESSAGE_count = 0; static float smooth_zong = 0.0f, ranqi_zong = 0.0f; u8 key;
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { TEMP_count++; KEY_count++; ADC_count++; MESSAGE_count++;
if (KEY_count >= 5) { KEY_count = 0; key = KEY_Scan(0); if (key == SET_PRES) { OLED_Clear(); show_mode = (show_mode + 1) % 5; if (show_mode == 0) page_one(); else if (show_mode == 1) page_two(); else if (show_mode == 2) page_three(); else if (show_mode == 3) page_four(); else if (show_mode == 4) page_five(); } else if (key == CHOICE_PRES) { if (show_mode != 0) tiaozheng_status = !tiaozheng_status; else { cunchu_shuzu[0] = temp_yuzhi; cunchu_shuzu[1] = humi_yuzhi; cunchu_shuzu[2] = smooth_yuzhi / 256; cunchu_shuzu[3] = smooth_yuzhi % 256; cunchu_shuzu[4] = ranqi_yuzhi / 256; cunchu_shuzu[5] = ranqi_yuzhi % 256; AT24CXX_Write(0, (u8*)cunchu_shuzu, 6); } } else if (key == ADD_PRES) { if (show_mode == 1) temp_yuzhi += (tiaozheng_status ? 10 : 1); else if (show_mode == 2) humi_yuzhi += (tiaozheng_status ? 10 : 1); else if (show_mode == 3) smooth_yuzhi += (tiaozheng_status ? 100 : 10); else if (show_mode == 4) ranqi_yuzhi += (tiaozheng_status ? 100 : 10); } else if (key == REDUCE_PRES) { if (show_mode == 1) temp_yuzhi -= (tiaozheng_status ? 10 : 1); else if (show_mode == 2) humi_yuzhi -= (tiaozheng_status ? 10 : 1); else if (show_mode == 3) smooth_yuzhi -= (tiaozheng_status ? 100 : 10); else if (show_mode == 4) ranqi_yuzhi -= (tiaozheng_status ? 100 : 10); } }
if (TEMP_count >= 50) { DHT11_Read_Data(&temp, &humi); TEMP_count = 0; }
adc1 = (float)ADC_ConvertedValue[0] / 4096; adc2 = (float)ADC_ConvertedValue[1] / 4096; smooth_zong += adc1; ranqi_zong += adc2; if (ADC_count >= 50) { ADC_count = 0; smooth_data = smooth_zong / 50 * 6600; ranqi_data = ranqi_zong / 50 * 3300; smooth_zong = 0; ranqi_zong = 0; }
if (smooth_data >= smooth_yuzhi || temp >= temp_yuzhi || humi >= humi_yuzhi || ranqi_data >= ranqi_yuzhi) baojing_status = 1; else baojing_status = 0;
if (baojing_status) BEEP = 1, LED = 1; else BEEP = 0, LED = 0;
if (MESSAGE_count >= 50) { MESSAGE_count = 0; if (show_mode == 0) { OLED_ShowNum(72, 0, temp, 3, 16); OLED_ShowNum(72, 2, humi, 3, 16); OLED_ShowNum(72, 4, smooth_data, 4, 16); OLED_ShowNum(72, 6, ranqi_data, 4, 16); } else if (show_mode == 1) OLED_ShowNum(36, 4, temp_yuzhi, 3, 16); else if (show_mode == 2) OLED_ShowNum(36, 4, humi_yuzhi, 3, 16); else if (show_mode == 3) OLED_ShowNum(36, 4, smooth_yuzhi, 4, 16); else if (show_mode == 4) OLED_ShowNum(36, 4, ranqi_yuzhi, 4, 16); }
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); } }
|
4.4 AT24C02 掉电保存
阈值在上电时从 AT24C02 读取,在首页按 CHOICE 键时写入:
AT24CXX_Write(0, (u8*)cunchu_shuzu, 6);
|
这样即使断电,用户设置的报警阈值也不会丢失。
4.5 显示页面
系统有 5 个显示页面:
- page_one:首页,显示当前温度、湿度、烟雾 PPM、燃气 PPM。
- page_two:温度阈值设置页。
- page_three:湿度阈值设置页。
- page_four:烟雾阈值设置页。
- page_five:燃气阈值设置页。
按 SET 键循环切换页面,在阈值页按 CHOICE 切换粗调/细调,ADD/REDUCE 调整数值。
五、实物展示

实物板采用绿色 PCB,中间放置 STM32F103C8T6 最小系统板,右侧为 0.96 寸 OLED,下方为 4 个独立按键,左侧为蜂鸣器和 LED,外接 MQ 系列传感器模块和 DHT11。上电后首页实时显示温度、湿度、烟雾浓度、燃气浓度。
六、功能特点
| 功能 |
说明 |
| 四路监测 |
温度、湿度、烟雾、燃气同时采集 |
| 本地显示 |
0.96 寸 OLED 五页菜单,数据/阈值一目了然 |
| 声光报警 |
任意一路超阈值,蜂鸣器 + LED 同时响应 |
| 阈值可调 |
4 按键设置温度/湿度/烟雾/燃气报警阈值 |
| 掉电保存 |
AT24C02 保存阈值,重启自动恢复 |
| 定时调度 |
TIM1 10ms 中断统一处理按键、ADC、DHT11、显示、报警 |
| 成本可控 |
整机 BOM 约 80~120 元,适合课程设计复刻 |
七、项目总结
这个项目完整覆盖了一个嵌入式小产品的常见要素:
- ADC + DMA:双通道模拟量采集。
- 单总线协议:DHT11 温湿度读取。
- IIC 总线:OLED 显示 + AT24C02 存储。
- 定时器中断:多任务周期调度。
- GPIO 输入/输出:按键、蜂鸣器、LED。
- 人机交互:OLED 菜单 + 独立按键。
无论是课程设计、毕业设计,还是想快速搭建一个环境监测原型,这套方案都可以直接参考复刻。
资料下载
完整资料包含:
- STM32 工程源码(Keil 标准库工程)
- 原理图 PDF
- 嘉立创 EDA 工程文件
- 测试视频
- 硬件清单 BOM
资料链接见博客侧边栏头像下方的 资料链接(下载图标)处。
通过网盘分享的文件:嵌入式-手把手教你能存储数据的环境监测系统
链接: https://pan.baidu.com/s/1VPzLpk2IzHB2c1n_O80EHQ?pwd=u1ab 提取码: u1ab