LinCoding

【原创】详细解析基于FPGA的IIC程序

0
阅读(10275)

【主题】:详细解析基于FPGA的IIC程序

【作者】:LinCoding

【时间】:2016.12.04

【声明】:转载、引用,请注明出处

 今天把IIC搞定了,IIC可以说是太重要了,很多IC都是IIC协议驱动的,IIC就是两根线一根SCL,一根SDA,下面就介绍下IIC的Verilog实现。

先上图:

笔者是将Microchip公司的24LC04这个EEPROM作为IIC slave作为测试,在单字节读写模式下,向24LC04的8'h01寄存器写入8'hBB数据,然后再读出来送到串口做显示。可见下图,数据十分稳定。

blob.png

 下图为IIC的写时序,因为板子上没有引出EEPROM的引脚,因此无法用示波器抓取读时序,此外,在数据抖动时,其实是IIC master已经释放总线等待slave的ACK信号。

blob.png

(移植、修改自CrzayBingo的源码程序)

module iic_driver
#(
	parameter 		CLK_FREQ = 27'd100_000_000,	//100Mhz
	parameter		IIC_FREQ = 27'd1_000	        //100Khz ( < 400Khz )
)
(
	input			clk,		//global clock
	input			rst_n,		//global reset

	input		[1:0]	iic_cmd,	//read and write command, 10 write, 01 read
	input		[7:0]	iic_dev_addr,	//IIC device address
	input		[7:0]	iic_reg_addr,	//IIC register address
	input		[7:0]	iic_wr_data,	//IIC write data
	output	reg	[7:0]	iic_rd_data,	//IIC read data
	
	output			device_done,
	output	reg		iic_ack,

	output			iic_sclk,			//IIC SCLK
	inout			iic_sdat			//IIC SDA
);

 第一部分是输入输出定义,需要注意两点:

1、第一个parameter后需要有一个逗号,第二个parameter后什么也不需要。

2、iic_sdat就是SDA,因此为inout类型。

//-----------------------------------
//delay 1ms for IIC slave is steady
localparam	DELAY_TOP = CLK_FREQ/1000;	//delay 1ms
//localparam	DELAY_TOP = 100;	//just for simulation
reg	[16:0]	delay_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		delay_cnt	<= 17'd0;
	else if ( delay_cnt < DELAY_TOP )
		delay_cnt	<= delay_cnt + 1'b1;
	else
		delay_cnt	<= delay_cnt;
end
wire	delay_done	= ( delay_cnt == DELAY_TOP ) ? 1'b1 : 1'b0;
assign	device_done     = delay_done;

 第二部分是个计数器,由于一些IIC slave器件在上电后需要一段时间的准备时间,因此,这里定义了一个1ms的计数器,用于满足这些器件的准备时间。并且将delay_done赋值给device_done以表示器件已经准备就绪,可以开始读写操作了。

//-----------------------------------
//generate IIC control clock
reg	[16:0]	clk_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		clk_cnt		<= 17'd0;
	else if ( delay_done )
		begin
			if ( clk_cnt < ( CLK_FREQ / IIC_FREQ ) ) 
				clk_cnt		<= clk_cnt + 1'b1;
			else
				clk_cnt		<= 17'd1;
		end
	else
		clk_cnt			<= 17'd0;	
end

wire	iic_ctrl_clk	= ( ( clk_cnt >= ( CLK_FREQ/IIC_FREQ ) / 4 + 1'b1 ) && 			 ( clk_cnt <  ( ( CLK_FREQ/IIC_FREQ ) / 4 ) * 3 + 1'b1 )   ) ? 1'b1 : 1'b0;

