基于FPGA的I2C协议实现

I²CInter-Integrated Circuit)即集成电路总线,它其实是I²C Bus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”("I-squared-C"),而“I二C”("I-two-C")则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。--------维基百科

基于FPGA的I2C协议实现_第1张图片 图1 I2C总线

 一、I^2C总线的概念

I^2C通过SCL和SDA在连接到总线的器件之间进行信息交流。每个器件都有一个唯一的地址识别(微处理器、lCD驱动器、存储器及键盘接口)。而且都可以作为一个发送器或接收器,由器件的功能决定。很明显 LCD驱动器只是一个接收器,而存储器则既可以接收又可以发送数据,除了发送器和接收器外器件在执行数据传输时也可以被看作是主机或从机 。见表 1主机是初始化总线的数据传输并产生允许传输的时钟信号的器件,此时任何被寻址的器件都被认为是从机。
1.1I^2C总线的特征

I^2C总线的两条线SCL和SDA都是双向线路,都通过一个电流源或上拉电阻连接到正的电源电压。实际使用中,通常将SCL定义为输出类型,SDA为输入输出类型。当总线空闲时,这两条线路都是高电平。I^2C总线上的数据传输速率在标准模式下可达100Kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s。连接到总线的接口数量只由总线电容400pF的限制决定。

 1.2 位传输

由于连接到I^2C总线的器件有不同种类的工艺(CMOS、NMOS、双极性),逻辑0和1的电平不是固定的,它由V_D_D的相关电平决定。每传输一个数据位就产生一个时钟脉冲。

1.2.1 数据的有效性

SDA线上的数据必须在时钟的高电平周期保持稳定。数据线的高或低电平状态只有在SCL线的时钟信号是低电平时才能改变。

 

基于FPGA的I2C协议实现_第2张图片 图2 IIC数据改变

 图2中给出了SDA上数据何时改变的示意图,由图可知,在SCL高电平期间,数据必须保持稳定;在SCL为低电平期间,才允许SDA上数据发生改变。

1.2.2 起始和停止条件

起始条件:在SCL为高电平器件,SDA由高电平跳变为低电平,此情况表示起始条件。

停止条件:在SCL为高电平期间,SDA由低电平跳变为高电平,此情况表示停止条件。

起始条件和停止条件一般由主机产生。起始条件和停止条件如图3所示:

基于FPGA的I2C协议实现_第3张图片 图3 起始条件和停止条件

 图3中S表示起始条件,此时,SCL为高电平,SDA由高电平跳变为低电平;P表示结束条件,此时,SCL为高电平,由低电平跳变为高电平。

1.2.3 数据发送过程

在检测到起始条件后,接下来可以进行数据的发送,发送到SDA线上的每个字节必须为8位。每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应信号。首先传输的是数据的最高位。

响应:在一个字节发送完毕后后面必须跟一个响应信号。想关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间,主机释放SDA线(此时SDA为输入类型),此时若从机发通过SDA数据线传入一个低电平,则从机响应;从机相应后可以继续进行数据的传输,若不在进行数据传输,此时只需要在从机响应后主机再产生一个停止信号即可。

基于FPGA的I2C协议实现_第4张图片 图4 IIC 总线的数据发送过程

图4中通过是I^2C 总线发送数据的过程,首先主控器产生起始信号(告诉从机主机要发送数据了),然后从机接收到起始信号后开始做好接收数据的准备,接下来主机就可以进行数据的发送,图4中首先发送的是被控器7位地址和一位读写选择信号,在主机发送完成后,主机会释放SDA总线(此时SDA由输出类型变为输入类型),同时从机在接收完数据信号后通过SDA线传输给主机一个应答信号ACK(SCL为高时,SDA为低),在主机检测到应答信号后主机继续发送下一个字节,待发送完成后主机释放SDA总线(SDA由输出变为输入类型),同时从机在接收完数据信号后通过SDA线传输给主机一个应答信号ACK(SCL为高时,SDA为低);从机应答后,若主机需要继续发送数据则重复上述过程;若主机不需要再进行数据的发送,主机需要发送一个停止信号告诉从机本轮数据发送完毕。

二、IIC总线数据发送的VERILOG描述

由图4可知,IIC发送数据的过程包括以下步骤:1、主控器发送开始信号—>2、主控器发送8位命令—>3、被控器应答—>4、主控器发送8位数据—>5、被控器应答—>6、主控器发送结束信号(如果还有数据要发送只需继续重复步骤4和5,待所有数据发送完后执行步骤6)。因此,IIC协议发送过程有明显的先后顺序关系,可以使用状态机来描述数据发送的整个过程。

