前言

最近做了一套基于 STM32F103C8T6 的 DHT11 采集工程,搭配 I2C 0.96 寸 OLED 实时显示温湿度,踩了很多单总线时序、IO 方向切换、数据校验的坑,整理完整原理、硬件接线、通信时序、底层驱动代码逻辑,零基础也能看懂,工程文件分层清晰,可直接移植使用。

演示视频:STM32F103驱动DHT11温湿度传感器+0.96寸OLED显示完整工程详解(单总线时序+源码解析)https://www.bilibili.com/video/BV1N9MG6PEDQ/?share_source=copy_web&vd_source=91b967b41a17a5686913521545a8c3fb&t=0

一、硬件整体方案

1. 使用器件
  • 主控:STM32F103C8T6
  • 传感器:DHT11 数字温湿度模块(内置 8 位单片机、湿敏 / 测温元件,自带校准)
  • 显示:0.96 寸 I2C OLED 屏幕

2. 核心特点
  1. DHT11 采用单总线通信,仅 1 根 DATA 数据线完成主机下发指令、从机回传数据
  2. 纯数字输出,无需 ADC 采集,单次通信输出固定 40bit 数据
  3. 数据自带校验和,有效规避干扰导致的读数错误
  4. 推荐采集周期≥1s,频繁读取会造成传感器响应异常
3. 硬件接线(本工程固定引脚)
DHT11 引脚 STM32 引脚 说明
VCC 3.3V 可接5V 供电,具体需看说明书
GND GND 共地必须稳定
DATA PA11 单总线通信引脚,代码全局宏定义

OLED 屏幕接线:

  • SCL → PB8
  • SDA → PB9
  • VCC/GND 接 3.3V 与系统地
接线注意事项
  1. 成品 DHT11 模块内部自带 4.7K 上拉电阻,无需额外焊接;裸片传感器必须在 DATA 与 VCC 之间接 4.7k~10k 上拉电阻
  2. 时序依赖微秒级精准延时,delay_us()延时函数必须校准准确,否则会出现读不到应答、数据乱码
  3. 走线尽量短,长距离传输容易受电磁干扰导致校验失败

二、工程文件分层结构(Keil MDK 工程)

OLED.uvprojx // Keil工程文件
├─ USER
│  ├─ main.c // 主循环,周期读取DHT11、OLED温湿度/错误显示
├─ HARDWARE
│  ├─ DHT11
│  │  ├─ dht11.c // 复位、应答检测、位/字节读取、数据解析
│  │  └─ dht11.h // IO输入输出宏、引脚定义
│  └─ OLED
│     └─ oled.c // I2C OLED底层显示驱动

三、DHT11 单总线通信核心原理

单总线最大特性:同一根数据线双向通信,因此程序中需要动态切换 PA11 的 GPIO 输入 / 输出模式。

  1. STM32 发送启动信号:PA11 配置为推挽输出
  2. 等待 DHT11 回传数据:PA11 切换为上拉输入
dht11.h IO 模式宏解析
// PA11设为输入模式
#define DHT11_IO_IN()  {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00008000;}
// PA11设为50MHz推挽输出
#define DHT11_IO_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00003000;}
#define DHT11_DQ_OUT PAout(11) // 输出电平控制
#define DHT11_DQ_IN  PAin(11)  // 读取输入电平

PA11 属于 GPIOA 高 8 位,操作寄存器CRH,bit15~bit12 控制 PA11 模式:

  • 0x8:上拉输入;0x3:50MHz 推挽输出

四、完整通信时序拆解

1. 主机启动复位时序 DHT11_Rst ()

STM32 主动下发唤醒信号,步骤:

  1. PA11 切换输出,拉低 DATA 20ms(大于 18ms 标准启动时长)
  2. 拉高 DATA,保持 30us
  3. IO 切换输入,等待 DHT11 应答
void DHT11_Rst(void)
{
    DHT11_IO_OUT();
    DHT11_DQ_OUT=0;
    delay_ms(20);
    DHT11_DQ_OUT=1;
    delay_us(30);
}
2. 从机应答检测 DHT11_Check ()

