基于FPGA的I2C通信(三)终

目录

四、EEPROM读写系统设计

1. 整体系统概述

2. fifo_ctrl(写数据fifo控制模块)

3. I2C_wr_ctrl(I2C写控制模块)

4. I2C_rd_ctrl(I2C读控制模块)

5. fifo_ctrl2(读数据fifo控制模块)

6. I2C_eeprom_top 顶层模块

五、仿真调试及板级验证

1. 仿真调试

2. 板级验证


  本专题EEPROM读写系统(在下一篇博客讲解,包含本篇内容)整体功能实现的工程下载链接如下:
 https://download.csdn.net/download/qq_33231534/12503289


四、EEPROM读写系统设计

1. 整体系统概述

图:EEPROM读写系统整体框图
图:EEPROM读写系统RTL视图

整个系统的要求为:首先通过串口助手发送四个字节数据,将四个字节数据存储在fifo中,然后将fifo中的数据读出存储到EEPROM中,EEPROM读写均采用连续读写4个字节数据,当写完数据后,再从EEPROM中将刚刚写入的4字节数据依次读出来存储到fifo中,再从fifo中读出数据发送到出口,在串口助手上显示出来。

其中I2C_ctrl模块在上一篇博客中已经讲解了,uart_rx 和 uart_tx 在串口篇和数据采集系统篇里边也讲的很详细,是通用模块,sync_fifo1 和 sync_fifo2 是同步FIFO模块,可以通过使用IP核,或者自己编写代码,同步fifo模块也是通用模块,在数据采集系统里边有介绍和代码,后期会专门写一下FIFO的博客,这里也不赘述。

fifo_ctrl(写数据fifo控制模块):串口发送的数据由sync_fifo1来保存,而fifo_ctrl模块就是控制串口接收模块和fifo模块的桥梁,保证串口接受的数据能保存在fifo中。

I2C_wr_ctrl(I2C写控制模块):从FIFO中逐个数据取出写入EEPROM。

I2C_rd_ctrl(I2C读控制模块):从EEPROM逐个读出数据存入FIFO中。

fifo_ctrl2(读数据fifo控制模块):将fifo中的数据逐个读出发送到串口发送模块。

2. fifo_ctrl(写数据fifo控制模块)

表:fifo_ctrl 模块信号说明
信号名称I/O位数功能描述
clkI1系统时钟50MHz
rst_nI1系统复位
data_byteI8接收到串口发的数据
rx_doneI1串口接收数据完成标志
wrreqO1fifo写请求
data_to_fifoO8输出保存到fifo的数据

代码如下:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           fifo_ctrl.v
//Last modified Date:  2020/6.6
//Last Version:        
//Descriptions:        串口发送的数据由sync_fifo1来保存,而fifo_ctrl模块就
//                     是控制串口接收模块和fifo模块的桥梁,保证串口接受的数据能保存在fifo中。
//-------------------------------------------------------------------
module fifo_ctrl(
    input                   clk         ,//系统时钟50MHz
    input                   rst_n       ,//系统复位
    input   [ 7: 0]         data_byte   ,//接收到串口发的数据
    input                   rx_done     ,//串口接收数据完成标志
    output  reg             wrreq       ,//fifo写请求
    output  reg [ 7: 0]     data_to_fifo //输出保存到fifo的数据
);

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wrreq <= 0;
    end
    else if(rx_done) begin
        wrreq <= 1;
    end
    else begin
        wrreq <= 0;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_to_fifo <= 0;
    end
    else if(rx_done) begin
        data_to_fifo <= data_byte;
    end
    else begin
        data_to_fifo <= data_to_fifo;
    end
end

endmodule

3. I2C_wr_ctrl(I2C写控制模块)

