目錄
本專題EEPROM讀寫系統(在下一篇博客講解,包含本篇內容)整體功能實現的工程下載鏈接如下:
https://download.csdn.net/download/qq_33231534/12503289
四、EEPROM讀寫系統設計
1. 整體系統概述
整個系統的要求爲:首先通過串口助手發送四個字節數據,將四個字節數據存儲在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控制模塊)
信號名稱 | 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寫控制模塊)
信號名稱 | 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讀控制模塊)
信號名稱 | 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控制器模塊的正確性。