简单接口开发|Verilog实现IIC协议读写EEPROM

在FPGA设计中,IIC协议是一个十分常见的协议,因为几乎所有的EEPROM都是用这个协议进行读写的,此外,一些特殊场合,也会用到此协议。这里我首先给出IIC协议的中文标准文档的下载链接(不要积分),作为例子的EEPROM芯片AT24C64的官方的中英文双版本datasheet的下载链接(不要积分),此外还有本文所附源代码的下载链接(本文的源代码来自正点原子微信公众号,不是本人所写,我只是加了点注释,写了一个简单的仿真脚本,象征性收取1个积分)。
一、IIC协议概述 IIC协议,英文全称是Inter-Integrated Circuit,直译过来是内部集成电路总线,它实际上是一种总线协议,一般也叫作I2C。IIC总线是一种共享的串行总线,是用于两个设备之间的短距离低速低数据量的数据通信,一般就用在PCB板卡上的设备之间,当然在芯片内部,做SOC设计时也可以把这种协议用在片上功能模块之间。IIC只有两根信号线,一根是双向的数据通信线SDA,一根是有效使能和时钟双功能线SCL,IIC总线上允许连接多个主设备和多个从设备,如下图所示,一般情况下,主设备是单片机或者FPGA,其他都是从设备。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图1 IIC总线系统示意图 当总线空闲时, IIC的两根信号线都是高电平,连到总线上的任一设备输出的低电平,都将使总线的信号变低,即各设备的SDA和SCL都是线“与”关系。这两根信号线的驱动规则是这样子的:SDA是主设备和从设备都可以驱动的,SCL只能由主设备驱动。很多资料把SCL称之为时钟线,我认为时钟线这个名称容易引起误解,我把SCL称之为有效使能和时钟双功能线,这是因为对于主设备来说,SCL相当于指示SDA上的数据有效的使能信号,主设备不会用SCL的沿来发送和采样数据,但是对于从设备来说,SCL是可以作为有效数据的采样时钟的,从设备可以用SCL的沿来采样数据,也可以同样把它当成一个有效使能信号。
IIC总线是比较简单的总线,不像大多数总线(比如PCI)那样子用统一编址的地址空间来管理各个设备,IIC总线没有什么地址空间的概念,它是用器件地址(注意和寄存器/存储器地址区分开来)来区分每个设备的,IIC总线的任何总线事务都是从器件地址的传输开始的。对于一个IIC总线系统来说,每个连到IIC总线上的设备都有唯一的7位器件地址,这7位中有固定部分和自定义部分,比如,对于AT24C64这个EEPROM设备,其器件地址的高4位是由厂商定义的,后3位是用户自定义的,3位的自定义表示允许一个IIC系统中最多拥有8个AT24C64,关于这点本文第三节会再次说明。
二、IIC总线的事务和时序 1、起始和停止条件 IIC标准文档规定了IIC总线事务的起始和停止的情况,称为起始和停止条件,如下图所示。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图2 IIC的起始和停止条件 当SCL是高电平时,SDA从高电平变到低电平是起始条件,表示一次总线事务的开始。当SCL是高电平时,SDA从低电平变到高电平是停止条件,表示一次总线事务的结束。起始和停止条件都是由主设备产生的。
2、数据的有效性 起始条件之后,总线处于繁忙状态,这时就可以传输数据了,数据的有效性规则如下图所示。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图3 IIC的数据有效性规则 IIC标准文档规定,SCL为高电平期间,SDA上的数据必须保持稳定,只有在SCL为低电平期间,SDA上的数据才允许变化。我个人认为SCL这时就相当于数据有效使能。
3、数据传输的格式和规则 IIC总线的数据传输是以字节为单位进行的,每次总线事务可以传输多个字节。传输时先传输数据的最高位,传输完一个字节的数据之后必须跟一个响应位,响应位为0表示应答,为1表示非应答(这里需要注意的是,应答和非应答实际上都是给出了响应,具体意义后面会讨论),无论是数据位还是响应位都要满足上面的有效性规则,所以一次字节传输实际上传输的是9位数据,其中前8位数据位是由发送器驱动的,第9位响应位是由接收器驱动的(主设备和从设备都有可能是数据的发送器和接收器)。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图4 IIC总线的数据传输格式 无论何种总线事务,数据传输时都是由主设备发送器件地址+命令(1表示读,0表示写)开始的。
总线上只有下面这3种情况(下面3图中,S表示起始条件,A表示响应位,P表示停止条件,阴影部分表示传输方向是主发从收,非阴影部分图5图6表示从发主收,图7根据命令位决定):
1、总线读事务
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图5 IIC总线读事务 上图中的SLAVE ADDRESS表示的是器件地址,读事务时一般是不传输寄存器地址(或存储器地址)的,这个原因在第三节中解释。如果主设备在接收到一个字节数据之后给出应答,则表示它还要继续读,连续读的时候会自动读出下一个地址的数据,直到主机给出非应答才表明它不读了。
2、总线写事务
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图6 IIC总线写事务 同样的,上图中的SLAVE ADDRESS也是表示的器件地址,写事务时是需要传输寄存器地址(或存储器地址)的,只是这个地址的传输在这里就相当于数据的传输,在第三节中会解释。从设备如果成功接收到数据则给出应答,否则给出非应答。总线写事务何时结束是由主设备决定的,有两种结束情况,或许是主设备写完了全部数据给出结束条件,也或许是从设备给出非应答之后,主设备决定暂时不写数据,于是给出结束条件。
3、总线复合事务
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图7 IIC总线复合事务 上图中Sr表示主设备发出一个新的起始条件,在一次总线复合事务中,主设备对同一个从设备可以改变其访问命令,让数据传输方向改变。上图中没有阴影的部分的数据传输方向由访问命令决定。
4、其他规则
数据传输时还需要注意下面的规则:
1、传输中断规则。从设备可以在数据传输的任意时刻将SDA拉高(这时或许它有其他事情要做),并且可以一直拉高SDA直到它做完其他事情。主设备必然会在这之后某时刻去检查响应位(因为每传输8位就会有一个响应位),如果为1,则表示从设备没有响应,此时它要么就给出一个停止条件,要么就将SCL置0并且等待一段时间之后再次给出一个起始条件,如图8所示。(这里存疑)
2、时序规则。也就是IIC总线信号的建立时间和保持时间等规则,这个规则在本文所附标准文档的第15章,还要结合具体IIC设备的datasheet,本文不介绍。
3、SCL的同步规则。多个主设备都可以驱动SCL,每个主设备的SCL的占空比不完全一样,因此一个IIC总线系统需要规定一个占空比规则,用于产生一个同步的SCL时钟,可以用这样的时钟来采样总线上的任意一个数据。于是这样子规定一个同步的SCL时钟:它的低电平周期是由低电平最长的主设备SCL决定,而高电平周期是由高电平最短的主设备SCL决定。
4、仲裁规则。IIC是共享总线,显然,当IIC总线上的多个主设备申请总线控制权时,必然会导致冲突,所以IIC总线是需要仲裁器的。作为FPGA工程师,我们在设计中用到IIC最多的地方就是读写EEPROM,不需要考虑仲裁,所以IIC仲裁的相关知识本文不涉及,有兴趣的读者可以参考本文所附标准文档的第8.2节。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图8 IIC总线数据传输中断
三、EEPROM的读写规则 本节以AT24C64芯片为例来说明,所有的EEPROM的读写规则都类似。
1、器件地址+命令 参照datasheet,可以知道芯片只有3个地址引脚A0~A2,这说明7位器件地址中有4位是厂商定义的,只有3位是用户自定义的,也就是说在一个系统中我们最多只能用8片AT24C64.
固定的厂商地址就是A6~A3,为1010。无论读写,所有的总线操作,都是先由主设备发送器件地址和命令开始的,因此FPGA发送的第一个字节的数据格式如下图。这里的最低位是命令位,读命令表示后续操作是读操作,但是写命令不一定代表后续操作是写操作,或许是一次dummy写,具体见后文。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图9 AT24C64器件地址 2、单字节写和连续写 【简单接口开发|Verilog实现IIC协议读写EEPROM】主设备在发送完器件地址+写命令之后就发送存储器地址,存储器地址可以是8位或16位,发送完存储器地址之后就可以发送要写的数据了。对于AT24C64,其存储容量是64Kb=8KB,每一字节对应一个地址值,所以它需要13位地址线。由于IIC一次只能传输一个字节,所以主设备读写该EEPROM时,存储器地址要发送两次,共16位,高3位补0或者1都可以。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图10 单字节写和连续写 单字节写和连续写的操作如上图所示。EEPROM器件内部有一个指示当前存储器地址的内部指针,这个指针具有记忆功能和自动加1功能。EEPROM的厂商规定如果主设备发送器件地址+写命令之后,接下来的一个或两个字节会作为该指针的值,如果主设备发送器件地址+读命令之后,则该指针的值保留上一次最后访问时那个值,如果主设备连续读或写,则该指针会自动加1。连续写又叫页写,是因为最多只能连续写一页,每页有32B,如果从一页的开始地址处连续写了32个字节之后,这个指针会回到这页的开始,再写就会覆盖了。
3、当前地址读、任意地址读和连续读 有了上面的那个内部指针的概念之后,理解这三个操作就很容易了,如下图。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片

