FPGA——基于IIC协议的EEPROM功能实现

目录

一、EEPROM介绍

二、I2C协议

2.1 简介

2.2 I2C总线协议

2.2.1 开始与停止条件

 2.2.2 地址帧

2.2.3 数据帧

 三、EEPROM 24C02芯片

 3.1 写时序

 3.2 读时序   

​四、核心代码

4.1 I2C协议

4.1.1 逻辑图

4.1.2 相关代码 

4.2 EEPROM读写控制模块

4.2.1 原理框图

 4.2.2 相关代码

五、总结


一、EEPROM介绍

        EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦除可编程只读存储器)是一种非易失性存储器,它可以在不需要额外的设备支持的情况下被电子设备重新编程和擦除。

        EEPROM最明显的特点是它可以被电子设备内部的电路电子擦除和编程,而不需要使用外部设备或特殊的擦除/编程方法。这使得EEPROM非常方便和灵活,适用于各种应用,例如存储系统配置数据、校准值、设备参数等。

        EEPROM的存储单元由晶体管和电容器组成,每个存储单元可以存储一个比特(1或0)。为了编程和擦除EEPROM,一个存储单元的电荷状态会被改变,进而改变其保存的信息。编程通常是将存储单元的电荷增加到一个特定的电压,而擦除则是将存储单元的电荷完全清除。

        由于EEPROM是非易失性存储器,意味着它的数据在断电后仍然保持。这使得EEPROM非常适用于存储需要长期保存的重要信息,例如系统配置和设备的唯一识别码。

        EEPROM在计算机、消费电子产品和嵌入式系统中被广泛使用。它提供了一种可编程的存储解决方案,为电子设备提供了灵活性和可靠性。此外,EEPROM还具有较快的读取速度和较高的擦写/编程寿命,使得它成为许多应用中的理想选择。

二、I2C协议

2.1 简介

        IIC(Inter-Integrated Circuit),也被称为I2C(Inter-Integrated Circuit),是一种串行通信协议,用于在芯片和芯片之间进行数据传输。该协议由飞利浦半导体(现在是恩智浦半导体)在上世纪80年代开发,目的是在芯片之间实现简单、高效的通信。

        I2C协议使用两根线作为物理通信通道:Serial Data Line(SDA)和Serial Clock Line(SCL)。SDA线用于传输数据,而SCL线用于传输时钟信号。这两根线共享与多个设备连接的总线,因此I2C支持多主机和多从机的通信。

        I2C协议中的一个重要概念是主机和从机。主机是启动和控制通信的设备,而从机则被动地响应主机的命令并传输数据。通信过程中,主机向从机发送地址和命令,然后接收或发送数据。

        I2C协议支持多种数据传输模式,包括标准模式(最高传输速率为100 kbit/s)、快速模式(最高传输速率为400 kbit/s)和高速模式(最高传输速率为3.4 Mbit/s)。传输速率通常是由设备的能力和总线负载决定的。

        由于其简单灵活的设计和广泛的设备支持,I2C已被广泛应用于各种领域,包括电子设备、嵌入式系统、传感器、存储器和显示器等。它为芯片和模块之间的通信提供了一种方便可靠的解决方案,并简化了多设备系统的设计和开发过程。

 FPGA——基于IIC协议的EEPROM功能实现_第1张图片

2.2 I2C总线协议

        I2C 协议把传输的消息分为两种类型的帧:

        一个地址帧 :用于 master 指明消息发往哪个 slave;

        一个或多个数据帧: 由 master 发往 slave 的数据(或由 slave 发往 master),每一帧是 8-bit 的数据。

        I2C 数据传输的时序图如下:

FPGA——基于IIC协议的EEPROM功能实现_第2张图片

2.2.1 开始与停止条件

FPGA——基于IIC协议的EEPROM功能实现_第3张图片

 2.2.2 地址帧

        地址帧总是在一次通信的最开始出现。一个 7-bit 的地址是从最高位(MSB)开始发送的,这个地址后面会紧跟 1-bit 的操作符,1 表示读操作,0 表示写操作。接下来的一个 bit 是 NACK/ACK,当这个帧中前面 8bits 发送完后,接收端的设备获得 SDA 控制权,此时接收设备应该在第 9 个时钟脉冲之前回复一个 ACK(将 SDA 拉低)以表示接收正常,如果接收设备没有将 SDA 拉低,则说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由 master来决定如何处理(stop 或 repeated start condition)。

2.2.3 数据帧

        在地址帧发送之后,就可以开始传输数据了。Master 继续产生时钟脉冲,而数据则由 master(写操作)或 slave(读操作)放到 SDA 上。每个数据帧 8bits,数据帧的数量可以是任意的,直到产生停止条件。每一帧数据传输(即每 8-bit)之后,接收方就需要回复一个 ACK 或 NACK(写数据时由 slave 发送 ACK,读数据时由 master 发送 ACK。当 master 知道自己读完最后一个 byte 数据时,可发送 NACK 然后接 stop condition)。

FPGA——基于IIC协议的EEPROM功能实现_第4张图片

 三、EEPROM 24C02芯片

        24C02 的容量位 2Kbit=256Bytes,每 1 页 16Bytes 因此又 16 页。对于写操作一次最多写 16Bytes,对于读操作可以一次全部读完 256Bytes.

        如下图,A0-A2 是 EEPROM I2C 器件地址,SDA 和 SCL 是 EEPROM I2C 总线 SLAVE 接口,WP 是保护脚,一般接 VCC。

FPGA——基于IIC协议的EEPROM功能实现_第5张图片

         器件地址:

FPGA——基于IIC协议的EEPROM功能实现_第6张图片

 3.1 写时序

        支持单个字节的写,以及多个字节的写。首先发送器件的地址,然后发送需要写 EEPROM 存储空间的地址,之后就是数据,对于读操作一次可以写 1 个字节或者多个字节。

FPGA——基于IIC协议的EEPROM功能实现_第7张图片

 3.2 读时序   

        我们看下 24C02 的读时序,可以看到,支持单个字节的读,以及多个字节的读。以下支持 3 种读的方式:

        第一种:CURRENT ADDRESS READ 只要发送器件地址就能读数据。

        第二种:RANDOM READ 需要发送器件地址,然后发送内存地址,之后再发送器件地址并且读取到数据,支持连续读取。

        第三种:SEQUENTIAL CURRENT READ 只要发送器件地址,就能连续读取当前地址的数据,支持连续读取。

FPGA——基于IIC协议的EEPROM功能实现_第8张图片四、核心代码

4.1 I2C协议

4.1.1 逻辑图

FPGA——基于IIC协议的EEPROM功能实现_第9张图片

4.1.2 相关代码 

module i2c_dri
    #(
      parameter   SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址
      parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
      parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率
    )
   (                                                            
    input                clk        ,    
    input                rst_n      ,   
                                         
    //i2c interface                      
    input                i2c_exec   ,  //I2C触发执行信号
    input                bit_ctrl   ,  //字地址位控制(16b/8b)
    input                i2c_rh_wl  ,  //I2C读写控制信号
    input        [15:0]  i2c_addr   ,  //I2C器件内地址
    input        [ 7:0]  i2c_data_w ,  //I2C要写的数据
    output  reg  [ 7:0]  i2c_data_r ,  //I2C读出的数据
    output  reg          i2c_done   ,  //I2C一次操作完成
    output  reg          i2c_ack    ,  //I2C应答标志 0:应答 1:未应答
    output  reg          scl        ,  //I2C的SCL时钟信号
    inout                sda        ,  //I2C的SDA信号
                                       
    //user interface                   
    output  reg          dri_clk       //驱动I2C操作的驱动时钟
     );

//localparam define
localparam  st_idle     = 8'b0000_0001; //空闲状态
localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100; //发送16位字地址
localparam  st_addr8    = 8'b0000_1000; //发送8位字地址
localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束I2C操作

//reg define
reg            sda_dir   ; //I2C数据(SDA)方向控制
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //地址
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数

//wire define
wire          sda_in     ; //SDA输入信号
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数
//SDA控制
assign  sda     = sda_dir ?  sda_out : 1'bz;     //SDA数据输出或高阻
assign  sda_in  = sda ;                          //SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数

//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        dri_clk <=  1'b0;
        clk_cnt <= 10'd0;
    end
    else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;
    end
    else
        clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle: begin                          //空闲状态
           if(i2c_exec) begin
               next_state = st_sladdr;
           end
           else
               next_state = st_idle;
        end
        st_sladdr: begin
            if(st_done) begin
                if(bit_ctrl)                    //判断是16位还是8位字地址
                   next_state = st_addr16;
                else
                   next_state = st_addr8 ;
            end
            else
                next_state = st_sladdr;
        end
        st_addr16: begin                        //写16位字地址
            if(st_done) begin
                next_state = st_addr8;
            end
            else begin
                next_state = st_addr16;
            end
        end
        st_addr8: begin                         //8位字地址
            if(st_done) begin
                if(wr_flag==1'b0)               //读写判断
                    next_state = st_data_wr;
                else
                    next_state = st_addr_rd;
            end
            else begin
                next_state = st_addr8;
            end
        end
        st_data_wr: begin                       //写数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_wr;
        end
        st_addr_rd: begin                       //写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd: begin                       //读取数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_rd;
        end
        st_stop: begin                          //结束I2C操作
            if(st_done)
                next_state = st_idle;
            else
                next_state = st_stop ;
        end
        default: next_state= st_idle;
    endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    //复位初始化
    if(!rst_n) begin
        scl       <= 1'b1;
        sda_out   <= 1'b1;
        sda_dir   <= 1'b1;                          
        i2c_done  <= 1'b0;                          
        i2c_ack   <= 1'b0;                          
        cnt       <= 1'b0;                          
        st_done   <= 1'b0;                          
        data_r    <= 1'b0;                          
        i2c_data_r<= 1'b0;                          
        wr_flag   <= 1'b0;                          
        addr_t    <= 1'b0;                          
        data_wr_t <= 1'b0;                          
    end                                              
    else begin                                       
        st_done <= 1'b0 ;                            
        cnt     <= cnt +1'b1 ;                       
        case(cur_state)                              
             st_idle: begin                          //空闲状态
                scl     <= 1'b1;                     
                sda_out <= 1'b1;                     
                sda_dir <= 1'b1;                     
                i2c_done<= 1'b0;                     
                cnt     <= 7'b0;               
                if(i2c_exec) begin                   
                    wr_flag   <= i2c_rh_wl ;         
                    addr_t    <= i2c_addr  ;         
                    data_wr_t <= i2c_data_w;  
                    i2c_ack <= 1'b0;                      
                end                                  
            end                                      
            st_sladdr: begin                         //写地址(器件地址和字地址)
                case(cnt)                            
                    7'd1 : sda_out <= 1'b0;          //开始I2C
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b0;          //0:写
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;                         
                    end                              
                    7'd37: scl     <= 1'b1;            
                    7'd38: begin                     //从机应答 
                        st_done <= 1'b1;
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位     
                    end                                          
                    7'd39: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_addr16: begin                         
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1 ;            
                        sda_out <= addr_t[15];       //传送字地址
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[14];    
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[13];    
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[12];    
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[11];    
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[10];    
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[9];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[8];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;   
                    end                              
                    7'd33: scl  <= 1'b1;             
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end        
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_addr8: begin                          
                case(cnt)                            
                    7'd0: begin                      
                       sda_dir <= 1'b1 ;             
                       sda_out <= addr_t[7];         //字地址
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[6];     
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[5];     
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[4];     
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[3];     
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[2];     
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[1];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[0];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;         
                        sda_out <= 1'b1;                    
                    end                              
                    7'd33: scl     <= 1'b1;          
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_data_wr: begin                        //写数据(8 bit)
                case(cnt)                            
                    7'd0: begin                      
                        sda_out <= data_wr_t[7];     //I2C写8位数据
                        sda_dir <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= data_wr_t[6];  
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= data_wr_t[5];  
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= data_wr_t[4];  
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= data_wr_t[3];  
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= data_wr_t[2];  
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= data_wr_t[1];  
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= data_wr_t[0];  
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;           
                        sda_out <= 1'b1;                              
                    end                              
                    7'd33: scl <= 1'b1;              
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end          
                    7'd35: begin                     
                        scl  <= 1'b0;                
                        cnt  <= 1'b0;                
                    end                              
                    default  :  ;                    
                endcase                              
            end                                      
            st_addr_rd: begin                        //写地址以进行读数据
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd2 : sda_out <= 1'b0;          //重新开始
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b1;          //1:读
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;            
                        sda_out <= 1'b1;                    
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default : ;
                endcase
            end
            st_data_rd: begin                        //读取数据(8 bit)
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;          //非应答
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            st_stop: begin                           //结束I2C操作
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;             //结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 1'b0;
                        i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end
endmodule

4.2 EEPROM读写控制模块

4.2.1 原理框图

FPGA——基于IIC协议的EEPROM功能实现_第10张图片

 4.2.2 相关代码

module e2prom_rw(
    input                 clk        , //时钟信号
    input                 rst_n      , //复位信号

    //i2c interface
    output   reg          i2c_rh_wl  , //I2C读写控制信号
    output   reg          i2c_exec   , //I2C触发执行信号
    output   reg  [15:0]  i2c_addr   , //I2C器件内地址
    output   reg  [ 7:0]  i2c_data_w , //I2C要写的数据
    input         [ 7:0]  i2c_data_r , //I2C读出的数据
    input                 i2c_done   , //I2C一次操作完成
    input                 i2c_ack    , //I2C应答标志

    //user interface
    output   reg          rw_done    , //E2PROM读写测试完成
    output   reg          rw_result    //E2PROM读写测试结果 0:失败 1:成功
);

	parameter WR_WAIT_TIME 	= 14'd5000;
	parameter MAX_BYTE		= 16'd256;

	reg	[1 :0] flow_cnt;
	reg [13:0] wait_cnt;
	
	always@(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		i2c_rh_wl 	<= 1'b0;
		i2c_addr	<= 16'd0;
		i2c_data_w	<= 8'd0;
		i2c_exec	<= 1'b0;
		flow_cnt	<= 2'd0;
		wait_cnt	<= 14'd0;
		rw_done		<= 1'b0;
		rw_result	<= 1'b0;
	end
	else begin
		i2c_exec	<= 1'b0;
		rw_done		<= 1'b0;
		case(flow_cnt)
			2'd0:begin
				wait_cnt <= wait_cnt + 1'b1;
				if(wait_cnt == WR_WAIT_TIME - 1'b1) begin
					if(i2c_addr == MAX_BYTE - 1'b1) begin
						i2c_addr  	<= 16'd0;
						i2c_rh_wl 	<= 1'b1;
						flow_cnt	<= 2'd2;
					end
					else begin
						flow_cnt	<= flow_cnt + 1'b1;
						i2c_exec	<= 1'b1;
					end
				end
			end
			
			2'd1:begin
				if(i2c_done == 1'b1) begin
					i2c_addr	<= i2c_addr + 1'b1;
					flow_cnt	<= 2'd0;
					i2c_data_w	<= i2c_data_w + 1'b1;
				end		
			end
			
			2'd2:begin
				flow_cnt	<= flow_cnt + 1'b1;
				i2c_exec	<= 1'b1;
			end
			
			2'd3:begin
				if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
					rw_done		<= 1'b1;
					rw_result	<= 1'b0;
				end
				else if(i2c_addr == MAX_BYTE - 1'b1) begin
					rw_done		<= 1'b1;
					rw_result	<= 1'b1;
				end
				else begin
					flow_cnt	<= 2'd2;
					i2c_addr	<= i2c_addr + 1'b1;
				end
			end
		endcase
	end
	end

endmodule

五、总结

        关于EEPROM功能的实现的关键在于I2C协议,因此只需要熟悉了解与掌握关于I2C协议的相关驱动模块状态跳转图,按照状态图进行相关的代码编写即可。当然关于I2C协议的时序图,同样重要,需要了解与掌握开始、停止、地址位、数据位的发送与SClK时钟的同步。

        本次代码中具有一个非常重要的因素。就是关于系统时钟的分频。

在生成dri_clk时钟时,本代码选择了与SCLK时钟4倍的关系,即可更好的生成SCLK与数据位、地址位的传输。

FPGA——基于IIC协议的EEPROM功能实现_第11张图片

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