基于STM32开发板I²C总线通信协议浅析

基于STM32开发板I²C总线通信协议浅析

一、前言

I²C(Inter-Integrated Circuit),中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,是由飞利浦公司在1980年代初设计的,方便了主板、嵌入式系统或手机与周边设备组件之间的通讯。由于其简单性,它被广泛用于微控制器与传感器阵列,显示器,IoT设备,EEPROM等之间的通信。

二、I²C接口

2.1 主要特点

I²C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I 2 C总线。允许连接到标准(高达100kHz)或 快速(高达400kHz)的I 2 C总线
(1)只需要两条总线;
(2)没有严格的波特率要求,例如使用RS232,主设备生成总线时钟;
(3)所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;
(4)I²C是真正的多主设备总线,可提供仲裁和冲突检测;
(5)传输速度;
标准模式:Standard Mode = 100 Kbps
快速模式:Fast Mode = 400 Kbps
(6)最大主设备数:无限制;
(7)最大从机数:理论上是127;

2.2 硬件层

I²C协议仅需要一个SDA和SCL引脚。SDA是串行数据线的缩写,而SCL是串行时钟线的缩写。这两条数据线需要接上拉电阻。
设备间的连接如下所示:
在这里插入图片描述

使用I²C,可以将多个从机(Slave)连接到单个主设备(Master),并且还可以有多个主设备(Master)控制一个或多个从机(Slave)。
I²C总线(SDA,SCL)内部都使用漏极开路驱动器(开漏驱动),因此SDA和SCL 可以被拉低为低电平,但是不能被驱动为高电平,所以每条线上都要使用一个上拉电阻,默认情况下将其保持在高电平;以此实现总线上所有节点SDA、SCL信号的 线与 逻辑关系。使得总线上不会出现数据冲突,实现了总线的仲裁控制。总线的控制权会交给最后一个输出低电平的设备。
在这里插入图片描述

开漏输出就是不输出电压,控制输出低电平时引脚接地,控制输出高电平时引脚既不输出高电平,也不输出低电平,为高阻态。如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机电压低的时候。

2.3 数据传输协议

2.3.1 SDA 线和 SCL线

I²C设备有两条接口线:串行数据 SDA 线和串行时钟 SCL 线。连接到I²C总线上的设备通过这两根线互相传递信息,SDA 和 SCL 都是双向线,可以互相之间进行信息的交互,通过同步半双工的通信方式,同一时刻只能有一个方向进行数据的操作,不能同时进行。
I²C设备上的两根通信线一般示意如下:
在这里插入图片描述

2.3.2 I²C总线协议

主设备和从设备进行数据传输时遵循以下协议格式。数据通过一条SDA数据线在主设备和从设备之间传输0和1的串行数据。串行数据序列的结构可以分为,开始条件、地址位、读写位、应答位、数据位、停止条件,具体如下所示
在这里插入图片描述

数据传输一位对应一个时钟脉冲,注意I²C跟SPI不同,数据采样并不是在时钟的上升沿或下降沿采样,而是需要在SCL一个高电平期间,SDA保持稳定为高或低,才有效,如下图:(不然就跟“起始条件”或“结束条件”混了)。
在这里插入图片描述

2.3.2.1 起始条件和结束条件

起始条件:在 SCL 是高电平时 SDA 线从高电平向低电平切换,这个情况表示起始条件
停止条件:当 SCL 是高电平时 SDA 线由低电平向高电平切换表示停止条件
在这里插入图片描述

I²C协议规定,总线上数据的传输必须以一个起始条件作为开始,以一个结束条件作为传输的停止。在数据传输过程中,总线处于忙状态,且总线由本次数据传输的主从设备独占,其他I²C器件无法访问总线,直到停止条件产生,本次数据传输的主从设备释放总线,总线再次处于空闲状态后,其它I²C器件才可访问总线。在主设备发送开始条件信号之后,所有从机即使处于睡眠模式也将变为活动状态,并等待接收地址位。
如果在一次通信过程中,有两台以上的设备同时发出了起始信号,都希望获得总线控制权的话,那么第一个发出起始信号的设备将获得总线控制权,作为主设备开始传输数据。起始信号由主机负责产生。

2.3.2.2 数据传输

在起始条件产生后,就是进行数据传输,I²C协议规定,传输数据时,发送到SDA线上的每个字节必须为8位,且先传输数据的高位(MSB),而且每传输一个字节后必须跟一个响应位。但是每次传输字节数量是不受限制的。

2.3.2.3 应答ACK

主机每次发送完数据之后会等待从设备的应答信号ACK;在第9个时钟信号,如果从设备发送应答信号ACK,则SDA会被拉低;若没有应答信号NACK,则SDA会输出为高电平,这过程会引起主设备发生重启或者停止;
I²C协议规定,数据传输必须带响应。比如主机发了一个字节的数据,那么这时从机应该发送一个响应告诉主机它收到了(注意,此时的响应虽然是从机发出的,但是时钟还是主机提供的,也就是不管什么时候,不管主机是接收还是发送,只要是主机,那么时钟就是由这个主机提供)。
在响应的时钟期间,发送器释放SDA线(释放后,SDA就被上拉电阻拉高了),而同时,在这个响应的时钟脉冲期间,接收器必须将SDA线拉低,且保持整个时钟脉冲的高电平期间,SDA为稳定的低电平,就相当于回应了发送器ACK了,这就是响应,主机发送起始条件,然后发送8bit数据,然后主机把SDA线释放并且再给一个时钟,在这个时钟周期内,如果从机拉低了SDA表示回复了ACK,如果没有拉低,表示没有回复ACK
在这里插入图片描述
在这里插入图片描述

2.3.2.4 数据传输与有效性

数据的传输是在发送完成了启动信号之后便可以进行数据的传输了。
I²C协议要求在时钟信号SCL为高电位的期间,数据线SDA上的数据要保持稳定,不能发生变化。只有在时钟信号SCL电位变低的时候,数据线SDA上的电平状态才能发生跳变。每一个数据的bit位传输需要一个时钟脉冲,一次传输最多是8bit。

一个完整的传输过程的通信时序如图:
在这里插入图片描述

通信开始时,最开始发送的都是地址帧。比如,一个7Bit的地址,首先发出的是最高位,即读写位(1-读,0-写),用于指示是读操作还是写操作。
帧的第9位是应答位(ACK/NACK)。所有帧(数据或地址)都是一样的。一旦发送帧的前8位,接收设备就可以控制数据线SDA。如果接收设备在第9个时钟脉冲没有将SDA线拉低进行应答,则可能是接收设备没有接收到数据,或者出现错误。在这种情况下,主机需要决定该做什么样的处理(一般考虑重发或者退出)。
注意:(1)SCL时钟信号由主机负责产生。数据的发送是高位先发的!
(2)SCL上的时钟脉冲是由主机提供,但从机也有控制SCL的时候,有且仅有一种情况下从机会控制时钟,就是它忙的时候,会拉低SCL,表示忙,不能收发数据,如图红色箭头处

2.3.2.5 总线的仲裁

I²C总线支持多个主机同时在总线上发送数据,但是同一时刻只能有一个主机传送数据。因此必须要通过某些手段来决定哪个主机获得总线的控制权,其它的没有获得主机控制权的设备就只能进行等待,直到获得总线控制权才能进行数据的传输。I²C总线仲裁的方式有两种:时钟同步、仲裁。

2.3.2.5.1 时钟同步

时钟同步是通过时钟线SCL来实现的。在时钟信号SCL由高到低的切换过程中,I²C器件会开始数自身的低电平周期。当主器件的时钟信号变为低电平的时候,它会使SCL线保持这个电平状态直到达到高电平。假如这个时候有另外一个器件的时钟依然是处于低电平的周期,这个时钟的低到高的变化不会改变SCL线的状态。因此,SCL线被有着最长的低电平周期的器件占有总线的控制权,而这个时候低电平周期短的器件会进入高电平的等待状态,直到当前的主器件释放总线控制权,自身能够获得总线控制权才会改变这些状态。
时钟同步流程如图:
在这里插入图片描述

一旦主设备1的时钟拉低,就会把SCL总线拉低。当SCL总线从高拉低时,主设备将计数主设备时钟的低电位周期。
主设备2检测到SCL线为低,也会立刻将CLK2拉低,不会等到CLK2的正常周期才拉低。
如果此时另外一个主设备的时钟仍然是低电平的,此时SCL总线也同样的保持为低。在此期间,低周期比较短的主设备1将进入高电平状态等待。如图,主设备1提前进入高电平等待,主设备2继续保持低电平。
当所有主设备的时钟都为高电平时,将会把SCL线拉高。也就是说时钟低电平周期较长的主设备2决定了SCL线为低的周期长度。
所有的主设备时钟都拉高时,将对他们的高电平时钟计数,第一个结束时钟高电平周期的主设备将会把SCL线拉低。主设备1将SCL拉低后,主设备2检测到SCL线为低时,CLK2也立刻拉低。

2.3.2.5.2 仲裁

仲裁和同步一样,都是为了解决多主机情况下的总线控制冲突。仲裁的过程与从机无关。
只有在总线空闲的时候主机才可以启动传输。两个主机可以在启动条件的最小保持时间内生成一个启动条件,从而在总线上生成一个有效的传输启动条件,然后仲裁程序决定哪一个主设备可以完成它的传输。SDA默认生成主机挨个比较
仲裁是逐位进行,在每一位数据的仲裁期间,当时钟线SCL为高电平时,每个主机都检查数据总线SDA上的电平是否和自己要发送的相同。
这个过程需要持续很多位。理论上讲,如果两个主机所传输的内容完全相同,那么他们能够成功传输而不出现错误。但是,如果一个主机发送高电平但检测到SDA总线上的电平为低时,则认为自己仲裁失败并关闭自己的SDA数据线上的数据传输,而另一个主机则继续完成自己的传输。
I²C总线仲裁的时序示意图如下:
在这里插入图片描述

当两个主机对同一个地址的从机进行传输时,多个主机会都能进入数据周期,仲裁会单比特进行比较。
在SCL为高的每一个比特位期间,每个主设备检查SDA线的电平是否与它所发送的相匹配。这个过程很可能需要比对很多位。
如果传输的数据时完全相同的,则这个主设备可以完成地发送整个事务。
当主设备发送的比特位与SDA线不匹配时,例如图中DATA1在SCL线的第三个周期,此时DATA1拉高,而DATA2W为低,DATA1和DATA2在线与后SDA为低,匹配不成功。当主设备1检测到一个与自身不相符的低电平,这时会关闭主设备1的SDA传输驱动,另一个主设备2会继续完成传输。

2.3.3 I²C通信流程

每个I²C设备都通过唯一的器件地址进行识别,根据设备功能,他们既可以是发送器也可作为接收器。通信的流程如下:
(1)I²C从机检测到I²C总线上的起始信号之后,就开始从总线上接收地址,之后会把从总线接收到的地址和自身的器件地址(通过软件编程)进行比较,一旦两个地址相同,I²C从机将发送一个确认应答(ACK),并响应总线的后续命令;
(2)发送或接收所数据;
(3)发送或接收完成之后,在收到应答信号ACK之后结束数据的传输。
此外,如果软件开启了广播呼叫,则I²C从机始终对一个广播地址 (0x00)发送确认应答。I2C模块始终支持7位和10位的地址。以7位为例
在这里插入图片描述

7Bit地址的通信中,开始信号之后的第一帧是地址帧+读写位,刚好是8Bit的数据,直接发送,等待从设备应答之后便可以进行数据的通信。

2.3.3.1 主机写-从机收

主机对从机发送数据时,主机对从机发送一个开始字节,然后即可一直发送数据。以示例来讲解,其第一帧数据为要操作的寄存器地址,所以为:“[1-Byte]开始字节(写) + [1-Byte]寄存器地址 + [1-Byte]寄存器数据”。
在这里插入图片描述

2.3.3.2 主机读-从机发

主机向从机读取数据时,方式同发送数据有所不同,要多一次通信过程。主机需要先向从机发送一次信号,告诉从机”我要读取数据“,然后重开一次通信,等待从机主动返回数据。以示例来讲解,发送 “[1-Byte]开始字节(写) + [1-Byte]要读取的寄存器的地址”,之后结束通信,或者重开始,来进入到第二次通信中,先发送 [1-Byte]开始字节(读),然后等待读取从机发送过来的 [1-Byte]数据即可。
在这里插入图片描述

1、主机首先产生START信号;
2、然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令;
3、这时候主机等待从机的应答信号(ACK);
4、当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号;
5、当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设置成接收模式开始读取数据;
6、这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不再接收数据;
7、主机进而产生停止信号,结束传送过程。

2.4 多主或多从

2.4.1 单个主设备连接多个从机

I2C总线上的主设备使用7位地址对从设备进行寻址。可以挂接127个不同地址的I2C设备。常用IIC接口通用器件的器件地址是由种类型号,及寻址码组成的,共7位。如格式如下:
D7 D6 D5 D4 D3 D2 D1 D0
器件类型由:D7-D4 共4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的。
用户自定义地址码:D3-D1共3位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC的3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了。
在这里插入图片描述

2.4.2 多个主设备连接多个从机

多个主设备可以连接到一个或多个从机。当两个主设备试图通过SDA线路同时发送或接收数据时,每个主设备都需要在发送消息之前检测SDA线是低电平还是高电平;如果SDA线为低电平,则意味着另一个主设备可以控制总线,并且主设备应等待发送消息。如果SDA线为高电平,则可以安全地发送消息。
在这里插入图片描述

三、程序示例

这里只是抓取IIC发送端波形,故代码只需发送和等待接收ACK共九个时钟信号(ACK默认返回高电平,即无响应)

1.	void IIC_Start(void)
2.	{
3.	   SDA_OUT();		//SDA切换至输出模式
4.	   SDA_H();		//SDA置高
5.	   SCL_H();		//SCL置高
6.	   HAL_Delay(1);
7.	   SDA_L();		//SDA置低
8.	   HAL_Delay(1);
9.		 SCL_L();		
10.	}
11.	
12.	void IIC_Stop(void)
13.	{
14.	   HAL_Delay(1);
15.	   SCL_H();
16.	   SDA_H();		//SDA置高
17.	   HAL_Delay(1);
18.	}
19.	
20.	void IIC_WriteByte(uint8_t data)	
21.	{
22.	    uint8_t i;
23.	    SDA_OUT();			//SDA设置为输出模式
24.		  SCL_L();				//SCL置低
25.	    for(i=0;i<8;i++)		
26.	    {
27.	        if(data & 0x80)     //如果Data的第八位是高电平
28.	            SDA_H();		//SDA置高
29.	        else
30.	            SDA_L();		//SDA置低
31.	        SCL_H();				//SCL置高
32.					SCL_L();
33.	        HAL_Delay(1);				//延时
34.	        data<<=1;						//data左移一位
35.	    }
36.			SDA_OUT();
37.	}	
38.	uint8_t IIC_Wait_Ask(void)
39.	{
40.		int count=0; 
41.		SDA_IN();
42.		SCL_L();
43.		SCL_H();
44.	
45.		while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7))   
46.	  {
47.	    count++;            //开始计数
48.	    if(count>250)       //计数超过250,则认为数据发送失败
49.	    {
50.		  SDA_OUT();
51.		  SCL_L();
52.	      IIC_Stop();			//发出停止信号	
53.	      return 1;           //函数返回1
54.	    }   
55.	   }
56.		SDA_OUT();
57.		SCL_L();
58.	    return 0;
59.	}

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kong sir

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

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

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

打赏作者

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

抵扣说明:

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

余额充值