IIC总线的FPGA实现

一、IIC总线介绍

IIC总线是两线式串行半双工总线,是一种多主机的总线,IIC总线由串行数据线(SDA)和串行时钟线(SCL)构成,总线的控制权归控制时钟线(SCL)的器件所有,尽管串行总线并没有并行总线的吞吐能力,但它们只要很少的配线和IC连接管脚,IIC总线可以使快速器件和慢速器件通讯,传输速率标准模式下可达100Kbit/s,在快速模式下可达400Kbit/s,在高速模式下可达3.4Mbit/s,连接到总线上的接口数量只由总线电容400pF的限制决定。

二、协议简介

1、开始信号和结束信号

开始信号:当时钟总线SCL为高电平时,数据线SDA由高电平向低电平跳变,表示数据传输开始;

结束信号:当SCL线为高电平时,SDA线从低电平向高电平跳变,表示数据传输结束。

IIC总线的FPGA实现_第1张图片

注意:开始和结束信号都是由主器件产生的,在开始信号产生以后,总线则处于忙状态,其它器件不能再产生开始信号,主器件产生结束信号以后,退出主器件角色,经过一段时间,总线被认为是空闲的,这是其它器件就可以再产生开始信号,另外,SDA线上的数据必须在时钟的高电平周期保持稳定 数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。IIC总线发送器送到SDA线上的每个字节必须为8位长,传送时高位在前,低位在后。

IIC总线的FPGA实现_第2张图片

 2、IIC的FPGA实现

  IIC总线有些应用可以没有应答信号,只需要开始信号和结束信号即可。故此处实现的总线没有应答信号。

本实验是通过串口将数据发送给FPGA,然后FPGA再将数据通过IIC总线传给STM32,然后STM32通过串口将接收到的数据在另一台电脑上打印,程序经过验证,此处只讲解FPGA的IIC的实现。

首先,看数据的RTL图,首先采用异步复位,同步释放的复位方式,然后是和电脑通讯的串口模块RX:UU2,然后数据传输的FIFO中,最后IIC:UU4模块读取FIFO中的数据,并将其发送出去。

IIC总线的FPGA实现_第3张图片

此处其他模块不再做详解,只介绍IIC模块,

IIC总线的FPGA实现_第4张图片

IIC模块由时钟产生模块IIC_CLK:UU5和数据控制模块IIC_DATA:UU6构成,其中IIC_DATA模块在检测到FIFO中有数据时,将通过使能信号iic_start_flag启动时钟产生模块IIC_CLK,时钟产生模块IIC_CLK产生时钟iic_clk和iic_clk_dvi,其中iic_clk主要用于外部和内部时候在那个同步,iic_clk_dvi主要用于数据控制模块IIC_DATA当中将要发送的数据的变化。

IIC总线的FPGA实现_第5张图片

下面是数据发送仿真图:

IIC总线的FPGA实现_第6张图片

IIC总线的FPGA实现_第7张图片

附上IIC模块代码:

IIC_CLK模块

module IIC_CLK(
						clk,sys_rst_n,
						iic_clk,
						iic_start_flag,
						iic_clk_dvi,
//						iic_data_en
						
);
input clk;
input sys_rst_n;//系统复位信号
input iic_start_flag;//iic开始标志,高电平有效,且有效期间要维持高电平
output iic_clk_dvi;//记录iic时钟的3/4时钟节点,作为数据的变换标志,产生一个时钟的高电平,只是数据可以变化
//output iic_data_en;//表示已经开始数据传输

output iic_clk;

//parameter clk_freq = 20_000_000;//20MHz
parameter iic_freq = 50;//400Khz
parameter iic_data_change = 38;//数据变化时间节点

reg [7:0] iic_clk_r;
reg [7:0] iic_clk_dvi_r;
//reg iic_data_en_r;
//-------------------------------------------------
//iic的时钟技计数
always@(posedge clk or negedge sys_rst_n) begin
	if(!sys_rst_n)	begin
		iic_clk_r <= 8'b0;
	end 
	else if(iic_start_flag) begin
		if(iic_clk_r > iic_freq) iic_clk_r = 8'd0;
		else iic_clk_r <= iic_clk_r + 1'b1;	
	end
	else iic_clk_r = 8'd0;
end
//--------------------------------------------------
//数据变化节点的计数,数据传输开始标志
always@(posedge clk or negedge sys_rst_n) begin
	if(!sys_rst_n)begin
		iic_clk_dvi_r <= 8'b0;
	end
	else if(iic_start_flag) begin
		if(iic_clk_r > iic_data_change) iic_clk_dvi_r <= 8'b0;
		else  iic_clk_dvi_r <= iic_clk_dvi_r + 1'b1;
	end
	else iic_clk_dvi_r <= 8'b0;
end
//-------------------------------------------------
reg [3:0] i;
always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) i <= 4'd0;
	else if(iic_clk_r == iic_freq) i <= i + 1'b1;	
	else if(!iic_start_flag) i <= 4'd0;
