《嵌入式》基于I2C协议的AHT20温湿度传感器的数据采集

本文将主要介绍解释什么是“软件I2C”和“硬件I2C”?

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

目录

一、I2C总线通信协议

1.I2C协议定义

2.I2C物理层

3.协议层

4.硬件I2C与软件I2C

二、利用AHT20完成温湿度采集

1.代码编写

2.实物连线

3.实验结果

三、参考文章


一、I2C总线通信协议

1.I2C协议定义

 I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

2.I2C物理层

I2C是一个支持设备的总线。可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。对于I2C 总线,只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。

I2C 通讯设备之间的常用连接方式如下图:

I2C 特点如下:
►支持多个设备的总线;

►总线由SCL、SDA总线组成,一条双向串行数据线(SDA) ,一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步;

► 每个连接到总线的设备都有一个设备地址,用于主机与多个设备之间的选择性通信;

► 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平;

► 当多个主机同时使用总线时,使用仲裁方式决定哪个设备占用总线,防止数据传输冲突;

► 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式;

► 由于总线上消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容.而线路中电容会影响总线传输速度.当电容过大时,有可能造成传输错误.所以,其负载能力为400pF,因此可以估算出总线允许长度和所接器件数量;

3.协议层

协议层主要是定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等。

I2C 协议层主要包含如下部分:
► 起始信号
► 数据传输
► 应答信号
► 停止信号

I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。

开始信号SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

通讯的起始和停止信号

 

4.硬件I2C与软件I2C

硬件I2C:直接利用 STM32 芯片中的硬件 I2C 外设。

硬件I2C的使用
只要配置好对应的寄存器,外设就会产生标准串口协议的时序。在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。

软件I2C:直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。

软件I2C的使用
需要在控制产生 I2C 的起始信号时,控制作为 SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的 GPIO 引脚在此期间完成由高电平至低电平的切换,最后再控制SCL 线切换为低电平,这样就输出了一个标准的 I2C 起始信号。 

两者的差别
硬件 I2C 直接使用外设来控制引脚,可以减轻 CPU 的负担。不过使用硬件I2C 时必须使用某些固定的引脚作为 SCL 和 SDA,软件模拟 I2C 则可以使用任意 GPIO 引脚,相对比较灵活。对于硬件I2C用法比较复杂,软件I2C的流程更清楚一些。如果要详细了解I2C的协议,使用软件I2C可能更好的理解这个过程。在使用I2C过程,硬件I2C可能通信更加快,更加稳定。

二、利用AHT20完成温湿度采集

1.代码编写

我这里贴上部分重要代码便于学习研究 完整工程文件在后面

main.c

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


int main(void)
{	
	//延时初始化
	delay_init();
	//串口初始化
	uart_init(115200);
	//
	IIC_Init();
	
	while(1)
	{
		read_AHT20_once();
		delay_ms(1500);
  }
}
/*********************************************END OF FILE**********************/

bsp_i2c.h

#ifndef __BSP_I2C_H
#define __BSP_I2C_H

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

 
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
//CRL = 0000 1111 1111 1111 1111 1111 1111 1111
//8<<28 = 1000 1111 1111 1111 1111 1111 1111 1111
//CRL = 1000 1111 1111 1111 1111 1111 1111 1111 = 0x8fffffff 表示 SDA 输入
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//CRL = 0x3fffffff 表示 SDA 输出


#define IIC_SCL    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //SDA 数据读取 7 管脚


void IIC_Init(void);
void  read_AHT20_once(void);
void  reset_AHT20(void);
void  init_AHT20(void);	
void  startMeasure_AHT20(void);
void  read_AHT20(void);
uint8_t  Receive_ACK(void);
void  Send_ACK(void);
void  SendNot_Ack(void);
void I2C_WriteByte(uint8_t  input);
uint8_t I2C_ReadByte(void);	
void  set_AHT20sendOutData(void);
void  I2C_Start(void);
void  I2C_Stop(void);

#endif

 bsp_i2c.c

#include "bsp_i2c.h"
#include "delay.h"
#include "string.h"

uint8_t   ack_status=0;
uint8_t   readByte[6];

uint32_t  H1=0;  //Humility
uint32_t  T1=0;  //Temperature

uint8_t  AHT20_OutData[4];

/****************
 *初始化 I2C 函数
 ****************/
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	//启用高速 APB (APB2) 外围时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );	
	
	//GPIO 定义
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//初始化 SCL(Pin6)高电平
	IIC_SCL=1;
	//初始化 SDA(Pin7)高电平
	IIC_SDA=1;
}

