logo头像

学如逆水行舟

一起玩转树莓派(23)——DHT11温湿度传感器实践

一起玩转树莓派(23)——DHT11温湿度传感器实践

一. 引言

DHT11是一款强大的复合传感器,支持环境温度和湿度的测量。其本身比较简单,但是由于其采用串行时序的方式进行数据读写,非常适合我们练习时序编程。本次实验我们使用的传感器模块如下图所示。

可以看到,此传感器模块有3个引脚,除了电源和接地引脚外,只有一个out引脚用来输出数据和传输控制指令。下面我们来介绍下如何使用此传感器模块。

二. 关于DHT11传感器模块

由于DHT11传感器元件只有一个通信引脚,因此其输入和输出都需要使用同一个引脚。即此引脚是一个串行的单线双向引脚。所谓单线双向是指其只有一条信号传输线,但是可以双向通信。这有些像我们使用的对讲机,一方说话时另一方只能听。DHT11的完整使用手册地址如下:

https://www.dfrobot.com.cn/image/data/DFR0067/DFR0067_DS_10.pdf

首先我们先来看DHT11所传输的信息数据的格式。根据文档介绍,DHT11一次完整的通信将传递40位数据,这40位数据包含了温度,湿度和用于校验正确性的数据。因此,我们在读取DHT11的数据时,要完整的读出40位数据后再进行计算。这40位数据的具体格式为:

[8bit的湿度整数部分数据]+[8bit的湿度小数部分数据]+[8bit的温度整数部分数据]+[8bit的温度小数部分数据]+8bit校验数据

其中[8bit的湿度整数部分数据]与[8bit的湿度小数部分数据]与[8bit的温度整数部分数据]与[8bit的温度小数部分数据]的和结果应为8bit的校验数据,如果结果不等则表明此次获取的数据出现异常,应该抛弃掉重新获取。

从传感器拿到的数据格式本身比较简单,比较复杂的点在于其通信过程。整体来说,树莓派与DHT11传感器的通信过程分为3个阶段:

1. 树莓派发出开始信号,之后开始等待传感器模块的应答。

2.传感器模块收到树莓派发出的开始信号后,返回应答信号。

3.树莓派接收到应答信号后,开始进行40位数据的接收。

整体的通信过程手册中有提供一张示意图,如下:

通电后,传感器模块的总线将始终处于空闲状态或通信状态中的一种状态下。定义当空闲状态时,总线输入高电平。对于上面通信过程中的第1个阶段,树莓派先将总线电平拉低,且必须大于18毫秒以让传感器模块检测到此拉低的信号。之后树莓派再将总线拉高,表示树莓派已经发出了一次开始通信信号,1阶段结束。

传感器模块检测到树莓派发起的开始信号后,此时总线电平为被树莓派拉高状态。传感器模块通过总线发送80微秒的低电平信号,表示响应了树莓派的开始信号,之后传感器模块会将总线电平再拉高80微妙,提示树莓派准备开始接收数据,2阶段结束。

阶段1和阶段2的更详细示意图如下:

3阶段为传感器模块发送数据,树莓派接收数据的阶段。每一位数据的发送有0和1两种状态,传感器每发送50微妙的低电平信号即表示要进行1bit数据的传输,之后如果传感器发送了26微秒到28微秒的高电平,则表示发送数据位0,如果发送了70微秒的高电平则表示发送数据位1。之后再进行下一位数据的发送。在实际编程操作时,我们可能不太好精准的测量高电平的时间,但是由于数据位0和数据位1的高电平时间相差很多,我们可以通过测试循环变量的计数来大致得到一个传输数据0时的大致循环次数和传输数据1时的大致循环次数,通过循环次数来判断数据具体是0还是1。

数据位0的信号示意图如下:

数据位1的信号示意图如下:

需要注意,每次测量的时间间隔最好大于1秒,且上电后等待1秒稳定再进行测量。

如果没有接触过元件单总线时序编程,上面的文字描述,总的来说还是有些抽象,下面我们会通过代码来实践。

三. 连线编码

