基于IIC协议实现EEPROM读写测试

一、IIC协议

1、初识IIC

  • IIC即(Inter-Integrated Circuit)【集成电路总线】,是一种多向控制总线,由飞利浦【PHILIPS】半导体公司在八十年代设计,主要用于连接整体电路【ICS】
  • 在IIC中,多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实施数据传输的控制源,简化了信号传输总线;
  • 是一种简单、双向、二线制、同步串行总线;
  • 一个主机可以同时挂载多个从机;
  • 连接在总线上的设备都是通过唯一的地址和其他器件通信,主机和从机角色可以互换;
  • 适用于近距离低速芯片间通信。

2、协议详解

1、IIC概述

IIC:两线式串行总线,它是由数据线【SDA】时钟线【SCL】构成的串行总线,可发送和接受数据

在CPU与被控IC之间,IC与IC之间进行双向传送,高速IIC总线一般可达【400kbs】以上。

  • 串行数据时钟【SCL】:在通信过程中起到控制作用
  • 串行数据线【SDA】:用来一位一位的传送数据

2、IIC分类

硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。
软件IIC:软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。

1.硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
2.IIC是半双工通信方式

3、外设挂载

挂载数量:IIC地址决定,8位地址,减去1位广播地址,是7位地址,2^7=128,但是地址0x00不用,那就是127个地址, 所以理论上可以挂127个从器件

IIC总线是一种【多主机总线】,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束依次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误发生;每个连接到IIC总线的器件都有一个【唯一的地址(7bit)】,且每个器件都可以作为从机【同一时刻只能有一个主机】,总线上的器件可以删除和增加不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件叫做发送器,接受数据的器件叫接收器
在这里插入图片描述

4、时序详解

IIC通信过程由开始、结束、发送、响应、接受五个部分构成;

  • 【在发送、接受数据时】当SCL为高电平时,SDA线不允许变化;当SCL线为低电平时,SDA可以任意0,1变化;
  • 【在任意时候】只有当SCL为高电平时,IIC电路才对SDA线上的电平(0或者1)进行记录;当SCL线为低电平时,无论SDA是高或者低,IIC都不对SDA进行采样。
  • SDA和SCL同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出场级效管均处于截止状态,即释放总线,由上拉电阻拉高电平。

数据有效传输在SCL信号的高电平期间,SDA数据线保持稳定;在SCL为低电平期间,允许SDA数据线变化。

开始信号
起始信号:当SCL为高期间,SDA由高到低的跳变;【SDA出现下降沿】属于电平跳变时序信号,而不是电平
在这里插入图片描述
结束信号
结束信号:当SCL为高电平期间,SDA由低到高的跳变,【SDA出现上升沿】属于电平跳变时序信号,而不是电平信号
在这里插入图片描述
应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。【应答信号为低电平时,规定为有效应答位(ACK简称应答位)】应答信号为高电平时,规定为非应答位【NACK】一般表示接收器接受该字节没有成功。

对于反馈有效应答ACK的要求是,接收器在第九个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间稳定的低电平。如果接收器是主控器,则在它接受到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。【低电平0表示应答,1表示非应答】

在这里插入图片描述

IIC传输时时从MSB开始传输到LSB结束。MSB是Most Significant Bit的缩写,最高有效位。在二进制数中,MSB是最高加权位。与十进制数字中最左边的一位类似。通常,MSB位于二进制数的最左侧,LSB位于二进制数的最右侧。LSB,英文 least significant bit,中文义最低有效位。

IIC写时序
时序图:
在这里插入图片描述

发送器件地址:主机给地址总线上发送数据,如果地址匹配,从机给出应答,建立通讯。IIC协议一次只能和一个设备/器件进行通讯。

①产生start位;
②传送器件地址,器件地址的最后一位数据为数据的传输方向位【R/W】,低电平0表示主机往从机写数据【W】,1表示主机从从机读数据【R】。ACK应答,应答是从机发送给主机的应答(写时序不管)
③传送写入器件寄存地址,即数据要写入的位置;
④传送要写入的数据
⑤产生stop信号

IIC读时序
时序图:
在这里插入图片描述
①产生start信号;
②传送器件地址,ACK
③传送字地址,ACK
④再次产生start信号;
⑤再次传送器件地址,ACK
⑥读取一个字节数据,读数据最后结束前无应答信号;
⑦产生stop信号

dummy write;指在读数据的时候,先写入写控制字与字节地址,这一段表示向设备写入要读的数据的地址,而不是真的向地址写数据,这个地址会被锁存起来,而不会触发写操作。

IIC读写时序补充

双字节地址写时序:写入器件地址,写入寄存器地址高位、低位,然后写数据
在这里插入图片描述
多数据写时序:写入器件地址,写入器件地址,连续写入时序
在这里插入图片描述
双字节地址多数据写时序:写入器件地址,写入器件地址高位、低位,然后连续写入数据
在这里插入图片描述
双字节地址读时序:写入器件地址,写入寄存器地址高位、低位,然后再次写入器件地址,写入数据。读数据最后结束前无应答ACK信号
在这里插入图片描述
多数据读时序:写入器件地址,写入寄存器地址,然后再次写入器件地址,连续写入数据,读数据最后结束前无应答ACK信号
在这里插入图片描述
双字节地址多数据读时序:写入器件地址,写入寄存器地址,然后再次写入器件地址,连续写入数据。读数据最后结束前无应答ACK信号。
在这里插入图片描述

