DHT11 简介

DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。DHT11 有 3 脚和 4 脚两款,在使用上没有差别,接线都一样,主要接三根,四脚的款式有一脚悬空。

img

典型接线线路如下图:

image.png

引脚:

Pin 名称 注释
1 VDD 供电 3-5.5V
2 GND 接地,电源负极
3 DATA 串行数据,单总线
4 NC 空脚,请悬空

DHT11 采样频率为 1HZ,建议平均 1~2s 读取一次数据。

单总线协议

DHT11 采用单总线协议与单片机通信,也就是使用一根 DATA 线进行数据的收发。单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。

数据格式(共 40bit):

  1. 8bit 湿度整数数据
  2. 8bit 湿度小数数据
  3. 8bit 温度整数数据
  4. 8bit 温度小数数据
  5. 8bit 校验和

image.png

DHT11 的 DATA 传输一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。

DHT11 只有在接收到开始信号后才触发一次温湿度采集,如果没有接收到主机发送复位信号,DHT11 不主动进行温湿度采集。当数据采集完毕且无开始信号后,DHT11 自动切换到低速模式。

初始化

DHT11 初始化过程分为:

  • 主机发送复位信号:首先主机拉低总线至少 18ms,然后再拉高总线,延时 20~40us,取中间值 30us,此时复位信号发送完毕。
  • DHT11 发送响应信号:DHT11 检测到复位信号后,触发一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了;然后 DHT11 拉高总线 80us,之后开始传输数据。

注意,主机一开始拉低总线是 18 毫秒,不是 18 微秒。

从模式下,DHT11 接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11 不会主动进行温湿度采集。采集数据后转换到低速模式。

实际上 DHT11 的响应时间并不是标准的 80us,往往存在误差,当响应时间处于 20~100us 之间时就可以认定响应成功。

DHT11 传输数据

DHT11 在拉高总线 80us 后开始传输数据。每 1bit 数据都以 50us 低电平时隙开始,告诉主机开始传输一位数据了。DHT11 以高电平的长短定义数据位是 0 还是 1,当 50us 低电平时隙过后拉高总线,高电平持续 26~28us 表示数据“0”;持续 70us 表示数据“1”。

当 最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,表示数据传输完毕,随后总线由上拉电阻拉高进入空闲状态。

区分 0 和 1 的方法:当数据位之前的 50us 低电平时隙过后,总线肯定会拉高,此时延时 40us 后检测总线状态,如果为高,说明此时处于 70us 的时隙,则数据为“1”;如果为低,说明此时处于下一位数据 50us 的开始时隙,那么上一位数据肯定是“0”。

驱动实现

标准库

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _DHT11_H
#define _DHT11_H
#include "stm32f10x.h" // Device header

void DHT11_GPIO_Init_OUT(void);
void DHT11_GPIO_Init_IN(void);
void DHT11_Start(void);
unsigned char DHT11_REC_Byte(void);
void DHT11_REC_Data(void);

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include "stm32f10x.h"                  // Device header
#include "dht11.h"
#include "SysTick.h" // 延时函数
//数据
unsigned int rec_data[4];

// GPIOB
#define DHT11_GPIO_Pin_x GPIO_Pin_11

#define dht11_high GPIO_SetBits(GPIOB, DHT11_GPIO_Pin_x)
#define dht11_low GPIO_ResetBits(GPIOB, DHT11_GPIO_Pin_x)
#define Read_Data GPIO_ReadInputDataBit(GPIOB, DHT11_GPIO_Pin_x)

//对于stm32来说,是输出
void DH11_GPIO_Init_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_Pin_x;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//对于stm32来说,是输入
void DH11_GPIO_Init_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_Pin_x;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//主机发送开始信号
void DHT11_Start(void)
{
DH11_GPIO_Init_OUT(); //输出模式

dht11_high; //先拉高
Delay_us(30);

dht11_low; //拉低电平至少18us
Delay_ms(20); // ms 没错

dht11_high; //拉高电平20~40us
Delay_us(30);

DH11_GPIO_Init_IN(); //输入模式
}

//获取一个字节
char DHT11_Rec_Byte(void)
{
unsigned char i = 0;
unsigned char data;

for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit
{
while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束
Delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us

data <<= 1; //左移

if( Read_Data == 1 ) //如果过了30us还是高电平的话就是数据1
{
data |= 1; //数据+1
}

while( Read_Data == 1 ); //高电平变低电平,等待高电平结束
}

return data;
}

//获取数据

void DHT11_REC_Data(void)
{
unsigned int R_H,R_L,T_H,T_L;
unsigned char RH,RL,TH,TL,CHECK;

DHT11_Start(); //主机发送信号
dht11_high; //拉高电平

if( Read_Data == 0 ) //判断DHT11是否响应
{
while( Read_Data == 0); //低电平变高电平,等待低电平结束
while( Read_Data == 1); //高电平变低电平,等待高电平结束

R_H = DHT11_Rec_Byte();
R_L = DHT11_Rec_Byte();
T_H = DHT11_Rec_Byte();
T_L = DHT11_Rec_Byte();
CHECK = DHT11_Rec_Byte(); //接收5个数据

dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us
Delay_us(55); //这里延时55us
dht11_high; //随后总线由上拉电阻拉高进入空闲状态。

if(R_H + R_L + T_H + T_L == CHECK) //和检验位对比,判断校验接收到的数据是否正确
{
RH = R_H;
RL = R_L;
TH = T_H;
TL = T_L;
}
}
rec_data[0] = RH;
rec_data[1] = RL;
rec_data[2] = TH;
rec_data[3] = TL;
}


