详解IIC通信协议以及FPGA实现

一、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整体的时序如下图所示:
在这里插入图片描述

  1. 空闲状态:在I2C空闲时因为串行时钟线 SCL 和串行数据线SDA 线都接有上拉电阻而处于高电平状态。
  2. 起始信号:传输开始前,主机设备会将SDA拉低(当总线空闲时,SDA 和 SCL 都处于高电平状态),然后所有从机设备就会知道传输即将开始。如果两个 master 设备在同一时刻都希望获得总线的所有权,那么谁先将 SDA 拉低,谁就赢得了总线的控制权。
  3. 传输状态:主机可以向从机写数据或者读数据。
  4. 停止信号:当数据传输完成,主机在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协议的SCLinout                                               sda  ,					//IIC协议的SDAoutput  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) beginif(rst_n == 1'b0)begini2c_drive_clk <= 1'b0;clk_cnt <= 'd0;endelse if(clk_cnt == clk_div_cnt_max[14:1] - 1)begini2c_drive_clk <= ~i2c_drive_clk;clk_cnt <= 'd0;endelse begini2c_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) beginif(rst_n == 1'b0)cur_state <= Idle;elsecur_state <= next_state;
end
3.2.5.2 三段式状态机第二段
reg                                                 state_done   ;          //状态机跳转信号//三段式第二段,组合逻辑描述状态转移条件
always @(*) begincase (cur_state)Idle: beginif(i2c_start == 1'b1)begin							//如果i2c_start 来临时,就跳入到发送从机地址状态next_state = Write_slave_addr_state;endelse beginnext_state = Idle;endendWrite_slave_addr_state:begin							//如果从机地址写完成,判断写入的内存地址是16位还是8位if(state_done == 1'b1)beginif(i2c_byte_addr_ctrl == 1'b1)beginnext_state = Write_byte_addr16_state;endelse beginnext_state = Write_byte_addr8_state;endendelse beginnext_state = Write_slave_addr_state;endendWrite_byte_addr16_state:begin							//写完内存地址的高8位后,跳转到写低8位状态if(state_done == 1'b1)beginnext_state = Write_byte_addr8_state;endelse beginnext_state = Write_byte_addr16_state;endendWrite_byte_addr8_state:begin							//内存地址低8位写完后,判断是读还是写操作if(state_done == 1'b1)begin	if(wr_rd_flag == 1'b1)next_state = Write_rd_addr_state;elsenext_state = Write_data_state;endelse next_state = Write_byte_addr8_state;endWrite_data_state:begin									//写数据状态,写完成后跳转到停止状态if(state_done == 1'b1)beginnext_state = Stop;endelse beginnext_state = Write_data_state;endendWrite_rd_addr_state:begin								//由于是读操作,因此还要产生一次虚写操作,就是再发送一次7位从机地址+1 if(state_done == 1'b1)beginnext_state = Read_data_state;endelse beginnext_state = Write_rd_addr_state;endendRead_data_state:begin									//接收从机发送的数据if(state_done == 1'b1)beginnext_state = Stop;endelse beginnext_state = Read_data_state;endendStop:begin												//产生停止信号,然后跳转到空闲状态if(state_done == 1'b1)beginnext_state = Idle;endelse beginnext_state = Stop;endenddefault: 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) beginif(rst_n == 1'b0)begini2c_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;endelse beginclk4_cnt <= clk4_cnt + 1'b1;case (cur_state)Idle: beginif(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;endelse beginclk4_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;endendWrite_slave_addr_state:begincase (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:   beginsda_out <= 1'b1;            sda_ctrl<= 1'b0;            //释放sda总线 end'd37:   scl <= 1'b1;'d38:   beginstate_done <= 1'b1;if(sda_in == 1'b1)          //如果第9位sda为低电平,则表示从机应答成功,状态机跳转 i2c_ack <= 1'b0;elsei2c_ack <= 1'b1;end'd39:   beginstate_done <= 1'b0;         //拉低状态机跳转信号clk4_cnt <= 'd0;            //计数器清零scl <= 1'b0;i2c_ack <= 1'b0;enddefault: ;endcaseendWrite_byte_addr16_state:begincase (clk4_cnt)'d0 :beginsda_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:   beginsda_out <= 1'b1;sda_ctrl<= 1'b0;            //释放sda总线 end'd33:   scl <= 1'b1;'d34:   beginstate_done <= 1'b1;if(sda_in == 1'b1)i2c_ack <= 1'b0;elsei2c_ack <= 1'b1;    end'd35:  beginstate_done <= 1'b0;scl <= 1'b0;clk4_cnt <= 'd0;i2c_ack <= 1'b0;end         default: ;endcaseendWrite_byte_addr8_state:begincase (clk4_cnt)'d0 :beginsda_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:   beginsda_out <= 1'b1;sda_ctrl<= 1'b0;                //释放sda总线 end'd33:   scl <= 1'b1;'d34:   beginstate_done <= 1'b1;if(sda_in == 1'b1)i2c_ack <= 1'b0;elsei2c_ack <= 1'b1;    end'd35:  beginstate_done <= 1'b0;scl <= 1'b0;clk4_cnt <= 'd0;i2c_ack <= 1'b0;end         default: ;endcaseendWrite_data_state:begincase (clk4_cnt)'d0 :beginsda_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:   beginsda_out <= 1'b1;sda_ctrl<= 1'b0;            //释放sda总线 end'd33:   scl <= 1'b1;'d34:   beginstate_done <= 1'b1;if(sda_in == 1'b1)i2c_ack <= 1'b0;elsei2c_ack <= 1'b1;    end'd35:  beginstate_done <= 1'b0;scl <= 1'b0;clk4_cnt <= 'd0;i2c_ack <= 1'b0;end         default: ;endcaseendWrite_rd_addr_state:begincase (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:   beginsda_out <= 1'b1;sda_ctrl<= 1'b0;                //释放sda控制权end'd40:   scl <= 1'b1;'d41:   beginstate_done <= 1'b1;if(sda_in == 1'b1)i2c_ack <= 1'b0;elsei2c_ack <= 1'b1;end'd42:   beginstate_done <= 1'b0;clk4_cnt <= 'd0;scl <= 1'b0;i2c_ack <= 1'b0;end     default: ;endcaseendRead_data_state:begincase (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:   beginsda_ctrl<= 1'b1;sda_out <= 1'b1;end'd33:   scl <= 1'b1;'d34:   state_done <= 1'b1;'d35:   beginstate_done <= 0;scl <= 1'b0;clk4_cnt <= 'd0;i2c_rd_data <= i2c_rd_data_temp;i2c_rd_data_valid <= 1'b1;enddefault: ;endcaseendStop:begincase (clk4_cnt)'d0:    beginsda_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:   beginclk4_cnt <= 'd0;i2c_done <= 1'b1;state_done <= 1'b0;enddefault: ;endcaseenddefault: ;endcaseend
endendmodule

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) beginif(rst_n == 1'b0)beginstate <= '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;endelse begini2c_start <= 1'b0;rw_done <= 1'b0;case (state)0: begindelay_cnt <= delay_cnt + 1'b1;if(delay_cnt == delay_cnt_max -1)begindelay_cnt <= 'd0;if(i2c_byte_addr == 255)begini2c_byte_addr <= 'd0;i2c_wr_rd_en <= 1'b1;state <= 2;endelse beginstate <= 1;i2c_start <= 1'b1;   endendelse beginstate <= 'd0;endend1:beginif(i2c_done == 1'b1)beginstate <= 0;i2c_byte_addr <= i2c_byte_addr + 1'b1;i2c_wr_data <= i2c_wr_data + 1'b1;endelsestate <= 'd1;end2: begini2c_start <= 1'b1; state <= 3;end3:begindelay_cnt <= delay_cnt + 1'b1;if(delay_cnt == delay_cnt_max -1)begindelay_cnt <= 'd0;if(i2c_byte_addr == 255)beginrw_done <= 1'b1;led<= 4'b0000;state <= 0;endelse beginstate <= 2;i2c_byte_addr <= i2c_byte_addr +1'b1;endendelse beginstate <= 'd3;endenddefault: ;endcaseend
endendmodule

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循环累加的数,验证成功。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/876.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】指针详解(五)

目录 1.字符指针 1.1常量字符串 2.指针数组 3.数组指针 1.字符指针 字符指针就是指向字符的指针&#xff0c;字符指针可以存储字符变量的地址。 举例如下&#xff1a; 定义指针变量pa存a的地址&#xff0c;改变*pa的值&#xff0c;a也会随之改变 。 1.1常量字符串 &#x1f…

代码随想录算法训练营第三十八天|509. 斐波那契数,70.爬楼梯,746. 使用最小花费爬楼梯

动态规划(DP) 如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的 一、动态规划包含哪些问题&#xff1f; 1、基础问题&#xff0c;如斐波那契数 2、背包问题&#xff0c;很经典的问题 3、打家劫舍 4、…

安全开发实战(1)--Cdn

目录 安全开发专栏 CDN介绍 1.信息收集阶段 1.1判断CDN是否存在 1.1.1, One 1.1.2,Two(改进) 1.1.3,进行整合 增加输入功能 1.1.4 批量读取监测存储(进行测试) 问题1: 问题2: 解决方案: 1.1.4 基本编写完成 命令框中: cdn存在.txt 总结 这里我是根据整个渗透测…

C语言数据结构-双链表

双链表是什么 双向链表&#xff0c;又称为双链表&#xff0c;是链表的一种&#xff0c;它的每个数据结点中都有两个指针&#xff0c;分别指向直接后继和直接前驱。所以&#xff0c;从双向链表中的任意一个结点开始&#xff0c;都可以很方便地访问它的前驱结点和后继结点。 我们…

使用mapinfo软件的在线地图插件运行错误解决

使用mapinfo软件的在线地图插件运行错误解决 一、如何解决win10/win11家庭版运行MapInfo中的在线地图插件报错【unexpected error&#xff1b;quitting】问题&#xff1f;二、如何解决在线地图切换地图源时的报错问题&#xff1f; 一、如何解决win10/win11家庭版运行MapInfo中的…

原牛角源码(修罗bbs)全站程序打包带数据库备份

原牛角源码(修罗bbs)全站程序打包带数据库备份&#xff0c;牛角源码全站数据全站文件、插件打包分享给大家&#xff0c;有兴趣的可以搭建玩玩&#xff01; conf文件夹中自己配置conf.php里面的数据库链接文件&#xff0c;默认管理账号&#xff1a;admin&#xff0c;密码&#…

代码随想录算法训练营Day60|LC84 柱状图中最大的矩形

一句话总结&#xff1a;完结撒花&#xff01; 原题链接&#xff1a;84 柱状图中最大的矩形 与接雨水的单调栈做法类似。代码如下&#xff1a; class Solution {public int largestRectangleArea(int[] heights) {int[] newHeight new int[heights.length 2];System.arrayco…

Ansible初识以及安装

1. Ansible应用简述&#xff1a; Ansible基于python语言实现&#xff0c;由Paramiko和PyYAML两个关键模块构建。具有独特的设计理念&#xff1a; 1&#xff09;安装部署简单 2&#xff09;管理主机便捷&#xff0c;支持多主机并行管理 3&#xff09;避免在被管理主机上安装客户…

SpringBoot集成Sharding-jdbc(水平分表)

SpringBoot集成Sharding-jdbc&#xff08;水平分表&#xff09; 1.Sharding-jdbc的应用场景2.实际使用2.0 项目层级2.1 导入依赖2.2 application.yml配置2.3 dao层2.4 对应的mybatis的xml文件2.5 Service层2.6 pojo2.7 controller2.8 多线程配置 1.Sharding-jdbc的应用场景 其…

【管理咨询宝藏78】MBB大型城投集团核心能力建设分析报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏78】MBB大型城投集团核心能力建设分析报告 【格式】PDF版本 【关键词】战略规划、商业分析、管理咨询、MBB顶级咨询公司 【强烈推荐】 这是一套…

什么是CPU与GPU,它们之间有什么关系

什么是CPU与GPU&#xff0c;它们之间有什么关系一、CPU1. 核心功能2. 工作原理3. 组成部分4. 发展历程5. 性能指标6. 架构种类7. 发展趋势8. 应用领域 二、GPU三、CPU与GPU的关系 什么是CPU与GPU&#xff0c;它们之间有什么关系 一、CPU CPU&#xff0c;全称是“Central Proc…

吃鸡游戏msvcp140.dll丢失的解决方法

msvcp140.dll 是一个与 Microsoft Visual C Redistributable 相关的动态链接库&#xff08;DLL&#xff09;文件&#xff0c;是 Windows 操作系统中众多应用程序正常运行所必需的关键组件之一。以下是对 msvcp140.dll 文件的总体介绍和msvcp140.dll丢失的多个解决方案分享。 *…

ROS仿真小车(一)—— urdf模型+rviz可视化

文章目录 前言一、创建功能包二、urdf文件三、launch文件四、图形化显示五、RVIZ可视化总结参考文献 前言 ROS学习过程记录&#xff0c;从零开始仿真一辆小车&#xff0c;之后会实现运动控制、雷达、相机等 部分代码已上传至本人的GitHub&#xff0c;如果需要请自行下载&…

算法题解记录18+++搜索二维矩阵Ⅱ

本题可以说是运用二分查找的典例&#xff0c;即使是面对矩阵&#xff0c;只要是它保持“排序好”这样的结构&#xff0c;就一定能采用二分查找法。【你知道的&#xff0c;对于排序好的数组&#xff0c;二分查找几乎是最优秀的算法】 当然&#xff0c;答案提供的是“Z字形查找法…

实在Agent:超自动化智能体的革命(附导引指南)

在自动化技术的浪潮中&#xff0c;实在智能推出了实在Agent&#xff08;智能体&#xff09;&#xff0c;一款基于大语言模型和屏幕语义理解技术的超自动化智能体。它通过自然对话交互&#xff0c;将复杂工作简化为一句话指令&#xff0c;自动规划并执行工作任务&#xff0c;极大…

C++奇迹之旅:深入理解赋值运算符重载

文章目录 &#x1f4dd;赋值运算符重载&#x1f320; 运算符重载&#x1f309;特性 &#x1f320; 赋值运算符重载&#x1f320;传值返回&#xff1a;&#x1f320;传引用赋值&#xff1a;&#x1f309;两种返回选择&#x1f309;赋值运算符只能重载成类的成员函数不能重载成全…

unity学习(86)——细节优化

东西已经做出来了&#xff0c;现在需要的是优化&#xff0c;说得简单&#xff0c;做起来难。 1.122包的优化&#xff0c;避免重复创建&#xff01; 2.为何会出现一边动&#xff0c;一边不动的情况。重复登录后依旧是unity可以看到移动&#xff0c;但是exe那边看不到移动&#…

关于图像YUV格式分类和排布方式的全学习

【学习笔记】关于图像YUV格式分类和排布方式的全学习_yuv图像-CSDN博客 下图是将多个yuv420p图像(A和B)&#xff0c;拼接成一个画面的思路 A大小:416*64 B大小:416*208 将A和B合并到一个416*416的尺寸上&#xff0c;代码如下 //整合char * ptd;ptd (char * ) malloc (416*41…

C#通用类库封装实战

数据库查询 特性方式获取数据库列的别名 数据库更新 使用简单工厂配置的方式

矽塔SA8321 单通道 2.7-12.0V 持续电流 3.0A H 桥驱动芯片

描述 SA8321是为消费类产品&#xff0c;玩具和其他低压或者电池供电的运动控制类应用提供了一个集成的电机驱动器解决方案。此器件能够驱动一个直流无刷电机&#xff0c;由一个内部电荷泵生成所需的栅极驱动电压电路和4个功率 NMOS组成H桥驱动&#xff0c;集成了电机正转/反…