首页 > 其他分享 >IIC驱动EEPROM

IIC驱动EEPROM

时间:2024-12-24 15:31:32浏览次数:6  
标签:I2C clk sda IIC 驱动 i2c parameter reg EEPROM

代码参考正点原子

i2c_dri:主要是三段式状态机的编写

module iic_dri
    #(
      parameter   SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址
      parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
      parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率
    )
   (                                                            
    input                clk        ,    
    input                rst_n      ,   
                                             
    //i2c interface                      
    input                i2c_exec   ,  //I2C触发执行信号
    input                bit_ctrl   ,  //字地址位控制(16b/8b),1代表字地址是16位的,0是8位的
    input                i2c_rh_wl  ,  //I2C读写控制信号     ,1代表读数据,0代表写数据
    input        [15:0]  i2c_addr   ,  //I2C器件内地址
    input        [ 7:0]  i2c_data_w ,  //I2C要写的数据
    output  reg  [ 7:0]  i2c_data_r ,  //I2C读出的数据
    output  reg          i2c_done   ,  //I2C一次操作完成
    output  reg          i2c_ack    ,  //I2C应答标志 0:应答 1:未应答
    output  reg          scl        ,  //I2C的SCL时钟信号
    inout                sda        ,  //I2C的SDA信号
                                       
    //user interface                   
    output  reg          dri_clk       //驱动I2C操作的驱动时钟
     );

//localparam define
localparam  st_idle     = 8'b0000_0001; //空闲状态
localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100; //发送16位字地址
localparam  st_addr8    = 8'b0000_1000; //发送8位字地址
localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束I2C操作

//reg define
reg            sda_dir   ; //I2C数据(SDA)方向控制
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //地址
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数

//wire define
wire          sda_in     ; //SDA输入信号
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数

//*****************************************************
//**                    main code
//*****************************************************

//SDA控制
assign  sda     = sda_dir ?  sda_out : 1'bz;     //SDA数据输出或高阻
assign  sda_in  = sda ;                          //SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数

//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        dri_clk <=  1'b0;
        clk_cnt <= 10'd0;
    end
    else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;
    end
    else
        clk_cnt <= clk_cnt + 1'b1;
end

