Verilog|IIC总线协议Verilog实现

【Verilog|IIC总线协议Verilog实现】感谢正点原子达芬奇FPGA开发板资料!
IIC协议是一种数据双向、二线制总线标准的总线协议。多用于主机(master)从机(slave)在数据量不大且传输距离短的场合下使用,比如对EEPROM的读写操作,就需要采用IIC协议实现读写操作(两线串行接口的双向数据传输协议)。主机(PC)启动总线,并产生时钟用于传输数据,此时任何接受数据的器件都被认为是从机。
IIC总线是由数据线SDA和时钟线SCL构成的,发送接收数据都行,在主从机之间进行双向数据传输,各种被控器件(从机)均并联在总线上,通过器件地址(SLAVE ADDR)识别。
Verilog|IIC总线协议Verilog实现
文章图片

如上图可知,当总线空闲时候,两条线路都是高电平状态,并且各个器件的两条线都是“与”关系。如果主机(PC/FPGA)想开始传输数据,只需要在SCL为高电平的时候将SDA线拉低,产生一个起始信号,从机检测到起始信号后,准备接收数据,当数据完成以后,主机产生一个停止信号,告诉从机数据传输结束。停止信号的产生是在SCL为高电平时,SDA从低电平跳变为高电平,从机检测到停止信号后,停止接收数据。下图为IIC协议的粗略时序图。
Verilog|IIC总线协议Verilog实现
文章图片

具体时序操作为:起始信号发送之后,主机开始发送传输的数据;在串行时钟线SCL为低电平的时候,SDA才允许改变传输的数据位,SCL在高电平时候,SDA要保持稳定,相当于在SCL的一个时钟周期SDA只可以传输1bit数据,经过8个时钟周期后传输了8bit数据/一个字节。在第八个时钟周期末,主机会释放SDA让从机应答,在第九个时钟周期的SCL为高电平期间,如果从机将SDA信号拉低(检测到SDA为低电平)则表示应答,反之亦然表示数据传输失败。在第九个时钟周期末,从机释放SDA使主机继续传输数据,如果主机发送停止信号则表示这次传输结束。IIC的传输数据模式是串行发送,最先发送的是字节的最高位。
Verilog|IIC总线协议Verilog实现
文章图片

上面我们谈到,每个从机都有一个器件地址(SLAVE ADDRESS),如果我们对从机进行数据传输,就涉及到器件地址。有些器件地址是固定的有些是灵活的,对于验证IIC总线协议的Verilog实现,我们采用FPGA去进行EEPROM的读写操作,EEPROM就是一个从机,FPGA与其进行数据传输就使用IIC协议。对于我们采用的EEPROM,会留下三个管脚用于可编程地址位,A2、1、0。将这三个管脚接到GND或者VCC可以设置不同的可编程地址,对于现在的实验,三个管脚都接地。采用的EEPROM的型号是AT24C64,其器件地址如下所示
Verilog|IIC总线协议Verilog实现
文章图片

当进行数据传输时,主机首先向总线上发出开始信号,对应开始位S,然后从高到低发送器件地址,一般是7bit,第八位是读写控制位R/W,该位是0表示主机对从机写操作,0为主机对从机进行读操作,然后接收从机响应。发送完第一个字节(7位器件地址和1位读写控制位)并且从机正确应答后就开始发送字地址(Word address)。一般来说对于兼容IIC协议的器件,内部总会有可以读写操作的寄存器或者存储器,对于实验用到的EEPROM存储器,其内部就是一系列顺序编址的存储单元,所以对存储单元或者寄存器读写时,首先要指定存储单元的地址即字地址,然后再该地址存储单元或者寄存器写入数据。该地址的长度是一个或者两个字节长度,具体看内部的存储单元数量决定(对于实验任务)。对于我们采用型号是AT24C64的EEPROM来说,其存储单元的容量位64Kb=8KB,就需要13位(2^13=8KB)的地址位,IIC的传输是以字节为单位进行传输,所以需要两个字节地址来寻址整个存储单元如下图。
Verilog|IIC总线协议Verilog实现
文章图片

主机发送完字地址后从机正确应答后就把内部存储单元地址指针指向该单元。对于从主对从机的写数据操作,写数据也分单次写(对于EEPROM为字节写)和连续写(页写)。其区别在于发送完一个字节数据后,是发送结束信号还是继续发送下一个字节数据。对于EEPROM的页写,当写完一页(AT24C64的一页单元容量为32Byte)的最后一个单元时,地址的指针指向该页的开头,再写入数据会覆盖之前的内容。
Verilog|IIC总线协议Verilog实现
文章图片

对于读写控制位R/W位是“1”时候,主机对从机进行读操作,主机处于接收数据状态,从机从该地址单元输出数据。读数据的方式有三种:当前地址读、随机读、连续读。当前地址读是指在一次读或写操作完成后发起读操作。IIC器件在进行读写操作后,内部器件地址指针自动加一,因此当前地址读可以读取下一个字地址的数据,如下图所示。
Verilog|IIC总线协议Verilog实现
文章图片