DHT11传感器模块只有3个引脚,中间的out引脚我们可以选择任意一个树莓派额GPIO引脚来连接,这里我们选择物理编码为11的GPIO引脚。按照上面我们介绍的DHT11模块的使用方法,编写代码如下:

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
135
#coding:utf-8
import RPi.GPIO as GPIO
import time
import math

# 使用物理编码为11的引脚做总线
P = 11

GPIO.setmode(GPIO.BOARD)

print("开始进行DHT11测量数据获取\n")
# 等待1s后再进行逻辑
time.sleep(1)


def readData():
print("readData")
# 先将总线引脚设置为输出模式
GPIO.setup(P, GPIO.OUT)
# 将总线电平拉低,发出开始信号
GPIO.output(P, GPIO.LOW)
# 手册要求至少保持18ms的低电平,我们这里保持20ms
time.sleep(0.02)
# 拉高电平
GPIO.output(P, GPIO.HIGH)
# 之后将引脚设为输入模式,等待传感器响应
GPIO.setup(P, GPIO.IN)

# 先等待80us的低电平信号
while GPIO.input(P) == GPIO.LOW:
continue
# 再等待80us的高电平信号
while GPIO.input(P) == GPIO.HIGH:
continue

# 开始接收数据

# 循环计数
i = 0
# 存放二进制位数据
data = []
# 存放中间数据
kdata = []
# 每次读取40位数据
while i < 40:
j = 0
# 50us的低电平表示准备传输一位数据
while GPIO.input(P) == GPIO.LOW:
continue
# 开始检测高电平的时间
while GPIO.input(P) == GPIO.HIGH:
j+=1
if j > 100:
return [False, 0, 0]
kdata.append(j)
i+=1


print("--------临时数据----------\n")
print(kdata)
print("--------临时数据----------\n")

# 开始整理数据
i = 0
while i < 40:
tmp = kdata[i]
if tmp > 7:
data.append(1)
else:
data.append(0)
i+=1
print("--------临时数据2----------\n")
print(data)
print("--------临时数据2----------\n")
# 解析湿度整数部分
t1 = data[0:8]
c1 = 0
i = 7
for n in t1:
c1 += n * math.pow(2, i)
i-=1

# 解析湿度整数部分
t2 = data[8:16]
c2 = 0
i = 7
for n in t2:
c2 += n * math.pow(2, i)
i-=1

# 解析温度整数部分
t3 = data[16:24]
c3 = 0
i = 7
for n in t3:
c3 += n * math.pow(2, i)
i-=1

# 解析温度整数部分
t4 = data[24:32]
c4 = 0
i = 7
for n in t4:
c4 += n * math.pow(2, i)
i-=1

# 解析校验
t5 = data[32:40]
c5 = 0
i = 7
for n in t5:
c5 += n * math.pow(2, i)
i-=1

# 进行校验
va = True
print("c1:%d\n2c:%d\n3c:%d\n4c:%d\n5:%d\n"%(c1,c2,c3,c4,c5))
if c1 + c2 + c3 +c4 == c5:
va = True
else:
va = False

return [va, "%d.%d"%(c1, c2), "%d.%d"%(c3, c4)]


while True:
time.sleep(1)
result = readData()
if result[0]:
hum = result[1]
temp = result[2]
print("当前环境湿度: %s %%, 当前环境温度:%s℃\n" % (hum, temp))
else:
print("此次数据无效,已被丢弃\n")
time.sleep(1)

上面代码中有比较详细的注释,有些地方我们还是可以再解释一下。首先,树莓派发出开始指令的过程比较简单,无需过多解释。麻烦之处在于循环40次来获取40位的二进制数据。在获取数据时,我们采用循环变量来记录高电平的时间比例,从而可以分析出传输的数据是0还是1。需要注意,不同的设备可能循环一次的时间并不完全相同,我们可以先测试下上面代码中kdata变量存储的数据,运行代码如下图:

可以看到,如果是传输数据0,循环计数基本是在3或者4。而如果传输的是数据1,则循环计数在11左右。代码中我们以7为分割,分析时,循环计数大于7时就认为当前传输数据1,否则为0。

获取到了完整的40位二进制数据后,将其转换为数值进行校验即可。最终代码运行效果如下图所示。

专注技术,懂的热爱,愿意分享,做个朋友

QQ:316045346