【FPGA】FPGA实现IIC协议读写EEPROM(三) ----- 汇总篇

写在前面
FPGA实现IIC协议读写EEPROM相关文章:

IIC通信协议
【FPGA】FPGA实现IIC协议读写EEPROM(一) ----- IIC接口驱动实现
【FPGA】FPGA实现IIC协议读写EEPROM(二) -----EEPROM读写控制模块实现
【FPGA】FPGA实现IIC协议读写EEPROM(三) ----- 汇总篇

在前面几篇文章中介绍了IIC通信协议的相关时序、IIC接口驱动以及EEPROM读写控制模块的实现,在本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),EEPROM型号:24LC04B,本文将对IIC协议读写EEPROM工程进行综合。

一、功能分析/模块划分

下面介绍此工程需要实现哪些功能,以此进行功能模块划分。

  1. 通过按键发送读/写请求给EEPROM读写控制模块控制读写时序。
  2. 通过串口发送和接收读写数据。
  3. 通过数码管显示正在进行操作。
  4. 编写IIC接口驱动实现EEPROM和控制模块的通信时序。
  5. 编写EEPROM读写控制模块,实现读写时序控制。

通过上述功能分析,我们不难把工程进行模块划分,系统框图划分如下图所示:
在这里插入图片描述

二、状态转移图

1、EEPROM读写控制状态转移图

在这里插入图片描述
EEPROM读写控制状态机实现参考文章:EEPROM读写控制状态机

2、IIC接口驱动状态转移图

在这里插入图片描述
IIC接口驱动状态机参考文章:IIC接口驱动状态机

三、工程代码实现

1、顶层模块

