【FPGA】FPGA基于i2c的eeprom读写

一、i2c协议

I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
I2C是一个能够支持多个设备的总线,包含一条双向串行数据线SDA,一条串行时钟线SCL。
在这里插入图片描述

二、看i2c–eeprom手册找关键

1.设别型号选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ONIcX1t7-1644636161350)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220117211209367.png)]

这里我是用的C4的板子,24LC04B,时钟最高是400kHZ

2.描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JhxhnjaY-1644636161352)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220117205934633.png)]

EEPROM的存储的大小为2个block,一个block是256*8bit的存储大小

3.总线时序图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVIITcGL-1644636161352)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220117214117925.png)]

4.总线开始和停止

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kz8HnP2U-1644636161352)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220117214234625.png)]

红色为开始标志

绿色为结束标志

5.数据在总线上传输

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oqQu8b8-1644636161353)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220117215013538.png)]

6.设备地址(控制命令)

在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEzS8vrL-1644636161353)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220117215434569.png)]

设备地址也称为控制命令,是一个字节

操作Control CodeBlock SelectR/W
1010( 对24XX04, 1010是读写操作)XX(对24XX04,这两位don’t care)1
1010( 对24XX04, 1010是读写操作)XX(对24XX04,这两位don’t care)0

7.写操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYKCNbKQ-1644636161353)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220118093253930.png)]

  • 单字节写
  1. 单字节写先开始位,
  2. 然后写控制字节,从机接收到发应答信号,
  3. 然后写数据地址,从机接收到发应答信号,如果数据地址是2位的话,就继续写数据地址,从机接收到发应答信号,
  4. 然后写数据,从机接收到发应答信号,
  5. 最后是结束位。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXYSGXhd-1644636161353)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220118094127139.png)]

  • 页写
  1. 页节写先开始位,
  2. 然后写控制字节,从机接收到应答信号,
  3. 然后写数据地址,从机接收到应答信号,如果数据地址是2位的话,就继续写数据地址,从机接收到应答信号,
  4. 然后写数据,从机接收到应答信号,
  5. 然后继续写数据,直到写完全部的数据,
  6. 最后是结束位。

8.读操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUzTpOFs-1644636161354)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220118095216519.png)]

  • 当前地址读

当前一次读操作或者写操作完毕后,24XX04内部有一个地址计数器,会自增一,所以当前地址读是对下一个地址去读。

  1. 当前地址读先开始位,
  2. 然后写控制字节,从机接收到应答信号,然后读数据,无应答信号,
  3. 最后结束位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mGYkiBvq-1644636161354)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220118095232335.png)]

  • 随机读
  1. 随机读先开始位,
  2. 然后写控制字节,从机接收到应答信号,
  3. 然后dummuy write 虚写,写数据地址,从机接收到应答信号,
  4. 然后开始位,
  5. 然后读控制字节,从机接收到应答信号
  6. 然后读数据
  7. 最后结束位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DrFrovvV-1644636161354)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220118095247274.png)]

  • 顺序读
  1. 顺序读就是随机读的加强版,读很多的数据

三、状态机设计

1.i2c协议接口的状态图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0pKBBKl-1644636161355)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220121141711475.png)]

2.eeprom读写的状态图

在这里插入图片描述


注意事项

eeprom是高位先发,

四、代码部分

1.i2c_interface.v

// i2c接口模块
module i2c_interface(
    input           clk,
    input           rst_n,
    // 接口与eeprom 单总线sda
    output          scl,
    input           sda_in,
    output          sda_out,
    output          sda_out_en,
    // 接口与主机
    input           req,
    input   [3:0]   cmd,
    input   [7:0]   din,
    output          slave_ack,
    output  [7:0]   dout,
    output          done
);

// i2c兼容100kHZ-400kHZ,我们这里使用200kHZ
// 我们先拉低sclk,再拉高sclk
parameter   SCL_TIME       = 250,// 200kHZ 的一个周期
            SCL_HALF_TIME  = 125,// 200kHZ 的半个周期
            SCL_LOW_MID    = 65,// 
            SCL_HIGH_MID   = 185;

