手把手教你能存储数据的环境监测系统: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 资源分配

// DHT11 数据:PA11
// MQ-2 烟雾:PA0(ADC1_CH0)
// MQ-7 燃气:PA1(ADC1_CH1)
// OLED IIC:SCL-PB10、SDA-PB11
// AT24C02 IIC:SCL-PB9、SDA-PB8
// 蜂鸣器:PA15
// LED:PA12
// 按键:SET-PB12、CHOICE-PB13、REDUCE-PB14、ADD-PB15

三、硬件设计

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; }

// 上电读取 AT24C02 保存的阈值
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); // 10ms 定时中断
while (1) { }
}

4.2 ADC + DMA 双通道采集

烟雾和燃气传感器都是模拟输出,使用 ADC1 的通道 0 和通道 1,配合 DMA1 通道 1 循环传输:

__IO uint16_t ADC_ConvertedValue[2];

void Init_adc(void)
{
// 开启 DMA1、ADC1、GPIOA 时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// PA0、PA1 配置为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// DMA1 通道 1 循环传输 2 个半字
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);

// ADC1 独立模式、扫描、连续转换
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++;

// 每 50ms 扫描一次按键
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);
}
}

// 每 500ms 读取一次 DHT11
if (TEMP_count >= 50)
{
DHT11_Read_Data(&temp, &humi);
TEMP_count = 0;
}

// ADC 累加,每 500ms 取平均并换算为 PPM
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;

// 每 500ms 刷新一次显示
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 键时写入:

// 写入 6 字节:温度阈值、湿度阈值、烟雾阈值高/低字节、燃气阈值高/低字节
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