基于IIC协议的EEPROM驱动控制

本文详细介绍了基于Verilog的FPGAIIC控制模块的设计,包括IIC协议的基本概念、时序分析、状态机设计以及单字节读写操作的步骤。同时,展示了IIC控制模块的状态转移图和端口说明,以及相关代码实现,强调了时钟分频和信号同步的重要性。
摘要由CSDN通过智能技术生成

本次内容是参考野火的文档,进行二次创作,会带有浓厚的个人味道,请谨慎观看!!!

附上网址:[野火]《FPGA Verilog开发实战指南》系列 — 野火产品资料下载中心 文档 (embedfire.com)

IIC协议介绍:

(这里摘抄一些重点进行罗列)

1.是低速三大总线之一(另外两个是UART和SPI)

2.有且仅有两根线,SDA(数据线)和SCL(时钟线)

3.属于同步通信,即公用同一时钟信号SCL

4.有主设备(Master)和从设备(Slave)之分

5.创新性地提出了器件地址这一说法,使得仅通过两根线就能进行不同设备之间的通讯

6.可以包含多个主机和多个从机,由于器件地址位为7位,因此理论上可以有128个器件

7.每一个设备按理说都应该有器件地址,这样才可以进行很好的通信,但本次仅写控制器,因此这里没有给出这个的器件地址

8.如果器件没有进行通讯,那么就大家都是在SDA和SCL上发送高阻态,如果所有的设备都不工作,那么就由和电源相连的上拉电阻将总线置于高电平。

9.这个通讯速度有三种100kbit/s、400kbit/s和3.4Mbit/s这个我的理解更多的是一个范围,实际上本次写的控制器是250kbit/s。还有一点比较重要,就是一般的设备通讯速度都不会很高,很少有3.4Mbit/s的设备的,但是这个相对的高速可以运用于芯片之间的通讯(单片机与FPGA的通讯)

10.接到总线的IC最大电容也有限制400pF,这个不是认为规定的,因为电容的大小决定了充放电的速度,电容越大进行充放电的时间越长,所产生的波形就会受到很大的影响,这样通讯的速度就很难提升上来,所以说并不是器件数量越多越好。

11.工作的基本时序,可以由图所示分为4个部分,后面还会进行具体的补充说明,如下图

根据颜色不难看出,总共可以分为4个不部分(空闲状态,绿色的)(开始状态,黄色的)(数据传输状态,蓝色的)(停止状态,红色的),在空闲状态时,此时总线中SDA和SCL都为高电平,严格意义上讲是高阻态,这里由空闲状态跳转到开始信号时,主机将SDA由高置低,下面就开始发送数据,从中多了一个SYS_CLK这个用于参考的,也是生成SCL的依据,蓝色部分是发送数据的部分,这里SCL呈现出周期性变化,是主从设备的同步时钟,这里总共发送了8个数据,而仔细观察,SDA数据的变化仅仅发生在SCL处于低电平的状态。在SCL处于高电平是,设备进行接收数据,从这里仔细想想为什么SYS_CLK会是SCL的4倍关系。接下来发送完8bit串行数据后就进入停止状态,这一状态的实现是在SCL为高电平的时候,SDA产生一个上升沿。

上面是存在错误的因为完成8bit数据传输后,并不会立即跳到停止状态中间需要有一个应答状态,如下图(响应位,注:在后面的SCCB时序中,是没有响应位的)

 具体响应位的电平如何设计可移步至下一节。

基于IIC的EEPROM的读/写操作时序:

这一节结合野火教程,仅对单字节的写时序和单字节的读时序进行介绍。

单字节写时序:

 这个给出了AT24C64这款存储芯片的单字节写时序,接下来就详尽的解释说明。

首先和上文一致,先进行开始信号的传递(即SDA产生一个下降沿),此时所有的设备都从原先的空闲状态进入工作状态准备接收主设备发出的七位器件地址信号(1010111)+读写控制位,读写控制位0表示进行写入从机操作,1表示从从机读取数据。然后连接在总线上的所有设备开始检查主设备发出的这个七位器件地址(1010111)和自己是否匹配,如果匹配此时,这个设备便会给SDA信号发送0,而正在此时主机是将SDA置位高组态来接收SDA信号在此时会不会有从设备发来0,如果有的话那么就可以进行下一步操作,如果没有从设备发来应答信号0,则主机信号发送失败,主机可以决定是否放弃写入或者重新发起开始状态,

