【FPGA】I2C读写EEPROM

一丶I2C协议

1.I2C简介

I2C即Inter-Integrated Circuit(集成电路总线),是由Philips半导体公司(现在的NXP半导体公司)在八十年代初设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。


I2C总线由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接收数据。在主控与被控IC之间可进行双向数据传送数据的传输速率:

  • 标准模式:100kbit/s
  • 快速模式:400kbit/s
  • 高速模式:3.4Mbit/s

各种被控器件均并联在总线上,通过器件地址(SLAVEADDR,具体可查器件手册)识别。结构示意图如下所示(出自原子哥):

43

图中的I2C_SCL串行时钟线I2C_SDA串行数据线,由于I2C器件一般采用开漏结构与总线相连,所以I2C_SCL和I2C_SDA均需接上拉电阻,也正因此,当总线空闲时,这两条线路都处于高电平状态,当连到总线上的任一器件输出低电平,都将使总线拉低,即各器件的SDA及SCL都是“线与”关系。

I2C总线支持多主主从两种工作方式,通常工作在主从工作方式,我们的开发板就采用主从工作方式。在主从工作方式中,系统中只有一个主机,其它器件都是具有I2C总线的外围从机。在主从工作方式中,主机启动数据的发送(发出启动信号)并产生时钟信号,数据发送完成后,发出停止信号。

I2C总线结构虽然简单,使用两线传输,然而要实现器件间的通信,需要通过控制SCL和SDA的时序,使其满足I2C的总线传输协议,方可实现器件间的数据传输。那么I2C协议的时序是怎样的呢?

2.I2C时序

空闲状态:
在I2C器件开始通信(传输数据)之前,串行时钟线SCL和串行数据线SDA线由于上拉的原因处于高电平状态,此时I2C总线处于空闲状态。
起始信号
如果主机(此处指FPGA)想开始传输数据,只需在SCL为高电平时将SDA线拉低,产生一个起始信号,从机检测到起始信号后,准备接收数据
停止信号:
当数据传输完成,主机只需产生一个停止信号,告诉从机数据传输结束,停止信号的产生是在SCL为高电平时,SDA从低电平跳变到高电平,从机检测到停止信号后,停止接收数据。


I2C整体时序如下图。起始信号之前为空闲状态,起始信号之后到停止信号之前的这一段为数据传输状态,主机可以向从机写数据,也可以读取从机输出的数据,数据的传输由双向数据线(SDA)完成。停止信号产生后,总线再次处于空闲状态。
在这里插入图片描述

了解到了整体时序之后,我们可能有疑问,
数据是以什么样的格式传输的呢?
满足怎样的时序要求呢?
是在任何时候改变都可以吗?
怎么知道从机有没有接收到数据呢?

带着这些疑问,我们继续学习I2C。

由于只有一根数据线进行数据的传输,如果不规定好传输规则肯定会导致信息错乱,如同在单条道路上驾驶,没有交通规则,再好的道路也会发生拥堵甚至更糟。采用两线结构的I2C虽然只有一根数据线,但由于还有一条时钟线,可以让数据线在时钟线的带领下有顺序的传送,就好像单条道路上的车辆在交警或信号指示灯的指示下有规则的通行。那么I2C遵循怎样的规则呢?

在这里插入图片描述

数据传输:
我们在起始信号之后,主机开始发送传输的数据;在串行时钟线SCL为低电平状态时,SDA允许改变传输的数据位(1为高电平,0为低电平),在SCL为高电平状态时,SDA要求保持稳定,相当于一个时钟周期传输1bit数据,经过8个时钟周期后,传输了8bit数据,即一个字节。

应答信号:
第8个时钟周期末主机释放SDA以使从机应答,在第9个时钟周期,从机将SDA拉低以应答(NCK);如果第9个时钟周期,SCL为高电平时,SDA未被检测到为低电平,视为非应答(NACK),表明此次数据传输失败。第9个时钟周期末,从机释放SDA以使主机继续传输数据,如果主机发送停止信号,此次传输结束。我们要注意的是数据以8bit即一个字节为单位串行发出,其最先发送的是字节的最高位

注意:若是接收端是主控端,则在它收到最后一个bit数据(发送完1字节数据)后,发送一个非应答(NACK)信号,以通知被控发送端结束数据发送,并释放SDA线,以便主控接收器发送停止信号。

二丶EEPROM存储器

1.有哪些存储器?

首先我们先弄清楚在计算机中有哪些存储器
在这里插入图片描述

ROM(只读存储器,Read only memory)指一旦写入,则无法擦除改写的存储器。此处,将ROM的定义做进一步的扩展,定义为非易失存储器,即器件掉电后,内部存储器内容仍保留,且支持电可擦除可改写的只读存储器
电路设计中常用的ROM包括EEPROM(电可擦写可编程只读存储器)和FLASH(闪速存储器)

其它存储器介绍见存储器分类


由于我们开发板上的EEPROM采用两线串行接口的双向数据传输协议——I2C协议实现读写操作,所以这里我们主要分析对EEPROM的读写

