verilog I2C_eeprom 手册分析及代码编写思路

verilog I2C_eeprom 手册分析及代码编写思路

.基本属性_EEPROM_24LC04B/24AA04

  • 型号(EEPROM):24LC04B
  • 时钟频率:100-400KHZ
  • 两线串行接口,兼容I2C协议
  • 电可擦除,断电数据不会丢失
  • 两个block,每个block :256*8bit.总共内存4kbit

一.AC特性表

verilog I2C_eeprom 手册分析及代码编写思路_第1张图片

  • TBUF :Bus free time: Time the bus must be free before a new transmission can start,两次传输之间需要等待1300~4700ns;
  • 其它的保持时间和建立时间在时钟频率设定在100~400Khz时都是正确的

二.I2C协议数据读写时序图,起始停止图

verilog I2C_eeprom 手册分析及代码编写思路_第2张图片

1.数据总线和时钟线在空闲时都必须拉高

2.开始条件:时钟总线高电平时,拉低数据总线

3.数据允许变化:在时钟总线低电平时

4.数据保持稳定,采样数据:在时钟总线高电平时

5.停止条件:时钟总线高电平时,拉高数据总线

6.ACK:应答是低电平有效,应答由接收方传出

三.24AA04/24LC04B如何通过I2C协议与主机进行通信

该eeprom的设备地址:1010

1.写入EEPROM(字节写/页写)

verilog I2C_eeprom 手册分析及代码编写思路_第3张图片

(1) 字节写

  • 发送起始位:SCLK高电平时,SDA拉低
  • 写入控制字节:设备地址(1010)+任意两位(xx)+block选择(0/1)+读写控制(1/0)
  • 等待从机应答:从机控制SDA拉低
  • 写入字节地址:word address(eeprom 中block中的地址)
  • 写入数据:SCLK低电平时,主机控制SDA写入数据
  • 发送停止位:stop

每一bit都需要一个sclk周期完成

控制字节

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fc0A9fnC-1642921692513)(C:\Users\DELL\Desktop\Revision\Protocol_Verilog\i2c_eeprom\doc\部分图\image-20220117215006419.png)]

(2)页写

  • 发送起始位:SCLK高电平时,SDA拉低
  • 写入控制字节:设备地址(1010)+任意两位(xx)+block选择(0/1)+读写控制(1/0)
  • 等待从机应答:从机控制SDA拉低
  • 写入字节地址:word address(eeprom 中block中的地址)
  • 写入数据:SCLK低电平时,主机控制SDA写入数据
  • .
  • .
  • .
  • 写入第16个数据
  • 发送停止位:stop
  • 每一bit都需要一个sclk周期完成

2. 读取EEPROM的数据(当前地址读/随机读/顺序读)

(1).当前地址读

verilog I2C_eeprom 手册分析及代码编写思路_第4张图片

(2).随机读,顺序读

verilog I2C_eeprom 手册分析及代码编写思路_第5张图片

  • 发送起始位:SCLK高电平时,SDA拉低
  • 写入控制字节:设备地址(1010)+任意两位(xx)+block选择(0/1)+写控制(0)
  • 等待从机应答:从机控制SDA拉低
  • 写入字节地址:word address(eeprom 中block中的地址)
  • 发送起始位:SCLK高电平时,SDA拉低
  • 写入控制字节:设备地址(1010)+任意两位(xx)+block选择(0/1)+读控制(1)
  • 读取数据:SCLK高电平时,读取数据SDA
  • .
  • .
  • .
  • 读取最后一个数据
  • 发送停止位:stop
  • 每一bit都需要一个sclk周期完成

四. 状态转移图以及代码

1.I2C接口(驱动)模块

(1).状态图

verilog I2C_eeprom 手册分析及代码编写思路_第6张图片

(2).接口代码

module eeprom_interface (
    input         clk       ,
    input         rst_n     ,
                             //ctrl模块传入
    input [7:0]   data_in   ,//写入eeprom的八位数据
    input         req       ,//请求一次读/写
    input [3:0]   command   ,//输入的命令:起/读/写/止
                             //传入ctrl模块
    output  reg   slack     ,//从机响应
    output        done      ,//一个字节读/写完成
    output [7:0]  data_out  ,//从EEPROM读取的数据
                             //与eeprom连接       
    output  reg   sclk      ,//从机时钟
    inout         sda        //从机数据线
);
/* 参数定义 */
localparam          IDLE   = 8'b00000_001,   //状态参数定义 
                    START  = 8'b00000_010,  
                    READ   = 8'b00000_100, 
                    WRITE  = 8'b00001_000,  
                    ACK    = 8'b00010_000,
                    SACK   = 8'b00100_000, 
                    STOP   = 8'b01000_000,
                    DONE   = 8'b1000_0000;

