基於FPGA的I2C通信(二)

目錄

三、I2C協議的FPGA實現

1. I2C接口設計

2. 仿真驗證


 本專題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)接口設計

 

圖1:字節寫時序圖
圖2:頁寫時序圖
圖3:隨機讀時序圖
圖4:連續讀shix

 根據上方4個讀寫時序圖,編寫I2C接口時序,I2C協議接口設計,這裏採用狀態機的方式,將整個設計爲9個狀態,分別爲IDLE 、WR_START  、WR_DEVICE 、WR_ADDR  、WR_DATA  、RD_START 、RD_DEVICE、RD_DATA  、STOP 。其狀態轉移圖如下:

 其各個狀態表示的含義以及該模塊接口信號的名稱及功能如下思維導圖:

這裏將接口信號用表格寫一下,看的更加清晰一點。如下:

表:I2C接口模塊控制信號說明
信號名稱 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讀寫系統實驗,以驗證設計的正確性。

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