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




張貼留言