wire	iic_transfer_en	= ( clk_cnt == 16'd1 ) ? 1'b1 : 1'b0;
wire	iic_capture_en	= ( clk_cnt == ( ( CLK_FREQ/IIC_FREQ ) / 4 ) * 2 ) ? 1'b1 : 1'b0;

 第三部分则是生成IIC所需要的时钟,还有读写使能信号,需要注意的是:

1、根据模块开头的两个paramter的定义可直接计算所需计数总数,然后生成所需要的时钟,IIC的时钟规定在100Khz至400Khz之间,太快肯定不行,太慢的话有些器件也不行(笔者测试EEPROM在10Khz时无法正常读写)

2、由于IIC协议规定,数据在高电平期间有效,所以如果是写IIC slave,必须在时钟低电平时将数据设置好,高电平时IIC slave会来读数据;如果是读数据,则必须在时钟高电平时读取数据。因此设定,iic_transger_en为低电平正中间,用于写II slave时作为设置数据的使能信号,iic_capture_en为高电平正中间,用于读IIC slave时的采样使能信号。

3、其实,这部分代码与——《详细解析74HC595驱动程序》这篇文章中生成74HC595时钟的那部分代码非常相似,这也就是告我我们,其实Verilog的程序只要掌握了固定的套路,其实并不难。

下图为上述代码所生成的波形:

blob.png

接下来就是一个标准的三段式FSM了,可参考——《详细解析基于三段式状态机的流水灯》、《详细解析基于FPGA的LCD1602驱动控制》:

//-----------------------------------
//FSM encode
localparam	IIC_IDLE	= 5'd0;
//IIC Write encode
localparam	IIC_WR_START	= 5'd1;
localparam	IIC_WR_IDADDR	= 5'd2;		//device address,write(0)
localparam	IIC_WR_ACK1	= 5'd3;
localparam	IIC_WR_REGADDR	= 5'd4;		//register address
localparam	IIC_WR_ACK2	= 5'd5;
localparam	IIC_WR_REGDATA	= 5'd6;		//register data
localparam	IIC_WR_ACK3	= 5'd7;
localparam	IIC_WR_STOP	= 5'd8;
//IIC Read encode
localparam	IIC_RD_START1	= 5'd9;
localparam	IIC_RD_IDADDR1	= 5'd10;	//device address,write(0)
localparam	IIC_RD_ACK1	= 5'd11;
localparam	IIC_RD_REGADDR	= 5'd12;	//register address
localparam	IIC_RD_ACK2	= 5'd13;
localparam	IIC_RD_STOP1	= 5'd14;
localparam	IIC_RD_IDLE	= 5'd15;
localparam	IIC_RD_START2	= 5'd16;	
localparam	IIC_RD_IDADDR2	= 5'd17;	//device address,read(1)
localparam	IIC_RD_ACK3	= 5'd18;
localparam	IIC_RD_REGDATA	= 5'd19;	//register data
localparam	IIC_RD_NACK	= 5'd20;
localparam	IIC_RD_STOP2	= 5'd21;

 第四部分是三段式FSM的状态编码,采用十进制编码方式。

//-----------------------------------
//FSM always 1
reg	[4:0] 	current_state;
reg	[4:0]	next_state;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		current_state	<= IIC_IDLE;
	else if ( iic_transfer_en )
		current_state	<= next_state;
	else
		current_state	<= current_state;
end

 第五部分,三段式FSM第一段,该说的在上文推荐的那两篇文章中已经说得非常详细了。

//-----------------------------------
//FSM always 2
reg	[3:0]	iic_stream_cnt;
always @ ( * )
begin
	next_state	= IIC_IDLE;
	case ( current_state )
		IIC_IDLE		:	//5'd0
			if ( iic_cmd == 2'b01 )		//01 read
				next_state	<= IIC_RD_START1;	//5'd9
			else if ( iic_cmd == 2'b10 )    //10 write
				next_state	<= IIC_WR_START;	//5'd1
			else
				next_state	<= IIC_IDLE;
		//IIC Write: { Device_Address(Write) + Register_Address + Write_Data }
		IIC_WR_START	:	//5'd1
			if ( iic_transfer_en )	
				next_state	<= IIC_WR_IDADDR;
			else					
				next_state	<= IIC_WR_START;
		IIC_WR_IDADDR	:	//5'd2
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_WR_ACK1;
			else					
				next_state	<= IIC_WR_IDADDR;		
		IIC_WR_ACK1		:	//5'd3
			if ( iic_transfer_en )	
				next_state	<= IIC_WR_REGADDR;
			else					
				next_state	<= IIC_WR_ACK1;		
		IIC_WR_REGADDR	:	//5'd4
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_WR_ACK2;
			else					
				next_state	<= IIC_WR_REGADDR;			
		IIC_WR_ACK2		:	//5'd5
			if ( iic_transfer_en )	
				next_state	<= IIC_WR_REGDATA;
			else					
				next_state	<= IIC_WR_ACK2;		
		IIC_WR_REGDATA	:	//5'd6
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_WR_ACK3;
			else					
				next_state	<= IIC_WR_REGDATA;	
		IIC_WR_ACK3		:	//5'd7
			if ( iic_transfer_en )	
				next_state	<= IIC_WR_STOP;
			else					
				next_state	<= IIC_WR_ACK3;			
		IIC_WR_STOP		:	//5'd8
			if ( iic_transfer_en )	
				next_state	<= IIC_IDLE;
			else					
				next_state	<= IIC_WR_STOP;	
	//IIC Read: { Device_Address(Write) + Regis_Address + Device_Address(Read) + Read_Data }				
		IIC_RD_START1	:	//5'd8
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_IDADDR1;
			else					
				next_state	<= IIC_RD_START1;		
		IIC_RD_IDADDR1	:
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_RD_ACK1;
			else					
				next_state	<= IIC_RD_IDADDR1;	
		IIC_RD_ACK1		:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_REGADDR;
			else					
				next_state	<= IIC_RD_ACK1;		
		IIC_RD_REGADDR	:
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_RD_ACK2;
			else					
				next_state	<= IIC_RD_REGADDR;			
		IIC_RD_ACK2		:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_STOP1;
			else					
				next_state	<= IIC_RD_ACK2;	
		IIC_RD_STOP1	:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_IDLE;
			else					
				next_state	<= IIC_RD_STOP1;		
		IIC_RD_IDLE		:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_START2;
			else					
				next_state	<= IIC_RD_IDLE;	
		IIC_RD_START2	:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_IDADDR2;
			else					
				next_state	<= IIC_RD_START2;	
		IIC_RD_IDADDR2	:
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_RD_ACK3;
			else					
				next_state	<= IIC_RD_IDADDR2;	
		IIC_RD_ACK3		:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_REGDATA;
			else					
				next_state	<= IIC_RD_ACK3;			
		IIC_RD_REGDATA	:
			if ( iic_transfer_en && iic_stream_cnt == 4'd8 )	
				next_state	<= IIC_RD_NACK;
			else					
				next_state	<= IIC_RD_REGDATA;	
		IIC_RD_NACK		:
			if ( iic_transfer_en )	
				next_state	<= IIC_RD_STOP2;
			else					
				next_state	<= IIC_RD_NACK;	
		IIC_RD_STOP2	:
			if ( iic_transfer_en )	
				next_state	<= IIC_IDLE;
			else					
				next_state	<= IIC_RD_STOP2;	
		default:
			next_state	<= IIC_IDLE;
	endcase
end

   第六部分,三段式FSM第二段,别看这么多,都是重复的内容罢了,需要注意三点:

1、在IIC_IDLE态,会根据输入的命令来判定,执行相应的读或者写操作。

2、iic_stream_cnt是计数当前发送了几位数据的计数器。

3、所有的信号转移状态都以iic_transfer_en为使能信号。

//-----------------------------------
//FSM always 3
reg		iic_sdat_out;		
reg	[7:0]	iic_wdata;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		begin			
			iic_sdat_out	<= 1'b1;
			iic_stream_cnt	<= 4'd0;
			iic_wdata	<= 8'd0;
		end
	else if ( iic_transfer_en )
		case ( next_state )
			IIC_IDLE		:	//5'd0
				begin
					iic_sdat_out 	<= 1'b1;
					iic_stream_cnt 	<= 4'd0;
					iic_wdata 	<= 8'd0;
				end
			//IIC Write: {Device_Address + REG_Address + Write_Data}
			IIC_WR_START	:	//5'd1
				begin
					iic_sdat_out 	<= 1'b0;
					iic_stream_cnt 	<= 4'd0;
					iic_wdata 	<= iic_dev_addr;
				end
			IIC_WR_IDADDR	:	//5'd2
				begin
					iic_stream_cnt	<= iic_stream_cnt + 1'b1;
					iic_sdat_out	<= iic_wdata[3'd7 - iic_stream_cnt];
				end
			IIC_WR_ACK1		:	//5'd3
				begin
					iic_stream_cnt	<= 4'd0;
					iic_wdata 	<= iic_reg_addr;
				end
			IIC_WR_REGADDR	:	//5'd4
				begin
					iic_stream_cnt	<= iic_stream_cnt + 1'b1;
					iic_sdat_out	<= iic_wdata[3'd7 - iic_stream_cnt];
				end
			IIC_WR_ACK2		:	//5'd5
				begin
					iic_stream_cnt	<= 4'd0;
					iic_wdata 	<= iic_wr_data;
				end
			IIC_WR_REGDATA	:	//5'd6
				begin
					iic_stream_cnt	<= iic_stream_cnt + 1'b1;
					iic_sdat_out	<= iic_wdata[3'd7 - iic_stream_cnt];
				end
			IIC_WR_ACK3		:	//5'd7
				begin
					iic_stream_cnt	<= 4'd0;
				end
			IIC_WR_STOP		:	//5'd8
				begin
					iic_sdat_out	<= 1'b0;
				end
		//IIC Read: {Device_Address + REG_Address} + {Device_Address + R_REG_Data}
			IIC_RD_START1	:	//5'd9
				begin
					iic_sdat_out 	<= 1'b0;
					iic_stream_cnt 	<= 4'd0;
					iic_wdata 	<= iic_dev_addr;
				end
			IIC_RD_IDADDR1	:	//5'd10
				begin
					iic_stream_cnt	<= iic_stream_cnt + 1'b1;
					iic_sdat_out	<= iic_wdata[3'd7 - iic_stream_cnt];
				end
			IIC_RD_ACK1		:	//5'd11
				begin
					iic_stream_cnt	<= 4'd0;
					iic_wdata 	<= iic_reg_addr;
				end
			IIC_RD_REGADDR	:	//5'd12
				begin
					iic_stream_cnt	<= iic_stream_cnt + 1'b1;
					iic_sdat_out	<= iic_wdata[3'd7 - iic_stream_cnt];
				end
			IIC_RD_ACK2		:	//5'd13
				begin
					iic_stream_cnt	<= 4'd0;
				end
			IIC_RD_STOP1	:	//5'd14
				begin
					iic_sdat_out	<= 1'b0;
				end
			IIC_RD_IDLE		:	//5'd15
				begin
					iic_sdat_out	<= 1'b1;
				end
			IIC_RD_START2	:	//5'd16
				begin
					iic_sdat_out	<= 1'b0;
					iic_stream_cnt	<= 4'd0;
					iic_wdata	<= iic_dev_addr | 8'h01;
				end
			IIC_RD_IDADDR2	:	//5'd17
				begin
					iic_stream_cnt 	<= iic_stream_cnt + 1'b1;
					iic_sdat_out 	<= iic_wdata[3'd7 - iic_stream_cnt];
				end
			IIC_RD_ACK3		:	//5'd18
				begin
					iic_stream_cnt 	<= 4'd0;
				end
			IIC_RD_REGDATA	:	//5'd19
				begin
					iic_stream_cnt  <= iic_stream_cnt + 1'b1;
				end
			IIC_RD_NACK		:	//5'd20
				begin
					iic_sdat_out    <= 1'b1;	//NACK
				end
			IIC_RD_STOP2	:	//5'd21
				begin
					iic_sdat_out    <= 1'b0;
				end
			default:
				begin		
					iic_sdat_out	<= 1'b1;
					iic_stream_cnt	<= 4'd0;
					iic_wdata	<= 8'd0;			
				end
		endcase
	else
		begin
			iic_stream_cnt     <= iic_stream_cnt;
			iic_sdat_out       <= iic_sdat_out;
		end
end

   第七部分,三段式FSM第三段的part1,需要注意的是:在IIC_RD_START2态,需要将iic_dev_addr | 8'h01,以将读写方向改成读。其他没什么可注意的,很简单。

//---------------------------------------------
//respone from slave for iic data transfer
reg		iic_ack1;
reg		iic_ack2;
reg		iic_ack3;
always @ ( posedge clk or negedge rst_n )
begin
	if( ! rst_n )
		begin
			iic_ack1	<= 1'b1; 
			iic_ack2 	<= 1'b1;
			iic_ack3	<= 1'b1;
			iic_ack 	<= 1'b1;
			iic_rd_data	<= 8'd0;
		end
	else if( iic_capture_en )
		case ( next_state )
			IIC_IDLE:
				begin
					iic_ack1	<= 1'b1; 
					iic_ack2 	<= 1'b1;
					iic_ack3	<= 1'b1;
					iic_ack 	<= 1'b1;
				end
			//Write IIC: {ID_Address, REG_Address, W_REG_Data}
			IIC_WR_ACK1	:	iic_ack1 <= iic_sdat;
			IIC_WR_ACK2	:	iic_ack2 <= iic_sdat;
			IIC_WR_ACK3	:	iic_ack3 <= iic_sdat;
			IIC_WR_STOP	:	iic_ack  <= ( iic_ack1 | iic_ack2 | iic_ack3 );
			//IIC Read: {ID_Address + REG_Address} + {ID_Address + R_REG_Data}
			IIC_RD_ACK1	:	iic_ack1 <= iic_sdat;
			IIC_RD_ACK2	:       iic_ack2 <= iic_sdat;
			IIC_RD_ACK3	:       iic_ack3 <= iic_sdat;
			IIC_RD_STOP2	:	iic_ack  <= ( iic_ack1 | iic_ack2 | iic_ack3 );
			IIC_RD_REGDATA	:	iic_rd_data<= { iic_rd_data[6:0], iic_sdat };
		endcase
	else
		begin
			iic_ack1	<= iic_ack1; 
			iic_ack2 	<= iic_ack2;
			iic_ack3	<= iic_ack3;
			iic_ack		<= iic_ack;
		end
end

 第八部分,三段式FSM第三段的part2,这部分主要是用来处理ACK信号,那么问题来了,为什么不把第三段的part1和part2合并起来呢?

原因是part1使能信号是iic_transfer_en,而part2都是用来处理“读”信号,因此使能信号为iic_capture_en,两者的使能信号不一样,必须分开来写;其次,将ACK信号单独写在一个always中更加利于维护,便于阅读。

//-----------------------------------
//IIC signal
wire		read_en	 = ( current_state == IIC_WR_ACK1 ||
			    current_state == IIC_WR_ACK2 ||
			    current_state == IIC_WR_ACK3 ||
			    current_state == IIC_RD_ACK1 ||
			    current_state == IIC_RD_ACK2 ||
			    current_state == IIC_RD_ACK3 ||
			    current_state == IIC_RD_REGDATA ) ? 1'b1 : 1'b0;   //release data bus
						 
assign		iic_sclk = ( current_state >= IIC_WR_IDADDR  && current_state <= IIC_WR_ACK3 ||						                             current_state >= IIC_RD_IDADDR1 && current_state <= IIC_RD_ACK2 ||					                             current_state >= IIC_RD_IDADDR2 && current_state <= IIC_RD_NACK  )
                             ? iic_ctrl_clk : 1'b1;
                             
assign 		iic_sdat = ( ~ read_en ) ? iic_sdat_out : 1'bz;

第九部分,就是输出sclk和sdat信号了,由于sdat为inout类型,也就是三态门,因此必须指定一个使能信号,来确定sdat何时为输入,何时为输出——详细参见《LinCoding告诉您什么才是IO口》这篇文章。

总之就是,在需要读取时,选择sdat为读入状态,在需要写入时,选择sdat为输出状态。

--------------------------------------------------------------------------------------

这样的话IIC的驱动程序就写完了,由于使用EEPROM作为IIC的测试slave,因此还需写EEPROM的程序。

module eeprom_test
(
	input			clk,		//global clock
	input			rst_n,		//global reset

	output	reg	[1:0]	iic_cmd,	//read and write command, 10 write, 01 read
	output	reg	[7:0]	iic_dev_addr,	//IIC device address
	output	reg	[7:0]	iic_reg_addr,	//IIC register address
	output	reg	[7:0]	iic_wr_data,	//IIC write data
	input		[7:0]	iic_rd_data,	//IIC read data

	input			device_done,
	input			iic_ack,
	
	output	reg	[7:0]	rxd_data,
	output	reg		rxd_flag
);
	
localparam	CLK_FREQ	= 27'd100_000_000;	//100Mhz
localparam	IIC_FREQ	= 27'd1_000;		//100Khz ( < 400Khz )
reg	[16:0]	delay_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		delay_cnt	<= 17'd0;
	else if ( delay_cnt < ( CLK_FREQ / IIC_FREQ ) ) 
		delay_cnt	<= delay_cnt + 1'b1;
	else
		delay_cnt	<= 17'd1;
end
wire	delay_done		= ( delay_cnt == ( CLK_FREQ / IIC_FREQ ) ) ? 1'b1 : 1'b0;

localparam	IDLE		= 3'd0;
localparam	WRITE		= 3'd1;
localparam	WAIT_WR_ACK	= 3'd2;
localparam	READ		= 3'd3;
localparam	WAIT_RD_ACK	= 3'd4;
localparam	STOP		= 3'd5;
reg	[2:0]	state;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		begin
			iic_cmd			<= 2'b00;
			iic_dev_addr	        <= 8'd0;
			iic_reg_addr	        <= 8'd0;
			iic_wr_data		<= 8'd0;
			state			<= IDLE;
			rxd_data		<= 8'd0;
			rxd_flag		<= 1'b0;
		end
	else if ( delay_done )
		case ( state )
			IDLE:
				if ( device_done )
					state	<= WRITE;
				else
					state	<= IDLE;
			WRITE:
				begin
					iic_cmd		<= 2'b10;	
					iic_dev_addr	<= 8'hA0;
					iic_reg_addr	<= 8'h01;
					iic_wr_data	<= 8'hBB;
					state		<= WAIT_WR_ACK;
				end
			WAIT_WR_ACK:
				if ( ! iic_ack )
					begin
						state		<= READ;
						iic_cmd		<= 2'b00;	
					end
				else
					begin
						iic_cmd		<= iic_cmd;	
						state		<= WAIT_WR_ACK;
					end
			READ:
				begin
					iic_cmd		        <= 2'b01;	
					iic_dev_addr	        <= 8'hA0;
					iic_reg_addr	        <= 8'h01;
					state		        <= WAIT_RD_ACK;
				end	
			WAIT_RD_ACK:
				if ( ! iic_ack )
					begin
						rxd_data	<= iic_rd_data;
						rxd_flag	<= 1'b1;
						iic_cmd		<= 2'b00;
						state		<= STOP;
					end
				else
					begin
						iic_cmd		<= iic_cmd;
						state		<= WAIT_RD_ACK;
					end		
			STOP:
				begin
					rxd_flag		<= 1'b0;
					state			<= STOP;
				end
			default:
				begin
					iic_dev_addr	        <= 8'd0;
					iic_reg_addr	        <= 8'd0;
					iic_wr_data		<= 8'd0;
					state			<= IDLE;
				end
		endcase
end

 这部分就不多做解释了,就是使用一个状态机来发送读写命令,很简单的。

 最后,笔者在做仿真的时候使用了Microchip公司提供的24LC04的仿真模型,在其官网上可以下载,但问题是,这个仿真模型笔者的程序写入时序没有任何问题,ACK信号也可以有效的返回,但是写入的数据却不能返回,找不到问题所在,但是笔者下载到板子上,板级调试没有任何问题,不知道是仿真模型的问题还是什么问题。如下图所示,如果知道原因的可以和笔者交流。

blob.png

 总结:至此就完成了IIC的测试程序,其实本程序可以学的新知识并不多,都是将之前一些小程序的思想运用进来,融会贯通,还是那句话,Verilog是有套路的,掌握了这些套路,写程序其实并不是很难!