localparam          CMD_START   = 4'b1000,//起/读/写/止
                    CMD_READ    = 4'b0100,
                    CMD_WRITE   = 4'b0010,
                    CMD_STOP    = 4'b0001;

localparam          T_CLK        = 200_000   ,//从机频率200KHZ
                    T_CYCLE      = 250       ,//从机一个周期的时间
                    T_CYCLE_HALF = 124       ,  
                    T_LOW_HALF   = 65        ,  
                    T_HIGH_HALF  = 189       ;  
/* 信号定义 */
reg             sda_out   ;
wire            sda_in    ;
reg             sda_out_en;
reg    [7:0]    data_out_r;
  
reg    [7:0]    state_c;//状态
reg    [7:0]    state_n;

wire            idle_start ;
wire            idle_read  ;
wire            idle_write ;
wire            start_read ;
wire            start_write;
wire            write_sack ;
wire            read_ack   ;
wire            ack_stop   ;
wire            ack_done   ;
wire            sack_stop  ;
wire            sack_done  ;
wire            stop_done  ;
wire            done_idle  ;

reg  [7:0]      clk_cnt    ;//时钟计数器
wire            add_clk_cnt;
wire            end_clk_cnt;

reg  [3:0]      bit_cnt    ;//bit计数器
wire            add_bit_cnt;
wire            end_bit_cnt;



//状态转换
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        state_c <= IDLE;
    end 
    else begin 
        state_c <= state_n;
    end 
end



//状态转换条件
assign idle_start = state_c == IDLE  && req && (command & CMD_START);                    
assign idle_read  = state_c == IDLE  && req && (command & CMD_READ);                    
assign idle_write = state_c == IDLE  && req && (command & CMD_WRITE);                    
assign start_read = state_c == START && (command & CMD_READ) && end_bit_cnt;                   
assign start_write= state_c == START && (command & CMD_WRITE)&& end_bit_cnt;                            
assign write_sack = state_c == WRITE &&  end_bit_cnt;   
assign read_ack   = state_c == READ  &&  end_bit_cnt;
assign ack_stop   = state_c == ACK   && (command & CMD_STOP) && end_bit_cnt;
assign ack_done   = state_c == ACK   && !((command & CMD_STOP))&& end_bit_cnt;//注意位取反(每一位都取反)和逻辑取反(0/1)的区别
assign sack_stop  = state_c == SACK  && (command & CMD_STOP || ~slack) && end_bit_cnt; 
assign sack_done  = state_c == SACK  && (!(command & CMD_STOP))&& end_bit_cnt && slack ;  
assign stop_done  = state_c == STOP  && end_bit_cnt;  
assign done_idle  = state_c == DONE  && 1'b1; 
//状态转移规律
always @(*) begin
    case(state_c)
        IDLE  : 
                if (idle_start) begin//优先判断起始,否则会与读写冲突
                    state_n = START;            
                end 
                else if (idle_read) begin
                    state_n = READ;            
                end 
                else if (idle_write) begin
                    state_n = WRITE;            
                end 
                else begin
                    state_n = state_c;
                end
        START : 
                if (start_read) begin
                    state_n = READ;            
                end 
                else if (start_write) begin
                    state_n = WRITE;            
                end 
                else begin
                    state_n = state_c;
                end
        READ  : 
                if (read_ack) begin
                    state_n = ACK;            
                end 
                else begin
                    state_n = state_c;
                end
        WRITE : 
                if (write_sack) begin
                    state_n = SACK;            
                end 
                else begin
                    state_n = state_c;
                end
        ACK   : 
                if (ack_stop) begin
                    state_n = STOP;            
                end 
                else if (ack_done) begin
                    state_n = DONE;            
                end 
                else begin
                    state_n = state_c;
                end
        SACK  : 
                if (sack_stop) begin
                    state_n = STOP;            
                end 
                else if (sack_done) begin
                    state_n = DONE;            
                end 
                else begin
                    state_n = state_c;
                end
        STOP  : 
                if (stop_done) begin
                    state_n = DONE;            
                end 
                else begin
                    state_n = state_c;
                end
        DONE  : 
                if (done_idle) begin
                    state_n = IDLE;            
                end 
                else begin
                    state_n = state_c;
                end
        default : state_n <= state_c ;
    endcase
