基于I2C硬件协议的AHT20温湿度传感器的数据采集

基于I2C硬件协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出

一、I2C通信协议

1. I2C协议简介

IC( Inter-- Integrated Circuit)总线是一种由 PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。
在CPU与被控I2C之间、I2C与I2C之间进行双向传送,高速IC总线一般可达400kbps以上。 I2C总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结東传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接
收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为 受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

2. I2C总线特点

它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。
一条 I2C 总线只使用两条总线线路,分别是一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲即都输出高阻态时,由上拉电阻把总线拉成高电平。
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

3. I2C工作原理

I2C通信过程
写数据
若配置的方向传输位为“写数据”方向,即第一幅图的情况,主机广播完地址并且接收到应答信号后,就正式向从机传输一个字节的数据,主机每发送完一个字节数据,都要等待从机的应答信号(ACK)。当数据传输结束时,主机向从机发送一个停止传输信号§,表示不再传输数据。
读数据
若配置的方向传输位为“读数据”方向,即第二幅图的情况,主机广播完地址并且接收到应答信号后,就开始向主机返回一个字节的数据,从机每发送完一个数据,都会等待主机的应答信号(ACK)。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
读和写数据
除了基本的数据读写,I2C 通信更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过SLAVE_ADDRESS寻找到从机设备后,发送一段表示从机设备内部的寄存器或存储器地址(注意区分它与SLAVE_ADDRESS的区别);在第二次的传输中,对该地址的内容进行读或写。
I2C通信时序
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到一个字节的数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,则判断为受控单元出现故障。

二、硬件I2C与软件I2C

1.软件I2C

将芯片的两个GPIO引脚分别用作SCL及SDA,按照I2C的时序要求,直接控制引脚的输出信号(若是接收数据时则读取 SDA 电平),就可以实现I2C通讯。由于是直接控制GPIO引脚的高低电平产生通讯时序,需要由CPU控制每个时刻的引脚状态,所以称为“软件模拟协议”方式即软件I2C方式。

2.硬件I2C

硬件I2C对应芯片上的I2C外设,具有相应的I2C驱动电路,其所使用的I2C管脚也是专用的,因而效率要远高于软件模拟的I2C,但是程序较为繁琐。硬件I2C是直接调用内部寄存器进行配置。
对于硬件I2C来说,它需要I2C片上外设专门负责实现I2C通讯协议,只要配置好该外设,它就会自动根据协议要求产生I2C的通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了CPU的工作负担,并且使软件开发更简单。

3.软硬I2C的比较

通信速度
相对来说,即使两者的通信速率设置成相等时,硬件I2C的通信速度要比软件I2C的速度要快。因为硬件I2C是通过片上外设实现的通信,CPU只需要去读写寄存器数据就可以进行I2C通信,程序较为简单并且占用的资源少。而软件I2C则需要程序模拟I2C时序,程序较为复杂且占用资源较多。
可移植性
对于硬件I2C来说,由于芯片I2C外设的IO口已经确定,无法随意更改其他IO口,因而可移植性较差;但是由于软件I2C是通过IO口模拟I2C通信时序实现的通信,因而可移植性比较好,在任何单片机上都可以使用,只需要修改一下通信时间以及配置好IO口就可以实现I2C通信。
稳定性
一般来说,软件模拟I2C稳定性要比硬件I2C更加稳定,硬件I2C不稳定,容易卡死,想要写得稳定程序就非常复杂;但是软件I2C可能会因为中断的影响造成数据读取不准确。

三、实现I2CAHT20温湿度传感器的数据采集

1、连接硬件

AHT20的SCL,GND,SDA,VCC分别对应接stm32f103指南者i2c模块的B6,GND,B7,5V。(GND和5V任意模块都可以)

2、代码实现

打开野火自带例程
在这里插入图片描述
测温湿度函数计算代码:

