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

news2025/7/14 13:12:57

IIC协议读写EEPROM

  • 一、功能分析/模块划分
  • 二、状态转移图
    • 1、EEPROM读写控制状态转移图
    • 2、IIC接口驱动状态转移图
  • 三、工程代码实现
    • 1、顶层模块
    • 2、EEPROM读写控制模块
    • 3、IIC接口驱动模块
    • 4、参数配置
    • 5、其他模块
  • 四、仿真测试
  • 五、上板验证

写在前面
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的操作,感兴趣的小伙伴可以尝试一下页写和其他几种方式的读操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8551.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【附源码】计算机毕业设计JAVA教学辅助系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven Vue 等等组成&#xff0c;B/…

【Qt】控件探幽——QWidget

注1&#xff1a;本系列文章使用的Qt版本为Qt 6.3.1 注2&#xff1a;本系列文章常规情况下不会直接贴出源码供复制&#xff0c;都以图片形式展示。所有代码&#xff0c;自己动手写一写&#xff0c;记忆更深刻。 本文目录探索QWidget1、ui文件最后会变成什么&#xff1f;2、如何改…

在 OpenHarmony 轻量设备开发应用

本文档旨在讲解新建 Helloworld 项目步骤、固件包烧录到 BES2600WM 开发板、实现 js 和 C 代码的通讯。该 Demo 重点体现的是 OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09; 3.1 Beta 系统轻量设备 js 和 C 的交互能力&#xff0c; 效果如图 &#xf…

dpdk PMD

PMD是Poll Mode Driver的缩写&#xff0c;即基于用户态的轮询机制的驱动 在不考虑vfio的情况下&#xff0c;PMD的结构图如下 虽然PMD是在用户态实现设备驱动&#xff0c;但还是依赖于内核提供的策略。其中uio模块&#xff0c;是内核提供的用户态驱动框架&#xff0c;而igb_uio…

深度探讨react-hooks实现原理

react hooks 实现 Hooks 解决了什么问题 在 React 的设计哲学中&#xff0c;简单的来说可以用下面这条公式来表示&#xff1a; UI f(data)等号的左边时 UI 代表的最终画出来的界面&#xff1b;等号的右边是一个函数&#xff0c;也就是我们写的 React 相关的代码&#xff1b…

最新最全面的Spring详解(一)——Spring概述与IOC容器

前言 本文为 【Spring】Spring概述与IOC容器 相关知识&#xff0c;下边将对Spring概述&#xff0c;IOC容器&#xff08;包括&#xff1a;IOC概述、配置元数据、容器实例化与使用、Bean的概述、依赖注入 Dependency Injection、Bean 作用范围&#xff08;作用域&#xff09;、更…

计算机网络(二)

三、数据链路层 3.1 数据链路层概述 数据链路层在物理层提供的服务的基础上向网络层提供服务&#xff0c;其最基本的服务是将源自网络层来的数据可靠地传输到相邻节点的目标机网络层。数据链路层在不可靠的物理介质上提供可靠的传输。 该层的作用包括&#xff1a;物理地址寻址…

安装Redis

一、Windows安装 1、下载安装包 2、下载完毕得到压缩包 3、解压到自己电脑上的环境目录 4、开启redis&#xff0c;双击运行服务 5、使用redis客户端来连接redis 注意&#xff1a;Window下使用确实简单,但是Redis推荐我们使用Linux去开发使用! 二、Linux安装 1、官网下载…

everything常用搜索命令

参考&#xff1a;玩转Everything&#xff08;三&#xff09; https://baijiahao.baidu.com/s?id1735662355311796969&wfrspider&forpc 可右键菜单显示要显示的内容 指定目录搜索 例&#xff1a;e: 文件名 &#xff08;注意加空格&#xff09; 多目录内搜索 例&#x…

ModStartCMS v5.2.0 字段扩展支持,SiteMap增强

系统介绍 ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰…

多肽914910-73-9:血管紧张素Angiotensin(1-12)(mouse, rat)

