一起玩轉樹莓派(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位二進制數據後,將其轉換爲數值進行校驗即可。最終代碼運行效果如下圖所示。
專注技術,懂的熱愛,願意分享,做個朋友
QQ:316045346