• FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)


    FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)
    FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)
    FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(3)

    工程目的

    使用按键控制数据写入或读出 EEPROM。使用写控制按键向 EEPROM 中写入数据 1-10 共 10 字节数据,使用读控制按键读出之前写入到 EEPROM 的数据,并将读出的数据在数码管上显示出来。

    I2C 单字节写操作

    在这里插入图片描述
    单字节写操作时序图(单字节存储地址)
    在这里插入图片描述
    单字节写操作时序图(2 字节存储地址)

    参照时序图,列出单字节写操作流程如下:
    (1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;
    (2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);
    (3) 先向从机写入高 8 位地址,且高位在前低位在后;
    (4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2字节地址,跳转到步骤(6);
    (5) 按高位在前低位在后的顺序写入单字节存储地址;
    (6) 地址写入完成,主机接收到从机回传的应答信号后,开始单字节数据的写入;
    (7) 单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,单字节数据写入完成。

    I2C 随机读操作

    在这里插入图片描述
    随机读操作时序图(单字节存储地址)

    在这里插入图片描述

    随机读操作时序图(2 字节存储地址)

    参照时序图,列出页写时序操作流程如下:
    (1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;
    (2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);
    (3) 先向从机写入高 8 位地址,且高位在前低位在后;
    (4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2字节地址,跳转到步骤(6);
    (5) 按高位在前低位在后的顺序写入单字节存储地址;
    (6) 地址写入完成,主机接收到从机回传的应答信号后,主机再次向从机发送一个起始信号;

    EEPROM 字节读写整体框图

    在这里插入图片描述

    模块功能简介

    在这里插入图片描述

    I2C 驱动模块

    模块框图

    I2C 驱动模块的主要功能是按照 I2C 协议对 EERPROM 存储芯片执行数据读写操作。

    在这里插入图片描述
    I2C 驱动模块框图和输入输出端口简介
    在这里插入图片描述
    在这里插入图片描述

    wr_en、rd_en 为写使能信号,由数据收发模块生成并传入,高电平有效;
    i2c_start 信号为单字节数据读/写开始信号, i2c_start 信号同时传入的还有数据存储地址 byte_addr 和待写入字节数据wr_data;
    写使能 wr_en 和 i2c_start 信号同时有效,模块执行单字节数据写操作,按照数据存储地址 byte_addr,向 EEPROM 对应地址写入数据 wr_data;
    读使能信号 rd_en 和i2c_start 信号同时有效,模块执行单字节数据读操作,按照数据存储地址 byte_addr 读取EEPROM 对应地址中的数据;

    I2C 设备存储地址有单字节和 2 字节两种,为了应对这一情况,我们向模块输入 addr_num 信号,当信号为低电平时,表示 I2C 设备存储地址为单字节,在进行数据读写操作时只写入数据存储地址 byte_addr 的低 8 位;当信号为高电平时,表示 I2C 设备存储地址为 2 字节,在进行数据读写操作时要写入数据存储地址 byte_addr 的全部 16 位。

    为了后面的通用性,我们这是使用了单字节操作,如果想实现数据的连续读写,可以持续拉高读写使能信号,同时要保证开始信号的使能,这样就能够实现数据连续读取或写入。

    跨时钟域处理

    输出信号中,i2c_clk 是本模块的工作时钟,由系统时钟 sys_clk 分频而来,它的时钟频率为串行时钟 i2c_scl 频率的 4 倍,时钟信号 i2c_clk 要传入数据收发模块(i2c_rw_data)作为模块的工作时钟;输出给数据收发模块(i2c_rw_data)的单字节数据读/写结束信号i2c_end,高电平有效,表示一次单字节数据读/写操作完成;rd_data 信号表示自 EEPROM读出的单字节单字节数据,输出至数据收发模块(i2c_rw_data);i2c_scl、i2c_sda 分别是串行时钟信号和串行数据信号,由模块产生传入 EEPROM 存储芯片。

    状态转移图

    参照 I2C 设备单字节写操作和随机读操作的操作流程
    在这里插入图片描述
    系统上电后,状态机处于 IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start 后,状态机跳转到 START_1(起始状态);FPGA 向 EEPROM 存储芯片发送起始信号;随后状态机跳转到 SEND_D_ADDR(发送器件地址状态),在此状态下向 EEPROM 存储芯片写入控制指令,控制指令高 7 位为器件地址,最低位为读写控制字,写入“0”,表示执行写操作;控制指令写入完毕后,状态机跳转ACK_1(应答状态)。

    在 ACK_1(应答状态)状态下,要根据存储地址字节数进行不同状态的跳转。当 FPGA接收到 EEPROM 回 传 的 应 答 信 号 且 存 储 地 址 字 节 为 2 字 节 , 状 态 机 跳 转 到SEND_B_ADDR_H(发送高字节地址状态),将存储地址的高 8 位写入 EEPROM,写入完成后,状态机跳转到 ACK_2(应答状态);FPGA 接收到应答信号后,状态机跳转到SEND_B_ADDR_L(发送低字节地址状态);当 FPGA 接收到 EEPROM 回传的应答信号且存储地址字节为单字节,状态机状态机直接跳转到SEND_B_ADDR_L(发送低字节地址状态);在此状态低 8 位存储地址或单字节存储地址写入完成后,状态机跳转到 ACK_3(应答状态)。

    在 ACK_3(应答状态)状态下,要根据读/写使能信号做不同的状态跳转。当 FPGA 接收到应答信号且写使能信号有效,状态机跳转到 WR_DATA(写数据状态);在写数据状态,向 EEPROM 写入单字节数据后,状态机跳转到 ACK_4(应答状态);待 FPGA 接收到有效应答信号后,状态机跳转到 STOP(停止状态);当 FPGA 接收到应答信号且读使能信号有效,状态机跳转到 START_2(起始状态);再次向EEPROM 写入起始信号,状态跳转到SEND_RD_ADDR(发送读控制状态);再次向 EEPROM 写入控制字节,高 7 位器件地址不变,读写控制位写入“1”,表示进行读操作,控制字节写入完毕后,状态机跳转到ACK_5(应答状态);待 FPGA 接收到有效应答信号后,状态机跳转到 RD_DATA(读数据状态);在 RD_DATA(读数据状态)状态,EEPROM 向 FPGA 发送存储地址对应存储单元下的单字节数据,待数据读取完成户,状态机跳转到 N_ACK(无应答状态),在此状态下向EEPROM 写入一个时钟的高电平,表示数据读取完成,随后状态机跳转到 STOP(停止状态)。在 STOP(停止状态)状态,FPGA 向 EEPROM 发送停止信号,一次单字节数据读/写操作完成,随后状态机跳回 IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

    波形分析

    单字节写操作局部波形图(一)

    单字节写操作局部波形图(一)

    单字节写操作局部波形图(二)

    在这里插入图片描述

    第一部分:输入信号说明

    本模块的输入信号有 8 路,其中 7 路信号与单字节写操作有关。系统时钟信号 sys_clk和复位信号 sys_rst_n 不必多说,这是模块正常工作必不可少的;写使能信号 wr_en、 单字节数据读/写开始信号 i2c_start,只有在两信号同时有效时,模块才会执行单字节数据写操作,若 wr_en 有效时,i2c_start 信号 n 次有效输入,可以实现 n 个字节的连续写操作;addr_num 信号为存储地址字节数标志信号,赋值为 0 时,表示 I2C 设备存储地址为单字节,赋值为 1 时,表示 2C 设备存储地址为 2 字节,本实验使用的 EEPROM 存储芯片的存储地址位 2 字节,此信号恒为高电平;信号 byte_addr 为存储地址;wr_data 表示要写入该地址的单字节数据。

    i2c_clk 信号波形图

    在这里插入图片描述

    第二部分:时钟信号计数器 cnt_clk 和输出信号 i2c_clk 的设计与实现

    串行时钟 scl 的时钟频率为 250KHz,我们要生成的新时钟 i2c_clk 的频率要是 scl 的 4倍,之所以这样是为了后面更好的生成 scl 和 sda,所以 i2c_clk 的时钟频率为 1MHz。经计算,cnt_clk 要在 0-24 内循环计数,每个系统时钟周期自加 1;cnt_clk 每计完一个周期,i2c_clk 进行一次取反,最后得到 i2c_clk 为频率 1MHz 的时钟,本模块中其他信号的生成都以此信号为同步时钟。

    第三部分:状态机相关信号波形的设计与实现

    前文理论部分提到,输出至 EEPROM 的串行时钟 scl 与串行数据 sda 只有在进行数据读写操作时有效,其他时刻始终保持高电平。由前文状态机相关讲解可知,除 IDLE(初始状态)状态之外的其他状态均属于数据读写操作的有效部分,所以声明一个使能信号cnt_i2c_clk_en,在除 IDLE(初始状态)状态之外的其他状态保持有效高电平,作为 I2C 数据读写操作使能信号。

    我们使用 50MHz 系统时钟生成了 1MHz 时钟 i2c_clk,但输出至 EEPROM 的串行时钟scl 的时钟频率为 250KHz,我们声明时钟信号计数器 cnt_i2c_clk,作为分频计数器,对时钟 i2c_clk 时钟信号进行计数,初值为 0,计数范围为 0-3,计数时钟为 i2c_clk 时钟,每个时钟周期自加 1,实现时钟 i2c_clk 信号的 4 分频,生成串行时钟 scl。同时计数器cnt_i2c_clk 也可作为生成串行数据 sda 的约束条件,以及状态机跳转条件。

    计数器 cnt_i2c_clk 循环计数一个周期,对应串行时钟 scl 的 1 个时钟周期以及串行数据 sda 的 1 位数据保持时间,进行数据读写操作时,传输的指令、地址以及数据,位宽为固定的 8 位数据,我们声明一个比特计数器 cnt_bit,对计数器 cnt_i2c_clk 的计数周期进行计数,可以辅助串行数据 sda 的生成,同时作为状态机状态跳转的约束条件。

    输出的串行数据 sda 作为一个双向端口,主机通过它向从机发送控制指令、地址以及数据,接收从机回传的应答信号和读取数据。回传给主机的应答信号是实现状态机跳转的条件之一。声明信号 sda_in 作为串行数据 sda 缓存,声明 ack 信号作为应答信号,ack 信号只在状态机处于各应答状态时由 sda_in 信号赋值,此时为从机回传的应答信号,其他状态时钟保持高电平。

    状态机状态跳转的各约束条件均已介绍完毕,声明状态变量 state,结合各约束信号,单字节写操作状态机跳转流程如下:
    系统上电后,状态机处于 IDLE(初始状态),接收到有效的单字节数据读/写开始信号i2c_start 后,状态机跳转到 START_1(起始状态),同时使能信号cnt_i2c_clk_en 拉高、计数器 cnt_i2c_clk、cnt_bit 开始计数,开始数据读写操作;
    在 START_1(起始状态)状态保持一个串行时钟周期,期间 FPGA 向 EEPROM 存储芯片发送起始信号,一个时钟周期过后,计数器 cnt_ i2c_clk 完成一个周期计数,计数器 cnt_i2c_clk 计数到最大值 3,状态机跳转到 SEND_D_ADDR(发送器件地址状态);
    计数器 cnt_i2c_clk、cnt_bit 同时归 0,重新计数,计数器 cnt_i2c_clk 每计完一个周期,cnt_bit 自加 1,当计数器 cnt_i2c_clk 完成 8 个计数周期后,cnt_bit 计数到 7,实现 8 个比特计数,器件 FPGA 按照时序向 EEPROM 存储芯片写入控制指令,控制指令高 7 位为器件地址,最低位为读写控制字,写入“0”,表示执行写操作。当计数器 cnt_ i2c_clk 计数到最大值 3、cnt_bit 计数到 7,两计数器同时归 0,状态机跳转到转到 ACK_1(应答状态);

    在 ACK_1(应答状态)状态下,计数器 cnt_i2c_clk、cnt_bit 重新计数,当计数器 cnt_i2c_clk 计 数 到 最 大 值 3 , 且 应 答 信 号 ack 为 有 效 的 低电平,状态机跳转到SEND_B_ADDR_H(发送高字节地址状态),两计数器清 0;此状态下,FPGA 将存储地址的高 8 位按时序写入 EEPROM,当计数器 cnt_ i2c_clk 计数到 3、cnt_bit 计数到 7,状态机跳转到 ACK_2(应答状态), 两计数器清 0;

    ACK_2 状态下,当计数器 cnt_ i2c_clk 计数到 3,且应答信号 ack 为有效的低电平,状态机跳转到 SEND_B_ADDR_L(发送低字节地址状态) ,两计数器清 0;在此状态下,低 8 位存储地址按时序写入 EEPROM,计数器 cnt_ i2c_clk 计数到 3、cnt_bit 计数到 7,状态机跳转到 ACK_3(应答状态);
    在 ACK_3(应答状态)状态下,当 cnt_ i2c_clk 计数 3、应答信号 ack 有效,且写使能信号 wr_en 有效,状态机跳转到 WR_DATA(写数据状态);
    在写数据状态,按时序向 EEPROM 写入单字节数据,计数器 cnt_ i2c_clk 计数到 3、cnt_bit 计数到 7,状态机跳转到 ACK_4(应答状态);
    在 ACK_4(应答状态)状态下,当 cnt_ i2c_clk 计数 3、应答信号 ack 有效,状态机跳转到 STOP(停止状态)状态;
    在 STOP(停止状态)状态,FPGA 向 EEPROM 发送停止信号,一次单字节数据读/写操作完成,随后状态机跳回 IDLE(初始状态),等待下一次单字节数据读/写开始信号i2c_start。

    状态机相关信号波形如下
    在这里插入图片描述
    状态机相关信号波形图
    在这里插入图片描述
    状态机相关信号波形图

    第四部分:输出串行时钟 i2c_scl、串行数据信号 i2c_sda 及相关信号的波形设计与实现
    串口数据 sda 端口作为一个双向端口,在单字节读取操作中,主机只在除应答状态之外的其他状态拥有它的控制权,在应答状态下主机只能接收由从机通过 sda 传入的应答信号。声明使能信号 sda_en,只在除应答状态之外的其他状态赋值为有效的高电平,sda_en有效时,主机拥有对 sda 的控制权。

    声明 i2c_sda_reg 作为输出 i2c_sda 信号的数据缓存,在 sda_en 有效时,将 i2c_sda_reg的值赋值给输出串口数据 i2c_sda,sda_en 无效时,输出串口数据 i2c_sda 为高阻态,主机放弃其控制权,接收其传入的应答信号。

    i2c_sda_reg 在使能信号 sda_en 无效时始终保持高电平,在使能 sda_en 有效时,在状态机对应状态下,以计数器 cnt_ i2c_clk、cnt_bit 为约束条件,对应写入起始信号、控制指令、存储地址、写入数据、停止信号。

    对于输出的串行时钟 i2c_clk,由 I2C 通讯协议可知,I2C 设备只在串行时钟为高电平时进行数据采集,在串行时钟低电平时实现串行数据更新。我们使用计数器 cnt_ i2c_clk、cnt_bit 以及状态变量 state 为约束条件,结合 I2C 通讯协议,生成满足时序要求的输出串行时钟i2c_clk。

    输出串行时钟 i2c_scl、串行数据信号 i2c_sda 及相关信号的波形图如下。
    在这里插入图片描述
    i2c_scl、i2c_sda 及相关信号波形图
    在这里插入图片描述
    i2c_scl、i2c_sda 及相关信号波形图

    单字节写操作部分涉及的各信号波形的设计与实现讲解完毕,下面开始随机读操作部分的讲解。单字节写操作和随机读操作所涉及的各信号大体相同,在随机读操作,我们只讲解差别较大之处,两操作相同或相似之处不再说明,读者可回顾单字节写操作部分的介绍。

    下面开始随机读操作部分的讲解。

    在这里插入图片描述
    状态机相关信号波形图
    在这里插入图片描述
    状态机相关信号波形图
    在这里插入图片描述
    状态机相关信号波形图
    在这里插入图片描述
    i2c_scl、i2c_sda、rd_data 及相关信号波形图
    在这里插入图片描述
    i2c_scl、i2c_sda、rd_data 及相关信号波形图
    在这里插入图片描述
    i2c_scl、i2c_sda、rd_data 及相关信号波形图

    驱动模块参考代码(i2c_ctrl.v)

    parameter CNT_CLK_MAX = (SYS_CLK_FREQ/SCL_FREQ) >> 2’d3 ;
    解释: 50000000/250000=200, 200 / (222)200 / (222)

    `timescale  1ns/1ns
    
    module  i2c_ctrl
    #(
        parameter   DEVICE_ADDR     =   7'b1010_000     ,   //i2c设备地址
        parameter   SYS_CLK_FREQ    =   26'd50_000_000  ,   //输入系统时钟频率
        parameter   SCL_FREQ        =   18'd250_000         //i2c设备scl时钟频率
    )
    (
        input   wire            sys_clk     ,   //输入系统时钟,50MHz
        input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
        input   wire            wr_en       ,   //输入写使能信号
        input   wire            rd_en       ,   //输入读使能信号
        input   wire            i2c_start   ,   //输入i2c触发信号
        input   wire            addr_num    ,   //输入i2c字节地址字节数
        input   wire    [15:0]  byte_addr   ,   //输入i2c字节地址
        input   wire    [7:0]   wr_data     ,   //输入i2c设备数据
    
        output  reg             i2c_clk     ,   //i2c驱动时钟
        output  reg             i2c_end     ,   //i2c一次读/写操作完成
        output  reg     [7:0]   rd_data     ,   //输出i2c设备读取数据
        output  reg             i2c_scl     ,   //输出至i2c设备的串行时钟信号scl
        inout   wire            i2c_sda         //输出至i2c设备的串行数据信号sda
    );
    
    //************************************************************************//
    //******************** Parameter and Internal Signal *********************//
    //************************************************************************//
    // parameter define
    parameter   CNT_CLK_MAX     =   (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3   ;   //cnt_clk计数器计数最大值
    
    parameter   CNT_START_MAX   =   8'd100; //cnt_start计数器计数最大值
    
    parameter   IDLE            =   4'd00,  //初始状态
                START_1         =   4'd01,  //开始状态1
                SEND_D_ADDR     =   4'd02,  //设备地址写入状态 + 控制写
                ACK_1           =   4'd03,  //应答状态1
                SEND_B_ADDR_H   =   4'd04,  //字节地址高八位写入状态
                ACK_2           =   4'd05,  //应答状态2
                SEND_B_ADDR_L   =   4'd06,  //字节地址低八位写入状态
                ACK_3           =   4'd07,  //应答状态3
                WR_DATA         =   4'd08,  //写数据状态
                ACK_4           =   4'd09,  //应答状态4
                START_2         =   4'd10,  //开始状态2
                SEND_RD_ADDR    =   4'd11,  //设备地址写入状态 + 控制读
                ACK_5           =   4'd12,  //应答状态5
                RD_DATA         =   4'd13,  //读数据状态
                N_ACK           =   4'd14,  //非应答状态
                STOP            =   4'd15;  //结束状态
    
    // wire  define
    wire            sda_in          ;   //sda输入数据寄存
    wire            sda_en          ;   //sda数据写入使能信号
    
    // reg   define
    reg     [7:0]   cnt_clk         ;   //系统时钟计数器,控制生成clk_i2c时钟信号
    reg     [3:0]   state           ;   //状态机状态
    reg             cnt_i2c_clk_en  ;   //cnt_i2c_clk计数器使能信号
    reg     [1:0]   cnt_i2c_clk     ;   //clk_i2c时钟计数器,控制生成cnt_bit信号
    reg     [2:0]   cnt_bit         ;   //sda比特计数器
    reg             ack             ;   //应答信号
    reg             i2c_sda_reg     ;   //sda数据缓存
    reg     [7:0]   rd_data_reg     ;   //自i2c设备读出数据
    
    //************************************************************************//
    //******************************* Main Code ******************************//
    //************************************************************************//
    // cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_clk <=  8'd0;
        else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
            cnt_clk <=  8'd0;
        else
            cnt_clk <=  cnt_clk + 1'b1;
    
    // i2c_clk:i2c驱动时钟
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            i2c_clk <=  1'b1;
        else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
            i2c_clk <=  ~i2c_clk;
    
    // cnt_i2c_clk_en:cnt_i2c_clk计数器使能信号
    always@(posedge i2c_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_i2c_clk_en  <=  1'b0;
        else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
            cnt_i2c_clk_en  <=  1'b0;
        else    if(i2c_start == 1'b1)
            cnt_i2c_clk_en  <=  1'b1;
    
    // cnt_i2c_clk:i2c_clk时钟计数器,控制生成cnt_bit信号
    always@(posedge i2c_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_i2c_clk <=  2'd0;
        else    if(cnt_i2c_clk_en == 1'b1)
            cnt_i2c_clk <=  cnt_i2c_clk + 1'b1;
    
    // cnt_bit:sda比特计数器
    always@(posedge i2c_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_bit <=  3'd0;
        else    if((state == IDLE) || (state == START_1) || (state == START_2)
                    || (state == ACK_1) || (state == ACK_2) || (state == ACK_3)
                    || (state == ACK_4) || (state == ACK_5) || (state == N_ACK))
            cnt_bit <=  3'd0;
        else    if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
            cnt_bit <=  3'd0;
        else    if((cnt_i2c_clk == 2'd3) && (state != IDLE))
            cnt_bit <=  cnt_bit + 1'b1;
    
    // state:状态机状态跳转
    always@(posedge i2c_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            state   <=  IDLE;
        else    case(state)
            IDLE:
                if(i2c_start == 1'b1)
                    state   <=  START_1;
                else
                    state   <=  state;
            START_1:
                if(cnt_i2c_clk == 3)
                    state   <=  SEND_D_ADDR;
                else
                    state   <=  state;
            SEND_D_ADDR:
                if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                    state   <=  ACK_1;
                else
                    state   <=  state;
            ACK_1:
                if((cnt_i2c_clk == 3) && (ack == 1'b0))
                    begin
                        if(addr_num == 1'b1)
                            state   <=  SEND_B_ADDR_H;
                        else
                            state   <=  SEND_B_ADDR_L;
                    end
                 else
                    state   <=  state;
            SEND_B_ADDR_H:
                if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                    state   <=  ACK_2;
                else
                    state   <=  state;
            ACK_2:
                if((cnt_i2c_clk == 3) && (ack == 1'b0))
                    state   <=  SEND_B_ADDR_L;
                else
                    state   <=  state;
            SEND_B_ADDR_L:
                if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
                    state   <=  ACK_3;
                else
                    state   <=  state;
            ACK_3:
                if((cnt_i2c_clk == 3) && (ack == 1'b0))
                    begin
                        if(wr_en == 1'b1)
                            state   <=  WR_DATA;
                        else    if(rd_en == 1'b1)
                            state   <=  START_2;
                        else
                            state   <=  state;
                    end
                 else
                    state   <=  state;
            WR_DATA:
                if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                    state   <=  ACK_4;
                else
                    state   <=  state;
            ACK_4:
                if((cnt_i2c_clk == 3) && (ack == 1'b0))
                    state   <=  STOP;
                else
                    state   <=  state;
            START_2:
                if(cnt_i2c_clk == 3)
                    state   <=  SEND_RD_ADDR;
                else
                    state   <=  state;
            SEND_RD_ADDR:
                if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                    state   <=  ACK_5;
                else
                    state   <=  state;
            ACK_5:
                if((cnt_i2c_clk == 3) && (ack == 1'b0))
                    state   <=  RD_DATA;
                else
                    state   <=  state;
            RD_DATA:
                if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                    state   <=  N_ACK;
                else
                    state   <=  state;
            N_ACK:
                if(cnt_i2c_clk == 3)
                    state   <=  STOP;
                else
                    state   <=  state;
            STOP:
                if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
                    state   <=  IDLE;
                else
                    state   <=  state;
            default:    state   <=  IDLE;
        endcase
    
    // ack:应答信号
    always@(*)
        case    (state)
            IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
            WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK:
                ack <=  1'b1;
            ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
                if(cnt_i2c_clk == 2'd0)
                    ack <=  sda_in;
                else
                    ack <=  ack;
            default:    ack <=  1'b1;
        endcase
    
    // i2c_scl:输出至i2c设备的串行时钟信号scl
    always@(*)
        case    (state)
            IDLE:
                i2c_scl <=  1'b1;
            START_1:
                if(cnt_i2c_clk == 2'd3)
                    i2c_scl <=  1'b0;
                else
                    i2c_scl <=  1'b1;
            SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
            ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
                if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
                    i2c_scl <=  1'b1;
                else
                    i2c_scl <=  1'b0;
            STOP:
                if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
                    i2c_scl <=  1'b0;
                else
                    i2c_scl <=  1'b1;
            default:    i2c_scl <=  1'b1;
        endcase
    
    // i2c_sda_reg:sda数据缓存
    always@(*)
        case    (state)
            IDLE:
                begin
                    i2c_sda_reg <=  1'b1;
                    rd_data_reg <=  8'd0;
                end
            START_1:
                if(cnt_i2c_clk <= 2'd0)
                    i2c_sda_reg <=  1'b1;
                else
                    i2c_sda_reg <=  1'b0;
            SEND_D_ADDR:
                if(cnt_bit <= 3'd6)
                    i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
                else
                    i2c_sda_reg <=  1'b0;
            ACK_1:
                i2c_sda_reg <=  1'b1;
            SEND_B_ADDR_H:
                i2c_sda_reg <=  byte_addr[15 - cnt_bit];
            ACK_2:
                i2c_sda_reg <=  1'b1;
            SEND_B_ADDR_L:
                i2c_sda_reg <=  byte_addr[7 - cnt_bit];
            ACK_3:
                i2c_sda_reg <=  1'b1;
            WR_DATA:
                i2c_sda_reg <=  wr_data[7 - cnt_bit];
            ACK_4:
                i2c_sda_reg <=  1'b1;
            START_2:
                if(cnt_i2c_clk <= 2'd1)
                    i2c_sda_reg <=  1'b1;
                else
                    i2c_sda_reg <=  1'b0;
            SEND_RD_ADDR:
                if(cnt_bit <= 3'd6)
                    i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
                else
                    i2c_sda_reg <=  1'b1;
            ACK_5:
                i2c_sda_reg <=  1'b1;
            RD_DATA:
                if(cnt_i2c_clk  == 2'd2)
                    rd_data_reg[7 - cnt_bit]    <=  sda_in;
                else
                    rd_data_reg <=  rd_data_reg;
            N_ACK:
                i2c_sda_reg <=  1'b1;
            STOP:
                if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
                    i2c_sda_reg <=  1'b0;
                else
                    i2c_sda_reg <=  1'b1;
            default:
                begin
                    i2c_sda_reg <=  1'b1;
                    rd_data_reg <=  rd_data_reg;
                end
        endcase
    
    // rd_data:自i2c设备读出数据
    always@(posedge i2c_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            rd_data <=  8'd0;
        else    if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
            rd_data <=  rd_data_reg;
    
    // i2c_end:一次读/写结束信号
    always@(posedge i2c_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            i2c_end <=  1'b0;
        else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
            i2c_end <=  1'b1;
        else
            i2c_end <=  1'b0;
    
    // sda_in:sda输入数据寄存
    assign  sda_in = i2c_sda;
    // sda_en:sda数据写入使能信号
    assign  sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
                        || (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
                        ? 1'b0 : 1'b1;
    // i2c_sda:输出至i2c设备的串行数据信号sda
    assign  i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;
    
    endmodule
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340

    数据收发模块

    数据收发模块的主要功能是:为 I2C 驱动模块提供读/写数据存储地址、待写入数据以及作为 EEPROM 读出数据缓存,待数据读取完成后将读出数据发送给数码管显示模块进行 数据显示。数据收发模块框图及模块输入输出端口简介
    在这里插入图片描述
    I2C 数据收发模块输入输出信号简介
    在这里插入图片描述
    输入信号中,有 2 路时钟信号和 1 路复位信号,sys_clk 为系统时钟信号,在数据收发模块中用于采集读/写触发信号 read 和 write,2 路触发信号均由外部按键输出,经消抖处理后传入本模块,消抖模块使用的时钟信号为与 sys_clk 相同的系统时钟,所以读/写触发信号的采集要使用系统时钟;i2c_clk 为模块工作时钟,由 I2C 驱动模块生成并传入,是存储地址、读/写数据以及使能信号的同步时钟,因为 I2C 模块的工作时钟为 i2c_clk 时钟信号,两模块工作时钟相同,不会出现时钟不同引起时序问题;复位信号 sys_rst_n,低电平有效,不必多说;i2c_end 为单字节数据读/写接数信号,由 I2C 驱动模块产生并传入,告知数据生成模块单字节数据读/写操作完成。若连续读/写多字节数据,此信号可作为存储地址、写数据的更新标志;rd_data 为 I2C 驱动模块传入的数据信号,表示由 EEPROM 读出的字节数据。

    输出信号中, rd_en、wr_en 分别为读写使能信号,生成后传入 I2C 驱动模块,作为I2C 驱动模块读/写操作的判断标志;i2c_start 是单字节数据读/写开始信号,作为 I2C 驱动模块单字节读/写操作开始的标志信号;byte_addr 为读写数据存储地址;wr_data 为待写入EEPROM 的字节数据;fifo_rd_data 为自 EEPROM 读出的字节数据,要发送到数码换显示模块在数码管显示出来。

    跨时钟域处理
    在这里插入图片描述
    写有效信号 wr_valid 拉高后,工作时钟 i2c_clk 上升沿采集到 wr_valid 高电平,拉高写使能信号 wr_en,告知 I2C 驱动模块接下来要进行数据写操作。在此次实验我们要连续写入 10 字节数据,所以写使能信号 wr_en 要保持 10 次数据写操作的有效时间,在这一时间段我们要输出 10 次有效的 i2c_start 信号,在接收到第 10 次 i2c_end 信号后,表示 10 字节数据均已写入完成,将写使能信号 rw_en 拉低,完成 10 字节数据的连续写入。

    要实现这一操作我们需要声明 2 个变量,声明字节计数器 wr_i2c_data_num 对已写入字节进行计数;由数据手册可知,两次相邻的读/写操作之间需要一定的时间间隔,以保证上一次读/写操作完成,所以声明计数器 cnt_start,对相邻读/写操作时间间隔进行计数。

    采集到写有效信号 wr_valid 为高电平,拉高写使能信号 wr_en,计数器 cnt_wait、wr_i2c_data_num 均由 0 开始计数,每一个工作时钟周期 cnt_wait 自加 1,计数到最大值1499,i2c_start 保持一个工作时钟的高电平,同时 cnt_wait 归 0,重新开始计数;I2C 驱动模块接收到有效的 i2c_start 信号后,向 EEPROM 写入单字节数据,传回 i2c_end 信号,表示一次单字节写操作完毕,计数器 wr_i2c_data_num 加 1;计数器 cnt_start 完成 10 次循环计数,i2c_start 拉高 10 次,在接收到第 10 次有效的 i2c_end 信号后,表示连续 10 字节数据写入完毕,将写使能信号 wr_en 拉低,写操作完毕。相关信号波形如下。

    在这里插入图片描述
    第二部分:输出存储地址 byte_addr、写数据 wr_data 信号波形的设计与实现既然是对 EEPROM 中兴写数据操作,存储地址和写数据必不可少,在本从实验中,向EEPROM 中 10 个连续存储存储单元写入 10 字节数据。对输出存储地址 byte_addr,赋值初始存储地址,当 i2c_end 信号有效时,地址加 1,待 10 字节数据均写入完毕,再次赋值初始从从地址;对于写数据 wr_data 处理方式相同,先赋值写数据初值,当 i2c_end 信号有效时,写数据加 1 ,待 10 字节数据均写入完毕,在此赋值写数据初值。两输出信号波形如下。

    在这里插入图片描述
    数据收发模块写操作部分介绍完毕,接下来介绍一下读操作部分各信号波形。

    与写操作部分相同,外部按键传入的读触发信号经消抖处理后传入本模块,该信号只保持一个有效时钟,且同步时钟为系统时钟 sys_clk,模块工作时钟 i2c_clk 很难采集到该触发信号。我们需要延长该读使能触发信号的有效时间,使模块工作时钟 i2c_clk 可以采集到该触发信号。处理方式和写操作方式相同,声明计数器 cnt_rd 和读有效信号 rd_valid 两信号,延长读触发信号 read 有效时间,使 i2c_clk 时钟能采集到该读触发信号。具体方法参照写操作部分相关介绍,计数器 cnt_rd 和读有效信号 rd_valid 波形图如下。
    在这里插入图片描述
    对于读使能信号的处理方式也与写操作方式相同,工作时钟 i2c_clk 上升沿采集到有效rd_valid 信号,拉高读使能信号 rd_en,告知 I2C 驱动模块接下来要进行数据读操作。
    声明字节计数器 rd_i2c_data_num 对已读出字节进行计数;使用之前声明的计数器cnt_start,对相邻读/写操作时间间隔进行计数。
    采集到读有效信号 rd_valid 为高电平,拉高读使能信号 rd_en,计数器 cnt_wait、rd_i2c_data_num 均由 0 开始计数,每一个工作时钟周期 cnt_wait 自加 1,计数到最大值1499,i2c_start 保持一个工作时钟的高电平,同时 cnt_wait 归 0,重新开始计数;I2C 驱动模块接收到有效的 i2c_start 信号后,自 EEPROM 读出单字节数据,传回 i2c_end 信号,表示一次单字节写操作完毕,计数器 rd_i2c_data_num 加 1;计数器 cnt_start 完成 10 次循环计数,i2c_start 拉高 10 次,在接收到第 10 次有效的 i2c_end 信号后,表示连续 10 字节数据写入完毕,将读使能信号 rd_en 拉低,读操作完毕。相关信号波形如下。
    在这里插入图片描述
    既然是数据读操作,自然有读出数据传入本模块,一次读操作连续读出 10 字节数据,先将读取的 10 字节数据暂存到内部例化的 FIFO 中,以传回的 i2c_end 结束信号为写使能,在 i2c_clk 时钟同步下将读出数据写入 FIFO 中。同时我们将 FIFO 的数据计数器引出,方便后续数据发送阶段的操作。相关信号波形图如下。
    在这里插入图片描述
    对于存储地址信号 byte_addr 的讲解,读者参阅写操作部分相关介绍,此处不再赘述,接下来开始数据发送部分各信号波形的讲解。

    等到读取的 10 字节均写入 FIFO 中,FIFO 数据计数器 data_num 显示为 10,表示 FIFO中存有 10 字节读出数据。此时拉高 FIFO 读有效信号 fifo_rd_valid,只有信号 fifo_rd_valid为有效高电平,对 FIFO 的读操作才有效;fifo_rd_valid 有效时,计数器 cnt_wait 开始循环
    计数,声明此计数器的目的是计数字节数据读出时间间隔,间隔越长,每字节数据在数码管显示时间越长,方面现象观察;当计数器 cnt_wait 计数到最大值时,归 0 重新计数,FIFO 读使能信号信号 fifo_rd_en 拉高一个时钟周期,自 FIFO 读出一个字节数据,由fifo_rd_data 将数据传出给数码管显示模块,读出字节计数器 rd_data_num 加 1;等到 10 字节数据均读取并传出后,fifo_rd_valid 信号拉低,数据发送操作完成。相关信号波形如下。
    在这里插入图片描述

    数据收发模块参考代码(i2c_rd_data.v)

    module i2c_rw_data
    (
    input wire sys_clk , //输入系统时钟,频率 50MHz
    input wire i2c_clk , //输入 i2c 驱动时钟,频率 1MHz
    input wire sys_rst_n , //输入复位信号,低有效
    input wire write , //输入写触发信号
    input wire read , //输入读触发信号
    input wire i2c_end , //一次 i2c 读/写结束信号
    input wire [7:0] rd_data , //输入自 i2c 设备读出的数据
    
    output reg wr_en , //输出写使能信号
    output reg rd_en , //输出读使能信号
    output reg i2c_start , //输出 i2c 读/写触发信号
    output reg [15:0] byte_addr , //输出 i2c 设备读/写地址
    output reg [7:0] wr_data , //输出写入 i2c 设备的数据
    output wire [7:0] fifo_rd_data //输出自 fifo 中读出的数据
    );
    
    //********************************************************************//
    //****************** Parameter and Internal Signal *******************//
    //********************************************************************//
    // parameter define
    parameter DATA_NUM = 8'd10 ,//读/写操作读出或写入的数据个数
    CNT_START_MAX = 11'd1500 ,//cnt_start 计数器计数最大值
    CNT_WR_RD_MAX = 8'd200 ,//cnt_wr/cnt_rd 计数器计数最大值
    CNT_WAIT_MAX = 28'd500_000 ;//cnt_wait 计数器计数最大值
    // wire define
    wire [7:0] data_num ; //fifo 中数据个数
    
    // reg define
    reg [7:0] cnt_wr ; //写触发有效信号保持时间计数器
    reg write_valid ; //写触发有效信号
    reg [7:0] cnt_rd ; //读触发有效信号保持时间计数器
    reg read_valid ; //读触发有效信号
    reg [10:0] cnt_start ; //单字节数据读/写时间间隔计数
    reg [7:0] wr_i2c_data_num ; //写入 i2c 设备的数据个数
    reg [7:0] rd_i2c_data_num ; //读出 i2c 设备的数据个数
    reg fifo_rd_valid ; //fifo 读有效信号
    reg [27:0] cnt_wait ; //fifo 读使能信号间时间间隔计数
    reg fifo_rd_en ; //fifo 读使能信号
    reg [7:0] rd_data_num ; //读出 fifo 数据个数
    
    //********************************************************************//
    //***************************** Main Code ****************************//
    //********************************************************************//
    //cnt_wr:写触发有效信号保持时间计数器,计数写触发有效信号保持时钟周期数
    always@(posedge sys_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		cnt_wr <= 8'd0;
    	else if(write_valid == 1'b0)
    		cnt_wr <= 8'd0;
    	else if(write_valid == 1'b1)
    		cnt_wr <= cnt_wr + 1'b1;
    
    //write_valid:写触发有效信号
    //由于写触发信号保持时间为一个系统时钟周期(20ns),
    //不能被 i2c 驱动时钟 i2c_scl 正确采集,延长写触发信号生成写触发有效信号
    always@(posedge sys_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		write_valid <= 1'b0;
    	else if(cnt_wr == (CNT_WR_RD_MAX - 1'b1))
    		write_valid <= 1'b0;
    	else if(write == 1'b1)
    		write_valid <= 1'b1;
    
    //cnt_rd:读触发有效信号保持时间计数器,计数读触发有效信号保持时钟周期数
    always@(posedge sys_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		cnt_rd <= 8'd0;
    	else if(read_valid == 1'b0)
    		cnt_rd <= 8'd0;
    	else if(read_valid == 1'b1)
    		cnt_rd <= cnt_rd + 1'b1;
    
    //read_valid:读触发有效信号
    //由于读触发信号保持时间为一个系统时钟周期(20ns),
    //不能被 i2c 驱动时钟 i2c_scl 正确采集,延长读触发信号生成读触发有效信号
    always@(posedge sys_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		read_valid <= 1'b0;
    	else if(cnt_rd == (CNT_WR_RD_MAX - 1'b1))
    		read_valid <= 1'b0;
    	else if(read == 1'b1)
    		read_valid <= 1'b1;
    
    //cnt_start:单字节数据读/写操作时间间隔计数
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		cnt_start <= 11'd0;
    	else if((wr_en == 1'b0) && (rd_en == 1'b0))
    		cnt_start <= 11'd0;
    	else if(cnt_start == (CNT_START_MAX - 1'b1))
    		cnt_start <= 11'd0;
    	else if((wr_en == 1'b1) || (rd_en == 1'b1))
    		cnt_start <= cnt_start + 1'b1;
    
    //i2c_start:i2c 读/写触发信号
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		i2c_start <= 1'b0;
    	else if((cnt_start == (CNT_START_MAX - 1'b1)))
    		i2c_start <= 1'b1;
    	else
    		i2c_start <= 1'b0;
    
    //wr_en:输出写使能信号
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		wr_en <= 1'b0;
    	else if((wr_i2c_data_num == DATA_NUM - 1) 
    		&& (i2c_end == 1'b1) && (wr_en == 1'b1))
    		wr_en <= 1'b0;
    	else if(write_valid == 1'b1)
    		wr_en <= 1'b1;
    
    //wr_i2c_data_num:写入 i2c 设备的数据个数
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		wr_i2c_data_num <= 8'd0;
    	else if(wr_en == 1'b0)
    		wr_i2c_data_num <= 8'd0;
    	else if((wr_en == 1'b1) && (i2c_end == 1'b1))
    		wr_i2c_data_num <= wr_i2c_data_num + 1'b1;
    
    //rd_en:输出读使能信号
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		rd_en <= 1'b0;
    	else if((rd_i2c_data_num == DATA_NUM - 1) 
    		&& (i2c_end == 1'b1) && (rd_en == 1'b1))
    		rd_en <= 1'b0;
    	else if(read_valid == 1'b1)
    		rd_en <= 1'b1;
    
    //rd_i2c_data_num:写入 i2c 设备的数据个数
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		rd_i2c_data_num <= 8'd0;
    	else if(rd_en == 1'b0)
    		rd_i2c_data_num <= 8'd0;
    	else if((rd_en == 1'b1) && (i2c_end == 1'b1))
    		rd_i2c_data_num <= rd_i2c_data_num + 1'b1;
    
    //byte_addr:输出读/写地址
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		byte_addr <= 16'h00_5A;
    	else if((wr_en == 1'b0) && (rd_en == 1'b0))
    		byte_addr <= 16'h00_5A;
    	else if(((wr_en == 1'b1) || (rd_en == 1'b1)) && (i2c_end == 1'b1))
    		byte_addr <= byte_addr + 1'b1;
    
    //wr_data:输出待写入 i2c 设备数据
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		wr_data <= 8'hA5;
    	else if(wr_en == 1'b0)
    		wr_data <= 8'hA5;
    	else if((wr_en == 1'b1) && (i2c_end == 1'b1))
    		wr_data <= wr_data + 1'b1;
    
    //fifo_rd_valid:fifo 读有效信号
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		fifo_rd_valid <= 1'b0;
    	else if((rd_data_num == DATA_NUM)
    		&& (cnt_wait == (CNT_WAIT_MAX - 1'b1)))
    		fifo_rd_valid <= 1'b0;
    	else if(data_num == DATA_NUM)
    		fifo_rd_valid <= 1'b1;
    
    //cnt_wait:fifo 读使能信号间时间间隔计数,计数两 fifo 读使能间的时间间隔
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		cnt_wait <= 28'd0;
    	else if(fifo_rd_valid == 1'b0)
    		cnt_wait <= 28'd0;
    	else if(cnt_wait == (CNT_WAIT_MAX - 1'b1))
    		cnt_wait <= 28'd0;
    	else if(fifo_rd_valid == 1'b1)
    		cnt_wait <= cnt_wait + 1'b1;
    
    //fifo_rd_en:fifo 读使能信号
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		fifo_rd_en <= 1'b0;
    	else if((cnt_wait == (CNT_WAIT_MAX - 1'b1))
    		&& (rd_data_num < DATA_NUM))
    		fifo_rd_en <= 1'b1;
    	else
    		fifo_rd_en <= 1'b0;
    
    //rd_data_num:自 fifo 中读出数据个数计数
    always@(posedge i2c_clk or negedge sys_rst_n)
    	if(sys_rst_n == 1'b0)
    		rd_data_num <= 8'd0;
    	else if(fifo_rd_valid == 1'b0)
    		rd_data_num <= 8'd0;
    	else if(fifo_rd_en == 1'b1)
    		rd_data_num <= rd_data_num + 1'b1;
    
    //****************************************************************//
    //************************* Instantiation ************************//
    //****************************************************************//
    //------------- fifo_read_inst -------------
    fifo_data fifo_read_inst
    (
    .clock (i2c_clk ), //输入时钟信号,频率 1MHz,1bit
    .data (rd_data ), //输入写入数据,1bit
    .rdreq (fifo_rd_en ), //输入数据读请求,1bit
    .wrreq (i2c_end && rd_en ), //输入数据写请求,1bit
    
    .q (fifo_rd_data ), //输出读出数据,1bit
    .usedw (data_num ) //输出 fifo 内数据个数,1bit
    );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217

    参考代码编写完毕

  • 相关阅读:
    vue3中使用svg并封装成组件
    JAVA线程池 -clt设计与分析
    C++行为型模式-职责链模式
    wireshark 过滤设置
    java毕业设计研究生实验室综合管理系统Mybatis+系统+数据库+调试部署
    react原理篇:react局部更新实现方式(虚拟DOM和Diff算法)
    蓝牙模块传输音频出现卡顿原因分析
    python萌新爬虫学习笔记【建议收藏】
    C# OpenCvSharp 去除字母后面的杂线
    Flink---12、状态后端(HashMapStateBackend/RocksDB)、如何选择正确的状态后端
  • 原文地址:https://blog.csdn.net/weixin_41226265/article/details/134104519