IIC介绍与代码实现,对照手册驱动AHT20温湿度传感器

  最近项目使用到温湿度模块AHT20,其为IIC协议,看了大佬们的文章后自己参照着写了一下代码,本文章主要是为了加深自己对IIC的理解所需写,可能存在很多不足,还望见谅。

  进入正题,这里先对IIC进行简单的介绍,本介绍来自互联网。IIC也称I2C,是一个多主从的串行总线,由飞利浦公司发明的通讯总线,属于半双工同步传输类总线,仅由两条线就能完成多机通讯,一条SCL时钟线,另外一条双向数据线SDA,IIC总线要求每个设备SCL/SDA线都是漏极开路模式,因此必须带上拉电阻才能正常工作。I2C协议占用引脚少,硬件实现简单,可扩展性强,I2C数据传输速率有标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps)。

  在多个设备中采用 IIC进行通讯时,主控制器按地址选择分配主从通讯,所以每个连接总线的从机都有一个特殊的地址,从机地址必须与 IIC装置的固有地址一致,并在制造时由厂商自行分配,用户可以按照说明书进行查询,注意总线必须有外接上拉电阻,以确保空闲状态的稳定而且 IIC总线时序是影响通讯工作能否顺利进行的重要因素,下图是IIC通信的总时序图如下图所示。

  从上图可以看到,IIC时序图主要包括几个方面,起始信号,停止信号以及应答/非应答信号,下面将一一对其讲解。

  本次示例代码为STM32F103系列。

  为了方便后续开发,决定写一个IIC驱动程序

  首先初始化IIC的IO口,为方便后续修改,这里选择宏定义方式。

#define SDA GPIO_Pin_5
#define SCL GPIO_Pin_3
#define IIC_Port GPIOB

#define SDA_Hight GPIO_SetBits(IIC_Port,SDA)//置高
#define SDA_Low GPIO_ResetBits(IIC_Port,SDA)

#define SCL_Hight GPIO_SetBits(IIC_Port,SCL)
#define SCL_Low GPIO_ResetBits(IIC_Port,SCL)

#define OUT 1
#define INPUT 0

  由于IIC通信时,IO口应当由输出或者输入模式,来发送和接受信息,所以,还需要写一个配置输入输出函数来进行切换,代码如下所示。