//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;    //清除锁存器
    case(cur_state)
        st_idle: begin                          //空闲状态
           if(i2c_exec) begin
               next_state = st_sladdr;  //器件地址
           end
           else
               next_state = st_idle;
        end
        
        st_sladdr: begin
            if(st_done) begin
                if(bit_ctrl)                    //判断是16位还是8位字地址
                   next_state = st_addr16;      //字地址的高8位
                else
                   next_state = st_addr8 ;      //字地址的低8位
            end
            else
                next_state = st_sladdr;
        end
        st_addr16: begin                        //写16位字地址
            if(st_done) begin
                next_state = st_addr8;
            end
            else begin
                next_state = st_addr16;
            end
        end
        st_addr8: begin                         //8位字地址
            if(st_done) begin
                if(wr_flag==1'b0)               //读写判断
                    next_state = st_data_wr;
                else
                    next_state = st_addr_rd;
            end
            else begin
                next_state = st_addr8;
            end
        end
        
        st_data_wr: begin                       //写数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_wr;
        end
        st_addr_rd: begin                       //写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd: begin                       //读取数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_rd;
        end
        st_stop: begin                          //结束I2C操作
            if(st_done)
                next_state = st_idle;
            else
                next_state = st_stop ;
        end
        default: next_state= st_idle;
    endcase
end

//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    //复位初始化
    if(!rst_n) begin
        scl       <= 1'b1;
        sda_out   <= 1'b1;
        sda_dir   <= 1'b1;                          
        i2c_done  <= 1'b0;                          
        i2c_ack   <= 1'b0;                          
        cnt       <= 1'b0;                          
        st_done   <= 1'b0;                          
        data_r    <= 1'b0;                          
        i2c_data_r<= 1'b0;                          
        wr_flag   <= 1'b0;                          
        addr_t    <= 1'b0;                           
        data_wr_t <= 1'b0;                          
    end                                              
    else begin                                       
        st_done <= 1'b0 ;                            
        cnt     <= cnt +1'b1 ;                       
        case(cur_state)                              
             st_idle: begin                          //空闲状态
                scl     <= 1'b1;                     
                sda_out <= 1'b1;                     //当sda_dir为1时,sda_out就是sda  
                sda_dir <= 1'b1;                     
                i2c_done<= 1'b0;                     
                cnt     <= 7'b0;               
                if(i2c_exec) begin                   
                    wr_flag   <= i2c_rh_wl ;         
                    addr_t    <= i2c_addr  ;         
                    data_wr_t <= i2c_data_w;  
                    i2c_ack <= 1'b0;                      
                end                                  
            end                                      
            st_sladdr: begin                         //写地址(器件地址和字地址)
                case(cnt)                            
                    7'd1 : sda_out <= 1'b0;          //开始I2C    起始信号 
                    7'd3 : scl <= 1'b0;              //scl在3、5、7、9...时0 1 0 1变化,四分频
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b0;          //0:写  表示接着写 
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;       //拉低释放总线       
                        sda_out <= 1'b1;                         
                    end                              
                    7'd37: scl     <= 1'b1;            
                    7'd38: begin                     //从机应答 
                        st_done <= 1'b1;
                        if(sda_in == 1'b1)           //高电平表示未应答  0表示有效应答
                            i2c_ack <= 1'b1;         //拉高应答标志位     
                    end                                          
                    7'd39: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_addr16: begin                         
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1 ;            
                        sda_out <= addr_t[15];       //传送字地址i2c_addr
                    end                              
                    7'd1 : scl <= 1'b1;              //四分频          
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[14];    
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[13];    
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[12];    
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[11];    
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[10];    
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[9];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[8];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;   
                    end                              
                    7'd33: scl  <= 1'b1;             
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end        
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_addr8: begin                          
                case(cnt)                            
                    7'd0: begin                      
                       sda_dir <= 1'b1 ;             
                       sda_out <= addr_t[7];         //字地址
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[6];   //  
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[5];     
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[4];     
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[3];     
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[2];     
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[1];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[0];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;         
                        sda_out <= 1'b1;                    
                    end                              
                    7'd33: scl     <= 1'b1;          
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_data_wr: begin                        //写数据(8 bit)
                case(cnt)                            
                    7'd0: begin                      
                        sda_out <= data_wr_t[7];     //I2C写8位数据     data_wr_t要写的数据
                        sda_dir <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= data_wr_t[6];  
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= data_wr_t[5];  
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= data_wr_t[4];  
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= data_wr_t[3];  
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= data_wr_t[2];  
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= data_wr_t[1];  
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= data_wr_t[0];  
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;           
                        sda_out <= 1'b1;                              
                    end                              
                    7'd33: scl <= 1'b1;              
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end          
                    7'd35: begin                     
                        scl  <= 1'b0;                
                        cnt  <= 1'b0;                
                    end                              
                    default  :  ;                    
                endcase                              
            end                                      
            st_addr_rd: begin                        //写地址以进行读数据
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd2 : sda_out <= 1'b0;          //重新开始
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b1;          //1:读
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;            
                        sda_out <= 1'b1;                    
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default : ;
                endcase
            end
            st_data_rd: begin                        //读取数据(8 bit)
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;          //非应答
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            st_stop: begin                           //结束I2C操作
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;             //结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 1'b0;
                        i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end

EEPROM读写测试模块:

module e2prom_rw(
    input              clk ,
    input              rst_n ,
 
    output reg        i2c_rh_wl,
    output reg        i2c_exec,
    output reg [15:0] i2c_addr, //i2c器件内地址
    output reg [7:0]  i2c_data_w,
    
    input       [7:0]  i2c_data_r,
    input              i2c_done,
    input              i2c_ack,
    
    output reg         rw_done, //E2PROM读写测试完成 
    output reg         rw_result  //E2PROM读写测试结果  0:失败   1:成功             
    
    );
    
    //parameter define
    //EEPROM写数据需要添加间隔时间,读数据则不需要
    parameter   WR_WAIT_TIME = 14'd50000;  //5ms写入数据的间隔时间为5ms
    parameter   MAX_BYTE     = 16'd256;    //读写测试的字节个数
    
    //reg define
    reg [1:0] flow_cnt;  //状态流控制
    reg [13:0] wait_cnt;  //延时计数器
    
    //EEPROM读写测试,先写后读,并比较读出的值与输入的值是否一致
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n) begin 
            flow_cnt   <= 2'b0;
            i2c_rh_wl  <= 1'b0;
            i2c_exec   <= 1'b0;
            i2c_addr   <= 16'b0;
            i2c_data_w <= 8'b0;
            wait_cnt   <= 14'b0;
            rw_done    <= 1'b0;
            rw_result  <= 1'b0;
         end
         else begin 
         i2c_exec <= 1'b0;
         rw_done  <= 1'b0;
         case(flow_cnt)
            2'd0 : begin
                wait_cnt <= wait_cnt + 1'b1;
                if(wait_cnt == (WR_WAIT_TIME  - 1'b1))begin
                    wait_cnt <= 14'b0;
                    if(i2c_addr == MAX_BYTE)begin
                        i2c_addr <= 16'b0;
                        i2c_rh_wl <= 1'b1;
                        flow_cnt  <= 2'd2;
                    end
                    else begin
                        flow_cnt <= flow_cnt + 2'b1;
                        i2c_exec <= 1'b1;  //启动i2c
                    end
                    end
            end
            2'd1 : begin
                if(i2c_done == 1'b1)begin
                    flow_cnt <= 2'd0;
                    i2c_addr <= i2c_addr + 16'b1;       //0-255
                    i2c_data_w <= i2c_data_w + 8'b1;    //0-255
                end
            end
            2'd2 : begin
                flow_cnt <= flow_cnt + 2'b1;
                i2c_exec <= 1'b1;
            end
            2'd3 : begin
                if(i2c_done == 1'b1)begin
                    //读出的值错误或者i2c未应答,读写测试失败
                    if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
                        rw_done <= 1'b1;
                        rw_result <=1'b0;
                    end
                    else if(i2c_addr == (MAX_BYTE - 16'b1))begin  //读写测试成功
                        rw_done <= 1'b1;
                        rw_result <=1'b1;
                    end
                    else begin
                        flow_cnt <= 2'd2;
                        i2c_addr <= i2c_addr + 16'b1;
                    end
               end
           end
           default : ;
         endcase
      end

 读写结果判定:

reg          rw_done_flag;    //读写测试完成标志
reg  [16:0]  led_cnt     ;    //led计数

//*****************************************************
//**                    main code
//*****************************************************

//读写测试完成标志rw_done_flag
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rw_done_flag <= 1'b0;
    else if(rw_done)
        rw_done_flag <= 1'b1;
end        

//错误标志为1时PL_LED0闪烁,否则PL_LED0常亮
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        led_cnt <= 17'd0;
        led <= 1'b0;
    end
    else begin
        if(rw_done_flag) begin
            if(rw_result)                          //读写测试正确
                led <= 1'b1;                       //led灯常亮
            else begin                             //读写测试错误
                led_cnt <= led_cnt + 17'd1;
                if(led_cnt == (L_TIME - 17'b1)) begin
                    led_cnt <= 17'd0;
                    led <= ~led;                   //led灯闪烁
                end
                else
                    led <= led;
            end
        end
        else
            led <= 1'b0;                           //读写测试完成之前,led灯熄灭
    end    
end

endmodule

 top:

module top_e2prom(
    input               sys_clk    ,      //系统时钟
    input               sys_rst_n  ,      //系统复位
    //eeprom interface
    output              iic_scl    ,      //eeprom的时钟线scl
    inout               iic_sda    ,      //eeprom的数据线sda
    //user interface
    output              led               //led显示eeprom读写测试结果
);

//parameter define
parameter    SLAVE_ADDR = 7'b1010000     ; //器件地址(SLAVE_ADDR)
parameter    BIT_CTRL   = 1'b1           ; //字地址位控制参数(16b/8b)
parameter    CLK_FREQ   = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter    I2C_FREQ   = 18'd250_000    ; //I2C的SCL时钟频率
parameter    L_TIME     = 17'd125_000    ; //led闪烁时间参数
parameter    MAX_BYTE   = 16'd256        ; //读写测试的字节个数

//wire define
wire           dri_clk   ; //I2C操作时钟
wire           i2c_exec  ; //I2C触发控制
wire   [15:0]  i2c_addr  ; //I2C操作地址
wire   [ 7:0]  i2c_data_w; //I2C写入的数据
wire           i2c_done  ; //I2C操作结束标志
wire           i2c_ack   ; //I2C应答标志 0:应答 1:未应答
wire           i2c_rh_wl ; //I2C读写控制
wire   [ 7:0]  i2c_data_r; //I2C读出的数据
wire           rw_done   ; //E2PROM读写测试完成
wire           rw_result ; //E2PROM读写测试结果 0:失败 1:成功 

//*****************************************************
//**                    main code
//*****************************************************

//e2prom读写测试模块
e2prom_rw #(
    .MAX_BYTE    (MAX_BYTE  )   //读写测试的字节个数
) u_e2prom_rw(
    .clk         (dri_clk   ),  //时钟信号
    .rst_n       (sys_rst_n ),  //复位信号
    //i2c interface
    .i2c_exec    (i2c_exec  ),  //I2C触发执行信号
    .i2c_rh_wl   (i2c_rh_wl ),  //I2C读写控制信号
    .i2c_addr    (i2c_addr  ),  //I2C器件内地址
    .i2c_data_w  (i2c_data_w),  //I2C要写的数据
    .i2c_data_r  (i2c_data_r),  //I2C读出的数据
    .i2c_done    (i2c_done  ),  //I2C一次操作完成
    .i2c_ack     (i2c_ack   ),  //I2C应答标志 
    //user interface
    .rw_done     (rw_done   ),  //E2PROM读写测试完成
    .rw_result   (rw_result )   //E2PROM读写测试结果 0:失败 1:成功
);

//i2c驱动模块
iic_dri #(
    .SLAVE_ADDR  (SLAVE_ADDR),  //EEPROM从机地址
    .CLK_FREQ    (CLK_FREQ  ),  //模块输入的时钟频率
    .I2C_FREQ    (I2C_FREQ  )   //IIC_SCL的时钟频率
) u_i2c_dri(
    .clk         (sys_clk   ),  
    .rst_n       (sys_rst_n ),  
    //i2c interface
    .i2c_exec    (i2c_exec  ),  //I2C触发执行信号
    .bit_ctrl    (BIT_CTRL  ),  //器件地址位控制(16b/8b)
    .i2c_rh_wl   (i2c_rh_wl ),  //I2C读写控制信号
    .i2c_addr    (i2c_addr  ),  //I2C器件内地址
    .i2c_data_w  (i2c_data_w),  //I2C要写的数据
    .i2c_data_r  (i2c_data_r),  //I2C读出的数据
    .i2c_done    (i2c_done  ),  //I2C一次操作完成
    .i2c_ack     (i2c_ack   ),  //I2C应答标志
    .scl         (iic_scl   ),  //I2C的SCL时钟信号
    .sda         (iic_sda   ),  //I2C的SDA信号
    //user interface
    .dri_clk     (dri_clk   )   //I2C操作时钟
);

//led指示模块
rw_result_led #(.L_TIME(L_TIME  )   //控制led闪烁时间
) u_rw_result_led(
    .clk         (dri_clk   ),  
    .rst_n       (sys_rst_n ), 
    
    .rw_done     (rw_done   ),  
    .rw_result   (rw_result ),
    .led         (led       )    
);

endmodule

tb:时钟激励

`timescale  1ns/1ns                     //定义仿真时间单位1ns和仿真时间精度为1ns

module  tb_e2prom_top;              

//parameter  define
parameter  T = 20                          ; //时钟周期为20ns
parameter  SLAVE_ADDR     = 7'b1010000     ; //器件地址(SLAVE_ADDR)
parameter  BIT_CTRL       = 1'b1           ; //字地址位控制参数(16b/8b)
parameter  CLK_FREQ       = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ),周期就是20纳秒
parameter  I2C_FREQ       = 18'd250_000    ; //I2C的SCL时钟频率,周期是4us
parameter  L_TIME         = 17'd1          ; //led闪烁时间参数
parameter  MAX_BYTE       = 16'd3          ; //读写测试的字节个数

//reg define
reg          sys_clk  ;                 //时钟信号
reg          sys_rst_n;                 //复位信号

//wire define
wire         iic_scl;
wire         iic_sda;
wire         led    ;

//*****************************************************
//**                    main code
//*****************************************************

//给输入信号初始值
initial begin
    sys_clk            = 1'b0;
    sys_rst_n          = 1'b0;     //复位
    #(T+1)  sys_rst_n  = 1'b1;     //在第21ns的时候复位信号信号拉高
end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;

//将SDA数据线上拉
pullup(iic_sda);

//例化e2prom_top模块
top_e2prom  #(
    .MAX_BYTE   (MAX_BYTE )   //读写测试的字节个数
) u_top_e2prom(
    .sys_clk    (sys_clk  ),  //系统时钟
    .sys_rst_n  (sys_rst_n),  //系统复位
    //eeprom interface
    .iic_scl    (iic_scl  ),  //eeprom的时钟线scl
    .iic_sda    (iic_sda  ),  //eeprom的数据线sda
    //user interface
    .led        (led      )   //led显示
);

//例化e2prom仿真模型
EEPROM_AT24C64 u_EEPROM_AT24C64(
    .scl         (iic_scl),
    .sda         (iic_sda)
    );

endmodule

 上拉电阻:

wire abc;
pullup(abc);
assign abc = enable ? 1’b0 : 1’bz;

enable为1的时候 abc信号为0
enable 为0的时候 assign语句的输出为高阻,但是因为pullup了abc,所以abc的值为1

EEPROM仿真模型:

`timescale 1ns/1ns
`define timeslice 1250
module EEPROM_AT24C64(
scl,
sda
);
input scl; 
inout sda; 
reg out_flag; 
reg[7:0] memory[8191:0]; 
reg[12:0]address; 
reg[7:0]memory_buf; 
reg[7:0]sda_buf; 
reg[7:0]shift; 
reg[7:0]addr_byte_h; 
reg[7:0]addr_byte_l; 
reg[7:0]ctrl_byte; 
reg[1:0]State;
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;

initial
begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
always@(negedge sda)
begin
if(scl == 1)
begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end

always@(posedge sda)
begin
if(scl == 1) 
stop_W_R;
else
begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4
|| ctrl_byte == w3 || ctrl_byte == w2
|| ctrl_byte == w1 || ctrl_byte == w0)
begin
State = 2'b10;
write_to_eeprom; 
end
else
State = 2'b00;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end 
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask

task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask

task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
State = 2'b00;
end
endtask

task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0)
begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl)
begin
#(`timeslice);
out_flag = 1;
sda_buf = 0;
end
@(negedge scl)
begin
#(`timeslice-250);
out_flag = 0;
end
end
endtask
task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1)
begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1;
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule

i2c状态机的编写:

 

读数据: 

i2c第一次写入地址0的字节数据0:

 i2c第二次写入地址1的字节数据1: 

 i2c第三次写入地址2的字节数据2: 

读数据的时候sda总线由输入变为输出状态,需要给从机一个非应答信号,不能给应答信号!给应答信号就不再是任意地址读,就变成了从某个地址连续读,不符合本次设计的任意地址读模式:

 

 

上板验证:

        胡萝卜鸡... 

标签:I2C,clk,sda,IIC,驱动,i2c,parameter,reg,EEPROM
From: https://blog.csdn.net/weixin_60610210/article/details/144687025

相关文章

  • Ubuntu22.04 LTS 安装nvidia显卡驱动
    准备跑老师给定的Github上的多模态源码,但是用了这么久ubuntu还没有尝试过安装nvidia驱动,好在也是一次成功,于是记录下来。借鉴的是https://blog.csdn.net/Eric_xkk/article/details/131800365这篇文章,按照流程来基本没有问题,不过个人觉得有些步骤比较冗余,所以记录下来主要流程关......
  • 驱动钛丝(SMA)的可靠性设计(12)生产过程中的偏差和误差
    【前言】形状记忆合金(Shapememoryalloy,SMA),也叫形态记忆合金、钛镍记忆合金,它是由Ti(钛)-Ni(镍)材料组成,经过多道工序制成的丝,我们简称钛丝,可以通过电路驱动钛丝发生运动。相比于传统的电机、电磁铁动力,钛丝是一种新型的动力元件。钛丝驱动技术目前已经在航空航天、洲际导弹、......
  • 大语言模型驱动的Agent:定义、工作原理与应用
    文章目录引言什么是大语言模型?Agent的概念LLMAgent的工作原理Dify平台上的AgentLLMAgent的应用场景挑战与展望结论引言随着人工智能(AI)技术的发展,特别是自然语言处理(NLP)领域的进步,大语言模型(LLM,LargeLanguageModels)已经成为AI领域的一颗璀璨明星。这些模......
  • OpenAI 推出嵌入式硬件 SDK,支持 ESP32 语音开发;INFP:音频驱动的双人对话头像生成,自动区
      开发者朋友们大家好: 这里是「RTE开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享RTE(Real-TimeEngagement)领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的会议」,但内容仅代表编辑的......
  • 【嵌入式Linux】---- 基于petaLinux和SDK开发的LED驱动和应用测试(全流程实战)
    1配置petaLinux环境变量在Linuxproject目录下,打开终端,输入命令source/opt/pkg/petalinux/2018.3/settings.sh2新建petaLinux工程petalinux-create-tproject--templatezynq-nZYNQ7010_LED3配置petaLinux工程输入cdZYNQ7010_LED,进入刚刚创建的工程文件;输入p......
  • TMC6300-LA-T直流无刷(BLDC)电机驱动原理图设计
    一、TMC6300-LA-T简介:        TMC6300由单个锂离子电池或双个AA电池共同工作,最适合用于电池操作的设备。使用CPU的6线控制操作带有块或正弦换向的BLDC电机。集成电源mosfets处理电机电流高达2A。保护和诊断功能支持稳健和可靠的操作。它的集成充电泵,为一流的RDSon和超......
  • 鼠标 hgpk型号 linux驱动程序
    /*SPDX-License-Identifier:GPL-2.0//OLPCHGPK(XO-1)touchpadPS/2mousedriver*/#ifndef_HGPK_H#define_HGPK_H#defineHGPK_GS0xff/*TheGlideSensor/#defineHGPK_PT0xcf/ThePenTablet*/enumhgpk_model_t{HGPK_MODEL_PREA=0x0a,/*p......
  • PS2功能 sa1111 linux驱动程序
    /*linux/drivers/input/serio/sa1111ps2.cCopyright©2002RussellKingThisprogramisfreesoftware;youcanredistributeitand/ormodifyitunderthetermsoftheGNUGeneralPublicLicenseaspublishedbytheFreeSoftwareFoundation;eitherver......
  • ssm基于项目驱动的课程管理系统1u51b程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容项目名称:基于项目驱动的课程管理系统一、项目背景随着教育信息化的不断发展,传统的教学管理模式已难以满足现代教育的需求。课程管理作为教育管理......
  • Linux驱动开发笔记(七):操作系统MMU介绍,操作系统操作寄存器的原理和Demo
    前言  做过单片机的都知道,写驱动是直接代码设置和读取寄存器来控制外设实现基本的驱动功能,而linux操作系统上是由MMU(内存管理单元)来控制,MMU实现了虚拟地址与芯片物理地址的对应,设置和获取MMU地址就是设置和获取映射的物理地址,从而跟单片机一样实现与物理硬件的驱动连接。 ......