// 开始读写结束命令
localparam  START_CMD = 4'b0001,
            WRITE_CMD = 4'b0010,
            READ_CMD  = 4'b0100,
            STOP_CMD  = 4'b1000;

localparam  IDLE   = 7'b000_0001,// 默认状态
            START  = 7'b000_0010,// 开始位
            WRITE  = 7'b000_0100,// 写状态
            READ   = 7'b000_1000,// 读状态
            SEDACK = 7'b001_0000,// 发送应答信号状态
            RECACK = 7'b010_0000,// 接收应答信号状态
            STOP   = 7'b100_0000;// 停止位

// 状态机
reg     [6:0]       state_c;
reg     [6:0]       state_n;

// 状态转移条件
wire                idle2start  ;
wire                idle2write  ;
wire                idle2read   ;
wire                start2write ;
wire                start2read  ;
wire                write2recack;
wire                read2sedack ;
wire                sedack2stop ;
wire                sedack2idle ;
wire                recack2stop ;
wire                recack2idle ;
wire                stop2idle   ;

// 串行时钟计数器 何时拉高何时拉低
reg     [8:0]       cnt_scl;
wire                add_cnt_scl;
wire                end_cnt_scl;

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

// 寄存i2c接口与eeprom的数据
reg                 i2c_scl;
reg                 i2c_sda_out;
reg                 i2c_sda_out_en;
// 寄存接收从eeprom发来的数据
reg     [7:0]       rx_data;
// 寄存主机接收从机发来的应答信号
reg                 rx_ack;

// 状态机
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)begin
                        state_n = START;
                    end
                    else if(idle2write)begin
                        state_n = WRITE;
                    end
                    else if(idle2read)begin
                        state_n = READ;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        START      :begin 
                    if(start2write)begin
                        state_n = WRITE;
                    end
                    else if(start2read)begin
                        state_n = READ;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        WRITE      :begin 
                    if(write2recack)begin
                        state_n = RECACK;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        READ      :begin 
                    if(read2sedack)begin
                        state_n = SEDACK;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        SEDACK      :begin 
                    if(sedack2stop)begin
                        state_n = STOP;
                    end
                    else if(sedack2idle)begin
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        RECACK      :begin 
                    if(recack2stop)begin
                        state_n = STOP;
                    end
                    else if(recack2idle)begin
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        STOP      :begin 
                    if(stop2idle)begin
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        default: state_n = IDLE;
    endcase
end
    
assign idle2start   = state_c == IDLE   && (req && (cmd & START_CMD));// 有请求就开始
assign idle2write   = state_c == IDLE   && (req && (cmd & WRITE_CMD)); // 有请求并且继续写 
assign idle2read    = state_c == IDLE   && (req && (cmd & READ_CMD));// 有请求并且继续读
assign start2write  = state_c == START  && (end_cnt_bit && (cmd & WRITE_CMD));// 1bit开始位
assign start2read   = state_c == START  && (end_cnt_bit && (cmd & READ_CMD));// 1bit1开始位
assign write2recack = state_c == WRITE  && (end_cnt_bit);// 8bit数据写完
assign read2sedack  = state_c == READ   && (end_cnt_bit);// 8bit数据写完
assign sedack2stop  = state_c == SEDACK && (end_cnt_bit && (cmd & STOP_CMD));// 1bit发送应答信号
assign sedack2idle  = state_c == SEDACK && (end_cnt_bit && (cmd & STOP_CMD) == 0);// 没有停止命令 就继续
assign recack2stop  = state_c == RECACK && (end_cnt_bit && (cmd & STOP_CMD));// 1bit接收应答信号
assign recack2idle  = state_c == RECACK && (end_cnt_bit && (cmd & STOP_CMD) == 0);// 没有停止命令 就继续
assign stop2idle    = state_c == STOP   && (end_cnt_bit);// 1bit接收位

// 串行时钟计数器
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
   else  begin
       cnt_scl <= cnt_scl;
    end
