STM32 實例應用 ─ DHT11 溫溼度感測

  溫溼度感測是目前很常見的需求及技術,而 DHT11 是相當容易控制及撰寫的溫溼度感測器, DHT11 為數位信號輸出溫濕度複合感測器,包括一個電阻式感濕元件和一個NTC測溫元件,為 4 Pin*2.54的DIP封裝,使用及安裝相當方便。

該實例以STM32L031F6Px進行開發,配合STM32CubeMX及Keil 5,使用HAL函式庫。關於感測器更詳細的內容及資料可以參考資料手冊:AOSONG DHT11 Datasheet

照片取自廣華電子

如上圖及資料手冊所示, DHT11 僅透過Pin2─DATA進行感測資料的存取,一共有40個bits,分別是:濕度整數、濕度小數、溫度整數、溫度小數、資料檢驗碼,各8個bits。在資料手冊第5頁提及將前32個bits相加若等於檢驗碼,則是正確資料,若不是則放棄該筆資料,重新存取即可。

上圖可以看到整體的存取時序,且將時序用黑色及灰色呈現,分別是由MCU輸出給DHT11及MCU讀取 DHT11 的輸出訊號,因此在稍後的接腳設定在輸出輸入皆可,因實際上仍是由軟體進行切換控制。整體時序可以看到有一個起始訊號由MCU送給 DHT11 (Host the start of signal),接著Pull-up等待 DHT11 回應給MCU,收到Ready訊號後,則由 DHT11 輸出給MCU,透過輸出的High與Low時間長短表示資料”0″或是資料”1″。

在資料手冊中分為5個步驟,依序來了解。
1. 電源啟動需等待1秒,並有提升電阻於DATA Pin。
2. 須由MCU輸出至少18ms的Low訊號為起始訊號至 DHT11

起始訊號

3. DHT11 收到起始訊號後,會輸出80us的Low與80us的High訊號至MCU,表示為Ready

Ready訊號


4. 存取溫溼度資料,起始皆為50us的Low,若接著是26~28us的HIGH則代表Data”0″,若為70us則是Data”1″,連續存取40個Bits。

資料存取訊號

STM32CubeMX專案設定

透過上述的資料手冊介紹,可以了解到需要1隻GPIO與 DHT11 溝通及1個TIMER進行時間的量測,同時使用USART列印至PC端觀看。因此CubeMX的設定如下,PA0為GPIO Out,不須做內部的設定,因為該腳會輸出輸入切換,實際由軟體進行控制,而TIM2為計數的TIMER,除頻為31,Period為65535(最大計數量),因為Clock的設定為32Mhz,因此TIMER2以1us的周期進行計數最符合DHT11的需求。

CubeMX專案設定

Keil

首先創立一個.c檔及.h檔,名稱皆為 dht11 ,用於撰寫 DHT11 的存取函式。
在.h檔先建立一個 DHT11 的結構體為 dht11_device,在後續可以用於存取所宣告的 DHT11 資料及TIMER設定。


※ .h檔記得引用”stm32l0xx.h”及”main.h”

struct dht11{
	TIM_HandleTypeDef *htim; 
	GPIO_TypeDef* port;
	uint16_t pin; 
	float temperature;
	uint8_t humidty; 
};
typedef struct dht11 dht11_device;
DHT11結構體

接著在.c檔先寫該結構體的初始化設定副程式。
將所要設定的 dht11_deivce 傳入,並將該結構體中所定義的TIMER、GPIO PORT、GPIO PIN與當初在Cube設定的傳入。

void dht11_init(dht11_device *dht, TIM_HandleTypeDef *htim, GPIO_TypeDef* port, uint16_t pin){
	dht->htim = htim;
	dht->port = port;
	dht->pin = pin;
}

在前方有提及DATA Pin會切換輸出及輸入,並需要Pull-up的設定,因此直接寫一個針對GPIO Mode設定的副程式,當傳入的Mode為1,則設定 dht11_device 傳入結構體中的DATA Pin為輸出,反之若為0,則是輸入。

void set_pin_mode(dht11_device *dht, uint8_t Mode)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	if(Mode)		//OUTPUT
	{
	  GPIO_InitStruct.Pin = dht->pin;
	  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	  GPIO_InitStruct.Pull = GPIO_PULLUP;
	  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	  HAL_GPIO_Init(dht->port, &GPIO_InitStruct);
	}else				//INPUT
	{
	  GPIO_InitStruct.Pin = dht->pin;
	  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	  GPIO_InitStruct.Pull = GPIO_PULLUP;
	  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	  HAL_GPIO_Init(dht->port, &GPIO_InitStruct);
	}
}

接著以防 DHT11 死機,因此透過TIMER計數,若200us都沒有反應,則回傳0,致能所有中斷,以避免因為 DHT11 存取時的問題導致程式皆卡死。

