第二十七章DHT11數字溫濕度傳感器實驗
本章,我們将介紹數字溫濕度傳感器DHT11的使用,與前一章的DS18B20溫度傳感器相比,該傳感器不但能測溫度,還能測濕度。我們将學習如何擷取DHT11傳感器的溫濕度資料,并把資料通過序列槽列印出來。
本章分為如下幾個小節:
27.1、DHT11簡介;
27.2、硬體設計;
27.3、軟體設計;
27.4、編譯和測試;
27.1 DHT11簡介
27.1.1 DHT11簡介
DHT11是一款溫濕度一體化的數字傳感器。該傳感器包括一個電阻式測濕元件和一個NTC測溫元件,并與一個高性能8位單片機相連接配接。通過單片機等微處理器簡單的電路連接配接就能夠實時的采集本地濕度和溫度。DHT11與單片機之間能采用簡單的單總線進行通信,僅僅需要一個I/O口。傳感器内部濕度和溫度資料共有40 bit的資料一次性傳給單片機,資料采用校驗和方式進行校驗,有效的保證資料傳輸的準确性。DHT11功耗很低,5V電源電壓下,工作平均最大電流0.5mA。
DHT11的技術參數如下:
- 工作電壓範圍:3.3V ~ 5.5V
- 工作電流:平均0.5mA
- 輸出:單總線數字信号
- 測量範圍:濕度20~90%RH,溫度0~50℃
- 精度:濕度±5%,溫度±2℃
- 分辨率:濕度1%,溫度1℃
DHT11的管腳排列如圖43.1.1所示:
圖27.1.1.1 DHT11管腳排列圖
DHT11廣泛應用于暖通空調、除濕器、農業、冷鍊倉儲、測試及檢測裝置、消費品、
汽車、自動控制、資料記錄器、氣象站、家電、濕度調節器、醫療等相關溫濕度檢測控制領域。
27.1.2 DHT11工作時序簡介
1. 單總線結構
DHT11器件采用單總線通信,隻有一根資料線,系統中的資料交換、控制均由單總線完成。單總線接一個約4.7kΩ的上拉電阻,當總線空閑時,總線狀态為高電平。關于單總線我們在前面的DS18B20章節已經講解過,後面編寫程式時注意的是,要配置總線占用的IO口為開漏輸出模式。
2. 資料結構
單總線上的裝置屬于主從結構,隻有主機呼叫從機時,從機才能應答,是以主機通路器件都必須嚴格遵循單總順序列,如果出現序列混亂,器件将不響應主機。雖然DHT11與DS18B20類似,都是單總線通路,但是DHT11的通路,相對DS18B20來說簡單很多。下面我們先來看看DHT11的資料結構。
單總線上一次傳送5 byte(40 bit)的資料,且高位先發送,資料分小數部分和整數部分,DHT11的資料格式為:
8bit濕度整數資料+8bit濕度小數資料+8bit溫度整數資料+8bit溫度小數資料+8bit校驗位。
其中校驗和資料為前面四個位元組相加。傳感器資料輸出的是未編碼的二進制資料,資料(濕度、溫度、整數、小數)之間應該分開處理。
(1)溫度是正數的情況
例如,某次從DHT11讀到的資料如下圖所示:
byte4 byte3 byte2 byte1 byte0
00110101 00000000 00011000 00000100 01010001
濕度高8位 濕度低8位 溫度高8位 溫度低8位 校驗位
整數 小數 整數 小數 校驗位
根據以上的資料進行計算:
1)檢查資料接收
00110101+00000000+00011000+00000100=01010001
計算結果和校驗位相等,接收資料正确,如果計算得到的值和校驗位不一緻,那麼本次接收的資料不正确,放棄,重新接收資料。
2)計算濕度和溫度的值
濕度:
整數)=35H=53%RH
小數)=00H=0.0%RH
最後濕度=>53%RH+0.0%RH=53.0%RH
溫度:
整數)=18H=24℃
小數)=04H=0.4℃
最後溫度=>24℃+0.4℃=24.4℃
(2)溫度是負數的情況
如果,當溫度低于0℃時,溫度資料的低8位的最高位置為1,計算時,該位可以視為0。如果溫度是負數,校驗位和濕度計算和上面的情況一樣,這裡說明一下溫度計算,如-10.1℃表示為00001010 10000001,計算方法為:
0000 1010(整數)=0AH=10℃
00000001(小數)=01H=0.1℃
最後溫度=>-(10℃+0.1℃)=-10.1℃
3. DHT11時序
DHT11上電後(DHT11上電後要等待1S以越過不穩定狀态,在此期間不能發送任何指令),待1s過後,可以測試環境溫濕度資料,DHT11的DATA資料線由上拉電阻拉高一直保持高電平,此時DHT11的DATA引腳處于輸入狀态,時刻檢測外部信号。下面我們看看DHT11的時序:
1)複位信号和應答信号時序
首先主機發送開始信号(也叫複位信号),即:拉低資料線,保持t1(至少18ms,最大不得超過30ms)時間,然後主機拉高資料線t2(20~40us)時間,然後主機讀取DHT11的響應,正常的話,DHT11會拉低資料線,并保持t3(40~50us)時間來作為響應信号,然後DHT11拉高資料線,保持t4(40~50us)時間後,DHT11開始輸出資料。
圖27.1.2.1 開始信号和應答信号
2)DHT11輸出數字‘0’時序
DHT11每發送的1 bit 資料都以低電平開始,低電平延時時間約為12~14us,然後DHT11拉高總線,如果總線拉高26~28us,表示發送0,然後DHT11再将總線拉低12~14us,為下一次發送資料做準備。(注意,此時序圖中,為按位發送。)
圖27.1.2.2 DHT11輸出數字‘0’
3)DHT11輸出數字‘1’時序
如果DHT11拉低總線12~14us後,再将總線拉高116~118us,表示發送數字1,然後DHT11再将總線拉低12~14us,為下一次發送資料做準備。(注意,此時序圖中,為按位發送。)
圖27.1.2.3 DHT11輸出數字‘1’
27.2硬體設計
1. 例程功能
把DHT11傳感器插在預留的接口上,測試目前的環境溫度和濕度,并将溫度和濕度通過序列槽列印出來。實驗中通過觀察LED0閃爍提示程式在運作。預留的接口在開發闆底闆的位置如下:
圖27.2.1 開發闆硬體示意圖
上圖中的開發闆預留接口的4個引腳從左到右依次為VCC、DQ、GND、GND,是以DHT11這樣插入開發闆(注意,不要插錯!):
圖27.2.2硬體連接配接示意圖
2. 硬體資源
1)LED燈:LED0
2)UART4
3)DS18B20(接在PF2上)
LED0 | DHT11 | UART4_TX | UART4_RX |
PI0 | 接在PF2上 | PG11 | PB2 |
表27.2.1硬體資源
3. 原理圖
如下圖,DS18B20接在PF2上,我們通過該IO口模拟單總線的時序來控制DS18B20:
圖27.2.3原理圖部分
27.3 軟體設計
本實驗配置好的實驗工程已經放到了開發闆CD光牒中,路徑為:開發闆CD光牒A-基礎資料\1、程式源碼\11、M4 CubeIDE裸機驅動例程\CubeIDE_project\ 20 DHT11。
27.3.1 建立和配置工程
建立工程DHT11,然後配置LED0和UART4,再配置PF2為開漏輸出、上拉、高速模式:
圖26.3.1.1 配置GPIO
本節實驗會用到微秒延時函數,需要将第二十三章實驗的delay.h和delay.c檔案。将上一章節實驗的BSP檔案夾拷貝到M4工程的Core/Src下,然後在BSP檔案夾下建立dht11.c檔案,在BSP/Include下建立dht11.h檔案,最後的部分工程如下:
圖26.3.2 工程部分目錄
27.3.2 添加使用者代碼
關于LED0和UART4相關的代碼請參考前面實驗章節部分,DHT11驅動代碼我們把它放在dht11.c和dht11.h檔案中。首先我們先看一下dht11.c頭檔案裡面的内容。
1. dht11.h檔案代碼
dht11.h檔案代碼如下:
#ifndef __DHT11_H
#define __DHT11_H
#include"gpio.h"
/* DHT11 引腳 定義 */
#define DHT11_DQ_GPIO_PORT GPIOF
#define DHT11_DQ_GPIO_PIN GPIO_PIN_2
/* PF口時鐘使能 */
#define DHT11_DQ_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)
/* IO操作函數 */
#define DHT11_DQ_OUT(x) do{ x ? \
HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_SET) : \ HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_RESET); \ }while(0) /* 資料端口輸出 */
/* 資料端口輸入 */
#define DHT11_DQ_IN HAL_GPIO_ReadPin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN)
uint8_t dht11_init(void); /* 初始化DHT11 */
uint8_t dht11_check(void); /* 檢測是否存在DHT11 */
uint8_t dht11_read_data(uint8_t *temp,uint8_t *humi); /* 讀取溫濕度 */
#endif
以上主要是對DHT11的相關引腳、時鐘使能和IO操作函數進行宏定義,友善程式中調用。
2. dht11.c檔案代碼
根據前面對DHT11的時序分析,我們按照時序部分進行程式設計,首先是複位DHT11函數,如下:
(1)複位DHT11
/**
* @brief複位DHT11
* @param無
* @retval無
*/
static void dht11_reset(void)
{
DHT11_DQ_OUT(0); /* 拉低DQ */
delay_ms(20); /* 拉低至少18ms */
DHT11_DQ_OUT(1); /* DQ=1 */
delay_us(30); /* 主機拉高20~40us */
}
(2)等待DHT11應答
/**
* @brief等待DHT11的回應
* @param無
* @retval正常
* 1, DHT11異常/不存在
*/
uint8_t dht11_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DHT11_DQ_IN && retry < 100) /* DHT11會拉低40~80us */
{
retry++;
delay_us(1);
}
if (retry >= 100) /* 逾時,DHT11異常 */
{
rval = 1;
}
else
{
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* DHT11拉低後會再次拉高40~80us */
{
retry++;
delay_us(1);
}
if (retry >= 100) rval = 1; /* 逾時,DHT11異常 */
}
return rval;
}
應答信号也是按照前面的時序圖進行編寫的,首先DHT11将總線拉低,是以程式先等待總線變為低電平,這裡在測試引腳電平的時候,設定最大的檢測時間是100us,如果超過100us内電平沒有變化,則認為DHT11異常。如果DHT11拉低總線後,再釋放總線,則總線為高電平,是以,在測試總線拉低以後,再檢測總線是否被拉高。
(3)從DHT11讀取一個位
和DS18B20不一樣,DHT11不需要寫的過程,直接可以讀取溫濕度資料:
1 /**
2 * @brief從DHT11讀取一個位
3 * @param無
4 * @retval讀取到的位值: 0 / 1
5 */
6 uint8_t dht11_read_bit(void)
7 {
8 uint8_t retry = 0;
9 while (DHT11_DQ_IN && retry < 100) /* 等待變為低電平 */
10 {
11 retry++;
12 delay_us(1);
13 }
14 retry = 0;
15 while (!DHT11_DQ_IN && retry < 100) /* 等待變高電平 */
16 {
17 retry++;
18 delay_us(1);
19 }
20 delay_us(40); /* 等待40us */
21 if (DHT11_DQ_IN) /* 根據引腳狀态傳回 bit */
22 {
23 return 1;
24 }
25 else
26 {
27 return 0;
28 }
29 }
DHT11輸出數字0或者1都是先以低電平開始的,是以第9~13行在等待DHT11變為低電平,同樣,測試的最大時間為100us。DHT11通過拉高總線發送數字0或1,第15~19行在等待總線拉高,總線拉高後,延時40us後檢查DHT11的IO的狀态,如果此時引腳為高電平,則認為從DHT11讀取到1,反之則從DHT11讀取到0。
(4)從DHT11讀取一個位元組
讀取一個位元組需要對讀取一個位循環8次,如下:
/**
* @brief從DHT11讀取一個位元組
* @param無
* @retval讀到的資料
*/
static uint8_t dht11_read_byte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++) /* 循環讀取8位資料 */
{
data <<= 1; /* 高位資料先輸出, 先左移一位 */
data |= dht11_read_bit(); /* 讀取1bit資料 */
}
return data;
}
(5)從DHT11讀取一次資料
DHT11的資料組成我們前面已經分析過了,40位(5個位元組)資料中,高位先發送,我們設定5和Buffer用于存儲這5個位元組的資料,是以接收到的資料中,Buffer4是檢驗位,Buffer3和Buffer2分别是溫度的小數和整數部分,而Buffer1和Buffer0分别是濕度的小數和整數部分。 我們先擷取到這5個位元組的資料,然後先通過校驗位檢查接收到的資料是否正确,如果正确的話,通過指針的方式指向溫度和濕度的整數位。
/**
* @brief從DHT11讀取一次資料
* @param溫度值(範圍:0~50°)
* @param濕度值(範圍:20%~90%)
* @retval正常.
失敗
*/
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
dht11_reset();
if (dht11_check() == 0)
{
for (i = 0; i < 5; i++) /* 讀取40位資料 */
{
buf[i] = dht11_read_byte();/* 讀到的值存在buf[i]中 */
}
/* 通過校驗位檢查讀取到的資料是否正确 */
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0]; /* 濕度整數和小數分别在buf[0]和buf[1]中 */
*temp = buf[2]; /* 溫度整數和小數分别在buf[2]和buf[3]中 */
}
}
else /* DHT11沒有應答 */
{
return 1;
}
return 0;
}
(6)初始化DHT11的IO口
最後,不要忘了要初始化IO口才可以使用IO口,如下是DHT11的IO口初始化函數,配置IO口為開漏輸出、上拉、高速模式,單總線中一定要配置總線為開漏輸出,這點我們在前面部分已經分析過。
/**
* @brief初始化DHT11的IO口 DQ 同時檢測DHT11的存在
* @param無
* @retval正常
不存在/不正常
*/
uint8_t dht11_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
DHT11_DQ_GPIO_CLK_ENABLE(); /* 開啟DQ引腳時鐘 */
gpio_init_struct.Pin = DHT11_DQ_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 開漏輸出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
/* 初始化DHT11_DQ引腳 */
HAL_GPIO_Init(DHT11_DQ_GPIO_PORT, &gpio_init_struct);
/* DHT11_DQ引腳模式設定,開漏輸出,上拉, 這樣就不用再設定IO方向了, 開漏輸出的時候(=1), 也可以讀取外部信号的高低電平 */
dht11_reset(); /* 複位DHT11 */
return dht11_check(); /* 等待DHT11的回應 */
}
3. main.c檔案代碼
main.c檔案的部分代碼如下,我們需要在标紅的字型之間手動添加代碼:
1 #include "main.h"
2 #include "usart.h"
3 #include "gpio.h"
4 /* USER CODE BEGIN Includes */
5 #include "./BSP/Include/led.h"
6 #include "./BSP/Include/delay.h"
7 #include "./BSP/Include/dht11.h"
8 /* USER CODE END Includes */
9
10 void SystemClock_Config(void);
11
12 int main(void)
13 {
14 HAL_Init();
15 if(IS_ENGINEERING_BOOT_MODE())
16 {
17 SystemClock_Config();
18 }
19 MX_GPIO_Init();
20 MX_UART4_Init();
21 /* USER CODE BEGIN 2 */
22 HAL_UART_Receive_IT(&huart4,&RxBuffer,1); /* 以中斷方式接收函數 */
23 led_init(); /* 初始化LED */
24 delay_init(209); /* 延時初始化延時函數 */
25 uint8_t t = 0;
26 uint8_t temperature;
27 uint8_t humidity;
28 /* USER CODE END 2 */
29 while (1)
30 {
31 /* USER CODE BEGIN 3 */
32 while (dht11_init()) /* DHT11初始化函數 */
33 {
34 printf("DHT11 Error!\r\n"); /* 如果DHT11不在,列印資訊 */
35 delay_ms(1000); /* 延時1s */
36 }
37 while (1)
38 {
39 t++;
40 if (t == 20)
41 {
42 t = 0;
43 dht11_read_data(&temperature, &humidity); /* 讀DHT11 */
44 /* 列印溫度值和濕度值 */
45 printf("DHT11 Temp=%d\r, Humi=%d%%\r\n", temperature, humidity);
46 LED0_TOGGLE(); /* LED0閃爍 */
47 }
48 delay_ms(10); /* 延時10ms*/
49 }
50 }
51 /* USER CODE END 3 */
52 }
27.4 編譯和測試
開發闆上電,進入仿真運作後結果如下,序列槽列印此時測試的環境溫度為24℃,濕度為63%,且LED0在閃爍。