复位完成后 DHT11 自动回复应答波形:

  • 先拉低 DATA 40~80us,再拉高 40~80us
  • 函数返回 0 = 应答正常;返回 1 = 未检测到传感器(接线 / 供电故障)
3. 单 bit 数据读取逻辑 DHT11_Read_Bit ()

DHT11 每一位数据起始固定低电平 50us,通过高电平持续时间区分 0/1:

  • 高电平短 = 数据 0
  • 高电平长 = 数据 1 读取逻辑:等待电平下降→等待电平上升→延时 40us 采样当前电平判断数值
4. 单字节读取 DHT11_Read_Byte ()

单次读取 8bit,高位先行,循环读取 8 次 bit,左移拼接成完整字节。

五、40bit 数据帧格式与校验机制

DHT11 一次传输固定 5 字节(40bit),数据定义:

字节 含义
buf[0] 湿度整数
buf[1] 湿度小数(DHT11 固定为 0)
buf[2] 温度整数
buf[3] 温度小数(DHT11 固定为 0)
buf[4]

校验和

校验规则:buf[4] = buf[0]+buf[1]+buf[2]+buf[3] 校验不匹配代表本次数据受干扰,直接判定读取失败,主程序会重新初始化传感器重试。

六、驱动初始化流程 DHT11_Init ()

  1. 开启 GPIOA 外设时钟
  2. PA11 初始化为推挽输出,默认高电平
  3. 发送复位启动信号
  4. 检测传感器应答,返回初始化状态

dht11.c

#include "dht11.h"
#include "delay.h"

//复位DHT11
void DHT11_Rst(void)	   
{                 
		DHT11_IO_OUT(); 	//SET OUTPUT
    DHT11_DQ_OUT=0; 	//拉低DQ
    delay_ms(20);    	//拉低至少18ms
    DHT11_DQ_OUT=1; 	//DQ=1 
		delay_us(30);     	//主机拉高20~40us
}

//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void) 	   
{   
	u8 retry=0;
	DHT11_IO_IN();//SET INPUT	 
    while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=100)return 1;
	else retry=0;
    while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
	{
		retry++;
		delay_us(1);
	};
	if(retry>=100)return 1;	    
	return 0;
}

//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
 	u8 retry=0;
	while(DHT11_DQ_IN&&retry<100)//等待变为低电平
	{
		retry++;
		delay_us(1);
	}
	retry=0;
	while(!DHT11_DQ_IN&&retry<100)//等待变高电平
	{
		retry++;
		delay_us(1);
	}
	delay_us(40);//等待40us
	if(DHT11_DQ_IN)return 1;
	else return 0;		   
}

//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)    
{        
    u8 i,dat;
    dat=0;
	for (i=0;i<8;i++) 
	{
   		dat<<=1; 
	    dat|=DHT11_Read_Bit();
    }						    
    return dat;
}

//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败

u8 DHT11_Read_Data(u8 *humiH,u8 *humiL,u8 *tempH,u8 *tempL)
{
	u8 buf[5];
	u8 i;

	DHT11_Rst();
	if(DHT11_Check()!=0)
	{
		return 1;
	}

	for(i=0;i<5;i++)
	{
		buf[i]=DHT11_Read_Byte();
	}

	if((buf[0]+buf[1]+buf[2]+buf[3])!=buf[4])
	{
		return 1;
	}

	*humiH=buf[0];
	*humiL=buf[1];
	*tempH=buf[2];
	*tempL=buf[3];

	return 0;
}

// Init DHT11 IO and check presence.
u8 DHT11_Init(void)
{	 
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 //使能PA端口时钟
	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;				 //PA11端口配置
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);				 //初始化IO口
 	GPIO_SetBits(GPIOA,GPIO_Pin_11);						 //PA11 输出高
			    
	DHT11_Rst();  //复位DHT11
	return DHT11_Check();//等待DHT11的回应
} 

dht11.h

