ARTICLE DETAIL

资讯详情

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

【STM32入门项目】DHT11温湿度监测与声光报警系统

【STM32入门项目】DHT11温湿度监测与声光报警系统

【STM32 入门项目】DHT11 温湿度监测与声光报警系统

  • 前言
  • 一、项目功能
  • 二、硬件清单
  • 三、引脚连接
  • 四、项目实物与接线展示
    • 项目整体实物图
    • 硬件接线示意图
    • 搭建要点
  • 五、DHT11 通信原理
  • 六、工程模块划分工程按功能进行封装:
  • 七、DHT11 驱动代码
  • 八、蜂鸣器与 LED 控制
  • 九、按键读取按键使用内部上拉输入,按下时引脚被拉低。
  • 九、主程序逻辑主程序中设置了四个阈值:
  • 十、调试过程中遇到的问题
  • 总结

前言

最近使用 STM32F103C8T6 做了一个温湿度监测与报警小项目。项目使用 DHT11 采集温湿度数据,使用 0.96 英寸 OLED 实时显示温度、湿度和报警状态。当温湿度超过设定范围时,红灯亮、蜂鸣器间歇报警;正常状态下绿灯亮。同时加入了按键调节阈值和错误码显示。

这个项目比较适合 STM32 入门练习,主要涉及 GPIO 输入输出、DHT11 单总线通信、OLED 显示、蜂鸣器控制、LED 状态指示、按键读取以及模块化编程。

一、项目功能

本项目实现功能如下:

  • OLED 实时显示温度和湿度
  • 支持温度上限、温度下限、湿度上限、湿度下限判断
  • 超出阈值时红灯亮,蜂鸣器间歇报警
  • 正常范围内绿灯亮,蜂鸣器关闭
  • 使用两个按键调节报警阈值
  • DHT11 读取异常时显示不同错误状态
  • 按功能封装 DHT11、OLED、Buzzer、LED、Key 等模块

二、硬件清单

硬件说明
STM32F103C8T6主控芯片
DHT11温湿度传感器
0.96 英寸 OLEDI²C 显示屏
有源蜂鸣器低电平触发
红色 LED超阈值报警
绿色 LED正常状态
按键 x2调节阈值
ST‑Link程序下载
杜邦线、面包板硬件连接

三、引脚连接

四、项目实物与接线展示

项目整体实物图

示意图:STM32F103C8T6 最小系统板居中放置,左侧通过杜邦线连接 DHT11 温湿度传感器,右侧连接 0.96 英寸 OLED 显示屏,上方为蜂鸣器与红绿 LED,下方为两个按键,整体通过面包板完成供电与信号连接。

硬件接线示意图

示意图:以 STM32F103C8T6 为核心,DHT11 的 DATA 接 PA6,OLED 的 SCL/SDA 接 PB8/PB9,蜂鸣器接 PB12,红灯 LED1 接 PA1,绿灯 LED2 接 PA2,KEY1 接 PB1,KEY2 接 PB11,所有模块共地并接入 3.3V/5V 电源。

搭建要点

  • 先接电源与地线,确认 STM32 与面包板共地,避免信号电平不一致。
  • DHT11 的 DATA 线尽量短,减少干扰;上拉电阻可选用 4.7kΩ 至 10kΩ。
  • OLED 使用软件 I²C,SCL 与 SDA 不要接反,否则无法正常显示。
  • 蜂鸣器与 LED 均为低电平触发,接线时注意正负极方向。
  • 按键使用内部上拉,按下时引脚被拉低,接线时一端接 GPIO、另一端接地。
  • 上电前用万用表检查电源与地之间是否短路,再下载程序。
模块STM32 引脚说明
DHT11 DATAPA6单总线数据引脚
OLED SCLPB8软件 I²C 时钟线
OLED SDAPB9软件 I²C 数据线
蜂鸣器PB12低电平触发
红灯 LED1PA1低电平点亮
绿灯 LED2PA2低电平点亮
KEY1PB1低电平按下
KEY2PB11低电平按下

五、DHT11 通信原理

DHT11 使用单总线通信方式,只需要一根 DATA 线完成数据传输。

