STM32筆記(26):讀取DHT11溫濕度感測值

DHT11 是一個常見的溫濕度感測器,過去曾使用不同的開發板(Arduino 及 Raspberry Pi)讀取溫濕度顯示在 LCD 上。本篇將繼續深入瞭解 DHT11 的運作方式,如何藉由高低電位的改變,讓 DHT11 回傳一串 40 Bits 帶有溫濕度的感測值,然後將溫濕度顯示在 OLED 上。


DHT11 是經過校準的數位溫濕度傳感器,包括一個電阻式感濕元件和一個 NTC 測溫元件。其他特性還有:
  1. 訊號傳輸型式: 數位 (Digital)
  2. 電壓範圍:3.3 ~ 5.5V。
  3. 測溫範圍:濕度 20 ~ 90%RH,溫度 0 ~ 50℃
  4. 精度:濕度 ±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 01010000 00000001 10000000 00000100 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 連接的方式如下:
STM32F103xSSD1306 OLEDDHT11
3.3vVDDVCC
GNDGNDGND
B8SCK/SCL-
B9SDA-
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

[結果]



[參考資料]

Post a Comment

較新的 較舊