uint8_t dht11_timeout(dht11_device *dht)
{
	if((uint16_t)dht->htim->Instance->CNT > 200){
		__enable_irq();
		return 0;
	}
	return 1;
}

最後則是資料存取的函式為uint8_t get_dht11_data(dht11_device *dht)。
變數描述如下:
t1、t2用來計算訊號的時間、Bit為讀取到的”0″ or “1”
humVal、tempVal為濕度、溫度的整數資料
temp_float為溫度的小數資料(濕度在此沒有存取小數)
buffer[40] 為40個Bits的完整資料。

在此因小數的計算使用Pow函式,因此.c檔最前方需引入math.h。

依序為Start Signal、Ready Signal(80us Low、80us High)、資料存取。
※ 在此無做資料驗證判斷及實際的溫溼度校準,雖DHT11出廠已有初步校正,實際仍需透過精準的儀器比較後針對DHT11撰寫演算法或以線性回歸方式校正數值。

uint8_t get_dht11_data(dht11_device *dht)
{
	uint16_t t1 = 0, t2 = 0, Bit = 0;
	uint8_t humVal = 0, tempVal = 0;
	float temp_float = 0;
	uint8_t buffer[40];

	//Start Signal
	set_pin_mode(dht, 1);	
	dht->port->ODR&=~(1<<(dht->pin));
	HAL_Delay(18);
	dht->port->ODR|=(1<<(dht->pin));
	
	__disable_irq();	//disable all interupts to do only read dht otherwise miss timer
	HAL_TIM_Base_Start(dht->htim); //start timer
	
	//set pin mode to input & check the DATA pin is high
	set_pin_mode(dht, 0);
	dht->htim->Instance->CNT=0;	
	while(HAL_GPIO_ReadPin(dht->port, dht->pin) == 1){
		if(!dht11_timeout(dht))return 0;
	}
	
	//80us of low
	dht->htim->Instance->CNT=0;	
	while(HAL_GPIO_ReadPin(dht->port, dht->pin) == 0){
		if(!dht11_timeout(dht))return 0;
	}
	t1 = (uint16_t)dht->htim->Instance->CNT;
	
	//80us of high
	dht->htim->Instance->CNT=0;	
	while(HAL_GPIO_ReadPin(dht->port, dht->pin) == 1){
		if(!dht11_timeout(dht))return 0;
	}
	t2 = dht->htim->Instance->CNT;

	//if something wrong,the answer is not 80us.
	if(t1 < 75 && t1 > 85 && t2 < 75 && t2 > 85)
	{
		__enable_irq();
		return 0;
	}

	//Get Data
	for(int j = 0; j < 40; j++)
	{
		dht->htim->Instance->CNT=0;	
		while(HAL_GPIO_ReadPin(dht->port, dht->pin) == 0){
			if(!dht11_timeout(dht))return 0;
		}
		
		dht->htim->Instance->CNT=0;	
		while(HAL_GPIO_ReadPin(dht->port, dht->pin) == 1){
			if(!dht11_timeout(dht))return 0;
		}
		t1 = dht->htim->Instance->CNT;

		//if pass time 25us set as LOW
		if(t1 > 20 && t1 < 30)
		{
			Bit = 0;
		}
		//if pass time 70us set as HIGH
		else if(t1 > 60 && t1 < 80) 
		{
			 Bit = 1;
		}

		buffer[j] = Bit;
	}

	HAL_TIM_Base_Stop(dht->htim); 	//stop timer
	__enable_irq(); 								//enable all interrupts
	
	//get hum value from data buffer
	for(int i = 0; i < 8; i++)
	{
		humVal += buffer[i];
		humVal = humVal << 1;
	}

	//get temp value from data buffer
	for(int i = 16; i < 24; i++)
	{
		tempVal += buffer[i];
		tempVal = tempVal << 1;
	}
	for(int i = 24; i < 30; i++)
	{
		temp_float +=(float)(buffer[i]*pow(2,-8+(i-24)));
	}

	humVal = humVal >> 1;
	tempVal = tempVal >> 1;

	dht->temperature =(float)(temp_float);
	dht->temperature+=tempVal;
	dht->humidty=humVal;
	
	return 1;
}

完成後須到.h檔完成副程式的宣告如下

void dht11_init(dht11_device *dht, TIM_HandleTypeDef *htim, GPIO_TypeDef* port, uint16_t pin);
void set_pin_mode(dht11_device *dht, uint8_t Mode);
uint8_t dht11_timeout(dht11_device *dht);
uint8_t get_dht11_data(dht11_device *dht);

main.c

在while前方需先宣告dht11_device及初始化結構體。

	dht11_device dht;
	dht11_init(&dht, &htim2, GPIOA, 1<<0);

在while當中get_dht11_data進行溫溼度資料的存取。

使用全域變數tempData、humData存取則可以在Debug模式透過Watch視窗查看變數變化,並以1000ms的頻率進行資料採樣。

在此附上該DHT11的專案於Github:STM32L0_DHT11

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *