單字節讀寫I2C的verilog實現


本設計用verilog實現了一個簡單的I2C協議,實現功能為往固定地址先寫入一個字節,然后再讀出該字節。

涉及到的EEPROM為Atmel家的AT24C04,4Kbit存儲空間,8位位寬,需要9位寬的地址,其他細節參見規格書doc0180。

AT24C04支持5種讀寫模式:字節寫,頁寫,當前地址讀,隨機讀,順序讀。在當前地址讀或順序讀時序的最后不發送NAK,則可以繼續下一個地址的數據,直到主控給出一個NAK。

另外需要注意一點是,AT24C04需要至多5ms的寫時間,即在寫操作的結束時序到下一個操作的開始時許之間需要間隔5ms,以保證器件對寫入數據的固化。

簡化起見,本設計使用了一個1s的從開始寫到開始讀的時間間隔。

接口程序

`timescale 1ns/1ps

// AT24C04接口程序,只支持字節寫入和隨機讀取
// 本接口接收指定地坿的單字節寫入和單字節讀取
module i2c_intf #(
    parameter SYS_FREQ = 200_000_000,
    parameter SCL_FREQ = 100_000
    )(
    input wire clk, nrst,
    // 寫信號
    input wire wrreq,
    input wire [8:0] waddr, 
    input wire [7:0] wdata,
    input wire rdreq,
    // 讀信號
    input wire [8:0] raddr,
    output reg [7:0] rdata,
    output reg vld,
    // 忙線指示信號
    output reg rdy,
    // i2c接口信號
    output reg scl,
    inout sda
    );
    
    reg sda_out;
    // 用於觀察信號
    wire sda_in;
    
    assign sda = (sda_out == 0) ? 1'b0 : 1'bz;
    assign sda_in = sda;
    // I2C的SCL周期
    localparam SCL_T = SYS_FREQ / SCL_FREQ;

    // 由於AT24C04為4K容量,數據長度8位,需要9bit地址,最高位存於器件地址中
    localparam DADDR_6 = 6'b101000;
    
    reg [7:0] device_addr;
    
    // SCL計數
    reg [15:0] cnt_scl;
    wire add_cnt_scl;
    wire end_cnt_scl;
    // bit計數
    reg [3:0] cnt_bit;
    wire add_cnt_bit;
    wire end_cnt_bit;
    // cnt計數狀態內執行順序
    reg [3:0] cnt_step;
    wire add_cnt_step;
    wire end_cnt_step;
    // 變量對應不同狀態需要執行的步驟數
    reg [3:0] bit_num, step_num;
        
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            device_addr <= 0;
        else if(state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && cnt_step == 2 - 1)
            device_addr <= {DADDR_6, waddr[8], 1'b0};
        else if(state_c == S_RD_RANDOM && cnt_step == 5 - 1)
            device_addr <= {DADDR_6, raddr[8], 1'b1};
    end

    // 狀態划分為:空閑,寫字節,隨機寫
    localparam S_IDLE         = 6'b000_001;
    localparam S_WR_BYTE     = 6'b000_010;
    localparam S_RD_RANDOM    = 6'b000_100;
    
    reg [5:0] state_c, state_n;
    
    wire idle2wr_byte;
    wire idle2rd_random;
    wire wr_byte2idle;
    wire rd_random2idle;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            state_c <= S_IDLE;
        else
            state_c <= state_n;
    end
    
    always @* begin
        case (state_c)
            S_IDLE: begin
                    if(idle2wr_byte)
                        state_n = S_WR_BYTE;
                    else if(idle2rd_random)
                        state_n = S_RD_RANDOM;
                    else
                        state_n = state_c;
                end
            S_WR_BYTE: begin
                    if(wr_byte2idle)
                        state_n = S_IDLE;
                    else
                        state_n = state_c;
                end
            S_RD_RANDOM: begin
                    if(rd_random2idle)
                        state_n = S_IDLE;
                    else
                        state_n = state_c;
                end
            default: state_n = state_c;
        endcase
    end
    
    assign idle2wr_byte        = state_c == S_IDLE     && wrreq;
    assign idle2rd_random    = state_c == S_IDLE        && rdreq;
    assign wr_byte2idle        = state_c == S_WR_BYTE    && end_cnt_step;
    assign rd_random2idle    = state_c == S_RD_RANDOM && end_cnt_step;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt_scl <= 0;
        else if(add_cnt_scl) begin
            if(end_cnt_scl)
                cnt_scl <= 0;
            else
                cnt_scl <= cnt_scl + 1'b1;
        end
    end
    assign add_cnt_scl = state_c != S_IDLE;
    assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_T - 1;
        
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt_bit <= 0;
        else if(add_cnt_bit) begin
            if(end_cnt_bit)
                cnt_bit <= 0;
            else
                cnt_bit <= cnt_bit + 1'b1;
        end
    end
    assign add_cnt_bit = end_cnt_scl;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1;
    // 狀態內以開始,讀,寫或結束為一個步驟
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt_step <= 0;
        else if(add_cnt_step) begin
            if(end_cnt_step)
                cnt_step <= 0;
            else
                cnt_step <= cnt_step + 1'b1;
        end
    end
    assign add_cnt_step = end_cnt_bit;
    assign end_cnt_step = add_cnt_step && cnt_step == step_num - 1;

    // 寫單個字節分為以下步驟:開始,寫器件地址,寫存儲地址,寫數據,結束
    // 讀單個字節分為以下步驟:開始,寫器件地址,寫存儲地址,開始時序,寫器件地址,讀數據,結束
    always @* begin
        if(state_c == S_IDLE) begin
            step_num = 0;
            bit_num = 0;
        end
        else if(state_c == S_WR_BYTE) begin
            step_num = 5;
            if(cnt_step == 1 - 1 || cnt_step == step_num - 1)
                bit_num = 1;
            else
                bit_num = 9;
        end
        else if(state_c == S_RD_RANDOM) begin
            step_num = 7;
            if(cnt_step == 1 - 1 || cnt_step == 4 - 1 || cnt_step == step_num - 1)
                bit_num = 1;
            else
                bit_num = 9;
        end
        else begin
          step_num = 0;
          bit_num = 0;
        end
    end
    
    // scl信號前半個周期為低,后半個周期為高,且第1個半個周期保持高電平
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            scl <= 1;
        else if(add_cnt_scl && cnt_scl == SCL_T / 2 - 1)
            scl <= 1;
        else if(end_cnt_scl && !end_cnt_step)
            scl <= 0;
    end
    
    // 重點,SDA信號
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            sda_out <= 1;
        else begin
            // 開始時序
            if((cnt_step == 1 - 1 || state_c == S_RD_RANDOM && cnt_step == 4 - 1) && cnt_scl == SCL_T * 3 / 4 - 1)
                sda_out <= 0;
            // 結束時序,且在結束時鍾周期需要需要事先將SDA拉低
            else if(cnt_step == step_num - 1 && cnt_scl == SCL_T / 4 - 1)
                sda_out <= 0;
            else if(cnt_step == step_num - 1 && cnt_scl == SCL_T * 3 / 4 - 1)
                sda_out <= 1;
            // 在應答周期將SDA拉高
            else if(cnt_bit == 9 - 1 && cnt_scl == SCL_T / 4 - 1)
                sda_out <= 1;
            // 器件地址
            else if((state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && (cnt_step == 2 - 1 || cnt_step == 5 - 1)) && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= device_addr[7 - cnt_bit];
            // 寫地址
            else if(state_c == S_WR_BYTE && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= waddr[7 - cnt_bit];
            // 寫數據
            else if(state_c == S_WR_BYTE && cnt_step == 4 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= wdata[7 - cnt_bit];
            // 讀地址
            else if(state_c == S_RD_RANDOM && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= raddr[7 - cnt_bit];
        end
    end

    // 讀數據代碼
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            rdata <= 0;
        else if(state_c == S_RD_RANDOM && cnt_step == 6 - 1 && cnt_scl == SCL_T / 4 * 3 - 1 && cnt_bit != 9 - 1)
            rdata[7 - cnt_bit] <= sda;
    end
    
    // 讀數據有效指示信號
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            vld <= 0;
        else if(state_c == S_RD_RANDOM && end_cnt_step)
            vld <= 1;
        else
            vld <= 0;
    end
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            rdy <= 1;
        else if(state_c == S_IDLE)
            rdy <= 1;
        else
            rdy <= 0;
    end
    
    // ILA例化代碼
    ila_read ila_read_u (
    .clk    (clk    ), // input wire clk
    .probe0 (vld    ), // input wire [7:0] probe0
    .probe1 (scl    ),
    .probe2 (sda_out),
    .probe3 (cnt_step),
    .probe4 (cnt_bit),
    .probe5 (rdata  ),
    .probe6 (wrreq  ),
    .probe7 (rdreq  ),
    .probe8 (sda_in )
    ); 
endmodule

 

頂層測試例程

`timescale 1ns / 1ps

