本人自己想做一個項目,做到AHT10獲取溫濕度時,由於開發經驗不足,在網上不斷查找資料,但都沒有完整詳細的步驟講解,在此卡了好幾天。經歷幾天的摸索,最終一步步實現軟件模擬IIC通訊,讀取AHT10溫濕度數據。在此做個記錄分享,給跟我一樣的初學者們一點借鑒。
首先想要跟I2C設備通訊實現方式上有兩種,一種是軟件模擬I2C協議,就是本文所講的內容,另一種就是硬件I2C,通過STM32CubeMX配置I2C,再進行一些應用編程。我還沒有嘗試過。
基於HAL庫軟件實現I2C的話,要注意的一點是微秒延時。HAL庫只有毫秒延時,沒有微秒延時。具體一些內容可以百度看看。
手冊的內容我就不做分享了,百度下載手冊,代碼結合手冊內容和I2C協議內容理解。
我的MCU是STM32L151C8T6。
先貼微秒延時代碼。這段代碼沒有實測過到底准不准確,建議只在I2C通訊中使用,是沒什么問題的。
void Delay_us(uint16_t usec) { uint16_t i=4; usec >>= 1; while (usec--) { while(i--) { ; } } }
第一步就是要對我們的I2C接口進行初始化了。我選用的是PB6/PB7引腳。
1 static void AHT10_IIC_Init(void) 2 { 3 GPIO_InitTypeDef GPIO_InitStruct; 4 __HAL_RCC_GPIOB_CLK_ENABLE(); 5 6 GPIO_InitStruct.Pin = AHT10_SDA_GPIO_PIN; 7 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 8 GPIO_InitStruct.Pull = GPIO_NOPULL; 9 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 10 HAL_GPIO_Init(AHT10_SDA_GPIO_PORT, &GPIO_InitStruct); 11 12 GPIO_InitStruct.Pin = AHT10_SCL_GPIO_PIN; 13 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 14 GPIO_InitStruct.Pull = GPIO_NOPULL; 15 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 16 HAL_GPIO_Init(AHT10_SCL_GPIO_PORT, &GPIO_InitStruct); 17 18 AHT10_SDA_H; 19 AHT10_SCL_H; 20 }
然后我們定義一個SDA初始化的函數,設置入口參數,方便配置各種參數配置。
void AHT10_SDA_INIT(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin, uint32_t Mode, uint32_t Pull) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_Pin; GPIO_InitStruct.Mode = Mode; GPIO_InitStruct.Pull = Pull; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOx, &GPIO_InitStruct); }
設置幾個讀寫函數
1 void AHT10_SDA_State(GPIO_PinState PinState) 2 { 3 HAL_GPIO_WritePin(AHT10_SDA_GPIO_PORT,AHT10_SDA_GPIO_PIN,PinState); 4 } 5 6 void AHT10_SCL_State(GPIO_PinState PinState) 7 { 8 HAL_GPIO_WritePin(AHT10_SCL_GPIO_PORT,AHT10_SCL_GPIO_PIN,PinState); 9 } 10 11 uint8_t read_sda_data(void) 12 { 13 return HAL_GPIO_ReadPin(AHT10_SDA_GPIO_PORT,AHT10_SDA_GPIO_PIN); 14 }
I2C啟動函數。I2C啟動,SCL為高時,SDA從高變低。(結合通訊協議來理解。)
1 void AHT10_IIC_Start(void) 2 { 3 AHT10_SDA_INIT_OUT; 4 AHT10_SDA_H; 5 AHT10_SCL_H; 6 AHT10_Delay_us(4); 7 AHT10_SDA_L; 8 AHT10_Delay_us(4); 9 AHT10_SCL_L; 10 }
I2C停止函數,SCL為高時,SDA從低變高
1 void AHT10_IIC_Stop(void) 2 { 3 AHT10_SDA_INIT_OUT; 4 AHT10_SDA_L; 5 AHT10_SCL_L; 6 AHT10_Delay_us(4); 7 AHT10_SCL_H; 8 AHT10_SDA_H; 9 AHT10_Delay_us(4); 10 }
啟動和停止寫完了。我們來寫等待從機設備響應ACK部分。
SCL為高時,SDA為高則未響應,反之。
1 static uint8_t AHT10_IIC_Wait_Ack(void) 2 { 3 uint8_t ucErrTime=0; 4 AHT10_SDA_INIT_IN; 5 AHT10_SDA_H; 6 AHT10_Delay_us(1); 7 AHT10_SCL_H; 8 AHT10_Delay_us(1); 9 while(read_sda_data()) 10 { 11 ucErrTime++; 12 if(ucErrTime>250) 13 { 14 AHT10_IIC_Stop(); 15 return 1; 16 } 17 } 18 AHT10_SCL_L; 19 return 0; 20 }
主機向從機發送應答ACK部分和非應答NACK部分
1 static void AHT10_IIC_Ack(void) 2 { 3 AHT10_SCL_L; 4 AHT10_SDA_INIT_OUT; 5 AHT10_SDA_L; 6 AHT10_Delay_us(2); 7 AHT10_SCL_H; 8 AHT10_Delay_us(2); 9 AHT10_SCL_L; 10 }
1 static void AHT10_IIC_NAck(void) 2 { 3 AHT10_SCL_L; 4 AHT10_SDA_INIT_OUT; 5 AHT10_SDA_H; 6 AHT10_Delay_us(2); 7 AHT10_SCL_H; 8 AHT10_Delay_us(2); 9 AHT10_SCL_L; 10 }
主機向從機發送一個字節。
1 static void AHT10_IIC_Send_Byte(uint8_t txd) 2 { 3 //uint8_t t; 4 AHT10_SDA_INIT_OUT; 5 AHT10_SCL_L; 6 for(uint8_t t=0;t<8;t++) 7 { 8 if((txd&0x80)>>7)//高位先傳,&1000 0000后將第八位右移至第一位 9 AHT10_SDA_H; 10 else 11 AHT10_SDA_L; 12 txd<<=1;//每傳完一位后,低位向高位進1 13 AHT10_Delay_us(2); 14 AHT10_SCL_H; 15 AHT10_Delay_us(2); 16 AHT10_SCL_L; 17 AHT10_Delay_us(2); 18 } 19 }
主機讀取一個字節。
1 static uint8_t AHT10_IIC_Read_Byte(unsigned char ack) 2 { 3 unsigned char i,receive=0; 4 AHT10_SDA_INIT_IN; 5 for(i=0;i<8;i++) 6 { 7 AHT10_SCL_L; 8 AHT10_Delay_us(2); 9 AHT10_SCL_H; 10 receive<<=1; 11 if(read_sda_data()) 12 receive++; 13 AHT10_Delay_us(1); 14 } 15 if (!ack) 16 AHT10_IIC_NAck();//發送nack 17 else 18 AHT10_IIC_Ack(); //發送ack 19 return receive; 20 }
以上就是實現I2C通訊的幾個基本流程,將其模塊化,方便調用。
檢查AHT10模塊是否響應。
1 uint8_t AHT10Check(void) 2 { 3 uint8_t ack=0; 4 AHT10_IIC_Start(); 5 AHT10_IIC_Send_Byte(AHT10_ADDRESS); 6 ack = AHT10_IIC_Wait_Ack(); 7 AHT10_IIC_Stop(); 8 return ack;//返回1則未檢測到 9 }
AHT10的軟件復位
1 void AHT10Reset(void) 2 { 3 AHT10_IIC_Start(); 4 AHT10_IIC_Send_Byte(AHT10_WRITE); 5 AHT10_IIC_Wait_Ack(); 6 AHT10_IIC_Send_Byte(0xba); 7 AHT10_IIC_Wait_Ack(); 8 AHT10_IIC_Stop(); 9 }
接下來是AHT10的初始化。
1 void AHT10Init() 2 { 3 AHT10_IIC_Init(); 4 5 AHT10_IIC_Start(); 6 AHT10_IIC_Send_Byte(AHT10_ADDRESS); 7 AHT10_IIC_Send_Byte(0xe1); //AHT10啟動的初始化指令 8 AHT10_IIC_Send_Byte(0x08); 9 AHT10_IIC_Send_Byte(0x00); 10 AHT10_IIC_Stop(); 11 AHT10_Delay_ms(40);//延時40MS讓傳感器穩定 12 }
AHT10讀取溫濕度數據
1 uint8_t AHT10ReadData(uint16_t *temperature,uint16_t *humidity) 2 { 3 uint8_t ack; 4 uint32_t SRH=0,ST=0; 5 uint8_t databuff[6]; 6 AHT10_IIC_Start(); 7 AHT10_IIC_Send_Byte(AHT10_WRITE); 8 AHT10_IIC_Wait_Ack(); 9 AHT10_IIC_Send_Byte(0xac); 10 AHT10_IIC_Wait_Ack(); 11 AHT10_IIC_Send_Byte(0x33); 12 AHT10_IIC_Wait_Ack(); 13 AHT10_IIC_Send_Byte(0x00); 14 AHT10_IIC_Wait_Ack(); 15 AHT10_IIC_Stop(); 16 AHT10_Delay_ms(80);//延時等待數據讀取。 17 AHT10_IIC_Start(); 18 AHT10_IIC_Send_Byte(AHT10_READ); 19 AHT10_IIC_Wait_Ack(); 20 ack=AHT10_IIC_Read_Byte(1); 21 if((ack&0x40)==0) 22 { 23 databuff[0]=AHT10_IIC_Read_Byte(1); 24 databuff[1]=AHT10_IIC_Read_Byte(1); 25 databuff[2]=AHT10_IIC_Read_Byte(1); 26 databuff[3]=AHT10_IIC_Read_Byte(1); 27 databuff[4]=AHT10_IIC_Read_Byte(0); 28 AHT10_IIC_Stop(); 29 SRH=(databuff[0]<<12)+(databuff[1]<<4)+(databuff[2]>>4); 30 ST=((databuff[2]&0X0f)<<16)+(databuff[3]<<8)+(databuff[4]); 31 32 *humidity = SRH*1000/1024/1024; 33 *temperature = ST *200*10/1024/1024-500; 34 return 1; 35 } 36 AHT10_IIC_Stop(); 37 return 0; 38 }
以上就是全部流程,將其放在一個新的.c文件中就行了。代碼網上可以找到類似的,我就是在基礎上進行了結合而已。
最后我們定義一個讀取函數,在main里直接調用就行,不過要先定義緩存變量,把地址傳進來存儲溫濕度數據。
1 void aht10_read(uint16_t *AHT10_tem,uint16_t *AHT10_hum) 2 { 3 AHT10Init(); 4 AHT10ReadData(AHT10_tem,AHT10_hum); 5 }
.h文件的代碼我也貼在下面。全部流程的代碼都在這里面了,直接復制就可以用,注意GPIO口是否一致。
1 #ifndef _AHT10_H_ 2 #define _AHT10_H_ 3 4 #include "stm32l1xx_hal.h" 5 6 #define AHT10_ADDRESS 0x70 7 #define AHT10_WRITE 0x70 8 #define AHT10_READ 0x71 9 10 11 #define AHT10_Delay_us(time) Delay_us(time) 12 #define AHT10_Delay_ms(time) HAL_Delay(time) 13 #define AHT10_SDA_GPIO_PIN GPIO_PIN_7 14 #define AHT10_SCL_GPIO_PIN GPIO_PIN_6 15 #define AHT10_SDA_GPIO_PORT GPIOB 16 #define AHT10_SCL_GPIO_PORT GPIOB 17 18 #define AHT10_SDA_INIT_OUT AHT10_SDA_INIT(AHT10_SDA_GPIO_PORT,AHT10_SDA_GPIO_PIN,GPIO_MODE_OUTPUT_PP,GPIO_NOPULL) 19 #define AHT10_SDA_INIT_IN AHT10_SDA_INIT(AHT10_SDA_GPIO_PORT,AHT10_SDA_GPIO_PIN,GPIO_MODE_INPUT,GPIO_PULLUP) 20 #define AHT10_SDA_H AHT10_SDA_State(GPIO_PIN_SET) 21 #define AHT10_SDA_L AHT10_SDA_State(GPIO_PIN_RESET) 22 23 #define AHT10_SCL_H AHT10_SCL_State(GPIO_PIN_SET) 24 #define AHT10_SCL_L AHT10_SCL_State(GPIO_PIN_RESET) 25 26 27 void AHT10_SDA_INIT(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin, uint32_t Mode, uint32_t Pull); 28 void AHT10_SDA_State(GPIO_PinState PinState); 29 void AHT10_SCL_State(GPIO_PinState PinState); 30 31 32 void AHT10Init(void); 33 uint8_t AHT10Check(void); 34 void AHT10Reset(void); 35 uint8_t AHT10ReadData(uint16_t *temperature,uint16_t *humidity); 36 void Delay_us(uint16_t usec); 37 void aht10_read(uint16_t *AHT10_tem,uint16_t *AHT10_hum); 38 #endif
結束啦~,沒有對代碼進行解釋,是因為結合手冊看就行了~,還是比較清楚的。
1 static void AHT10_IIC_Init(void) 2 { 3 GPIO_InitTypeDef GPIO_InitStruct; 4 __HAL_RCC_GPIOB_CLK_ENABLE();//ʹÄÜGPIOʱÖÓ 5 6 GPIO_InitStruct.Pin = AHT10_SDA_GPIO_PIN; 7 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 8 GPIO_InitStruct.Pull = GPIO_NOPULL; 9 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 10 HAL_GPIO_Init(AHT10_SDA_GPIO_PORT, &GPIO_InitStruct); 11 12 GPIO_InitStruct.Pin = AHT10_SCL_GPIO_PIN; 13 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; 14 GPIO_InitStruct.Pull = GPIO_NOPULL; 15 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 16 HAL_GPIO_Init(AHT10_SCL_GPIO_PORT, &GPIO_InitStruct); 17 18 AHT10_SDA_H; 19