然后,有了应答信号后,主机开始进行数据地址的写入,而本次使用的芯片AT24C64是64Kbit即8K字节数据的地址位至少为13位(2的十次方为1024),因此采用16为数据地址位。则进行高8位的数据地址位写入,然后从设备产生应答信号,主机接收到了应答信号后,就开始进行低8位的数据地址位传输,从机再次发送应答信号。

这时主机就把16位数据地址位传输完毕,就可以进行数据的写入了,写入完成后从设备再次发送应答信号,这次便跳到最后一个状态,停止状态,一次写操作便完成了。

单字节读时序:

 这个和写时序有较大的差别

首先开始信号的传递和写入器件的地址是一样的,但是注意这里可是读操作时序按理说在进行写器件地址时第8位读写控制位应该位1进行读,实则不是,此处仍然和写时许一样读写控制位为0进行写操作,因为这个读写控制位并不是看整个的过程而是 根据下一操作进行判断,这里在下一操作进行的是写入数据地址位,因此为0

接下来进行数据地址位写入,这里和上述写操作时序是完全一致的。

下来由接收应答信号后,开始进入第二个开始状态,这里在进行器件地址位的写入此时的读写标志位为1,然后从设备再次发送应答信号。

后面主设备的SDA端口放置为高阻态Z进行8bit数据的接收,然后主设备向从设备发送高电平应答信号,最后进入停止状态。

以上便结束了单字节写与单字节读的操作时序,后面就可以根据时序图进行IIC控制模块的编码了。

IIC控制模块的编写:

IIC控制模块状态转移图:

 这个状态转移图是一步一步按照上面单字节读写操作时序进行绘制的,上半部分是写操作,下半部分是读操作。

IIC控制模块端口说明:

下面开始从左到右,从上到下对这些端口进行介绍与说明:

1.sys_clk:系统时钟由外部晶振提供,我这个板子上的晶振是50MHz

2.sys_rst_n:异步复位按钮,因为我使用的是Altera的开发板,是支持异步复位操作的,而Xilinx的 开发板对于异步复位不太友好,需要进行一个同步的处理

3.wr_en:写使能信号由IIC数据读写模块提供

4.rd_en:读使能信号由IIC数据读写模块提供

5.i2c_start:也是由IIC数据读写模块提供,这个信号用于判断是否进行i2c通信

6.byte_addr[15:0]:这个用于存放数据地址位,也是由IIC数据读写模块提供,注意这里没有给出器件地址位的端口,器件地址位是在IIC控制模块内部就已经写入了如果需要修改后面可以修改参数就行了

7.wr_data[7:0]:这是需要写入的8bit数据,由IIC数据读写模块提供

8.addr_num:这个只有0和1两种状态,0代表数据地址位为8位,即仅将byte_addr[15:0]低8位写入数据地址位,反之16位数据都需要写入

9.i2c_scl:这是SCL端口,是输出信号

10.i2c_sda:这是SDA端口,是输入输出信号,定义成wire类型

11.i2c_clk:是工作时钟,由本模块产生与SCL呈现4倍的关系,也是第一幅图中的SYS_CLK

12.i2c_end:是结束的标志信号

13.rd_data[7:0]:这个是将SDA读取的串行信号转换成8位并行信号

IIC控制模块单字节写时序图:

这里按照状态将时序图整体分为4个部分,在每一部分都会进行详细的说明