对应的状态机如图5所示:

基于FPGA的I2C协议实现_第5张图片 图5 IIC 发送过程状态机描述

 

 在复位信号rst有效时进入IDLE状态,在IDLE状态,初始化SCL、SDA以及SDA_link(控制SDA是输入还是输出);IIC发送数据开始信号wr_en有效时,进入START状态,在该状态产生IIC发送数据的起始条件start(SCL为高时,SDA由高变为低);start有效时,进入ADDR0,在该状态,加载待发送的8位地址,并将最高位发送出去,i用来计数发送几个地址的第几位(由于SCL是i计数器的时钟的2分频,因此这里使用ADDR1和ADDR2这两个状态来完成数据的发送,ADDR1发送数据,ADDR2无操作);当8个地址发送完毕后,进入ACK1 状态,此状态,主机释放SDA,从机给出应答信号,并加载下一次待发送的数据;DATA0和DATA1用来实现8位数据信号的发送,DATA0状态对数据无操作,DATA1状态发送数据,当数据发送完毕后即i=16,进入ACK2状态,此时,主机释放SDA,从机给出应答;i=0时,进入STOP状态,该状态用于产生IIC发送数据的结束条件stop(SCL 为高时,SDA由低跳变为高);stop信号有效时进入DONE状态,表示数据发送完成,拉高done信号,然后进入IDLE状态,等待下一次数据的发送。

verilog描述如下所示:

I2C_MODULE:使用三段式状态机描述IIC数据发送过程,主要生成控制信号;同时例化实现过程中所需要的电路。

module I2C_MODULE(input clk_50m,
						input clk_in,/////用来同步SDA,使其在SCL低电平改变
						input rst_n,
						input wr_en,
						input [7:0]addr_in,
						input [7:0]data_in,
						output reg done,
						inout SDA,
						output SCL,
						output reg ack1,///////仿真信号
						output reg ack2////////仿真信号
    );
wire [4:0] i;
wire sda_r;
reg load_a;/////////SCl空闲时加载数据
reg en_a;/////////////产生SCL
reg load_b;///////加载待发送地址
reg en_b;///////地址移位寄存器使能
reg load_c;///////计数器加载数据
reg en_c;////////计数器计数
reg load_d;///////加载待发送数据
reg en_d;
reg SDA_link;//////1时SDA为输出,0时为输入
reg sad_load;///////sda_r空闲时加载数据
reg sda_en_addr;//////将地址赋值给sda_r
reg sda_en_data;//////将数据赋值给sda_r
reg start;
reg stop;
//reg ack1;////////应答信号给ack1
//reg ack2;///////应答信号给ack2
reg SDA_R = 'd0;
assign SDA = SDA_link?SDA_R:1'b0/*Z*/;///////实际应为z,为仿真方便设置为0
always @(posedge clk_in)
	SDA_R <= sda_r;
parameter [10:0]IDLE = 11'b000_0000_0001;
parameter [10:0]STRAT = 11'b000_0000_0010;
parameter [10:0]ADDR0 = 11'b000_0000_0100;////////加载待发送地址
parameter [10:0]ADDR1 = 11'b000_0000_1000;/////////发送地址
parameter [10:0]ADDR2 = 11'b000_0001_0000;/////////保持地址
parameter [10:0]ACK1 = 11'b000_0010_0000;
parameter [10:0]DATA0 = 11'b000_0100_0000;///////加载待发送数据
parameter [10:0]DATA1 = 11'b000_1000_0000;///////发送数据
parameter [10:0]ACK2 = 11'b001_0000_0000;///////应答
parameter [10:0]STOP = 11'b010_0000_0000;
parameter [10:0]DONE = 11'b100_0000_0000;
//parameter [11:0]DATA2 = 'b0001_0000_0000;///////保持数据
//parameter [11:0]ACK2 = 'b0010_0000_0000;
//parameter [11:0]STOP = 'b0100_0000_0000;
//parameter [11:0]DONE = 'b1000_0000_0000;
reg [10:0]current_state = 'd0;
reg [10:0]next_state = 'd0;
////////////次态转移
always @(posedge clk_50m or negedge rst_n)
	if(!rst_n)
		current_state <= IDLE;
	else
		current_state <= next_state;