/*********************
 *AHT20 数据操作总函数
 *********************/
void read_AHT20_once(void)
{
	printf("读取数据中");
	
	//延时 10 微妙
	delay_ms(10);
	
  //传输数据前进行启动传感器和软复位
	reset_AHT20();
	delay_ms(10);
  
	//查看使能位
	init_AHT20();
	delay_ms(10);
	
  //触发测量
	startMeasure_AHT20();
	delay_ms(80);
  
	//读数据
	read_AHT20();
	delay_ms(5);
}


void reset_AHT20(void)
{
	//数据传输开始信号
	I2C_Start();
	
	//发送数据
	I2C_WriteByte(0x70);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
	{
		printf(">");
	}
	else
		printf("×");
	
	//发送软复位命令(重启传感器系统)
	I2C_WriteByte(0xBA);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//停止 I2C 协议
	I2C_Stop();
}

//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xE1 —> 看状态字的校准使能位Bit[3]是否为 1
//0x08 0x00 —> 0xBE 命令的两个参数,详见 AHT20 参考手册
void init_AHT20(void)
{
	//传输开始
	I2C_Start();

	//写入 0x70 数据
	I2C_WriteByte(0x70);
	//接收 ACK 信号
	ack_status = Receive_ACK();
	//判断 ACK 信号
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//写入 0xE1 数据
	I2C_WriteByte(0xE1);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else
		printf("×");
	
	//写入 0x08 数据
	I2C_WriteByte(0x08);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else 
		printf("×");
	
	//写入 0x00 数据
	I2C_WriteByte(0x00);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	//停止 I2C 协议
	I2C_Stop();
}

//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xAC —> 触发测量
//0x33 0x00 —> 0xAC 命令的两个参数,详见 AHT20 参考手册
void startMeasure_AHT20(void)
{
	//启动 I2C 协议
	I2C_Start();
	
	I2C_WriteByte(0x70);
	ack_status = Receive_ACK();
	if(ack_status)
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0xAC);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0x33);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_WriteByte(0x00);
	ack_status = Receive_ACK();
	if(ack_status) 
		printf(">");
	else 
		printf("×");
	
	I2C_Stop();
}


void read_AHT20(void)
{
	uint8_t i;

	//初始化 readByte 数组
	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}

	I2C_Start();

	//通过发送 0x71 可以获取一个字节的状态字
	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();
	
	//接收 6 个 8 bit的数据
	readByte[0]= I2C_ReadByte();
	//发送 ACK 信号
	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();
	//发送 NACK 信号
	SendNot_Ack();

	I2C_Stop();

	//温湿度的二进制数据处理
	//0x68 = 0110 1000
  //0x08 = 0000 1000	
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		//H1 左移 8 位并与 readByte[2] 相或 
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		//H1 右移 4 位
		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("完成!\n");
	printf("----温度:%d%d.%d °C\n",T1/100,(T1/10)%10,T1%10);
	printf("----湿度:%d%d.%d %%",H1/100,(H1/10)%10,H1%10);
	printf("\n\n");
}

//接收 ACK 信号
uint8_t Receive_ACK(void)
{
	uint8_t result=0;
	uint8_t cnt=0;

	//置 SCL 低电平
	IIC_SCL = 0;
	//设置 SDA 为读取数据模式
	SDA_IN();
	delay_us(4);

	//置 SCL 高电平
	IIC_SCL = 1;
	delay_us(4);

	//等待从机发送 ACK 信号,等待时间为 100 个循环
	while(READ_SDA && (cnt<100))
	{
		cnt++;
	}

	IIC_SCL = 0;
	delay_us(4);

	//如果在等待时间内,则结果为 1
	if(cnt<100)
	{
		result=1;
	}
	
	return result;
}

//发送 ACK 信号
void Send_ACK(void)
{
	//设置 SDA 为写数据模式
	SDA_OUT();
	IIC_SCL = 0;
	delay_us(4);

	//置 SDA 为低电平
	IIC_SDA = 0;
	delay_us(4);

	IIC_SCL = 1;
	delay_us(4);
	IIC_SCL = 0;
	delay_us(4);

	SDA_IN();
}

//发送 NACK 信号
void SendNot_Ack(void)
{
	//设置 SDA 为写数据模式
	SDA_OUT();
	
	IIC_SCL = 0;
	delay_us(4);

	IIC_SDA = 1;
	delay_us(4);

	IIC_SCL = 1;
	delay_us(4);

	IIC_SCL = 0;
	delay_us(4);

	IIC_SDA = 0;
	delay_us(4);
}

