基于I2C协议的EEPROM 驱动控制

yummy 阅读:456 2022-04-07 14:56:47 评论:0

I2C通讯协议

I2C通讯协议(Inter-Integrated Circuit)是由Philips公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。

I2C通讯协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行AD,图像处理领域的摄像头配置,工业控制领域的X射线管配置等等。除之之外,由于I2C占用引脚特别少,硬件实现简单,可扩展性强,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

I2C物理层

I2C_SCL:串行时钟线,用于同步通讯数据。

I2C_SDA:双向串行数据线,传输通讯数据

 I2C协议层

image.png

①总线空闲状态 ②起始信号 ③数据读写状态 ④停止信号

image.png

器件地址

image.png

I2C读写操作--单字节数据写操作

image.png

页写操作--单字节和2字节

image.png

数据读操作--随机读操作和顺序读操作

image.png

顺序读操作

image.png

EEPROM 24C64 -- 器件地址1010011

image.png

子功能模块

顶层

image.png

image.png

image.png


系统框图

image.png

状态转移图

image.png


I2C控制模块

写波形图


image.png

image.png


image.png


读波形

image.png


image.png

代码部分

module i2c_ctrl
#(parameter     SYS_CLK_FREQ    =   'd50_000_000 , //系统时钟参数
                SCL_FREQ        =   'd250_000    , //串行时钟参数
                DEVICE_ADDER    =   7'd1010_011    //器件地址
)
(
    input   wire          sys_clk   , //系统时钟,50MHZ
    input   wire          sys_rst_n , //系统复位信号,低电平有效
    input   wire          i2c_start , //I2C开始信号
    input   wire          wr_en     , //写数据使能
    input   wire          rd_en     , //读数据使能
    input   wire  [15:0]  byte_addr , //字节地址
    input   wire  [7:0]   wr_data   , //写数据
    input   wire          add_num   , //地址字节控制,低电平表示存储单字节地址
   
    output  reg   [7:0]   rd_data   , //读数据  
    output  reg           i2c_clk   , //I2C时钟,1MHZ,I2C_ctrl模块时钟频率是I2C_scl的4倍,作为读写模块的时钟
    output  reg           i2c_scl   , //串行时钟信号
    output  reg           i2c_sda   , //串行数据信号
    output  reg           i2c_end     //I2C结束信号
);
reg  [7:0]  cnt_clk                 ; //时钟分频计数器
reg  [2:0]  i2c_state               ; //I2C状态
reg  [1:0]  cnt_i2c_clk             ; //I2C时钟计数器
reg         cnt_i2c_clk_en         ; //I2C时钟计数器使能信号
reg  [3:0]  cnt_bit                 ; //sda bit计数器
reg         sda_out                 ; //输出数据寄存
wire        sda_en                  ; //sda使能信号
reg         ack                     ; //应答信号
wire        sda_in                  ; //输入数据寄存  
reg  [7:0]  rd_data_reg             ; //读数据寄存器


parameter   IDLE       = 4'b0000    , //空闲状态
            START      = 4'b0001    , //开始状态
            SEND_D_A   = 4'b0010    , //发送读控制指令
            ACK_1      = 4'b0011    , //响应1
            SEND_B_H_A = 4'b0100    , //发送地址高八位
            ACK_2      = 4'b0101    , //响应2
            SEND_B_L_A = 4'b0110    , //发送地址第八位
            ACK_3      = 4'b0111    , //响应3
            WR_DATA    = 4'b1000    , //写数据状态
            ACK_4      = 4'b1001    , //响应4
            START_2    = 4'b1010    , //开始2状态
            SEND_RD_A  = 4'b1011    , //发送数据读取控制字
            ACK_5      = 4'b1100    , //响应5
            RD_DATA    = 4'b1101    , //读数据状态
            N_ACK      = 4'b1110    , //无响应状态
            STOP       = 4'b1111    ; //停止状态

parameter    CNT_CLK_MAX  = (SYS_CLK_FREQ / SCL_FREQ) >>3 ; //计数最大值