#ifndef __DHT11_H
#define __DHT11_H	 
#include "sys.h"
//IO方向设置
#define DHT11_IO_IN()  {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00008000;}
#define DHT11_IO_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00003000;}
////IO操作函数											   
#define	DHT11_DQ_OUT PAout(11) //数据端口	PA11出方向 
#define	DHT11_DQ_IN  PAin(11)  //数据端口	PA11入方向


u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *humiH,u8 *humiL,u8 *tempH,u8 *tempL);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11  
		 				    
#endif

七、主程序运行逻辑 main.c

主循环 1s 采集一次 DHT11 数据:

  1. 读取温湿度数据,校验校验和
  2. 读取成功:OLED 打印温湿度数值
  3. 读取失败:OLED 打印报错信息,重新初始化 DHT11 重试
while(1)
{
    if(DHT11_Read_Data(&humiH,&humiL,&tempH,&tempL)==0)
    {
        Show_DHT11_Data(humiH,humiL,tempH,tempL);
    }
    else
    {
        Show_DHT11_Error();
        DHT11_Init();
    }
    delay_ms(1000);
}

main.c

// Function: show DHT11 temperature and humidity on SSD1306 OLED.
// Wiring:
//   OLED SCL -> PB8
//   OLED SDA -> PB9
//   DHT11 DATA -> PA11
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "dht11.h"

#define SENSOR_REFRESH_MS 1000

static void Show_DHT11_Data(u8 humiH,u8 humiL,u8 tempH,u8 tempL);
static void Show_DHT11_Error(void);

int main(void)
{
	u8 humiH=0;
	u8 humiL=0;
	u8 tempH=0;
	u8 tempL=0;

	delay_init();
	delay_ms(1000);
	NVIC_Configuration();
	OLED_Init();
	OLED_ColorTurn(0);
	OLED_DisplayTurn(0);
	DHT11_Init();

	while(1)
	{
		if(DHT11_Read_Data(&humiH,&humiL,&tempH,&tempL)==0)
		{
			Show_DHT11_Data(humiH,humiL,tempH,tempL);
		}
		else
		{
			Show_DHT11_Error();
			DHT11_Init();
		}

		delay_ms(SENSOR_REFRESH_MS);
	}
}

static void Show_DHT11_Data(u8 humiH,u8 humiL,u8 tempH,u8 tempL)
{
	OLED_Clear();
	OLED_ShowString(20,0,(u8 *)"DHT11 PA11",16);

	OLED_ShowString(0,18,(u8 *)"Temp:",16);
	OLED_ShowNum(48,18,tempH,2,16);
	OLED_ShowString(64,18,(u8 *)".",16);
	OLED_ShowNum(72,18,tempL,1,16);
	OLED_ShowString(88,18,(u8 *)"C",16);

	OLED_ShowString(0,34,(u8 *)"Humi:",16);
	OLED_ShowNum(48,34,humiH,2,16);
	OLED_ShowString(64,34,(u8 *)".",16);
	OLED_ShowNum(72,34,humiL,1,16);
	OLED_ShowString(88,34,(u8 *)"%",16);

	OLED_ShowString(0,52,(u8 *)"Read OK",12);
	OLED_Refresh();
}

static void Show_DHT11_Error(void)
{
	OLED_Clear();
	OLED_ShowString(20,0,(u8 *)"DHT11 PA11",16);
	OLED_ShowString(8,22,(u8 *)"Sensor Error",16);
	OLED_ShowString(0,46,(u8 *)"Check DATA/VCC",12);
	OLED_Refresh();
}

OLED 正常显示内容:

DHT11 PA11
Temp: xx.x C
Humi: xx.x %
Read OK

故障报错页面:

DHT11 PA11
Sensor Error
Check DATA/VCC

八、常见踩坑总结

  1. 延时不准:微秒延时误差大,直接读不到应答,必须校准 delay_us
  2. IO 忘记切换模式:一直输出无法接收传感器数据,一直输入无法下发启动信号
  3. 采集间隔过短:小于 1s 频繁读取,传感器无响应、数据漂移
  4. 忽略校验和:干扰下会出现离谱温湿度数值,校验可过滤无效数据
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