2.手册分析及时序分析

又到了最痛苦的环节,阅读英文手册😨,没办法,一点一点来,不要放弃,看的多了自然就熟练了,同志们加油!!!😄

从头到尾提取出我们需要的信息:

1.查看手册对于开发板上EEPROM的描述
在这里插入图片描述

可以知道本次实验所用的EEPROM的存储容量为4Kbit,内部分成两块(block),每一块有256个字节,共有4096bit(256*2*8)

2.查看手册中的功能描述

在这里插入图片描述

总线由生成时钟信号(SCL)和起始结束条件的主控设备控制

3.查看总线特性
在这里插入图片描述

在数据传输期间,数据线(SDA)在时钟线(SCL)高电平期间保持稳定;而在时钟线高电平期间,数据线的改变作为通信的开始和结束条件

4.查看通信的起始和结束条件
在这里插入图片描述

起始信号:在时钟线(SCL)高电平期间,数据线(SDA)由高电平拉低,且起始信号必须在所有指令之前,也就是作为通信的开始
结束信号:在时钟线(SCL)高电平期间,数据线(SDA)由低电平拉高,且结束信号必须在所有指令操作之后,也就是以结束信号作为通信的结束

5.查看**“数据有效”**
在这里插入图片描述
重要信息:在对EEPROM进行写操作的时候,只会存储最后写进去的16个字节。这里不是很清晰我们在下面的PageWrite详细解释

6.查看**“应答位”**

在这里插入图片描述
i2C总线上的所有数据都是以字节传送的,发送端每发送一个字节,就必须在第9个SCL脉冲期间释放SDA,由接收端反馈一个应答信号。应答信号为低电平时,称为有效应答位(ACK),表示接收端成功接收了该字节;应答信号为高电平时,称为非应答位(NACK),表示接收端接收该字节失败。对于反馈有效应答位ACK的要求是,接收端在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟周期的高电平期间保持低电平。如果接收端是主控端,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送端结束数据发送,并释放SDA线,以便主控接收器发送停止信号。
7.查看**“设备地址”**
在这里插入图片描述

主机在与从机通信之前,主机需要配置对应要访问的从机的地址。

  • 所以主从机通信的第一个字节是控制字节,其中前四位是从的设备编码“1010”;
  • 接下来的两位没实际作用;
  • “B0”控制所访问从机的哪一块存储空间;
  • “R/W”控制读写操作,0代表写操作,1代表读操作;
  • 最后从机发送应答位(ACK)

在这里插入图片描述

8.查看 “写操作”
在这里插入图片描述

看到这里可以发现写操作分为两种:即 字节写页写
①首先来看字节写
在这里插入图片描述

  • 首先是起始位,接着发送第一个字节(控制字节),注意最后一位设置为“0”,代表写操作;
  • 之后从机发送应答位(ACK),主机接着发送第二个字节(要写数据的存储地址);
  • 之后从机发送应答位(ACK),主机发送要写的数据;
  • 之后从机发送应答位(ACK),主机发送停止位结束这一次写操作

②然后看页写
在这里插入图片描述
很清晰的可以看出页写与字节写的区别只在于多写了15个字节的数据(一页最多写16字节)
另外这里需要注意一个重点:

在这里插入图片描述
翻译:
收到每个字后,组成字节计数器的四个低阶地址指针位在内部加1。字地址的高阶4位和B0位保持不变。

在写满16字节之后如果再写数据,地址指针将滚动,并且先前接收到的数据将被覆盖。(通俗来讲就是,低四位表示“1111”,再增加数据,加1进位,变成“0000”,也就是又从第一位开始写,这样会覆盖之前写入的数据)

9.查看 “读操作”
在这里插入图片描述

有三种基本的读操作:当前地址读、随机读和顺序读。

当前地址读
在这里插入图片描述

翻译:
如果之前的访问(读或写操作)是为了访问地址n,那么下一个当前地址读操作将访问地址n + 1的数据

然后来看时序图
在这里插入图片描述
与写操作不同的是,控制字节的最后1bit是“1”,表示读操作;
之后从机发送8位数据,主机返回非应答(NACK)并发送停止位。

随机地址读

在这里插入图片描述

随机地址读分为了两个部分
写操作:起始位→一个写控制字节→ACK→字地址→ACK
读操作:起始位→一个读控制字节→ACK→数据→NACK

顺序地址读

在这里插入图片描述

顺序地址读的时序承接随即地址读,随机地址读只读出了一个字节数据主机就发送了NACK,顺序地址读不止读出一个数据。

到这里我们需要的信息基本提取完成!

三丶需求分析

系统功能需求

1、使用按键,模拟读写请求信号;
2、收到读写请求信号时,FPGA 向 EEPROM 芯片写入单字节数据或从 EEPROM 芯片读出单字节数据;
3、把写入或者读出的数据显示在数码管,并指示当前是读操作、写操作或者 空操作。

系统模块划分