1
2
3
4
5
6
7
8
9
#include "dht11.h"
extern unsigned int rec_data[4]; // 直接使用里面的数据
int main_task(void)
{
while(1)
{
// 直接使用rec_data里面的数据。注意间隔一定时间再读取,以免错误。
}
}

HAL

DHT11 对时序要求严格,需要微妙级延时,我们常用的 HAL_Delay 是毫秒级时延。由于 FreeRTOS 是不支持微秒级延时的,需要自己定义微秒级别的延时函数(详见:STM32 延时函数(FreeRTOS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef __dht11_h
#define __dht11_h

extern uint8_t temperature;
extern uint8_t humidity;

void DHT11_REST(void); //复位DHT11
uint8_t DHT11_Check(void); //DHT11状态反馈
uint8_t DHT11_Read_Bit(void); //读DHT11一位数据
uint8_t DHT11_Read_Byte(void); //读DHT11一字节数据
uint8_t DHT11_Read_Data(uint8_t* humi,uint8_t* temp); //DHT11数据显示

#endif /*__dht11_h*/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

#include "stm32f1xx_hal.h"
#include "delay.h" # us级延时函数

#define DHT11_GPIOx GPIOB
#define DHT11_GPIO_PIN GPIO_PIN_11

uint8_t temperature; //给一个初始化,用以判断
uint8_t humidity;

void DHT11_OUT(void )//输出引脚
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(DHT11_GPIOx, &GPIO_InitStruct);
}
void DHT11_IN(void )//输入引脚
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT ;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(DHT11_GPIOx, &GPIO_InitStruct);
}

void DHT11_REST(void ) //主机发出起始信号
{
DHT11_OUT();
HAL_GPIO_WritePin (DHT11_GPIOx ,DHT11_GPIO_PIN ,GPIO_PIN_RESET );
HAL_Delay (20); // ms
HAL_GPIO_WritePin (DHT11_GPIOx ,DHT11_GPIO_PIN ,GPIO_PIN_SET );
delay_us (30);
}

//gpio端口为输入模式时,配置为上拉输入或者浮空输入,因外接上拉电阻,默认为高电平
//当有负信号输入时,gpio端口为1.
//若有负信号输入,当信号引脚连接到GND或者其他低电平信号时,GPIO端口会检测到低电平并显示为0。
//这是因为负信号的优先级高于上拉电阻的电平设置。


uint8_t DHT11_Check(void)
{
uint8_t retry = 0;

DHT11_IN(); // 设置数据引脚为输入模式

while (HAL_GPIO_ReadPin(DHT11_GPIOx, DHT11_GPIO_PIN) == 1 && retry < 80)
{
retry++;
delay_us(1); // 延迟1微秒
}

if (retry >= 80)
return 1; // 如果在规定时间内引脚仍为高电平,表示传感器未响应,返回错误代码 1
else
retry = 0;

while (HAL_GPIO_ReadPin(DHT11_GPIOx, DHT11_GPIO_PIN) == 0 && retry < 80)
{
retry++;
delay_us(1); // 延迟1微秒
}

if (retry >= 80)
return 1; // 如果在规定时间内引脚仍为低电平,表示传感器未响应,返回错误代码 1

return 0; // 传感器响应正常,返回成功代码 0
}
//读取一个位,参考高低电平的时序
uint8_t DHT11_Read_Bit(void)
{
uint8_t retry=0;
while((HAL_GPIO_ReadPin (DHT11_GPIOx ,DHT11_GPIO_PIN)==1) && (retry <100))
{
retry ++;
delay_us (1);
}
retry=0;
while((HAL_GPIO_ReadPin(DHT11_GPIOx ,DHT11_GPIO_PIN)==0) && (retry <100))
{
retry ++;
delay_us (1);
}
delay_us (40);
if(HAL_GPIO_ReadPin (DHT11_GPIOx ,DHT11_GPIO_PIN )==1)
return 1;// 返回读取到的位为高电平
else
return 0;// 返回读取到的位为低电平
}

//读取一个字节,接收数据
uint8_t DHT11_Read_Byte(void)
{
uint8_t dat=0;
for(uint8_t i=0;i<8;i++)
{
dat <<= 1;
dat |= DHT11_Read_Bit();
}
return dat;

}
//更据上面的数据格式,解码数据
uint8_t DHT11_Read_Data(uint8_t* humi, uint8_t* temp)
{
uint8_t buf[5]; // 存储读取到的5个字节数据的缓冲区

DHT11_REST(); // 初始化传感器通信

if (DHT11_Check() == 0) // 检查传感器是否正常响应
{
for (uint8_t i = 0; i < 5; i++)
buf[i] = DHT11_Read_Byte(); // 逐个字节读取传感器发送的数据

if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) // 校验数据的准确性
{
*humi = buf[0]; // 将湿度值存储到指定的变量中
*temp = buf[2]; // 将温度值存储到指定的变量中
}
}
else
{
return 1; // 传感器响应异常,返回错误代码
}

return 0; // 读取数据成功,返回正常代码
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "dht11.h"

int main_task(void)
{
DHT11_REST(); //复位DHT11
while(DHT11_Check()) //检测DHT11连接
{
OLED_ShowString(1,1,"DHT11 No Connect!");
HAL_Delay(500);
}
while(1)
{
DHT11_Read_Data(&humidity,&temperature);
// 使用temperature和humidity
}
}

本文参考