顶层模块verilog代码如下:

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: i2c_eeprom    
//  Module Name: top         
//  Target Device: Cyclone IV E (EP4CE6F17C8), EEPROM(24LC04B)                
//  Tool versions: Quartus Prime 18.1             
//  Description: I2C读写EEPROM工程顶层设计模块
//  **************************************************************
module top (
    input           clk             ,
    input           rst_n           ,
    //  key_filter
    input   [1:0]   key_in          ,
    // input           wr_req          ,
    // input           rd_req          ,
    //  uart
    input           uart_rxd        ,
    output          uart_txd        ,
    //  eeprom
    inout           sda             ,
    output          scl             ,
    //  数码管
    output  [5:0]   seg_sel         ,
    output  [7:0]   seg_dig           
);

    wire            wr_req          ;
    wire            rd_req          ;

    wire            req             ;
    wire   [7:0]    wr_dout         ; 
    wire   [3:0]    cmd             ;
    wire   [7:0]    rdout           ;
    wire            rdout_vld       ;
    wire            rw_done         ;

    wire            sda_in          ;
    wire            sda_out         ;
    wire            sda_out_en      ;

    wire    [1:0]   rw_flag         ;
    //  uart_rx
    wire    [7:0]   rx_dout         ;
    wire            rx_dout_vld     ;
    //  uart_tx
    wire    [7:0]   tx_din          ;
    wire            tx_din_vld      ;
    wire            ready           ;

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

    key_filter u_key_filter1 (
    /*input           */.clk         (clk           ),
    /*input           */.rst_n       (rst_n         ),
    /*input           */.key_in      (key_in[0]     ),
    /*output    reg   */.key_out     (wr_req        )
    );

    key_filter u_key_filter2 (
    /*input           */.clk         (clk           ),
    /*input           */.rst_n       (rst_n         ),
    /*input           */.key_in      (key_in[1]     ),
    /*output    reg   */.key_out     (rd_req        )
    );

    uart_rx u_uart_rx (
    /*input               */.clk             (clk       ),
    /*input               */.rst_n           (rst_n     ),
    /*input               */.uart_rx         (uart_rxd  ),       //  接收到的串口数据
/**/
    /*output  [7:0]       */.rx_dout         (rx_dout   ),       //  串行数据转换成并行数据后输出给control模块
    /*output              */.rx_dout_vld     (rx_dout_vld)
    );

    uart_tx u_uart_tx (
    /*input               */.clk             (clk       ),
    /*input               */.rst_n           (rst_n     ),
    /*//  control*/
    /*input   [7:0]       */.tx_din          (tx_din    ),
    /*input               */.tx_din_vld      (tx_din_vld),
    /*output              */.ready           (ready     ),       //  给control模块的握手信号,表示可以接收数据进行发送 
    /*//  上位机*/
    /*output              */.uart_tx         (uart_txd  )
    );

    eeprom_ctrl eeprom_ctrl (
    /*input               */.clk                 (clk           ),
    /*input               */.rst_n               (rst_n         ),
    /*//  key_filter*/
    /*input               */.wr_req              (wr_req        ),       //  读请求
    /*input               */.rd_req              (rd_req        ),       //  写请求
    /*//  uart_rx*/
    /*input   [7:0]       */.rx_din              (rx_dout       ),       //  uart接收到的要发送的数据
    /*input               */.rx_din_vld          (rx_dout_vld   ),       //  数据有效标志
    /*//  uart_tx*/
    /*output  [7:0]       */.tx_dout             (tx_din        ),       //  读回的数据给到串口发送模块
    /*output              */.tx_dout_vld         (tx_din_vld    ),       //  数据有效
    /*input          m    */.ready               (ready         ),
    /*//  i2c_interface*/
    /*input   [7:0]       */.rdin                (rdout         ),       //  从I2C接口读回的数据
    /*input               */.rdin_vld            (rdout_vld     ),
    /*input               */.rw_done             (rw_done       ),       //  读写一字节数据完成标志
    /*output              */.req                 (req           ),       //  读写请求
    /*output  [7:0]       */.wr_dout             (wr_dout       ),       //  要发送的数据
    /*output  [3:0]       */.cmd                 (cmd           ),
    /*output  [1:0]       */.rw_flag             (rw_flag       )       //  读写标志 00:空闲   01:写   10:读       
    );


    i2c_interface u_i2c_interface (
    /*input               */.clk                 (clk           ),
    /*input               */.rst_n               (rst_n         ),

    /*//  eeprom_ctrl*/
    /*input               */.req                 (req           ),       //  读写请求
    /*input   [7:0]       */.wr_din              (wr_dout       ),       //  需要发送的一字节数据
    /*input   [3:0]       */.cmd                 (cmd           ),       //  控制命令组合
    /*output  [7:0]       */.rdout               (rdout         ),       //  读取的数据
    /*output              */.rdout_vld           (rdout_vld     ),       //  读取数据有效标志
    /*output              */.rw_done             (rw_done       ),       //  读写一字节完成标志
    /*//  EEPROM*/
    /*input               */.sda_in              (sda_in        ),
    /*output              */.sda_out             (sda_out       ),
    /*output              */.sda_out_en          (sda_out_en    ),
                            .scl                 (scl           )
    );

    seg_driver u_seg_driver (
        /*input           */.clk             (clk           ),
        /*input           */.rst_n           (rst_n         ),
        /*//  eeprom_ctrl*/
        /*// input   [7:0]din             ,  ()     //  要显示的数据*/
        /*// input        din_vld         ,  ()     //  显示数据有效标志*/
        /*input   [1:0]   */.rw_flag         (rw_flag       ),       //  读写标志
        /*//  数码管*/
        /*output  [5:0]   */.seg_sel         (seg_sel       ),
        /*output  [7:0]   */.seg_dig         (seg_dig       )             
    );
   

    
endmodule

2、EEPROM读写控制模块

