10-verilog的EEPROM驱动-单字节读写

verilog的EEPROM驱动-单字节读写

1.读写

c 复制代码
module i2c_dri(                                                            
    input                clk        ,    
    input                rst_n      ,   
                                         
    //i2c interface                      
    input                i2c_rw         ,  //0是写,1是读
    input                i2c_begin      ,  //I2C触发执行信号
    input        [15:0]  i2c_addr       ,  //I2C器件内地址
    input        [ 7:0]  i2c_data_w     ,  //I2C要写的数据
    output  reg  [ 7:0]  i2c_data_r     ,  //I2C读出的数据
    output  reg          i2c_busy       ,  //I2C忙信号
    output  reg          i2c_done   ,  //I2C一次操作完成
    output  reg          scl            ,  //I2C的SCL时钟信号
    inout                sda   ,             //I2C的SDA信号
    output       reg    led2    ,   
    //
     output reg            dri_clk    //I2C数据(SDA)方向控制                                 
     );
parameter   SLAVE_ADDR = 7'b1010000   ;  //EEPROM从机地址
parameter   CLK_FREQ   = 26'd50_000_000; //模块输入的时钟频率
parameter   I2C_FREQ   = 18'd250_000;     //IIC_SCL的时钟频率

//reg define
reg    [ 9:0]  clk_cnt   ; //分频时钟计数
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [15:0]  addr_t    ; //读写地址临时寄存
reg    [ 7:0]  data_r    ; //数据
reg    [ 7:0]  data_w    ; //数据
reg            r_or_w   ;   //读写
reg            sda_dir   ; //I2C数据(SDA)方向控制
reg            sda_out   ; //

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

