数字 IC]深入了解 I2C 协议
一、什么是I2C协议
I2C是由Philips开发的简单的双向两线总线,在深入浅出理解SPI协议中,我们区分了单工,半双工,全双工协议数据流向的区别,根据特征,I2C协议属于半双工协议(即同一时刻,数据单向流动)。此外,I2C也是一种可以多主设备,多从设备的总线协议,通过地址索引,I2C可以使能所需从设备,I2C的出现主要是用来实现不同集成电路组件之间的控制功能,比如通过I2C协议,连接MCU与LCD驱动器,远程I/O口,RAM,EEPROM或数据转换器。
二、I2C,SPI,UART协议的区别
作者按照顺序,依次完成了UART,SPI,I2C协议,因为这三种协议都属于低速通用协议接口,因此作者将这三种协议放在一块进行比较,诚然,这些协议经过数十年的发展,衍生出了很多新版本,拥有了很多新特性,但他们的基本通信方式没变,因此我们仅比较他们的基本版本,得到如下表格,当然特性太多,也未必绝对准确,仅供初学者参考。
重点解释一下UART,SPI,I2C这三个协议的选通方式,作者觉得很有意思。
对于UART来说,正常来说是没有办法满足一个主设备,多个从设备的通信,它的通信方式最为简单,最低只需要一根线即可完成通信,协议本身并不允许外接多个从设备,但是我们也可以通过比如485转接、增加二极管的方式来进行多从设备选择(电路层面的设计内容,不是数字IC需要考虑的内容)。
其次是SPI协议,它有一个专门的NSS端口,默认拉低来选择所需的从设备。
最后是I2C协议,每个主设备和每个从设备都对应一个地址,通信的时候先发送地址信号,若一致,则被选中。
三、I2C的信号线
I2C仅需要两根信号线即可完成通信,如下图所示。除此以外,I2C的信号线需要连接上拉电路,有关上拉电阻的大小,是电路设计工程师需要操心的事,不归Digital IC Design Engineer管,就不在这里赘述了。
SDA(Serial Data) :串行数据线,用来传输数据信号。
SCL(Serial Clock):串行时钟线,用来传输时钟信号,一般是主设备向从设备提供。
四、I2C的连接方式
I2C的连接的连接形式非常灵活,可以是单主设备,单从设备,也可以是单主设备,多从设备,还可以是多主设备,多从设备。
4.1 单主设备,单从设备
4.2 单主设备,多从设备
4.3 多主设备,多从设备
五、I2C的数据传输格式
5.1 空闲位
空闲时SDA与SCL默认都是高电平,对应于主设备状态机IDLE(默认态)时的输出为高
5.2 起始位
SCL为高电平的时候 ,主设备控制SDA从1到0,为起始位,进入起始位后,SCL按照始终的要求进行翻转
从设备接收到起始位信息,状态机发生跳变,等待地址信号的输入
从设备在这里需要使用到下降沿检测电路,作为状态机跳变的控制信号,详情可参考作者之前的文章。【数字IC手撕代码】Verilog边沿检测电路
5.3 地址位与读写控制
主设备按照从高到低的顺序,依次发送地址位,从设备进行接收,通常情况下,地址位为7bit,读写选择为1bit。每个从设备有且只有一个唯一的地址编号,依靠着这个编号,确定主设备具体与哪一个从设备进行通信。
同时,为了确保采样时信号稳定,对于主设备,我们在下降沿的时候将信号放在SDA上,对于从设备,我们在上升沿的时候进行采样。
对于读写控制位来说如果主设备需要将数据发送到从设备,则该位设置为 0;如果主设备需要往从设备接收数据,则将其设置为 1 。即写为0,读为1。
5.4 应答位(ACK/NACK)
发送了标题为5.3的8bit后,主机释放对SDA的控制权,由于上拉电阻的作用这个时候SDA默认为高电平,从机接管SDA的控制权,假如从机正确的接收了数据,会将SDA拉低,假如没有正确的接收数据,在从设备的控制下,SDA依旧为高电平。
读者在这里会发现,同一个SDA,怎么主设备也能控制,从设备也能控制呢?这里涉及到了inout双向端口的相关问题,可以参考作者的这篇文章进行解读和理解通俗易懂的带你解读inout双向端口
5.4.1 正确接收数据(ACK)
正确接收,SDA由从设备拉低
5.4.2 未正确接收数据(NACK)
未正确接收,SDA依旧为高电平
5.5 数据位
当我们成功收到ACK信号后,就可以正式传输数据位了,每一次默认传输一个字节(即8bit),每个字节的传输都需要跟一个应答位(ACK/NACK)
5.6 停止位
SCL先拉高,在SCL为高电平的时候,SDA从低到高,即为停止位,此后,I2C协议重新进入到空闲状态。这里使用了上升沿检测电路,原理同起始位的下降沿检测电路
5.7 总结
首先为起始位S(start),接着传输地址7位SLAVE ADDRESS和1位读写控制信号R/W,再往后8位8位的传输数据位,每个字节紧跟ACK信号,最后为停止位
所有的阴影部分,都是主设备在操作总线,而A对应的ACK,则为从设备在操作总线。
六、I2C可配置变量
6.1 传输模式
标准模式(Standard):100kbps
快速模式(Fast):400kbps
快速模式+(Fast-Plus):1Mbps
高速模式(High-speed):3.4Mbps
超快模式(Ultra-Fast):5Mbps(单向传输)
提起不同速度的传输模式,读者首先想到的可能是指SCK的频率,这没有错,但是绝不仅限于此,为了获得更高的传输速率,除开芯片设计工程师外,电路的设计人员需要认真思考诸如“负载电容,上拉电阻的大小”等更偏向于电路设计或模拟设计的内容。
6.2 地址位宽
标准I2C:七位寻址
扩展I2C:十位寻址
每个主设备或者从设备都能对应一个唯一的地址,大多数情况下,7位的地址,已经够用了。但是也可以对其进行扩展,转变为10位地址,多出来的3位地址相当于提供了8倍潜在设备数量,同时,按照NXP2021版的I2C协议规定,10位地址的从设备,和7位地址的从设备,都可以挂在一个总线上,彼此相互兼容,不过,客观来讲,10位寻址的I2C不常用,7位寻址的I2C协议就足够大家日常使用了。
6.3 设备地址
每个主设备与从设备需要设置互不相同的七位地址或十位地址。
等等等等
七、I2C的仲裁机制
7.1 SCL同步问题
总线天生带线与逻辑,即总线的几个输入端,任意有一个拉低,总线表现为低电平,全部位高电平时,总线才是高电平,真值表如下所示。
假设有两个主设备都想拉低SCL信号,Master1先拉低 ,Master2后拉低,那么SCL会按照CLK1的时间来拉低自身(线与逻辑的应用),而假如Master1先拉高,Master2后拉高,SCL又会按照CLK2的时间来拉高自身。
因此:当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号。这就是SCL的同步原理
7.2 SDA仲裁问题
设想一种多主设备,多从设备的情况
假如在空闲状态时,两个主设备先后想要操控I2C总线(相隔时间很短),I2C岂不是会发生错误(数据紊乱等),如何解决这个问题呢?
我们可以采取仲裁机制,同样应用到总线的线与逻辑,在箭头所指的位置,SCL上升沿到来,对SDA上的数据进行采样,结果为0,与DATA2上的数据0相同,与DATA1上的数据1不同,通过这种比较 Master1退出了对总线的控制,而Master2所发送的数据都是正确的,完成仲裁。
八、写在最后
接下来的文章,我们将从零开始使用Verilog与I2C设计一个控制器出来,并进行不那么充分的验证工作,其中具体满足的参数如下
单主设备,单从设备(不涉及仲裁与同步)
全局时钟100Mhz
标准模式(100kbps)传输速率
标准I2C的七位寻址
从设备为EEPROM
九、其他数字IC基础协议解读
9.1 UART协议
【数字IC】深入浅出理解UART
【数字IC】从零开始的Verilog UART设计
9.2 SPI协议
【数字IC】深入浅出理解SPI协议
【数字IC】从零开始的Verilog SPI设计
9.3 I2C协议
【数字IC】深入浅出理解I2C协议
9.4 AXI协议
【AXI】解读AXI协议双向握手机制的原理
【AXI】解读AXI协议中的burst突发传输机制
【AXI】解读AXI协议事务属性(Transaction Attributes)
【AXI】解读AXI协议乱序机制
【AXI】解读AXI协议原子化访问
【AXI】解读AXI协议的额外信号
【AXI】解读AXI协议的低功耗设计
【数字IC】深入浅出理解AXI协议
【数字IC】深入浅出理解AXI-lite协议
推荐阅读
-
HTTP]402- 深入了解 http2.0 协议,读到这里就够了!
-
HTTP]402- 深入了解 http2.0 协议,读到这里就够了!
-
深入了解 TCP/IP 协议的实施(基于 linux1.2.13)
-
go语言Socket编程-Socket编程 什么是Socket Socket,英文含义是插座、插孔,一般称之为套接字,用于描述IP地址和端口。可以实现不同程序间的数据通信。 Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket,该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。 套接字的内核实现较为复杂,不宜在学习初期深入学习,了解到如下结构足矣。 套接字通讯原理示意 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。 常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。 网络应用程序设计模式 C/S模式 传统的网络应用设计模式,客户机(client)/服务器(server)模式。需要在通讯两端各自部署客户机和服务器来完成数据通信。 B/S模式 浏览器(Browser)/服务器(Server)模式。只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。 优缺点 对于C/S模式来说,其优点明显。客户端位于目标主机上可以保证性能,将数据缓存至客户端本地,从而提高数据传输效率。且,一般来说客户端和服务器程序由一个开发团队创作,所以他们之间所采用的协议相对灵活。可以在标准协议的基础上根据需求裁剪及定制。例如,腾讯所采用的通信协议,即为ftp协议的修改剪裁版。 因此,传统的网络应用程序及较大型的网络应用程序都首选C/S模式进行开发。如,知名的网络游戏魔兽世界。3D画面,数据量庞大,使用C/S模式可以提前在本地进行大量数据的缓存处理,从而提高观感。 C/S模式的缺点也较突出。由于客户端和服务器都需要有一个开发团队来完成开发。工作量将成倍提升,开发周期较长。另外,从用户角度出发,需要将客户端安插至用户主机上,对用户主机的安全性构成威胁。这也是很多用户不愿使用C/S模式应用程序的重要原因。 B/S模式相比C/S模式而言,由于它没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小。只需开发服务器端即可。另外由于其采用浏览器显示数据,因此移植性非常好,不受平台限制。如早期的偷菜游戏,在各个平台上都可以完美运行。 B/S模式的缺点也较明显。由于使用第三方浏览器,因此网络应用支持受限。另外,没有客户端放到对方主机上,缓存数据不尽如人意,从而传输数据量受到限制。应用的观感大打折扣。第三,必须与浏览器一样,采用标准http协议进行通信,协议选择不灵活。 因此在开发过程中,模式的选择由上述各自的特点决定。根据实际需求选择应用程序设计模式。 简单的C/S模型通信 Server端:Listen函数 func Listen(network, address string) (Listener, error) network:选用的协议:TCP、UDP, 如:“tcp”或 “udp” address:IP地址+端口号, 如:“127.0.0.1:8000”或 “:8000” Listener 接口: type Listener interface { Accept (Conn, error) Close error Addr Addr } Conn 接口: type Conn interface { Read(b byte) (n int, err error) Write(b byte) (n int, err error) Close error LocalAddr Addr RemoteAddr Addr SetDeadline(t time.Time) error SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error } 参看 [<u>https://studygolang.com/pkgdoc</u>](https://studygolang.com/pkgdoc) 中文帮助文档中的demo: 示例代码:TCP服务器.go package main import ( "net" "fmt" ) func main { // 创建监听 listener, err:= net.Listen("tcp", ":8000") if err != nil { fmt.Println("listen err:", err) return } defer listener.Close // 主协程结束时,关闭listener fmt.Println("服务器等待客户端建立连接...") // 等待客户端连接请求 conn, err := listener.Accept if err != nil { fmt.Println("accept err:", err) return } defer conn.Close // 使用结束,断开与客户端链接 fmt.Println("客户端与服务器连接建立成功...") // 接收客户端数据 buf := make(byte, 1024) // 创建1024大小的缓冲区,用于read n, err := conn.Read(buf) if err != nil { fmt.Println("read err:", err) return } fmt.Println("服务器读到:", string(buf[:n])) // 读多少,打印多少。 }
-
还不知道何时使用 UART、I2C、SPI 协议?一篇文章带您彻底了解
-
数字 IC]深入了解 I2C 协议
-
深入了解网络协议 (I) - http 数据包传输
-
DevOps] 深入了解 RabbitMQ:AMQP 协议基础、消息队列原理和应用场景
-
深入了解数字音频处理:基础理论与实践
-
深入了解 CDP(Chrome DevTools 协议)