DHT11 是一個常見的溫濕度感測器,過去曾使用不同的開發板(Arduino 及 Raspberry Pi)讀取溫濕度顯示在 LCD 上。本篇將繼續深入瞭解 DHT11 的運作方式,如何藉由高低電位的改變,讓 DHT11 回傳一串 40 Bits 帶有溫濕度的感測值,然後將溫濕度顯示在 OLED 上。
DHT11 是經過校準的數位溫濕度傳感器,包括一個電阻式感濕元件和一個 NTC 測溫元件。其他特性還有:
STM32 在發送啟動信號後,DHT11 從低功耗模式轉換到高速模式,STM32 直到啟動信號結束,DHT11 發出響應信號,發送 40 bits 的數據並觸發信號採集。如果沒有接收到 STM32 主機發送開始信號,DHT11 不會主動進行溫濕度採集,採集數據後轉換到低速模式。信號傳輸的時序圖如下:
STM32 如要讀取 DHT11 外部設備的數據,之間的通信可以透過以下步驟完成:
40 Bits 的數據表示溫濕度的方式如下表:如果資料傳送正確,前面四個 8 bits 資料相加會和校驗位的 8 bits 相同。
上表的溫濕度整數位進行換算得到以下結果:
校驗位將前面四個 8 bits 的資料相加:0011 0101 + 0000 0000 + 0001 1000 + 0000 0000 = 0100 1101
如果和回傳的結果一樣,表示資料正確,若不相同則重新測試再傳回感測值。
DHT11 是經過校準的數位溫濕度傳感器,包括一個電阻式感濕元件和一個 NTC 測溫元件。其他特性還有:
- 訊號傳輸型式: 數位 (Digital)
- 電壓範圍:3.3 ~ 5.5V。
- 測溫範圍:濕度 20 ~ 90%RH,溫度 0 ~ 50℃
- 精度:濕度 ±5%,溫度 ±2℃
STM32 在發送啟動信號後,DHT11 從低功耗模式轉換到高速模式,STM32 直到啟動信號結束,DHT11 發出響應信號,發送 40 bits 的數據並觸發信號採集。如果沒有接收到 STM32 主機發送開始信號,DHT11 不會主動進行溫濕度採集,採集數據後轉換到低速模式。信號傳輸的時序圖如下:
STM32 如要讀取 DHT11 外部設備的數據,之間的通信可以透過以下步驟完成:
- DHT11 通電後,這時等待通過不穩定期間,不適宜發送任何命令。初始化時,經上拉電阻提高 DATA 數據線的電位,並保持高電位;這時 DHT11 的 DATA pin 為輸入狀態,等待輸入外部信號。
- STM32 將連接 DHT11 DATA 的 I/O ,設置為低電位,保持時間不能小於 18 ms,再拉高 I/O 的電位,此時 STM32 進入設置狀態,等待 DHT11 應答信號。發射信號如下圖(左):
- 當 DHT11 DATA 引腳檢測到外部信號為低電位時,等待低電平結束,等待 DHT11 延時過後,DATA 引腳變更為輸出,輸出低電位 80 微秒響應信號,接著輸出 80 微秒通知第二個高電位外設準備好接收數據,此時 STM32 處於輸入狀態,檢測 I/O 為低電位,直到 80 微秒高電位,數據收發信號如下圖(右):
- 由 DHT11 DATA 引腳輸出的 40 位元數據,STM32 根據 I/O 電位接收 40 位元數據,如要表示訊號「0」:高電位持續 50 微秒,接著低電位 26~28 微秒;訊號「1」:低電位 50 微秒,接著 70 微秒高電位。數據「0」、「1」格式信號如下圖:
- 結束信號時,DHT11 的 DATA 引腳輸出 40 位數據後,持續 50 微秒低電位的狀態後,上拉電阻隨之拉高。
40 Bits 的數據表示溫濕度的方式如下表:如果資料傳送正確,前面四個 8 bits 資料相加會和校驗位的 8 bits 相同。
濕度整數位 (8 Bits) | 濕度小數位 (8 Bits) | 溫度整數位 (8 Bits) | 溫度小數位 (8 Bits) | 校驗位 (8 Bits) |
---|---|---|---|---|
0011 0101 | 0000 0000 | 0001 1000 | 0000 0000 | 0100 1101 |
上表的溫濕度整數位進行換算得到以下結果:
- 濕度:0011 0101 = 35(十六進位) = 53(十進位) %RH
- 溫度:0001 1000 = 18(十六進位) = 24(十進位) °C
校驗位將前面四個 8 bits 的資料相加:0011 0101 + 0000 0000 + 0001 1000 + 0000 0000 = 0100 1101
如果和回傳的結果一樣,表示資料正確,若不相同則重新測試再傳回感測值。
[材料]
- STM32F103C8T6 主板 x 1
- OLED SSD1306 顯示器 x 1
- STLINK V2 模擬下載器 x 1
- DHT11 x 1
- 麵包板 x 1
- 連接線 x N 條
[接線與電路圖]
三個可變電阻左右兩側分別接 3.3v 及 GND,中間的接腳分別接在 STM32F103x 的 PA0、PA1 及 PA2,與 SSD1306 連接的方式如下:STM32F103x | SSD1306 OLED | DHT11 |
---|---|---|
3.3v | VDD | VCC |
GND | GND | GND |
B8 | SCK/SCL | - |
B9 | SDA | - |
A1 | - | DATA |
[程式]
#include <stm32f10x.h> #include "oled.h" #include "delay.h" #include "dht11.h" int main() { u8 temperature, tempd; u8 humidity, humid; delay_init(); //延時函數初始化 OLED_Init(); //初始化OLED DHT11_Init(); //DHT11初始化 OLED_Clear(0); //清除螢幕 OLED_ShowString(1, 2, "Temper. :",16); OLED_ShowString(1, 4, "Humidity:",16); while(1) { DHT11_Read_Data(&temperature,&tempd,&humidity,&humid); OLED_ShowNum(70, 2, temperature, 3, 16); OLED_ShowString(94,2, ".",16); OLED_ShowNum(98, 2, tempd, 1, 16); OLED_ShowNum(70, 4, humidity, 3, 16); OLED_ShowString(94,4, ".",16); OLED_ShowNum(98, 4, humid, 1, 16); delay_ms(100); } }dht11.c 的程式如下:
#include "stm32f10x.h" #include "dht11.h" #include "delay.h" // DHT11初始化,返回0:初始化成功,1:失敗 uint8_t DHT11_Init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitStructure.GPIO_Pin = DHT11_pin; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(DHT11_Port,&GPIO_InitStructure); GPIO_SetBits(DHT11_Port,DHT11_pin); //拉高 DHT11_Rst(); return DHT11_Check(); } //重置DHT11 void DHT11_Rst() { DHT11_IO_OUT(); //設定I/O口為輸出模式 DHT11_DQ_OUT=0; //拉低DQ delay_ms(20); //拉低至少18ms DHT11_DQ_OUT=1; //DQ=1 delay_us(30); //主機拉高20~40us } // 檢測DHT11,傳回0:存在,返回1:未檢測到DHT11的存在 uint8_t DHT11_Check() { u8 retry=0; DHT11_IO_IN(); //設定I/O口為輸入模式 while (DHT11_DQ_IN&&retry<100) //高電平迴圈,低電平跳出,DHT11會拉低40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; else retry=0; while (!DHT11_DQ_IN && retry<100) //DHT11拉低後會再次拉高40~80us { retry++; delay_us(1); }; if(retry>=100)return 1; return 0; } //從DHT11讀取一個位,返回值:1或0 uint8_t DHT11_Read_Bit(void) { u8 retry=0; while(DHT11_DQ_IN&&retry<100) //等待變為低電平 12-14us 開始 { retry++; delay_us(1); } retry=0; while(!DHT11_DQ_IN&&retry<100) //等待變高電平 26-28us表示0,116-118us表示1 { retry++; delay_us(1); } delay_us(40); //等待40us if(DHT11_DQ_IN)return 1; else return 0; } //從DHT11讀取一個位元組,返回值:讀到的數據 uint8_t DHT11_Read_Byte(void) { u8 i,dat; dat=0; for (i=0;i<8;i++) { dat<<=1; //dat的數據左移一個bit, 右邊補0 dat|=DHT11_Read_Bit(); //兩邊做 OR 運算,結果存到 dat } return dat; } //從DHT11讀取一次資料,返回值:0,正常;1,讀取失敗 uint8_t DHT11_Read_Data(u8 *temp,u8 *tempd, u8 *humi, u8 *humid) { u8 buf[5]; u8 i; DHT11_Rst(); if(DHT11_Check()==0) { for(i=0;i<5;i++) //讀取40位元數據 { buf[i]=DHT11_Read_Byte(); } if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi =buf[0]; *humid=buf[1]; *temp =buf[2]; *tempd=buf[3]; } } else { return 1;} return 0; } //DHT11 輸出模式配置 void DHT11_IO_OUT() { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin=DHT11_pin; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽輸出 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(DHT11_Port,&GPIO_InitStructure); } //DHT11 輸入模式配置 void DHT11_IO_IN() { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin=DHT11_pin; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉輸入模式 GPIO_Init(DHT11_Port,&GPIO_InitStructure); }dht11.h 程式如下:
#ifndef __dht11_H #define __dht11_H #include "sys.h" #define DHT11_pin GPIO_Pin_1 //PA1 #define DHT11_Port GPIOA //GPIO Port #define DHT11_DQ_IN PAin(1) //輸入 #define DHT11_DQ_OUT PAout(1) //輸出 void DHT11_IO_OUT(void); void DHT11_IO_IN(void); void DHT11_Rst(void); uint8_t DHT11_Init(void); uint8_t DHT11_Check(void); uint8_t DHT11_Read_Bit(void); uint8_t DHT11_Read_Byte(void); uint8_t DHT11_Read_Data(u8 *temp,u8 *tempd, u8 *humi, u8 *humid); #endif完整的程式請參考 Github:11. Get DHT11 Temperature & Humidity
張貼留言