////////////////状态跳变
always @(*)
	case(current_state)
		IDLE:	begin
			if(wr_en)
				next_state = STRAT;
			else
				next_state = IDLE;
		end
		STRAT:	begin
			if(start)
				next_state = ADDR0;
			else
				next_state = STRAT;
		end
		ADDR0:	begin
			if(i == 'd1)
				next_state = ADDR1;
			else
				next_state = ADDR0;
		end
		ADDR1:	begin
			if(i == 'd16)
				next_state = ACK1;
			else if(i[0] == 'b0)//////i为偶数
				next_state = ADDR2;
			else
				next_state = ADDR1;
		end
		ADDR2:	begin
			if(i[0] == 'b1)
				next_state = ADDR1;
			else
				next_state = ADDR2;
		end
		ACK1:	begin//////////////////////ack1应答,同时将待发送的数据加载进移位寄存器
			if(i == 'd0)
				next_state = DATA0;
			else
				next_state = ACK1;
		end
		DATA0:	begin	
			if(i[0] == 'd1)
				next_state = DATA1;
			else
				next_state = DATA0;
		end
		DATA1:	begin	
			if(i == 'd16)
				next_state = ACK2;		
			else if(i[0] == 'b0)
				next_state = DATA0;
			else
				next_state = DATA1;
		end
		ACK2:	begin
			if(i == 'd0)
				next_state = STOP;
			else
				next_state = ACK2;
		end
		STOP:	begin
			if(stop)
				next_state = DONE;
			else
				next_state = STOP;				
		end
		DONE:	begin
			if(done)
				next_state = IDLE;
			else
				next_state = DONE;
		end
		default:	begin
			next_state = IDLE;
		end
	endcase
//////////////////输出赋值
always @(*)
	case(current_state)
		IDLE:	begin
			load_a = 'd1;/////////SCl空闲时加载数据,初始化SCL
			en_a = 'd0;
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;
			load_c = 'd1;///////计数器加载数据
			en_c = 'd0;
			load_d = 'd0;///////加载待发送地址
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd1;///////sda_r空闲时加载数据,初始化SDA
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;
			ack2 = 'd0;
		end
		STRAT:	begin
			load_a = 'd1;/////////SCl空闲时加载数据
			en_a = 'd0;
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;
			load_c = 'd1;///////计数器加载数据
			en_c = 'd0;
			load_d = 'd0;///////加载待发送地址
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd1;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd1;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;
			ack2 = 'd0;
		end
		ADDR0:	begin
			load_a = 'd0;/////////SCl空闲时加载数据
			en_a = 'd1;
			load_b = 'd1;///////加载待发送地址
			en_b = 'd0;
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;
			load_d = 'd0;///////加载待发送地址
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd1;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;
			ack2 = 'd0;
		end
		ADDR1:	begin
			load_a = 'd0;/////////SCl空闲时加载数据
			en_a = 'd1;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd1;///////地址移位寄存器使能
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;////////计数器计数
			load_d = 'd0;///////加载待发送地址
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd1;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;
			ack2 = 'd0;
		end
		ADDR2:	begin
			load_a = 'd0;/////////SCl空闲时加载数据
			en_a = 'd1;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;////////计数器计数
			load_d = 'd0;///////加载待发送地址
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd1;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;
			ack2 = 'd0;
		end
		ACK1:	begin
			load_a = 'd0;/////////SCl空闲时加载数据
			en_a = 'd1;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;////////计数器计数
			load_d = 'd1;///////加载待发送数据
			en_d = 'd0;
			SDA_link = 'd0;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = !SDA;////////应答信号给ack1
			ack2 = 'd0;
		end
		DATA0:	begin
			load_a = 'd0;/////////SCl空闲时加载数据
			en_a = 'd1;/////////////产生SCL 
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;////////计数器计数
			load_d = 'd0;///////加载待发送数据
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd1;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;////////应答信号给ack1
			ack2 = 'd0;
		end
		DATA1:	begin
			load_a = 'd0;/////////SCl空闲时加载数据
			en_a = 'd1;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;////////计数器计数
			load_d = 'd0;///////加载待发送数据
			en_d = 'd1;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd1;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;////////应答信号给ack1
			ack2 = 'd0;
		end
		ACK2:	begin/////////////////////应答后由于之后产生stop信号,所以此时应该就让SCL处于空闲状态;
			load_a = 'd1;/////////SCl空闲时加载数据
			en_a = 'd0;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd0;///////计数器加载数据
			en_c = 'd1;////////计数器计数
			load_d = 'd0;///////加载待发送数据
			en_d = 'd0;
			SDA_link = 'd0;//////1时SDA为输出,0时为输入
			sad_load = 'd0;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd1;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;////////应答信号给ack1
			ack2 = !SDA;///////应答信号给ack2
		end
		STOP:	begin
			load_a = 'd1;/////////SCl空闲时加载数据
			en_a = 'd0;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd1;///////计数器加载数据
			en_c = 'd0;////////计数器计数
			load_d = 'd0;///////加载待发送数据
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd1;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd1;
			done = 'd0;
			ack1 = 'd0;////////应答信号给ack1
			ack2 = 'd0;///////应答信号给ack2				
		end
		DONE:	begin
			load_a = 'd1;/////////SCl空闲时加载数据
			en_a = 'd0;/////////////产生SCL
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;///////地址移位寄存器使能
			load_c = 'd1;///////计数器加载数据
			en_c = 'd0;////////计数器计数
			load_d = 'd0;///////加载待发送数据
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd1;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd1;
			ack1 = 'd0;////////应答信号给ack1
			ack2 = 'd0;///////应答信号给ack2
		end
		default:	begin
			load_a = 'd1;/////////SCl空闲时加载数据
			en_a = 'd0;
			load_b = 'd0;///////加载待发送地址
			en_b = 'd0;
			load_c = 'd1;///////计数器加载数据
			en_c = 'd0;
			load_d = 'd0;///////加载待发送地址
			en_d = 'd0;
			SDA_link = 'd1;//////1时SDA为输出,0时为输入
			sad_load = 'd1;///////sda_r空闲时加载数据
			sda_en_addr = 'd0;//////将地址赋值给sda_r
//			sda_en_ack1 = 'd0;//////ack1时给sda_r赋值
			sda_en_data = 'd0;//////将数据赋值给sda_r
//			sda_en_ack2 = 'd0;//////ack2时给sda_r赋值
			start = 'd0;
			stop = 'd0;
			done = 'd0;
			ack1 = 'd0;
			ack2 = 'd0;
		end
	endcase
// Instantiate the module
scl_generate scl_generate (
    .clk_50m(clk_50m), 
    .load_a(load_a), 
    .en_a(en_a), 
    .scl(SCL)
    );
// Instantiate the module
ad_left_shifter ad_left_shifter (
    .clk_50m(clk_50m), 
    .addr_in(addr_in), 
    .load_b(load_b), 
    .en_b(en_b),  
    .addr_o(addr_o)
    );
// Instantiate the module
count_num count_num (
    .clk_50m(clk_50m), 
    .load_c(load_c), 
    .en_c(en_c), 
    .count(i)
    );
// Instantiate the module
data_left_shifter data_left_shifter (
    .clk_50m(clk_50m), 
    .data_in(data_in), 
    .en_d(en_d), 
    .load_d(load_d), 
    .data_o(data_o)
    );
// Instantiate the module
SDA_strat_stop SDA_strat_stop (
    .clk_50m(clk_50m), 
    .start(start), 
    .stop(stop), 
    .edge_detect(edge_detect)
    );
// Instantiate the module
sdar_signal sdar_signal (
    .clk_50m(clk_50m), 
    .sad_load(sad_load), 
    .start(start), 
    .edge_detect(edge_detect), 
    .sda_en_addr(sda_en_addr), 
    .addr_o(addr_o), 
    .sda_en_data(sda_en_data), 
    .data_o(data_o), 
    .stop(stop), 
    .sda_r(sda_r)
    );
endmodule

 scl_generate:用于产生SCL

module scl_generate(input clk_50m,
							  input load_a,
							  input en_a,
							  output reg scl
    );
always @(posedge clk_50m)
	if(load_a)
		scl <= 'd1;
	else if(en_a)
		scl <= ~scl;
	else
		scl <= scl;

endmodule

 ad_left_shifter:用于将并行的8位地址转换成串行的一位一位的发送出去

module ad_left_shifter(input clk_50m,
							  input [7:0]addr_in,
							  input load_b,
							  input en_b,
							  output addr_o
    );
reg [7:0]addr_reg = 'd0;
always @(posedge clk_50m)
	if(load_b)
		addr_reg <= addr_in;
	else if(en_b=='b1)
		addr_reg <= {addr_reg[6:0],1'b0};
	else
		addr_reg <= addr_reg;
assign addr_o = addr_reg[7];
endmodule

 count_num:计数器,用于计数发送地址或者数据的个数

module count_num(input clk_50m,
					  input load_c,
					  input en_c,
					  output reg[4:0]count
    );
always @(posedge clk_50m)
	if(load_c)
		count <= 'd0;
	else if(en_c)	begin
		if(count == 'd17)
			count <= 'd0;
		else
			count <= count + 'd1;
	end
	else
		count <= count;
endmodule

 data_left_shifter:用于将并行的8位数据转换成串行的一位一位的发送出去

module data_left_shifter(input clk_50m,
								 input [7:0]data_in,
								 input en_d,
								 input load_d,
								 output data_o
    );
reg [7:0]data_reg = 'd0;
always @(posedge clk_50m)
	if(load_d)
		data_reg <= data_in;
	else if(en_d) 
		data_reg <= {data_reg[6:0],1'b0};
	else
		data_reg <= data_reg;
assign data_o = data_reg[7];
endmodule

 SDA_strat_stop:用于生成起始信号和结束信号

module SDA_strat_stop(input clk_50m,
							 input start,
							 input stop,
							 output reg edge_detect = 'd0
    );
always @(posedge clk_50m)
	case({stop,start})
		2'b01:	edge_detect <= 'd0;
		2'b10:	edge_detect <= 'd1;
		default:	edge_detect <= 'd0;
	endcase
endmodule

 sdar_signal:用于控制SDA发送的是地址信号、数据信号、开始信号还是结束信号

module sdar_signal(input clk_50m,
						 input sad_load,
						 input start,
						 input edge_detect,
						 input sda_en_addr,
						 input addr_o,
						 input sda_en_data,
						 input data_o,
						 input stop,
						 output reg sda_r
    );
always @(*)
	if(sad_load)
		sda_r <= 'd1;
	else	begin
		case({stop,sda_en_data,sda_en_addr,start})
			4'b0001:	sda_r <= edge_detect;
			4'b0010:	sda_r <= addr_o;
			4'b0100: sda_r <= data_o;
			4'b1000: sda_r <= edge_detect;
		default:	sda_r <= 'd1;
		endcase
	end

endmodule

 仿真激励文件:

module tb;

	// Inputs
	reg clk_50m;
	reg clk_in;
	reg rst_n;
	reg wr_en;
	reg [7:0] addr_in;
	reg [7:0] data_in;

	// Outputs
	wire done;
	wire SCL;
	wire ack1;
	wire ack2;

	// Bidirs
	wire SDA;

	// Instantiate the Unit Under Test (UUT)
	I2C_MODULE uut (
		.clk_50m(clk_50m),
		.clk_in(clk_in),
		.rst_n(rst_n), 
		.wr_en(wr_en), 
		.addr_in(addr_in), 
		.data_in(data_in), 
		.done(done), 
		.SDA(SDA), 
		.SCL(SCL), 
		.ack1(ack1), 
		.ack2(ack2)
	);

	initial begin
		// Initialize Inputs
		clk_50m = 0;
		clk_in = 0;
		rst_n = 0;
		wr_en = 0;
		addr_in = 0;
		data_in = 0;

		// Wait 100 ns for global reset to finish
		#100;
        
		// Add stimulus here

	end
   always #10 clk_50m = ~clk_50m;
	always #5 clk_in = ~clk_in;
///////////////////////////////////rst_n
reg [3:0] cnt = 'd0;  
always @(posedge clk_50m)
	if(cnt == 'd10)
		cnt <= 'd10;
	else
		cnt <= cnt + 'd1;
always @(posedge clk_50m)
	if(cnt=='d10)
		rst_n <= 'd1;
	else
		rst_n <= 'd0;
///////////////////////////////////////
reg[4:0]count = 'd0;
always @(posedge clk_50m)
	if(done)	
		count <= 'd0;
	else if(count == 'd30)
		count <= 'd30;
	else
		count <= count + 'd1;    
always @(posedge clk_50m)
	if(count == 'd29)	
		wr_en <= 'd1;
	else
		wr_en <= 'd0;
always @(posedge clk_50m)
	if(count == 'd30)		begin
		addr_in <= 8'b10101010;
		data_in <= 8'b10101010;
	end
	else	begin
		addr_in <= 'd0;
		data_in <= 'd0;
	end
		
//always @(posedge clk_50m)
//	if(ack1||ack2)
//		SDA <= 'd0;
//	else
//		SDA <= SDA;

endmodule

 仿真波形:

基于FPGA的I2C协议实现_第6张图片

 从图中可以看出:空闲时SCL和SDA均处于高电平;在SCL有效时,SDA由高变为低(开始信号)后开始进行数据的发送,数据只在SCL的低电平发生变化;在ack时,SCL为高时,SDA为低,表示从机发送应答信号,当数据发送完毕后,在SCL有效时,SDA由低变为高(结束信号),之后done信号拉高,表示IIC发送一次数据完毕。

 

你可能感兴趣的:(verilog,数字电路,FPGA)