void read_AHT20(void)
{
   
   
	uint8_t   i;

	for(i=0; i<6; i++)
	{
   
   
		readByte[i]=0;
	}

	//-------------
	I2C_Start();

	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();
	readByte[0]= I2C_ReadByte();
	Send_ACK();

	readByte[1]= I2C_ReadByte();
	Send_ACK();

	readByte[2]= I2C_ReadByte();
	Send_ACK();

	readByte[3]= I2C_ReadByte();
	Send_ACK();

	readByte[4]= I2C_ReadByte();
	Send_ACK();

	readByte[5]= I2C_ReadByte();
	SendNot_Ack();
	//Send_ACK();

	I2C_Stop();

	//--------------
	if( (readByte[0] & 0x68) == 0x08 )
	{
   
   
		H1 = readByte[1];
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		H1 = H1>>4;

		H1 = (H1*1000)/1024/1024;

		T1 = readByte[3];
		T1 = T1 & 0x0000000F;
		T1 = (T1<<8) | readByte[4];
		T1 = (T1<<8) | readByte[5];

		T1 = (T1*2000)/1024/1024 - 500;

		AHT20_OutData[0] = (H1>>8) & 0x000000FF;
		AHT20_OutData[1] = H1 & 0x000000FF;

		AHT20_OutData[2] = (T1>>8) & 0x000000FF;
		AHT20_OutData[3] = T1 & 0x000000FF;
	}
	else
	{
   
   
		AHT20_OutData[0] = 0xFF;
		AHT20_OutData[1] = 0xFF;

		AHT20_OutData[2] = 0xFF;
		AHT20_OutData[3] = 0xFF;
		printf("失败了");

	}
	printf("\r\n");
	printf("当前温度为: %d%d.%d",T1/100,(T1/10)%10,T1%10);
	printf("\r\n");
	printf("当前湿度为: %d%d.%d",H1/100,(H1/10)%10,H1%10);
	printf("\r\n");
}

将它原来带有的main.c函数删掉并移除,然后重新添加main.c
main.c文件源码:

#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"


int main(void)
{
   
   	
	delay_init();    
	uart_init(115200);	 
	IIC_Init();
		while(1)
	{
   
   
		printf("野火STM32指南者基于I2C协议的AHT20温湿度传感器开始测量:");
		read_AHT20_once();
		delay_ms(1500);
  }
}

将以下代码文件和main.c放到同一个路径下
在这里插入图片描述
usart.c

#include "sys.h"
#include "usart.h"


//STM32F103ºËÐİåÀý³Ì
//¿âº¯Êý°æ±¾Àý³Ì
/********** mcudev.taobao.com ³öÆ·  ********/


// 	 
//Èç¹ûʹÓÃucos,Ôò°üÀ¨ÏÂÃæµÄÍ·Îļþ¼´¿É.
#if SYSTEM_SUPPORT_UCOS
#include "includes.h"					//ucos ʹÓÃ	  
#endif
//	 
//STM32¿ª·¢°å
//´®¿Ú1³õʼ»¯		   

// 	  
//
//¼ÓÈëÒÔÏ´úÂë,Ö§³Öprintfº¯Êý,¶ø²»ÐèҪѡÔñuse MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//±ê×¼¿âÐèÒªµÄÖ§³Öº¯Êý                 
struct __FILE 
{
   
    
	int handle; 

}; 

