FPGA -- IIC 时序实现 (超灵活,超好用)

1 篇文章 0 订阅

前言

IIC因为其时序简单,广泛运用于各种低速器件,常用的的三种IIC通信速率:标准速度(高达100 kHz)、快速(高达400 kHz)、高速(高达3.4MHz)。现在以AT24C04为例子讲解IIC的通信时序,并给出Verilog IIC接口代码。

IIC时序讲解

IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。通过从机地址区分从器件。
从时序上看 IIC可分为 开始、发送、接收、结束这四个阶段。

开始\结束阶段

在IIC中规定了在SCL为高时,数据不可以突变,一旦突变便认为是开始信号,或者结束信号。如下图所示,在SCL为高的时刻SDA从高电平跳转到低电平,便是开始信号;在SCL为高的时刻SDA从低电平跳转到高电平,便是结束信号
图1.1

发送/接收数据阶段

在发送数据阶段又可以分为,发送从机地址,发送地址,发送数据。
他们的共同点都是在主机发送完成数据以后,从机都会响应一个ACK信号,这个ACK信号是一个低电平信号,而主机通过这个ACK信号进而判断从机是否收到主机的信号。
在这里插入图片描述
在发送/接收数据时为了方便管理时钟将时钟划分为为4个相位阶段,在相位0时,进行数据发送,以确保接收方不会把数据理解为开始信号或者结束阶段。而在相位270时在统计发送的数据个数;在相位180时接收数据,在相位270统计接收数据


代码说明

实现原理

在这里插入图片描述
具体实现部分都是基于此状态机实现的,在实现过程中只是做严格读写状态区分,通过i_page_size做自动识别时单字节操作还是多字节操作,同时i_operate_mode[2]中注明时单地址还是多地址操作。


接口代码


`timescale 1ns / 100ps
//
// Company: 
// Engineer:  Deam
// 
// Create Date:    22:06:45  2020年11月15日 
// Design Name: 
// Module Name:    iic_intf 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 

/*i_operate_mode:
	1xx: 单地址
	2xx:双地址
*/
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//
 
module iic_intf #(
	parameter SYS_FRE  		= 50,					//单位MHz
	parameter IIC_CLK  		= 400					//单位KHz
)(
	input 			i_sys_clk,
	input 			i_reset_n,
	input			i_start,
	
	input	[2:0]	i_operate_mode,
	input	[7:0]	i_page_size	,  
	input	[7:0]	i_slave_addr,				/* 写 0 (读 1) */
	input   [7:0]	i_data,						/* 是地址也可以是数据 */
	output	[7:0]	o_data,
	
	input			i_axi_rdy,
	output  reg  	o_axi_vail,
	output	reg 	o_rx_rdy,					/* 准备接收信号 上升沿有效*/
	inout 			io_sda,
	output 			o_clk,
	output			o_busy
);


/****************************************************************/
// calc clk_fre
localparam CLK_PRE_SET = SYS_FRE * 1000 / IIC_CLK ;

reg data_dir;
reg data_out;
wire data_in = io_sda;
assign io_sda = data_dir ?  1'bz : data_out;	// 1 :in 0 :out

/****************************************************************/
reg [1:0]start_f;

always @(posedge i_sys_clk)
	start_f <= {start_f[0],i_start};

wire start_flag = (start_f == 2'b01);

/****************************************************************/
reg   [7:0]data_f;
reg   [7:0]page_size_f;
reg   [7:0]slave_addr_f;
reg   [1:0]operate_mode;
reg   	   addr_size;
reg   	   load_data_done;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n) begin
		data_f <= 0;
		slave_addr_f <= 0;
		addr_size <= 0;
		page_size_f <= 0;
		o_axi_vail <= 0;
	end
	else if(start_flag) begin    			//开始触发一次操作
		data_f <= i_data;
		slave_addr_f <= i_slave_addr;
		addr_size <= i_operate_mode[2];	//0:单地址 1:双地址
		operate_mode <= i_operate_mode[1:0]; //暂时未用到
		page_size_f <= i_page_size + (i_operate_mode[2] ? 3:2);
	end
	else if(load_data_done) begin
		o_axi_vail <= 0;
	end
	else if(i_axi_rdy ) begin
		data_f <= i_data; 
		o_axi_vail <= 1;
	end
end		


/****************************************************************/

localparam idle  = 10'h1;
localparam wr_start     = idle << 1;
localparam wr_id 		= idle << 2;
localparam wr_addr 		= idle << 3;
localparam wr_nack		= idle << 4;
localparam wr_ack		= idle << 5;
localparam wr_stop		= idle << 6;

localparam rd_ack 		= idle << 7;
localparam rd_page		= idle << 8;
localparam wr_page		= idle << 9;

//fsm_1
(* fsm_coding = "One-HOt" *)reg [9:0]curr_sta;
(* fsm_coding = "One-HOt" *)reg [9:0]next_sta;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n)
		curr_sta <= idle;
	else
		curr_sta <= next_sta;
end

//fsm_2
reg wr_done;
reg ack_right;
reg rd_done;
reg do_again;
reg [3:0]trans_mode;
reg [7:0]wr_rd_cnt;
wire clk_000,clk_090,clk_180,clk_270;

always @(*) begin
	if(!i_reset_n)
		next_sta = idle;
	else begin
		case(curr_sta)
			idle:
				if(start_flag)
					next_sta = wr_start;
				else 
					next_sta = idle;
			wr_start:
				if(wr_done)
					next_sta = wr_id;
				else 
					next_sta = wr_start;
			wr_id:
				if(wr_done)
					next_sta = rd_ack;
				else
					next_sta = wr_id;
			wr_addr:
				if(wr_done)
					next_sta = rd_ack;
				else
					next_sta = wr_addr;
			rd_ack,wr_ack:						//做核心的跳转
				if((ack_right && rd_done) || wr_done) begin						
					case (trans_mode)
						4'b0000: next_sta = slave_addr_f [0] ? rd_page : (wr_rd_cnt == page_size_f) ? wr_stop:wr_page;
						4'b0001: next_sta = rd_page;
						/* 写完ID   */
						4'b0010: next_sta = wr_addr;
						4'b0100: next_sta = wr_addr;
						/* 写完地址 */
						4'b1000: next_sta = slave_addr_f [0] ? rd_page : wr_page;
						default:next_sta = idle;
           			endcase
				end
				else if(rd_done)				//ack err
					next_sta = idle;
			rd_page:
				if(rd_done)
					next_sta = (wr_rd_cnt == page_size_f) ? wr_nack :wr_ack;
				else 
					next_sta = rd_page;
			wr_page:
				if(wr_done)
					next_sta = rd_ack;
				else
					next_sta = wr_page;
			wr_ack:
				if(wr_done)
					next_sta = slave_addr_f [0] ? rd_page:wr_page;
				else
					next_sta = wr_ack;
			wr_nack:
				if(wr_done)
					next_sta = wr_stop;
				else 
					next_sta = wr_nack;
			wr_stop:
				if(wr_done)
					next_sta = idle;
				else 
					next_sta = wr_stop;
			default :;
		endcase
	end
end		

//fsm_3
reg clk_clr;
reg clk;
reg [3:0]bit_cnt;
reg [7:0]data_buff;
reg [7:0]rx_data_f;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n) begin
		clk_clr  <= 1;
		data_dir <= 1;					//释放总线
		data_out <= 1;
		clk		 <= 1;
		wr_done	 <= 0;
		rd_done	 <= 0;
		bit_cnt	 <= 0;
		ack_right <= 0;
		trans_mode <= 1;
		wr_rd_cnt  <= 0;
		load_data_done <= 0;
		rx_data_f <= 0;
		o_rx_rdy  <=0;
	end
	else begin
		case (curr_sta) 
			idle:begin
				clk_clr 	<= 1;
				data_dir 	<= 1;
				clk 		<= 1;
				data_out	<= 1;
				bit_cnt		<= 0;
				wr_done		<= 0;
				rd_done		<= 0;
				ack_right   <= 0;
				trans_mode  <= 1;
				wr_rd_cnt   <= 0; 
				load_data_done <= 0;
				rx_data_f   <= 0;
				o_rx_rdy    <= 0;
			end
			wr_start:begin				
				clk_clr		<= 0;
				data_dir 	<= 0;
				data_out 	<= 0;
				bit_cnt		<= 0;
				load_data_done <= 0;
				if(clk_090) begin 	
					clk			<= 0;
				end
				else if(clk_180)begin		
					wr_done 	<= 1;					//先传从机地址
					data_buff   <= slave_addr_f;
					clk_clr		<= 1;
				end
				else begin
					wr_done 	<= 0;
				end
			end
			wr_id,wr_addr,wr_page:begin
				case ({clk_270,clk_180,clk_090,clk_000})
					4'b0001: begin clk <= 0;data_dir <= 0; data_out <= data_buff[7]; data_buff <= data_buff << 1;end
					4'b0010: begin clk <= 1; end
					4'b0100: begin clk <= 1; end
					4'b1000: begin clk <= 0; bit_cnt <= bit_cnt + 1;end
					default:;
				endcase
				if(bit_cnt == 8) begin
					wr_done <= 1;
					bit_cnt <= 0;
					wr_rd_cnt <= wr_rd_cnt + 1;
					data_buff <= data_f;
					load_data_done <= 1;
					if(trans_mode == 4'b0010 && addr_size == 0)
						trans_mode <= trans_mode << 2;
					else 
						trans_mode <= trans_mode << 1;
				end
				else begin
					wr_done <= 0; 
					load_data_done <= 0;
				end
			end
			rd_page:begin
				data_dir <= 1;
				if(bit_cnt == 7 && clk_180)
					rx_data_f <= data_buff;
				else if(bit_cnt == 8) begin
					rd_done <= 1;
					wr_rd_cnt <= wr_rd_cnt + 1;
					bit_cnt <= 0;
					o_rx_rdy  <= 1;
					trans_mode <= trans_mode << 1; 
				end
				else begin
					rd_done <= 0;
					o_rx_rdy <= 0;
				end
				
				case ({clk_270,clk_180,clk_090,clk_000})
					4'b0001: begin clk <= 0; end
					4'b0010: begin clk <= 1; end
					4'b0100: begin clk <= 1;data_buff[0] <= data_in; end
					4'b1000: begin clk <= 0;data_buff <= data_buff << 1; bit_cnt <= bit_cnt + 1; end
					default:;
				endcase
			end
			rd_ack: begin
				data_dir <= 1;			//释放总线
				wr_done  <= 0;
				if(clk_180)
					ack_right <= ~data_in;
				else if(clk_270)
					rd_done <=  1;
				else
					rd_done <= 0;
				
				case ({clk_270,clk_180,clk_090,clk_000})
					4'b0001: begin clk <= 0;end
					4'b0010: begin clk <= 1;end
					4'b0100: begin clk <= 1;end
					4'b1000: begin clk <= 0;end
					default:;
				endcase
			end
			wr_nack,wr_ack:begin
				
				wr_done <= 0; 
				rd_done <= 0;
				case ({clk_270,clk_180,clk_090,clk_000})
					4'b0001: begin 
						clk <= 0; 
						data_dir <= 0;
						if(curr_sta == wr_ack)
							data_out <= 0;
						else 
							data_out <= 1;
						end
					4'b0010: begin clk <= 1;end
					4'b0100: begin clk <= 1;end
					4'b1000: begin clk <= 0;wr_done <= 1;end
					default:;
				endcase
			end
			wr_stop: begin
				
				wr_done <= 0; 
				case ({clk_270,clk_180,clk_090,clk_000})
					4'b0001: begin clk <= 0; data_dir <= 0; data_out <= 0;end
					4'b0010: begin clk <= 1;end
					4'b0100: begin clk <= 1; wr_done <= 1; data_out <= 1; end
					4'b1000: begin clk <= 0;end
					default:;
				endcase
			end
			default:;
		endcase
	end
		
end

assign o_clk = clk;
assign o_data = rx_data_f;
assign o_busy = (curr_sta == idle) ? 0 : 1;
/****************************************************************/
parameter DIV_WIDTH = calc_width(CLK_PRE_SET);
reg [DIV_WIDTH : 0]div_cnt;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n)
		div_cnt <= 0;
	else if(clk_clr || div_cnt == CLK_PRE_SET)
		div_cnt <= 0;
	else 
		div_cnt <= div_cnt + 1;
end


assign clk_000 = (div_cnt == 1);
assign clk_090 = (div_cnt == CLK_PRE_SET >> 2);
assign clk_180 = (div_cnt == CLK_PRE_SET >> 1);
assign clk_270 = (div_cnt == CLK_PRE_SET /4 * 3);
/****************************************************************/
 
function integer calc_width(input integer num);
	begin
		calc_width = 0;
		while(num > 0) begin
			num = num >> 1 ;
			calc_width = calc_width + 1;
		end
		calc_width = calc_width - 1;
	end
endfunction

endmodule 

测试代码

`timescale 1ns / 100ps
//
// Company: 
// Engineer: 	Deam
// 
// Create Date:    22:23:17  2020年11月18日 
// Design Name: 
// Module Name:    tb_iic_intf 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//
module   tb_iic_intf  ;