5、数据收发

  • 在I2C总线上传送的每位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,SDA逐位地串行传送每一位数据。数据位的传输是边沿触发。
  • 发送数据是一位一位发送,接收数据也是一位一位接收进来,最后返回应答信号.
  • IIC 传输时,按照从高到低的位序进行传输。控制字节的最低位为读写控制位,当该位为 0 时表示主机对从机进行写操作,当该位为 1 时表示主机对从机进行读操作。

二、EEPROM

1、EEPROM概述

  • EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。
  • EEPROM是一种特殊形式的闪存,其应用通常是个人电脑中的电压来擦写和重编程。

2、特点

  • EEPROM,一般用于即插即用【Plug&Play】;
  • 常用在接口卡中,用来存放硬件设置数据。
  • 也常用在防止软件非法拷贝的“硬件锁”上面。

3、基本原理

  • 由于EPROM操作的不便,后来出的主板上BIOS ROM芯片大部分都采用EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)。
  • EEPROM的擦除不需要借助于其它设备,它是以电子信号来修改其内容的,而且是以Byte为最小修改单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。
  • EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商提供的专用刷新程序就可以轻而易举地改写内容,所以,它属于双电压芯片。
  • 借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至“on”的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时使用时,则把跳线开关打至“off”的位置,防止CIH类的病毒对BIOS芯片的非法修改。
  • 所以,仍有不少主板采用EEPROM作为BIOS芯片并作为自己主板的一大特色。

4、读写时序

总线时序数据
在这里插入图片描述
总线计时开始/停止
在这里插入图片描述
串行总线上的数据传输序列
在这里插入图片描述
控制字节分配
在这里插入图片描述

Operation Control Code Block Select R/W_N
Read 1010 Block Address 1
Write 1010 Block Address 0

字节写
在这里插入图片描述
页写
在这里插入图片描述
当前地址读
在这里插入图片描述
随机读
在这里插入图片描述
顺序读
在这里插入图片描述

三、项目概述

1、功能简述

本次实验是基于I2C协议的EEPROM的读写实验。

本实验实现了上位机通过串口发送命令帧和数据帧给FPGA,FPGA对命令和数据帧解析之后,对EEPROM进行读操作和写操作,并将从EEPROM读取数据通过串口返回给上位机。
各个模块实现功能如下:

  • 串口发送模块【uart_tx】:将从EEPROM读取的数据通过串口返回给上位机;
  • 串口接收模块【uart_rx】:接收上位机发送的字节,给到命令解析模块
  • 命令解析模块【cmd_analy】:对串口接收到的字节进行解析,提取出数据和读写请求;将数据利用FIFO【wr_fifo】进行缓存;或者将读写请求给到EEPROM控制模块
  • I2C接口模块【i2c_interface】:根据接收到的读写请求、命令码,在FPGA和EEPROM之间进行通信【传输数据】
  • EEPROM控制模块【eeprom_control】:根据接收到的读写请求,向i2c接口发起读写传输请求、命令码和数据

2、系统框图

在这里插入图片描述

3、状态机设计

I2C接口模块状态转移图
在这里插入图片描述
状态参数说明:

  • IDLE:初始状态
  • START:起始信号
  • READ:读数据
  • WRITE:写数据
  • SEND_ACK:发送应答
  • RECV_ACK:接收应答
  • STOP:停止信号

4、源码

1、顶层模块(i2c_eeprom.v)

//顶层模块
module i2c_eeprom (
    input               clk     ,
    input               rst_n   ,

    input               uart_rxd,
    output              uart_txd,

    output              i2c_scl ,
    inout               i2c_sda     
);
    
//信号定义

    wire    [7:0]       rx_data     ;
    wire                rx_data_vld ;
    wire    [7:0]       wr_data     ;
    wire                wr_data_vld ;
    wire                wr_en       ;
    wire                rd_en       ;
    wire    [8:0]       rw_addr     ;
    wire                req         ;
    wire    [3:0]       cmd         ;
    wire    [7:0]       data        ;
    wire                wr_fail     ;
    wire    [7:0]       rd_dout     ;
    wire                rw_done     ;
    wire                sda_in      ;
    wire                sda_out     ;
    wire                sda_out_en  ;
    wire                rdy         ;
    wire    [7:0]       tx_data     ;
    wire                tx_data_vld ;

    assign i2c_sda = sda_out_en?sda_out:1'bz;
    assign sda_in = i2c_sda;
    
