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

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

一. 引言

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

一起玩转树莓派(23)——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位数据的接收。

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

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

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

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

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

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

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

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

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

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

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

需要注意,每次测量的时间间隔最好大于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变量存储的数据,运行代码如下图:

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

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

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

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

专注技术,懂的热爱,愿意分享,做个朋友
上一篇:无影云桌面之初体验


下一篇:一起玩转树莓派(21)——火情报警器