reg 	i_sys_clk;
reg 	i_reset_n;

always #0.5 i_sys_clk =~i_sys_clk;

initial begin
	i_sys_clk = 0;
	i_reset_n = 0;
#2
	i_reset_n = 1;
end
/******************************************************************************/
localparam ADDR_WIDTH = 8;
localparam DATA_WIDTH = 8;

reg i_start;
reg [3:0]i_operate_mode;
reg [7:0]i_page_size;
reg  i_axi_rdy;
wire o_axi_vail;
wire io_sda;
wire o_clk;
wire o_busy;

reg   [7:0]i_slave_addr;
reg   [7:0]i_data;
wire  [7:0]o_data;

 iic_intf #(
	.SYS_FRE  		(50			), 		//单位MHz
	.IIC_CLK  		(1000		) 		//单位KHz
)uut(
	.i_sys_clk		(i_sys_clk	),
	.i_reset_n		(i_reset_n	),
	.i_start		(i_start	),
	.i_operate_mode	(i_operate_mode	),
	.i_page_size	(i_page_size	),
	.i_slave_addr	(i_slave_addr	),
	.i_data			(i_data			),
	.o_data			(o_data			),
	.i_axi_rdy		(i_axi_rdy		),
	.o_axi_vail		(o_axi_vail		),
	
	.io_sda			(io_sda			),
	.o_clk			(o_clk			),
	.o_busy			(o_busy			)
/************************************************/
);