end

//创建从机时钟
//时钟计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        clk_cnt <= 0;
    end
    else if((state_c == IDLE) || (state_c == DONE))begin
        clk_cnt <= 0;
    end 
    else if(add_clk_cnt)begin 
            if(end_clk_cnt)begin 
                clk_cnt <= 0;
            end
            else begin 
                clk_cnt <= clk_cnt + 1;
            end 
    end
   else  begin
       clk_cnt <= clk_cnt;
    end
end 

assign add_clk_cnt = state_c != IDLE;
assign end_clk_cnt = add_clk_cnt && clk_cnt == T_CYCLE - 1;

//SCLK
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sclk <= 1'b1;
    end 
    else if (idle_start || idle_write || idle_read) begin
        sclk <= 1'b0;
    end
    else if(clk_cnt == T_CYCLE_HALF && add_clk_cnt)begin 
        sclk <= 1'b1;
    end 
    else if (stop_done || ack_done || sack_done) begin
        sclk <= 1'b1;
    end    
    else if (end_clk_cnt) begin
        sclk <= 1'b0;
    end

end

//位计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        bit_cnt <= 0;
    end 
    else if(add_bit_cnt)begin 
            if(end_bit_cnt)begin 
                bit_cnt <= 0;
            end
            else begin 
                bit_cnt <= bit_cnt + 1;
            end 
    end
   else  begin
       bit_cnt <= bit_cnt;
    end
end 

assign add_bit_cnt = (state_c == START || state_c == READ || state_c == WRITE || state_c == READ||state_c == ACK||state_c == SACK||state_c == STOP) && end_clk_cnt;
assign end_bit_cnt = add_bit_cnt && bit_cnt == ((state_c == READ || state_c==WRITE)?7:0);

//sda_out
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sda_out <= 1'b1;
    end 
    else if(state_c == START && clk_cnt == T_LOW_HALF)begin
        sda_out    <= 1'b1; 
    end
    else if(state_c == START && clk_cnt == T_HIGH_HALF)begin 
        sda_out    <= 1'b0; 
    end 
    else if(state_c == WRITE && clk_cnt == T_LOW_HALF)begin //写数据
        sda_out    <= data_in[7 - bit_cnt];   
    end 
    else if (state_c == ACK && clk_cnt == T_LOW_HALF && !(command &CMD_STOP)) begin
        sda_out    <= 1'b0; 
    end
    else if (state_c == ACK && clk_cnt == T_LOW_HALF && (command &CMD_STOP)) begin
        sda_out    <= 1'b1; 
    end
    else if (state_c == STOP && clk_cnt == T_LOW_HALF) begin //防止应答后sdaout最后为1达不到上升沿的效果
        sda_out    <= 1'b0;
    end
    else if (state_c == STOP && clk_cnt == T_HIGH_HALF) begin
        sda_out    <= 1'b1;
    end
end

//sda_out_en
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sda_out_en <= 0;
    end 
    else if(idle_start || idle_write || sack_stop || read_ack)begin //在开始,写,停止,主机应答需要控制数据线
        sda_out_en =1'b1;
    end 
    else if(write_sack || idle_read )begin //在从机应答,从eeprom读取数据需要从机控制总线
        sda_out_en = 1'b0;
    end 
    else if (ack_done || sack_done || stop_done) begin//释放数据线
        sda_out_en = 1'b0;
    end
end

//sda_in在SACK和READ状态下,sda低电平响应
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        data_out_r <= 0;
        slack <= 1'b1;
    end 
    else if(state_c == READ && clk_cnt == T_HIGH_HALF)begin 
       data_out_r[7-bit_cnt] <= sda_in ;
    end 
    else if (state_c == SACK && clk_cnt == T_HIGH_HALF ) begin
       slack <= ~sda_in; 
    end
end



assign sda_in = sda;
assign sda = sda_out_en?sda_out:1'bz;
assign data_out = data_out_r ;

assign done = (stop_done || sack_done || ack_done )?1'b1:1'b0;
endmodule //eeprom_interface

2. I2C控制模块

(1).状态图

verilog I2C_eeprom 手册分析及代码编写思路_第7张图片

(2).控制模块代码模块

module    eeprom_ctrl (
    input             clk       ,
    input             rst_n     ,
    input       [1:0] key_down  ,//控制读写请求

    input             slack     ,
    input             done      ,
    input       [7:0] data_rec  ,//从EEPROM读取的数据

    output  reg [7:0] data_send ,//写入eeprom的八位数据
    output  reg       req       ,//请求一次读/写
    output  reg [3:0] command    //输出的命令:起/读/写/止

);
/* 参数定义 */
localparam        IDLE  = 4'b0001,
                  READ  = 4'b0010,
                  WRITE = 4'b0100,
                  DONE  = 4'b1000;