module i2c_example(
    input wire clk_p, clk_n, nrst,
    input wire key_in,
    output wire scl,
    inout sda    
    );
    
    parameter SYS_FREQ = 200_000_000;
    parameter SCL_FREQ = 100_000;
    
    // 差分時鍾信號轉為單端信號
    IBUFGDS #(
        .DIFF_TERM("FALSE"),
        .IBUF_LOW_PWR("TRUE"),
        .IOSTANDARD("DEFAULT")
    )  IBUFGDS_inst(
        .O(clk),
        .I(clk_p),
        .IB(clk_n)
    );
    
    reg [31:0] cnt;
    wire add_cnt;
    wire end_cnt;
    reg flag;
    wire [7:0] data;
    wire vld;
    wire wrreq;
    
    // 加入按鍵模塊用於調試
    wire key_out;
    reg key_out_ff;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_out_ff <= 0;
        else
            key_out_ff <= key_out;
    end
    
    assign wrreq = key_out == 0 && key_out_ff == 1;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            flag <= 0;
        else if(wrreq)
            flag <= 1;
        else if(end_cnt)
            flag <= 0;
    end
           
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;
        else if(add_cnt) begin
            if(end_cnt)
                cnt <= 0;
            else
                cnt <= cnt + 1'b1;
        end
    end
    assign add_cnt = flag;
    assign end_cnt = add_cnt && cnt == SYS_FREQ - 1;
    
    i2c_intf #(SYS_FREQ, SCL_FREQ) i2c_intf_u(
        .clk     (clk    ), 
        .nrst    (nrst   ),
        .wrreq   (wrreq  ),
        .waddr   (9'h03  ), 
        .wdata   (8'h55  ),
        
        .rdreq   (end_cnt),
        .raddr   (9'h03  ),
        .rdata   (data   ),
        .vld     (vld    ),
        
        .rdy     (       ),
        
        .scl     (scl    ),
        .sda     (sda    )
        );
    
    debounce debounce_u(
        .clk    (clk    ), 
        .nrst   (nrst   ),
        .key_in (key_in ),
        .key_out(key_out)
    );               
endmodule

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM