?導(dǎo)讀:DHT11 數(shù)字溫濕度傳感器是一款含有已校準(zhǔn)數(shù)字信號輸出的溫濕度復(fù)合傳感器,內(nèi)部由一個 8 位單片機(jī)控制一個電阻式感濕元件和一個 NTC 測溫元件。DHT11憑借其超小的體積、極低的功耗在業(yè)界得以廣泛的認(rèn)可和應(yīng)用。需要指出的是,由于本人水平有限,如有錯誤還請讀者指出,非常感謝。那么,接下來讓我們來一起學(xué)習(xí)一下DHT11這款溫濕度傳感器吧。
一、DHT11基礎(chǔ)儲備
DHT11 數(shù)字溫濕度傳感器是一款含有已校準(zhǔn)數(shù)字信號輸出的溫濕度復(fù)合傳感器,內(nèi)部由一個 8 位單片機(jī)控制一個電阻式感濕元件和一個 NTC 測溫元件。DHT11 雖然也是采用單總線協(xié)議,但是該協(xié)議與 DS18B20 的單總線協(xié)議稍微有些不同之處。
相比于 DS18B20 只能測量溫度,DHT11 既能檢測溫度又能檢測濕度,不過 DHT11 的精度和測量范圍都要低于 DS18B20,其溫度測量范圍為 0~50℃,誤差在±2℃;濕度的測量范圍為 20%~90%RH(Relative Humidity 相對濕度—指空氣中水汽壓與飽和水汽壓的百分比),誤差在±5%RH。DHT11 電路很簡單,只需要將 Dout 引腳連接單片機(jī)的一個 I/O 即可,不過該引腳需要上拉一個 5K 的電阻,DHT11 的供電電壓為 3~5.5V。
二、協(xié)議及數(shù)據(jù)格式
DHT11 采用單總線協(xié)議與單片機(jī)通信,單片機(jī)發(fā)送一次復(fù)位信號后,DHT11 從低功耗模式轉(zhuǎn)換到高速模式,等待主機(jī)復(fù)位結(jié)束后,DHT11 發(fā)送響應(yīng)信號,并拉高總線準(zhǔn)備傳輸數(shù)據(jù)。一次完整的數(shù)據(jù)為 40bit,按照高位在前,低位在后的順序傳輸
數(shù)據(jù)格式為:8bit 濕度整數(shù)數(shù)據(jù)+8bit 濕度小數(shù)數(shù)據(jù)+8bit 溫度整數(shù)數(shù)據(jù)+8bit 溫度小數(shù)數(shù)據(jù)+8bit 校驗和,一共 5 字節(jié)(40bit)數(shù)據(jù)。由于 DHT11 分辨率只能精確到個位,所以小數(shù)部分是數(shù)據(jù)全為 0。校驗和為前 4 個字節(jié)數(shù)據(jù)相加,校驗的目的是為了保證數(shù)據(jù)傳輸?shù)臏?zhǔn)確性。
DHT11 只有在接收到開始信號后才觸發(fā)一次溫濕度采集,如果沒有接收到主機(jī)發(fā)送復(fù)位信號,DHT11 不主動進(jìn)行溫濕度采集。當(dāng)數(shù)據(jù)采集完畢且無開始信號后,DHT11 自動切換到低速模式。
注意:由于 DHT11 時序要求非常嚴(yán)格,所以在操作時序的時候,為了防止中斷干擾總線時序,先關(guān)閉總中斷,操作完畢后再打開總中斷。
三、操作時序
1、 主機(jī)發(fā)送復(fù)位信號
DHT11 的初始化過程同樣分為復(fù)位信號和響應(yīng)信號。首先主機(jī)拉低總線至少 18ms,然后再拉高總線,延時 20~40us,取中間值 30us,此時復(fù)位信號發(fā)送完畢。
2、DHT11 發(fā)送響應(yīng)信號
DHT11 檢測到復(fù)位信號后,觸發(fā)一次采樣,并拉低總線 80us 表示響應(yīng)信號,告訴主機(jī)數(shù)據(jù)已經(jīng)準(zhǔn)備好了;然后 DHT11 拉高總線 80us,之后開始傳輸數(shù)據(jù)。如果檢測到響應(yīng)信號為高電平,則 DHT11 初始化失敗,請檢查線路是否連接正常。
當(dāng)復(fù)位信號發(fā)送完畢后,如果檢測到總線被拉低,就每隔 1us 計數(shù)一次,直至總線拉高,計算低電平時間;當(dāng)總線被拉高后重新計數(shù)檢測 80us 的高電平。如果檢測到響應(yīng)信號之后的80us 高電平,就準(zhǔn)備開始接收數(shù)據(jù)。實際上 DHT11 的響應(yīng)時間并不是標(biāo)準(zhǔn)的 80us,往往存在誤差,當(dāng)響應(yīng)時間處于 20~100us 之間時就可以認(rèn)定響應(yīng)成功。
3、數(shù)據(jù)傳輸
DHT11 在拉高總線 80us 后開始傳輸數(shù)據(jù)。每 1bit 數(shù)據(jù)都以 50us 低電平時隙開始,告訴主機(jī)開始傳輸一位數(shù)據(jù)了。DHT11 以高電平的長短定義數(shù)據(jù)位是 0 還是 1,當(dāng) 50us 低電平時隙過后拉高總線,高電平持續(xù) 26~28us 表示數(shù)據(jù)“0”;持續(xù) 70us 表示數(shù)據(jù)“1”。
當(dāng) 最后 1bit 數(shù)據(jù)傳送完畢后,DHT11 拉低總線 50us,表示數(shù)據(jù)傳輸完畢,隨后總線由上拉電阻拉高進(jìn)入空閑狀態(tài)。
4、區(qū)分?jǐn)?shù)據(jù)0/1的巧法
還是像檢測響應(yīng)時間那樣計算高電平持續(xù)時間那就太麻煩了!?。?/p>
數(shù)據(jù)“0”的高電平持續(xù) 26~28us,數(shù)據(jù)“1”的高電平持續(xù)70us,每一位數(shù)據(jù)前都有 50us 的起始時隙。如果我們?nèi)∫粋€中間值 40us 來區(qū)分?jǐn)?shù)據(jù)“0”和數(shù)據(jù)“1”的時隙。
當(dāng)數(shù)據(jù)位之前的 50us 低電平時隙過后,總線肯定會拉高,此時延時 40us 后檢測總線狀態(tài),如果為高,說明此時處于 70us 的時隙,則數(shù)據(jù)為“1”;如果為低,說明此時處于下一位數(shù)據(jù) 50us 的開始時隙,那么上一位數(shù)據(jù)肯定是“0”。
為什么延時 40us?由于誤差的原因,數(shù)據(jù)“0”時隙并不是準(zhǔn)確 26~28us,可能比這短,也可能比這長。當(dāng)數(shù)據(jù)“0”時隙大于 26~28us 時,如果延時太短,無法判斷當(dāng)前處于數(shù)據(jù)“0”的時隙還是數(shù)據(jù)“1”的時隙;如果延時太長,則會錯過下一位數(shù)據(jù)前的開始時隙,導(dǎo)致檢測不到后面的數(shù)據(jù)
四、51及STM32例程
51對應(yīng)的
dht11.c
#include "config.h"
#include "delay.h"
//1-檢測到響應(yīng)信號 0-未檢測到
u8 DHT11RstAndCheck(void)
{
u8 timer = 0;
EA = 0;
DHT11 = 0;
Delay20ms();
DHT11 = 1;
Delay30us();
while(!DHT11)
{
timer++;
Delay1us();
}
if(timer>100 || timer<20)
{
EA = 1;
return 0;
}
timer = 0;
while(DHT11)
{
timer++;
Delay1us();
}
EA = 1;
if(timer>100 || timer<20)
{
return 0;
}
return 1;
}
//讀一字節(jié)數(shù)據(jù)
u8 DHT11ReadByte(void)
{
u8 i;
u8 byt = 0;
EA = 0;
for(i=0; i<8; i++)
{
while(DHT11);
while(!DHT11);
Delay40us();
byt <<= 1;
if(DHT11)
{
byt |= 0x01;
}
}
EA = 1;
return byt;
}
//讀取一次數(shù)據(jù)(整數(shù)) 0-讀取失敗 1-讀取成功
u8 DHT11ReadData(u8 *Humi, u8 *Temp)
{
u8 sta = 0;
u8 i;
u8 buf[5];
if(DHT11RstAndCheck())
{
for(i=0; i<5; i++)
{
buf[i] = DHT11ReadByte();
}
if(buf[0]+buf[1]+buf[2]+buf[3] == buf[4])
{
*Humi = buf[0];
*Temp = buf[2];
}
sta = 1;
}
else
{
*Humi = 0xFF;
*Temp = 0xFF;
sta = 0;
}
return sta;
}
dht11.h
#ifndef DHT11_H
#define DHT11_H
u8 DHT11ReadData(u8 *Humi, u8 *Temp);
#endif
STM32對應(yīng)的
dht11.c
#include "dht11.h"
/*DHT11復(fù)位和檢測響應(yīng)函數(shù),返回值:1-檢測到響應(yīng)信號;0-未檢測到響應(yīng)信號*/
u8 DHT11RstAndCheck(void)
{
u8 timer = 0;
__set_PRIMASK(1); //關(guān)總中斷
DHT11_OUT = 0; //輸出低電平
delay_ms(20); //拉低至少18ms
DHT11_OUT = 1; //輸出高電平
delay_us(30); //拉高20~40us
while (!DHT11_IN) //等待總線拉低,DHT11會拉低40~80us作為響應(yīng)信號
{
timer++; //總線拉低時計數(shù)
delay_us(1);
}
if (timer>100 || timer<20) //判斷響應(yīng)時間
{
__set_PRIMASK(0); //開總中斷
return 0;
}
timer = 0;
while (DHT11_IN) //等待DHT11釋放總線,持續(xù)時間40~80us
{
timer++; //總線拉高時計數(shù)
delay_us(1);
}
__set_PRIMASK(0); //開總中斷
if (timer>100 || timer<20) //檢測響應(yīng)信號之后的高電平
{
return 0;
}
return 1;
}
/*讀取一字節(jié)數(shù)據(jù),返回值-讀到的數(shù)據(jù)*/
u8 DHT11ReadByte(void)
{
u8 i;
u8 byt = 0;
__set_PRIMASK(1); //關(guān)總中斷
for (i=0; i<8; i++)
{
while (DHT11_IN); //等待低電平,數(shù)據(jù)位前都有50us低電平時隙
while (!DHT11_IN); //等待高電平,開始傳輸數(shù)據(jù)位
delay_us(40);
byt <<= 1; //因高位在前,所以左移byt,最低位補(bǔ)0
if (DHT11_IN) //將總線電平值讀取到byt最低位中
{
byt |= 0x01;
}
}
__set_PRIMASK(0); //開總中斷
return byt;
}
/*讀取一次數(shù)據(jù),返回值:Humi-濕度整數(shù)部分?jǐn)?shù)據(jù),Temp-溫度整數(shù)部分?jǐn)?shù)據(jù);返回值: -1-失敗,1-成功*/
u8 DHT11ReadData(u8 *Humi, u8 *Temp)
{
s8 sta = 0;
u8 i;
u8 buf[5];
if (DHT11RstAndCheck()) //檢測響應(yīng)信號
{
for(i=0;i<5;i++) //讀取40位數(shù)據(jù)
{
buf[i]=DHT11ReadByte(); //讀取1字節(jié)數(shù)據(jù)
}
if(buf[0]+buf[1]+buf[2]+buf[3] == buf[4]) //校驗成功
{
*Humi = buf[0]; //保存濕度整數(shù)部分?jǐn)?shù)據(jù)
*Temp = buf[2]; //保存溫度整數(shù)部分?jǐn)?shù)據(jù)
}
sta = 1;
}
else //響應(yīng)失敗返回-1
{
*Humi = 0xFF; //響應(yīng)失敗返回255
*Temp = 0xFF; //響應(yīng)失敗返回255
sta = -1;
}
return sta;
}
/*DHT11初始化函數(shù)*/
u8 DHT11Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能GPIOC端口時鐘
GPIO_SetBits(GPIOC,GPIO_Pin_13); //設(shè)置PC13輸出高電平,(先設(shè)置引腳電平可以避免IO初始化過程中可能產(chǎn)生的毛刺)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //設(shè)置DHT11數(shù)據(jù)引腳->PC13
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //設(shè)置為開漏輸出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //設(shè)置輸出速率為50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC端口
return DHT11RstAndCheck(); //返回DHT11狀態(tài)
}
dht11.h
#ifndef _DHT11_H
#define _DHT11_H
#include "config.h"
#define DHT11_OUT PC_OUT(13)
#define DHT11_IN PC_IN(13)
u8 DHT11Init(void);
u8 DHT11ReadData(u8 *Humi,u8 *Temp);
#endif
總結(jié):此篇文章主要講述了DHT11的驅(qū)動原理,接著引出了基于STM32和STC51兩款主流單片機(jī)的具體驅(qū)動代碼。以此拋磚引玉,希望讀者一來可以快速上手(DHT11的使用),二來可以舉一反三(其他類型的單片機(jī)驅(qū)動DHT11)。
希望大家多多支持我的原創(chuàng)文章。如有錯誤,請大家及時指正,非常感謝。