//发送一个字节数据
void I2C_WriteByte(uint8_t  input)
{
	uint8_t  i;
	//设置 SDA 为写数据模式
	SDA_OUT();
	
	//循环左移发送 8 bit数据
	for(i=0; i<8; i++)
	{
		IIC_SCL = 0;
		delay_ms(5);

		if(input & 0x80)
		{
			IIC_SDA = 1;
		}
		else
		{
			IIC_SDA = 0;
		}

		IIC_SCL = 1;
		delay_ms(5);

		input = (input<<1);
	}

	IIC_SCL = 0;
	delay_us(4);

	SDA_IN();
	delay_us(4);
}	

//循环检测 SDA 的电平状态并存储起来
uint8_t I2C_ReadByte(void)
{
	uint8_t  resultByte=0;
	uint8_t  i=0, a=0;

	IIC_SCL = 0;
	SDA_IN();
	delay_ms(4);

	//循环检测
	for(i=0; i<8; i++)
	{
		IIC_SCL = 1;
		delay_ms(3);

		a=0;
		if(READ_SDA)
		{
			a=1;
		}
		else
		{
			a=0;
		}

		resultByte = (resultByte << 1) | a;

		IIC_SCL = 0;
		delay_ms(3);
	}

	SDA_IN();
	delay_ms(10);

	return   resultByte;
}

//设置 I2C 协议开始
void I2C_Start(void)
{
	SDA_OUT();
	
	IIC_SCL = 1;
	delay_ms(4);

	//SDA 从 1 跳变为 0 的这个过程
	//表示起始信号
	IIC_SDA = 1;
	delay_ms(4);
	IIC_SDA = 0;
	delay_ms(4);

	//SCL 变为 0
	//表示 SDA 数据无效,此时 SDA 可以进行电平切换
	IIC_SCL = 0;
	delay_ms(4);
}

//设置 I2C 协议停止
void I2C_Stop(void)
{
	SDA_OUT();
	
	//SCL 高电平,SDA 高电平
	//停止时序
	IIC_SDA = 0;
	delay_ms(4);
	IIC_SCL = 1;
	delay_ms(4);

	//SDA 切换到高电平
	IIC_SDA = 1;
	delay_ms(4);
}

 完整的工程文件

链接:https://pan.baidu.com/s/16FMDU_bJAJWz237Ia3iXJg 
提取码:1234 


2.实物连线

因为我的板子是正点原子的ministm32开发板,无需外接usb转串口,把温度传感器即可。

VCC→3V3,GND→GND,SCL→PB6,SDA→PB7 

3.实验结果

将代码编译后烧录进stm32板里面 打开串口助手,用手捂住温度传感器观察温度湿度变化:

 

三、参考文章

https://blog.csdn.net/qq_35701387/article/details/119315176?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163739778516780271552514%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163739778516780271552514&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-119315176.first_rank_v2_pc_rank_v29&utm_term=I2C+%E9%80%9A%E8%AE%AF%E5%8D%8F%E8%AE%AE%28Inter%EF%BC%8DIntegrated+Circuit%29%E6%98%AF%E7%94%B1+Phiilps+%E5%85%AC%E5%8F%B8%E5%BC%80%E5%8F%91%E7%9A%84%EF%BC%8C%E7%94%B1%E4%BA%8E%E5%AE%83%E5%BC%95%E8%84%9A%E5%B0%91%EF%BC%8C%E7%A1%AC%E4%BB%B6%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%EF%BC%8C%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7%E5%BC%BA%EF%BC%8C%E4%B8%8D%E9%9C%80%E8%A6%81+USART%E3%80%81CAN+%E7%AD%89%E9%80%9A%E8%AE%AF%E5%8D%8F%E8%AE%AE%E7%9A%84%E5%A4%96%E9%83%A8%E6%94%B6%E5%8F%91%E8%AE%BE%E5%A4%87%EF%BC%8C%E7%8E%B0%E5%9C%A8%E8%A2%AB%E5%B9%BF%E6%B3%9B%E5%9C%B0%E4%BD%BF%E7%94%A8%E5%9C%A8%E7%B3%BB%E7%BB%9F%E5%86%85%E5%A4%9A%E4%B8%AA%E9%9B%86%E6%88%90%E7%94%B5%E8%B7%AF%28IC%29%E9%97%B4%E7%9A%84%E9%80%9A%E8%AE%AF%E3%80%82&spm=1018.2226.3001.4187

 https://blog.csdn.net/ssj925319/article/details/111461000?spm=1001.2014.3001.5502icon-default.png?t=LA92https://blog.csdn.net/ssj925319/article/details/111461000?spm=1001.2014.3001.5502

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值