一、IIC简介
IIC也称为I2C(Inter-Integrated Circuit)由飞利浦公司(现在的恩智浦半导体)开发,是一种用于短距离数字通信的串行,同步,半双工通信接口协议;传输在标准模式下可以达到100kbit/s,在快速模式下可以达到400Kbit/s, 在快速模式增强模式下可以达到1Mbit/s,在高速模式下可以达到3.4Mbit/s。
I2C通信协议使用两根线(串行数据线SDA和串行时钟线SCL)进行通信,其中SDA用于传输数据,SCL用于传输时钟信号;支持多主设备和多从设备的通信,通过地址来识别不同的设备,并支持数据的读取和写入操作。
I2C通信协议在很多嵌入式系统和电子设备中被广泛应用,例如传感器、存储器、显示屏等设备之间的通信;由于其简单的硬件连接和灵活的设备支持,I2C通信协议在许多应用中都具有很好的适用性,通信框图如下:
二、IIC通信协议
由于挂在I2C总线上的设备可以有多个,因此每个挂在I2C总线的设备都有唯一的地址来标识。有些 I2C 器件的器件地址是固定的,而有些 I2C 器件的器件地址由一个固定部分和一个可编程的部分构成,这是因为很可能在一个系统中有几个同样的器件,器件地址的可编程部分能最大数量的使这些器件连接到 I2C 总线上。
2.1 协议层
I2C整体的时序如下图所示:
- 空闲状态:在I2C空闲时因为串行时钟线 SCL 和串行数据线SDA 线都接有上拉电阻而处于高电平状态。
- 起始信号:传输开始前,主机设备会将SDA拉低(当总线空闲时,SDA 和 SCL 都处于高电平状态),然后所有从机设备就会知道传输即将开始。如果两个 master 设备在同一时刻都希望获得总线的所有权,那么谁先将 SDA 拉低,谁就赢得了总线的控制权。
- 传输状态:主机可以向从机写数据或者读数据。
- 停止信号:当数据传输完成,主机在SCL为高电平时,拉高SDA线就表示本次传输完成。
为了保证传输过程稳定,I2C总线协议规定:每次传输数据必须为一个字节(即8bit,高位在前);在串行时钟线 SCL 为低电平状态时,SDA 允许改变传输的数据位(1 为高电平,0 为低电平)。经过 8 个时钟周期后,传输了 8bit 数据,即一个字节。第 8 个时钟周期末,主机释放 SDA 以使从机应答,在第 9 个时钟周期,从机将 SDA 拉低以应答;如果第 9 个时钟周期,SCL 为高电平时,SDA 未被检测到为低电平,视为非应答,表明此次数据传输失败。第 9 个时钟周期末,从机释放 SDA 以使主机继续传输数据,如果主机发送停止信号,此次传输结束。
2.2 器件地址
地址帧总是在一次通信的最开始出现。一个 7bit 的地址是从最高位(MSB)开始发送的,这个地址后面会紧跟 1bit 的操作符,(1 表示读操作,0 表示写操作) 。地址格式如下所示:
2.3 写时序
主机发送完器件地址加上写命令0,从机正确应答后就处于接收数据的状态;此时,主机向器件发送要写数据的起始地址,从机正确应答后,就开始向该地址写数据了,写时序如下:
单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,字节写完成。
2.4 读时序
主机发送完器件地址加上读命令1,从机正确应答后就处于发送数据的状态;此时从机向主机发送单字节数据,读时序如下:
三、FPGA实现
本次实现使用I2C协议对EEPROM进行读写操作,在EEPROM的地址0到255,写入0到255的累加数,然后再按照顺序读出来,并使用ILA连续捕获出来看数据是否正确
3.1 结构框图
系统整体流程就是,I2C_ctrl模块负责把要读写的地址和写数据给I2C_interface,I2C_interface模块负责实现I2C协议部分,然后把读出的数据给上游。
3.2 详解I2C_interface模块
3.2.1 接口部分
例化接口代码:
module I2C_interface #
(
parameter SYS_CLK_FREQ = 'd50_000_000, //系统时钟频率
parameter I2C_CLK = 'd100_000, //I2C输出时钟
parameter SLAVE_ADDR = 7'b1010000 //从机地址
)
(
input sys_clk ,
input rst_n ,
input i2c_start , //I2C开始信号
input i2c_byte_addr_ctrl , //为1时表示要写入的内存地址是16位的,1表示8位
input [15:0] i2c_byte_addr , //写入从机内存地址
input i2c_wr_rd_en , //读写使能,0表示写,1表示读
input [7:0] i2c_wr_data , //写入从机的数据
output reg [ 7:0] i2c_rd_data , //从从机读出的数据
output reg i2c_rd_data_valid, //读数据有效信号
output reg i2c_done , //本次I2C操作完成信号
output reg i2c_ack , //从机给的ack信号
output reg scl , //IIC协议的SCL
inout sda , //IIC协议的SDA
output reg i2c_drive_clk //却驱动I2C模块的驱动时钟
);
3.2.2 定义状态机
使用状态机来实现I2C协议,整个状态机跳转流程图如下:
代码如下:
localparam Idle = 8'b0000_0001; //空闲状态
localparam Write_slave_addr_state = 8'b0000_0010; //发送从机器件地址写状态
localparam Write_byte_addr16_state = 8'b0000_0100; //发送16位内存地址状态
localparam Write_byte_addr8_state = 8'b0000_1000; //发送8位内存地址状态
localparam Write_data_state = 8'b0001_0000; //写数据(8 bit)状态
localparam Write_rd_addr_state = 8'b0010_0000; //发送器件地址读状态
localparam Read_data_state = 8'b0100_0000; //读数据(8 bit)状态
localparam Stop = 8'b1000_0000; //结束I2C操作
3.2.3 实现I2C模块的驱动时钟部分
使用实际IIC通信的四倍频时钟来驱动整个模块,至于为什么是四倍频,下面会讲到
localparam clk_div_cnt_max = SYS_CLK_FREQ/I2C_CLK/4;//I2C四倍频时钟计数最大值
reg [14:0] clk_cnt ; //分频时钟需要的计数器
//生成SCL的四倍频时钟
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
i2c_drive_clk <= 1'b0;
clk_cnt <= 'd0;
end
else if(clk_cnt == clk_div_cnt_max[14:1] - 1)begin
i2c_drive_clk <= ~i2c_drive_clk;
clk_cnt <= 'd0;
end
else begin
i2c_drive_clk <= i2c_drive_clk;
clk_cnt <= clk_cnt + 1'b1;
end
end
3.2.4 实现控制SDA方向部分
reg sda_ctrl ; //控制sda方向,1的时候sda为输出;0的时候sda为输入
reg sda_out ; //sda输出信号
wire sda_in ; //sda输入信号
//控制SDA
assign sda = sda_ctrl ? sda_out : 1'bz;
assign sda_in = sda;
3.2.5 实现I2C协议的三段式状态机
3.2.5.1 三段式状态机第一段
reg [7:0] cur_state = Idle; //状态机当前状态
reg [7:0] next_state = Idle; //状态机下一状态
//三段式第一段,时序电路描述状态转移
always @(posedge i2c_drive_clk or negedge rst_n) begin
if(rst_n == 1'b0)
cur_state <= Idle;
else
cur_state <= next_state;
end
3.2.5.2 三段式状态机第二段
reg state_done ; //状态机跳转信号
//三段式第二段,组合逻辑描述状态转移条件
always @(*) begin
case (cur_state)
Idle: begin
if(i2c_start == 1'b1)begin //如果i2c_start 来临时,就跳入到发送从机地址状态
next_state = Write_slave_addr_state;
end
else begin
next_state = Idle;
end
end
Write_slave_addr_state:begin //如果从机地址写完成,判断写入的内存地址是16位还是8位
if(state_done == 1'b1)begin
if(i2c_byte_addr_ctrl == 1'b1)begin
next_state = Write_byte_addr16_state;
end
else begin
next_state = Write_byte_addr8_state;
end
end
else begin
next_state = Write_slave_addr_state;
end
end
Write_byte_addr16_state:begin //写完内存地址的高8位后,跳转到写低8位状态
if(state_done == 1'b1)begin
next_state = Write_byte_addr8_state;
end
else begin
next_state = Write_byte_addr16_state;
end
end
Write_byte_addr8_state:begin //内存地址低8位写完后,判断是读还是写操作
if(state_done == 1'b1)begin
if(wr_rd_flag == 1'b1)
next_state = Write_rd_addr_state;
else
next_state = Write_data_state;
end
else
next_state = Write_byte_addr8_state;
end
Write_data_state:begin //写数据状态,写完成后跳转到停止状态
if(state_done == 1'b1)begin
next_state = Stop;
end
else begin
next_state = Write_data_state;
end
end
Write_rd_addr_state:begin //由于是读操作,因此还要产生一次虚写操作,就是再发送一次7位从机地址+1
if(state_done == 1'b1)begin
next_state = Read_data_state;
end
else begin
next_state = Write_rd_addr_state;
end
end
Read_data_state:begin //接收从机发送的数据
if(state_done == 1'b1)begin
next_state = Stop;
end
else begin
next_state = Read_data_state;
end
end
Stop:begin //产生停止信号,然后跳转到空闲状态
if(state_done == 1'b1)begin
next_state = Idle;
end
else begin
next_state = Stop;
end
end
default: next_state = Idle;
endcase
end
3.2.5.3 三段式状态机第三段
由于I2C协议规定,sda上的数据需要在scl为高电平的时候保持稳定,只能在scl为低电平的时候改变。因此我们产生一个四倍频的驱动时钟再加一个四倍频时钟的计数器,就能够实现在任何位置改变数据,波形图如下:
从波形图可以看出,在cnt=1时scl为高电平,此时拉低sda就表示了开始信号;然后在cnt=3时,拉低scl;在cnt=4的时候给sda赋值,在cnt=5的时候,拉高scl。后面依次类推。这样就利用scl四倍频的时钟产生了scl与sda,程序如下:
reg wr_rd_flag ; //读写信号,0的时候为写;1的时候为读
reg [6:0] clk4_cnt ; //I2C四倍频时钟周期计数器
reg [15:0] i2c_byte_addr_reg ; //字地址缓存
reg [7:0] i2c_wr_data_reg ; //I2C写数据缓存
reg [7:0] i2c_rd_data_temp ; //I2C读数据临时数据
//三段式第三段,时序逻辑描述状态输出
always @(posedge i2c_drive_clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
i2c_rd_data <= 'd0;
i2c_rd_data_valid <= 1'b0;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
scl <= 1'b1;
sda_ctrl <= 1'b1;
sda_out <= 1'b1;
state_done <= 1'b0;
wr_rd_flag <= 1'b0;
clk4_cnt <= 'd0;
i2c_byte_addr_reg <= 'd0;
i2c_wr_data_reg <= 'd0;
i2c_rd_data_temp <= 'd0;
end
else begin
clk4_cnt <= clk4_cnt + 1'b1;
case (cur_state)
Idle: begin
if(i2c_start ==1'b1)begin //开始传输时,把读写使能,地址,数据都暂存下来
wr_rd_flag <= i2c_wr_rd_en;
i2c_byte_addr_reg <= i2c_byte_addr;
i2c_wr_data_reg <= i2c_wr_data;
clk4_cnt <= 'd0;
i2c_rd_data_valid <= 1'b0;
end
else begin
clk4_cnt <= 'd0;
scl <= 1'b1;
sda_ctrl <= 1'b1;
sda_out <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
i2c_rd_data_valid <= 1'b0;
end
end
Write_slave_addr_state:begin
case (clk4_cnt)
'd1: sda_out <= 1'b0; //SCL为高电平时,拉低SDA为起始信号
'd3: scl <= 1'b0;
'd4: sda_out <= SLAVE_ADDR[6]; //低电平时传输器件地址低6位
'd5: scl <= 1'b1;
'd7: scl <= 1'b0;
'd8: sda_out <= SLAVE_ADDR[5]; //低电平时传输器件地址低5位
'd9: scl <= 1'b1;
'd11: scl <= 1'b0;
'd12: sda_out <= SLAVE_ADDR[4]; //低电平时传输器件地址低4位
'd13: scl <= 1'b1;
'd15: scl <= 1'b0;
'd16: sda_out <= SLAVE_ADDR[3]; //低电平时传输器件地址低3位
'd17: scl <= 1'b1;
'd19: scl <= 1'b0;
'd20: sda_out <= SLAVE_ADDR[2]; //低电平时传输器件地址低2位
'd21: scl <= 1'b1;
'd23: scl <= 1'b0;
'd24: sda_out <= SLAVE_ADDR[1]; //低电平时传输器件地址低1位
'd25: scl <= 1'b1;
'd27: scl <= 1'b0;
'd28: sda_out <= SLAVE_ADDR[0]; //低电平时传输器件地址低0位
'd29: scl <= 1'b1;
'd31: scl <= 1'b0;
'd32: sda_out <= 1'b0; //写命令
'd33: scl <= 1'b1;
'd35: scl <= 1'b0;
'd36: begin
sda_out <= 1'b1;
sda_ctrl<= 1'b0; //释放sda总线
end
'd37: scl <= 1'b1;
'd38: begin
state_done <= 1'b1;
if(sda_in == 1'b1) //如果第9位sda为低电平,则表示从机应答成功,状态机跳转
i2c_ack <= 1'b0;
else
i2c_ack <= 1'b1;
end
'd39: begin
state_done <= 1'b0; //拉低状态机跳转信号
clk4_cnt <= 'd0; //计数器清零
scl <= 1'b0;
i2c_ack <= 1'b0;
end
default: ;
endcase
end
Write_byte_addr16_state:begin
case (clk4_cnt)
'd0 :begin
sda_ctrl<= 1'b1; //拿回SDA控制权,并且传开始输内存地址的高8位
sda_out <= i2c_byte_addr_reg[15];
end
'd1 : scl <= 1'b1;
'd3 : scl <= 1'b0;
'd4 : sda_out <= i2c_byte_addr_reg[14];
'd5 : scl <= 1'b1;
'd7 : scl <= 1'b0;
'd8 : sda_out <= i2c_byte_addr_reg[13];
'd9 : scl <= 1'b1;
'd11: scl <= 1'b0;
'd12: sda_out <= i2c_byte_addr_reg[12];
'd13: scl <= 1'b1;
'd15: scl <= 1'b0;
'd16: sda_out <= i2c_byte_addr_reg[11];
'd17: scl <= 1'b1;
'd19: scl <= 1'b0;
'd20: sda_out <= i2c_byte_addr_reg[10];
'd21: scl <= 1'b1;
'd23: scl <= 1'b0;
'd24: sda_out <= i2c_byte_addr_reg[9];
'd25: scl <= 1'b1;
'd27: scl <= 1'b0;
'd28: sda_out <= i2c_byte_addr_reg[8];
'd29: scl <= 1'b1;
'd31: scl <= 1'b0;
'd32: begin
sda_out <= 1'b1;
sda_ctrl<= 1'b0; //释放sda总线
end
'd33: scl <= 1'b1;
'd34: begin
state_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b0;
else
i2c_ack <= 1'b1;
end
'd35: begin
state_done <= 1'b0;
scl <= 1'b0;
clk4_cnt <= 'd0;
i2c_ack <= 1'b0;
end
default: ;
endcase
end
Write_byte_addr8_state:begin
case (clk4_cnt)
'd0 :begin
sda_ctrl<= 1'b1; //拿回SDA控制权,并且传开始输内存地址的低8位
sda_out <= i2c_byte_addr_reg[7];
end
'd1 : scl <= 1'b1;
'd3 : scl <= 1'b0;
'd4 : sda_out <= i2c_byte_addr_reg[6];
'd5 : scl <= 1'b1;
'd7 : scl <= 1'b0;
'd8 : sda_out <= i2c_byte_addr_reg[5];
'd9 : scl <= 1'b1;
'd11: scl <= 1'b0;
'd12: sda_out <= i2c_byte_addr_reg[4];
'd13: scl <= 1'b1;
'd15: scl <= 1'b0;
'd16: sda_out <= i2c_byte_addr_reg[3];
'd17: scl <= 1'b1;
'd19: scl <= 1'b0;
'd20: sda_out <= i2c_byte_addr_reg[2];
'd21: scl <= 1'b1;
'd23: scl <= 1'b0;
'd24: sda_out <= i2c_byte_addr_reg[1];
'd25: scl <= 1'b1;
'd27: scl <= 1'b0;
'd28: sda_out <= i2c_byte_addr_reg[0];
'd29: scl <= 1'b1;
'd31: scl <= 1'b0;
'd32: begin
sda_out <= 1'b1;
sda_ctrl<= 1'b0; //释放sda总线
end
'd33: scl <= 1'b1;
'd34: begin
state_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b0;
else
i2c_ack <= 1'b1;
end
'd35: begin
state_done <= 1'b0;
scl <= 1'b0;
clk4_cnt <= 'd0;
i2c_ack <= 1'b0;
end
default: ;
endcase
end
Write_data_state:begin
case (clk4_cnt)
'd0 :begin
sda_ctrl<= 1'b1; //拿回SDA控制权,并且传开始输8位写数据
sda_out <= i2c_wr_data_reg[7];
end
'd1 : scl <= 1'b1;
'd3 : scl <= 1'b0;
'd4 : sda_out <= i2c_wr_data_reg[6];
'd5 : scl <= 1'b1;
'd7 : scl <= 1'b0;
'd8 : sda_out <= i2c_wr_data_reg[5];
'd9 : scl <= 1'b1;
'd11: scl <= 1'b0;
'd12: sda_out <= i2c_wr_data_reg[4];
'd13: scl <= 1'b1;
'd15: scl <= 1'b0;
'd16: sda_out <= i2c_wr_data_reg[3];
'd17: scl <= 1'b1;
'd19: scl <= 1'b0;
'd20: sda_out <= i2c_wr_data_reg[2];
'd21: scl <= 1'b1;
'd23: scl <= 1'b0;
'd24: sda_out <= i2c_wr_data_reg[1];
'd25: scl <= 1'b1;
'd27: scl <= 1'b0;
'd28: sda_out <= i2c_wr_data_reg[0];
'd29: scl <= 1'b1;
'd31: scl <= 1'b0;
'd32: begin
sda_out <= 1'b1;
sda_ctrl<= 1'b0; //释放sda总线
end
'd33: scl <= 1'b1;
'd34: begin
state_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b0;
else
i2c_ack <= 1'b1;
end
'd35: begin
state_done <= 1'b0;
scl <= 1'b0;
clk4_cnt <= 'd0;
i2c_ack <= 1'b0;
end
default: ;
endcase
end
Write_rd_addr_state:begin
case (clk4_cnt)
'd0:begin
sda_ctrl<= 1'b1; //拿回SDA控制权,开始传输器件地址加上读命令
sda_out<= 1'b0;
end
'd1: scl <= 1'b1;
'd2: sda_out<= 1'b1; //停止信号
'd4: sda_out<= 1'b0; //开始信号
'd6: scl <= 1'b0;
'd7: sda_out <= SLAVE_ADDR[6];
'd8: scl <= 1'b1;
'd10: scl <= 1'b0;
'd11: sda_out <= SLAVE_ADDR[5];
'd12: scl <= 1'b1;
'd14: scl <= 1'b0;
'd15: sda_out <= SLAVE_ADDR[4];
'd16: scl <= 1'b1;
'd18: scl <= 1'b0;
'd19: sda_out <= SLAVE_ADDR[3];
'd20: scl <= 1'b1;
'd22: scl <= 1'b0;
'd23: sda_out <= SLAVE_ADDR[2];
'd24: scl <= 1'b1;
'd26: scl <= 1'b0;
'd27: sda_out <= SLAVE_ADDR[1];
'd28: scl <= 1'b1;
'd30: scl <= 1'b0;
'd31: sda_out <= SLAVE_ADDR[0];
'd32: scl <= 1'b1;
'd34: scl <= 1'b0;
'd35: sda_out <= 1'b1; //读命令
'd36: scl <= 1'b1;
'd38: scl <= 1'b0;
'd39: begin
sda_out <= 1'b1;
sda_ctrl<= 1'b0; //释放sda控制权
end
'd40: scl <= 1'b1;
'd41: begin
state_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b0;
else
i2c_ack <= 1'b1;
end
'd42: begin
state_done <= 1'b0;
clk4_cnt <= 'd0;
scl <= 1'b0;
i2c_ack <= 1'b0;
end
default: ;
endcase
end
Read_data_state:begin
case (clk4_cnt)
'd1: scl <= 1'b1;
'd2: i2c_rd_data_temp[7] <= sda_in;
'd3: scl <= 1'b0;
'd5: scl <= 1'b1;
'd6: i2c_rd_data_temp[6] <= sda_in;
'd7: scl <= 1'b0;
'd9: scl <= 1'b1;
'd10: i2c_rd_data_temp[5] <= sda_in;
'd11: scl <= 1'b0;
'd13: scl <= 1'b1;
'd14: i2c_rd_data_temp[4] <= sda_in;
'd15: scl <= 1'b0;
'd17: scl <= 1'b1;
'd18: i2c_rd_data_temp[3] <= sda_in;
'd19: scl <= 1'b0;
'd21: scl <= 1'b1;
'd22: i2c_rd_data_temp[2] <= sda_in;
'd23: scl <= 1'b0;
'd25: scl <= 1'b1;
'd26: i2c_rd_data_temp[1] <= sda_in;
'd27: scl <= 1'b0;
'd29: scl <= 1'b1;
'd30: i2c_rd_data_temp[0] <= sda_in;
'd31: scl <= 1'b0;
'd32: begin
sda_ctrl<= 1'b1;
sda_out <= 1'b1;
end
'd33: scl <= 1'b1;
'd34: state_done <= 1'b1;
'd35: begin
state_done <= 0;
scl <= 1'b0;
clk4_cnt <= 'd0;
i2c_rd_data <= i2c_rd_data_temp;
i2c_rd_data_valid <= 1'b1;
end
default: ;
endcase
end
Stop:begin
case (clk4_cnt)
'd0: begin
sda_ctrl<= 1'b1;
sda_out <= 1'b0;
end
'd1: scl <= 1'b1; //在scl为高电平时候,拉高sda代表停止信号
'd3: sda_out <= 1'b1;
'd10: state_done <= 1'b1;
'd11: begin
clk4_cnt <= 'd0;
i2c_done <= 1'b1;
state_done <= 1'b0;
end
default: ;
endcase
end
default: ;
endcase
end
end
endmodule
3.3 详解I2C_ctrl模块
该模块主要产生要读和要写的数据和地址,本次想要在EEPROM的0-255地址写入0-255的数字。因为本次板卡上的EEPROM手册要求每次操作完一次后,需要等待500ms,因此本模块也是有状态机完成,每次跳转等待500ms,知道读写完256个数字,代码如下:
`timescale 1ns/1ns
module I2C_ctrl
(
input clk ,
input rst_n ,
output reg i2c_wr_rd_en , //I2C读写控制信号
output reg i2c_start , //I2C触发执行信号
output reg [15:0] i2c_byte_addr , //I2C器件内地址
output reg [ 7:0] i2c_wr_data , //I2C要写的数据
input [ 7:0] i2c_rd_data , //I2C读出的数据
input i2c_done , //I2C一次操作完成
input i2c_ack, //I2C应答标志
output reg [3:0] led
);
localparam delay_cnt_max = 200000;
reg [25:0] delay_cnt ;
reg [3:0] state ;
reg rw_done ;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
state <= 'd0;
delay_cnt <= 'd0;
i2c_start <= 1'b0;
i2c_byte_addr <= 'd0;
i2c_wr_rd_en <= 1'b0 ;
i2c_wr_data <='d0;
rw_done <= 1'b0;
led <= 4'b1111;
end
else begin
i2c_start <= 1'b0;
rw_done <= 1'b0;
case (state)
0: begin
delay_cnt <= delay_cnt + 1'b1;
if(delay_cnt == delay_cnt_max -1)begin
delay_cnt <= 'd0;
if(i2c_byte_addr == 255)begin
i2c_byte_addr <= 'd0;
i2c_wr_rd_en <= 1'b1;
state <= 2;
end
else begin
state <= 1;
i2c_start <= 1'b1;
end
end
else begin
state <= 'd0;
end
end
1:begin
if(i2c_done == 1'b1)begin
state <= 0;
i2c_byte_addr <= i2c_byte_addr + 1'b1;
i2c_wr_data <= i2c_wr_data + 1'b1;
end
else
state <= 'd1;
end
2: begin
i2c_start <= 1'b1;
state <= 3;
end
3:begin
delay_cnt <= delay_cnt + 1'b1;
if(delay_cnt == delay_cnt_max -1)begin
delay_cnt <= 'd0;
if(i2c_byte_addr == 255)begin
rw_done <= 1'b1;
led<= 4'b0000;
state <= 0;
end
else begin
state <= 2;
i2c_byte_addr <= i2c_byte_addr +1'b1;
end
end
else begin
state <= 'd3;
end
end
default: ;
endcase
end
end
endmodule
3.4 仿真测试
顶层接口就如同系统框图所示,只有sys_clk,rst_n,以及scl,sda。因此tb文件只需要写入一个时钟和复位即可,然后打开仿真,由于每次操作延时了500ms,可以在仿真的时候修改sys_clk的值来加快仿真,打开仿真如下所示:
我们可以看到,每次请求iic传输时,给的地址和数据都是累加的,我们放大局部来看具体细节:
当收到i2c_start信号的时候,首先传输开始信号,然后传输设备地址,本次EEPROM地址为1010000,然后跟上写操作0,第九位释放总线,判断ack。其它的同理。
3.5 上板验证
我们添加ila然后打开capture mode抓取信号,观察读出来的数据是否连续,关于capture mode怎么使用,请看《Vivado ILA Capture Control 模式与 Advanced Trigger的功能使用以及TSM(触发状态机)的编写》这篇文章,下载bit文件后,打开ila窗口,设置捕捉信号为,rd_data_valid上升沿,观察波形如下:
我们可以看到,读出来的数据是0-255循环累加的数,验证成功。