温度湿度模块看起来简单,只有三个引脚(实际4个接口),但仔细一想,3个引脚分别作为VCC、GND、DATA用处的话,因为传给树莓派GPIO的只有高电平、低电平,那么怎么来读取整型的温度数字和湿度数字呢?这么一想,并不简单!反而是因为引脚少,它需要高低变化的时序信号来表达数值,还有一些其它信号如开始信号等等。看样子这次我们得先深入了解一下DHT11模块了。
0. 先来看模块结构和特性
如上图,三根引脚分别是:“+”对应VCC 3.3V或者5V,中间OUT为DATA接GPIO口,“-”对应电源负极。
1. DHT11模块时序信号
百度一下就能搜到中文版的说明书,传送门:http://wenku.baidu.com/link?url=TZ8UkMmHxmwWfMUlThThtYJpEx1-um21yejWpvB_XSAog3TIr2AStTXNPTYb0FhJ_mgsWI0_KUf-LAAU4t6_aBELQBLL8NPSh315A1Bztw7
我们需要掌握两个关键的时序信号:开始握手阶段和数据发送阶段。
1)开始握手阶段
主机端GPIO发送开始信号首先拉低至少18ms,然后拉高20-40us,模式变为IN等待信号输入。
DHT11等待主机端开始信号(低电平)结束后,发送80us低电平响应开始信号。然后DHT11拉高电平80us。握手完毕。
2)数据发送阶段
一次的湿度和温度数据,DHT11需要发送40bits(0、1)数据,每一位数据之前都以50us低电平开始,随后的高电平时序信号,持续26us-28us的表示这一位是0,持续70us表示这一位是1,然后继续50us低电平,紧接着下一位的高电平开始。。
40bits数据的组成是=8bits湿度整数部分+8bits湿度小数部分(暂时没用)+8bits温度整数部分+8bits温度小数部分(暂时没用)+8bits校验和
8bits的顺序都是高位先出,然后用移位相加的方式,将这8位转换成整型数字。
2. 线路连接
DHT11说明书中,推荐的做法是,在数据端口接5K上拉电阻,如下所示:
在树莓派B+端选择第2(5V)、14(0V)、16(GPIO.4)来分别连接DHT11的“+”、“-”和“OUT”端:
电路图并不难,关键是如何按照时序信号来写相关处理代码。
3. 代码实现(python3)
我们还是使用RPi.GPIO库来实现,这里的难点是,读取DHT11的输出信号,需要微秒级的定时,否则在数据传输阶段,很难准确的识别出每一位是“0”还是“1”,而python又在单核 700MHz的CPU上,很难实现准确的微秒级定时,只能通过执行几条无意义代码来替代微秒延时。
1)与DHT11交互阶段
#!/usr/bin/python
#coding=utf-8
#注意本程序是python3!!!
import RPi.GPIO as GPIO
import time
from ctypes import *
import os
#存放时序数据
data = [0 for i in range(40)]
def driver():
j = 0
# 传感器上电后,要等待1s以越过不稳定状态
GPIO.setmode(GPIO.BOARD)
time.sleep(1)
# 先向传感器发送开始信号,握手-LOW-
GPIO.setup(16, GPIO.OUT)
GPIO.output(16, GPIO.LOW)
# 主机把总线拉低必须大于18毫秒,这里采用20毫秒
time.sleep(0.02)
# 然后主机拉高并延时等待传感器的响应
GPIO.output(16, GPIO.HIGH)
# 执行1次需要十几微秒
i = 1
i = 1
# 等待传感器的握手响应信号和数据信号
GPIO.setup(16, GPIO.IN)
while GPIO.input(16) == 1:
continue
# 总线为低电平,说明传感器发送响应信号,80us低电平
while GPIO.input(16) == 0:
continue
# 然后传感器再把总线拉高80us,然后才准备发送数据
while GPIO.input(16) == 1:
continue
# 开始发送数据
# 一次完整的数据为40bit,高位先出
# 8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和
while j < 40:
k = 0
#每一位的起始信号,都以50us低电平开始
while GPIO.input(16) == 0:
continue
#每一位的数值信号,高电平的长短决定了数据位是0还是1。
while GPIO.input(16) == 1:
#需要知道每次循环的耗时,才能知道k < x是表示0
k += 1
if k > 100:
break
# 高电平持续26-28us表示0, 高电平持续70us表示1
if k < 3:
data[j] = 0
else:
data[j] = 1
j += 1
print(data)
2)计算湿度、温度、校验和
按照每8位转换成一个十进制数字
def compute():
humidity_bit = data[0:8]
humidity_point_bit = data[8:16]
temperature_bit = data[16:24]
temperature_point_bit = data[24:32]
check_bit = data[32:40]
humidity = 0
humidity_point = 0
temperature = 0
temperature_point = 0
check = 0
for i in range(8):
# 湿度整数部分
humidity += humidity_bit[i] * 2**(7-i)
humidity_point += humidity_point_bit[i] * 2**(7-i)
# 温度整数部分
temperature += temperature_bit[i] * 2**(7-i)
temperature_point += temperature_point_bit[i] * 2**(7-i)
check += check_bit[i] * 2**(7-i)
sum = humidity + humidity_point + temperature + temperature_point
print("temperature:", temperature, ", humidity:", humidity)
if check == sum:
print("temperature:", temperature, ", humidity:", humidity)
else:
print("wrong!", check, "!=", sum)
if __name__ == "__main__":
driver()
compute()
GPIO.cleanup()
3)测试几次结果
错误率还是比较高的,暂时不知道如何比较准确的识别时序信号,而且在树莓派执行多任务时,很可能错的很离谱。
另外,在没有上拉电阻的时候,也可以正常工作。