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