本人自己想做一个项目,做到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