always @(posedge sys_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            cnt_clk <= 8'd0 ; //如果复位信号有效,时钟分频计数器清零
        end
    else if(cnt_clk == CNT_CLK_MAX - 1'b1)
        begin
            cnt_clk <= 8'd0 ; //如果时钟分频计数器计数到最大值,清零
        end    
    else
        begin
            cnt_clk <= cnt_clk + 1'b1 ; //其他情况下,计数继续
        end    
end

always @(posedge sys_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            i2c_clk <= 1'b0 ; //如果复位信号有效,I2C时钟拉低
        end
    else if(cnt_clk == CNT_CLK_MAX - 1'b1)
        begin
            i2c_clk <= ~i2c_clk ; //如果时钟分频计数器计数到最大值,I2C时钟信号取反
        end
    else
        begin
            i2c_clk <= i2c_clk ; //否则保持不变
        end        
end

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(~sys_rst_n) begin
        i2c_state <= IDLE ; //如果复位信号有效,I2C状态为空闲状态
    end
    else
        begin
            case(i2c_state)
            IDLE       :
                        if(i2c_start == 1'b1)
                            begin
                                i2c_state <= START ; //如果I2C开始信号拉高,状态切换到开始状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; //否则保持当前状态
                            end    
            START      :
                        if(cnt_i2c_clk == 2'd3)
                            begin
                                i2c_state <= SEND_D_A ; //如果I2C时钟计数器计到3,状态切换到发送读控制指令
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end    
            SEND_D_A   :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
                            begin
                                i2c_state <= ACK_1 ; //如果I2C时钟计数器计到3并且bit计数器计到7,状态切换到响应1状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end
            ACK_1      :
                        if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0)) //如果I2C时钟计数器计到3,并且响应信号拉低
                            begin
                                if(add_num == 1'b1)
                                    begin
                                        i2c_state <= SEND_B_H_A ; //如果地址字节为高,状态跳转到发送高八位状态
                                    end
                                else
                                    begin
                                        i2c_state <= SEND_B_L_A ; //否则跳转到发送第八位地址状态
                                    end                                  
                            end
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end
            SEND_B_H_A :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
                            begin
                                i2c_state <= ACK_2 ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应2状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end
            ACK_2      :
                        if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
                            begin
                                i2c_state <= SEND_B_L_A ; //如果I2C时钟计数器计到3,响应信号拉低,状态跳转到发送低字节状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end
            SEND_B_L_A :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
                            begin
                                i2c_state <= ACK_3 ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应3状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end  
            ACK_3      :
                        if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0)) //如果I2C时钟计数器计到3,响应信号拉低
                            begin
                                if(wr_en == 1'b1)
                                    begin
                                        i2c_state <= WR_DATA ; //如果写使能有效,状态跳转到写状态
                                    end
                                else if(rd_en == 1'b1)
                                    begin
                                        i2c_state <= START_2 ; //如果读使能有效,状态跳转到开始2状态
                                    end  
                            end                                    
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end  
            WR_DATA    :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
                            begin
                                i2c_state <= ACK_4 ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应4状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end  
            ACK_4      :
                        if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
                            begin
                                i2c_state <= STOP ;//如果I2C时钟计数器计到3,响应信号拉低,状态跳转到停止状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end
            START_2    :
                        if(cnt_i2c_clk == 2'd3)
                            begin
                                i2c_state <= SEND_RD_A ;//如果I2C时钟计数器计到3,状态跳转到发送读数据状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end  
            SEND_RD_A  :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
                            begin
                                i2c_state <= ACK_5 ;//如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应5状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end    
            ACK_5      :
                        if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
                            begin
                                i2c_state <= RD_DATA ; //如果I2C时钟计数器计到3,响应信号拉低,状态跳转到读数据状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end    
            RD_DATA    :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
                            begin
                                i2c_state <= N_ACK ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到无响应状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end
            N_ACK      :
                        if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
                            begin
                                i2c_state <= STOP ;//如果I2C时钟计数器计到3,响应信号拉低,状态跳转到停止状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ;// 否则保持当前状态
                            end  
            STOP       :
                        if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd3))
                            begin
                                i2c_state <= IDLE ;//如果I2C时钟计数器计到3,并且bit计数器计到3,状态跳转到空闲状态
                            end
                        else
                            begin
                                i2c_state <= i2c_state ; // 否则保持当前状态
                            end
            default    : i2c_state <= IDLE  ;  //其他情况下,状态保持空闲状态        
            endcase
        end
end

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            cnt_i2c_clk <= 2'd0 ; //如果复位信号有效,I2C时钟计数器清零
        end
    else  if(cnt_i2c_clk_en == 1'b1)
        begin
            cnt_i2c_clk <= cnt_i2c_clk + 1'b1 ; //如果I2C时钟计数器使能,I2C时钟计数器继续计数
        end  
end

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            cnt_i2c_clk_en <= 1'b0 ; //如果复位信号有效,I2C时钟计数器使能信号拉低
        end
    else if(i2c_start ==1'b1)
        begin
            cnt_i2c_clk_en <= 1'b1 ; //如果I2C开始信号拉高,I2C时钟计数器使能信号拉高
        end    
    else if((cnt_i2c_clk == 2'd3)&&(i2c_state == STOP)&&(cnt_bit == 3'd3))
        begin
            cnt_i2c_clk_en <= 1'b0 ; //如果I2C时钟计数器计到3,并且I2C保持停止状态,bit计数器计到3,I2C时钟计数器使能信号拉低
        end      
end

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            cnt_bit <= 3'd0 ; //如果复位信号有效,bit计数器清零
        end
    else  if((i2c_state == IDLE)||(i2c_state == START)||(i2c_state == ACK_1)||(i2c_state == ACK_2)||(i2c_state == ACK_3)||(i2c_state == ACK_4)||(i2c_state == ACK_5)||(i2c_state == N_ACK)||(i2c_state ==START_2))
        begin
            cnt_bit <= 3'd0 ; //如果状态为空闲状态,或者开始状态,或者响应1状态、或者响应2状态、或者响应3状态、或者响应4状态、或者响应5状态、或者无响应状态、或者开始2状态
        end  
    else if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
        begin
            cnt_bit <= 1'b0 ; //如果I2C计数器计数到3,并且bit计数计到7,bit计数器清零
        end  
    else if((cnt_i2c_clk == 2'd3)||(i2c_state != IDLE ))
        begin
            cnt_bit <= cnt_bit + 1'b1 ; //如果I2C时钟计数器计到3,或者当前状态不是空闲状态,bit计数器继续计数
        end        
end

always @(*) begin
        case (i2c_state)
            IDLE :
                sda_out <= 1'b1 ; //空闲状态,输出数据寄存器拉高
            START :
                if(cnt_i2c_clk == 2'd0)
                    begin
                        sda_out <= 1'b1 ; //I2C时钟计数器计到0,输出数据寄存器拉高
                    end
                else  
                    begin
                        sda_out <= 1'b0 ;//否则输出数据寄存器拉低
                    end
            SEND_D_A:
                if(cnt_bit <= 3'd6)
                    begin
                        sda_out <= DEVICE_ADDER[6 - cnt_bit] ; //如果bit计数到6,把器件地址寄存到输出数据寄存器中    
                    end
                else
                    begin
                        sda_out <= 1'b0 ; //否则输出数据寄存器拉低
                    end
            ACK_1,ACK_2,ACK_3,ACK_4,ACK_5,N_ACK,RD_DATA :
                sda_out <= 1'b1 ; //当前状态为响应1,响应2,响应3,响应4,响应5,无响应,读数据状态,输出数据寄存器拉高
            SEND_B_H_A :
                sda_out <= byte_addr[15-cnt_bit] ; //当前状态为发送高字节地址状态,把地址的高八位寄存到输出数据寄存器中    
            SEND_B_L_A :
                sda_out <= byte_addr[7-cnt_bit] ; //当前状态为发送低字节地址状态,把地址的低八位寄存到输出数据寄存器中
            WR_DATA :
                sda_out <=  wr_data[7-cnt_bit] ; // 当前状态为读数据状态,把写数据地址低八位寄存到输出数据寄存器中
            START_2 :
                if(cnt_i2c_clk <= 2'd1)  
                    begin
                        sda_out <= 1'b1 ; //I2C时钟计数器计到1,输出数据寄存器拉高    
                    end
                else  
                    begin
                        sda_out <= 1'b0 ; //否则输出数据寄存器拉低
                    end
            SEND_RD_A  :
                if(cnt_bit <= 3'd6)
                    begin
                        sda_out <= DEVICE_ADDER[6 - cnt_bit] ; //bit计数器计到6,把器件地址寄存到输出数据寄存器中    
                    end
                else
                    begin
                        sda_out <= 1'b1 ; //否则输出数据寄存器拉高
                    end  
            RD_DATA :
                sda_out <= 1'b1; //输出数据寄存器拉高          
            STOP :
                if((cnt_i2c_clk <= 2'd3)&&(cnt_bit == 3'd0))
                    begin
                        sda_out <= 1'b0 ; //I2C时钟计数器计数到3,bit计数器计到0
                    end
                else
                    begin
                        sda_out <= 1'b1 ; //输出数据寄存器拉高
                    end    
            default: sda_out <= 1'b1 ;//输出数据寄存器拉高
        endcase
end

assign sda_en = ((i2c_state==RD_DATA)||(i2c_state==ACK_1)||(i2c_state== ACK_2)||(i2c_state==ACK_3)||(i2c_state==ACK_4)||(i2c_state==ACK_5)||(i2c_state==N_ACK))?1'b0:1'b1;
//当前状态为读数据状态时,或者响应1状态、或者响应2状态、或者响应3状态、或者响应4状态、或者响应5状态、或者无响应状态、或者开始2状态,数据输出使能拉高,或者拉低

always @(*) begin
    case(i2c_state)
        ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
            begin
                if(cnt_i2c_clk == 2'd0)
                    begin
                        ack <= sda_in ; //在响应1,2,3,4,5,状态下,I2C时钟计数器计到0,把数据输入寄存器赋值给响应信号
                    end
                else
                    begin
                        ack <= ack ; // 否则保持不变
                    end  
            end                      
        default : ack <= 1'b1 ; //其他情况下,响应信号拉高
    endcase    
end

assign  sda_in = i2c_sda ; //I2C串行数据赋值给数据输入寄存器

always @(*) begin
        case (i2c_state)
            IDLE :
                i2c_scl <= 1'b1 ; //空闲状态下,I2C串行时钟信号拉高
            START:
                if(cnt_i2c_clk == 2'd3)
                    begin
                        i2c_scl <= 1'b0 ; //开始状态下,I2C时钟计数器计到3,I2C串行时钟信号拉低
                    end
                else
                    begin
                        i2c_scl <= 1'b1 ; //否则I2C串行时钟信号拉高
                    end    
            SEND_D_A ,ACK_1 ,SEND_B_H_A, ACK_2 ,SEND_B_L_A ,ACK_3 ,WR_DATA ,ACK_4 ,START_2 ,SEND_RD_A ,ACK_5 ,RD_DATA ,N_ACK ://在发送读控制指令、响应1,2,3,4,5,发送高字节地址、发送低字节地址、写数据、开始2、发送读控制指令、读数据、无响应状态下。
                if((cnt_i2c_clk == 2'd1)||(cnt_i2c_clk == 2'd2))
                    begin
                        i2c_scl <= 1'b1 ; //如果I2C时钟计数器计到1或者计到2,I2C串行时钟信号拉高
                    end
                else
                    begin
                        i2c_scl <= 1'b0 ;//否则I2C串行时钟信号拉低
                    end  
            STOP :
                if((cnt_i2c_clk ==2'd0)&&(cnt_bit==3'd0))
                    begin
                        i2c_scl <= 1'b0 ;//如果I2C时钟计数器计到0,并且bit计数器计到0,I2C串行时钟信号拉低
                    end
                else
                    begin
                        i2c_scl <= 1'b1 ; //否则I2C串行时钟信号拉高
                    end
            default : i2c_scl <= 1'b1 ;//其他状态下,I2C串行时钟信号拉高
        endcase                      
    end    

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            i2c_sda <= 1'b1 ; // 1'如果复位信号有效,串行数据信号拉高
        end
    else begin
        case (i2c_state)
            IDLE,START,SEND_D_A,SEND_B_H_A,SEND_B_L_A,WR_DATA,START_2,SEND_RD_A,STOP: //空闲状态、开始状态、发送读控制指令、发送高字节地址、发送低字节地址、写数据状态、开始2状态、发送写控制指令、停止状态下
                i2c_sda <= sda_out ; //数据输出寄存器赋值给串行数据信号
            ACK_1,ACK_2 ,ACK_3 ,ACK_4 ,ACK_5 ,N_ACK :
                i2c_sda <= 1'b0 ; //响应1、2、3、4、5,无响应状态下,串行数据信号拉低
            RD_DATA  :
                i2c_sda <= rd_data[7-cnt_bit] ; //读数据状态下,读数据赋值给串行数据信号
            default : i2c_sda <= 1'b1 ; //其他状态下串行数据信号拉高
        endcase                      
    end    
end

always @(*) begin
    case(i2c_state)
        IDLE  : rd_data_reg <= 8'd0 ; //空闲状态下,读数据寄存器清零
        RD_DATA : rd_data_reg[7-cnt_bit] <= sda_in ; //读数据状态下,数据输入赋值给读数据寄存器
        default : rd_data_reg <= rd_data_reg; //其他情况下,读数据寄存器保持不变
    endcase
end

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if (~sys_rst_n) begin
        rd_data <= 8'd0 ; //如果复位信号有效,读数据清零
    end
    else if((i2c_state == RD_DATA)&&(cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
        begin
            rd_data <= rd_data_reg ; //如果I2C时钟计数器计到3,并且bit计数器计到7,当前状态为读数据系统下,读数据寄存器的数据赋值给读数据
        end
end        

always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(~sys_rst_n)
        begin
            i2c_end <= 1'b0 ; //如果复位信号有效,I2C结束信号拉低
        end
    else if((i2c_state == STOP)&&(cnt_bit == 3'd3)&&(cnt_i2c_clk == 2'd3))    
        begin
            i2c_end <= 1'b1 ; //如果当前状态为停止状态,bit计数器计到3,并且I2C时钟计数器计到3,I2C结束信号拉高
        end
    else
        begin
            i2c_end <= i2c_end ; // 否则保持不变
        end    
end

endmodule

I2C读写数据模块

image.png

读写按键的波形图

image.png

写操作波形图

image.png

数据读操作波形

image.png

数据发送模块波形

image.png







本文 zblog模板 原创,转载保留链接!网址:https://xn--zqqs03dbu6a.cn/?id=26

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。