前言
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从低电平跳转到高电平,便是结束信号
发送/接收数据阶段
在发送数据阶段又可以分为,发送从机地址,发送地址,发送数据。
他们的共同点都是在主机发送完成数据以后,从机都会响应一个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
测试结果