基於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 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
data_byte I 8 接收到串口發的數據
rx_done I 1 串口接收數據完成標誌
wrreq O 1 fifo寫請求
data_to_fifo O 8 輸出保存到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 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
usedw I 3 fifo中數據個數
q I 8 fifo讀出的數據
wr_data_vaild I 1 I2C_ctrl模塊中寫數據有效信號
rdreq O 1 fifo讀請求
wr_en O 1 I2C_ctrl模塊寫使能
wr_data O 8 寫入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 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
done I 1 I2C_ctrl模塊讀寫數據結束標誌
rd_data I 8 從EEPROM讀出的數據
rd_data_vaild I 1 從EEPROM讀出數據有效位
wrreq O 1 fifo寫請求
I2C_rd_data O 8 要寫入fifo的數據
rd_en O 1 I2C_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 位數 功能描述
clk I 1 系統時鐘50MHz
rst_n I 1 系統復位
usedw I 3 fifo中數據個數
q I 8 fifo中讀出的數據
tx_done I 1 串口發送一次數據完成標誌
rdreq O 1 fifo讀請求
send_en O 1 串口發送使能信號
data_send O 8 串口發送的數據

代碼如下:

//-------------------------------------------------------------------
//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控制器模塊的正確性。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章