1、I2C 接口模块:按照 I2C 协议时序将数据串行写入 EEPROM 或者从 EEPROM 读出串行数据; EEPROM
2、读写控制模块:根据接收到的按键信号请求以及 I2C 接口模块反馈信号,发送请求、指令以及数据给 I2C 接口模块或者从 I2C
3、接口模块读取数据; 数码管驱动模块:指示当前状态,并将写入或读出的数据显示在数码管上。


i2c_master状态机 - - - I2C接口模块
在这里插入图片描述

  • IDLE:空闲状态
  • START: 发送起始位
  • WRITE:写数据
  • READ:读数据
  • R_ACK:接收应答
  • S_ACK:发送应答
  • STOP:发送停止位

这个状态机我们可以分为读和写操作分别分析

读:
以随机地址读为例
IDLE→START→WRITE(首先写读控制字节)→R_ACK
→IDLE→WRITE(写字节地址)→R_ACK
→IDLE→START→WRITE(写读控制字节)→R_ACK
→IDLE→READ→S_ACK→STOP

写:
以字节写为例
IDLE→START→WRITE(首先写读控制字节)→R_ACK
→IDLE→WRITE(写字节地址)→R_ACK
→IDLE→WRITE(写数据)→R_ACK→STOP

eeprom读写控制状态机 - - - 读写控制模块
在这里插入图片描述

  • IDLE:空闲状态
  • WR_REQ: 写传输 发送请求、命令、数据
  • WAIT_WR:等待一个字节传完
  • RD_REQ:读传输 发送请求、命令、数据
  • WAIT_RD:等待一个字节传完
  • DONE:一次读或写完成

四丶系统框图

在这里插入图片描述在这里插入图片描述

五丶代码设计

1.顶层模块

top.v

module top (
    input              clk         ,
    input              rst_n       ,
    input              rx          ,//uart接收端口
    input              key         ,//按键输入
    output             tx          ,//uart发送端口
    output             scl         ,//IIC时钟线
    inout              sda          //IIC数据线 
);
    //按键
    wire        key_done     ;
    //串口
    wire [7:0]  dout         ;
    wire        dout_vld     ;
    wire [7:0]  din          ;
    wire        din_vld      ;
    wire        ready        ;


    uart_rx u_uart_rx(
        /* input  wire         */ .clk       (clk       )    ,
        /* input  wire         */ .rst_n     (rst_n     )    ,
        /* input  wire         */ .rx        (rx        )    ,
        /* output reg  [7:0]   */ .dout      (din      )    ,    //由接收到的数据进行并转串输出给uart_tx模块
        /* output reg          */ .dout_vld  (din_vld  )         //接收完成标志位
    );

    uart_tx u_uart_tx(
        /* input  wire         */ .clk       (clk       )    ,
        /* input  wire         */ .rst_n     (rst_n     )    ,
        /* input  wire [7:0]   */ .din       (dout       )    ,
        /* input  wire         */ .din_vld   (dout_vld   )    ,
        /* output reg          */ .tx        (tx        )    ,
        /* output reg          */ .ready     (ready     )    
    );

    key_debounce u_key_debounce(   
        /* input  wire         */ .clk       (clk       )    ,     //系统时钟 50MHz
        /* input  wire         */ .rst_n     (rst_n     )    ,     //复位信号
        /* input  wire         */ .key       (key       )    ,     //按键输入信号
        /* output reg          */ .key_done  (key_done  )          //消抖之后的按键信号
    );

    control u_control(
        /* input               */ .clk       (clk       )    ,
        /* input               */ .rst_n     (rst_n     )    ,
        /* //串口  *
        /* input       [7:0]   */ .din       (din      )    ,
        /* input               */ .din_vld   (din_vld  )    ,
        /* output      [7:0]   */ .dout      (dout       )    ,
        /* output              */ .dout_vld  (dout_vld   )    ,
        /* input               */ .ready     (ready     )    ,
        /* //按键  *
        /* input               */ .rd_en     (key_done  )    ,
        /* //IIC信号  *
        /* output              */ .i2c_scl   (scl       )    ,//IIC时钟
        /* inout               */ .i2c_sda   (sda       )     //IIC数据
    );

endmodule

2.串口模块

uart_rx

module uart_rx (
    input  wire         clk,
    input  wire         rst_n,
    input  wire         rx,
    output reg  [7:0]   dout,    //由接收到的数据进行并转串输出给uart_tx模块
    output reg          dout_vld //接收完成标志位
);

reg             flag;       //rx下降沿来临flag拉高   
reg [9:0]       dout_r;     //锁存rx接收到的数据(从起始位 到数据为 到停止位)

reg             rx_r0;      //同步
reg             rx_r1;      //打拍
wire            nedge;      //下降沿检测

//波特率计数器
reg [8:0]       cnt_bps;
wire            add_cnt_bps;
wire            end_cnt_bps;

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

parameter       BPS_115200 = 434;


//同步
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_r0<=1;
    end
    else
        rx_r0<=rx;
end
//打拍
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_r1<=1;   
    end
    else
         rx_r1<=rx_r0;
end