//模块例化

    uart_rx u_uart_rx(
    .clk        (clk           ),
    .rst_n      (rst_n         ),

    .din        (uart_rxd      ),
    .dout       (rx_data       ),
    .dout_vld   (rx_data_vld   )
);

    cmd_analy u_cmd_analy(  //命令帧解析模块
    .clk        (clk           ),
    .rst_n      (rst_n         ),

    .din        (rx_data       ),
    .din_vld    (rx_data_vld   ),
    .dout       (wr_data       ),//写入FIFO的数据
    .dout_vld   (wr_data_vld   ),
    .wr_en      (wr_en         ),//写请求
    .rd_en      (rd_en         ),//读请求
    .addr       (rw_addr       ) //读写地址
);

    eeprom_control u_eeprom_control(
    .clk        (clk           ),
    .rst_n      (rst_n         ),
    
    //user interface
    .wr_req     (wr_en         ),
    .rd_req     (rd_en         ),
    .wr_din     (wr_data       ),
    .wr_din_vld (wr_data_vld   ),
    .rw_addr    (rw_addr       ),
    .rdy        (rdy           ),
    .tx_data    (tx_data       ),
    .tx_data_vld(tx_data_vld   ),

    //i2c interface
    .req        (req           ),
    .cmd        (cmd           ),
    .data       (data          ),
    .wr_fail    (wr_fail       ),
    .rd_din     (rd_dout       ),
    .rw_done    (rw_done       )
);

    i2c_interface u_i2c_interface(  //仅仅实现I2C协议时序
    .clk         (clk           ),//50MHz
    .rst_n       (rst_n         ),

    .req         (req           ),//传输使能
    .cmd         (cmd           ),//传输命令码
    .wr_din      (data          ),//需要传输的数据

    .sda_in      (sda_in        ),
    .sda_out     (sda_out       ),
    .sda_out_en  (sda_out_en    ),
    .scl         (i2c_scl       ),

    .wr_fail     (wr_fail       ),//从机未应答
    .rd_dout     (rd_dout       ),//从slave读取的1字节数据 
    .rw_done     (rw_done       ) //1个字节读写完成
);

    uart_tx u_uart_tx(
    .clk        (clk            ),
    .rst_n      (rst_n          ),
    .din        (tx_data        ),
    .din_vld    (tx_data_vld    ),
    .rdy        (rdy            ),//ready 表示串口发送模块可以接收数据并发送
    .dout       (uart_txd       )    
);

endmodule

2、串口接收模块(uart_rx.v)