void I2C_SDA_Mode(u8 addr)//切换管脚模式
{
	GPIO_InitTypeDef GPIO_Initstructrue;
	if(addr )//输出模式(写)
    {
	GPIO_Initstructrue.GPIO_Pin=SDA ;
	GPIO_Initstructrue.GPIO_Speed =GPIO_Speed_50MHz ;
	GPIO_Initstructrue.GPIO_Mode=GPIO_Mode_Out_PP ;
	GPIO_Init (IIC_Port,&GPIO_Initstructrue);
    }
  else //输入模式(读)
	{
	GPIO_Initstructrue.GPIO_Pin=SDA ;
	GPIO_Initstructrue.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init (IIC_Port,&GPIO_Initstructrue);
	}

接下来便是对IIC时序进行代码模拟编写及理解。以下为IIC协议的几种重要信号,分别是起始信号,终止信号,应答及非应答信号。

1 IIC的起始信号

  从图中可以清楚的看到,主机发送IIC启始信号经历以下过程,SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平,因此,我们可以写,先配置模式为输出模式,将SCL线拉高,延迟5us后拉低SDA线,延迟5us后拉低SCL,至此,发送了IIC的开始信号,相应代码如下所示。

 

void I2C_start(void)
{
	I2C_SDA_Mode (OUT );//配置为输出模式
	SCL_Hight ;//拉高SCL
	delay_us(5);//延时5MS
	SDA_Low ;//拉低SDA
	delay_us(5);//延时
	SCL_Low ;//拉低SCL
	
}

2 IIC的终止信号

  同样的,主机发送结束信号的过程为,SCL保持高电平。SDA由低电平变为高电平。因此将其配为输出模式,将SDA拉低,延迟5us后再将SDA拉高,延迟5us后拉低SCL,完成结束信号,代码如下所示。

void I2C_Stop(void)
{
	I2C_SDA_Mode (OUT );//配置为输出模式
	SDA_Low ;//拉低SDA
	delay_us(5);//延时5us
	SCL_Hight ;//拉高SCL线
	delay_us(5);//延时5us
	SDA_Hight ;//拉高SDA线
}

3  应答与非应答信号

  什么是应答信号呢,这个过程就好比如你与朋友间的交流一样,你向朋友传达信息,例如,小明问小红,你今天吃饭没有?小红不管回答吃了与没吃,都对你发送了一个应答信号,表明她已经接收到你的消息了,否则她可能没有听到你的问题。无论何时,主机将一个字节发送到从机,总要等待从机发出回应信号,以确定从机是否已成功地收到了信息,这是应答信号。

应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答应答信号为低电平,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功, 如图下图所示。

 

  因此,配置为输入模式,将主机SCL拉高,等待应答,若有应答则返回0,无应答返回1,相应代码如下

u8 I2C_Write_Ack(void)
{
	I2C_SDA_Mode (INPUT );//配置为输入模式,接受从机的应答信号
	u8 tempack;
	SCL_Hight ;
	delay_us(4);
	while(GPIO_ReadInputDataBit (IIC_Port ,SDA ))//读取SDA管脚状态,若有应答,其IO口状态应为0
	{
		if(++tempack >250)//若超时,则退出
		{
			I2C_Stop ();//发送结束信号
			return 1;//无应答返回一
		}
			
	}
	SCL_Low ;//否则拉低SCL
	delay_us(4);//延时4us
	return 0;//有应答返回0
}

  由于交流是互相的,有时候主机也要发送应答或是非应答信号,

  主机产生应答信号:应答:1.先拉低SCL,再拉低SDA2.拉高SCL 3.拉低SCL 4发送停止信号

  非应答:1先拉低SCL,再拉高SDA 3.拉高SCL 4发送停止信号

  因此,程序这样写,通过传入参数来选择发送应答或非应答, 代码如下所示。

void I2C_send_Ack(char flag)//传入参数代表应答或是非应答
{
	I2C_SDA_Mode (OUT );//配置为输出模式
	SCL_Low;//拉低SCL
	delay_us(4);
	if(flag==0)//产生非应答信号
	{
		SDA_Low;
		delay_us(4);
		SCL_Hight;
        delay_us(4);
        SCL_Low;
	    delay_us(4);
	    I2C_SDA_Mode (INPUT );
	}
	else if(flag==1)产生应答信号
	{
		SDA_Hight;
		delay_us(4);
		SCL_Hight;
		delay_us(4);
		SCL_Low;
	    delay_us(4);
		SDA_Low;
		delay_us(4);
	}


}

4 主机向IIC设备写数据

  SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位),当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据[16]。因此,所写函数应传入一个数据形参,通过与运算,用传入的数&0X80,来判断数据线拉高拉低,先判断高位,例如传入1000 0000 时,左移0&0X80,则为1SDA拉高,下一次循环时,左移1位,补0在结尾,则&0X800,则SDA拉低,以此类推,具体代码下所示。

void I2C_write_Byte(u8 Date)//1101 1010 写入从机
{
	
	SCL_Low ;
	delay_us(4);
	for(u8 i=0;i<8;i++)
	{
		I2C_SDA_Mode (OUT);//必须放进内部
		if((Date <<i)&0X80) SDA_Hight ;//先写高位 例如传入1000 0000 写1 0100 0000 写0 当i=1时 变成1000 0000 写1
		else SDA_Low ;
		SCL_Hight;//写完一个数据后,拉高SCL 证明数据有效
		delay_us(4);
		SCL_Low ;//拉低继续写(只有在SCL为低电平时,SDA改变才有效)
		delay_us(4);
	
	}
}

 5 读取IIC从设备数据

  设置为输入模式,读取8次SDA电平信号,使用或运算,从高位写到低位,若检测到SDA为高,则写1,否则写0,每次循环后,都左移一位,循环8次后结束,代码如下所示。

u8 I2C_Read_Date(void )//从从机里面读
{
	u8 Date;
	
	for(u8 i=0;i<8;i++)
	{
		I2C_SDA_Mode (INPUT );//必须放进内部
		SCL_Hight ;
		
		Date<<=1;//0000 0001 1000 0000
		if(GPIO_ReadInputDataBit (IIC_Port ,SDA )==1)
		{
			Date|=0X01;
		}
		SCL_Low ;
	  delay_us(4);
	}
	return Date;
}
	

至此,IIC驱动程序已完成,后续我将演示使用它来驱动温湿度传感器AHT20。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值