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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python:元组,字符串,切片

一、元组# 列表可以修改内容&#xff0c;元组可以不被修改 # 在程序内封装数据&#xff0c;不希望数据被篡改&#xff0c;所以使用元组 # 语法&#xff1a; 不限制类型 # 定于元组的字面量&#xff1a; &#xff08;元素&#xff0c;元素&#xff0c;元素.....&#xff09; # 定…

apipost、postman等工具上传图片测试flask、fastapi的文件api接口

参考&#xff1a;https://blog.csdn.net/qq_15821487/article/details/119354129 https://www.cnblogs.com/wyxjava/p/16076176.html 选择from-data&#xff0c;下拉选择file上传文件发送即可

线上真实案例之执行一段逻辑后报错Communications link failure

1.问题发现 在开发某个项目的一个定时任务计算经销商返利的功能时&#xff0c;有一个返利监测的调度&#xff0c;如果某一天返利计算调度失败了&#xff0c;需要重新计算&#xff0c;这个监测的调度就会重新计算某天的数据。 在UAT测试通过&#xff0c;发布生产后&#xff0c…

NVIDIA安装程序失败-Nsight Visual Studio Edition失败解决办法

博主是要升级cuda版本&#xff0c;那么在安装新版本之前需要卸载以前的版本。 博主一溜卸载下去&#xff0c;最后有这么个东西卸载不掉&#xff0c;Nsight Visual Studio Edition 不管是电脑系统卸载还是360卸载&#xff0c;都卸载不掉。 此时安装新的cuda也遇到了这个问题 由…

PLC存储器分类及西门子SIMATIC S7-1200存储器参数

存储器用来储存程序和数据&#xff0c;分为系统存储器和用户存储器。 系统存储器存放由PLC生产厂商编写好的系统程序&#xff0c;并固化在只读存储器&#xff08;ROM&#xff09;内&#xff0c;用户不能修改。用户存储器存放用户根据控制要求编写的应用程序。目前大多数PLC采用…

面试经典150题——从中序与后序遍历序列构造二叉树

1. 题目描述 2. 题目分析与解析 其实这个题目和昨天那个很相似&#xff0c;思考思路如下&#xff1a; 解决从中序&#xff08;inorder&#xff09;与后序&#xff08;postorder&#xff09;遍历序列构造二叉树的问题时&#xff0c;考虑到这两个遍历序列为我们提供了树结构中…

解决方案 SHUTDOWN_STATE xmlrpclib.py line: 794 ERROR: supervisor shutting down

Supervisor操作命令 重新加载 Supervisor 配置&#xff1a; sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl restart all这将重新读取 Supervisor 的配置文件&#xff0c;更新进程组&#xff0c;然后重启所有进程。 查看 Supervisor 日志&#xff1…

尺取法知识点讲解

一、固定长度的情况&#xff1a; 最小和(sum) 输入N个数的数列&#xff0c;所有相邻的M个数的和共有N-M1个&#xff0c;求其中的最小值。 输入格式 第1行&#xff0c;2个整数N&#xff0c;M&#xff0c;范围在[3…100000]&#xff0c;N>M。 第2行&#xff0c;有N个正…

R语言入门:“Hellinger“转化和“normalize“转化(弦转化)的公式表示与R代码实现

1、写在前面 vegan包中的decostand()函数为群落生态学研究提供了一些流行的(和有效的)标准化方法。有关decostand()函数标准化的一些标准化方法可以看我的另一篇笔记&#xff1a;R语言入门&#xff1a;vegan包使用decostand()函数标准化方法 由于在网络上没有找到关于这两个转…

Redis-键值设计

Redis-键值设计 1.设置key的规范 遵循基本格式&#xff1a;【业务名称】&#xff1a;【数据名】&#xff1a;【id】 可读性强&#xff0c;在客户端的情况下使用:如果前缀相同会分目录层级长度不超过44字节 string数据结构的三种类型&#xff0c;在44字节之内是embstring 内存…

鸿蒙应用开发之Web组件3

前面学习了从网上加载网页的显示,本文将要学习加载本地的网页。比如很多显示的内容,可以制作网页的文件格式,然后直接使用它来显示,就可以减少界面的制作。另外,当手机没有网络的时候,如果想从网络上获取内容就会失败,这时候可以使用本地的网页内容来代替。这样不会导致…

Python的Logging模块高级用法-日志处理

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 探索Python中的日志处理&#xff1a;Logging模块的高级用法 在Python应用程序中&#xff0…

lementui el-menu侧边栏占满高度且不超出视口

做了几次老是忘记&#xff0c;这次整理好逻辑做个笔记方便重复利用&#xff1b; 问题&#xff1a;elementui的侧边栏是占不满高度的&#xff1b;但是使用100vh又会超出视口高度不美观&#xff1b; 解决办法&#xff1a; 1.获取到侧边栏底部到视口顶部的距离 2.获取到视口的高…

【动态规划】dp 路径问题(不同路径、路径最小和、地下城游戏...)

文章目录 1. 前言 - 理解动态规划算法1.5 关于dp路径问题2. 例题2.1_不同路径Warning. 关于状态表示 3. 算法题3.1_不同路径II3.2_珠宝的最高价值3.3_下降路径最小和3.4_最小路径和3.5_地下城游戏关于状态表示的两种选法&#xff1a; 1. 前言 - 理解动态规划算法 关于 动态规划…

Pytorch 之torch.nn初探 池化--Pooling Layers

任务描述 本关任务&#xff1a;本关提供了一个Variable 类型的变量x&#xff0c;要求按照条件创建一个Conv2d变量conv&#xff0c;一个MaxPool2d变量pool&#xff0c;对x应用卷积和最大池化操作并赋值给变量outpout_pool&#xff0c;并输出outpout_pool 的大小。 相关知识 P…

Blerden4.1基础操作方法

软件安装 下载软件地址 中文文档 偏好设置 编辑——》偏好设置——》界面——》设置分辨率缩放 1.20 方便观看字体 设置快捷键 是为了方便几个3d软件都变成同一种操作方式 这样就不会自己搞混了 编辑——》偏好设置——》键位映射——》3D视图——》3D视图&#xff08;全局…

将windows作为网关

开启转发 reg add HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v IPEnableRouter /D 1 /f开启routing and remote access服务 这样局域网里面别的设备能通过windows进行上网 参考&#xff1a;https://www.cnblogs.com/chrishg/articles/12861053.html

Springboot+Vue项目-基于Java+MySQL的房屋租赁系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

OceanBase V4.2特性解析:用 Show Trace 快速定位数据库性能瓶颈

在数据库日常运维中&#xff0c;当遇到慢SQL问题时&#xff0c;若无法迅速查明原因&#xff0c;将极大地影响用户的使用感受&#xff0c;甚至可能引发业务或服务的中断。相较于单机数据库&#xff0c;分布式数据库系统因其涉及多个节点和多组件的协同工作&#xff0c;集群规模可…

短视频流媒体平台的系统设计

1. 功能需求: 我们的系统有两类参与者 内容创作者 •上传任何类型的视频&#xff08;格式编解码器&#xff09;•视频可以被删除•视频元数据•必填项: 标题&#xff0c;作者&#xff0c;描述•选填项: 分类/标签列表•可以随时更新•当视频对观众可用时&#xff0c;向内容创作…
最新文章