1.空闲状态、开始1状态、写器件地址状态和应答状态

 看到那么多的信号,不要畏惧仔细分析,也很有意思,这里绿色和红色的信号是之前的输入和输出信号,无需赘述,黄色的信号就尤为重要。因为本次使用的是有状态机的方法写的IIC控制模块,而状态的转移需要有个依据,这里利用两个计时器来实现状态的转移(cnt_i2c_clk[1:0]和cnt_bit[2:0])这样,根据这两个中间变量可以实现状态的跳转,当然只有这两个变量还是不够的,这两个变量仅仅是状态跳转的前提,后面具体跳到哪一个状态还需要根据scl信号判断。这两个计数器产生也是有依据的这两个信号是以i2c_clk为基准进行计数的,这里不要惊讶为什么,之所以这个是输出信号因为别的模块也会用到这个信号,而且由于系统时钟为50MHz比较大这里就以SCL时钟信号的4倍进行分屏,之所以选择4倍而不选择其他的倍数可以仔细思考一下。我们本次IIC选用250kbit/s进行数据的传输,进行一个50倍的分频实现1MHz的i2c_clk,这个i2c_clk的翻转也是需要一个计数器cnt_clk[6:0]这个50倍分频可以当cnt_clk以25个系统时钟上升沿到来是让i2c_clk进行一次电平的翻转就可以实现了。正是因为工作时钟i2c_clk频率是每比特数据传输的4倍关系,因此在cnt_i2c_clk完成4个数的计数的同时SCL就完成一个周期的变化。然而在不同的状态下需要cnt_i2c_clk完成4个数的计数的个数不同,这里就不同状态下cnt_i2c_clk完成4个数的计数进行罗列,IDLE空闲状态,此时不进行计数;START_1开始1状态,此时需要一个,即cnt_bit[3:0]完成0的计数即可以转移;SEND_D_ADDR发送器件地址状态,此时需要完成8个,因为是7个地址位外加1个读写控制位,即cnt_bit[3:0]完成01234567的计数即可以转移;ACK_1应答1状态,此时需要一个,即cnt_bit[2:0]完成0的计数即可以转移。这里的罗列的四个状态下,开始说一下sda和scl的产生。在空闲状态下,两个信号都为高电平,当i2c_start到来时,状态开始跳出空闲状态进入开始1状态,而sda和scl的电平是变高还是变低就受cnt_i2c_clk影响,这里在cnt_i2c_clk在0时sda为高电平,cnt_i2c_clk为123时,sda为低电平;在cnt_i2c_clk为012时scl为高电平,cnt_i2c_clk为3时,scl为低电平,因为这个sda拉低要比scl拉低早半个bit周期。这样开始状态的信号总线数据便确定了,接下来是发送器件地址信号+读写控制位,此时scl就只管周期性的变化就行了,主要考虑如何设计sda信号,这里IIC总线根据的先发送器件地址位最高位之前设计的cnt_bit[2:0]可以用到了将器件地址参数D_ADDR[6-cnt_bit]传递给sda就行了,最后一位读写控制位置0,进行写的操作。注意在对sda和scl进行赋值的时候是没有考虑延迟一拍的,因此设计的时候应将sda和scl设置成线网类型,并且利用组合逻辑对这两个变量进行赋值,还有就是sda要注意是一个输入输出类型的变量,而scl只是输出类型变量。完成器件地址+读写控制位的赋值接下来就可以进入第一个应答状态了。在这个状态scl还是按照原有的方式进行周期性变化,可以说知道stop状态不会按照周期性变化,后面就不再单独讲这个scl了,而sda作为输入信号,由主机接收从机的应答,如果有设备此时发出了低电平,表明主机可以和从机进行后续通讯,而这毕竟是主机的一个端口啊,怎样设置这个变量呢,此时主机会使这个状态下的sda输出高阻态,虽然在时序图上画成了绿色的低电平这个表明sda上想要一个低电平的输入,此时就开始引出了4个比较重要的信号线ack,i2c_sda_reg,sda_in,sda_en。ack应答信号,只有在应答状态下才会处于低电平,其他状态都是高电平。i2c_sda_reg就是我们想要输出的sda数据,sda_in是从总线sda中接收到的所有信号,sda_en这个信号用于判断什么时候将i2c_sda_reg赋值给sda,只有在从机应答和从机发送传输数据时为低电平。这几个信号的组合可以实现在sda进行发送数据和接收数据以备后续状态转移的判断。其中时序图也可以看到只有scl处于高电平的时候,sda数据是保持稳定不变的。 

2.写字节地址高8位,应答状态2,写字节地址低8位,应答状态3

在结束了应答状态1后就开始发送从设备的字节地址,有的设备地址是8位因此通过输入变量addr_num==0,直接由应答状态1跳到写字节地址低8位,最后在应答状态3