end 

assign add_cnt_scl = (state_c != IDLE);
assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_TIME - 1;

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

assign add_cnt_bit = end_cnt_scl;
assign end_cnt_bit = add_cnt_bit && cnt_bit == (((state_c == WRITE) || (state_c == READ))?(8 - 1):(1 - 1));

// 串行时钟scl
// 利用计数器形成一个串行时钟
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        i2c_scl <= 1;
    end 
    else if(idle2start || idle2write || idle2read)begin 
        i2c_scl <= 1'b0; 
    end 
    else if(add_cnt_scl && (cnt_scl == SCL_HALF_TIME))begin 
        i2c_scl <= 1'b1; // 
    end 
    else if(end_cnt_scl)begin
        i2c_scl <= 1'b0;
    end
    else if(stop2idle)begin
        i2c_scl <= 1'b1;
    end
end

// sda数据总线
// sda_in
// 在串行总线为高的中间时刻数据稳定采集eeprom的数据 
// 串并转换
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rx_data <= 0;
    end 
    else if((state_c == READ) && add_cnt_scl && (cnt_scl == SCL_HIGH_MID))begin 
        rx_data[7-cnt_bit] <= sda_in;
    end 
end

// sda_in
// 主机采样从机的应答信号
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rx_ack <= 1'b1;
    end 
    else if((state_c == RECACK) && add_cnt_scl && (cnt_scl == SCL_HIGH_MID))begin 
        rx_ack <= sda_in;
    end 
end

// sda_out
// 在串行总线为低的中间时刻数据改变给eeprom发数据
// 串并转换
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        i2c_sda_out <= 0;
    end 
    else if(state_c == START)begin 
        if(add_cnt_scl && cnt_scl == SCL_LOW_MID)begin
            i2c_sda_out <= 1'b1; // 保证之前不是拉低的状态,先将它拉高,检测开始位
        end
        else if(add_cnt_scl && cnt_scl == SCL_HIGH_MID)begin
            i2c_sda_out <= 1'b0; // 检测开始位
        end
    end 
    else if(state_c == STOP)begin 
        if(add_cnt_scl && cnt_scl == SCL_LOW_MID)begin
            i2c_sda_out <= 1'b0; // 保证之前不是拉低的状态,先将它拉高,检测结束位
        end
        else if(add_cnt_scl && cnt_scl == SCL_HIGH_MID)begin
            i2c_sda_out <= 1'b1; // 检测结束位
        end
    end
    else if((state_c == WRITE) && add_cnt_scl && (cnt_scl == SCL_LOW_MID))begin
        i2c_sda_out <= din[7-cnt_bit]; // 串并转换数据
    end
    else if((state_c == SEDACK) && add_cnt_scl && (cnt_scl == SCL_LOW_MID))begin
        i2c_sda_out <= (cmd & STOP_CMD)?1'b1:1'b0;// 发送应答信号
    end
end

// sda_out_en
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        i2c_sda_out_en <= 0;
    end 
    else if(idle2start || idle2write || start2write || read2sedack || sedack2stop || recack2stop)begin 
        i2c_sda_out_en <= 1'b1;
    end 
    else if(idle2read || start2read || write2recack || sedack2idle || recack2idle || stop2idle)begin 
        i2c_sda_out_en <= 1'b0; 
    end 
end

assign scl = i2c_scl;
assign sda_out = i2c_sda_out;
assign sda_out_en = i2c_sda_out_en;
assign dout = rx_data;
// 主机接受从机的应答信号
assign slave_ack = rx_ack;
// 一个字节 8bit结束就done
assign done = sedack2idle || recack2idle || stop2idle;

endmodule

2.master_ctrl.v

module master_ctrl(
    input           clk,
    input           rst_n,
    // 按键
    input   [1:0]   key_out,
    // 接口与主机
    output          req,
    output  [3:0]   cmd,
    output  [7:0]   dout,
    input           slave_ack,
    input           done,
    input   [7:0]   rx_data,
    // 数码管
    output  [23:0]  seg_data,
    // 串口uart
    input   [7:0]   uart_rx_data,
    input           uart_rx_data_vld,
    output  [7:0]   uart_tx_data,
    output          uart_tx_data_vld,
    input           busy
);

