IIC 通信协议

在这里插入图片描述

【 1. 概述 】

  • IIC ( I2C, Inter-Integrated Circuit :集成电路之间 ),由PHILIPS公司开发用于连接微控制器及其外围设备。
  • 特点
        两线式 串行同步半双工 通信方式。
  • 功能
        它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。
        在CPU与被控IC(集成电路)之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。
  • 具有多主机的IIC总线结构
    在这里插入图片描述

【 2. 传输特性 】

0. 两总线的特性

开漏输出(外加上拉电阻)

  • SCL和SDA硬件结构上属于外加上拉电阻的开漏输出,即写“0”输出0,写“1”输出1(没有外加上拉电阻时写“1”输出高阻,可以通过软件配置上拉电阻)。
  • 为什么是要开漏输出呢?前面我们说过IIC总线上可以有多个从设备,那么多个GPIO口可能会连接在同一根线上,存在某个GPIO输出高电平, 另一个GPIO输出低电平的情况。如果使用推挽输出,你会发现这个GPIO的VCC和另一个GPIO的GND接在了一起, 也就是短路了(凉凉了)。如果换成开漏输出呢? VCC和GND多了个电阻, 这样电路就是安全的。所以总线一般会使用开漏输出。如下图展示了总线上为什么不能用推挽输出。

在这里插入图片描述

  • 为什么要加上拉电阻?这是因为IIC通信需要输出高电平的能力,不然只传输低电平和高阻态(高阻态是不确定的电平状态),谈什么传输信息!
  • 由于有的单片机内部能通过软件配置内置上拉电阻,故如果是加上拉电阻的推挽输出GPIO也可以进行IIC通信。

拉高SDA的必要性

  • 假设有这么一个情况:设备1想要把SDA线拉高,但是此时设备2已经把SDA线拉低了,那么由于两设备都连接着SDA线,线与:1&0=0,无论设备1的SDA线输出什么,设备2总会把SDA线拉低!因此,在设备1将SDA线拉高前,设备2应先把SDA线拉高。
  • 因此对于IIC:只有在拉高释放SDA总线后,才可进行主机/从机对SDA线控制的切换。

1. 空闲状态

  • I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态,此时由两条信号线各自的上拉电阻把电平拉高,各个器件的输出级场效应管均处在截止状态。

2. 起始信号、停止信号

  • 起始信号
    当SCL为高电平期间,SDA由高电平到低电平的跳变。
  • 停止信号
    当SCL为高电平期间,SDA由低电平到高电平的的跳变。
    在这里插入图片描述

3. 应答信号

  • 应答信号
        发送器每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器反馈一个应答信号。
  • 有效应答
        应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已成功地接收了该字节;
        对于反馈有效应答位ACK的要求:接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟SCL的高电平期间为稳定的低电平。
  • 无效应答
        应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
    在这里插入图片描述
  • 接收器是主控器的情况(读)
        在主控器收到最后一个字节后,发送一个NACK信号,以通知发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。

4. 数据的有效性

  • IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定。
  • 只有在时钟线上的信号为低电平期间,数据线上的电平状态才允许变化。
    在这里插入图片描述

5. 数据传输

  • 在IIC总线上传送的 每一位数据都对应一个时钟脉冲(或同步控制)。即:在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据,数据位的传输是边沿触发。

6. 工作过程

主机向从机写入数据

简要过程:
        主机发送起始位,这会通知总线上的所有设备传输开始了,接下来主机发送设备地址+读/写信号,与这一地址匹配的从设备将成为从机并与主机进行传输,而其它从设备将会退出此次通信。
        主机匹配到从机后,主机发送它所要读取或写入的从机的内部寄存器地址;然后发送数据;数据发送完毕后,发送停止位。
在这里插入图片描述
详细过程:

  • 主机发送起始位。
  • 主机发送从机的地址+读选择位。主机释放SDA总线,等待从机的应答。
    如果从机接收主机成功,则进行应答;若没有握手成功或者发送的数据错误时从机不产生应答,此时要求重发或者终止。
  • 主机发送想要写入从机内部寄存器的地址。主机释放SDA总线,等待从机的应答(应答过程如上一步)。
  • 主机发送数据。
  • 主机发送停止位。
    PS:从机是EEPROM时,EEPROM 收到停止信号后,进入到一个内部的写入周期,大概需要10ms,此间任何操作都不会被EEPROM响应;(因此以这种方式的两次写入之间要插入一个延时,否则会导致失败)。

主机向从机读取数据

简要过程:
主机先向从机写入其想要读取从机内部寄存器的地址,再从从机中读取数据。
在这里插入图片描述
详细过程:

  • 主机发送起始位。
  • 主机发送从机地址+写选择位。主机释放SDA总线,等待从机应答。
  • 主机发送内部寄存器地址。主机释放SDA总线,等待从机应答。
  • 主机重新发送起始位,即 restart。
  • 主机重新发送从机地址+读选择位。主机释放SDA总线,等待从机应答。
  • 主机读取数据。
  • 主机接收器在接收到最后一个字节后,主机释放SDA总线并发送NACK信号,以允许主机发出停止位结束传输。
  • 主机发送停止位。

【 3. IIC底层驱动程序 】

基于STM32F407ZET6

1. 初始化 IO

//初始化IIC
void IIC_Init(void)
{			
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟

  //GPIOB1,B2初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_1;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//开漏输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//IIC总线要加上拉,使其能输出高电平。
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
  IIC_SCL=1;//拉高SCL和SDA,此时为IIC的初始状态
  IIC_SDA=1;
}

2. 起始信号

SCL高电平期间,SDA由高电平向低电平的转换。

//产生IIC起始信号
void IIC_Start(void)
{
	SDA_OUT(); //SDA线设置 输出模式
	IIC_SDA=1; //拉高 SDA
	IIC_SCL=1; //拉高 SCL
	delay_us(4); //等待上升沿结束
 	IIC_SDA=0;  // 拉低 SDA 
	delay_us(4); //等待SDA线下降沿结束
	IIC_SCL=0; //释放SCL,等待下次指令的时钟信号来临。
}	

3. 终止信号

SCL高电平期间,SDA线由低电平到高电平的转换。

//产生IIC停止信号
void IIC_Stop(void)
{
	SDA_OUT();//SDA线设置 输出 模式
	IIC_SCL=0;//SCL低电平时才允许SDA变化。
	IIC_SDA=0;//拉低 SDA
 	delay_us(4); //等待下降沿结束
	IIC_SCL=1; // 拉高 SCL(来了一个时钟信号,表示新的指令开始)
	IIC_SDA=1; //拉高 SDA 
	delay_us(4); //等待上升沿结束							   	
}

4. 发送有效应答

    接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟SCL的高电平期间为稳定的低电平。

//产生ACK应答
void IIC_Ack(void)
{
	IIC_SCL=0; //SCL低电平时才允许SDA变化
	SDA_OUT();//SDA设置输出模式
	IIC_SDA=0;//拉低SDA
	delay_us(2);//等待SDA下降沿结束
	IIC_SCL=1; //拉高 SCL(来了一个时钟信号,表示新的指令开始) 
	delay_us(2); //等待SCL上升沿结束
	IIC_SCL=0; //释放IIC,等待下个指令的时钟信号来临
}

5. 发送无效应答

    接收器在第9个时钟脉冲之前的低电平期间将SDA线拉高,并且确保在该时钟SCL的高电平期间为稳定的低电平。

//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL=0;//SCL低电平时才允许SDA变化
	SDA_OUT();//SDA设置输出模式
	IIC_SDA=1;//拉高SDA
	delay_us(2);//等待SDA上升沿结束
	IIC_SCL=1; //拉高 SCL(来了一个时钟信号,表示新的指令开始) 
	delay_us(2); //等待SCL上升沿结束
	IIC_SCL=0; //释放SCL,等待下个指令的时钟信号
}				

6. 等待应答

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为 输入 模式  
	IIC_SDA=1;delay_us(1); // 完全拉高SDA,读取从机的SDA信号。	   
	IIC_SCL=1;delay_us(1); // 完全拉高SCL	 
	while(READ_SDA) //当读取到的 SDA=1,即无效应答时
	{
		ucErrTime++; 
		if(ucErrTime>250)
		{
			IIC_Stop(); //超过阈值,则停止
			return 1;
		}
	}
	IIC_SCL=0;//释放SCL,等待下个指令的时钟信号   
	return 0;  
} 

7. 发送字节

//发送一个字节			  
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
	SDA_OUT(); //SDA设置输出模式	    
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        IIC_SDA=(txd&0x80)>>7; //取txd二进制位的最高位
        txd<<=1; //txd二进制位左移,其余位向最高位平移补空位	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL=1; 	//拉高 SCL(来了一个时钟信号,表示新的指令开始) 
		delay_us(2); //等待SCL上升沿结束
		IIC_SCL=0;	// 释放IIC,等待下次指令的时钟信号
		delay_us(2);// 等待SCL下降沿结束
    }	 
} 

8. 读字节

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//SDA设置为 输入 模式
    for(i=0;i<8;i++ )
	{
        IIC_SCL=0; //拉低释放 SCL
        delay_us(2); //等待 SCL 下降沿结束
		IIC_SCL=1;  //拉高 SCL(来了一个时钟信号,表示新的指令开始) 
        receive<<=1; //receive 左移1位(IIC数据传输是从高位向低位传输)
        if(READ_SDA) receive++; //若读取的值为1,则receive+1   
		delay_us(1); //等待传输
    }					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive; //返回读取的值 receive
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MR_Promethus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值