EEPROM读写控制模块verilog代码如下:

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: i2c_eeprom    
//  Module Name: eeprom_ctrl         
//  Target Device: Cyclone IV E (EP4CE6F17C8) , EEPROM(24LC04B)               
//  Tool versions: Quartus Prime 18.1             
//  Description: 使用I2C协议读写EEPROM驱动控制模块
//  **************************************************************
module eeprom_ctrl (
    input               clk                 ,
    input               rst_n               ,
    //  key_filter
    input               wr_req              ,       //  写请求
    input               rd_req              ,       //  读请求
    //  uart_rx
    input   [7:0]       rx_din              ,       //  uart接收到的要发送的数据
    input               rx_din_vld          ,       //  数据有效标志
    //  uart_tx
    output  [7:0]       tx_dout             ,       //  读回的数据给到串口发送模块
    output              tx_dout_vld         ,       //  数据有效
    input               ready               ,
    //  i2c_interface
    input   [7:0]       rdin                ,       //  从I2C接口读回的数据
    input               rdin_vld            ,
    input               rw_done             ,       //  读写一字节数据完成标志
    output              req                 ,       //  读写请求
    output  [7:0]       wr_dout             ,       //  要发送的数据
    output  [3:0]       cmd                 ,
    //  seg_driver
    output  [1:0]       rw_flag                     //  读写标志 00:空闲   01:写   10:读         
);

    //  参数定义
    //  状态机参数定义
    localparam  IDLE        =   7'b000_0001 ,
                SEND_WRREQ  =   7'b000_0010 ,   //  发送写请求
                SEND_RDREQ  =   7'b000_0100 ,   //  发送读请求
                WAIT_WRDONE =   7'b000_1000 ,   //  等待写数据完成
                WAIT_RDDONE =   7'b001_0000 ,   //  等待读数据完成
                WR_DONE     =   7'b010_0000 ,   //  写数据完成
                RD_DONE     =   7'b100_0000 ;   //  读数据完成

    //  信号定义
    reg     [6:0]       state_c                 ;
    reg     [6:0]       state_n                 ;
    
    reg     [3:0]       byte_num                ;   //  字节数
    reg     [3:0]       cnt_byte                ;   //  字节计数器
    wire                add_cnt_byte            ;
    wire                end_cnt_byte            ;

    reg     [7:0]       wrdata                  ;   //  寄存将要发送的数据
    reg     [3:0]       command                 ;   //  寄存将要发送的命令
    reg                 rw_req                  ;

    reg     [1:0]       rwflag                  ;

    //  状态转移条件
    wire                idle2sendwrreq          ;
    wire                idle2sendrdreq          ;
    wire                sendwrreq2waitwrdone    ;
    wire                sendrdreq2waitrddone    ;
    wire                waitwrdone2wrdone       ;
    wire                waitwrdone2sendwrreq    ;
    wire                waitrddone2rddone       ;
    wire                waitrddone2sendrdreq    ;
    wire                wrdone2idle             ;
    wire                rddone2idle             ;

    reg     [7:0]       rxdata                  ;       //  串口接收数据寄存
    reg     [7:0]       txdata                  ;       //  串口发送数据寄存

    //  wrfifo
    // wire                wrfifo_empty            ;
    // wire                wrfifo_full             ;
    // wire    [2:0]       wrfifo_usedw            ;
    // wire    [7:0]       wrfifo_q                ;
    // wire                wrfifo_rdreq            ;
    // //  rdfifo
    // wire                rdfifo_empty            ;
    // wire                rdfifo_full             ;
    // wire    [2:0]       rdfifo_usedw            ;
    // wire    [7:0]       rdfifo_q                ;
    // wire                rdfifo_rdreq            ;

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

    always @(*)begin
        case (state_c)
            IDLE: begin
                if(idle2sendwrreq)
                    state_n = SEND_WRREQ ;
                else if(idle2sendrdreq)
                    state_n = SEND_RDREQ ;
                else
                    state_n = state_c ;
            end
            SEND_WRREQ: begin
                if(sendwrreq2waitwrdone)
                    state_n = WAIT_WRDONE ;
                else
                    state_n = state_c ;
            end
            SEND_RDREQ: begin
                if(sendrdreq2waitrddone)
                    state_n = WAIT_RDDONE ;
                else
                    state_n = state_c ;
            end
            WAIT_WRDONE: begin
                if(waitwrdone2wrdone)
                    state_n = WR_DONE ;
                else if(waitwrdone2sendwrreq)
                    state_n = SEND_WRREQ ;
                else
                    state_n = state_c ;
            end
            WAIT_RDDONE: begin
                if(waitrddone2rddone)
                    state_n = RD_DONE ;
                else if(waitrddone2sendrdreq)
                    state_n = SEND_RDREQ ;
                else
                    state_n = state_c ;
            end
            WR_DONE: begin
                if(wrdone2idle)
                    state_n = IDLE ;
                else
                    state_n = state_c ;
            end
            RD_DONE: begin
                if(rddone2idle)
                    state_n = IDLE ;
                else
                    state_n = state_c ;
            end
            default: state_n = IDLE ;
        endcase
    end
    //  状态转移
    assign idle2sendwrreq      = state_c == IDLE        && wr_req                       ;
    assign idle2sendrdreq      = state_c == IDLE        && rd_req                       ;  
    assign sendwrreq2waitwrdone= state_c == SEND_WRREQ  && (1'b1)                       ;
    assign sendrdreq2waitrddone= state_c == SEND_RDREQ  && (1'b1)                       ;
    assign waitwrdone2wrdone   = state_c == WAIT_WRDONE && end_cnt_byte                 ;
    assign waitwrdone2sendwrreq= state_c == WAIT_WRDONE && (cnt_byte <= 2) && rw_done   ;
    assign waitrddone2rddone   = state_c == WAIT_RDDONE && end_cnt_byte                 ;
    assign waitrddone2sendrdreq= state_c == WAIT_RDDONE && (cnt_byte <= 3) && rw_done   ;
    assign wrdone2idle         = state_c == WR_DONE     && (1'b1)                       ;
    assign rddone2idle         = state_c == RD_DONE     && (1'b1)                       ;

    //  byte_num
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            byte_num <= 0 ;
        end
        else if(wr_req)begin
            byte_num <= 3 ;
        end
        else if(rd_req)begin
            byte_num <= 4 ;
        end
        // else if(wrdone2idle | rddone2idle)begin
        //     byte_num <= 0 ;
        // end
    end

    //  cnt_byte
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_byte <= 0 ;
        end
        else if(add_cnt_byte)begin
            if(end_cnt_byte)begin
                cnt_byte <= 0 ;
            end				
            else begin	    
                cnt_byte <= cnt_byte + 1 ;
            end 		    
        end
    end                   
    assign	add_cnt_byte = rw_done ;
    assign	end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1) ;

    //  rw_req
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rw_req <= 1'b0 ;
        end
        else if(idle2sendwrreq | idle2sendrdreq | sendwrreq2waitwrdone | sendrdreq2waitrddone)begin
            rw_req <= 1'b1 ;
        end
        else begin
            rw_req <= 1'b0 ;
        end
    end

    //  wrdata   command
    always @(*)begin
        if(state_c == SEND_WRREQ || state_c == WAIT_WRDONE)begin
            case (cnt_byte)
                0: begin wrdata = 8'b1010_0000 ;command = 4'b1010 ; end    //  写控制字
                1: begin wrdata = 8'b1101_1001 ;command = 4'b1000 ; end    //  写地址
                2: begin wrdata = rxdata ;command = 4'b1001 ; end    //  写数据
                default: begin wrdata = 8'b0 ;command = 4'b0 ;end
            endcase
        end
        else if(state_c == SEND_RDREQ || state_c == WAIT_RDDONE)begin
            case (cnt_byte)
                0: begin wrdata = 8'b1010_0000 ;command = 4'b1010 ; end    //  写控制字
                1: begin wrdata = 8'b1101_1001 ;command = 4'b1000 ; end    //  读地址
                2: begin wrdata = 8'b1010_0001 ;command = 4'b1010 ; end    //  写读控制字
                3: begin wrdata = 8'b0 ;command = 4'b0101 ;      end    //  读数据
                default: begin wrdata = 8'b0 ;command = 4'b0 ;end     
            endcase
        end
        else begin
            wrdata = wrdata ;
            command = command ;
        end
    end

    //  rwflag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rwflag <= 0 ;
        end
        else if(wr_req)begin
            rwflag <= 2'b01 ;
        end
        else if(rd_req)begin
            rwflag <= 2'b10 ;
        end
    end

    //  rxdata
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rxdata <= 0 ;
        end
        else if(rx_din_vld)begin
            rxdata <= rx_din ;
        end
    end

    //  txdata
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
           txdata <= 0 ; 
        end
        else if(rdin_vld)begin
            txdata <= rdin ;
        end
    end

    //  wrfifo例化
    // wrfifo	wrfifo_inst (
	//     .aclr       ( ~rst_n        ),
	//     .clock      ( clk           ),
	//     .data       ( rx_din        ),
	//     .rdreq      ( wrfifo_rdreq  ),
	//     .wrreq      ( rx_din_vld    ),

	//     .empty      ( wrfifo_empty  ),
	//     .full       ( wrfifo_full   ),
	//     .q          ( wrfifo_q      ),
	//     .usedw      ( wrfifo_usedw  )
	// );
    // assign  wrfifo_rdreq = wr_req && ~wrfifo_empty ;

    // //  rdfifo例化
    // rdfifo	rdfifo_inst (
	//     .aclr       ( ~rst_n        ),
	//     .clock      ( clk           ),
	//     .data       ( rdin          ),
	//     .rdreq      ( rdfifo_rdreq  ),
	//     .wrreq      ( rdin_vld      ),

	//     .empty      ( rdfifo_empty  ),
	//     .full       ( rdfifo_full   ),
	//     .q          ( rdfifo_q      ),
	//     .usedw      ( rdfifo_usedw  )
	// );
    // assign  rdfifo_rdreq = rddone2idle && ~rdfifo_empty && ready;

    //  输出
    assign  req = rw_req ;
    assign  cmd = command ;
    assign  wr_dout = wrdata ;
    assign  rw_flag = rwflag ;
    assign  tx_dout = txdata ;
    assign  tx_dout_vld = rddone2idle ;

endmodule                               

3、IIC接口驱动模块

EEPROM读写控制模块verilog代码如下:

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: i2c_eeprom    
//  Module Name: i2c_interface         
//  Target Device: Cyclone IV E (EP4CE6F17C8), EEPROM(24LC04B)             
//  Tool versions: Quartus Prime 18.1             
//  Description: I2C接口驱动模块
//  **************************************************************
`include "param.v"

module i2c_interface (
    input               clk                 ,
    input               rst_n               ,

    //  eeprom_ctrl
    input               req                 ,       //  读写请求
    input   [7:0]       wr_din              ,       //  需要发送的一字节数据
    input   [3:0]       cmd                 ,       //  控制命令组合
    output  [7:0]       rdout               ,       //  读取的数据
    output              rdout_vld           ,       //  读取数据有效标志
    output              rw_done             ,       //  读写一字节完成标志
    //  EEPROM
    input               sda_in              ,
    output              sda_out             ,
    output              sda_out_en          ,
    output              scl
);
    //  参数定义
    localparam  IDLE    =   7'b000_0001 ,
                START   =   7'b000_0010 ,
                WR_DATA =   7'b000_0100 ,
                RD_DATA =   7'b000_1000 ,
                REC_ACK =   7'b001_0000 ,
                SEND_ACK=   7'b010_0000 ,
                STOP    =   7'b100_0000 ;

    //  信号定义
    reg     [6:0]       state_c         ;
    reg     [6:0]       state_n         ;

    reg     [7:0]       cnt_scl         ;       //  scl周期计数器
    wire                add_cnt_scl     ;
    wire                end_cnt_scl     ;

    reg     [3:0]       bit_num         ;       //  bit数
    reg     [3:0]       cnt_bit         ;       //  bit计数器
    wire                add_cnt_bit     ;
    wire                end_cnt_bit     ;

    reg     [7:0]       dout_data       ;       //  发送数据寄存
    reg     [3:0]       command         ;       //  控制命令寄存

    reg                 scl_dout         ;       //  SDA寄存     
    reg                 sda_dout        ;
    reg                 sda_dout_en     ;

    reg     [7:0]       rx_data         ;       //  接收读回的数据
    reg                 ack_flag        ;       //  ack响应标志


    //  状态转移条件
    wire                idle2start      ;
    wire                idle2wrdata     ;
    wire                idle2rddata     ;
    wire                start2wrdata    ;
    wire                start2rddata    ;
    wire                wrdata2recack   ;
    wire                rddata2sendack  ;
    wire                recack2idle     ;
    wire                recack2stop     ;
    wire                sendack2idle    ;
    wire                sendack2stop    ;
    wire                stop2idle       ;

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

    always @(*)begin
        case (state_c)
            IDLE: begin
                if(idle2start)
                    state_n = START ;
                else if(idle2wrdata)
                    state_n = WR_DATA ;
                else if(idle2rddata)
                    state_n = RD_DATA ;
                else
                    state_n = state_c ;
            end
            START: begin
                if(start2wrdata)
                    state_n = WR_DATA ;
                else if(start2rddata)
                    state_n = RD_DATA ;
                else
                    state_n = state_c ;
            end
            WR_DATA: begin
                if(wrdata2recack)
                    state_n = REC_ACK ;
                else
                    state_n = state_c ;
            end
            RD_DATA: begin
                if(rddata2sendack)
                    state_n = SEND_ACK ;
                else
                    state_n = state_c ;
            end
            REC_ACK: begin
                if(recack2idle)
                    state_n = IDLE ;
                else if(recack2stop)
                    state_n = STOP ;
                else
                    state_n = state_c ;
            end
            SEND_ACK: begin
                if(sendack2idle)
                    state_n = IDLE ;
                else if(sendack2stop)
                    state_n = STOP ;
                else
                    state_n = state_c ;
            end
            STOP: begin
                if(stop2idle)
                    state_n = IDLE ;
                else
                    state_n = state_c ;
            end
            default: state_n = IDLE ;
        endcase
    end

    //  状态转移条件
    assign  idle2start      = state_c == IDLE       && req && (cmd & `STA)                      ;
    assign  idle2wrdata     = state_c == IDLE       && req && (cmd & `WRITE)                    ;
    assign  idle2rddata     = state_c == IDLE       && req && (cmd & `READ)                     ;
    assign  start2wrdata    = state_c == START      && end_cnt_bit && (command & `WRITE)        ;   
    assign  start2rddata    = state_c == START      && end_cnt_bit && (command & `READ)         ;   
    assign  wrdata2recack   = state_c == WR_DATA    && end_cnt_bit                              ;
    assign  rddata2sendack  = state_c == RD_DATA    && end_cnt_bit                              ;
    assign  recack2idle     = state_c == REC_ACK    && end_cnt_bit && (command & `STO) == 0     ;
    assign  recack2stop     = state_c == REC_ACK    && end_cnt_bit && (command & `STO)          ;
    assign  sendack2idle    = state_c == SEND_ACK   && end_cnt_bit && (command & `STO) == 0     ;
    assign  sendack2stop    = state_c == SEND_ACK   && end_cnt_bit && (command & `STO)          ;
    assign  stop2idle       = state_c == STOP       && end_cnt_bit                              ;

    //  cnt_scl  200KHz  一个SCL周期为250个系统时钟周期
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_scl <= 0 ;
        end
        else if(add_cnt_scl)begin
            if(end_cnt_scl)begin
                cnt_scl <= 0 ;
            end				
            else begin	    
                cnt_scl <= cnt_scl + 1 ;
            end 		    
        end
    end                 
    assign	add_cnt_scl	= state_c != IDLE ;
    assign	end_cnt_scl	= add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;

    //  bit_num
    always @(*)begin
        if(state_c == RD_DATA || state_c == WR_DATA)begin
            bit_num = 8 ;
        end
        else begin
            bit_num = 1 ;
        end
    end

    //  cnt_bit
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_bit <= 0 ;
        end
        else if(add_cnt_bit)begin
            if(end_cnt_bit)begin
                cnt_bit <= 0 ;
            end				
            else begin	    
                cnt_bit <= cnt_bit + 1 ;
            end 		    
        end
    end           
    assign	add_cnt_bit	= end_cnt_scl ;
    assign	end_cnt_bit	= add_cnt_bit && (cnt_bit == bit_num - 1) ;

    //  dout_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            dout_data <= 0 ;
        end
        else if(req)begin
            dout_data <= wr_din ;
        end
    end

    //  command
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            command <= 0 ;
        end
        else if(req)begin
            command <= cmd ;
        end
    end

    //  scl_dout
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            scl_dout <= 1'b1 ;
        end
        else if(idle2start | idle2wrdata | idle2rddata)begin
            scl_dout <= 1'b0 ;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
            scl_dout <= 1'b1 ;
        end
        else if(end_cnt_scl && ~stop2idle)begin
            scl_dout <= 1'b0 ;
        end
    end

    //  sda_dout_en
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sda_dout_en <= 1'b0 ;
        end
        else if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)begin
            sda_dout_en <= 1'b1 ;
        end
        else if(idle2rddata || start2rddata || wrdata2recack || stop2idle)begin
            sda_dout_en <= 1'b0 ;
        end
    end

    //  sda_dout
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sda_dout <= 1'b1 ;
        end
        else if(state_c == START)begin
            if(cnt_scl == `HIGH_HALF)begin
                sda_dout <= 1'b0 ;
            end
            else if(cnt_scl == `LOW_HALF)begin
                sda_dout <= 1'b1 ;
            end
        end
        else if(state_c == WR_DATA && cnt_scl == `LOW_HALF)begin
            sda_dout <= dout_data[7 - cnt_bit] ;
        end
        else if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)begin
            sda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;
        end
        else if(state_c == STOP)begin
            if(cnt_scl == `LOW_HALF)begin       
                sda_dout <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    
                sda_dout <= 1'b1;               
            end 
        end
        else if(wrdata2recack | rddata2sendack)begin
            sda_dout <= 1'b1;  
        end
    end
    //  rx_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rx_data <= 0 ;
        end
        else if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)begin
            rx_data[7 - cnt_bit] <= sda_in ;
        end
    end

    //  ack_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            ack_flag <= 1'b0 ;
        end
        else if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)begin
            ack_flag <= ~sda_in ;
        end
        else begin
            ack_flag <= 1'b0 ;
        end
    end

    //  输出
    assign rdout = rx_data ;   
    assign rdout_vld = rddata2sendack ;
    assign rw_done = stop2idle | sendack2idle | recack2idle ;
    assign sda_out = sda_dout ; 
    assign sda_out_en = sda_dout_en ;
    assign scl = scl_dout ;
    
