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