目錄
本專題EEPROM讀寫系統(在下一篇博客講解,包含本篇內容)整體功能實現的工程下載鏈接如下:
https://download.csdn.net/download/qq_33231534/12503289
三、I2C協議的FPGA實現
上一篇博客對I2C總線協議進行了大體的講解,以及對I2C總線器件EEPROM(AT24C64)讀寫時序進行詳細闡述,下邊就要對EEPROM器件在FPGA上如何進行讀寫,以及接口設計和調試系統進行具體敘述。本實驗平臺使用的是小梅哥的AC620開發板,FPGA芯片是cyclone IV EP4CE10F17C8N。
1. I2C(EEPROM)接口設計
根據上方4個讀寫時序圖,編寫I2C接口時序,I2C協議接口設計,這裏採用狀態機的方式,將整個設計爲9個狀態,分別爲IDLE 、WR_START 、WR_DEVICE 、WR_ADDR 、WR_DATA 、RD_START 、RD_DEVICE、RD_DATA 、STOP 。其狀態轉移圖如下:
其各個狀態表示的含義以及該模塊接口信號的名稱及功能如下思維導圖:
這裏將接口信號用表格寫一下,看的更加清晰一點。如下:
信號名稱 | I/O | 位數 | 功能描述 |
clk | I | 1 | 系統時鐘50MHz |
rst_n | I | 1 | 系統復位 |
rd_data_num | I | 6 | 讀數據個數,最大32 |
wr_data_num | I | 6 | 寫數據個數,最大32 |
word_addr | I | 16 | 數據字地址,AT24C64是16位地址 |
device_addr | I | 3 | 設備可變地址,由硬件連接決定,本實驗平臺爲3‘b001 |
wr_en | I | 1 | 寫數據使能 |
wr_data | I | 8 | 寫數據 |
rd_en | I | 1 | 讀數據使能 |
wr_data_vaild | O | 1 | 寫數據有效位,讀多個數據就是讀完每個數據時產生一個有效位 |
rd_data | O | 8 | 讀數據 |
rd_data_vaild | O | 1 | 讀數據有效位,寫多個數據就是寫完每個數據時產生一個有效位 |
done | O | 1 | 讀寫操作結束標誌 |
scl | O | 1 | 串行時鐘線 |
sda | IO | 1 | 串行數據線 |
直接上代碼:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: I2C_ctrl.v
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: EEPROM(AT24C64)接口設計
//-------------------------------------------------------------------
module I2C_ctrl(
input clk ,//系統時鐘50MHz
input rst_n ,//系統復位
input [ 5: 0] rd_data_num ,//讀數據個數,最大32
input [ 5: 0] wr_data_num ,//寫數據個數,最大32
input [15: 0] word_addr ,//數據字地址,AT24C64是16位地址
input [ 2: 0] device_addr ,//設備可變地址,由硬件連接決定,本實驗平臺爲3‘b001
input wr_en ,//寫數據使能
input [ 7: 0] wr_data ,//寫數據
input rd_en ,//讀數據使能
output reg wr_data_vaild ,//寫數據有效位,讀多個數據就是讀完每個數據時產生一個有效位
output reg [7:0] rd_data ,//讀數據
output reg rd_data_vaild ,//讀數據有效位,寫多個數據就是寫完每個數據時產生一個有效位
output reg done ,//讀寫操作結束標誌
output reg scl ,//串行時鐘線
inout sda //串行數據線
);
parameter SYS_CLK = 50_000_000 ;//系統時鐘頻率
parameter SCLK = 100_000 ;//串行時鐘線時鐘頻率
localparam SCLK_CNT = SYS_CLK/SCLK;//產生時鐘線的時鐘計數次數
//狀態機狀態定義
parameter IDLE = 4'd0 ;//空閒狀態
parameter WR_START = 4'd1 ;//寫數據啓動狀態
parameter WR_DEVICE = 4'd2 ;//發送設備地址狀態
parameter WR_ADDR = 4'd3 ;//發送數據字地址狀態
parameter WR_DATA = 4'd4 ;//發送數據狀態
parameter RD_START = 4'd5 ;//讀數據啓動
parameter RD_DEVICE = 4'd6 ;//讀操作設備地址狀態
parameter RD_DATA = 4'd7 ;//讀數據狀態
parameter STOP = 4'd8 ;//停止狀態
reg [ 3: 0] state_c ;//狀態機改變後的狀態,時序邏輯
reg [ 3: 0] state_n ;//狀態機當前狀態,組合邏輯
reg add_flag ;//計數條件標誌
reg [ 8: 0] sclk_cnt ;//sclk時鐘信號計數
reg scl_high ;//scl信號高電平中間信號,1個時鐘週期
reg scl_low ;//scl信號低電平中間信號,1個時鐘週期
reg wr_flag ;//寫標誌
reg rd_flag ;//讀標誌
reg ack ;//響應信號
reg waddr_cnt ;//數據字地址字節計數,共2字節
reg [ 5: 0] wdata_cnt ;//寫數據字節計數,最大32個
reg [ 5: 0] rdata_cnt ;//讀數據字節計數,最大32個
reg [ 4: 0] bit_cnt ;//對傳輸的8位數據中的scl_highhe scl_low信號計數
reg sda_en ;//三態信號sda使能
reg sda_reg ;//三態信號sda寄存數據
reg [ 7: 0] device_addr_a ;//傳輸的8位設備地址
reg [ 2: 0] sda_reg_cnt ;//傳輸的8位數據傳遞數
wire [ 7: 0] word_addr_high;//數據字地址高8位
wire [ 7: 0] word_addr_low ;//數據字地址低8位
assign word_addr_high = word_addr[15:8];
assign word_addr_low = word_addr[ 7:0];
assign sda = sda_en ? sda_reg : 1'bz;
//sclk計數器計數標誌
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
add_flag <= 0;
end
else if(wr_en || rd_en) begin
add_flag <= 1;
end
else if(done)begin
add_flag <= 0;
end
end
//scl信號計數器
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sclk_cnt <= 0;
end
else if(add_flag) begin
if((add_flag && sclk_cnt==SCLK_CNT-1) || done)
sclk_cnt <= 0;
else
sclk_cnt <= sclk_cnt + 1'b1;
end
end
//scl信號生成
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl <= 1;
end
else if(sclk_cnt==SCLK_CNT/2-1) begin
scl <= 0;
end
else if(sclk_cnt==1'b0)begin
scl<= 1;
end
else begin
scl <= scl;
end
end
//scl_high信號:scl信號高電平正中間脈衝
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl_high <= 0;
end
else if(sclk_cnt==SCLK_CNT*1/4-1) begin
scl_high <= 1;
end
else begin
scl_high <= 0;
end
end
//scl_low信號:scl信號低電平正中間脈衝
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl_low <= 0;
end
else if(sclk_cnt==SCLK_CNT*3/4-1) begin
scl_low <= 1;
end
else begin
scl_low <= 0;
end
end
//寫信號標誌
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_flag <= 0;
end
else if(wr_en) begin
wr_flag <= 1;
end
else if(done) begin
wr_flag <= 0;
end
end
//讀信號標誌
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_flag <= 0;
end
else if(rd_en) begin
rd_flag <= 1;
end
else if(done) begin
rd_flag <= 0;
end
end
//狀態機第一段
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//狀態機第二段
always @(*)begin
case(state_c)
IDLE :begin
if(wr_en || rd_en)
state_n = WR_START;
else
state_n = state_c;
end
WR_START :begin
if(scl==0 && scl_low)
state_n = WR_DEVICE;
else
state_n = state_c;
end
WR_DEVICE :begin
if(ack==1 && scl_low)
state_n = WR_ADDR;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
WR_ADDR :begin
if(wr_flag && ack==1 && waddr_cnt==1 && scl_low)
state_n = WR_DATA;
else if(rd_flag && ack==1 && waddr_cnt==1 &&scl_low)
state_n = RD_START;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
WR_DATA :begin
if(wdata_cnt==wr_data_num-1 && ack==1 && wr_flag==1 && scl_low)
state_n = STOP;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
RD_START :begin
if(scl==0 && scl_low)
state_n = RD_DEVICE;
else
state_n = state_c;
end
RD_DEVICE :begin
if(ack==1 && scl_low)
state_n = RD_DATA;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
RD_DATA :begin
if(rdata_cnt==rd_data_num-1 && rd_flag==1 && bit_cnt==17 && scl_low)
state_n = STOP;
else
state_n = state_c;
end
STOP :begin
if(scl_high)
state_n = IDLE;
else
state_n = state_c;
end
endcase
end
//在傳輸設備地址、字地址和數據時要傳輸8位數據,這裏對其計數,計數scl_low和scl_high
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
bit_cnt <= 0;
end
else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
if(scl_high || scl_low)begin
if(bit_cnt==17 && scl_low)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 1'b1;
end
end
else begin
bit_cnt <= bit_cnt;
end
end
//ack響應信號
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
ack <= 0;
end
else if(scl_high && bit_cnt==16 && sda==0) begin
ack <= 1;
end
else if(bit_cnt==17 && scl_low)begin
ack <= 0;
end
else begin
ack <= ack;
end
end
//2個字地址計數器
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
waddr_cnt <= 0;
end
else if(state_c==WR_ADDR && bit_cnt==17 && scl_low) begin
if(waddr_cnt==1)
waddr_cnt <= 0;
else
waddr_cnt <= waddr_cnt + 1'b1;
end
else begin
waddr_cnt <= waddr_cnt;
end
end
//寫數據個數計數
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_cnt <= 0;
end
else if(state_c==WR_DATA && bit_cnt==17 && scl_low) begin
if(wdata_cnt==wr_data_num-1)
wdata_cnt <= 0;
else
wdata_cnt <= wdata_cnt + 1'b1;
end
else begin
wdata_cnt <= wdata_cnt;
end
end
//讀數據個數計數
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata_cnt <= 0;
end
else if(state_c==RD_DATA && bit_cnt==17 && scl_low) begin
if(rdata_cnt==rd_data_num-1)
rdata_cnt <= 0;
else
rdata_cnt <= rdata_cnt + 1'b1;
end
else begin
rdata_cnt <= rdata_cnt;
end
end
//sda_en信號輸出
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_en <= 0;
end
else begin
case(state_c)
IDLE:sda_en <= 0;
WR_START,RD_START,STOP:sda_en <= 1;
WR_DEVICE,WR_ADDR,WR_DATA,RD_DEVICE:
begin
if(bit_cnt<16)
sda_en <= 1;
else
sda_en <= 0;
end
RD_DATA:
//sda_en <= 0;
begin
if(bit_cnt<16)
sda_en <= 0;
else
sda_en <= 1;
end
default:sda_en <= 0;
endcase
end
end
//wr_data_vaild信號:寫數據有效信號標誌
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_data_vaild <= 0;
end
else if(state_c==WR_DATA && ack==1 && scl_low && bit_cnt==17) begin
wr_data_vaild <= 1;
end
else begin
wr_data_vaild <= 0;
end
end
//rd_data信號:讀出8位數據
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data <= 0;
end
else if(state_c==RD_DATA && bit_cnt<15 && scl_high) begin
rd_data <= {rd_data[6:0],sda};
end
else begin
rd_data <= rd_data;
end
end
//rd_data_vaild信號:讀出8位數據有效信號
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data_vaild <= 0;
end
else if(state_c==RD_DATA && bit_cnt==15 && scl_low) begin
rd_data_vaild <= 1;
end
else begin
rd_data_vaild <= 0;
end
end
//done:I2C工作結束標誌
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
done <= 0;
end
else if(state_c==STOP && scl_high) begin
done <= 1;
end
else begin
done <= 0;
end
end
//設備地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
device_addr_a <= 0;
end
else if(rd_flag && (state_c==RD_DEVICE || state_c==RD_START || state_c==RD_DATA))begin
device_addr_a <= {4'b1010,device_addr,1'b1};
end
else begin
device_addr_a <= {4'b1010,device_addr,1'b0};
end
end
//sda_reg_cnt
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_reg_cnt <= 7;
end
else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
if(bit_cnt<16 && scl_low)begin
if(sda_reg_cnt==0)
sda_reg_cnt <= 7;
else
sda_reg_cnt <= sda_reg_cnt - 1'b1;
end
end
else begin
sda_reg_cnt <= sda_reg_cnt;
end
end
//sda_reg
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_reg <= 1;
end
else begin
case(state_c)
WR_START:begin
if(scl_high)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end
WR_DEVICE:begin
sda_reg <= device_addr_a[sda_reg_cnt];
end
WR_ADDR:begin
if(waddr_cnt==0)
sda_reg <= word_addr_high[sda_reg_cnt];
else if(waddr_cnt==1)
if(bit_cnt<16)
sda_reg <= word_addr_low[sda_reg_cnt];
else
sda_reg <= 1;
end
WR_DATA:begin
sda_reg <= wr_data[sda_reg_cnt]; //輸入信號wr_data新數據應該wr_data_vaild時給出
end
RD_START:begin
if(scl_high)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end
RD_DEVICE:begin
sda_reg <= device_addr_a[sda_reg_cnt];
end
RD_DATA:/*begin
if(bit_cnt==16 || bit_cnt==17)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end*/
if(rdata_cnt==rd_data_num-1 && bit_cnt>15)
sda_reg <= 1;
else
sda_reg <= 0;
STOP:begin
sda_reg <= 0;
/*if(scl_high)
sda_reg <= 1;
else
sda_reg <= sda_reg;*/
end
default: sda_reg <= 1;
endcase
end
end
endmodule
2. 仿真驗證
這裏採用的EEPROM仿真模型進行仿真驗證,使用的是鎂光的EEPROM仿真模型。需要的人可以從我的工程裏邊下載,完整工程源碼看上方下載鏈接,這裏不多贅述。測試代碼如下:
`timescale 1 ns/ 1 ns
module I2C_ctrl_tb();
// test vector input registers
reg clk;
//reg [2:0] device_addr;
//reg [5:0] rd_data_num;
reg rd_en;
reg rst_n;
//reg treg_sda;
reg [15:0] word_addr;
reg [7:0] wr_data;
//reg [5:0] wr_data_num;
reg wr_en;
// wires
wire done;
wire [7:0] rd_data;
wire rd_data_vaild;
wire scl;
wire sda;
wire wr_data_vaild;
pullup(sda);
// assign statements (if any)
//assign sda = treg_sda;
parameter clk_period = 20;
I2C_ctrl i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.device_addr(3'b000),
.done(done),
.rd_data(rd_data),
.rd_data_num(6'd4),
.rd_data_vaild(rd_data_vaild),
.rd_en(rd_en),
.rst_n(rst_n),
.scl(scl),
.sda(sda),
.word_addr(word_addr),
.wr_data(wr_data),
.wr_data_num(6'd4),
.wr_data_vaild(wr_data_vaild),
.wr_en(wr_en)
);
M24LC64 u_M24LC64(
.A0(1'b0),
.A1(1'b0),
.A2(1'b0),
.WP(1'b0),
.SDA(sda),
.SCL(scl),
.RESET(!rst_n)
);
initial clk = 0;
always #(clk_period/2) clk=~clk;
initial begin
#2;
rst_n = 0;
word_addr = 0;
wr_en = 0;
wr_data = 0;
rd_en = 0;
#(clk_period*200);
rst_n = 1;
#(clk_period*20);
//寫入20組數據
word_addr = 0;
wr_data = 0;
repeat(20)begin
wr_en = 1;
#(clk_period);
wr_en = 0;
repeat(4)begin
@(posedge wr_data_vaild)
wr_data = wr_data + 1;
end
@(posedge done);
#(clk_period*200);
word_addr = word_addr + 4;
end
#(clk_period*500);
//讀出剛寫入的20組數據
word_addr = 0;
repeat(20)begin
rd_en = 1;
#(clk_period);
rd_en = 0;
@(posedge done);
#(clk_period*200);
word_addr = word_addr + 4;
end
#(clk_period*500);
$stop;
end
endmodule
下圖是對AT24C64型號EEPROM仿真結果:
下圖是對AT24C64型號EEPROM寫時序仿真結果:
下圖是對AT24C64型號EEPROM讀時序仿真結果:
仿真結果符合預期效果。下一篇將會在對I2C接口模塊進行板級調試,設計了一個EEPROM讀寫系統實驗,以驗證設計的正確性。