parameter   DEVICE_ID = 7'b1010_000,// 设备地址 + block(dont care)
            WR_ID     = 1'b0,// 控制写
            RD_ID     = 1'b1;// 控制读

parameter   WR_LEN = 16+2,// 写的字节
            RD_LEN = 16+3;// 读的字节

// 开始读写结束命令
localparam  START_CMD = 4'b0001,
            WRITE_CMD = 4'b0010,
            READ_CMD  = 4'b0100,
            STOP_CMD  = 4'b1000;

localparam  IDLE   = 6'b000_001,
            WRREQ  = 6'b000_010,
            WAITWR = 6'b000_100,
            RDREQ  = 6'b001_000,
            WAITRD = 6'b010_000,
            DONE   = 6'b100_000; 

reg     [5:0]       state_c;
reg     [5:0]       state_n;

wire                idle2wrreq  ;
wire                idle2rdreq  ;
wire                wrreq2waitwr;    
wire                WAITWR2wrreq;
wire                waitwr2done ;
wire                rdreq2waitrd;
wire                waitrd2rdreq;
wire                waitrd2done ;

// 字节计数器 一个block最大256
reg     [7:0]       cnt_byte;
wire                add_cnt_byte;
wire                end_cnt_byte;

// 读写请求
reg                 wr_req;
reg                 rd_req;

// 寄存要输出的req cmd dout
reg                 tx_req;
reg     [3:0]       tx_cmd;
reg     [7:0]       tx_data;

// wrfifo参数
wire                wrfifo_rdreq;
wire                wrfifo_wrreq;
wire                wrfifo_empty;
wire                wrfifo_full ;
wire    [7:0]       wrfifo_qout ;
wire    [7:0]       wrfifo_usedw;              

// rdfifo参数
wire                rdfifo_rdreq;
wire                rdfifo_wrreq;
wire                rdfifo_empty;
wire                rdfifo_full ;
wire    [7:0]       rdfifo_qout ;
wire    [7:0]       rdfifo_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(idle2wrreq)begin
                        state_n = WRREQ;
                    end
                    else if(idle2rdreq)begin
                        state_n = RDREQ;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        WRREQ      :begin 
                    if(wrreq2waitwr)begin
                        state_n = WAITWR;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        WAITWR      :begin 
                    if(WAITWR2wrreq)begin
                        state_n = WRREQ;
                    end
                    else if(waitwr2done)begin
                        state_n = DONE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        RDREQ      :begin 
                    if(rdreq2waitrd)begin
                        state_n = WAITRD;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        WAITRD      :begin 
                    if(waitrd2rdreq)begin
                        state_n = RDREQ;
                    end
                    else if(waitrd2done)begin
                        state_n = DONE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        DONE      : begin
                    if(done2idle)begin
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                    end
        default: state_n = IDLE;
    endcase
end
    