表:I2C_wr_ctrl模块信号说明
信号名称I/O位数功能描述
clkI1系统时钟50MHz
rst_nI1系统复位
usedwI3fifo中数据个数
qI8fifo读出的数据
wr_data_vaildI1I2C_ctrl模块中写数据有效信号
rdreqO1fifo读请求
wr_enO1I2C_ctrl模块写使能
wr_dataO8写入eeprom的数据

代码如下:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           I2C_wr_ctrl
//Last modified Date:  2020/6/6
//Last Version:        
//Descriptions:        从FIFO中逐个数据取出写入EEPROM
//-------------------------------------------------------------------
module I2C_wr_ctrl(
    input                   clk             ,//系统时钟50MHz
    input                   rst_n           ,//系统复位
    input   [2:0]           usedw           ,//fifo中数据个数
    input   [7:0]           q               ,//fifo读出的数据
    input                   wr_data_vaild   ,//I2C_ctrl模块中写数据有效信号

    output  reg             rdreq           ,//fifo读请求
    output  reg             wr_en           ,//I2C_ctrl模块写使能
    output  reg [7:0]       wr_data          //写入eeprom的数据
);

reg                    rdreq_r;

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rdreq_r <= 0;
    end
    else begin
        rdreq_r <= rdreq;
    end
end
always  @(*)begin
    if(usedw>=4 || wr_data_vaild) begin
        rdreq = 1;
    end
    else begin
        rdreq = 0;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_en <= 0;
    end
    else if(usedw>=4) begin
        wr_en <= 1;
    end
    else begin
        wr_en <= 0;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_data <= 0;
    end
    else if(rdreq_r) begin
        wr_data <= q;
    end
    else begin
        wr_data <= wr_data;
    end
end

endmodule

4. I2C_rd_ctrl(I2C读控制模块)

表:I2C_rd_ctrl模块信号说明
信号名称I/O位数功能描述
clkI1系统时钟50MHz
rst_nI1系统复位
doneI1I2C_ctrl模块读写数据结束标志
rd_dataI8从EEPROM读出的数据
rd_data_vaildI1从EEPROM读出数据有效位
wrreqO1fifo写请求
I2C_rd_dataO8要写入fifo的数据
rd_enO1I2C_ctrl模块读使能 

代码如下:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           I2C_rd_ctrl
//Last modified Date:  2020/6/6
//Last Version:        
//Descriptions:        从EEPROM逐个读出数据存入FIFO中
//-------------------------------------------------------------------
module I2C_rd_ctrl(
    input                   clk             ,//系统时钟50MHz
    input                   rst_n           ,//系统复位
    input                   done            ,//I2C_ctrl模块读写数据结束标志
    input   [7:0]           rd_data         ,//从EEPROM读出的数据
    input                   rd_data_vaild   ,//从EEPROM读出数据有效位

    output  reg             wrreq           ,//fifo写请求
    output  reg [7:0]       I2C_rd_data     ,//要写入fifo的数据
    output  reg             rd_en            //I2C_ctrl模块读使能   
);
reg                    flag_rd;

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        flag_rd <= 0;
    end
    else if(done)begin
        flag_rd <= ~flag_rd;
    end
	 else begin
		  flag_rd <= flag_rd;
	 end
	 
end    


always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_en <= 0;
    end
    else if(done && flag_rd==0) begin
        rd_en <= 1;
    end
    else begin
        rd_en <= 0;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        I2C_rd_data <= 0;
    end
    else if(rd_data_vaild) begin
        I2C_rd_data <= rd_data;
    end
    else begin
        I2C_rd_data <= I2C_rd_data;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wrreq <= 0;
    end
    else if(rd_data_vaild) begin
        wrreq <= 1;
    end
    else begin
        wrreq <= 0;
    end
end

endmodule

5. fifo_ctrl2(读数据fifo控制模块)

信号名称I/O位数功能描述
clkI1系统时钟50MHz
rst_nI1系统复位
usedwI3fifo中数据个数
qI8fifo中读出的数据
tx_doneI1串口发送一次数据完成标志
rdreqO1fifo读请求
send_enO1串口发送使能信号
data_sendO8串口发送的数据