读取一次数据的大致流程如下:

  1. STM32 将 DATA 拉低至少 18 ms,发送起始信号。
  2. STM32 拉高 DATA 20~40 µs。
  3. STM32 将 DATA 引脚切换为输入模式。
  4. DHT11 响应低电平约 80 µs。
  5. DHT11 响应高电平约 80 µs。
  6. DHT11 连续发送 40 位数据。
  7. STM32 接收 5 个字节并进行校验。

DHT11 返回 5 个字节:

字节含义
Byte1湿度整数
Byte2湿度小数
Byte3温度整数
Byte4温度小数
Byte5校验和

校验方式:

check_sum==humi_int+humi_dec+temp_int+temp_dec
  • DHT11 每一位数据的判断方式:
  • 每一位开始先有约 50 µs 低电平
  • 高电平约 26~28us 表示 0
  • 高电平约 70us 表示 1

六、工程模块划分工程按功能进行封装:

文件功能
DHT11.c / DHT11.hDHT11 初始化、响应检测、数据读取
OLED.c / OLED.hOLED 显示
BUZZER.c / BUZZER.h蜂鸣器控制
LED.c / LED.h红绿灯控制
KEY.c / KEY.h按键读取
Delay.c / Delay.h微秒、毫秒延时
main.c主程序逻辑

七、DHT11 驱动代码

  1. 初始化与模式切换PA6 是 DHT11 的 DATA 引脚。由于 DHT11 是单总线通信,STM32 需要在输出模式和输入模式之间切换。
#include"stm32f10x.h"#include"Delay.h"voidDHT11_ModeOut(void){GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);}voidDHT11_ModeIn(void){GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);}voidDHT11_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);DHT11_ModeOut();GPIO_SetBits(GPIOA,GPIO_Pin_6);}

这里使用开漏输出 GPIO_Mode_Out_OD,适合这种一根数据线由双方轮流控制的通信方式。

  1. 发送起始信号
voidDHT11_Start(void){DHT11_ModeOut();GPIO_ResetBits(GPIOA,GPIO_Pin_6);Delay_ms(20);GPIO_SetBits(GPIOA,GPIO_Pin_6);Delay_us(30);}
  1. 检测 DHT11 响应
uint8_tDHT11_Check(void){uint8_tretry=0;while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==SET){retry++;Delay_us(1);if(retry>100)return1;}retry=0;while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==RESET){retry++;Delay_us(1);if(retry>100)return1;}retry=0;while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==SET){retry++;Delay_us(1);if(retry>100)return1;}return0;}

返回值含义:
0:响应正常
1:无响应
这个函数依次等待:

  • DHT11 拉低
  • DHT11 拉高
  • DHT11 再次拉低,进入数据发送阶段
  1. 读取 1 bit 数据
uint8_tDHT11_ReadBit(void){uint8_tbit=0;while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==RESET);Delay_us(35);if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==RESET){bit=0;}else{bit=1;while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==SET);}returnbit;}

这里的核心是延时 35us 后判断电平:

  • 如果已经变低,说明高电平时间短,该位为 0
  • 如果仍然是高电平,说明高电平时间长,该位为 1
    需要注意:如果读到 1,要等待高电平结束,否则下一位可能会错位,导致校验失败。
  1. 读取 1 byte 数据
uint8_tDHT11_ReadByte(void){uint8_tByte=0x00;uint8_ti;for(i=0;i<8;i++){Byte=Byte<<1;Byte|=DHT11_ReadBit();}returnByte;}

DHT11 是高位先发送,所以每读一位,就将当前字节左移一位,再把新读到的 bit 放到最低位。
6. 读取完整温湿度数据

uint8_tDHT11_ReadData(uint8_t*temp,uint8_t*humi){uint8_thumi_int;uint8_thumi_dec;uint8_ttemp_int;uint8_ttemp_dec;uint8_tcheck_sum;DHT11_Start();DHT11_ModeIn();if(DHT11_Check()==1){return1;}humi_int=DHT11_ReadByte();humi_dec=DHT11_ReadByte();temp_int=DHT11_ReadByte();temp_dec=DHT11_ReadByte();check_sum=DHT11_ReadByte();if((humi_int+humi_dec+temp_int+temp_dec)!=check_sum){return2;}*temp=temp_int;*humi=humi_int;return0;}

返回值含义:

返回值含义
0读取成功
1DHT11 无响应
2校验失败

八、蜂鸣器与 LED 控制

  1. 蜂鸣器控制蜂鸣器为低电平触发,因此拉低响,拉高关闭。
voidBUZZER_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_INSTRUCTURE;GPIO_INSTRUCTURE.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_INSTRUCTURE.GPIO_Pin=GPIO_Pin_12;GPIO_INSTRUCTURE.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_INSTRUCTURE);GPIO_SetBits(GPIOB,GPIO_Pin_12);}voidBUZZER_ON(void){GPIO_ResetBits(GPIOB,GPIO_Pin_12);}voidBUZZER_OFF(void){GPIO_SetBits(GPIOB,GPIO_Pin_12);}
  1. LED 控制
    LED 同样采用低电平点亮。
voidLED_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_INSTRUCTURE;GPIO_INSTRUCTURE.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_INSTRUCTURE.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2;GPIO_INSTRUCTURE.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_INSTRUCTURE);GPIO_SetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2);}voidLED1_ON(void){GPIO_ResetBits(GPIOA,GPIO_Pin_1);}voidLED1_OFF(void){GPIO_SetBits(GPIOA,GPIO_Pin_1);}voidLED2_ON(void){GPIO_ResetBits(GPIOA,GPIO_Pin_2);}voidLED2_OFF(void){GPIO_SetBits(GPIOA,GPIO_Pin_2);}

本项目中:
LED1 接红灯,
LED2 接绿灯。

九、按键读取按键使用内部上拉输入,按下时引脚被拉低。

voidKey_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_INITSTRUCTURE;GPIO_INITSTRUCTURE.GPIO_Mode=GPIO_Mode_IPU;GPIO_INITSTRUCTURE.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_11;GPIO_INITSTRUCTURE.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_INITSTRUCTURE);}uint8_tKey_GetNum(void){uint8_tKeyNum=0;if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){Delay_ms(20);while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);Delay_ms(20);KeyNum=1;}if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0){Delay_ms(20);while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);Delay_ms(20);KeyNum=2;}returnKeyNum;}

按键功能设计:

按键功能
KEY1切换当前设置项
KEY2当前阈值加 1

九、主程序逻辑主程序中设置了四个阈值:

uint8_ttemp_high=40;uint8_ttemp_low=20;uint8_thumi_high=65;uint8_thumi_low=30;

分别表示:

变量含义
temp_high温度上限
temp_low温度下限
humi_high湿度上限
humi_low湿度下限

主函数如下:

#include"stm32f10x.h"#include"Delay.h"#include"OLED.h"#include"BUZZER.h"#include"DHT11.h"#include"LED.h"#include"KEY.h"uint8_ttemp,humi,status;uint8_ttemp_high=40;uint8_ttemp_low=20;uint8_thumi_high=65;uint8_thumi_low=30;uint8_tKeyNum;uint8_tset_item=0;voidShow_SetItem(void){if(set_item==0){OLED_ShowString(4,1,"Set TH:");OLED_ShowNum(4,8,temp_high,2);OLED_ShowString(4,10," ");}elseif(set_item==1){OLED_ShowString(4,1,"Set TL:");OLED_ShowNum(4,8,temp_low,2);OLED_ShowString(4,10," ");}elseif(set_item==2){OLED_ShowString(4,1,"Set HH:");OLED_ShowNum(4,8,humi_high,2);OLED_ShowString(4,10," ");}else{OLED_ShowString(4,1,"Set HL:");OLED_ShowNum(4,8,humi_low,2);OLED_ShowString(4,10," ");}}intmain(void){OLED_Init();BUZZER_Init();DHT11_Init();LED_Init();Key_Init();while(1){KeyNum=Key_GetNum();if(KeyNum==1){set_item++;if(set_item>3){set_item=0;}}elseif(KeyNum==2){if(set_item==0){temp_high++;}elseif(set_item==1){temp_low++;}elseif(set_item==2){humi_high++;}else{humi_low++;}}status=DHT11_ReadData(&temp,&humi);if(status==0){OLED_ShowString(1,1,"humi: % ");OLED_ShowString(2,1,"temp: C ");OLED_ShowNum(1,6,humi,2);OLED_ShowNum(2,6,temp,2);if(temp>temp_high||temp<temp_low||humi>humi_high||humi<humi_low){LED1_ON();LED2_OFF();OLED_ShowString(3,1,"Alarm: ON ");BUZZER_ON();Delay_ms(200);BUZZER_OFF();Delay_ms(200);Show_SetItem();}else{LED2_ON();LED1_OFF();BUZZER_OFF();OLED_ShowString(3,1,"Alarm: OFF ");Show_SetItem();}}elseif(status==1){BUZZER_OFF();LED1_OFF();LED2_OFF();OLED_ShowString(1,1,"NO RESPONSE ");OLED_ShowString(2,1,"Code:1 ");OLED_ShowString(3,1,"Sensor Error ");OLED_ShowString(4,1," ");}elseif(status==2){BUZZER_OFF();LED1_OFF();LED2_OFF();OLED_ShowString(1,1,"CHECKSUM ERROR ");OLED_ShowString(2,1,"Code:2 ");OLED_ShowString(3,1,"Data Error ");OLED_ShowString(4,1," ");}else{BUZZER_OFF();LED1_OFF();LED2_OFF();OLED_ShowString(1,1,"UNKNOWN ERROR ");OLED_ShowString(2,1," ");OLED_ShowString(3,1," ");OLED_ShowString(4,1," ");}Delay_ms(1000);}}

程序逻辑说明:

  1. 读取按键,判断是否需要切换或修改阈值。
  2. 读取 DHT11 温湿度。
  3. 如果读取成功,显示温湿度。
  4. 判断温湿度是否超出上下限。
  5. 超限时红灯亮、蜂鸣器间歇报警。
  6. 正常时绿灯亮、蜂鸣器关闭。
  7. 如果 DHT11 读取失败,显示对应错误码。

十、调试过程中遇到的问题

  1. DHT11 第一次检测成功,后面一直失败
    现象:复位后第一次 OLED 显示 correct,后面一直 error
    原因:
    第一次读取后,PA6 被切换成输入模式。下一次开始通信时,如果没有重新切回输出模式,STM32 就无法拉低 DATA 发送起始信号。
    解决方法:
    DHT11_Start()函数开头加入:
DHT11_ModeOut();
  1. status = 2,校验失败
    现象:
    DHT11 有响应,但是校验失败
    原因:
    读取 bit 时,如果该位是 1,35us 采样时 DATA 仍然处于高电平。如果此时直接返回,下一次读取会提前进入,导致数据错位。
    解决方法:
    读到 1 后等待高电平结束:
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==SET);
  1. 蜂鸣器低于阈值后仍然响
    原因:
    GPIO 输出状态会保持上一次写入的状态。调用BUZZER_ON()后,如果后续分支没有明确调用BUZZER_OFF(),蜂鸣器会继续响。
    解决方法:
    在正常、报警、错误分支中都明确控制蜂鸣器状态。
  2. OLED_Clear()放在循环中导致闪烁
    原因:
    OLED_Clear()会整屏清空,如果在主循环中频繁调用,会出现明显闪烁。
    解决方法:
    使用空格覆盖旧字符,而不是每次清屏。例如:
OLED_ShowString(1,1,"humi: % ");OLED_ShowString(2,1,"temp: C ");

总结

通过这个项目,我熟悉了 STM32F103C8T6 的 GPIO 输入输出、DHT11 单总线时序、OLED 显示、蜂鸣器控制、LED 状态指示、按键读取以及模块化编程方法。
虽然项目整体不算复杂,但它完整地包含了硬件连接、外设配置、传感器读取、数据显示、报警控制、按键调参和问题调试等过程,非常适合作为 STM32 入门阶段的综合练习项目。
后续可以继续扩展:
1、使用定时器替代阻塞延时
2、使用外部中断读取按键
3、将阈值保存到 Flash,断电不丢失
4、通过串口输出温湿度数据
5、加入 ADC 采集其他模拟传感器数据

返回列表