3.写数据状态、应答状态4和停止状态

 将字节地址写完后,状态机会根据输入信号读使能rd_en和写使能wr_en判断是进入读操作还是写操作,如果读使能有效则跳入开始2状态,如果是写使能有效会跳转到写数据状态,这里是写有效,因此,在i2c_sda_reg会由高到底将需要发送的8位数据逐个赋值给sda。完成后,进入应答状态,最后应答完毕进入停止状态。在这个状态当scl在cnt_bit=0,cnt_i2c_clk=0为低电平外其他时刻都为高电平,sda在cnt_bit为123时为高电平。这里有一个结束标志信号比较重要,i2c_end在跳出结束状态后会一个工作时钟周期的高电平。

IIC控制模块单字节读时序图:

1.空闲状态、开始1状态、写器件地址状态和应答状态

2.写字节地址高8位,应答状态2,写字节地址低8位,应答状态3

上面两个状态和单字节写时序图一摸一样。

3.开始2状态、写器件地址状态和应答状态

这个其实只是开始状态的名称不一样,因为在应答1结束后会根据读使能信号进行判断是否进入开始2状态。写器件地址还是一样,应答5也没什么区别

4.读数据状态,无应答状态,停止状态

 读数据状态此时sda为输入信号,主机将sda赋值为高阻态,进行数据的接收,sda_en为低电平,sda收集的数据传递给sda_in,进而sda_in将每次读到数据逐位赋值给rd_data_reg[7:0],这样就收集到了8bit位宽的数据,在读数据状态结束的时候将读到的数据以输出的方式发给rd_data[7:0]寄存器。此时进入无应答响应状态,这时主机在sda发送高电平数据告诉从机我已经接收到你发的数据了。这样比便可以进入停止状态和写数据一样,则读写时序完毕,可以开始写代码了。

IIC控制模块代码:

`timescale  1ns/1ns
module i2c_ctrl_wf
#(
	parameter DEVICE_ADDR = 7'b101_0011,
	parameter SYS_CLK     = 50_000_000,
	parameter SCL_FREQ    = 250_000	
)
(
	input              sys_clk       ,
	input              sys_rst_n     ,	
	input              i2c_start     ,	
	input              wr_en         ,  
	input              rd_en         ,
	input      [15:0]  byte_addr     ,
	input      [7:0]   wr_data       ,
	input              addr_num      , //用于判断字节地址是16位还是8位
 	
	output reg		   i2c_clk       ,			   
	output reg [7:0]   rd_data       ,			   	
	output reg		   i2c_end       ,			   
	output reg		   scl           ,
	inout			   sda	         ,
	
	output reg        sda_en         ,
	output reg        i2c_sda_reg    ,
	output reg [7:0]  rd_data_reg     
	
);

//---完成参数的定义-------//
reg [7:0]  cnt_clk;
reg        cnt_i2c_clk_en;
reg [1:0]  cnt_i2c_clk;
reg [2:0]  cnt_bit;
reg        ack;     
 
wire       sda_in;

reg [3:0]  state;
//---16种状态定义--------//
parameter	IDLE                 =      4'd0;
parameter	START_1              =      4'd1;
parameter	SEND_D_ADDR          =      4'd2;
parameter	ACK_1                =      4'd3;
parameter	SEND_BYTE_ADDR_H     =      4'd4;
parameter   ACK_2                =      4'd5;
parameter	SEND_BYTE_ADDR_L     =      4'd6;
parameter	ACK_3                =      4'd7;
parameter	WR_DATA              =      4'd8;
parameter	ACK_4                =      4'd9;
parameter	START_2              =      4'd10;
parameter	SEND_RD_ADDR         =      4'd11;
parameter	ACK_5                =      4'd12;
parameter	RD_DATA              =      4'd13;
parameter	N_ACK                =      4'd14;
parameter	STOP                 =      4'd15;

//cnt_clk 的计数最大值定义
parameter CNT_CLK_MAX = SYS_CLK / SCL_FREQ >> 2'd3;

//-----cnt_clk------//这个信号是一直在工作的
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_clk <= 1'b0;
	else if(cnt_clk == CNT_CLK_MAX - 1'd1)
		cnt_clk <= 1'b0;
	else
		cnt_clk <= cnt_clk + 1'b1;
//----i2c_clk----//工作时钟		
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		i2c_clk <= 1'b0;
	else if(cnt_clk == CNT_CLK_MAX - 1'd1)
		i2c_clk <= ~i2c_clk;	

//---cnt_i2c_clk_en---//这个信号触发cnt_i2c_clk开始工作
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_i2c_clk_en <= 1'b0;
	else if(state != IDLE)
		cnt_i2c_clk_en <= 1'b1;
	else
		cnt_i2c_clk_en <= 1'b0;	
//---cnt_i2c_clk-----//进行0到3循环计数,正好可以将scl的完整周期分为4份
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_i2c_clk <= 2'd0;
	else if(cnt_i2c_clk_en == 1'b1)
		cnt_i2c_clk <= cnt_i2c_clk + 1'b1;
//----cnt_bit-----//这个在不同的状态下,具有不同的bit计数
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_bit <= 1'b0;
	else if(state == IDLE || state == START_1 || state == START_2 || state == ACK_1 || state == ACK_2 ||
			state == ACK_3 || state == ACK_4 || state == ACK_5 || state == N_ACK)
		cnt_bit <= 1'b0;	
	else if(cnt_i2c_clk == 2'd3)//尽管STOP只需要跳转到3,但是最后可以通过由STOP状态跳转至IDLE状态进行置零
		cnt_bit <= cnt_bit + 1'b1;
	
		
//-----状态的转移----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		state = IDLE;
	else begin
			case(state)
				IDLE:	if(i2c_start == 1'b1)
							state <= START_1;
						else
							state <= state;
							
				START_1:if(cnt_i2c_clk == 2'd3)
							state <= SEND_D_ADDR;
						else
							state <= state;
							
				SEND_D_ADDR:if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd7)
								state <= ACK_1;
							else
								state <= state;
								
				ACK_1:	if(cnt_i2c_clk == 2'd3 && addr_num == 1'b1)
							state <= SEND_BYTE_ADDR_H;
						else if(cnt_i2c_clk == 2'd3 && addr_num == 1'b0)
							state <= SEND_BYTE_ADDR_L;		
						else
							state <= state;	
							
				SEND_BYTE_ADDR_H:if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd7)
										state <= ACK_2;
									else
										state <= state;
										
				ACK_2:	if(cnt_i2c_clk == 2'd3)
							state <= SEND_BYTE_ADDR_L;	
						else
							state <= state;	
							
				SEND_BYTE_ADDR_L:if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd7)
										state <= ACK_3;
									else
										state <= state;			
				
				ACK_3:	if(cnt_i2c_clk == 2'd3 && wr_en == 1'b1)
							state <= WR_DATA;	
						else if(cnt_i2c_clk == 2'd3 && rd_en == 1'b1)
							state <= START_2;
						else
							state <= state;

				WR_DATA:	if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd7)
								state <= ACK_4;	
							else
								state <= state;					
				
				ACK_4:	if(cnt_i2c_clk == 2'd3)
							state <= STOP;	
						else
							state <= state;	

				START_2:if(cnt_i2c_clk == 2'd3)
							state <= SEND_RD_ADDR;
						else
							state <= state;							

				SEND_RD_ADDR:if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd7)
								state <= ACK_5;
							else
								state <= state;
					
				ACK_5:	if(cnt_i2c_clk == 2'd3)
							state <= RD_DATA;	
						else
							state <= state;				
				
				RD_DATA:	if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd7)
								state <= N_ACK;	
							else
								state <= state;
								
				N_ACK:	if(cnt_i2c_clk == 2'd3)
							state <= STOP;	
						else
							state <= state;
							
				STOP:	if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd3)
							state <= IDLE;	
						else
							state <= state;
				default:state = IDLE;
			endcase
	end

//----中间变量ack,i2c_sda_reg,sda_in,sda_en,rd_data_reg---//


//---ack--只有在应答状态为低电平---//
always@(*)
	if(state == ACK_1 || state == ACK_2 || state == ACK_3 || state == ACK_4 || state == ACK_5 || state == N_ACK)
		ack <= 1'b0;
	else 
		ack <= 1'b1;

//----sda_en---在使能信号有效的情况下,将寄存在i2c_sda_reg中的变量可以发送给 sda//		
always@(*)
	if(state == START_1 || state == START_2 || state == SEND_D_ADDR || state == SEND_BYTE_ADDR_H || state == SEND_BYTE_ADDR_L || state == SEND_RD_ADDR ||state == WR_DATA || state == N_ACK || state == STOP)
		sda_en <= 1'b1;
	else 
		sda_en <= 1'b0;

//-----i2c_sda_reg ,rd_data_reg----在不同的状态下,会有不同的赋值情况//
always@(*)
	case(state)
		IDLE:	begin
					i2c_sda_reg <= 1'b1;
					rd_data_reg <= 8'd0;
				end			
		START_1:if(cnt_i2c_clk == 2'd0)
					i2c_sda_reg <= 1'b1;
				else
					i2c_sda_reg <= 1'b0;
					
		SEND_D_ADDR:if(cnt_bit <= 3'd6)
						i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
					else
						i2c_sda_reg <= 1'b0;
						
		ACK_1:i2c_sda_reg <= 1'b1;//感觉这里赋值高阻态要好一点,但影响不是很大	
					
		SEND_BYTE_ADDR_H:i2c_sda_reg <= byte_addr[15 - cnt_bit];
								
		ACK_2:i2c_sda_reg <= 1'b1;//
					
		SEND_BYTE_ADDR_L:i2c_sda_reg <= byte_addr[7 - cnt_bit];	
		
		ACK_3:i2c_sda_reg <= 1'b1;//
		
		WR_DATA:i2c_sda_reg <= wr_data[7 - cnt_bit];					
		
		ACK_4:i2c_sda_reg <= 1'b1;//
		
		START_2:if(cnt_i2c_clk == 2'd0 || cnt_i2c_clk == 2'd1)
					i2c_sda_reg <= 1'b1;
				else
					i2c_sda_reg <= 1'b0;						

		SEND_RD_ADDR:if(cnt_bit <= 3'd6)
						i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
					else
						i2c_sda_reg <= 1'b1;
			
		ACK_5:i2c_sda_reg <= 1'b1;//			
		
		RD_DATA:begin
					i2c_sda_reg <= 1'b1;//
					if(scl == 1'b1)
						rd_data_reg[7 - cnt_bit] <= sda_in;
				end	
						
		N_ACK:i2c_sda_reg <= 1'b1;	
					
		STOP:	if(cnt_bit == 3'd0)
					i2c_sda_reg <= 1'b0;	
				else
					i2c_sda_reg <= 1'b1;
		default:i2c_sda_reg <= 1'b1;
	endcase

//----rd_data_reg---//
//always@(posedge scl or negedge sys_rst_n)
//	if(sys_rst_n == 1'b0)
//		rd_data_reg <= 8'd0;
//	else if(state == RD_DATA)	
//		rd_data_reg[7 - cnt_bit] <= sda_in;
//	else
//		rd_data_reg <=	rd_data_reg;
		
//---sda_in----//
assign sda_in = sda;
//--输出---//

//---scl---//
always@(*)
	if(state == IDLE)
		scl <= 1'b1;
	else if(state == STOP)	
		if(cnt_i2c_clk == 2'd0 && cnt_bit == 3'd0)
			scl <= 1'b0;
		else
			scl <= 1'b1;
	else if(state == START_1)	
		if(cnt_i2c_clk == 2'd3 && cnt_bit == 3'd0)
			scl <= 1'b0;
		else
			scl <= 1'b1;
	else
		if(cnt_i2c_clk == 2'd1 || cnt_i2c_clk == 2'd2)
			scl <= 1'b1;
		else
			scl <= 1'b0;
						
			
//---sda-----//
assign sda = sda_en ? i2c_sda_reg : 1'bz;

//---i2c_end---//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		i2c_end <= 1'b0;
	else if (state == STOP && cnt_i2c_clk == 2'd3 && cnt_bit == 3'd3)
		i2c_end <= 1'b1;
	else 
		i2c_end <= 1'b0;		

//---rd_data---//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		rd_data <= 8'd0;
	else if (state == N_ACK)
		rd_data <= rd_data_reg;
	else
		rd_data <= rd_data;	
		
endmodule

//--------2023----8---12---//

上个月完成了i2c控制模块,那么以后就可以直接使用这个i2c控制模块了

下面开始抄录i2c读写数据模块,本模块实现将需要读写的数据传递给i2c控制模块,同时也告诉i2c下载是进行读操作还是进行写操作。

进行变量的介绍:

 从左到右,从上到下依次介绍,左边是输入信号,右边是输出信号。sys_clk为系统时钟,用于后面捕捉按键滤波模块传来的信号;sys_clk_n复位信号;write写信号,由按键滤波信号传递过来的;read读信号,由按键滤波信号传递过来的;i2c_clk为此模块的工作信号,这里使用的是i2c控制模块产生的工作时钟;i2c_end用于给i2c控制模块告诉他,读写操作结束;rd_data[7:0]由i2c控制模块传来的读数据,进行fifo存储;fifo_rd_data[7:0]fifo里面读出的数据;i2c_start开始信号;wr_en写使能信号;rd_en读使能信号;byte_addr[15:0]传出地址信号;wr_data[7:0]需要写的数据。

接下来,进行波形图的介绍

这个部分是捕捉输入的读写信号,也就是跨时钟域处理的其中一个方案,将50Mhz系统时钟信号,能被工作时钟i2c_clk工作时钟捕捉到,这里引用4个时钟变量,write_valid,cnt_wr,read_valid,cnt_rd,目的让i2c_clk捕获到write_valid和read_valid。

 写数据模块,在write_valid被检测后,输出读使能信号,当需要读的数据全部读出,则读使能置零,这个通过wr_data_num[3:0]进行判断,cnt_start是根据eeprom芯片手册中数据写在5ms以内(5000*20*25/1000/1000=2.5ms),地址信号byte_addr[15:0]由数据读写模块内部进行顺序产生,写的数据也是,结束信号,由i2c控制模块产生。

一大半的信号与写模块的一样,不同的在于有一个内置fifo。

 当rd_data_num[3:0]为最大时,此时就可以进行数据的读出了,fifo_rd_en置位高电平,同时fifo_rd_data_num也开始进行计数,记到最大值,标志fifo中的数都读出了,fifo_rd_en置位低电平。

这样就完成了所有的时序,开始写代码。

module i2c_rw_data_wf
#(
parameter WR_RD_NUM = 4'd3,
parameter BYTE_ADDR = 16'h00_5A,
parameter WR_DATA   = 8'd8
)
(
	input 		        sys_clk			,
	input 		        sys_rst_n		,
	input 		        write			,
	input 		        read			,
	input 		        i2c_clk			,
	input 		        i2c_end			,
	input  [7:0]	    rd_data			,
                                        
	output [7:0]	    fifo_rd_data    ,
	output reg		    i2c_start		,
	output reg   	    wr_en			,
	output reg		    rd_en			,
	output reg  [15:0] 	byte_addr		,
	output reg  [7:0]   wr_data
);


//---完成参数的定义-------//
reg 		write_valid;
reg  		read_valid;
reg [7:0]   cnt_wr;
reg [7:0]   cnt_rd;
reg [12:0]  cnt_start;
reg [27:0]  cnt_wait;
reg [3:0]   wr_data_num;
reg [3:0]   rd_data_num;
reg 		fifo_rd_valid;
reg 		fifo_rd_en;
reg [3:0]   fifo_rd_data_num;
wire[3:0]   data_num;


parameter CNT_WR_RD_MAX = 8'd99;
parameter CNT_START_MAX = 13'd5000;//5000
parameter CNT_WAIT_MAX  = 28'd500_000;//500_000

//-----cnt_wr------//
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_wr <= 1'b0;
	else if (write_valid == 1'b1)
		cnt_wr <= cnt_wr + 1'b1;
	else if (write_valid == 1'b0)
		cnt_wr <= 8'd0;
//-----write_valid------//
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		write_valid <= 1'b0;
	else if (write == 1'b1)
		write_valid <= 1'b1;
	else if (cnt_wr == CNT_WR_RD_MAX)
		write_valid <= 1'b0;
		
//-----cnt_rd------//
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_rd <= 1'b0;
	else if (read_valid == 1'b1)
		cnt_rd <= cnt_rd + 1'b1;
	else if (read_valid == 1'b0)
		cnt_rd <= 8'd0;
//-----read_valid------//
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		read_valid <= 1'b0;
	else if (read == 1'b1)
		read_valid <= 1'b1;
	else if (cnt_rd == CNT_WR_RD_MAX)
		read_valid <= 1'b0;
		
//---wr_en----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		wr_en <= 1'b0;
	else if (write_valid == 1'b1)
		wr_en <= 1'b1;
	else if (i2c_end == 1'b1 && wr_data_num == WR_RD_NUM - 1)
		wr_en <= 1'b0;
//---rd_en----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		rd_en <= 1'b0;
	else if (read_valid == 1'b1)
		rd_en <= 1'b1;
	else if (i2c_end == 1'b1 && rd_data_num == WR_RD_NUM - 1)
		rd_en <= 1'b0;
//---cnt_start---//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_start <= 1'b0;
	else if (cnt_start == CNT_START_MAX - 1'b1)
		cnt_start <= 1'b0;
	else if (wr_en == 1'b1 || rd_en == 1'b1)
		cnt_start <= cnt_start + 1'b1;

//---i2c_start----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		i2c_start <= 1'b0;
	else if ((wr_en == 1'b1 || rd_en == 1'b1) && cnt_start == CNT_START_MAX - 1'b1)
		i2c_start <= 1'b1;
	else
		i2c_start <= 1'b0;
//---wr_data_num----//		
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		wr_data_num <= 4'd0;
	else if (wr_en == 1'b0)
		wr_data_num <= 4'd0;
	else if (wr_en == 1'b1 && i2c_end == 1'b1)
		wr_data_num <= wr_data_num + 1'b1;
//---byte_addr----//	
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		byte_addr <= BYTE_ADDR;
	else if (wr_en == 1'b0 && rd_en == 1'b0)
		byte_addr <= BYTE_ADDR;
	else if ((wr_en == 1'b1 || rd_en == 1'b1) && i2c_end == 1'b1)
		byte_addr <= byte_addr + 1'b1;
	
//---wr_data----//	
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		wr_data <= WR_DATA;
	else if (wr_en == 1'b0)
		wr_data <= WR_DATA;
	else if (wr_en == 1'b1 && i2c_end == 1'b1)
		wr_data <= wr_data + 1'b1;
//---rd_data_num---//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		rd_data_num <= 4'd0;
	else if (rd_en == 1'b0)
		rd_data_num <= 4'd0;
	else if (rd_en == 1'b1 && i2c_end == 1'b1)
		rd_data_num <= rd_data_num + 1'b1;
//---data_num---//
//always@(posedge i2c_clk or negedge sys_rst_n)
//	if(sys_rst_n == 1'b0)		
//		data_num <= 4'd0;
//	else if (rd_en == 1'b1 && i2c_end == 1'b1)
//		data_num <= data_num + 1'b1;

//----fifo_rd_valid----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		fifo_rd_valid <= 1'b0;
	else if (data_num == WR_RD_NUM)
		fifo_rd_valid <= 1'b1;
	else if (data_num == 4'd0 && cnt_wait == CNT_WAIT_MAX - 1'b1)
		fifo_rd_valid <= 1'b0;		
//----cnt_wait----//		
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		cnt_wait <= 1'b0;
	else if (cnt_wait == CNT_WAIT_MAX - 1'b1 || fifo_rd_valid == 1'b0)
		cnt_wait <= 16'd0;		
	else if (fifo_rd_valid == 1'b1)
		cnt_wait <= cnt_wait + 1'b1;		
//---fifo_rd_en-----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		fifo_rd_en <= 1'b0;
	else if (cnt_wait == CNT_WAIT_MAX - 1'b1 && (fifo_rd_data_num < WR_RD_NUM))
		fifo_rd_en <= 1'b1;		
	else
		fifo_rd_en <= 1'b0;	

//---fifo_rd_data_num----//
always@(posedge i2c_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)		
		fifo_rd_data_num <= 4'd0;
	else if (fifo_rd_valid == 1'b0)
		fifo_rd_data_num <= 4'd0;
	else if (fifo_rd_en == 1'b1 && fifo_rd_valid == 1'b1)
		fifo_rd_data_num <= fifo_rd_data_num + 1'b1;	
		
fifo_read	fifo_read_inst (
	.clock  (i2c_clk         ),
	.data   (rd_data         ),
	.rdreq  (fifo_rd_en      ),
	.wrreq  (rd_en && i2c_end),
	.q 		(fifo_rd_data    ),
	.usedw  (data_num        )
	);
	

endmodule

EEPROM 的读写_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值