assign idle2wrreq   = state_c == IDLE   && (wr_req);// 写请求
assign idle2rdreq   = state_c == IDLE   && (rd_req);// 读请求
assign wrreq2waitwr = state_c == WRREQ  && (1'b1);// 等一个周期
assign WAITWR2wrreq = state_c == WAITWR && (~slave_ack && done && ~end_cnt_byte);// 写完一个字节从机发送了应答信号并没有写完全部字节 
assign waitwr2done  = state_c == WAITWR && ((slave_ack || end_cnt_byte) && done);// 写完一个字节从机没有发送应答信号或者写完全部字节
assign rdreq2waitrd = state_c == RDREQ  && (1'b1);// 等一个周期
assign waitrd2rdreq = state_c == WAITRD && (~slave_ack && done && ~end_cnt_byte);// 写完一个字节从机发送了应答信号并没有写完全部字节 
assign waitrd2done  = state_c == WAITRD && ((slave_ack || end_cnt_byte) && done);// 写完一个字节从机没有发送应答信号或者写完全部字节
assign done2idle    = state_c == DONE   && (1'b1);

// 字节计数器
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
    else if((state_c == WAITWR) && (slave_ack == 0) && done)begin
        cnt_byte <= 0;
    end
    else  begin
       cnt_byte <= cnt_byte;
    end
end 

assign add_cnt_byte = ((state_c == WAITRD || state_c == WAITWR) && done);
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c == WAITWR)?(WR_LEN - 1):(RD_LEN - 1));

// rd_req和wr_req
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rd_req <= 0;
        wr_req <= 0;
    end 
    else if(key_out[0])begin 
        wr_req <= 1'b1;
    end 
    else if(key_out[1])begin 
        rd_req <= 1'b1;
    end 
    else begin
        rd_req <= 0;
        wr_req <= 0;
    end
end

// 不同byte发送不同的cmd din
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_req  <= 0;
        tx_cmd  <= 4'b0;
        tx_data <= 8'b0;
    end 
    else if(state_c == WRREQ)begin 
        case(cnt_byte)
        0   :   begin
                    tx_req  <= 1;
                    tx_cmd  <= {START_CMD | WRITE_CMD};
                    tx_data <= {DEVICE_ID,WR_ID};
                end
        1   :   begin
                    tx_req <= 1;
                    tx_cmd <= WRITE_CMD;
                    tx_data <= 8'b0001_0001;
                end
        WR_LEN-1:   begin
                    tx_req <= 1;
                    tx_cmd <= {STOP_CMD | WRITE_CMD};
                    tx_data <= wrfifo_qout;
                end
        
        default :begin
                    tx_req <= 1;
                    tx_cmd <= WRITE_CMD;
                    tx_data <= wrfifo_qout;
                end
        endcase
    end 
    else if(state_c == RDREQ)begin 
        case(cnt_byte)
        0   :   begin
                    tx_req  <= 1;
                    tx_cmd  <= {START_CMD | WRITE_CMD};
                    tx_data <= {DEVICE_ID,WR_ID};
                end
        1   :   begin
                    tx_req <= 1;
                    tx_cmd <= WRITE_CMD;
                    tx_data <= 8'b0001_0001;
                end
        2   :   begin
                    tx_req  <= 1;
                    tx_cmd  <= {START_CMD | WRITE_CMD};
                    tx_data <= {DEVICE_ID,RD_ID};
                end
        RD_LEN-1   :   begin
                    tx_req <= 1;
                    tx_cmd <= {STOP_CMD | READ_CMD};
                    tx_data <= 0;
                end
        default :begin
                    tx_req <= 1;
                    tx_cmd <= READ_CMD;
                    tx_data <= 0;
                end
        endcase
    end 
    else begin
        tx_req  <= 0;
        tx_cmd  <= tx_cmd;
        tx_data <= tx_data;
    end
end

// 例化写fifo
wrfifo	wrfifo_inst (
	.clock  ( clk      ),
	.data   ( uart_rx_data ),
	.rdreq  ( wrfifo_rdreq ),
	.wrreq  ( wrfifo_wrreq ),
	.empty  ( wrfifo_empty ),
	.full   ( wrfifo_full ),
	.q      ( wrfifo_qout ),
	.usedw  ( wrfifo_usedw )
	);

assign wrfifo_wrreq = ~wrfifo_full && uart_rx_data_vld;
assign wrfifo_rdreq = ~wrfifo_empty && state_c == WAITWR && done && cnt_byte > 2;

// 例化读fifo
rdfifo	rdfifo_inst (
	.clock  ( clk ),
	.data   ( rx_data ),
	.rdreq  ( rdfifo_rdreq ),
	.wrreq  ( rdfifo_wrreq ),
	.empty  ( rdfifo_empty ),
	.full   ( rdfifo_full ),
	.q      ( rdfifo_qout ),
	.usedw  ( rdfifo_usedw )
	);

assign rdfifo_wrreq = ~rdfifo_full && state_c == WAITRD && done && cnt_byte > 3;
assign rdfifo_rdreq = ~rdfifo_empty && ~busy; 

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

assign uart_tx_data = rdfifo_qout;
assign uart_tx_data_vld = rdfifo_rdreq;

// 数码管
assign seg_data = {16'h0000,rx_data};

endmodule

3.top.v

module top(
    input           clk,
    input           rst_n,
    input   [1:0]   key_in,
    output          scl,
    inout           sda,
    output  [7:0]   seg_dig,
    output  [5:0]   seg_sel,
    input           uart_rx,
    output          uart_tx
);


wire    [1:0]       key_out;
wire                req;
wire    [3:0]       cmd;
wire    [7:0]       tx_data;
wire    [7:0]       rx_data;
wire                done;
wire    [23:0]      seg_data;
wire                slave_ack;
wire    [7:0]       uart_rx_data;    
wire                uart_rx_data_vld;
wire    [7:0]       uart_tx_data;    
wire                uart_tx_data_vld;
wire                busy;                          
wire                sda_in ;       
wire                sda_out;
wire                sda_out_en;


assign sda_in = sda;
assign sda = sda_out_en?sda_out:1'bz;


key_filter u_key_filter(
    /* input                    */.clk      (clk    ),
    /* input                    */.rst_n    (rst_n  ),
    /* input         [2-1:0]    */.key_in   (key_in ),
    /* output  reg   [2-1:0]    */.key_out  (key_out)
);

// 串口接收模块
uart_rx u_uart_rx(
    /* input            */.clk      (clk      ),
    /* input            */.rst_n    (rst_n    ),
    /* input            */.baud_sel (0 ),// 波特率的选择
    /* input            */.din      (uart_rx      ),// 串口接收模块接收到主机来的1bit的数据
    /* output  [7:0]    */.dout     (uart_rx_data     ),// 串口接收模块串并转换的数据发送
    /* output           */.dout_vld (uart_rx_data_vld )
);

uart_tx u_uart_tx(
    /* input            */.clk      (clk     ),
    /* input            */.rst_n    (rst_n   ),
    /* input            */.baud_sel (0),// 波特率的选择
    /* input   [7:0]    */.din      (uart_tx_data     ),// 串并转换的数据
    /* input            */.din_vld  (uart_tx_data_vld ),// 串并转换的数据有效
    /* output           */.dout     (uart_tx ),// 发送模块发送的1bit数据
    /* output           */.busy     (busy    ) // 发送模块忙标志
);

// 数码管驱动
seg_driver u_seg_driver(
    /* input                        */.clk      (clk    ),
    /* input                        */.rst_n    (rst_n  ),
    /* input           [23:0]       */.data     (seg_data   ),
    /* output   reg    [7:0]        */.seg_dig  (seg_dig),
    /* output   reg    [5:0]        */.seg_sel  (seg_sel)
);

// 控制模块
master_ctrl u_master_ctrl(
    /* input            */.clk      (clk      ),
    /* input            */.rst_n    (rst_n    ),
    /* input   [1:0]    */.key_out  (key_out  ),
    /* output           */.req      (req      ),
    /* output  [3:0]    */.cmd      (cmd      ),
    /* output  [7:0]    */.dout     (tx_data  ),
    /* input            */.slave_ack(slave_ack),
    /* input            */.done     (done     ),
    /* input   [7:0]    */.rx_data  (rx_data  ), 
    /* output  [23:0]   */.seg_data (seg_data),
    /* input   [7:0]    */.uart_rx_data     (uart_rx_data    ),
    /* input            */.uart_rx_data_vld (uart_rx_data_vld),
    /* output  [7:0]    */.uart_tx_data     (uart_tx_data    ),
    /* output           */.uart_tx_data_vld (uart_tx_data_vld),
    /* input            */.busy             (busy            )         
);

// i2c接口模块
i2c_interface u_i2c_interface(
    /* input            */.clk          (clk       ),
    /* input            */.rst_n        (rst_n     ),
    /* output           */.scl          (scl       ),
    /* input            */.sda_in       (sda_in    ),
    /* output           */.sda_out      (sda_out   ),
    /* output           */.sda_out_en   (sda_out_en),
    /* input            */.req          (req       ),
    /* input   [3:0]    */.cmd          (cmd       ),
    /* input   [7:0]    */.din          (tx_data   ),
    /* output           */.slave_ack    (slave_ack ),
    /* output  [7:0]    */.dout         (rx_data   ),
    /* output           */.done         (done      )
);
endmodule

4.其他模块

串口发送模块
串口接收模块
数码管驱动模块
按键消抖模块

五、仿真验证

只是看看i2c接口的状态有无错误

`timescale 1 ns/1 ns
module i2c_interface_tb();

//时钟复位输入
    reg             clk     ;
    reg             rst_n   ;

//激励输入
    reg             req     ;
    reg     [3:0]   cmd     ;
    reg     [7:0]   din     ;

    //输出
    wire    [7:0]   dout    ;
    wire            done    ;
    wire            scl     ;
    reg             sda_in   ;
    wire            sda_out   ;
    wire            sda_out_en  ;


    //时钟周期定义
    parameter CYCLE    = 20;

    // 参数定义
    parameter   WR_ID = 8'b1010_1110,
                RD_ID = 8'b1010_1111,
                START_CMD = 4'b0001,
                WRITE_CMD = 4'b0010,
                READ_CMD  = 4'b0100,
                STOP_CMD  = 4'b1000;

    //复位时间定义
    parameter RST_TIME = 3 ;

    //模块例化
i2c_interface u_i2c_interface(
    /*input               */.clk         (clk       ),
    /*input               */.rst_n       (rst_n     ),

    /*input               */.req         (req       ),
    /*input       [3:0]   */.cmd         (cmd       ),
    /*input       [7:0]   */.din         (din       ),
    /*output      [7:0]   */.dout        (dout      ),
    /*output              */.done        (done      ),
    /*output              */.scl         (scl       ),
    /*input               */.sda_in      (sda_in    ),
    /*output              */.sda_out     (sda_out   ),
    /*output              */.sda_out_en  (sda_out_en)   
    );

    task traffic_gen;   
        input       [7:0]       data    ;
        input       [3:0]       command ;
        begin 
            #2;
            req = 1'b1;
            din = data;
            cmd = command;
            #(CYCLE*1);
            // req = 1'b0;
            @(negedge done);
            #(CYCLE*1);
        end 
    endtask 

        //产生时钟
        initial begin
            clk = 1;
            forever
            #(CYCLE/2)
            clk=~clk;
        end

        //产生复位
        initial begin
        rst_n = 0;
        #(CYCLE*RST_TIME);
        rst_n = 1;
        end

    //激励
    initial begin
        #1;
        req = 0 ;
        cmd = 0 ;
        din = 0 ;
        #(10*CYCLE);
        //字节写
        traffic_gen(WR_ID,{START_CMD | WRITE_CMD});//发起始位 + 写控制字
        traffic_gen(8'ha1,WRITE_CMD);                  //写字地址
        traffic_gen(8'hb2,{WRITE_CMD | STOP_CMD});     //发数据 + 停止位
        #(50*CYCLE);

        //随机地址读
        traffic_gen(WR_ID,{START_CMD | WRITE_CMD});//发起始位 + 写控制字
        traffic_gen(8'ha1,WRITE_CMD);                          //写字地址
        traffic_gen(RD_ID,{START_CMD | WRITE_CMD});//发起始位 + 发读控制字
        traffic_gen(8'h00,{READ_CMD | STOP_CMD});             //读数据 + 发停止位

        #(500*CYCLE);
        $stop;

    end



endmodule

                                                

在这里插入图片描述

六、上板验证

单字节读写和页写和随机读都没有问题
在这里插入图片描述

七、总结

这个i2c模块也花了我很多的时间去调试,一定要好好看看手册,是高字节MSB还是低字节LSB

  • 9
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
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核)来简化开发过程。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值