endmodule     

4、参数配置

参数配置文件param.v如下:

//  IIC时钟参数
`define  SCL_PERIOD  250
`define  SCL_HALF    125
`define  LOW_HALF    65 
`define  HIGH_HALF   190

//  控制命令
`define  WRITE    4'b1000     //  写
`define  READ     4'b0100     //  读
`define  STA      4'b0010     //  起始位
`define  STO      4'b0001     //  停止位

//  UART参数配置文件
`define BAUD_RATE_115200

`define CLK_FREQ 50_000_000

`ifdef BAUD_RATE_9600
    `define BAUD `CLK_FREQ / 9600
`elsif BAUD_RATE_19200
    `define BAUD `CLK_FREQ / 19200
`elsif BAUD_RATE_38400
    `define BAUD `CLK_FREQ / 38400
`elsif BAUD_RATE_57600
    `define BAUD `CLK_FREQ / 57600
`elsif BAUD_RATE_115200
    `define BAUD `CLK_FREQ / 115200
`endif 

5、其他模块

按键消抖、数码管显示模块比较简单,这里就不过多赘述。
串口数据收发参考文章:UART串口通信

四、仿真测试

1、仿真文件如下:

`timescale 1ns/1ps

module test_tb ();
    reg     tb_clk          ;
    reg     tb_rst_n        ;

    reg     [1:0]       key_in;
    wire    sda             ;
    wire    scl             ;

    wire    [7:0]       seg_dig     ;
    wire    [5:0]       seg_sel     ;

    reg     [7:0]       data         ;
    reg                 data_vld     ;
    wire                ready       ;
    //  PC
    wire                uart_txd    ;
    wire                uart_rxd    ;

    uart_tx uart_tx_pc (
    /*input               */.clk             (tb_clk       ),
    /*input               */.rst_n           (tb_rst_n     ),
    /*//  control*/
    /*input   [7:0]       */.tx_din          (data    ),
    /*input               */.tx_din_vld      (data_vld),
    /*output              */.ready           (ready     ),       //  给control模块的握手信号,表示可以接收数据进行发送 
    /*//  上位机*/
    /*output              */.uart_tx         (uart_txd  )
    );

    top u_top (
        /*input           */.clk             (tb_clk    ),
        /*input           */.rst_n           (tb_rst_n  ),
        /*//  key_filter*/
        //input           */.wr_req          (wr_req    ),
        //input           */.rd_req          (rd_req    ),
        /*input   [1:0]   */.key_in          (key_in    ),
        //  uart
        /*input           */.uart_rxd        (uart_txd  ),
        /*input           */.uart_txd        (uart_rxd  ),
        /*//  eeprom*/
        /*inout           */.sda             (sda       ),
        /*output          */.scl             (scl       ),
        //  数码管
        /*output  [5:0]   */.seg_sel         (seg_sel   ),
        /*output  [7:0]   */.seg_dig         (seg_dig   ) 
    );

    M24LC04B u_M24LC04B(
        .A0             (0              ),
        .A1             (0              ),
        .A2             (0              ),
        .WP             (0              ),

        .SDA            (sda            ),
        .SCL            (scl            ),

        .RESET          (~tb_rst_n         )
    );

    parameter CYCLE = 20;
    defparam  u_top.u_key_filter1.DELAY = 10 ;
    defparam  u_top.u_key_filter2.DELAY = 10 ;

    always #(CYCLE / 2) tb_clk = ~tb_clk;

    initial begin
        tb_clk = 1'b1 ;
        tb_rst_n = 1'b1 ;
        key_in = 0 ;
        data = 0 ;
        data_vld = 1'b0 ;

        #(CYCLE * 2);
        #2;
        tb_rst_n = 1'b0 ;
        #(CYCLE * 2);
        tb_rst_n = 1'b1 ;
        #(CYCLE * 20);

        Send(8'ha9);

        key_in[0] = 1'b1 ;
        #(CYCLE * 20) ;
        key_in[0] = 1'b0 ;

        #(CYCLE * 100 * 100 * 30) ;
        key_in[1] = 1'b1 ;
        #(CYCLE * 20) ;
        key_in[1] = 1'b0 ;

        #(CYCLE * 100 * 100 * 10) ;
        $stop ;
    end

    task Send;
        input   [7:0]   send_data ;
        begin
            data = send_data ;
            data_vld = 1'b1 ;
            # CYCLE ;
            data_vld = 1'b0 ;
            // @(posedge ready)
            # (CYCLE*10) ;
        end
    endtask

endmodule

2、仿真结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:
1. 24LC04B读数据只能读到0,无法读1,当读1的时候显示的是高阻状态。
2. 仿真的时候写操作完成后,延时5ms再开始读操作。

五、上板验证

在这里插入图片描述

总结:此工程主要实现IIC单字节读写EEPROM的操作,感兴趣的小伙伴可以尝试一下页写和其他几种方式的读操作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值