//-------------------------------------------------
//数据传输开始标志
assign iic_clk = (iic_start_flag&&(i < 4'd9))?((iic_clk_r <= iic_freq/2)?1'b1:1'b0):1'b1;//当iic开始标志有效时,开始iic时钟翻转
assign iic_clk_dvi = (iic_start_flag)?((iic_clk_dvi_r == iic_data_change)?1'b1:1'b0):1'b0;//当iic开始标志有效时,开始iic数据变化计数

endmodule

 


IIC_DATA模块:

<span style="font-family:SimSun;"></span><pre name="code" class="csharp" style="font-size: 9pt;">module IIC_DATA(
						clk,
						sys_rst_n,						
						iic_clk,
						iic_clk_dvi,
//						iic_data_en,//高电平时,表示已经可以开始发送数据
						fifo_data,
						iic_data,
						iic_start_flag,
						rdusedw_sig,
						fifo_req
);
input clk;
input sys_rst_n;
input iic_clk;
input iic_clk_dvi;
//input iic_data_en;

input [7:0] fifo_data;//来自FIFO的数据
input [3:0]rdusedw_sig;//检测FIFO中的数据量
output fifo_req;

output iic_data;//数据的输出,数据是串行输出,并且高位首先输出;作为输入时接受应答信号
output iic_start_flag;//数据输出开始标志

reg [7:0] fifo_data_r;
reg fifo_req_r;
//----------------------------------------------------------------
reg iic_clk_r1;
reg iic_clk_r2;
wire iic_clk_x;
wire iic_clk_s;
//----------------------------------------------------------------------
reg iic_start_flag_r;
reg [3:0] rdusedw_sig_r;
//----------------------------------------------------------------------
reg iic_data_r1;
//reg [7:0] k;
//----------------------------------------------------------------
reg [3:0] i;
//-----------------------------------------------------------------

reg [3:0] j;
//---------------------------------------------------------------
reg iic_data_r2;

//-----------------------------------------------------------------
//读FIFO中的数据

always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) begin
		fifo_data_r <= 8'b0;
		fifo_req_r <= 1'b0;
	end
//	else if((rdusedw_sig !=4'd0)&&rec_ack)begin//当一个字节的数据发送完成之后再读取新的数据
	else if(rdusedw_sig !=4'd0)begin
		fifo_req_r <= 1'b1;
		fifo_data_r <= fifo_data;
	end
	else begin
		fifo_req_r <= 1'b0;
	end

//-----------------------------------------------------------------
//捕捉IIC时钟的下降沿和上升沿


always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n)begin
		iic_clk_r1 <= 1'b0;
		iic_clk_r2 <= 1'b0;
	end
	else begin
		iic_clk_r1 <= iic_clk;
		iic_clk_r2 <= iic_clk_r1;
	end

//----------------------------------------------------------------------
//当FIFO中的数据不为空时,且在数据传送标志有效时(即要在数据总线空闲时),开始标志产生
always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) begin
		rdusedw_sig_r <= 4'd0;
	end
	else 	rdusedw_sig_r = rdusedw_sig;
//-----------------------------------------------------------------------
//产生开始标志位,并启动数据传输
reg [8:0] k;
always @(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) k <= 9'd0;
	else if((k <= 9'd40)&&(j == 9)) k <= k + 1'b1;
	else k <= 9'd0;
	
always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) begin
		iic_start_flag_r <= 1'b0;
		iic_data_r1 <= 1'b1;
	end
	else if((j == 4'd9)&&(k == 9'd40))begin
		iic_start_flag_r = 1'b0;//结束标志的产生
		iic_data_r1 <= 1'b1;
	end
	else if(rdusedw_sig_r != 4'd0)begin
		iic_start_flag_r <= 1'b1;
		iic_data_r1 <= 1'b0;
	end
	else;
//----------------------------------------------------------------------
//IIC时钟下降沿计数,用于发送数据

always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) i <= 4'd0;
	else if(iic_clk_x) i <= i + 1'b1;//在时钟的下降沿进行计数处理
//	else if(i > 4'd9) i <= 4'd0;
	else if(!iic_start_flag) i <= 4'd0;
//----------------------------------------------------------------------
//IIC时钟上升沿计数,用于发送数据

always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) j <= 4'd0;
	else if(iic_clk_s) j <= j + 1'b1;//在时钟的下降沿进行计数处理
//	else if(j > 4'd10) j <= 4'd0;
	else if(!iic_start_flag) j <= 4'd0;
	else ;	
//-------------------------------------------------------------------------
//开始正式发送数据

always@(posedge clk or negedge sys_rst_n)
	if(!sys_rst_n) begin
		iic_data_r2 <= 1'b1;
	end
	else if(iic_clk_dvi == 1)begin
			case (i)
				4'd1:iic_data_r2 <= fifo_data_r[7];
				4'd2:iic_data_r2 <= fifo_data_r[6];
				4'd3:iic_data_r2 <= fifo_data_r[5];
				4'd4:iic_data_r2 <= fifo_data_r[4];
				4'd5:iic_data_r2 <= fifo_data_r[3];
				4'd6:iic_data_r2 <= fifo_data_r[2];
				4'd7:iic_data_r2 <= fifo_data_r[1];
				4'd8:iic_data_r2 <= fifo_data_r[0];
				4'd9:iic_data_r2 <= 1'b0;
			endcase
	end
	//下降沿和上升沿都要采集,并且要对比第九个,要求都为低电平,才有一次应答成功,此程序还没有完成结束动作
	else ;
//--------------------------------------------------------
assign fifo_req = fifo_req_r;
////--------------------------------------------------------
assign iic_clk_x = ~iic_clk_r1 & iic_clk_r2; // 捕捉IIC时钟的下降沿
assign iic_clk_s = iic_clk_r1 & ~iic_clk_r2; // 捕捉IIC时钟的上升沿
//--------------------------------------------------------
//assign 	rec_ack = rec_ack_r;
//--------------------------------------------------------

assign iic_start_flag = iic_start_flag_r;
assign iic_data = (iic_start_flag)?((i == 0)?iic_data_r1:iic_data_r2):1'b1;
endmodule
	
</pre>

你可能感兴趣的:(数据,FPGA,IIC)