FILE __stdout;       
//¶¨Òå_sys_exit()ÒÔ±ÜÃâʹÓðëÖ÷»úģʽ    
void _sys_exit(int x) 
{
   
    
	x = x; 
} 
//ÖØ¶¨Òåfputcº¯Êý 
int fputc(int ch, FILE *f)
{
   
         
	while((USART1->SR&0X40)==0);//Ñ­»··¢ËÍ,Ö±µ½·¢ËÍÍê±Ï   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

/*ʹÓÃmicroLibµÄ·½·¨*/
 /* 
int fputc(int ch, FILE *f)
{
	USART_SendData(USART1, (uint8_t) ch);

	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
   
    return ch;
}
int GetKey (void)  { 

    while (!(USART1->SR & USART_FLAG_RXNE));

    return ((int)(USART1->DR & 0x1FF));
}
*/
 
#if EN_USART1_RX   //Èç¹ûʹÄÜÁ˽ÓÊÕ
//´®¿Ú1ÖжϷþÎñ³ÌÐò
//×¢Òâ,¶ÁÈ¡USARTx->SRÄܱÜÃâĪÃûÆäÃîµÄ´íÎó   	
u8 USART_RX_BUF[USART_REC_LEN];     //½ÓÊÕ»º³å,×î´óUSART_REC_LEN¸ö×Ö½Ú.
//½ÓÊÕ״̬
//bit15£¬	½ÓÊÕÍê³É±êÖ¾
//bit14£¬	½ÓÊÕµ½0x0d
//bit13~0£¬	½ÓÊÕµ½µÄÓÐЧ×Ö½ÚÊýÄ¿
u16 USART_RX_STA=0;       //½ÓÊÕ״̬±ê¼Ç	  
  
void uart_init(u32 bound){
   
   
    //GPIO¶Ë¿ÚÉèÖÃ
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//ʹÄÜUSART1£¬GPIOAʱÖÓ
     //USART1_TX   PA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//¸´ÓÃÍÆÍìÊä³ö
    GPIO_Init(GPIOA, &GPIO_InitStructure);
   
    //USART1_RX	  PA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//¸¡¿ÕÊäÈë
    GPIO_Init(GPIOA, &GPIO_InitStructure);  

   //Usart1 NVIC ÅäÖÃ

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//ÇÀÕ¼ÓÅÏȼ¶3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//×ÓÓÅÏȼ¶3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQͨµÀʹÄÜ
	NVIC_Init(&NVIC_InitStructure);	//¸ù¾ÝÖ¸¶¨µÄ²ÎÊý³õʼ»¯VIC¼Ä´æÆ÷
  
   //USART ³õʼ»¯ÉèÖÃ

	USART_InitStructure.USART_BaudRate = bound;//Ò»°ãÉèÖÃΪ9600;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//×Ö³¤Îª8λÊý¾Ý¸ñʽ
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//Ò»¸öֹͣλ
	USART_InitStructure.USART_Parity = USART_Parity_No;//ÎÞÆæÅ¼Ð£Ñéλ
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//ÎÞÓ²¼þÊý¾ÝÁ÷¿ØÖÆ
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//ÊÕ·¢Ä£Ê½

    USART_Init(USART1, &USART_InitStructure); //³õʼ»¯´®¿Ú
    USART_ITConfig(
<think>好的,我需要帮助用户解决STM32F103C8T6通过模拟I2CAHT20通信的问题。用户想要示例代码,所以我得先确保代码正确,并符合他们的需求。 首先,用户提到使用模拟I2C协议,所以不能依赖硬件I2C,需要软件实现GPIO控制。我需要定义SDA和SCL的引脚,并初始化它们为输出模式。通常,这两个引脚需要上拉电阻,但用户可能已经接好,所以在代码中可能需要配置为开漏模式,但STM32的GPIO模式设置可能需要特别注意。不过,模拟I2C通常会将引脚设置为推挽输出,并在适当的时候切换为输入读取数据。因此,初始化部分应该设置GPIO为输出,并初始化为高电平。 接下来是I2C的起始和停止信号。起始信号是SCL高时,SDA从高到低;停止信号是SCL高时,SDA从低到高。这部分需要正确实现时序,可能需要微秒级的延时,这里用HAL_Delay不太合适,应该用更精确的延时函数,比如HAL_Delay(1)可能太长,但作为示例代码可能简化处理,用户实际使用时可能需要调整。 发送字节的函数需要逐位发送,从高位开始。每个时钟周期,设置SDA的电平,然后拉高SCL,保持一段时间,再拉低SCL。接收字节时,需要释放SDA(设置为输入模式),读取每一位,并在SCL高时采样。 AHT20的初始化步骤需要发送初始化命令,比如0xE1后跟两个参数0x08和0x00。然后需要等待传感器校准完成,通过读取状态字,判断最高位是否为0。如果校准未完成,可能需要延时等待。 读取数据时,发送触发测量命令0xAC,然后等待数据就绪,同样通过状态字判断。之后读取6个字节的数据,组合成温湿度值。注意数据需要转换,比如湿度值SRH是20位的,温度是20位的,需要按位操作提取,并进行公式计算。 示例代码中需要包含这些步骤,并添加必要的注释,让用户理解每个部分的作用。同时,需要提醒用户检查引脚配置、延时函数是否合适,以及可能的硬件连接问题。 可能的问题包括:I2C地址是否正确(AHT20地址是0x38),GPIO是否正确配置,延时是否足够,是否处理了传感器的响应信号(ACK/NACK)。此外,用户可能需要根据实际情况调整计算公式中的参数,确保温湿度值的准确性。 最后,生成相关问题,帮助用户深入理解,比如如何提高采集精度,处理通信失败的情况,或者优化代码结构。这些问题应该与用户的应用场景相关,帮助他们进一步开发和调试。</think>### 解答步骤 #### 步骤1:配置GPIO模拟I2C协议 STM32F103C8T6需通过GPIO模拟I2C时序,定义`SDA`和`SCL`引脚: ```c // 定义I2C引脚(示例使用GPIOB_Pin6和GPIOB_Pin7) #define I2C_SCL_PIN GPIO_PIN_6 #define I2C_SDA_PIN GPIO_PIN_7 #define I2C_PORT GPIOB // 初始化GPIO void I2C_Init() { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出模式 GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Pin = I2C_SCL_PIN | I2C_SDA_PIN; HAL_GPIO_Init(I2C_PORT, &GPIO_InitStruct); HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(I2C_PORT, I2C_SDA_PIN, GPIO_PIN_SET); } ``` #### 步骤2:实现I2C基础函数 包括起始信号、停止信号、发送字节、接收应答等: ```c // I2C起始信号 void I2C_Start() { HAL_GPIO_WritePin(I2C_PORT, I2C_SDA_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(I2C_PORT, I2C_SDA_PIN, GPIO_PIN_RESET); Delay_us(5); HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); } // 发送一个字节 void I2C_WriteByte(uint8_t data) { for (int i = 0; i < 8; i++) { HAL_GPIO_WritePin(I2C_PORT, I2C_SDA_PIN, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); data <<= 1; HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); Delay_us(5); } // 等待ACK HAL_GPIO_WritePin(I2C_PORT, I2C_SDA_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(I2C_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); } ``` #### 步骤3:AHT20初始化与数据读取 AHT20需发送校准命令并等待就绪: ```c #define AHT20_ADDRESS 0x38 // I2C地址 void AHT20_Init() { I2C_Start(); I2C_WriteByte(AHT20_ADDRESS << 1); // 写模式 I2C_WriteByte(0xE1); // 初始化命令 I2C_WriteByte(0x08); // 参数1 I2C_WriteByte(0x00); // 参数2 I2C_Stop(); HAL_Delay(40); // 等待校准完成 } // 读取温湿度数据 void AHT20_ReadData(float *temp, float *humi) { uint8_t data[6] = {0}; I2C_Start(); I2C_WriteByte(AHT20_ADDRESS << 1); I2C_WriteByte(0xAC); // 触发测量命令 I2C_WriteByte(0x33); I2C_WriteByte(0x00); I2C_Stop(); HAL_Delay(80); // 等待测量完成 // 读取6字节数据 I2C_Start(); I2C_WriteByte((AHT20_ADDRESS << 1) | 0x01); // 读模式 for (int i = 0; i < 5; i++) { data[i] = I2C_ReadByte(); I2C_SendAck(0); // 发送ACK } I2C_SendAck(1); // 发送NACK I2C_Stop(); // 数据解析(公式见AHT20手册) uint32_t raw_humi = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; uint32_t raw_temp = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; *humi = (raw_humi * 100.0) / (1 << 20); *temp = (raw_temp * 200.0) / (1 << 20) - 50; } ``` #### 步骤4:主函数调用 ```c int main() { HAL_Init(); SystemClock_Config(); I2C_Init(); AHT20_Init(); float temp, humi; while (1) { AHT20_ReadData(&temp, &humi); HAL_Delay(2000); // 每2秒采集一次 } } ``` #### 注意事项 1. **延时函数**需根据实际时钟频率调整(示例中`Delay_us`需自行实现) 2. **GPIO模式**需根据硬件设计选择(开漏输出需外接上拉电阻) 3. **数据校验**需检查状态字(参考AHT20手册)[^1][^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值