一起玩转树莓派(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模块的使用方法,编写代码如下:
#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位二进制数据后,将其转换为数值进行校验即可。最终代码运行效果如下图所示。
专注技术,懂的热爱,愿意分享,做个朋友