简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图11 当前地址读、任意地址读和连续读
当前地址读读出的数据是上一次访问之后那个内部指针所指的数据。如果要读一个任意地址处,则需要先发送一个写命令(这个写命令也叫作dummy写,假装写)之后再发起一次当前地址读,这时别忘了要重新给出一个起始条件。连续读同样也只能在一个页内进行。这里需要注意的是,在传输器件地址+命令和写操作(哪怕dummy写)时,响应位由从设备驱动,从设备给出的都是应答,表示已经成功接收到了数据,但是在读操作时,响应位由主设备驱动,主设备如果给出的是应答,表明它还要继续读,如果给出的是非应答,才表明它不再读了。
四、使用Verilog实现EEPROM的读写 本篇所附代码不是本人编写,这里我只简单分析下这个代码的思路。
编写代码时,可以参考我写的Verilog实现SPI接口的那篇文章的思路,先考虑清楚我们的目的(我们的目的就是产生满足要求的SCL和SDA这两个信号,于是可以设置一个驱动时钟,这个驱动时钟的频率为SCL频率的4倍,这样就可以用这个驱动时钟来产生这两个信号,4倍是为了满足IIC的数据有效性规则,如下图),站在用户的角度去定义我们的用户端端口;然后想清楚两个过程(用户使用的过程和我们要做哪些操作来满足用户的使用);最后设计出状态机,一切过程皆可状态机。这样子代码的大体框架就出来了,所有的接口代码都可以用这个思路去套。此外还需注意一些细节:考虑到代码的普遍适用性,可以将器件地址、SCL的频率作为可变参数;为了测试代码,可以编写一个EEPROM的模型。
简单接口开发|Verilog实现IIC协议读写EEPROM
文章图片
图12 4分频驱动时钟产生SDA和SCL Xilinx官方的IP中也有IIC接口,叫做AXI_IIC,用户端用的是AXI接口,这个IP属于比较简单的IP,我在这里只给出官方datasheet的下载链接,有兴趣的读者可以参考我以前写的阅读Xilinx官方IP的方法这篇文章来学习这个IP。
本文如有任何错误,欢迎在留言区指出。

    推荐阅读