assign nedeg=~rx_r0 & rx_r1;   //下降沿检测,起始位为低电平

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

//cnt_bps
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
            cnt_bps<=cnt_bps+1;
    end
end

assign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;

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

assign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==10 -1;

//dout_r
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        dout_r<=0;
    end
    else if(cnt_bps==(BPS_115200>>1)) begin
        dout_r[cnt_bit]<=rx_r0;
    end
end

//dout
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        dout<=0;
    end
    else if(end_cnt_bit) begin
        dout<=dout_r[8:1];        //只用把数据位传给uart_tx模块,所以取中间的8位
    end
end

//dout_vld
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        dout_vld<=0;
    end
    else if(end_cnt_bit) begin
        dout_vld<=1;        //给发送模块的数据有效信号
    end
    else
        dout_vld<=0;
end
endmodule //uart_rx

uart_tx

module uart_tx (
    input  wire         clk,
    input  wire         rst_n,
    input  wire [7:0]   din,
    input  wire         din_vld,
    output reg          tx,
    output reg          ready
);
//定义一个寄存器来锁存 din_vld 时的din
reg [9:0]       data;

//波特率计数器
reg [8:0]       cnt_bps;
wire            add_cnt_bps;
wire            end_cnt_bps;

//比特计数器
reg [4:0]       cnt_bit;
wire            add_cnt_bit;
wire            end_cnt_bit;

reg             flag;   //计数器开启标志位

parameter       BPS_115200=434;  //发送一bit数据需要的周期数

//data
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data<=0;
    end
    else if(din_vld) begin
        data<={1'b1,din,1'b0};   //拼接起始位和停止位
    end
    else
        data<=data;
end

//发送数据 tx
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        tx<=1'b1;
    end
    else if(cnt_bps==1) begin
        tx<=data[cnt_bit];
    end
end

//flag
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        flag<=0;
    end
    else if(din_vld) begin
        flag<=1;
    end
    else if(end_cnt_bit) begin   //发送完成关闭计数器
        flag<=0;
    end
    else
        flag<=flag;
end

//ready
always @(*) begin
    if (!rst_n) begin
        ready<=1;
    end
    else if(flag) begin
        ready<=0;
    end
    else begin
        ready<=1;
    end
end

//cnt_bps
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
            cnt_bps<=cnt_bps+1;
    end
end

assign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;

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

assign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==9;

endmodule //uart_tx

3.控制模块

control.v

module control (
    input               clk         ,
    input               rst_n       ,
    //串口
    input       [7:0]   din         ,
    input               din_vld     ,
    output      [7:0]   dout        ,
    output              dout_vld    ,
    input               ready       ,
    //按键
    input               rd_en       ,
    //IIC信号
    output              i2c_scl     ,//IIC时钟
    inout               i2c_sda      //IIC数据
);
//信号定义
    wire             req             ;
    wire     [3:0]   cmd             ;
    wire     [7:0]   wr_data         ;
    wire     [7:0]   rd_data         ;
    wire             done            ;
    wire             slave_ack       ;
    wire             i2c_sda_i       ;
    wire             i2c_sda_o       ;
    wire             i2c_sda_oe      ;

//三态门
    assign i2c_sda = i2c_sda_oe?i2c_sda_o:1'bz;
    assign i2c_sda_i = i2c_sda;
 

//模块例化

    eeprom_ctrl u_eeprom_ctrl(
    /*input               */.clk     (clk       ),
    /*input               */.rst_n   (rst_n     ),
    
    /*input       [7:0]   */.din     (din       ),
    /*input               */.din_vld (din_vld   ),
    /*input               */.rd_en   (rd_en     ),
    /*output      [7:0]   */.dout    (dout      ),//控制器输出数据
    /*output              */.dout_vld(dout_vld  ),
    /*input               */.ready   (ready     ),
    /*output              */.req     (req       ),
    /*output      [3:0]   */.cmd     (cmd       ),
    /*output      [7:0]   */.wr_data (wr_data   ),
    /*input       [7:0]   */.rd_data (rd_data   ),
    /*input               */.done    (done      ) //传输完成标志   
    );

    i2c_master u_i2c_master(
    /*input               */.clk         (clk       ),
    /*input               */.rst_n       (rst_n     ),

    /*input               */.req         (req       ),
    /*input       [3:0]   */.cmd         (cmd       ),
    /*input       [7:0]   */.din         (wr_data   ),

    /*output      [7:0]   */.dout        (rd_data   ),
    /*output              */.done        (done      ),

    /*output              */.i2c_scl     (i2c_scl   ),
    /*input               */.i2c_sda_i   (i2c_sda_i ),
    /*output              */.i2c_sda_o   (i2c_sda_o ),
    /*output              */.i2c_sda_oe  (i2c_sda_oe)   
    );
endmodule

4.IIC接口

i2c_master.v

