STM32筆記(29):使用I2C介面的AHT10測量溫濕度

之前在學習 Adruino 時,對於 I²C 的瞭解不是很清楚,偏重在其接線的方式,如 SDA 及 SCL 的連接,以及每種設備都有一個特殊的 ID 等,不是很深入的瞭解其運作原理。同樣的,剛開始學習使用 STM32 庫函式撰寫程式時,使用 SSD1306 OLED 顯示實做的結果,這個顯示器是 I²C 介面,為了要讓實做能順利進行,在網路上找了廠商提供的函式庫就繼續使用,那時還不瞭解 I²C 的通訊原理 ,本篇要對 I²C 的通訊方式進行整理,方便程式撰寫及未來查閱用。

本篇實做我使用 I²C 介面的 AHT10 溫濕度感測器來改寫 I²C 函式庫,包括 I²C 啟動、停止、傳送及接收位元等。過程中,我將 PA0 及 PA1 當作 I²C 的 SCL 及 SDA 引腳,執行程式時,得到的溫濕度都是 0,後來才發現不是每個引腳都能當作 I²C 的時鐘及資料引腳,找到我使用的開發板 STM32F103C8T6 的 Pinout 圖(請參考:STM32筆記(1):我的第一個STM32程式,使用Arduino IDE 介面)找到開發板上共有三組 I²C ,OLED 用掉一組,改選用 PB6 及 PB7 引腳,就可以正常顯示溫濕度值。

[I²C 串列通訊]

I²C(Inter-Integrated Circuit)字面上的意思是積體電路之間,它其實是I²C Bus簡稱,所以中文應該叫積體匯流排電路,它是一種串列通訊匯流排,使用多主從架構,由飛利浦公司在 1980 年代為了讓主機板、嵌入式系統或手機用以連接低速週邊裝置而發展。I²C的正確讀法為「I平方C」("I-squared-C")。[維基百科]

I²C 使用兩條線連接,其中一條線為傳輸資料的串列資料線(SDA),另一條線是啟動或停止傳輸以及傳送時鐘序列的串列時脈(SCL)線,如有多個 I²C 裝置,所有的SDA 串行數據都接到相同的 SDA 線,時鐘線 SCL 接到共通的 SCL 線,速率最高400Kbit/s。I2C 允許連接多個設備(如下圖),只能由 Master 與 Slave 之間互傳,無法由 Slave 對 Slave 進行傳輸,且每個 Slave 都要有一個特定且唯一的位址。

來源:analog.com

[I²C 協定與程式]

I²C 資料的傳輸,是靠 SCL 及 SDA 電位的高低及轉換來進行通訊,通訊的 Bus 協定如下:
上圖可分成三個部分,最左方的起始訊號(Start)、中央的資料傳輸及右方的停止訊號(Stop),I²C 的起始和停止訊號的電位轉換如下:
起始訊號(Start condition):當 SCL 高電位時,SDA 由高電位向低電位轉換;
停止訊號(Stop condition):當 SCL 高電位時,SDA 由低電位向高電位轉換;

將上述 I²C 起始訊號的電位轉換過程寫成程式,如下:
void I2C_Start(void)
{
  SDA_OUT();     	//SDA線輸出
  SDA_High;	
  SCL_High;
  delay_us(4);
  SDA_Low;	//START:當 SCL 為高電位時,SDA(DATA) 從高電位變成低電位 
  delay_us(4);
  SCL_Low;	//準備發送或接收資料 
}

將上述 I²C 停止訊號的電位轉換過程寫成程式,如下:
void I2C_Stop(void)
{
  SDA_OUT();	//SDA線輸出
  SDA_Low; 	//STOP:當 SCL 為高電位時,SDA(DATA) 從低電位變成高電位
  delay_us(4);
  SCL_High;	
  delay_us(4);
  SDA_High;	//發送I2C匯流排結束信號
  delay_us(4);							   	
}