assign  sda     = sda_out ;     //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
//////////////////////////////////
reg    [ 7:0]  data_temp    ; //数据
reg     flow_cnt_ok ;        // 流转计数
reg    [ 6:0]  cnt       ; //计数
////////////////////////////////////////////////
/*
写
0:空闲
1:加载器件地址
2:写字节
3:加载16为地址高字节
4:写字节
5:加载16为地址低字节
6:写字节
7:加载数据
8:写字节
9:发送停止位

读
0:空闲
1:加载器件地址
2:写字节
3:加载16为地址高字节
4:写字节
5:加载16为地址低字节
6:写字节
10:加载器件地址+读
8:写字节
11:读数据
12:发送停止


*/
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n) begin
        scl <=  1'b1;
        sda_out <=  1'b1;
        cnt<=  7'b0;
        flow_cnt<=4'b0;
        i2c_busy<=1'b0;
        flow_cnt_ok<= 1'b0;
        cnt<= 1'b0;
    end
    ////////////////////////////////////////////
    else if (flow_cnt_ok&&(r_or_w==0))begin 
        flow_cnt<=((flow_cnt>=4'd9)? (4'd0):(flow_cnt+4'd1));//状态技术+1
        i2c_done<=1'b0;
        flow_cnt_ok<= 1'b0;
    end 
    else if (flow_cnt_ok&&(r_or_w==1))begin 
        if(flow_cnt<=4'd5)
            flow_cnt<=flow_cnt+1'b1;
        else if (flow_cnt==4'd6)
            flow_cnt<=4'd10;
        else if (flow_cnt==4'd10)
            flow_cnt<=4'd8;
       else if (flow_cnt==4'd8)
            flow_cnt<=4'd11;     
        else if (flow_cnt==4'd11)
            flow_cnt<=4'd12;
        else if (flow_cnt==4'd12)
            flow_cnt<=4'd0;
        i2c_done<=1'b0;
        flow_cnt_ok<= 1'b0;
    end 
    ///////////////////////////////////////////////
    else if(flow_cnt == 4'b0) begin
        if(i2c_begin==1'b1&&i2c_busy==1'b0) begin
            r_or_w<=i2c_rw;
            addr_t<=i2c_addr;
            data_w<=i2c_data_w;
            i2c_busy<=1'b1;
            flow_cnt_ok<=1'b1;
        end
        else 
            flow_cnt = 4'b0;
    end
    else if(flow_cnt == 4'd1) begin//加载写字节
        data_temp<={SLAVE_ADDR,1'b0};
        flow_cnt_ok<=1'b1;
    end
    else if(flow_cnt == 4'd3) begin//加载eeprom地址
        data_temp<=addr_t[15:8];
        flow_cnt_ok<=1'b1;
    end
    else if(flow_cnt == 4'd5) begin//加载低8位地址
        data_temp<=addr_t[7:0];
        flow_cnt_ok<=1'b1;
    end
    else if(flow_cnt == 4'd7) begin//加载要写入的数据
        data_temp<=data_w[7:0];
        flow_cnt_ok<=1'b1;
    end
    else if(flow_cnt == 4'd9) begin//发送STOP
        cnt     <= cnt +1'b1 ;
        case(cnt)
            7'd1 : sda_out <= 1'b0;          //开始I2C
            7'd2 : scl <= 1'b1;              
            7'd3 : sda_out <= 1'b1; //传送器件地址
            7'd4 : begin
                i2c_busy<=1'b0;
                i2c_done<=1'b1;
                scl <= 1'b1;
                sda_out <= 1'b1;
                flow_cnt_ok<=1'b1;
                cnt <= 1'b0;
                end
             default :  ;   
         endcase
    end
    else if(flow_cnt == 4'd2||flow_cnt == 4'd4||flow_cnt == 4'd6||flow_cnt == 4'd8) begin//发送一个字节
        cnt     <= cnt +1'b1 ;
        case(cnt)
            //7'd0 :begin scl <= 1'b1;sda_out <= 1'b1;end
            7'd1 : sda_out <= 1'b0;          //开始I2C
            7'd3 : scl <= 1'b0;              
            7'd4 : sda_out <= data_temp[7]; //传送器件地址
            7'd5 : scl <= 1'b1;              
            7'd7 : scl <= 1'b0;              
            7'd8 : sda_out <= data_temp[6]; 
            7'd9 : scl <= 1'b1;              
            7'd11: scl <= 1'b0;              
            7'd12: sda_out <= data_temp[5]; 
            7'd13: scl <= 1'b1;              
            7'd15: scl <= 1'b0;              
            7'd16: sda_out <= data_temp[4]; 
            7'd17: scl <= 1'b1;              
            7'd19: scl <= 1'b0;              
            7'd20: sda_out <= data_temp[3]; 
            7'd21: scl <= 1'b1;              
            7'd23: scl <= 1'b0;              
            7'd24: sda_out <= data_temp[2]; 
            7'd25: scl <= 1'b1;              
            7'd27: scl <= 1'b0;              
            7'd28: sda_out <= data_temp[1]; 
            7'd29: scl <= 1'b1;              
            7'd31: scl <= 1'b0;              
            7'd32: sda_out <= data_temp[0];          //0:写
            7'd33: scl <= 1'b1;              
            7'd35: scl <= 1'b0;              
            7'd36: sda_out <= 1'bz;  
            //            
            7'd37: scl     <= 1'b1;    
            7'd38: begin                     //从机应答 
                  if(sda == 1'b1) begin          //高电平表示未应答
                    cnt <= 1'b0;
                    flow_cnt = 4'b0;
                   end    
            end                                          
            7'd39: begin                     
                  scl <= 1'b0;                 
                  cnt <= 1'b0;
                  flow_cnt_ok<=1'b1;
                  end
            default :  ;    
         endcase
    end
    else if(flow_cnt == 4'd10) begin//加载要读的芯片地址
        data_temp<={SLAVE_ADDR,1'b1};
        flow_cnt_ok<=1'b1;
        scl <= 1'b1;//添加起始位
    end
    else if(flow_cnt == 4'd12) begin//发送STOP
        cnt     <= cnt +1'b1 ;
        case(cnt)
            7'd1 : sda_out <= 1'b0;          //开始I2C
            7'd2 : scl <= 1'b1;              
            7'd3 : sda_out <= 1'b1; //传送器件地址
            7'd4 : begin
                i2c_busy<=1'b0;
                i2c_done<=1'b1;
                scl <= 1'b1;
                sda_out <= 1'b1;
                flow_cnt_ok<=1'b1;
                cnt <= 1'b0;
                end
             default :  ;   
         endcase
    end
    else if(flow_cnt == 4'd11) begin//加载要读的芯片地址
        cnt     <= cnt +1'b1 ;
        case(cnt)
            7'd1 : sda_out <= 1'bz;          //开始I2C
            7'd3 : scl <= 1'b0;              
            7'd5 : scl <= 1'b1; 
            7'd6 : data_r[7]<=sda;
            7'd7 : scl <= 1'b0;              
            7'd9 : scl <= 1'b1;  
            7'd10 : data_r[6]<=sda;
            7'd11: scl <= 1'b0;              
            7'd13: scl <= 1'b1;    
            7'd14 : data_r[5]<=sda;
            7'd15: scl <= 1'b0;              
            7'd17: scl <= 1'b1;              
            7'd18: data_r[4]<=sda;
            7'd19: scl <= 1'b0;              
            7'd21: scl <= 1'b1;              
            7'd22: data_r[3]<=sda;
            7'd23: scl <= 1'b0;              
            7'd25: scl <= 1'b1;              
            7'd26: data_r[2]<=sda;
            7'd27: scl <= 1'b0;              
            7'd29: scl <= 1'b1;              
            7'd30:data_r[1]<=sda;
            7'd31: scl <= 1'b0;              
            7'd33: scl <= 1'b1;              
            7'd34: data_r[0]<=sda;
            7'd35: scl <= 1'b0;  
            7'd36: sda_out <= 1'b0;           
            7'd37: scl <= 1'b1; 
            7'd39: scl <= 1'b0; 
            7'd40: sda_out <= 1'b1; 
            7'd42: begin                                  
                  cnt <= 1'b0;
                  flow_cnt_ok<=1'b1;
                  end
            default :  ;  
          endcase 
    end
    else 
    ;
end
endmodule

2.例化

c 复制代码
module e2prom_top(
    input               sys_clk    ,      //系统时钟
    input               sys_rst_n  ,      //系统复位
    //eeprom interface
    output              iic_scl    ,      //eeprom的时钟线scl
    inout               iic_sda    ,      //eeprom的数据线sda
    
    output              iic_scl_b    ,      
    output              iic_sda_b   ,
    output              dri_clk   ,
    
    output       reg    led0    ,      
    output       reg    led1    ,    
    output              led2    ,    
    
    input               key0    ,
    input               key1
);

assign iic_scl_b=iic_scl;
assign iic_sda_b=iic_sda;
//parameter define
parameter    SLAVE_ADDR = 7'b1010000    ; //器件地址(SLAVE_ADDR)
parameter    CLK_FREQ   = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter    I2C_FREQ   = 18'd250_000   ; //I2C的SCL时钟频率


//*****************************************************
//**                    main code
//****************************************************//
reg  i2c_rw;
reg  i2c_begin;
reg  [15:0]  i2c_addr;
reg  [ 7:0]  i2c_data_w     ;  //I2C要写的数据
wire [ 7:0]  i2c_data_r     ;  //I2C读出的数据

always @(posedge dri_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n)begin
        led0<=1'b0;
        i2c_begin<=1'b0;
        i2c_rw<=1'b0;//写数据
        i2c_addr<=16'h0000;
        i2c_data_w<=8'h00;
    end
    else if (key0_flag&&key0_value&&(!i2c_busy))begin
        i2c_begin<=1'b1;
        i2c_rw<=1'b0;//写数据
        i2c_addr<=16'h0001;
        i2c_data_w<=8'h12;
        led0<=~led0;
    end
    else if (key1_flag&&key1_value&&(!i2c_busy))begin
        i2c_begin<=1'b1;
        i2c_rw<=1'b1;//读数据
        i2c_addr<=16'h0001;
        i2c_data_w<=8'h12;
        led1<=~led1;
    end
    else
        i2c_begin<=1'b0;
end


//i2c驱动模块
i2c_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_rw      (i2c_rw   ),
    .i2c_begin   (i2c_begin   ),    //I2C触发执行信号
    .i2c_addr    (i2c_addr   ),     //I2C器件内地址
    .i2c_data_w  (i2c_data_w   ),  //I2C要写的数据
    .i2c_data_r  (i2c_data_r   ),  //I2C读出的数据
    .i2c_busy    (i2c_busy   ),     //I2C忙信号    
    .i2c_done    (i2c_done   ),     //I2C忙信号    
    .led2           (led2   ),     //I2C忙信号 
      
    .scl         (iic_scl   ),  //I2C的SCL时钟信号
    .sda         (iic_sda   ) , //I2C的SDA信号
    .dri_clk       (dri_clk)
);



/*
always @(posedge dri_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n)
        led0<=1'b0;
    else if (key0_flag&&key0_value)begin
        //led0<=~led0;
        ;
    end
    else
        //led0<=led0;
        ;
end 

always @(posedge dri_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n)
        led1<=1'b0;
    else if (key1_flag&&key1_value)
        led1<=~led1;
    else
        led1<=led1;
end 
*/
wire key0_value;
wire key0_flag;
key_in u0_key_in(
    .sys_clk         (dri_clk   ),  
    .rst_n           (sys_rst_n ),  
    
    .key_in          (key0 ),         
    .key_flag        (key0_flag ),        
	.key_value       (key0_value )       
);

key_in u1_key_in(
    .sys_clk         (dri_clk   ),  
    .rst_n           (sys_rst_n ),  
    
    .key_in          (key1 ),         
    .key_flag        (key1_flag ),        
	.key_value       (key1_value )       
);

endmodule
c 复制代码
module key_in(
    input            sys_clk,          //外部50M时钟
    input            rst_n,        //外部复位信号,低有效
    
    input            key_in,              //外部按键输入
    output reg       key_flag,         //按键数据有效信号
	output reg       key_value         //按键消抖后的数据  
    );

reg [31:0] delay_cnt;
reg        key_reg;

always @(posedge sys_clk or negedge rst_n) begin 
    if (!rst_n) begin 
        key_reg   <= 1'b1;
        delay_cnt <= 32'd0;
    end
    else begin
        key_reg <= key_in;				//非阻塞赋值,按键输入是wire类型的,随时可能有值的变化
        if(key_reg != key_in)          //一旦检测到按键状态发生变化(有按键被按下或释放)
            delay_cnt <= 32'd500_000;  //给延时计数器重新装载初始值(计数时间为10ms)
        else if(key_reg == key_in) begin  //在按键状态稳定时,计数器递减,开始10ms倒计时
                 if(delay_cnt > 32'd0)
                     delay_cnt <= delay_cnt - 1'b1;
                 else
                     delay_cnt <= delay_cnt;
             end           
    end   
end
always @(posedge sys_clk or negedge rst_n) begin 
    if (!rst_n) begin 
        key_flag  <= 1'b0;
        key_value <= 1'b1;          
    end
    else begin
        if(delay_cnt == 32'd1) begin   //当计数器递减到1时,说明按键稳定状态维持了20ms
            key_flag  <= 1'b1;         //此时消抖过程结束,给出一个时钟周期的标志信号
            key_value <= key_in;          //并寄存此时按键的值
        end
        else begin
            key_flag  <= 1'b0;
            key_value <= key_value; 
        end  
    end   
end
endmodule

3.波形