`include "param.v"

module i2c_master (
    input               clk         ,
    input               rst_n       ,
    //控制信号
    input               req         ,//请求
    input       [3:0]   cmd         ,//命令
    input       [7:0]   din         ,//写数据
    //IIC信号
    output              i2c_scl     ,//SCL---200kHz
    input               i2c_sda_i   ,//SDA---输入
    output              i2c_sda_o   ,//SDA---输出
    output              i2c_sda_oe  ,//SDA---输出使能 

    output      [7:0]   dout        ,
    output              done        
);
//状态机参数定义

    localparam  IDLE  = 7'b000_0001,
                START = 7'b000_0010,
                WRITE = 7'b000_0100,
                RACK  = 7'b000_1000,
                READ  = 7'b001_0000,
                SACK  = 7'b010_0000,
                STOP  = 7'b100_0000;

    reg [6:0]   state_c      ;//现态
    reg [6:0]   state_n      ;//次态

    reg [3:0]   cmd_r        ;//命令寄存器
    reg [7:0]   din_r        ;//写数据寄存器
    reg [7:0]   dout_r       ;//输出数据寄存器
    reg         slave_ack_r  ;//接收应答寄存器
//IIC信号寄存
    reg         scl          ;
    reg         sda_out      ;
    reg         sda_out_en   ;   

//比特计数器
    reg [3:0]  cnt_bit       ;
    wire       add_cnt_bit   ;
    wire       end_cnt_bit   ;

//IIC时钟计数器
    reg [7:0]  cnt_scl       ;
    wire       add_cnt_scl   ;
    wire       end_cnt_scl   ;

//状态转移条件
    wire idle2start          ;
    wire idle2write          ;
    wire idle2read           ;
    wire start2write         ;
    wire start2read          ;
    wire write2rack          ;
    wire rack2stop           ;
    wire rack2idle           ;
    wire read2sack           ;
    wire sack2stop           ;
    wire sack2idle           ; 
    wire stop2idle           ;          

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

    always @(*) begin
        case (state_c)
            IDLE  :begin
                if(idle2start)
                    state_n = START;
                else if (idle2write) begin
                    state_n = WRITE;
                end
                else if (idle2read) begin
                    state_n = READ;
                end
                else
                    state_n = state_c;
            end
            START :begin
                if (start2write) begin
                    state_n = WRITE;
                end
                else if (start2read) begin
                    state_n = READ;
                end
                else
                    state_n = state_c;
            end
            WRITE :begin
                if(write2rack)
                    state_n = RACK ;
                else 
                    state_n = state_c ;
            end
            RACK :begin
                if(rack2stop)
                    state_n = STOP ;
                else if(rack2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            READ :begin
                if(read2sack)
                    state_n = SACK ;
                else 
                    state_n = state_c ;
            end
            SACK :begin
                if(sack2stop)
                    state_n = STOP ;
                else if(sack2idle)
                    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_r &`CMD_WRITE)) ;
    assign start2read  = state_c==START && (end_cnt_bit && (cmd_r &`CMD_READ)) ;
    assign write2rack  = state_c==WRITE && (end_cnt_bit) ;
    assign read2sack   = state_c==READ  && (end_cnt_bit) ;
    assign rack2stop   = state_c==RACK  && (end_cnt_bit && ((cmd_r & `CMD_STOP) || slave_ack_r));
    assign sack2stop   = state_c==SACK  && (end_cnt_bit && (cmd_r & `CMD_STOP)) ;
    assign rack2idle   = state_c==RACK  && (end_cnt_bit && (cmd_r & `CMD_STOP) == 0) ;
    assign sack2idle   = state_c==SACK  && (end_cnt_bit && (cmd_r & `CMD_STOP) == 0) ;
    assign stop2idle   = state_c==STOP  && (end_cnt_bit) ;

//scl
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            scl <= 1;
        end
        else if (idle2start | idle2write | idle2read) begin
            scl <= 0;
        end
        else if (add_cnt_scl && cnt_scl == `SCL_HALF-1) begin
            scl <= 1;
        end
        else if (end_cnt_scl && ~stop2idle) begin
            scl <= 0;
        end
    end

//sda_out_en
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sda_out_en <= 0;
        end
        else if(idle2start | idle2write | read2sack | rack2stop)begin
            sda_out_en <= 1'b1;
        end
        else if(idle2read | start2read | write2rack | stop2idle)begin 
            sda_out_en <= 1'b0;
        end 
    end

//sda_out
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sda_out <= 1;
        end
    //发起始位
        else if (state_c == START) begin
             if (cnt_scl == `LOW_HLAF) begin    
                sda_out <= 1;       //时钟低电平拉高数据线
             end
             else if (cnt_scl == `HIGH_HALF) begin
                sda_out <= 0;       //时钟高电平拉低数据线---保证可以检测到起始位
             end
        end
    //发数据
        else if (state_c == WRITE) begin
            if (cnt_scl == `LOW_HLAF) begin
                sda_out <= din_r[7-cnt_bit];    //时钟低电平发送数据---并串转换
            end
        end
    //发应答位
        else if (state_c == SACK) begin
            if (cnt_scl ==`LOW_HLAF) begin
                sda_out <= (cmd_r & `CMD_STOP ? 1 : 0);    //时钟低电平发应答位
            end
        end
    //发停止位
        else if (state_c == STOP) begin
            if (cnt_scl == `LOW_HLAF) begin
                sda_out <= 0;       //时钟低电平拉低数据线
            end
            else if (cnt_scl == `HIGH_HALF) begin
                sda_out <= 1;       //时钟高电平拉高数据线---保证可以检测到停止位
            end
        end
    end

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

//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
                cnt_scl <= cnt_scl+1;
        end
    end
    
    assign add_cnt_scl = (state_c != IDLE);
    assign end_cnt_scl = add_cnt_scl && cnt_scl == (`SCL_PERIOD) -1;

//cnt_bit
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_bit <= 0;
        end
        else if(add_cnt_bit) begin
            if (end_cnt_bit) begin
                cnt_bit <= 0;
            end
            else
                cnt_bit <= cnt_bit+1;
        end
    end
    
    assign add_cnt_bit = end_cnt_scl;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == ((state_c == READ || state_c == WRITE) ? 8 :1)-1;//除开读写数据需要传输8bit数据,其他都是1bit

//dout_r
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            dout_r <= 0;
        end
        else if (state_c == READ) begin
            if (cnt_scl == `HIGH_HALF) begin
                dout_r[7-cnt_bit] <= i2c_sda_i;     //串并转换
            end
        end
    end

//slave_ack_r
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            slave_ack_r <= 1;
        end
        else if (state_c == RACK) begin
            if (cnt_scl == `HIGH_HALF) begin
                slave_ack_r <= i2c_sda_i;
            end
        end
    end

    assign i2c_scl = scl;
    assign i2c_sda_o = sda_out;
    assign i2c_sda_oe = sda_out_en;

    //assign slave_ack = slave_ack_r;
    assign dout = dout_r;
    assign done = rack2idle | sack2idle | stop2idle;//1字节数据传输完的标志---每传输1字节都有一个应答位
endmodule

5.eeprom控制器

eeptom_ctrl.v

`include "param.v"

module eeprom_ctrl (
    input               clk         ,
    input               rst_n       ,
    //按键使能
    input               rd_en       ,
    //串口信号
    input       [7:0]   din         ,
    input               din_vld     ,
    output      [7:0]   dout        ,
    output              dout_vld    ,
    input               ready    ,
    //IIC接口信号
    output              req         ,
    output      [3:0]   cmd         ,
    output      [7:0]   wr_data     ,//向EEPROM写数据
    input       [7:0]   rd_data     ,//从EEPROM读数据
    input               done        
);

//状态机参数
    localparam      IDLE    = 6'b00_0001    ,
                    WR_REQ  = 6'b00_0010    ,//写传输 发送请求、命令、数据
                    WAIT_WR = 6'b00_0100    ,//等待一个字节传完
                    RD_REQ  = 6'b00_1000    ,//读传输 发送请求、命令、数据
                    WAIT_RD = 6'b01_0000    ,//等待一个自己传完
                    DONE    = 6'b10_0000    ;//一次读或写完成
//信号定义
    reg [5:0]       state_c;//现态
    reg [5:0]       state_n;//次态

    reg [3:0]       cnt_byte;
    wire            add_cnt_byte;  
    wire            end_cnt_byte;  

    reg             tx_req          ;//请求
    reg     [3:0]   tx_cmd          ;
    reg     [7:0]   tx_data         ;

    reg     [8:0]   wr_addr         ;//写eeprom地址
    reg     [8:0]   rd_addr         ;//读eeprom地址

    reg     [7:0]   dout_r          ;
    reg             dout_vld_r      ;

    wire            wfifo_rd        ;
    wire            wfifo_wr        ;
    wire            wfifo_empty     ;
    wire            wfifo_full      ;
    wire    [7:0]   wfifo_qout      ;
    wire    [5:0]   wfifo_usedw     ;

    wire            rfifo_rd        ;
    wire            rfifo_wr        ;
    wire            rfifo_empty     ;
    wire            rfifo_full      ;
    wire    [7:0]   rfifo_qout      ;
    wire    [5:0]   rfifo_usedw     ;

    wire            idle2wr_req     ;
    wire            wr_req2wait_wr  ;
    wire            wait_wr2wr_req  ;
    wire            wait_wr2done    ;
    wire            idle2rd_req     ;
    wire            rd_req2wait_rd  ;
    wire            wait_rd2rd_req  ;
    wire            wait_rd2done    ;
    wire            done2idle       ;


//状态机
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state_c <= IDLE;
        end
        else
            state_c <= state_n;
    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
                    if(wr_req2wait_wr)
                        state_n = WAIT_WR ;
                    else 
                        state_n = state_c ;
                end
                WAIT_WR :begin
                    if(wait_wr2wr_req)
                        state_n = WR_REQ ;
                    else if(wait_wr2done)
                        state_n = DONE ;
                    else 
                        state_n = state_c ;
                end
                RD_REQ :begin
                    if(rd_req2wait_rd)
                        state_n = WAIT_RD ;
                    else 
                        state_n = state_c ;
                end
                WAIT_RD :begin
                    if(wait_rd2rd_req)
                        state_n = RD_REQ ;
                    else if(wait_rd2done)
                        state_n = DONE ;
                    else 
                        state_n = state_c ;
                end
                DONE :begin
                    if(done2idle)
                        state_n = IDLE ;
                    else 
                        state_n = state_c ;
                end
                default : state_n = IDLE ;
            endcase
        end

    assign  idle2wr_req    = state_c == IDLE     && (wfifo_usedw > 0);
    assign  wr_req2wait_wr = state_c == WR_REQ   && (1);
    assign  wait_wr2wr_req = state_c == WAIT_WR  && (done & cnt_byte < `WR_BYTE-1);
    assign  wait_wr2done   = state_c == WAIT_WR  && (end_cnt_byte);
    assign  idle2rd_req    = state_c == IDLE     && (rd_en);
    assign  rd_req2wait_rd = state_c == RD_REQ   && (1);
    assign  wait_rd2rd_req = state_c == WAIT_RD  && (done & cnt_byte < `RD_BYTE-1);
    assign  wait_rd2done   = state_c == WAIT_RD  && (end_cnt_byte);
    assign  done2idle      = state_c == DONE     && (1);

//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
                cnt_byte <= cnt_byte+1;
        end
    end
    
    assign add_cnt_byte = ((state_c == WAIT_RD || state_c == WAIT_WR) && done);
    assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c == WAIT_WR) ? `WR_BYTE : `RD_BYTE) -1;

//输出

    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            TX(1'b0,4'd0,8'd0);
        end
        else if(state_c==WR_REQ)begin
            case(cnt_byte)
                0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,wr_addr[8],`WR_BIT});//发起始位、写控制字
                1           :TX(1'b1,`CMD_WRITE,wr_addr[7:0]);   //发 写地址
                `WR_BYTE-1  :TX(1'b1,{`CMD_WRITE | `CMD_STOP},wfifo_qout);  //最后一个字节时 发数据、停止位
                default     :TX(1'b1,`CMD_WRITE,wfifo_qout);    //中间发数据(如果有)
            endcase 
        end
        else if(state_c==RD_REQ)begin
            case(cnt_byte)
                0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`WR_BIT});//发起始位、写控制字
                1           :TX(1'b1,`CMD_WRITE,rd_addr[7:0]);   //发 读地址
                2           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`RD_BIT});//发起始位、读控制字
                `RD_BYTE-1  :TX(1'b1,{`CMD_READ | `CMD_STOP},0);  //最后一个字节时 读数据、发停止位
                default     :TX(1'b1,`CMD_READ,0);    //中间读数据(如果有)
            endcase 
        end
        else begin 
             TX(1'b0,tx_cmd,tx_data);
        end 
    end


//用task发送请求、命令、数据(地址+数据)
    task TX;   
        input                   req     ;
        input       [3:0]       command ;
        input       [7:0]       data    ;
        begin 
            tx_req  = req;
            tx_cmd  = command;
            tx_data = data;
        end 
    endtask 

//wr_addr   rd_addr
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            wr_addr <= 0;
        end
        else if(wait_wr2done)begin
            wr_addr <= wr_addr + `WR_BYTE-2;
        end
    end
    
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            rd_addr <= 0;
        end
        else if(wait_rd2done)begin
            rd_addr <= rd_addr + `RD_BYTE - 3;
        end
    end


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

//输出

    assign req     = tx_req ; 
    assign cmd     = tx_cmd ; 
    assign wr_data = tx_data; 

    assign dout = dout_r;
    assign dout_vld = dout_vld_r;
//fifo例化

    wrfifo	u_wrfifo (
    	.aclr   (~rst_n     ),
    	.clock  (clk        ),
    	.data   (din        ),
    	.rdreq  (wfifo_rd   ),
    	.wrreq  (wfifo_wr   ),
    	.empty  (wfifo_empty),
    	.full   (wfifo_full ),
    	.q      (wfifo_qout ),
    	.usedw  (wfifo_usedw)
    );

    assign wfifo_rd = state_c==WAIT_WR && done && cnt_byte > 1;//读使能(脉冲信号)---IIC接口传输完控制字节,字地址,cnt_byte==2时读出数据
    assign wfifo_wr = ~wfifo_full & din_vld;//写使能(脉冲信号)---fifo非满且uart_rx模块数据有效

    rdfifo	u_rdfifo (
	    .aclr   (~rst_n     ),
	    .clock  (clk        ),
	    .data   (rd_data    ),
	    .rdreq  (rfifo_rd   ),
	    .wrreq  (rfifo_wr   ),
	    .empty  (rfifo_empty),
	    .full   (rfifo_full ),
	    .q      (rfifo_qout ),
	    .usedw  (rfifo_usedw)
	);

    assign rfifo_wr = ~rfifo_full && state_c == WAIT_RD && done && cnt_byte > 2;
    assign rfifo_rd = ~rfifo_empty && ready;




endmodule

6.按键消抖

key_debounce.v

module key_debounce (   
    input  wire     clk,     //系统时钟 50MHz
    input  wire     rst_n,   //复位信号
    input  wire     key,     //按键输入信号
    output reg      key_done //消抖之后的按键信号
);

reg                 key_r0;  //同步信号(滤波作用,滤除小于一个周期的抖动)
reg                 key_r1;  //打拍
reg                 flag;    //标志位
wire                nedge;   //下降沿检测(检测到下降沿代表开始抖动)

//计时器定义
reg [19:0]          cnt;
wire                add_cnt;  //计时器开启
wire                end_cnt;  //计时记满

parameter           MAX_CNT=20'd1_000_000;  //20ms延时

//同步
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_r0<=1'b1;
    end
    else
        key_r0<=key;
end

//打拍
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_r1<=1'b1;    
    end
    else
        key_r1<=key_r0;
end

assign nedge = ~key_r0 & key_r1;  //检测到下降沿拉高

//标志位
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        flag<=1'b0; 
    end
    else if (nedge) begin
        flag<=1'b1; 
    end
    else if (end_cnt) begin
        flag<=1'b0;
    end
end

//延时模块
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt<=20'b0;
    end
    else if (add_cnt) begin
        if (end_cnt) begin
            cnt<=20'b0;
        end
        else
            cnt<=cnt+1;
    end
end

assign add_cnt=flag;                    //计时器开启
assign end_cnt=add_cnt&&cnt==MAX_CNT-1; //计时器关闭

//key_done输出
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_done<=1'b0; 
    end
    else if (end_cnt) begin            //延时满20ms采样
        key_done<=~key_r0;
    end
    else
        key_done<=1'b0;
end

endmodule 

7.参数

//i2c时钟参数
`define  SCL_PERIOD  250    //200khz
`define  SCL_HALF    125
`define  LOW_HLAF    65 
`define  HIGH_HALF   190

//i2c命令参数
`define CMD_START   4'b0001
`define CMD_WRITE   4'b0010
`define CMD_READ    4'b0100
`define CMD_STOP    4'b1000

//定义eeprom读写模式
`define BYTE_WRITE      //字节写
//`define PAGE_WRITE      //页写

`ifdef  BYTE_WRITE
    `define WR_BYTE 3
`elsif  PAGE_WRITE
    `define WR_BYTE 18
`endif 

`define RANDOM_READ     //随机地址读    每次写16字节
//`define SEQU_READ       //顺序地址读    每次读16字节

`ifdef  RANDOM_READ
    `define RD_BYTE 4
`elsif  SEQU_READ
    `define RD_BYTE 19
`endif 


//I2C外设地址参数定义
`define     I2C_ADR 6'b1010_00  //6'b1010_00xy x:Block地址 y:读写控制位 WR_BIT/RD_BIT
`define     WR_BIT  1'b0    //bit0
`define     RD_BIT  1'b1    //bit0



`define STOP_BIT  1'b1
`define START_BIT 1'b0

六丶源码

链接:https://pan.baidu.com/s/12Ey9eZr1B6eKwrhubFFRDg?pwd=0pvo
提取码:0pvo

下载解压后进入doc目录可以查看芯片手册以及项目框图
在这里插入图片描述

  • 15
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA(Field-Programmable Gate Array)是一种可编程的逻辑器件,可以用来实现各种数字电路。I2C(Inter-Integrated Circuit)是一种串行通信协议,常用于连接集成电路芯片之间的通信。 在FPGA中实现I2C的单次读写操作,你需要完成以下几个步骤: 1. 配置I2C控制器:首先,你需要在FPGA中实现一个I2C控制器,该控制器负责管理I2C总线的时序和通信协议。你可以使用硬件描述语言(如VHDL或Verilog)来编写控制器的代码,定义I2C总线的时钟频率、地址、数据等。 2. 发送起始条件:在进行I2C通信时,首先需要发送起始条件。起始条件是一个高电平到低电平的跳变,表示通信的开始。你可以通过在FPGA中控制I2C总线的时钟和数据线来实现起始条件的发送。 3. 发送设备地址和读/写位:接下来,你需要发送要访问的设备地址和读/写位。设备地址是目标设备在I2C总线上的唯一标识符,读/写位用于指示是读取数据还是写入数据。 4. 读写数据:根据你的需求,你可以选择进行读取操作或写入操作。对于读取操作,你需要等待目标设备发送数据,并通过I2C总线接收数据。对于写入操作,你需要将要写入的数据发送到目标设备。 5. 发送停止条件:完成数据的读写后,你需要发送停止条件。停止条件是一个低电平到高电平的跳变,表示通信的结束。通过控制I2C总线的时钟和数据线,你可以实现停止条件的发送。 需要注意的是,具体实现方法可能会因硬件平台和使用的开发工具而有所不同。你可以参考FPGA厂商提供的资料和示例代码来帮助你完成这些步骤。另外,还可以使用一些开源的IP核(如OpenCores提供的I2C IP核)来简化开发过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值