血管紧张素 (1-12) 是局部生成血管紧张素的潜在前体。它在广泛的器官和组织中表达&#xff0c;包括小肠、脾脏、肝脏、肾脏和心脏。卡托普利和 CV-11974&#xff08;一种血管紧张素 II I 型受体拮抗剂&#xff09;可消除对静脉输注血管紧张素 (1-12) 的血管收缩和升压反应。编号…

【Linux详解】——环境变量

&#x1f4d6; 前言&#xff1a;本期将介绍Linux下的环境变量 目录&#x1f552; 1. 基本概念&#x1f558; 1.1 常见环境变量&#x1f558; 1.2 查看环境变量方法&#x1f558; 1.3 其他指令&#xff1a;&#x1f558; 1.4 环境变量的来源&#x1f552; 2. 环境变量的操作&…

2022亚太杯建模A题思路分析 小美赛数学建模 A题思路

一、 2022亚太杯大学生数学建模竞赛 注册截止日期&#xff1a;北京时间2022年11月23日&#xff08;星期三&#xff09;中午12点 竞赛开始时间&#xff1a;北京时间2022年11月24日&#xff08;星期四&#xff09;上午6点 竞赛结束时间&#xff1a;北京时间2022年11月28日&#…

OpenCV实战(2)——OpenCV核心数据结构

OpenCV实战&#xff08;2&#xff09;——OpenCV核心数据结构0. 前言1. cv::Mat 数据结构1.1 cv::Mat 简介1.2 cv::Mat 属性1.3 完整代码示例2. 探索 cv::Mat 数据结构2.1 cv::Mat 对象的创建2.2 OpenCV 输入和输出数组小结系列链接0. 前言 cv::Mat 类是用于保存图像(以及其他…

2022 SPSSPRO杯A|B|C题全网最全解题思路+数据分享

一&#xff0c;认证杯数学建模2022 ABC题干分析 2022年第十五届“SPSSPRO杯”数学中国数学建模网络挑战赛 2022认证杯数学中国数学建模网络挑战赛 认证杯这次叫spssrpo 二&#xff0c;A题 人员的紧急疏散 在过去的几十年里&#xff0c;由于大规模集会活动的数量和规模的增加…

大数据采集工具与采集业务划分

目录1- FlumeAgentSourceChannelSinkEvent2- Fluentd3- Logstash4- Chukwa5- Scribe6- Splunk7- Scrapy8- Kafka9- Datax10-日志采集11-数据源数据同步1- Flume https://flume.apache.org/ Flume是Cloudera提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志…

代码随想录56——动态规划:583两个字符串的删除操作、72编辑距离

文章目录1.583两个字符串的删除操作1.1.题目1.2.解答2.72编辑距离2.1.题目2.2.解答1.583两个字符串的删除操作 参考&#xff1a;代码随想录&#xff0c;583两个字符串的删除操作&#xff1b;力扣题目链接 1.1.题目 1.2.解答 本题和 动态规划&#xff1a;115.不同的子序列 相…

在Python中 先乘再除 和 先除再乘 是有差别的

浮点数的原因<font colorblue size4 face"楷体">1 问题来源<font colorblue size4 face"楷体">2 为什么会这样&#xff1f;<font colorblue size4 face"楷体">2.1 分解公式<font colorblue size4 face"楷体">…

最近面试被问到的vue题

v-for 为什么要加 key 如果不使用 key&#xff0c;Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记&#xff0c;通过这个 key&#xff0c;我们的 diff 操作可以更准确、更快速 更准确&#xff1a;因为…

初阶牛之牛客网刷题集(1)

前言 记录一下牛牛自己在牛客网上刷到的一些题目.分享一下牛牛的解题思路,希望可以帮到大家. 目录前言1.母牛的故事解题思路&#xff1a;代码实现&#xff1a;2.替换空格解题思路:代码实现3.二进制中1的个数解题思路代码实现结语1.母牛的故事 题目链接:传送门 有一头母牛&am…