• 详解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模块的驱动时钟
    
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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操作
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

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

  • 相关阅读:
    MySQL如果实现隔离级别-MVCC
    【机器学习-周志华】学习笔记-第八章
    Ubuntu server 22.04 安装kvm
    谷粒商城十二性能压测
    2265. 统计值等于子树平均值的节点数
    在Linux操作系统上安装 kafka
    基于Spring Boot+Vue+MySQL的在线商品销售平台(商家端)
    Java反射原理和实际用法
    基于生成对抗网络探索潜在空间的医学图像融合算法
    mac 查看端口占用
  • 原文地址:https://blog.csdn.net/qq_43156031/article/details/137337015