將 I²C 串列傳輸協定分成以下幾個狀態:起始信號、設備位址發送、數據傳送、應答信號和停止信號。進行資料傳輸與接收的過程如下:

  1. SDA 和 SCL 開始都為高,然後 Master 主機將 SDA 電位拉低,表示訊號開始。
  2. 接下來的 8 個時間週期裡,Master 主機控制 SDA 的高低,傳送 Slave 主機地址。其中第 8 位元如果為 0,表示接下來是寫入操作,即 Master 主機傳輸資料給 Slave 裝置;如果為 1,表示接下來是讀取操作,即 Slave 裝置傳輸資料給 Master 主機;另外,資料傳輸是從最高位到最低位,因此傳輸方式為 MSB(Most Significant Bit)。
  3. 匯流排中對應 Slave 裝置地址,發出應答訊號。
  4. 在接下來的 8 個時間週期裡(傳送 1 個 Byte 字元),如果是寫入操作,則 Mater 主機控制 SDA 的電位高低;如果是讀取操作,則由 Slave 裝置控制 SDA 的高低;
  5. 每次傳輸完成,接收資料的裝置,都發出應答訊號(第 9 個 bit)。
  6. Mater 主機會在 ACK 的 SCL 時脈釋放 SDA 線( SDA 為 high),如果 Slave 裝置正常收到 DATA 的話,會在第 9 個 bit 將 SDA 電位為 Low,也就是回應 ACK。
  7. 如果 Slave 裝置有狀況,無法正常接收(如:Bus 上找不到這個 Address 的 Slave 裝置,到了第 9 個 SCL 週期時,當 Mater 主機釋放 SDA 後,就沒有裝置去驅動 SDA,這時 SDA 會因為上拉電阻(Rp)的關係維持在 High, Mater 主機就知道出問題了,不會繼續傳輸,而要進行錯誤處理,這種情況稱之為「non-acknowledge」,或簡稱為「NACK」。
  8. 最後,在 SCL 為高電位時,Master 主機由低拉高 SDA 電位,表示停止訊號,整個傳輸結束。

通訊時,如要發送一個位元,程式如下:
void IIC_Send_Byte(u8 txd)
{                        
  SDA_OUT(); 	    
  SCL_Low;		//拉低時鐘開始資料傳輸
  for(u8 t=0;t<8;t++)
  {              
    if((txd&0x80)>>7)
     SDA_High;
    else
     SDA_Low;
    txd<<=1; 	  
    delay_us(2);   
    SCL_High;	
    delay_us(2); 
    SCL_Low;	
    delay_us(2);
  }	
} 	   

通訊時,如要接收一個位元,程式如下:
u8 I2C_Read_Data(void)
{
  u8 Data = RESET;
  SDA_IN();		
  for(u8 i=0;i<8;i++)
  {
    SCL_High;	
    delay_us(2);
    Data <<= 1; 
		
  if(GPIO_ReadInputDataBit(I2C_Prot,SDA) == SET)
  {
    Data |= 0x01;
  }
	
  SCL_Low;
  delay_us(2);
  }	
  return Data;
}

通訊時,如要發送 ACK 訊號,程式如下:
u8 I2C_Write_Ack(void)
{
  u8 TimeAck = RESET;
  SDA_IN();
  SCL_High;
  delay_us(2);
	
  while(GPIO_ReadInputDataBit(I2C_Prot,SDA))
  {
    if(++TimeAck > 250)
    {
     I2C_Stop();
     return 1;
    }
  }
  SCL_Low;
  delay_us(2);	
  return 0;
}

通訊時,根據 ack 參數,決定是否產生 ACK 應答, ack=1 時,產生 ACK 應答,程式如下:
void I2C_Is_Ack(u8 ack)
{
  SCL_Low;
  SDA_OUT();
  if(ack)
    SDA_Low;	
  else
    SDA_High;	
  delay_us(2);
  SCL_High;		
  delay_us(2);
  SCL_Low;
}

[AHT10溫濕度感測器]

AHT10 溫濕度感測器的精確度比 DHT11 高,規格如下:
  • 介面類別型:I2C
  • 工作電壓:直流 1.8-3.6V
  • 工作濕度:0~8100%RH
  • 濕度精度:±2%
  • 濕度解析度:0.024%
  • 溫度精度:±0.3℃
  • 溫度解析度:0.01℃
  • 工作溫度:-40℃~85℃

AHT10技術手冊 中提到,在啟動傳輸後,隨後傳輸的 I²C 首位元組包括 7 位元的 I²C 設備位址 0x38 和一個 SDA 方向位(1 表示讀取,0 表示寫入)。在第 8 個 SCL 時鐘下降沿之後,透過拉低 SDA 引腳(ACK位元),指示感測器資料接收正常。在發出初始化命令之後(1110 0001 代表初始化,1010 1100 代表溫濕度測量),MCU 必須等待測量完成。 將上述讀取 AHT10 的方法,參考STM32 AHT10溫濕度傳感器數據機智雲傳輸溫濕度 這篇文章的程式,讀取溫濕度的方法如下:
u8 AHT10_Read_Humi_Temp(float *humidity, float *temperature)
{ 
	u32 humi = 0,temp = 0;	
	I2C_Start();
	I2C_Send_Byte(AHT_WRITE);		
	I2C_Write_Ack();
	I2C_Send_Byte(0XAC);	 //觸發測量	
	I2C_Write_Ack();
	I2C_Send_Byte(0X33);		
	I2C_Write_Ack();
	I2C_Send_Byte(0X00);		
	I2C_Write_Ack();
	I2C_Stop();
	
	delay_ms(80);
	
	I2C_Start();
	I2C_Send_Byte(AHT_READ);	
	I2C_Write_Ack();
	ACK = I2C_Read_Data();
	I2C_Is_Ack(1);
	
	if((ACK&0X08) == 0)
	{
		AHT10_Write_Init();
	}
	if((ACK&0X80) == 0)
	{ 	
	//bit7 1 0
		for(u8 i=0;i<5;i++){ // 0 1 2 3 4 5 ++i
			
			DATA[i] = I2C_Read_Data();			
			if(i == 4)
				I2C_Is_Ack(0);
			else
				I2C_Is_Ack(1);
		} 
		I2C_Stop();
		
		humi = (DATA[0]<<12)|(DATA[1]<<4)|(DATA[2]>>4);
		temp = ((DATA[2]&0X0F)<<16)|(DATA[3]<<8)|(DATA[4]);
		
		*humidity = (humi * 100.0/1024/1024+0.5);
		*temperature = (temp * 2000.0/1024/1024+0.5)/10.0-50;

		return 0;
	}
	
	I2C_Stop();
	return 1;
}


[材料]

  • STM32F103C8T6 主板 x 1
  • OLED SSD1306 顯示器 x 1
  • AHT10 溫濕度感測器 x 1
  • STLINK V2 模擬下載器 x 1
  • 麵包板 x 1
  • 連接線 x N 條

[接線與電路圖]

AHT10 分別接 3.3v 及 GND,接腳 SCL 和 SDA 分別接在 STM32F103x 的 PB6、PB7,與 SSD1306 連接的方式如下:
STM32F103SSD1306 OLEDAHT10
3.3vVDDVCC
GNDGNDGND
PB8SCK/SCL-
PB9SDA-
PB6-SCL
PB7-SDA

[程式]

主程式 main.c 如下:
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "bmp.h"
#include "AHT10.h"
#include "I2C.h"

//主函數	
int main(void)
{
   float temperature,humidity;
	uint16_t tmp;
	delay_init();	    	 	//延時函數初始化	  
	
	OLED_Init();		 		//初始化OLED  
	OLED_Clear(0);          //清除螢幕
	OLED_ShowString(2, 2, "Humid :",16);	
	OLED_ShowString(2, 4, "Temper:",16);		
    I2C_Initation();
	
    while(1)
    {
		AHT10_Read_Humi_Temp(&humidity,&temperature);
		OLED_ShowNum(60, 2, humidity,3, 16); 
		OLED_ShowString(83, 2, ".",16); 

		tmp = (uint16_t)(humidity*100) % 100;  //小數點後兩位
		if (tmp < 10){
			OLED_ShowNum(90, 2, tmp, 2, 16);	
			OLED_ShowString(89, 2,"0",16); 		//個位數時前面補0	
		} else
		{
			OLED_ShowNum(90, 2, tmp, 2, 16);			
		}
		
		OLED_ShowNum(60, 4, temperature,3, 16); 
		OLED_ShowString(83, 4, ".",16); 		
		tmp = (uint16_t)(temperature*100) % 100;  //小數點後兩位
		if (tmp < 10){
			OLED_ShowNum(90, 4, tmp, 2, 16);	
			OLED_ShowString(89, 4,"0",16); 		//個位數時前面補0	
		} else
		{
			OLED_ShowNum(90, 4, tmp, 2, 16);			
		}
		
		delay_ms(300);
    }
}
有關完整 ATH10 及 I2C 完整的程式,請參考 Github:14. AHT10 Detect Temperature & Humidity

[結果]

檢測溫濕度的過程如以下影片:

[參考資料]


Post a Comment

較新的 較舊