![a66691b1001a8a399c17ee547ca7f0fb.png](https://i-blog.csdnimg.cn/blog_migrate/9708f0b31c1a5d1fc7d19da261f26551.jpeg)
我第一次知道I2C总线是1995年,项目中用到电视机高频头(也叫调谐器、Tuner),能够方便买到的高频头要么是飞利浦(Philips)的,要么是日系厂商的,但日系厂商联系起来比较费劲。Tuner其实就是通过I2C总线送控制字来改变其本振频率(LO)选择你需要的频段,当时知道I2C的鼻祖就是飞利浦半导体(NXP-恩智浦半导体的前身),也是第一次使用MC34063这颗后来如同555一样扑街的开关稳压芯片,用来产生高频头所需要的12V DC。
![820c81421a2abf2fe609f1ebd81daf90.png](https://i-blog.csdnimg.cn/blog_migrate/53fada62260fd1427be07ff53258b896.jpeg)
板子上的器件之间也需要Talk
器件和器件之间的也需要沟通信息,尤其是需要MCU/DSP等对其它外设进行控制的时候。工程界的大神们基于MCU/DSP开发了一系列的协议比如UART、USART、SPI、I2C、CAN等. . . .每种协议都有各自擅长的地方,也有其局限性,因此要做系统设计的硬件工程师就应该对每种接口协议有大概的认识(即便没有机会吃猪肉,也要知道各种猪是如何跑的),这样才能够帮助你在做方案选择的时候能够选用最合适的协议接口方式,这也是你需要阅读我写的文章的原因。
在同一个PCB板子上的不同器件之间进行通信最常用的有三种形式 - SPI、I2C和UART,上篇文章我们简单介绍了SPI,今天就来看看I2C,我们先看一下I2C最基本的一些特性,然后再跟其它的通信协议方式进行一下比较。
![82927fe31b7154907b6f7e3e5cc97bc3.png](https://i-blog.csdnimg.cn/blog_migrate/e2ac4620949cf3f339ef22435dfae853.jpeg)
简约而不简单的I2C总线
I2C来自于英文inter–integrated circuit,有时也写为IIC,字面意思也可以理解为IC之间进行交流用的,跟SPI对比,I2C没有天生的主、从之分,也就是说挂在两根线(数据线SDA和时钟线SCL)上的所有器件都是生而平等的。这个协议最早由飞利浦半导体推出来,几年后Intel又弄了一个SMBus(系统管理总线)协议,其实基本跟I2C一模一样,算是其扩展吧,一丢丢的差别而已。
![469a1ec5a4ce50f56e5e637457b03c9c.png](https://i-blog.csdnimg.cn/blog_migrate/f920d6b456750d75fbc8449aeae54ce2.jpeg)
有哲学家说 - 越是看起来简单的东西,背后处理的问题越复杂。I2C其实也是如此,虽然我们看到的是2根线能挂起一大串的器件,但就像一个没有了老师的课堂,没有一个好的管理机制一定会出现乱哄哄的局面,要让任何两个同学之间进行有序地交流,没有明确的协议是肯定会乱掉的。
最简单的情况就是在这个系统中有1主1仆,但如果有多个仆(从设备)呢?如果多个“从设备”不知道哪个是“主设备”呢?如果出现了多个“主设备”呢?如果一个“主设备”正由“从设备”获取数据,中途由于种种原因突然挂了怎么办呢?一个“从设备”正发着数据挂掉了怎么办呢?一个“主设备”获取了总线使用权用以数据的发送,在释放使用权之前崩溃了怎么办呢?
![8c25f5fe962bb4e04b3a3ff2287c47ca.png](https://i-blog.csdnimg.cn/blog_migrate/925c6f18df9ea892f81c6c6dcb7a37f4.jpeg)
在实际的运行环境中会有各种意外导致系统出现问题,我们在学习使用I2C的时候一定要做到心中有数 - 简单的架构背后有着复杂的结构来保证这个协议的顺利执行,才能让其成为灵活、可扩展、鲁棒、极少管脚的串行通信方案。
![3e46d14efa1780177ee8d308eba6234c.png](https://i-blog.csdnimg.cn/blog_migrate/07e16d9dd216df942e2fa7ead35a5278.jpeg)
I2C协议概要
以下是I2C的主要特征:
![9e23d69675de9bfbd030c6fa20df38ee.png](https://i-blog.csdnimg.cn/blog_migrate/f0170a3df97a992441362fb6da3f3bfc.jpeg)
- 不论总线上挂多少个设备,只需要两根信号线(时钟SCL和数据SDA)就搞定;
- 两根信号线都通过合适阻值(这个值的正确选取很重要)的上拉电阻连接到正电源上;
- 每个设备的接口都是通过漏极开路(或集电极开路)的输出驱动连接到时钟和数据信号线上;
- 每个从设备都有一个7位的地址,可供寻址用。主设备必须知道这些从设备的地址以便同指定的一个从设备进行通信。
- 所有传输均由“主设备”发起和终止; “主设备”可以将数据写入一个或多个“从设备”或从“从设备”请求数据。
- 在系统中“主”和“从”不是固定的,任何一个设备都可以作为“主”或“从”,只要它配置了适当的硬件或固件,实际上在嵌入式系统中最常采用的架构就是一个“主设备”向多个“从设备”发送命令或由多个“从设备”采集数据。
- 数据信号在时钟的下降沿更新,并在上升沿被采样,如下图。
![8010c8c12de8c4719f4bee0fc6392b0a.png](https://i-blog.csdnimg.cn/blog_migrate/9bf3dcf0a3cdeb3fd141928bcf641274.jpeg)
- 数据是以一个字节进行传输的,每一个字节跟着1位的握手信号,作为ACK/NACK(应答/无应答)位。
同UART和SPI相比,I2C有何优势?
I2C的主要优势如下:
- 管脚/信号数量少,即便挂了很多的设备,也只用两根线;
- 可以适应不同的从设备的要求;
- 可以支持多个主设备;
- 引入了ACK/NACK功能以提升应对错误的能力
当然也有一些劣势的地方:
- 增加了固件和底层硬件的复杂度
- 增加了协议的负荷,降低了数据传输的吞吐率
- 需要上拉电阻,会导致如下的后果
- 限制了时钟的速度
- 增加了功耗
由此可以看到I2C比较适合复杂、多样化、需要通信设备灵活扩展的场景;UART比较适合单点对单点的连接,因为UART没有标准的方式来寻址不同的设备或共享管脚。SPI比较适合系统中有一个主设备和少量的从设备,而且每一个从设备都有一个单独的“从设备选择”信号,当总线上有多个设备的时候会需要更多的管脚,布线的难度也会增加,当你需要支持多个主设备的时候,SPI用起来也会非常尴尬。
如果你需要较高的传输速率,使用I2C就不太合适,SPI能够支持更高的时钟频率,数据负载开销也最小。如果你想使用FPGA从头设计串行数据传输,SPI和UART的底层硬件设计要简单得多,迫不得已再用I2C。I2C硬件电路特色
I2C的一个特性是总线上的每个器件都必须通过漏极开路(或集电极开路)输出驱动器连接时钟信号(缩写为SCL)和数据信号(缩写为SDA)。这也就意味着:
- 信号缺省始终为逻辑高电平,如果I2C主设备尝试与已失效的从设备通信,则数据信号永远不会进入未定义状态,如果从设备没有驱动信号,它将被读为逻辑高电平。 同样,如果主设备在传输过程中断电,SCL和SDA将返回逻辑高电平,其它设备可以通过观察SCL和SDA在一定时间内逻辑高电平来确定总线是否可用于新的传输。
- 即使另一个设备试图将它们驱动为高电平,总线上的任何设备都可以安全地将信号驱动为逻辑低电平,这是I2C“时钟同步”或“时钟延长”功能的基础:主器件产生串行时钟,但如果需要,从器件可以将SCL保持为低电平,从而降低时钟频率。
- 具有不同电源电压的器件可以共存于同一总线上,只要较低电压的器件不会被较高的电压损坏即可。 例如,如果SCL和SDA上拉至5V,3.3V的器件就可以与5V的器件进行通信 - 即使3.3V的器件无法驱动来自典型的推挽输出级的5V,漏极开路的配置可以让逻辑高电压达到5V。
有电阻R,就会有RC
由于漏极开路输出驱动器存在着明显的缺点,并不是数字IC的标准配置。电压的变化会受到与特定节点相关的电容充电或放电所需的时间的限制。 SCL和SDA上的上拉电阻限制了充电的电流量 - 也就是说,我们在RC时间常数中可以更多地通过R来控制从逻辑低到逻辑高的转换。
![26a8f859287a431f6cf2c8b7ddd6c977.png](https://i-blog.csdnimg.cn/blog_migrate/66b397c3b0bc0e32bd116e3437dec46e.jpeg)
![29de3952a787ea59174cfff04dd419af.png](https://i-blog.csdnimg.cn/blog_migrate/033c237e99070a09f22bbe504d9e3945.jpeg)
从这个图可以看出从低到高的转换比从高到低的转换要慢很多,导致出现常见的I2C锯齿波形:
![a5ea58ab8fe063208236f20276748a73.png](https://i-blog.csdnimg.cn/blog_migrate/5081a839793086e53d8cff94c813a5b0.jpeg)
下图为示波器上捕捉到的实际的时钟信号波形 - 采用1kΩ的电阻做上拉,即便最小的电容效应(总线上只有两个器件,且很短的PCB走线)的时候I2C时钟信号的低到高以及高到低的变化。
![6bb1d732f1a849e91772bb0e9aad33bc.png](https://i-blog.csdnimg.cn/blog_migrate/354c070a80036a6c6592326bd1b1f592.jpeg)
![7cf892cdd5c4a5c5ad1acd0907346f1b.png](https://i-blog.csdnimg.cn/blog_migrate/7451461db2626e402f04f9f4071b7992.jpeg)
上拉电阻的值如何选择?
可见,上拉电阻限制了数据传输的最大时钟速率。实际上,电阻和电容都有影响,我们无法控制电容,因为它主要取决于总线上有多少器件以及这些器件之间互连的方式。那问题来了,考虑到所需的数据传输速率要求以及可能带来的功耗,使用多少值的上拉电阻才最合适?较低的电阻RC时间常数也比较低,但会通过上拉电阻增加从VDD流向地的电流(只要SCL或SDA为逻辑低电平)。
官方I2C规范(第9页)规定,在达到VDD的70%之前,电压不被视为“逻辑高”。 RC时间常数告诉我们电压达到最终电压的约63%需要多长时间。 因此,为简单起见,我们假设R×C告诉我们信号从接地电压附近上升到逻辑高电压需要多长时间。
如何计算电容呢? 比较可行的方法是查找总线上每个器件的引脚电容进行粗略估计,然后再添加每英寸PCB走线3pF和每英尺同轴电缆30pF。
假设我们有50pF的总线电容,按照I2C“标准模式”规范规定 - 最大上升时间为1000ns。
![69e93f15a71c380c13e009b4805cdfd1.png](https://i-blog.csdnimg.cn/blog_migrate/edcb39d347c6347acd7e79519cb20e92.jpeg)
也就是说上拉电阻可以定为20kΩ,这个值的功耗也比较低,速度如何呢?假设你希望时钟高的时间至少是上升时间的三倍。
![69ab79db39296db2bec38261e4dd532d.png](https://i-blog.csdnimg.cn/blog_migrate/cbb4c4dc8d1409d64ab4fb5d156b7028.jpeg)
如果167 kHz不够快,您可以降低电阻(以增加功耗为代价),直到达到所需的时钟速度。 (实际上,“标准模式”将时钟速度限制为100 kHz,但可以根据系统需要调整这些规格。)
当然,这只是粗略的计算,具体的应用中要根据挂在总线上的器件的数量以及电路设计来进行估算,还可以配合示波器上实际的测量进行调整,以满足系统的综合要求。典型的数据传输
下面的时序图为一个典型的I2C传输时序。
![a9e0601bf5f45eda616142c19f9f88a1.png](https://i-blog.csdnimg.cn/blog_migrate/9c16dd0864c94efc0acde4e22e6dda7e.jpeg)
可以看到以下几点:
- 对应于时钟逻辑高电平部分的虚线提醒我们逻辑高电平(对于SCL和SDA)都是“隐性”状态 - 换句话说,信号通过上拉电阻自然浮动到逻辑高电平。 “主导”状态是逻辑低,因为只有当设备实际将其驱动为低时,信号才会变低。
- 每一次的传输都是以“起始位”开始,该起始位定义为在SCL为逻辑高电平的时候SDA的下降沿。
- 传输以“停止位”结束 - 定义为当SCL为逻辑高电平的时候SDA的上升沿。 I2C传输必须以停止位结束,但在生成停止位之前可能会出现多个起始位。
- 数据在时钟为高时有效,在时钟为低时改变状态; 数字通信系统通常都是边沿驱动的,因此实际上数据在时钟的上升沿被读取,在时钟的下降沿时进行更新。
- 信息一次一个字节地交换,从最高有效位开始,每个字节后跟一个ACK或NACK。
- 你可能期望ACK由逻辑高指示,NACK由逻辑低指示,但事实并非如此。 ACK为逻辑低,NACK为逻辑高。 这是必要的,因为高是隐性状态 - 如果“从设备”不工作,信号自然浮动到NACK,同样只有当设备正在运行并准备继续进行传输时,才会发送ACK。
以下为I2C数据传输的顺序:
- 主机生成一个起始位以启动传输。
- 主设备发送与其想要通信的从设备相对应的7位地址。
- 第一个单字节段中的最后一位是读/写指示符。如果想要从“从设备”读取数据,则“主设备”将该位设置为逻辑高电平; 如果要将数据写入“从设备”,则将其设置为逻辑低电平。
- 下一个字节是第一个数据字节,它来自主设备或从设备,具体取决于读/写位的状态。像往常一样,我们有8位数据,从最重要的位开始。
- 数据字节之后是ACK或NACK,如果这是读数据传输则由“主设备”生成,如果是写数据传输,则由“从设备”生成。 ACK和NACK可能意味着不同的东西,具体取决于通信设备的固件以及底层的硬件设计。例如,主设备可以使用NACK来表示“这是最后一个数据字节”,或者如果“从设备”知道要发送多少数据,它可以使用ACK来确认数据是否已成功接收。
- 传输以“主设备”生成的停止位终止。
传输多少字节?
每次传输都是以同样的方式开始:起始位、地址、读/写、ACK/NACK。 之后,任何数量的字节都可以从“主设备”发送到“从设备”或从“从设备”发送到”主设备“,每个字节后跟ACK或NACK。 NACK可以用来表示“停止发送数据!”。例如,“主设备”可能希望从“从设备”(例如温度传感器)接收连续的数据流,每个字节后面都会有ACK,如果“主设备”需要处理其它事情,它可以用NACK告知”从设备“并在它准备就绪时再开始新的传输。
![77734371d0a00866501cdb65ba85e9d2.png](https://i-blog.csdnimg.cn/blog_migrate/b6dfdcda50b7748b94f0596f2d1a5fc3.jpeg)
由于篇幅限制,在此我们不做更详细的介绍,有兴趣的朋友可以阅读Wikipedia中关于I2C的介绍以及该词条下面的参考文章。对该总线的使用以及技术细节有一定程度的了解会帮助我们在实际的设计中更加有效地完成数据的传输设计以及有可能的问题定位。
一个应用举例 - 下面是我们用小脚丫FPGA做的计算器,我们通过FPGA逻辑实现了I2C的主控制功能,来操作挂在I2C总线上的触摸按键控制器、挂在SPI总线上的LCD显示屏。
![fc026f770f5c07310557b1df437c208b.png](https://i-blog.csdnimg.cn/blog_migrate/c1656a648c8d7de9bc7b9d0839bd9dfb.jpeg)
![1bb6f9c86419b95cf2db3514d98bc89a.png](https://i-blog.csdnimg.cn/blog_migrate/86c0f2119a2103dddbfcb956d018360e.jpeg)
END
![cb99139dc34b92dc660310afbc559d90.png](https://i-blog.csdnimg.cn/blog_migrate/1784df707ae01a81e8a32f4b74d002da.png)
硬禾团队一直致力于给电子工程师和相关专业的同学,带来规范的核心技能课程,帮助大家在学习和工作的各个阶段,都能有效地提升自己的职业能力。
![8ad1ad33c5b61181eedc3e313930ce3b.png](https://i-blog.csdnimg.cn/blog_migrate/e3be54a5873c580dbed27e51a9b1681d.jpeg)