代码如下:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           fifo_ctrl2.v
//Last modified Date:  2020/6/6
//Last Version:        
//Descriptions:        将fifo中的数据逐个读出发送到串口发送模块
//-------------------------------------------------------------------
module fifo_ctrl2(
    input                   clk         ,//系统时钟50MHz
    input                   rst_n       ,//系统复位
    input   [2:0]           usedw       ,//fifo中数据个数
    input   [7:0]           q           ,//fifo中读出的数据
    input                   tx_done     ,//串口发送一次数据完成标志

    output  reg             rdreq       ,//fifo读请求
    output  reg             send_en     ,//串口发送使能信号
    output  reg [7:0]       data_send    //串口发送的数据
);

reg                    rdreq_r;

always  @(*)begin
    if(rst_n==1'b0)begin
        rdreq = 0;
    end
    else if(usedw>=4 || usedw>0&&tx_done) begin
        rdreq = 1;
    end
    else begin
        rdreq = 0;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rdreq_r <= 0;
    end
    else begin
        rdreq_r <= rdreq;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data_send <= 0;
    end
    else if(rdreq_r) begin
        data_send <= q;
    end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        send_en <= 0;
    end
    else if(rdreq_r) begin
        send_en <= 1;
    end
    else begin
        send_en <= 0;
    end
end

endmodule

6. I2C_eeprom_top 顶层模块

将以上各个模块例化,信号连接,代码如下:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           I2C_eeprom_top.v
//Last modified Date:  2020/6/6
//Last Version:        
//Descriptions:        EEPROM读写系统顶层模块
//-------------------------------------------------------------------

module I2C_eeprom_top(
    input                   clk         ,
    input                   rst_n       ,
    input                   rs232_rx    ,

    output                  rs232_tx    ,
    output                  scl         ,
    inout                   sda             
);

wire  [  7: 0]         data_byte        ;
wire                   rx_done          ;
wire                   wrreq1           ;
wire                   rdreq1           ;
wire  [  7: 0]         data_to_fifo     ;
wire  [  2: 0]         usedw1           ;
wire  [  7: 0]         data_to_eeprom   ;
wire                   wr_en            ;
wire                   wr_data_vaild    ;
wire  [  7: 0]         wr_data          ;

wire                   done             ;
wire  [  7: 0]         rd_data          ;
wire                   rd_data_vaild    ;
wire                   wrreq2           ;
wire  [  7: 0]         I2C_rd_data      ;
wire                   rd_en            ;
wire  [  2: 0]         usedw2           ;
wire  [  7: 0]         data_to_uart     ;
wire                   rdreq2           ;
wire                   tx_done          ;
wire                   send_en          ;
wire  [  7: 0]         data_send        ;


UART_Byte_Rx u_UART_Byte_Rx(
    .clk       (clk) ,//系统时钟50MHz
    .rst_n     (rst_n) ,//系统复位
    .rs232_rx  (rs232_rx) ,//串口串行数据发送数据口
    .baud_set  (3'd1) ,//波特率选择信号

    .data_byte (data_byte) ,//并行数据输出
    .rx_done   (rx_done)  //接收1字节数据完成标志,rx_done可以作为输出有效信号使用
);

fifo_ctrl u_fifo_ctrl(
    .clk           (clk),
    .rst_n         (rst_n),
    .data_byte     (data_byte),
    .rx_done       (rx_done),
    .wrreq         (wrreq1),
    .data_to_fifo  (data_to_fifo)  
);

sync_fifo
#(.WIDTH         (8),          //缓存的数据宽度
  .DEPTH         (5),         //缓存的数据深度
  .MAX_DEPTH_BIT (3))           //可设置的最大深度位数7,即最大深度为2^7-1
u_sync_fifo1(
    .clk       (clk), //系统时钟50MHz
    .rst_n     (rst_n), //系统复位
    .wrreq     (wrreq1), //写使能
    .data      (data_to_fifo), //写数据
    .rdreq     (rdreq1), //读使能

    .q         (data_to_eeprom), //读数据
    .empty     (), //空信号
    .full      (), //满信号
    .half      (), //半满信号
    .usedw     (usedw1)  //fifo中剩余数据个数    
);

I2C_wr_ctrl u_I2C_wr_ctrl(
    .clk           (clk) ,
    .rst_n         (rst_n) ,
    .usedw         (usedw1) ,
    .q             (data_to_eeprom) ,
    .wr_data_vaild (wr_data_vaild) ,

    .rdreq         (rdreq1) ,
    .wr_en         (wr_en) ,
    .wr_data       (wr_data)          
);

I2C_ctrl u_I2C_ctrl(
    .clk           (clk) ,
    .rst_n         (rst_n) ,
    .rd_data_num   (6'd4) ,//最大32
    .wr_data_num   (6'd4) ,//最大32
    .word_addr     (16'h05) ,
    .device_addr   (3'b001) ,
    .wr_en         (wr_en) ,
    .wr_data       (wr_data) ,
    .rd_en         (rd_en) ,

    .wr_data_vaild (wr_data_vaild) ,
    .rd_data       (rd_data) ,
    .rd_data_vaild (rd_data_vaild) ,
    .done          (done) ,
    .scl           (scl) ,

    .sda           (sda)   
);

I2C_rd_ctrl u_I2C_rd_ctrl(
    .clk           (clk) ,
    .rst_n         (rst_n) ,
    .done          (done) ,
    .rd_data       (rd_data) ,
    .rd_data_vaild (rd_data_vaild) ,

    .wrreq         (wrreq2) ,
    .I2C_rd_data   (I2C_rd_data) , 
    .rd_en         (rd_en)     
);

sync_fifo
#(.WIDTH         (8),          //缓存的数据宽度
  .DEPTH         (5),         //缓存的数据深度
  .MAX_DEPTH_BIT (3))           //可设置的最大深度位数7,即最大深度为2^7-1
u_sync_fifo2(
    .clk       (clk), //系统时钟50MHz
    .rst_n     (rst_n), //系统复位
    .wrreq     (wrreq2), //写使能
    .data      (I2C_rd_data), //写数据
    .rdreq     (rdreq2), //读使能

    .q         (data_to_uart), //读数据
    .empty     (), //空信号
    .full      (), //满信号
    .half      (), //半满信号
    .usedw     (usedw2)  //fifo中剩余数据个数    
);

fifo_ctrl2 u_fifo_ctrl2(
    .clk       (clk) ,
    .rst_n     (rst_n) ,
    .usedw     (usedw2) ,
    .q         (data_to_uart) ,
    .tx_done   (tx_done) ,

    .rdreq     (rdreq2) ,
    .send_en   (send_en) ,
    .data_send (data_send)     
);

Uart_Byte_Tx u_Uart_Byte_Tx(
    .clk        (clk), //系统时钟
    .rst_n      (rst_n), //系统复位
    .send_en    (send_en), //发送使能
    .data_byte  (data_send), //发送的数据
    .baud_set   (3'd1), //波特率设置
    .rs232_tx   (rs232_tx), //FPGA将数据转换成串行数据发出
    .tx_done    (tx_done), //发送数据完毕标志
    .uart_state ()  //串口发送状态,1为忙,0为空闲
);

endmodule

五、仿真调试及板级验证

1. 仿真调试

测试时从串口发送4个字节数据,8'b0000_1010 、8'b1000_0101 、8'b0000_1010 、8'b1000_0101 ,然后再重复一次。

其测试代码如下:

`timescale 1 ns/ 1 ns
module I2C_eeprom_top_tb();
// constants                                           
// test vector input registers
reg clk;
reg rs232_rx;
reg rst_n;
//reg treg_sda;
// wires                                               
wire rs232_tx;
wire scl;
wire sda;

parameter clk_period = 20;

// assign statements (if any)                          
//assign sda = treg_sda;
I2C_eeprom_top i1 (
// port map - connection between master ports and signals/registers   
	.clk(clk),
	.rs232_rx(rs232_rx),
	.rs232_tx(rs232_tx),
	.rst_n(rst_n),
	.scl(scl),
	.sda(sda)
);
M24LC64 u_M24LC64(
    .A0(1'b1), 
    .A1(1'b0), 
    .A2(1'b0), 
    .WP(1'b0), 
    .SDA(sda), 
    .SCL(scl), 
    .RESET(!rst_n)
);

pullup(sda);

initial clk = 0;
always #(clk_period/2) clk = ~clk;

initial begin
	#1;
	rst_n = 0;
	rs232_rx = 1;
	#(clk_period*5);
	rst_n = 1;
	#(clk_period*3);
	
	repeat(2)begin
	//0000_1010
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208*4);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	
	//1000_0101
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208*4);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);	
	end
	#(clk_period*500000);

    repeat(2)begin
	//0000_1010
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208*4);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	
	//1000_0101
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208*4);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 0;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	rs232_rx = 1;
	#(clk_period*5208);
	end
	#(clk_period*500000);

	$stop;
end
endmodule

仿真结果如下:

可以看到串口发送的数据和最后串口接收的数据保持一致。

2. 板级验证

管脚分配:

由于硬件上没有对 scl 和 sda 信号进行上拉,而I2C协议要求 scl 和 sda 上拉,因此在对管脚分配时可对这两个信号进行弱上拉,具体设置如下:

在分配管脚右侧空白处右击,选择Customize Columns,将 Weak Pull-Up Resistor移到右边,点击OK,再到引脚分配后边将其弱上拉打开,见下图所示:

 将jic文件下载到FPGA板子后,打开串口助手,发送四个数据 12 13 12 11,可以看到串口接收到发送的四个数据,验证成功。

测试发现,输入有些数据,接收到会有异常,如下:

在这里,我将别人写好的比较权威的I2C代码移植到我的系统里,测试发现还是这个样子,这里分析了一下,可能是这个测试系统逻辑方面存在不足,但已经通过仿真和部分测试验证了I2C控制器模块的正确性。

 

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
基于FPGAI2C控制器设计是一种用于实现I2C总线通信协议的方案。I2C是一种串行通信接口,用于在多个设备之间进行数据传输。FPGA作为一种可编程逻辑器件,具有高度灵活性和可重构性,非常适合用于设计I2C控制器。 在设计基于FPGAI2C控制器时,首先需要了解I2C协议的工作原理。I2C总线由一个主设备和多个从设备组成,主设备负责发起并控制通信过程,从设备负责响应主设备的指令。 设计中,我们可以利用FPGA的可编程性和并行处理能力来实现I2C控制器。首先,我们可以使用FPGA的I/O引脚作为SCL(时钟线)和SDA(数据线)来与外部的I2C总线进行连接。然后,我们可以使用FPGA内部的逻辑电路实现I2C协议的各个功能。 例如,在FPGA中可以设计一个状态机来控制I2C的起始,停止,发送和接收等操作。该状态机可以根据I2C的时钟以及外部的读写信号进行状态转换。同时,我们可以使用FPGA内部的存储器来缓存发送和接收的数据,以便实现数据的存储和传输。 此外,FPGA还可以使用硬件描述语言(如Verilog或VHDL)来描述I2C控制器的行为,方便实现和调试。通过FPGA的可编程性,我们可以根据需求对控制器进行优化和修改,以适应不同的应用场景。 总的来说,基于FPGAI2C控制器设计可以充分发挥FPGA的可编程性和并行处理能力,实现灵活、高效的I2C通信功能。该设计方案可以应用于各种需要I2C通信的电子设备中,提升系统的可扩展性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值