localparam        BYTE_WRITE  = 3 ,//字节写/读,页写/读
                  PAGE_WRITE  = 18,
                  BYTE_READ   = 4 ,
                  PAGE_READ   = 19;

localparam        WORD_ADDR  = 8'b0001_0000;

localparam       CMD_START   = 4'b1010,//起/读/写/止
                 CMD_READ    = 4'b0100,
                 CMD_WRITE   = 4'b0010,
                 CMD_STOP    = 4'b0001;
/* 信号定义 */
reg  [7:0]  data_rec_r;
reg  [3:0]  state_c;
reg  [3:0]  state_n;

reg         r_req;
reg         w_req;


reg  [4:0]  byte_cnt;
wire        end_byte_cnt;
wire        add_byte_cnt;
reg  [4:0]   byte_sel;

wire    idle_read ;
wire    idle_write;
wire    read_done ;
wire    read_idle ;
wire    write_done;
wire    write_idle;
wire    done_idle ;


//状态转换
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        state_c <= IDLE;
    end 
    else begin 
        state_c <= state_n;
    end 
end

//状态转移条件
assign  idle_read  = state_c == IDLE  && r_req;  
assign  idle_write = state_c == IDLE  && w_req;  
assign  read_done  = state_c == READ  && (end_byte_cnt ); 
assign  read_idle  = state_c == READ  && (~end_byte_cnt && ~slack);//从机不响应会 中止数据传输
assign  write_done = state_c == WRITE && (end_byte_cnt );
assign  write_idle = state_c == WRITE && (~end_byte_cnt && ~slack);//从机不响应会 中止数据传输    
assign  done_idle  = state_c == DONE  && 1'b1;  

//状态转移规律
always @(*) begin
    case(state_c)
        IDLE : 
                if (idle_read) begin
                    state_n = READ;            
                end 
                else if (idle_write) begin
                    state_n = WRITE;
                end
                else begin
                    state_n = state_c;
                end
        READ : 
                if (read_done) begin
                    state_n = DONE;            
                end 
                else if (read_idle) begin
                    state_n = IDLE;           
                end 
                else begin
                    state_n = state_c;
                end
        WRITE : 
                if (write_done) begin
                    state_n = DONE;           
                end 
                else if (write_idle) begin
                    state_n = IDLE;           
                end 
                else begin
                    state_n = state_c;
                end
        DONE : 
                if (done_idle) begin
                    state_n = IDLE;            
                end 
                else begin
                    state_n = state_c;
                end
        default : state_n <= state_c ;
    endcase
end

//BYTE计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        byte_cnt <= 0;
    end 
    else if(add_byte_cnt)begin 
            if(end_byte_cnt)begin 
                byte_cnt <= 0;
            end
            else begin 
                byte_cnt <= byte_cnt + 1;
            end 
    end
   else  begin
       byte_cnt <= byte_cnt;
    end
end 

assign add_byte_cnt = done;
assign end_byte_cnt = add_byte_cnt && byte_cnt ==byte_sel - 1;


//r_req,w_req
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        r_req <= 0;
        w_req <= 0;
    end 
    else if(key_down[0])begin 
        r_req <= 1'b1;
    end 
    else if(key_down[1])begin 
        w_req <= 1'b1;
    end 
    else begin
        r_req <= 0;
        w_req <= 0;     
    end
end

//byte_sel
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        byte_sel <= 0;
    end 
    else if(key_down[0])begin 
        byte_sel <= BYTE_READ ;
    end 
    else if(key_down[1])begin 
        byte_sel <= BYTE_WRITE;
    end 
end