pulldown (io_sda);
/************************************************/
always @(negedge  o_axi_vail)
	i_data <= i_data + 1;
/************************************************/
`define READ1
`ifdef READ
reg [7:0]clk_up_cnt = 0;	
reg io_sda_f;

always @(negedge o_clk ) begin
	clk_up_cnt <= clk_up_cnt + 1;
	if(clk_up_cnt >=18 && clk_up_cnt < 35)
		io_sda_f <= $random;
	else 
		io_sda_f <= 1'bz;
end

assign io_sda = io_sda_f; 
`endif
/******************************************************************************/
initial begin
	i_start = 0;
	i_page_size = 2;
	i_operate_mode = 3'b000;
	i_axi_rdy = 1;
	#20;
		send_data(8'h56,8'h90);
/* 	repeat (i_page_size) begin
		wait (o_axi_vail == 0);
		#10
			i_data = i_data + 1;
	
	end */
	
end
/******************************************************************************/
task send_data;
	input   [7:0]slave_addr;
	input   [7:0]data;
	begin
	#1;
		i_start = 1;
		i_slave_addr = slave_addr;
		i_data		 = data;
	#1; 
		i_start = 0;
	end
endtask


/******************************************************************************/
endmodule


测试文件(.do)

# ./当前路径
# ../上一级目录

#退出当前仿真
quit -sim

#清空命令行
.main clear

#创建库
#vlib   lib
#vlib   ./lib/work

vlib    ./work
#将创建库映射至物理逻辑库
#vmap work  ./lib/work

vmap     work ./work
#编译文件
vlog -work work ./../src_file/iic/iic_intf.v
vlog -work work ./../src_file/iic/tb_iic_intf.v

#启动仿真
vsim -voptargs=+acc work.tb_iic_intf

#添加波形
do wave_iic_intf.do
#
#virtual   type {
#{10'h001 idle}
#{10'h002 wr_start}
#{10'h004 wr_id}
#{10'h008 wr_addr}
#{10'h010 wr_nack}
#{10'h020 wr_ack}
#{10'h040 wr_stop}
#{10'h080 rd_ack}
#{10'h100 rd_page}
#{10'h200 wr_page}
#} fsm_signal
#
##fsm_signal 类型的信号,也就是把Currt_st进行类型转换
#virtual    function {(fsm_signal)tb_iic_intf/uut/curr_sta} new_curr_state
#virtual    function {(fsm_signal)tb_iic_intf/uut/next_sta} new_next_state
#add wave  -color red    tb_iic_intf/uut/new_curr_state
#add wave  -color red    tb_iic_intf/uut/new_next_state

#运行仿真
run 3us

################################################################################3
#10进制显示波形
#add wave -radix unsigned /tb_usart_rx/o_data

#模拟显示
#add wave -format Analog-Step -height 74 -max 255.0 /tb_usart_rx/o_data

测试结果

在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值