//串口接收模块
`include "parm.v"

module uart_rx(

    input                          clk      ,
    input                          rst_n    ,
    //串口
    input                          din      ,
    output      reg[7:0]           dout     ,
    output      reg                dout_vld  
);

    //信号定义
    reg             [12:0]           cnt_bps      ;//波特率计数器
    wire                             add_cnt_bps  ;
    wire                             end_cnt_bps  ;

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

    reg                              din_r0       ;//同步串口输入
    reg                              din_r1       ;//打拍
    wire                             nedge        ;//下降沿

    reg                              rx_flag      ;//接收数据标志信号
    reg             [9:0]            rx_data      ;//接收数据寄存

    //计数器设计  cnt_bps  cnt_bit
    always @(posedge clk  or negedge rst_n) begin
        if(!rst_n)begin
          cnt_bps <= 0;
        end
        else if(add_cnt_bps)begin
          if(end_cnt_bps)begin
              cnt_bps <= 0;
          end
          else begin
              cnt_bps <= cnt_bps + 1;
          end
        end
    end
    assign add_cnt_bps = rx_flag;
    assign end_cnt_bps = add_cnt_bps && cnt_bps == `BAUD-1;

    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_bps;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == 10-1;

    //串口接收数据标志 rx_flag
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          rx_flag <= 1'b0;
        end
        else if(nedge)begin
          rx_flag <= 1'b1;//出现下降沿 开始接收数据
        end
        else if(end_cnt_bit)begin
          rx_flag <= 1'b0;//bit计数结束 结束数据接收
        end
    end

    //同步打拍 检查下降沿
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          din_r0 <= 1'b1;
          din_r1 <= 1'b1;
        end
        else begin
          din_r0 <= din;
          din_r1 <= din_r0;
        end
    end
    assign nedge = ~din_r0 & din_r1;

    //熟记接收  rx_data
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          rx_data <= 0;
        end
        else if(add_cnt_bps && cnt_bps == (`BAUD>>1))begin
          rx_data[cnt_bit] <= din;
          /*
          rx_data <= {din,rx_data[10:1]};
          */
        end
    end

    //dout
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout <= 0;
        end
        else begin
          dout <= rx_data[8:1];
        end
    end

    //dout_vld
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout_vld <= 0;
        end
        else begin
          dout_vld <= end_cnt_bit;
        end
    end

endmodule

3、命令解析模块(cmd_analy.v)

//命令解析模块
module cmd_analy(

    input                clk       ,
    input                rst_n     ,
    input       [7:0]    din       ,//串口接收模块接口
    input                din_vld   ,
    output   reg[7:0]    dout      ,
    output   reg         dout_vld  ,
    output   reg         wr_en     ,//写请求
    output   reg         rd_en     ,//读请求
    output   reg[8:0]    addr      

);

    //信号定义
    localparam IDLE        = 9'b0_0000_0001,
               FRAME_HEAD  = 9'b0_0000_0010,//接收起始符
               WRDATA_INST = 9'b0_0000_0100,//接收写指示
               WR_DATA     = 9'b0_0000_1000,//接收写数据
               FRAME_END   = 9'b0_0001_0000,//接收帧结束符
               WR_REQ      = 9'b0_0010_0000,//接收写请求
               WR_ADDR     = 9'b0_0100_0000,//接收写地址
               RD_REQ      = 9'b0_1000_0000,//接收读请求
               RD_ADDR     = 9'b1_0000_0000;//接收读地址

    localparam SOF     = 4'b0001,//start of frame
               WR_INST = 4'b0010,//写指示
               EOF0    = 4'b0100,//end of frame
               EOF1    = 4'b1000;//end of frame

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

    reg            [7:0]          din_r0    ;//同步
    reg            [7:0]          din_r1    ;//打拍

    wire                idle2frame_head         ;     
    wire                frame_head2wrdata_inst  ;
    wire                frame_head2wr_req       ;     
    wire                frame_head2rd_req       ; 
    wire                wrdara_inst2wr_data     ; 
    wire                wr_data2frame_end       ;     
    wire                frame_end2idle          ;  
    wire                wr_req2wr_addr          ; 
    wire                wr_addr2frame_end       ; 
    wire                rd_req2rd_addr          ; 
    wire                rd_addr2frame_end       ; 

    //状态机 序列检测
    //第一段
    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(idle2frame_head)
                    state_n = FRAME_HEAD;
                else 
                    state_n = state_c; 
            end
            FRAME_HEAD :begin
                if(frame_head2wrdata_inst)
                    state_n = WRDATA_INST;
                else if(frame_head2wr_req)
                    state_n = WR_REQ; 
                else if(frame_head2rd_req)
                    state_n = RD_REQ; 
                else 
                    state_n = state_c; 
            end
            WRDATA_INST:begin
                if(wrdara_inst2wr_data)
                    state_n = WR_DATA;
                else 
                    state_n = state_c; 
            end
            WR_DATA    :begin
                if(wr_data2frame_end)
                    state_n = FRAME_END; 
                else 
                    state_n = state_c; 
            end
            FRAME_END  :begin
                if(frame_end2idle)
                    state_n = IDLE;
                else 
                    state_n = state_c; 
            end
            WR_REQ     :begin
                if(wr_req2wr_addr)
                    state_n = WR_ADDR;
                else 
                    state_n = state_c; 
            end
            WR_ADDR    :begin
                if(wr_addr2frame_end)
                    state_n = FRAME_END; 
                else 
                    state_n = state_c; 
            end
            RD_REQ     :begin
                if(rd_req2rd_addr)
                    state_n = RD_ADDR;
                else    
                    state_n = state_c; 
            end
            RD_ADDR    :begin
                if(rd_addr2frame_end)
                    state_n = FRAME_END; 
                else 
                    state_n = state_c; 
            end 
            default:state_n = IDLE; 
        endcase
    end
    assign  idle2frame_head        = state_c == IDLE        && (din_vld && din == SOF);
    assign  frame_head2wrdata_inst = state_c == FRAME_HEAD  && (din_vld && din == WR_INST);
    assign  frame_head2wr_req      = state_c == FRAME_HEAD  && (din_vld && din[4] == 1'b0);
    assign  frame_head2rd_req      = state_c == FRAME_HEAD  && (din_vld && din[4] == 1'b1);
    assign  wrdara_inst2wr_data    = state_c == WRDATA_INST && (din_vld);
    assign  wr_data2frame_end      = state_c == WR_DATA     && (din == EOF1 && din_r0 == EOF0);
    assign  frame_end2idle         = state_c == FRAME_END   && (din_r0 == EOF1 && din_r1 == EOF0);
    assign  wr_req2wr_addr         = state_c == WR_REQ      && (1'b1);
    assign  wr_addr2frame_end      = state_c == WR_ADDR     && (din_vld);
    assign  rd_req2rd_addr         = state_c == RD_REQ      && (1'b1);
    assign  rd_addr2frame_end      = state_c == RD_ADDR     && (din_vld);

    //同步打拍
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          din_r0 <= 0;
          din_r1 <= 0;
        end
        else begin
          din_r0 <= din;
          din_r1 <= din_r0;
        end
    end

    //dout
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout <= 0;
        end
        else if(state_c == WR_DATA && din_vld && din != EOF1 && din_r0 != EOF0)begin
          dout <= din_r0;
        end
    end

    //dout_vld
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout_vld <= 0;
        end
        else begin
          dout_vld <= state_c == WR_DATA && din_vld && din != EOF1 && din_r0 != EOF0;
        end
    end

    //wr_en  写请求
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          wr_en <= 1'b0;
        end
        else if(wr_addr2frame_end)begin
          wr_en <= 1'b1;//写地址到一帧数据结束
        end
        else begin
          wr_en <= 1'b0;
        end
    end

    //rd_en 读请求
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          rd_en <= 1'b0;
        end
        else if(rd_addr2frame_end)begin
          rd_en <= 1'b1;//读地址到一帧数据结束
        end
        else begin
          rd_en <= 1'b0;
        end
    end

    //读写地址 addr
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          addr <= 0;
        end
        else if(wr_addr2frame_end | rd_addr2frame_end)begin
          addr <= {
    
    din_r0[0],din};
        end
    end

endmodule

4、EEPROM控制模块(eeprom_control.v)

//EEPROM控制模块
`include "parm.v"

module eeprom_control(

    input                   clk        ,
    input                   rst_n      ,
    //用户接口
    input                   wr_req     ,
    input                   rd_req     ,
    input          [7:0]    wr_din     ,
    input                   wr_din_vld ,
    input          [8:0]    rw_addr    ,
    input                   rdy        ,
    output      reg[7:0]    tx_data    ,
    output      reg         tx_data_vld,
    //I2C接口模块
    output      reg         req        ,
    output      reg[3:0]    cmd        ,
    output      reg[7:0]    data       ,
    input                   wr_fail    ,
    input          [7:0]    rd_din     ,
    input                   rw_done    
);

    //状态机参数定义
    localparam  IDLE        = 6'b00_0001,
                WR_REQ      = 6'b00_0010,
                WAIT_WDONE  = 6'b00_0100,
                RD_REQ      = 6'b00_1000,
                WAIT_RDONE  = 6'b01_0000,
                DONE        = 6'b10_0000;

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

    reg     [7:0]       cnt_byte           ;
    wire                add_cnt_byte       ;
    wire                end_cnt_byte       ;
    reg     [7:0]       X                  ;

    wire                idle2wr_req        ;
    wire                idle2rd_req        ;
    wire                wait_wdone2wr_req  ;
    wire                wait_wdone2done    ;
    wire                wait_rdone2rd_req  ;
    wire                wait_rdone2done    ;

    reg                 wfifo_rdreq        ;
    wire                wfifo_wrreq        ;
    wire                wfifo_empty        ;
    wire                wfifo_full         ;
    wire    [7:0]       wfifo_qout         ;
    wire    [5:0]       wfifo_usedw        ;

    reg                 rfifo_rdreq        ;
    reg                 rfifo_wrreq        ;
    wire                rfifo_empty        ;
    wire                rfifo_full         ;
    wire    [7:0]       rfifo_qout         ;
    wire    [5:0]       rfifo_usedw        ;


    //状态机设计    
    //第一段
    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(idle2wr_req)
                    state_n = WR_REQ;
                else if(idle2rd_req)
                    state_n = RD_REQ;
                else 
                    state_n = state_c;
            end     
            WR_REQ    :begin 
                state_n = WAIT_WDONE;
            end 
            WAIT_WDONE:begin 
                if(wait_wdone2wr_req)
                    state_n = WR_REQ;
                else if(wait_wdone2done)
                    state_n = DONE;
                else 
                    state_n = state_c;
            end 
            RD_REQ    :begin 
                state_n = WAIT_RDONE;
            end 
            WAIT_RDONE:begin 
                if(wait_rdone2rd_req)
                    state_n = RD_REQ;
                else if(wait_rdone2done)
                    state_n = DONE;
                else 
                    state_n = state_c;
            end 
            DONE      :begin 
                state_n = IDLE;
            end  
            default:state_n = IDLE;
        endcase
    end
    assign idle2wr_req       = state_c == IDLE       && (wr_req && wfifo_usedw >= `WR_BYTE-2);
    assign idle2rd_req       = state_c == IDLE       && (rd_req);
    assign wait_wdone2wr_req = state_c == WAIT_WDONE && (rw_done && end_cnt_byte == 1'b0);
    assign wait_wdone2done   = state_c == WAIT_WDONE && (rw_done && end_cnt_byte == 1'b1);
    assign wait_rdone2rd_req = state_c == WAIT_RDONE && (rw_done && end_cnt_byte == 1'b0);
    assign wait_rdone2done   = state_c == WAIT_RDONE && (rw_done && end_cnt_byte == 1'b1);

    //第三段
    //计数器设计 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 = (state_c == WAIT_WDONE | state_c == WAIT_RDONE) && rw_done;
    assign end_cnt_byte = add_cnt_byte && cnt_byte == X-1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          X <= 0;
        end
        else if(idle2wr_req)begin
          X <= `WR_BYTE;
        end
        else if(idle2rd_req)begin
          X <= `RD_BYTE;
        end
    end

    //data cmd req
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          data <= 0;
          req  <= 0;
          cmd  <= 0;
        end
        else if(state_c == WR_REQ)begin
          case(cnt_byte)
          0:begin
            data <= {
    
    `SLAVE_CODE,2'b00,rw_addr[8],`WR_BIT};
            cmd  <= {
    
    `CMD_START | `CMD_WRITE};
            req  <= 1;
          end
          1:begin
            data <= rw_addr[7:0];
            cmd  <= `CMD_WRITE;
            req  <= 1;
          end
          `WR_BYTE-1:begin
            data <= wfifo_qout;
            cmd  <= {
    
    `CMD_WRITE | `CMD_STOP};
            req  <= 1;
          end
          default:begin
            data <= wfifo_qout;
            cmd  <= `CMD_WRITE;
            req  <= 1;
          end
        endcase
        end
        else if(state_c == RD_REQ)begin 
            case(cnt_byte)
            0:begin 
                req  <= 1;
                cmd  <= {
    
    `CMD_START | `CMD_WRITE};
                data <= {
    
    `SLAVE_CODE,2'b00,rw_addr[8],`WR_BIT};
            end  
            1:begin 
                req  <= 1;
                cmd  <= `CMD_WRITE;
                data <= rw_addr[7:0];
            end 
            2:begin 
                req  <= 1;
                cmd  <= {
    
    `CMD_START | `CMD_WRITE};
                data <= {
    
    `SLAVE_CODE,2'b00,rw_addr[8],`RD_BIT};
            end 
            `RD_BYTE-1:begin 
                req  <= 1;
                cmd  <= {
    
    `CMD_READ | `CMD_STOP};
                data <= 0;
            end 
            default:begin
                req  <= 1;
                cmd  <= `CMD_READ;
                data <= 0;
            end 
            endcase 
        end 
        else begin 
            req  <= 0;
        end 
    end

    //tx_data  tx_data_vld
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          tx_data <= 0;
        end
        else begin
          tx_data <= rfifo_qout;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          tx_data_vld <= 0;
        end
        else begin
          tx_data_vld <= rfifo_rdreq;
        end
    end

    //rfifo_rdreq
    always @(*) begin
        if(rfifo_empty == 1'b0 && rdy)begin
          rfifo_rdreq = 1'b1;
        end
        else begin
          rfifo_rdreq = 1'b0;
        end
    end

    //wfifo_rdreq
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wfifo_rdreq <= 0;
        end 
        else if(wfifo_empty == 1'b0 && state_c == WAIT_WDONE && req && cnt_byte > 1)begin 
            wfifo_rdreq <= 1'b1;
        end 
        else begin 
            wfifo_rdreq <= 1'b0;
        end 
    end

    //rfifo_wrreq
     always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rfifo_wrreq <= 0;
        end 
        else if(rfifo_full == 1'b0 && rw_done && state_c == WAIT_RDONE && cnt_byte > 2)begin 
            rfifo_wrreq <= 1'b1;
        end 
        else begin 
            rfifo_wrreq <= 1'b0;
        end 
    end

    //fifo例化
    wrfifo	wrfifo_inst 
    (
	    .aclr          ( ~rst_n        ),
	    .clock         ( clk           ),
	    .data          ( wr_din        ),
	    .rdreq         ( wfifo_rdreq   ),
	    .wrreq         ( wfifo_wrreq   ),
	    .empty         ( wfifo_empty   ),
	    .full          ( wfifo_full    ),
	    .q             ( wfifo_qout    ),
	    .usedw         ( wfifo_usedw   )
    );

    assign wfifo_wrreq = wr_din_vld && wfifo_full == 1'b0;

    rdfifo	rdfifo_inst 
    (
    	.aclr          ( ~rst_n        ),
    	.clock         ( clk           ),
    	.data          ( rd_din        ),
    	.rdreq         ( rfifo_rdreq   ),
    	.wrreq         ( rfifo_wrreq   ),
    	.empty         ( rfifo_empty   ),
    	.full          ( rfifo_full    ),
    	.q             ( rfifo_qout    ),
    	.usedw         ( rfifo_usedw   )
    );


endmodule

5、i2c接口模块(i2c_interface.v)

//I2C接口模块   实现I2C协议时序
`include "parm.v"
module i2c_interface(

    input                       clk          ,//系统时钟
    input                       rst_n        ,

    input                       req          ,//传输使能
    input        [3:0]          cmd          ,//传输命令
    input        [7:0]          wr_din       ,//数据传输

    input                       sda_in       ,//数据输入
    output    reg               sda_out      ,//数据输出
    output    reg               sda_out_en   ,//数据输出使能
    output    reg               scl          ,

    output    reg               wr_fail      ,//从机未应答
    output    reg[7:0]          rd_dout      ,//从机读取1字节
    output    reg               rw_done       //字节读写完成
);

   //状态机参数定义
   localparam IDLE      = 7'b000_0001,
              START     = 7'b000_0010,
              WRITE     = 7'b000_0100,
              READ      = 7'b000_1000,
              RECV_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       ;//产生i2c串行时钟
    wire                    add_cnt_scl   ;
    wire                    end_cnt_scl   ;

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

    reg                     slave_ack     ;//从机应答

    wire                idle2start        ;
    wire                idle2write        ;
    wire                idle2read         ;
    wire                start2write       ;
    wire                start2read        ;
    wire                write2recv_ack    ;
    wire                read2send_ack     ;
    wire                recv_ack2idle     ;
    wire                recv_ack2stop     ;
    wire                send_ack2idle     ;
    wire                send_ack2stop     ;
    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(idle2write)
                state_n = WRITE;
              else if(idle2read)
                state_n = READ;
              else
                state_n = state_c;
            end
            START   :begin
              if(start2write)
              state_n = WRITE;
              else if(start2read)
              state_n = READ;
              else 
              state_n = state_c;
            end
            WRITE   :begin
              if(write2recv_ack)
              state_n = RECV_ACK;
              else 
              state_n = state_c;
            end
            READ    :begin
              if(read2send_ack)
              state_n = SEND_ACK;
              else 
              state_n = state_c;
            end
            RECV_ACK:begin
              if(recv_ack2stop)
              state_n = STOP;
              else if(recv_ack2idle)
              state_n = IDLE;
              else
              state_n = state_c;
            end
            SEND_ACK:begin
              if(send_ack2stop)
              state_n = STOP;
              else if(send_ack2idle)
              state_n = IDLE;
              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 & `CMD_START));
    assign idle2write     = state_c == IDLE     && (req && (cmd & `CMD_WRITE));
    assign idle2read      = state_c == IDLE     && (req && (cmd & `CMD_READ));
    assign start2write    = state_c == START    && (end_cnt_bit && (cmd & `CMD_WRITE));
    assign start2read     = state_c == START    && (end_cnt_bit && (cmd & `CMD_READ));
    assign write2recv_ack = state_c == WRITE    && (end_cnt_bit);
    assign read2send_ack  = state_c == READ     && (end_cnt_bit);
    assign recv_ack2idle  = state_c == RECV_ACK && (end_cnt_bit && ((cmd & `CMD_STOP) == 'd0));
    assign recv_ack2stop  = state_c == RECV_ACK && (end_cnt_bit && ((cmd & `CMD_STOP) || slave_ack));
    assign send_ack2idle  = state_c == SEND_ACK && (end_cnt_bit && ((cmd & `CMD_STOP) == 'd0));
    assign send_ack2stop  = state_c == SEND_ACK && (end_cnt_bit && (cmd & `CMD_STOP));
    assign stop2idle      = state_c == STOP     && (end_cnt_bit);

    //第三段
    //计数器 cnt_scl
    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_MAX-1;

    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;

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

    //slave_ack 采样从机应答
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          slave_ack <= 1'b1;
        end
        else if(state_c == RECV_ACK && cnt_scl == `SAMPLE)begin
          slave_ack <= sda_in;
        end
    end

    //串行时钟scl
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          scl <= 1'b1;
        end
        //从空闲状态到读或写状态,拉低scl
        else if(idle2start | idle2write | idle2read)begin
          scl <= 1'b0;
        end
        //计数器计数结束 且非停止状态 拉低scl
        else if(end_cnt_scl && stop2idle == 1'b0)begin
          scl <= 1'b0;
        end
        //产生串行时钟 计数器计数到一半 scl拉高
        else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
          scl <= 1'b1;
        end
    end

    //串行数据线  数据输出 sda_out
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          sda_out <= 1'b1;//初始状态 scl&sda处于高电平截止状态
        end
        //发送起始信号 scl为高电平时 拉低sda scl为低电平时 拉高sda
        else if(state_c == START)begin
          if(end_cnt_scl)
          sda_out <= 1'b1;
          else if(cnt_scl == `SAMPLE)
          sda_out <= 1'b0;
        end
        else if(state_c == WRITE && cnt_scl == `SEND)begin
          sda_out <= wr_din[7-cnt_bit];
        end
        else if(state_c == SEND_ACK && cnt_scl == `SEND)begin
          sda_out <= (cmd & `CMD_STOP)?1'b1:1'b0;
        end
        else if(state_c == STOP)begin
          if(cnt_scl == `SEND)
          sda_out <= 1'b0;
          else if(cnt_scl == `SAMPLE)
          sda_out <= 1'b1;
        end
    end

    //数据输出使能 sda_out_en
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          sda_out_en <= 1'b0;
        end
        else if(state_c == START | state_c == WRITE | state_c == SEND_ACK | state_c == STOP)begin
          sda_out_en <= 1'b1;
        end
        else begin
          sda_out_en <= 1'b0;
        end
    end

    //wr_fail   从机不应答
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          wr_fail <= 1'b0;
        end
        else if(slave_ack && stop2idle)begin
          wr_fail <= 1'b1;
        end
    end

    //rd_dout  读输出
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          rd_dout <= 0;
        end
        else if(state_c == READ && cnt_scl == `SAMPLE)begin
          rd_dout[7-cnt_bit] <= sda_in;
        end
    end

    //读写完成 rw_done
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          rw_done <= 0;
        end
        else begin
          rw_done <= recv_ack2idle | send_ack2idle | stop2idle;
        end
    end


endmodule

6、串口发送模块(uart_tx.v)

//串口发送模块

`include "parm.v"

module uart_tx(

    input                           clk       ,
    input                           rst_n     ,
    input          [7:0]            din       ,
    input                           din_vld   ,
    output      reg                 rdy       ,//already 可以接收数据并发送
    output      reg                 dout       
);

    //信号定义
    reg             [12:0]           cnt_bps      ;//波特率计数器
    wire                             add_cnt_bps  ;
    wire                             end_cnt_bps  ;

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

    reg                              tx_flag      ;//发送数据标志信号
    reg              [9:0]           tx_data      ;//发送数据寄存 起始位+数据位+停止位

    //计数器设计 cnt_bps  cnt_bit
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          cnt_bps <= 0;
        end
        else if(add_cnt_bps)begin
          if(end_cnt_bps)begin
              cnt_bps <= 0;
          end
          else begin
              cnt_bps <= cnt_bps + 1;
          end
        end
    end
    assign add_cnt_bps = tx_flag;
    assign end_cnt_bps = add_cnt_bps && cnt_bps == `BAUD-1;

    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_bps;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == 10-1;

    //发送数据标志信号
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          tx_flag <= 1'b0;
        end
        else if(din_vld)begin
          tx_flag <= 1'b1;//当输入信号有效时,拉低传输数据标志 传输数据开始
        end
        else if(end_cnt_bit)begin
          tx_flag <= 1'b0;//bit计数结束 拉低传输数据标志信号 结束数据传输
        end
    end

    //传输数据  tx_data
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          tx_data <= 0;
        end
        else if(din_vld)begin
          tx_data <= {
    
    1'b1,din,1'b0};//停止位+数据位+起始位
        end
    end

    //dout
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout <= 1'b1;
        end
        else if(add_cnt_bps && cnt_bps == 1)begin
          dout <= tx_data[cnt_bit];
        end
    end

    //rdy
    always @(*) begin
        if(din_vld | tx_flag)begin
          rdy = 0;
        end
        else begin
          rdy = 1;
        end
    end

endmodule

7、参数文件

//串口参数定义
//波特率
`define     BAUD_115200 
//`define     BAUD_57600
//`define     BAUD_38400
//`define     BAUD_19200
//`define     BAUD_9600  

//不同波特率对应计数周期
/*50_000_000 / BAUD_x*/

`ifdef BAUD_115200
    `define BAUD  434
`elsif BAUD_57600
     `define BAUD  868
`elsif BAUD_38400
     `define BAUD  1302
`elsif BAUD_19200
     `define BAUD  2604
`elsif BAUD_9600
     `define BAUD  5208
`endif 


//I2C 时钟参数
`define       SCL_MAX      150    //I2C时钟周期
`define       SCL_HALF      75    //I2C时钟翻转时间
`define       SEND          25    //数据发送时间
`define       SAMPLE       115    //采样数据时间

//命令码参数定义
`define       CMD_START   4'b0001   //发送起始命令
`define       CMD_WRITE   4'b0010   //写1字节数据
`define       CMD_READ    4'b0100   //读1字节数据
`define       CMD_STOP    4'b1000   //发送停止命令

//EEPROM 读写模式定义
// `define       ENABLE_BYTE_WRITE     //使能字节写
`define       ENABLE_PAGE_WRITE     //使能页写

`define       ENABLE_RANDOM_READ   //使能随机地址读
`define       ENABLE_SEQUEN_READ   //使能连续地址读

//条件编译
`ifdef ENABLE_BYTE_WRITE
     `define WR_BYTE  3
`elsif  ENABLE_PAGE_WRITE
     `define WR_BYTE  18
`endif 


`ifdef ENABLE_RANDOM_READ
     `define RD_BYTE 4
`elsif ENABLE_SEQUEN_READ
     `define RD_BYTE 19
`endif 

//从机器件地址  读写字节
`define SLAVE_CODE    4'b1010
`define WR_BIT        1'b0
`define RD_BIT        1'b1

四、功能验证

1、波形仿真

testbench

`timescale 1ns/1ns
                
module i2c_eeprom_tb();
//激励信号定义 
    reg				        clk  	    ;
    reg				        rst_n	    ;
    reg     [7:0]			uart_din    ;
    reg				        uart_din_vld;
   
//输出信号定义	 
    wire	            	uart_txd	;

    wire                    i2c_scl     ;
    wire                    i2c_sda     ;
    wire	        		tx_rdy      ;
    wire                    uart_tx_dout;  


//时钟周期参数定义					        
    parameter		CLOCK_CYCLE = 20;    

uart_tx u_uart_tx(
    .clk     (clk           ),
    .rst_n   (rst_n         ),
    .din     (uart_din      ),
    .din_vld (uart_din_vld  ),
    .rdy     (tx_rdy        ),//ready 表示串口发送模块可以接收数据并发送
    .dout    (uart_tx_dout  )    
);

i2c_eeprom u_i2c_eeprom(
    .clk        (clk            ),
    .rst_n      (rst_n          ),

    .uart_rxd   (uart_tx_dout   ),
    .uart_txd   (uart_txd       ),

    .i2c_scl    (i2c_scl        ),
    .i2c_sda    (i2c_sda        )    
);

i2c_slave_model i2c_slave(
    .scl        (i2c_scl        ), 
    .sda        (i2c_sda        )
);

//产生时钟							       		
    initial 	clk = 1'b0;		       		
    always #(CLOCK_CYCLE/2) clk = ~clk;  		

    task SEND;  
        input   [7:0]   wr_data     ;   
        begin
            # 1; 
            uart_din = wr_data;
            uart_din_vld = 1'b1;
            #(CLOCK_CYCLE);
            uart_din_vld = 1'b0;
            @(posedge tx_rdy);
            #(CLOCK_CYCLE*5);
        end 
    endtask 

    integer i = 0;

//产生激励							       		
    initial  begin						       		
        rst_n = 1'b0;								
        uart_din = 0;   
        uart_din_vld = 0;						
        #(CLOCK_CYCLE*20);				            
        rst_n = 1'b1;							
        #(CLOCK_CYCLE*20);	

        for(i=0;i<10;i=i+1)begin 
            //发数据
            SEND(8'h55);    //起始符
            SEND(8'haa);    //写数据指示信号                                         

            SEND(8'h55);    //数据                                          
            SEND(8'h35);    //数据 
            SEND(8'h55);    //数据 
            SEND(8'h67);    //数据 
            SEND(8'hfe);    //数据 
            SEND(8'h8c);    //数据 
            SEND(8'ha2);    //数据 
            SEND(8'hb7);    //数据 
            SEND(8'ha9);    //数据 
            SEND(8'hd0);    //数据 
            SEND(8'h53);    //数据 

            SEND(8'h04);    //结束符
            SEND(8'h0d);    //结束符
            #(CLOCK_CYCLE*200);	    
        
            //发写请求
            SEND(8'h55);    //起始符
            SEND(8'h00);    //写请求 block0
            SEND(8'ha3);    //写地址
            SEND(8'h04);    //结束符
            SEND(8'h0d);    //结束符

            //@(posedge u_i2c_eeprom.u_eeprom_control.wait_wdone2done);
            #(CLOCK_CYCLE*200);	
            //发读请求
            SEND(8'h55);    //起始符
            SEND(8'h10);    //写请求    block0
            SEND(8'ha3);    //读地址
            SEND(8'h04);    //结束符
            SEND(8'h0d);    //结束符
            #(CLOCK_CYCLE*200);	
        end 
        #(CLOCK_CYCLE*200);	
        $stop;
    end 									       	
endmodule 									       	

在这里插入图片描述

2、上板验证

发送数据
![在这里插入图片描述](https://img-blog.csdnimg.cn/dbe7dcb8938f46d68f8945bcf8a6b79b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5aSp5Lya5pm05bCx5Lya5pqX,size_16,color_FFFFFF,t_70,g_se,x_16
发送读写请求
在这里插入图片描述

五、参考资料

IIC协议学习笔记

I2C 总线协议详解

猜你喜欢

转载自blog.csdn.net/QWERTYzxw/article/details/121507838