與DS18B20一樣DHT11也是采用單總線,但所不同的是DHT11可同時實現溫度和濕度的檢測。在我們的產品中經常使用它來檢測環境的溫濕度信息。這一篇我們將設計並封裝DHT11的驅動程序,以方便重復使用。
1、功能概述
DHT11數字溫濕度傳感器是一款含有已校准數字信號輸出的溫濕度復合傳感器。它應用專用的數字模塊采集技術和溫濕度傳感技術,確保產品具有極高的可靠性與卓越的長期穩定性。
1.1、硬件描述
傳感器包括一個電阻式感濕元件和一個NTC測溫元件,並與一個高性能8位單片機相連接。因此該產品具有品質卓越、超快響應、抗干擾能力強、性價比極高等優點。每個DHT11傳感器都在極為精確的濕度校驗室中進行校准。校准系數以程序的形式儲存在OTP內存中,傳感器內部在檢測信號的處理過程中要調用這些校准系數。單線制串行接口,使系統集成變得簡易快捷。超小的體積、極低的功耗,信號傳輸距離可達20米以上,使其成為各類應用甚至最為苛刻的應用場合的最佳選擇。產品為4針單排引腳封裝。
DHT11的供電電壓為 3-5.5V。傳感器上電后,要等待 1s 以越過不穩定狀態在此期間無需發送任何指令。電源引腳(VDD,GND)之間可增加一個100nF 的電容,用以去耦濾波。
1.2、通訊接口
DHT11傳感器單總線通訊建議連接線長度短於20米時用5K上拉電阻,大於20米時根據實際情況使用合適的上拉電阻。連線圖如下:
DATA用於微處理器與DHT11之間的通訊和同步,采用單總線數據格式,一次通訊時間4ms左右,數據分小數部分和整數部分,具體格式在下面說明,當前小數部分用於以后擴展,現讀出為零。一次完整的數據傳輸為40bit,高位在前。數據格式:8bit濕度整數數據+8bit濕度小數數據+8bi溫度整數數據+8bit溫度小數數據+8bit校驗和。
其中,在數據傳送正確時,校驗和數據等於“8bit濕度整數數據+8bit濕度小數數據+8bi溫度整數數據+8bit溫度小數數據”所得結果的末8位。
用戶MCU發送一次開始信號后,DHT11從低功耗模式轉換到高速模式,等待主機開始信號結束后,DHT11發送響應信號,送出40bit的數據,並觸發一次信號采集,用戶可選擇讀取部分數據。通訊過程如下圖所示:
從模式下,DHT11接收到開始信號觸發一次溫濕度采集,如果沒有接收到主機發送開始信號,DHT11不會主動進行溫濕度采集。采集數據后轉換到低速模式。
2、驅動設計與實現
我們已經了解了DHT11溫濕度傳感器的相關信息,接下來我們將設計並實現DHT11溫濕度傳感器的驅動程序。
2.1、對象定義
在使用一個對象之前我們需要獲得這個對象。同樣的我們想要操作DHT11溫濕度傳感器就需要先定義DHT11溫濕度傳感器的對象。
2.1.1、對象的抽象
我們要得到DHT11溫濕度傳感器對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下DHT11溫濕度傳感器的對象。
先來考慮屬性,作為屬性肯定是用於標識或記錄對象特征的東西。我們來考慮DHT11溫濕度傳感器對象屬性。DHT11溫濕度傳感器並沒有標識設備區別的特性,只有溫度和濕度信息可以表示當前的工作狀態我們將其作為屬性。
接着我們還需要考慮DHT11溫濕度傳感器對象的操作問題。我們知道DHT11溫濕度傳感器采用的是單總線。單總線就需要控制總線的輸入輸出方向,而且這對這條總線在不同的輸入輸出方向,我們需要讀數據和寫數據,而這些操作都依賴於硬件平台,所以我們將他們定義為DHT11溫濕度傳感器對象的操作。處於時序控制的需要,我們需要延時操作函數,而在不同的軟硬件平台延時操作會有差異,我們也將其作為對象的操作。
根據上述我們對DHT11溫濕度傳感器的分析,我們可以定義DHT11溫濕度傳感器的對象類型如下:
1 /* 定義DHT11對象類型 */ 2 typedef struct Dht11Object { 3 float temperature; //溫度值 4 float humidity; //濕度值 5 6 uint8_t (*SetPinOutValue)(DhtPinValueType setValue);//設置DHT11引腳的輸出值 7 uint8_t (*ReadPinBit)(void);//讀取引腳電平 8 void (*SetPinDirection)(DHT11IOModeType mode);//設置引腳的輸入輸出方向 9 10 void (*Delayms)(volatile uint32_t nTime); /*實現ms延時操作*/ 11 void (*Delayus)(volatile uint32_t nTime); /*實現us延時操作*/ 12 }Dht11ObjectType;
2.1.2、對象初始化
我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這里我們來考慮DHT11溫濕度傳感器對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計DHT11溫濕度傳感器對象的初始化函數如下:
1 /*DHT11初始化操作*/ 2 DHT11ErrorType InitializeDHT11(Dht11ObjectType *dht, //需要初始化對象 3 Dht11SetPinOutValueType setPinStatus, //設置總線輸出值 4 Dht11ReadPinBitType getPinStatus, //讀取總線輸入值 5 Dht11SetPinModeType mode, //配置總線的輸入輸出模式 6 Dht11DelayType delayms, //毫秒延時 7 Dht11DelayType delayus //微秒延時 8 ) 9 { 10 if((dht==NULL)||(setPinStatus==NULL)||(getPinStatus==NULL)||(mode==NULL)||(delayms==NULL)||(delayus==NULL)) 11 { 12 return DHT11_InitError; 13 } 14 15 dht->SetPinOutValue=setPinStatus; 16 dht->ReadPinBit=getPinStatus; 17 dht->SetPinMode=mode; 18 dht->Delayms=delayms; 19 dht->Delayus=delayus; 20 21 dht->humidity=0.0; 22 dht->temperature=0.0; 23 24 ResetDHT11(dht); 25 return CheckDHT11Status(dht); 26 }
2.2、對象操作
我們已經完成了DHT11溫濕度傳感器對象類型的定義和對象初始化函數的設計。但我們的主要目標是獲取對象的信息,接下來我們還要實現面向DHT11溫濕度傳感器的各類操作。
2.2.1、啟動數據通訊
DHT11溫濕度傳感器上電后,總線空閑狀態為高電平,主機把總線拉低等待DHT11響應,主機把總線拉低必須大於18毫秒,保證DHT11能檢測到起始信號。DHT11接收到主機的開始信號后,等待主機開始信號結束,然后發送80us低電平響應信號。主機發送開始信號結束后,延時等待20-40us后, 讀取DHT11的響應信號,主機發送開始信號后,可以切換到輸入模式,或者輸出高電平均可, 總線由上拉電阻拉高。啟動數據通訊的時序圖如下:
1 /*復位DHT11,開始通訊*/ 2 static void ResetDHT11(Dht11ObjectType *dht) 3 { 4 dht->SetPinMode(DHT11_Out); //設置為輸出方式 5 dht->SetPinOutValue(DHT11_Reset); //將引腳點位拉低 6 dht->Delayms(20); //拉低至少18ms 7 dht->SetPinOutValue(DHT11_Set); //拉高 8 dht->Delayus(30); //主機拉高20至40us 9 }
DHT11傳感器的DATA引腳檢測到外部信號有低電平時,等待外部信號低電平結束,延遲后DHT11的 DATA引腳處於輸出狀態,輸出80微秒的低電平作為應答信號,緊接着輸出 80 微秒的高電平通知外設准備接收數據,微處理器的 I/O 此時處於輸入狀態,檢測到 I/O 有低電平(DHT11 回應信號)后,等待80微秒的高電平后的數據接收。
1 /*等待DHT11的回應,返回1:未檢測到DHT11的存在;返回0:存在*/ 2 static DHT11ErrorType CheckDHT11Status(Dht11ObjectType *dht) 3 { 4 uint8_t retry=0; 5 dht->SetPinMode(DHT11_In); //設置為輸入方式 6 while(dht->ReadPinBit()&&(retry<100)) 7 { 8 retry++; 9 dht->Delayus(1); 10 } 11 if(retry>=100) 12 { 13 return DHT11_None; 14 } 15 retry=0; 16 while(!dht->ReadPinBit()&&(retry<100)) 17 { 18 retry++; 19 dht->Delayus(1); 20 } 21 if(retry>=100) 22 { 23 return DHT11_None; 24 } 25 return DHT11_NoError; 26 }
2.2.2、讀取數據位
當主機變為輸入模式后,檢測到總線為低電平,說明DHT11發送響應信號,DHT11發送響應信號后,再把總線拉高80us,准備發送數據,每一bit數據都以50us低電平時隙開始,高電平的長短定了數據位是“0”還是“1”。表示0”和“1”的時序圖如下所示:
1 /*從DHT11讀取一個位,返回值:1/0*/ 2 static uint8_t ReadBitFromDHT11(Dht11ObjectType *dht) 3 { 4 uint8_t retry=0; 5 /*等待變為低電平*/ 6 while(dht->ReadPinBit()&&(retry<100)) 7 { 8 retry++; 9 dht->Delayus(1); 10 } 11 retry=0; 12 /*等待變高電平*/ 13 while(!dht->ReadPinBit()&&(retry<100)) 14 { 15 retry++; 16 dht->Delayus(1); 17 } 18 dht->Delayus(40); //延時判斷此位是0還是1 19 20 return dht->ReadPinBit(); 21 }
當最后一bit數據傳送完畢后,DHT11拉低總線50us,隨后總線由上拉電阻拉高進入空閑狀態。
3、驅動的使用
我們已經實現了DHT11溫濕度傳感器的驅動,接下來將以此驅動為基礎設計了簡單的測試應用。
3.1、聲明並初始化對象
使用基於對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的DHT11溫濕度傳感器對象類型聲明一個DHT11溫濕度傳感器對象變量,具體操作格式如下:
Dht11ObjectType dht;
聲明了這個對象變量並不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:
Dht11ObjectType *dht,需要初始化對象
Dht11SetPinOutValueType setPinStatus,設置總線輸出值
Dht11ReadPinBitType getPinStatus,讀取總線輸入值
Dht11SetPinModeType mode,配置總線的輸入輸出模式
Dht11DelayType delayms,毫秒延時
Dht11DelayType delayus,微秒延時
對於這些參數,對象變量我們已經定義了。剩下的輸入參數就是我們操作中需要的函數,這幾個函數需要我們在應用中定義,並將函數指針作為參數。這幾個函數的類型如下:
1 typedef uint8_t (*Dht11SetPinOutValueType)(DhtPinValueType setValue);//設置DHT11引腳的輸出值 2 typedef uint8_t (*Dht11ReadPinBitType)(void);//讀取引腳電平 3 typedef void (*Dht11SetPinModeType)(DHT11IOModeType mode);//設置引腳的輸入輸出方向 4 typedef void (*Dht11DelayType)(volatile uint32_t nTime); /*實現ms延時操作*/
對於這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平台有關系。我們在STM32F4及HAL庫環境下使用,具體函數定義如下:
1 //設置DHT11引腳的輸出值 2 uint8_t Dht11SetPinOutValue(DhtPinValueType setValue) 3 { 4 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,(GPIO_PinState)setValue); 5 } 6 7 //讀取引腳電平 8 uint8_t Dht11ReadPinBit(void) 9 { 10 return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11); 11 } 12 13 //設置引腳的輸入輸出方向 14 void Dht11SetPinMode(DHT11IOModeType mode) 15 { 16 GPIO_InitTypeDef GPIO_InitStruct; 17 18 GPIO_InitStruct.Pin = GPIO_PIN_11; 19 if(mode==DHT11_In) 20 { 21 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 22 GPIO_InitStruct.Pull = GPIO_NOPULL; 23 } 24 else 25 { 26 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 27 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 28 } 29 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 30 }
對於延時函數我們可以采用各種方法實現。我們采用的STM32平台和HAL庫則可以直接使用HAL_Delay()函數。微秒延時函數則使用我們自己定義的。於是我們可以調用初始化函數如下:
InitializeDHT11(&dht,Dht11SetPinOutValue,Dht11ReadPinBit,Dht11SetPinMode,HAL_Delay,Delayus);
3.2、基於對象進行操作
我們定義了對象變量並使用初始化函數給其作了初始化。接着我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據並轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。
1 /*獲取數據值*/ 2 void GetMeasureDataFromDHT11(void) 3 { 4 float temperature; //溫度值 5 float humidity; //濕度值 6 7 GetProcessValueFromDHT11(&dht); 8 9 temperature=dht.temperature; 10 humidity=dht.humidity; 11 }
4、應用總結
我們已經實現了DHT11溫濕度傳感器的驅動,並在此基礎上設計了簡單的驗證應用。經過測試,利用驅動我們成功的讀取了溫濕度數據。
根據數據手冊的要求,DHT11溫濕度傳感器上電后要等待1S以越過不穩定狀態在此期間不能發送任何指令。
單總線數據傳輸時,會改變總線的輸入輸出方向。在我們的應用中,我們修改了對應GPIO引腳的輸入輸出模式。事實上如果我們在STM32中使用時,我們可將該引腳配置為開漏輸出模式,加上總線的上拉電阻,可以在不修改GPIO的輸入輸出模式的情況下實現讀寫。
源碼下載:https://github.com/foxclever/ExPeriphDriver