I2C接口小结

I2C接口简介

I2C总线最早由飞利浦提出,其主要包含SCL与SDA两个物理端口。SCL为串行时钟线,SDA为串行数据线。由于其简单性,它被广泛用于微控制器与传感器阵列,显示器,IoT设备,EEPROM等之间的通信。I2C总线包含如下特性(本文主要梳理I2C总线在Verilog下的实现方式,不包含具体的物理电气特性):

  • 标准速率:100Kbits/s
  • 快速速率:400Kbits/s
  • 快速模式增强:1Mbit/s
  • 高速速率:3.4Mbit/s
  • 超快速模式: 5 Mbit/s
  • 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;
  • I²C是真正的多主设备总线,可提供仲裁和冲突检测;对于最大主设备数没有限制,理论上最大从机数是127(7 bit 地址位)。

I2C的Verilog实现(主机视角)

I2C时序图

在这里插入图片描述
上图大体描述了I2C的一次读写操作(写)的时序图,下面详细讲解总线交互的各个阶段:

  • 空闲状态:空闲状态时,主机和从机都释放了对总线的控制,此时SDA=1、SCL=1都是高电平。
  • START:START的动作只能由主机发起,当需要开始一次I2C读或者写操作时,主机会接管总线,此时SCL=1、SDA由1变0 (下图所示)。
    在这里插入图片描述
  • SET ADDR:在START后,主机会发送8bit数据,该数据包含即将访问的从机的7bit地址位和1bit读写标志位(0写,1读)。其中7bit地址位高位在前,读写标志位在最后一位。
  • ACK:在从机接收到其对应的地址位和读写指示后,从机占据一个SCL cycle的时间,向主机回1bit 0(若主机接受不到该回0值,报错,此时可能时物理连接出错,或者从机地址错误等)。
  • WRITE or READ:接下来便是读数据或者写数据,读写数据都是以一个字节为单位,每完成一个字节的读写便会进入ACK状态,ACK状态是由接受数据端发送的(写状态,从机回ACK;读状态是主机回ACK,回ACK都是回 1 bit 0)。
  • NOACK:NOACK主要是当主机读取从机数据时,当读完从机的最后一个字节时,主机需要回NOACK而不是ACK,从而告诉从机一次读操作结束(区别于STOP),此时SDA=1 (有些器件关注NOACK阶段主机回的SDA是否为1,有些则不关注,要根据具体的器件的通讯协议来决定)。
  • STOP:STOP的动作只能由主机发起,当需要结束一次I2C读或者写操作时,主机会接管总线,此时SCL=1、SDA由0变1 (下图所示)。
    在这里插入图片描述

代码实现思路(状态机YYDS!!)

I2C的实际通讯中,除START、STOP状态外,当SCL=1时,SDA数据是要保持不变的,因此得到思路:根据SCL的下降沿发送数据,根据SCL的上升沿采集数据。但是实际应用时,为了保险起见,发送数据时,一般取SCL低电平的中间位作为发送数据的标志点。因为怕以SCL下降沿作为改变数据变化的标志位,会引起START或STOP的误操作。

在这里插入图片描述
一般像上图这种分阶段的I2C通讯模式,使用状态机实现是及其简便的。这里给出本人习惯用的一些状态划分:IDLE——>START——>TXADDR——>TXACK——>TXDATA——>TXACK…——>STOP
or
IDLE——>START——>TXADDR——>TXACK——>RDDATA——>RDACK…——>RDDATA ——>
NOACK——>STOP
上述分别为写和读的状态流程,只需要卡好各个状态与SCL的关系就行,经供参考。其中START、TXACK、RDACK、STOP状态占一个SCL cycle;TXADDR、TXDATA、RDDATA占8个SCL cycle。TXADDR最后一位包含读写位(代码在服务器中,这里就不给出了)。

新的实现IO端口的方式

由于SDL和SDA是inout型端口,因此在实际的Verilog代码中SDA会对应sda_in、sda_out、sda_oe三个信号,SCL对应scl_in、scl_out、scl_oe三个信号。其中_in信号为输入信号,_out信号为输出信号,_oe信号则是表示pad此时的输入输出状态。一般的_oe信号为1,表示此时pad信号线上输出的是_out信号;当_oe为0时,pad信号线上是对端从机的输出_in信号或者此时总线处于空闲状态。具体可以参考相应Foundry厂商提供的相关工艺的关于IO/PAD的使用手册。SCL由于是主机发出的,且作为交互时钟一直由主机发出,因此scl_oe一般固定为1,处理较为简单。

相对于传统的通过sda_oe信号来表示SDA的输入输出的繁琐方式,我这里运用了一种取巧的方式:scl_out、sda_out全为0,scl_oe = ~scl_out,sda_oe = ~sda_out。scl_in、sda_in正常使用。该方法下,Verilog代码只需要产生scl_out和sda_out即可,不需要再产生对应的_oe信号,相对简化了代码。

在这里插入图片描述
其实该方法在产生sda_out时,绝大部分是与传统方式相同的,只不过是需要在ACK和NOACK状态下需要特殊处理:总结为,sda_out在发送数据时,该发啥发啥;在接收数据时,全为1。

  • 总线释放状态时,sda_out(scl_out)=1;
  • 当进入START时,sda_out=0;
  • 进入TXADDR、TXDATA时,sda_out=实际应该发送的数据;
  • 进入RXDATA时,sda_out=1;
  • 进入NOACK时,sda_out=1;
  • 进入STOP时,因为NOACK时,sda_out=1,因此进入STOP时,sda_out要先为0,然后才scl_out=1后,sda_out再由0变为1。

关于锁死问题

在正常情况下,I2C总线协议能够保证总线正常的读写操作。但是,当I2C主设备异常复位时有可能导致I2C总线死锁产生。
一种情况是主机在读完最后一个字节时,没有在NOACK阶段回复1,导致从机会一直等待NOACK,从而占据总线,拉低SDA,锁死总线。
另外一种是在I2C主设备进行读写操作的过程中,主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从设备输出应答信号,将SDA信号拉为低电平。如果这个时候主设备异常复位,SCL就会被释放为高电平。此时,如果从设备没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号
而对于I2C主设备来说.复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。这样,I2C主设备等待从设备释放SDA信号,而同时I2C从设备又在等待主设备将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。同样,当I2C进行读操作,I2C从设备应答后输出数据,如果在这个时刻I2C主设备异常复位而此时I2C从设备输出的数据位正好为0,也会导致I2C总线进入死锁状态。

一般在ASIC角度,解决方法是每次I2C主设备复位后,如果检测到SDA数据线被拉低,则控制I2C中的SCL时钟线产生一次写时钟脉冲信号,这样I2C从设备就可以完成被挂起的操作,从死锁状态中恢复过来。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值