//data_send,req,command
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        data_send <= 0;
        req       <= 0;
        command   <= 0;
    end 
    else if (state_c == IDLE) begin
        data_send <= 0;
        req       <= 0;
        command   <= 0;
    end
    else if( end_byte_cnt) begin//读写一次结束
        req <= 1'b0;
    end
    else if(state_c == READ)begin 
        case(byte_cnt)
        0  : //写入设备地址
             begin 
                 req <= 1'b1;
                 data_send <= 8'b1010_1000;
                 command <= 4'b1010;
             end
        1  ://写入字节地址
            begin 
                req <= 1'b1;
                data_send <= WORD_ADDR;
                command <= 4'b0010;
            end 
        2  ://重新写入设备地址和起始位
            begin 
                req <= 1'b1;
                data_send <= 8'b1010_1001;
                command <= 4'b1010;
            end 
        3 ://最后一位读和发送停止位
            begin 
                req <= 1'b1;
                data_rec_r <= data_rec;
                command <= 4'b0101;
            end 
        default : 
            begin 
                req <= 1'b1;
                data_rec_r <= data_rec;
                command <= 4'b0100;
            end 
        endcase
    end 
    else if(state_c == WRITE)begin 
        case(byte_cnt)
        0  : //写入设备地址,起始位,存储块位,读写位
             begin 
                 req <= 1'b1;
                 data_send <= 8'b1010_1000;
                 command <= 4'b1010;
             end
        1  ://写入字节地址
            begin 
                req <= 1'b1;
                data_send <= WORD_ADDR;
                command <= 4'b0010;
            end 
        2  ://最后一位发送数据+停止位
            begin 
                req <= 1'b1;
                data_send <= 8'b1010_1111;
                command <= 4'b0011;
            end 
        default : 
            begin 
                req <= 1'b1;
                data_send <= 8'b1010_1111;
                command <= 4'b0010;
            end 
        endcase
    end 

end

endmodule

3.TOP顶层模块

(1).整体框架

verilog I2C_eeprom 手册分析及代码编写思路_第8张图片

(2).代码

module top (
    input          clk      ,
    input          rst_n    ,
    input  [1:0]   key_in   ,
                             //数码管模块
    output  [7:0]  seg_dig  ,
    output  [5:0]  seg_sel  ,
                             //EEPROM模块
    output         sclk     ,//eeprom时钟
    inout          sda      //数据线

);
wire [1:0] key_down;
wire [7:0] data_write;
wire [7:0] data_read;
wire [3:0] command;
wire       done   ;//一个字节是否读写完
wire       req    ;//请求一次读写
wire       slack  ;//从机应答

//eeprom接口模块
eeprom_interface u_eeprom_interface (
/*     input         */ .clk       (clk      )  ,
/*     input         */ .rst_n     (rst_n    )  ,
/*                   */ //ctrl模块传入
/*     input [7:0]   */ .data_in   (data_write  )  ,//写入eeprom的八位数据
/*     input         */ .req       (req      )  ,//请求一次读/写
/*     input [3:0]   */ .command   (command  )  ,//输入的命令:起/读/写/止
/*                   */ //传入ctrl模块
/*     output        */ .slack     (slack    )  ,//从机响应
/*     output        */ .done      (done     )  ,//一个字节读/写完成
/*     output [7:0]  */ .data_out  (data_read )  ,//从EEPROM读取的数据
/*                   */  //与eeprom连接       
/*     output        */ .sclk      (sclk     )  ,//从机时钟
/*     inout         */ .sda       (sda      )   //从机数据线
);

//eeprom控制模块
eeprom_ctrl u_eeprom_ctrl(
/*     input         */ .clk       (clk      ),
/*     input         */ .rst_n     (rst_n    ),
/*     input   [1:0] */ .key_down  (key_down ),
/*     input         */ .slack     (slack    ),
/*     input         */ .done      (done     ),
/*     input   [7:0] */ .data_rec  (data_read ),//从EEPROM读取的数据

/*     output  [7:0] */ .data_send (data_write),//写入eeprom的八位数据
/*     output        */ .req       (req       ) ,//请求一次读/写
/*     output  [3:0] */ .command   (command   ) //输入的命令:起/读/写/止

);

//按键模块
key_debounce u_key_debounce (
/* 	input					 */.clk		(clk	),
/* 	input					 */.rst_n	(rst_n	),
/* 	input		[KEY_W-1:0] */ .key_in  (key_in ),
/* 	 */ 
/* 	output	reg	[KEY_W-1:0] */ .key_out (key_down)	 //检测到按下,输出一个周期的高脉冲,其他时刻为0
);

seg u_seg(
   /*  input        */  .clk      (clk    ) ,
   /*  input        */  .rst_n    (rst_n  ) ,
   /*  input  [23:0 */  .data_in  ({4'b0,4'b0,4'b0,4'b0,data_read[7:4],data_read[3:0]}) ,//输入的数据

   /*  output [7:0] */  .seg_dig  (seg_dig) ,//数码管段选 + 小数点
   /*  output [5:0] */  .seg_sel  (seg_sel)  //数码管位选
);

endmodule //top

你可能感兴趣的:(fpga开发,verilog)