当前地址读简单但是不方便读取任意地址单元数据,所以就有了随机读,随机读需要先发送器件地址和写命令和字地址,然后再第二次发送器件地址和读命令。这是因为我们需要使从机地址的存储单元地址指针指向我们需要的读取存储单元地址处。所以先进行了一次虚写/Dummy Write操作。通过虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,再用当前地址读的方式读数据,如下图。
Verilog|IIC总线协议Verilog实现
文章图片

对于连续读,对应的是当前地址读和随机读都是一次读取一个字节而言,它是将当前地址读或者随机读的主机非应答改为应答,表示继续读取数据。下图为当前地址读下的连续读。
Verilog|IIC总线协议Verilog实现
文章图片

讲解完IIC协议,现在上版验证。对于型号为AT24C64的EEPROM,其引脚图如下图
Verilog|IIC总线协议Verilog实现
文章图片

A210是可编程地址输入端,SDA是双向串行数据的输入/输出端,SCL是串行时钟输入端,WP是写保护。上面说过,该器件地址的A210是接地的,所以其器件地址为1010000。功能框图如图
Verilog|IIC总线协议Verilog实现
文章图片

其顶层模块原理图如图
Verilog|IIC总线协议Verilog实现
文章图片

其中i2c_dri为I2C驱动模块,用来驱动I2C的读写操作。当EEPROM读写操作时,拉高i2c触发控制信号i2c_exec去使能i2c驱动模块。并且使用读写控制信号i2c_rh_wl控制读写操作,当读写控制信号i2c_rh_wl为低电平时,I2C驱动模块i2c_dri执行写操作,反之读操作。此外,通过i2c_addr接口向i2c_dri模块输入器件字地址,通过i2c_data_w接口向i2c_dri模块输入写数据,并通过i2c_data_r接口读取i2c_dri模块读到的数据。rw_done信号是读写测试完成的标志,rw_result是读写测试的结果。
对于i2c_dri驱动模块,根据上面解释的I2C读写操作时序,使用状态机书写特别合适,万物皆可状态机!
首先,无论是字节写还是随机读,都要从空闲状态开始,发送起始信号,然后发送器件地址和读写命令(对于下面的状态机状态,用发送器件地址和读写命令表示“控制命令”)。发送完控制命令后接收信号信号发送字地址,然后进行读写数据的传输。读写数据传输结束后接收应答信号,最后发送停止信号,此时I2C读写操作结束,再次进入空闲状态。分解后一共八个状态。
空闲状态st_idle 当I2C触发执行信号(i2c_exec=1)进入到
发送控制命令状态st_sladdr 发送完控制命令后发送字地址,字地址对于不同存储容量的EEPROM有单字节和双字节的区别,我们采用的是双字节,为了适应不同的场景,通过通过bit_ctrl信号判断是单字节还是双字节地址。当bit_ctrl=1时候,进入
发送双字节地址状态st_addr16,如果bit_ctrl=0,则进入
发送八位字节地址状态st_addr8,并且发送完字地址后,根据读写判断标志来判断是读还是写操作。如果是读操作(wr_flag=1)就进入
写数据状态st_data_wr,开始向EEPROM发送数据,如果是(wr_flag=0)就进入
发送器件地址状态st_addr_rd发送器件地址。(对应协议中的虚写),此状态结束后进入
读数据状态st_data_rd接收EEPROM输出的数据。读或写数据结束后就进入
结束IIC操作状态st_stop并发送结束信号,此时IIC再次进入
空闲状态st_idle
状态机跳转如下图
Verilog|IIC总线协议Verilog实现
文章图片

always @(*) begin next_state = st_idle; case(cur_state) st_idle: begin//空闲状态 if(i2c_exec) begin next_state = st_sladdr; end else next_state = st_idle; end st_sladdr: begin if(st_done) begin if(bit_ctrl)//判断是16位还是8位字地址 next_state = st_addr16; else next_state = st_addr8 ; end else next_state = st_sladdr; end st_addr16: begin//写16位字地址 if(st_done) begin next_state = st_addr8; end else begin next_state = st_addr16; end end st_addr8: begin//8位字地址 if(st_done) begin if(wr_flag==1'b0)//读写判断 next_state = st_data_wr; else next_state = st_addr_rd; end else begin next_state = st_addr8; end end st_data_wr: begin//写数据(8 bit) if(st_done) next_state = st_stop; else next_state = st_data_wr; end st_addr_rd: begin//写地址以进行读数据 if(st_done) begin next_state = st_data_rd; end else begin next_state = st_addr_rd; end end st_data_rd: begin//读取数据(8 bit) if(st_done) next_state = st_stop; else next_state = st_data_rd; end st_stop: begin//结束I2C操作 if(st_done) next_state = st_idle; else next_state = st_stop ; end default: next_state= st_idle; endcase end

以上为状态机一部分代码,其完整代码和AT24C64仿真模型和tb文件已经打包,仿真已通过。
链接:https://pan.baidu.com/s/1u_Ug84X1O-0cg-hflO_mSg
提取码:c29b

    推荐阅读