0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

I2C協(xié)議基本概念和數(shù)據(jù)傳輸

FPGA技術(shù)江湖 ? 來源:FPGA技術(shù)江湖 ? 作者:FPGA技術(shù)江湖 ? 2022-11-01 09:18 ? 次閱讀

今天給大俠帶來基于FPGA模擬I2C協(xié)議設(shè)計(jì),由于篇幅較長(zhǎng),分三篇。今天帶來第三篇,下篇,程序的仿真與測(cè)試。話不多說,上貨。

之前也有相關(guān)文章介紹,這里超鏈接一下,僅供各位大俠參考。

源碼系列:基于FPGA的 IIC 設(shè)計(jì)(附源工程)

這里也給出前兩篇的超鏈接:

基于 FPGA 的模擬 I2C協(xié)議設(shè)計(jì)(上)

基于 FPGA 的模擬 I2C協(xié)議設(shè)計(jì)(中)

導(dǎo)讀

I2C(Inter-Integrated Circuit),其實(shí)是I2CBus 簡(jiǎn)稱,中文就是集成電路總線,它是一種串行通信總線,使用多主從架構(gòu),由飛利浦公司在1980年代為了讓主板、嵌入式系統(tǒng)手機(jī)用以連接低速周邊設(shè)備而發(fā)展。I2C的正確讀法為“I平方C”("I-squared-C"),而“I二C”("I-two-C")則是另一種錯(cuò)誤但被廣泛使用的讀法。自2006年10月1日起,使用 I2C 協(xié)議已經(jīng)不需要支付專利費(fèi),但制造商仍然需要付費(fèi)以獲取 I2C 從屬設(shè)備地址。

I2C簡(jiǎn)單來說,就是一種串行通信協(xié)議,I2C的通信協(xié)議和通信接口在很多工程中有廣泛的應(yīng)用,如數(shù)據(jù)采集領(lǐng)域的串行 AD,圖像處理領(lǐng)域的攝像頭配置,工業(yè)控制領(lǐng)域的 X 射線管配置等等。除此之外,由于I2C協(xié)議占用的 IO 資源特別少,連接方便,所以工程中也常選用I2C接口做為不同芯片間的通信協(xié)議。I2C串行總線一般有兩根信號(hào)線,一根是雙向的數(shù)據(jù)線SDA,另一根是時(shí)鐘線SCL。所有接到I2C總線設(shè)備上的串行數(shù)據(jù)SDA都接到總線的SDA上,各設(shè)備的時(shí)鐘線SCL接到總線的SCL上。

在現(xiàn)代電子系統(tǒng)中,有為數(shù)眾多的 IC 需要進(jìn)行相互之間以及與外界的通信。為了簡(jiǎn)化電路的設(shè)計(jì),Philips 公司開發(fā)了一種用于內(nèi)部 IC 控制的簡(jiǎn)單的雙向兩線串行總線I2C(Intel-Integrated Circuit bus)。1998 年當(dāng)推出I2C總線協(xié)議 2.0 版本時(shí),I2C協(xié)議實(shí)際上已經(jīng)成為一個(gè)國(guó)際標(biāo)準(zhǔn)。

在進(jìn)行 FPGA 設(shè)計(jì)時(shí),經(jīng)常需要和外圍提供I2C接口的芯片通信。例如低功耗的 CMOS 實(shí)時(shí)時(shí)鐘/日歷芯片 PCF8563、LCD 驅(qū)動(dòng)芯片 PCF8562、并行口擴(kuò)展芯片 PCF8574、鍵盤/LED 驅(qū)動(dòng)器 ZLG7290 等都提供I2C接口。因此在 FPGA 中模擬I2C接口已成為 FPGA 開發(fā)必要的步驟。

本篇將詳細(xì)講解在 FPGA 芯片中使用 VHDL/Verilog HDL 模擬I2C協(xié)議,以及編寫 TestBench仿真和測(cè)試程序的方法。

第三篇內(nèi)容摘要:本篇會(huì)介紹程序的仿真與測(cè)試,包括主節(jié)點(diǎn)的仿真、從節(jié)點(diǎn)的仿真、仿真主程序、仿真結(jié)果以及總結(jié)等相關(guān)內(nèi)容。

四、程序的仿真與測(cè)試

I2C 協(xié)議的模擬程序完成后,還需要通過仿真程序?qū)Τ绦虻墓δ苓M(jìn)行測(cè)試。對(duì)本程序的仿真包括 3 個(gè)部分:第一部分是主節(jié)點(diǎn)的仿真,模擬數(shù)據(jù)讀/寫;第二部分是從節(jié)點(diǎn)的仿真,模擬數(shù)據(jù)的接收和應(yīng)答;第三部分是仿真主程序,負(fù)責(zé)整個(gè)仿真過程的控制。

4.1 主節(jié)點(diǎn)的仿真

主節(jié)點(diǎn)仿真的內(nèi)容包括讀數(shù)據(jù)、寫數(shù)據(jù)和比較數(shù)據(jù) 3 部分,代碼如下:

`include "timescale.v"
//模塊定義
module wb_master_model(clk, rst, adr, din, dout, cyc, stb, we, sel, ack, err, rty);
    //參數(shù)
    parameter dwidth = 32;
    parameter awidth = 32;
    
    //輸入、輸出
    input clk, rst;
    output [awidth -1:0] adr;
    input [dwidth -1:0] din;
    output [dwidth -1:0] dout;
    output cyc, stb;
    output we;
    output [dwidth/8 -1:0] sel;
    input ack, err, rty;
    
    //WIRE 定義
    reg [awidth -1:0] adr;
    reg [dwidth -1:0] dout;
    reg cyc, stb;
    reg we;
    reg [dwidth/8 -1:0] sel;
    reg [dwidth -1:0] q;
    
    // 存儲(chǔ)邏輯
    //初始化
    initial
        begin
            adr = {awidth{1'bx}};
            dout = {dwidth{1'bx}};
            cyc = 1'b0;
            stb = 1'bx;
            we = 1'hx;
            sel = {dwidth/8{1'bx}};
            #1;
        end
        
    // 寫數(shù)據(jù)周期
    task wb_write;
        input delay;
        integer delay;
        input [awidth -1:0] a;
        input [dwidth -1:0] d;
        begin
            // 延遲
            repeat(delay) @(posedge clk);
            // 設(shè)置信號(hào)值
            #1;
            adr = a;
            dout = d;
            cyc = 1'b1;
            stb = 1'b1;
            we = 1'b1;
            sel = {dwidth/8{1'b1}};
            @(posedge clk);
            // 等待從節(jié)點(diǎn)的應(yīng)答信號(hào)
            while(~ack) @(posedge clk);
            #1;
            cyc = 1'b0;
            stb = 1'bx;
            adr = {awidth{1'bx}};
            dout = {dwidth{1'bx}};
            we = 1'hx;
            sel = {dwidth/8{1'bx}};
        end
    endtask
    
    // 讀數(shù)據(jù)周期
    task wb_read;
        input delay;
        integer delay;
        input [awidth -1:0]a;
        output [dwidth -1:0] d;
        begin
            // 延遲
            repeat(delay) @(posedge clk);
            // 設(shè)置信號(hào)值
            #1;
            adr = a;
            dout = {dwidth{1'bx}};
            cyc = 1'b1;
            stb = 1'b1;
            we = 1'b0;
            sel = {dwidth/8{1'b1}};
            @(posedge clk);
            // 等待從節(jié)點(diǎn)應(yīng)答信號(hào)
            while(~ack) @(posedge clk);
            #1;
            cyc = 1'b0;
            stb = 1'bx;
            adr = {awidth{1'bx}};
            dout = {dwidth{1'bx}};
            we = 1'hx;
            sel = {dwidth/8{1'bx}};
            d = din;
        end
    endtask
    
    // 比較數(shù)據(jù)
    task wb_cmp;
        input delay;
        integer delay;
        input [awidth -1:0] a;
        input [dwidth -1:0] d_exp;
        begin
            wb_read (delay, a, q);
            if (d_exp !== q)
$display("Datacompareerror.Received%h,expected%hattime%t",q,d_exp,$time);
        end
    endtask
endmodule

4.2 從節(jié)點(diǎn)的仿真

從節(jié)點(diǎn)仿真程序需要模擬從主節(jié)點(diǎn)接收數(shù)據(jù),并發(fā)出應(yīng)答信號(hào),代碼如下:

`include "timescale.v"
//模塊定義
module i2c_slave_model (scl, sda);
    // 參數(shù)
    // 地址
    parameter I2C_ADR = 7'b001_0000;
    
    // 輸入、輸出
    input scl;
    inout sda;
    
    // 變量申明
    wire debug = 1'b1;
    reg [7:0] mem [3:0]; // 初始化內(nèi)存
    reg [7:0] mem_adr; // 內(nèi)存地址
    reg [7:0] mem_do; // 內(nèi)存數(shù)據(jù)輸出
    reg sta, d_sta;
    reg sto, d_sto;
    reg [7:0] sr; // 8 位移位寄存器
    reg rw; // 讀寫方向
    wire my_adr; // 地址
    wire i2c_reset; // RESET 信號(hào)
    reg [2:0] bit_cnt;
    wire acc_done; // 傳輸完成
    reg ld;
    reg sda_o;
    wire sda_dly;
    
    // 狀態(tài)機(jī)的狀態(tài)定義
    parameter idle = 3'b000;
    parameter slave_ack = 3'b001;
    parameter get_mem_adr = 3'b010;
    parameter gma_ack = 3'b011;
    parameter data = 3'b100;
    parameter data_ack = 3'b101;
    reg [2:0] state;
    
    // 模塊主體
    //初始化
    initial
        begin
            sda_o = 1'b1;
            state = idle;
        end
        
    // 產(chǎn)生移位寄存器
    always @(posedge scl)
        sr <= #1 {sr[6:0],sda};
        
    //檢測(cè)到訪問地址與從節(jié)點(diǎn)一致
    assign my_adr = (sr[7:1] == I2C_ADR);
    
    //產(chǎn)生位寄存器
    always @(posedge scl)
        if(ld)
            bit_cnt <= #1 3'b111;
        else
            bit_cnt <= #1 bit_cnt - 3'h1;
            
    //產(chǎn)生訪問結(jié)束標(biāo)志
    assign acc_done = !(|bit_cnt);
    
    // sda 延遲
    assign #1 sda_dly = sda;
    
    //檢測(cè)到開始狀態(tài)
    always @(negedge sda)
        if(scl)
            begin
                sta <= #1 1'b1;
                    if(debug)
                        $display("DEBUG i2c_slave; start condition detected at %t", $time);
            end
        else
            sta <= #1 1'b0;
            
    always @(posedge scl)
        d_sta <= #1 sta;
    
    // 檢測(cè)到停止?fàn)顟B(tài)信號(hào)
    always @(posedge sda)
        if(scl)
            begin
                sto <= #1 1'b1;
                    if(debug)
                        $display("DEBUG i2c_slave; stop condition detected at %t", $time);
            end
        else
            sto <= #1 1'b0;
            
    //產(chǎn)生 I2C 的 RESET 信號(hào)
    assign i2c_reset = sta || sto;
    
    // 狀態(tài)機(jī)
    always @(negedge scl or posedge sto)
        if (sto || (sta && !d_sta) )
            begin
                state <= #1 idle; // reset 狀態(tài)機(jī)
                sda_o <= #1 1'b1;
                ld <= #1 1'b1;
            end
        else
            begin
            // 初始化
            sda_o <= #1 1'b1;
            ld <= #1 1'b0;
            case(state)
                idle: // idle 狀態(tài)
                    if (acc_done && my_adr)
                        begin
                            state <= #1 slave_ack;
                            rw <= #1 sr[0];
                            sda_o <= #1 1'b0; // 產(chǎn)生應(yīng)答信號(hào)
                            #2;
                            if(debug && rw)
                                $display("DEBUG?i2c_slave;?command?byte?received?(read)?at?%t",$time);
                            if(debug && !rw)
                                $display("DEBUG?i2c_slave;?command?byte?received?(write)?at?%t",$time);
                            if(rw)
                                begin
                                    mem_do <= #1 mem[mem_adr];
                                        if(debug)
                                            begin
                                                #2?$display("DEBUG?i2c_slave;?data?block?read?%x?from address?%x?(1)",?mem_do,?mem_adr);
                                                #2?$display("DEBUG?i2c_slave;?memcheck?[0]=%x,?[1]=%x, [2]=%x",?mem[4'h0],?mem[4'h1],?mem[4'h2]);
                                            end
                                end
                        end
                    slave_ack:
                        begin
                            if(rw)
                                begin
                                    state <= #1 data;
                                    sda_o <= #1 mem_do[7];
                                end
                            else
                                state <= #1 get_mem_adr;
                                ld <= #1 1'b1;
?????????????????????????end????
?????????????????????????
                    get_mem_adr: // 等待內(nèi)存地址
                        if(acc_done)
                            begin
                                state <= #1 gma_ack;
                                mem_adr <= #1 sr; // 保存內(nèi)存地址
                                sda_o <= #1 !(sr <= 15); // 收到合法地址信號(hào)后發(fā)出應(yīng)答信號(hào)
                            if(debug)
                                #1?$display("DEBUG?i2c_slave;?address?received.?adr=%x,?ack=%b",sr,?sda_o);
                            end
                            
                    gma_ack:
                        begin
                            state <= #1 data;
                            ld <= #1 1'b1;
                        end
                   
                    data: // 接收數(shù)據(jù)
                        begin
                            if(rw)
                                sda_o <= #1 mem_do[7];
                            if(acc_done)
                                begin
                                    state <= #1 data_ack;
                                    mem_adr <= #2 mem_adr + 8'h1;
                                    sda_o <= #1 (rw && (mem_adr <= 15) );
                                if(rw)
                                    begin
                                        #3 mem_do <= mem[mem_adr];
                                        if(debug)
                                            #5?$display("DEBUG?i2c_slave;?data?block?read?%x?from address?%x?(2)",?mem_do,?mem_adr);                                    
????????????????????????????????????end
                                if(!rw)
                                    begin
                                        mem[ mem_adr[3:0] ] <= #1 sr; // store data in memory
                                        if(debug)
                                            #2?$display("DEBUG?i2c_slave;?data?block?write?%x?to address?%x",?sr,?mem_adr);
                                    end
                                end
                            end
                            
                        data_ack:
                            begin
                                ld <= #1 1'b1;
                                if(rw)
                                    if(sda) //
                                        begin
                                            state <= #1 idle;
                                            sda_o <= #1 1'b1;
                                        end
                                    else
                                        begin
                                            state <= #1 data;
                                            sda_o <= #1 mem_do[7];
                                        end
                                    else
                                        begin
                                            state <= #1 data;
                                            sda_o <= #1 1'b1;
                                        end
                                end
                            endcase
                        end
                        
    // 從內(nèi)存讀數(shù)據(jù)
    always @(posedge scl)
    if(!acc_done && rw)
    mem_do <= #1 {mem_do[6:0], 1'b1};
    
    // 產(chǎn)生三態(tài)
    assign sda = sda_o ? 1'bz : 1'b0;
    
    // 檢查時(shí)序
    wire tst_sto = sto;
    wire tst_sta = sta;
    wire tst_scl = scl;
    
    //指定各個(gè)信號(hào)的上升沿和下降沿
    specify
        specparam normal_scl_low = 4700,
            normal_scl_high = 4000,
            normal_tsu_sta = 4700,
            normal_tsu_sto = 4000,
            normal_sta_sto = 4700,
            fast_scl_low = 1300,
            fast_scl_high = 600,
            fast_tsu_sta = 1300,
            fast_tsu_sto = 600,
            fast_sta_sto = 1300;
        $width(negedge scl, normal_scl_low);
        $width(posedge scl, normal_scl_high);
        $setup(negedge sda &&& scl, negedge scl, normal_tsu_sta); // 開始狀態(tài)信號(hào)
        $setup(posedge scl, posedge sda &&& scl, normal_tsu_sto); // 停止?fàn)顟B(tài)信號(hào)
        $setup(posedge tst_sta, posedge tst_scl, normal_sta_sto);
    endspecify
    
endmodule

4.3 仿真主程序

仿真主程序完成主節(jié)點(diǎn)數(shù)據(jù)到從節(jié)點(diǎn)的控制,代碼如下:

`include "timescale.v"
//模塊定義
moduletst_bench_top();
    //連線和寄存器
    reg clk;
    reg rstn;
    wire [31:0] adr;
    wire [ 7:0] dat_i, dat_o;
    wire we;
    wire stb;
    wire cyc;
    wire ack;
    wire inta;
    
    //q 保存狀態(tài)寄存器內(nèi)容
    reg [7:0] q, qq;
    wire scl, scl_o, scl_oen;
    wire sda, sda_o, sda_oen;
    
    //寄存器地址
    parameter PRER_LO = 3'b000; //分頻寄存器低位地址
    parameter PRER_HI = 3'b001; //高位地址
    parameterCTR=3'b010;//控制寄存器地址,(7)使能位|6中斷使能位|5-0其余保留位
    parameterRXR=3'b011;//接收寄存器地址,(7)接收到的最后一個(gè)字節(jié)的數(shù)據(jù)
    parameter TXR = 3'b011; //傳輸寄存器地址,(7)傳輸?shù)刂窌r(shí)最后一位為讀寫位,1 為讀
    parameter CR = 3'b100; //命令寄存器地址,

//(7)開始|6結(jié)束|5讀|4寫|3應(yīng)答(作為接收方時(shí),發(fā)送應(yīng)答信號(hào),“0”為應(yīng)答,“1”為不應(yīng)答)|2保留位|1保留位|0中斷應(yīng)答位,這八位自動(dòng)清除
parameterSR=3'b100;//狀態(tài)寄存器地址,(7)接收應(yīng)答位(“0”為接收到應(yīng)答)|6忙位(產(chǎn)生開始信號(hào)后變?yōu)?,結(jié)束信號(hào)后變?yōu)?)|5仲裁位|4-2保留位|1傳輸中位(1表示正在傳輸數(shù)據(jù),0表示傳輸結(jié)束)|中斷標(biāo)志位
    parameter TXR_R = 3'b101;
    parameter CR_R = 3'b110;
    
    // 產(chǎn)生時(shí)鐘信號(hào),一個(gè)時(shí)間單位為 1ns,周期為 10ns,頻率為 100MHz。
    always #5 clk = ~clk;
    
    //連接 master 模擬模塊
    wb_master_model #(8, 32) u0 (
            .clk(clk), //時(shí)鐘
            .rst(rstn), //重起
            .adr(adr), //地址
            .din(dat_i), //輸入的數(shù)據(jù)
            .dout(dat_o), //輸出的數(shù)據(jù)
            .cyc(cyc),
            .stb(stb),
            .we(we),
            .sel(),
            .ack(ack), //應(yīng)答
            .err(1'b0),
            .rty(1'b0)
        );
    
    //連接 i2c 接口
    i2c_master_top i2c_top (
            //連接到 master 模擬模塊部分
            .wb_clk_i(clk), //時(shí)鐘
            .wb_rst_i(1'b0), //同步重起位
            .arst_i(rstn), //異步重起
            .wb_adr_i(adr[2:0]), //地址輸入
            .wb_dat_i(dat_o), //數(shù)據(jù)輸入接口
            .wb_dat_o(dat_i), //數(shù)據(jù)從接口輸出
            .wb_we_i(we), //寫使能信號(hào)
            .wb_stb_i(stb), //片選信號(hào),應(yīng)該一直為高
            .wb_cyc_i(cyc),
            .wb_ack_o(ack), //應(yīng)答信號(hào)輸出到 master 模擬模塊
            .wb_inta_o(inta), //中斷信號(hào)輸出到 master 模擬模塊
            
            //輸出的 i2c 信號(hào),連接到 slave 模擬模塊
            .scl_pad_i(scl),
            .scl_pad_o(scl_o),
            .scl_padoen_o(scl_oen),
            .sda_pad_i(sda),
            .sda_pad_o(sda_o),
            .sda_padoen_o(sda_oen)
        );
    
    //連接到 slave 模擬模塊
    i2c_slave_model #(7'b1010_000) i2c_slave (
            .scl(scl),
            .sda(sda)
        );
        
    //為 master 模擬模塊產(chǎn)生 scl 和 sda 的三態(tài)緩沖
    assign scl = scl_oen ? 1'bz : scl_o; // create tri-state buffer for i2c_master scl line
    assign sda = sda_oen ? 1'bz : sda_o; // create tri-state buffer for i2c_master sda line
    
    //上拉
    pullup p1(scl); // pullup scl line
    pullup p2(sda); // pullup sda line
    
    //初始化
    initial
        begin
            $display("
 狀態(tài): %t I2C 接口測(cè)試開始!

", $time);
            // 初始值
            clk = 0;
            //重起系統(tǒng)
            rstn = 1'b1; // negate reset
            #2;
            rstn = 1'b0; // assert reset
            repeat(20) @(posedge clk);
            rstn = 1'b1; // negate reset
            $display("狀態(tài): %t 完成系統(tǒng)重起!", $time);
            @(posedge clk);
            // 對(duì)接口編程
            // 寫內(nèi)部寄存器
            // 分頻 100M/100K*5=O'200=h'C8
            u0.wb_write(1, PRER_LO, 8'hc7);
            u0.wb_write(1, PRER_HI, 8'h00);
            $display("狀態(tài): %t 完成分頻寄存器操作!", $time);
            //讀分頻寄存器內(nèi)容
            u0.wb_cmp(0, PRER_LO, 8'hc8);
            u0.wb_cmp(0, PRER_HI, 8'h00);
            $display("狀態(tài): %t 完成分頻寄存器確認(rèn)操作!", $time);
            //接口使能
            u0.wb_write(1, CTR, 8'h80);
            $display("狀態(tài): %t 完成接口使能!", $time);
            // 驅(qū)動(dòng) slave 地址
            // h'a0=b'1010_0000,地址+寫狀態(tài),寫入的地址為 h'50
            u0.wb_write(1, TXR, 8'ha0);
            //命令內(nèi)容為 b'1001_0000,產(chǎn)生開始位,并設(shè)置寫狀態(tài)
            u0.wb_write(0, CR, 8'h90);
            $display("狀態(tài): %t 產(chǎn)生開始位, 然后寫命令 a0(地址+寫),命令開始!", $time);
            // 檢查狀態(tài)位信息
            // 檢查傳輸是否結(jié)束
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(0, SR, q);
            $display("狀態(tài): %t 地址驅(qū)動(dòng)寫操作完成!", $time);
            // 待寫的地址為 h'01
            u0.wb_write(1, TXR, 8'h01);
            // 產(chǎn)生寫命令 b'0001_0000
            u0.wb_write(0, CR, 8'h10);
            $display("狀態(tài): %t 待寫地址為 01,命令開始!", $time);
            // 檢查狀態(tài)位
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(0, SR, q);
            $display("狀態(tài): %t 寫操作完成!", $time);
            // 寫入內(nèi)容
            u0.wb_write(1, TXR, 8'ha5);
            u0.wb_write(0, CR, 8'h10);
            $display("狀態(tài): %t 寫入內(nèi)容為 a5,開始寫入過程!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 寫 a5 到地址 h'01 中完成!", $time);
            // 寫入下一個(gè)地址 5a
            u0.wb_write(1, TXR, 8'h5a); // present data
            // 寫入并停止
            u0.wb_write(0, CR, 8'h50); // set command (stop, write)
            $display("狀態(tài): %t 寫 5a 到下一個(gè)地址,產(chǎn)生停止位!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q); // poll it until it is zero
            $display("狀態(tài): %t 寫第二個(gè)地址結(jié)束!", $time);
            // 讀
            // 驅(qū)動(dòng) slave 地址
            u0.wb_write(1, TXR, 8'ha0);
            u0.wb_write(0, CR, 8'h90);
            $display("狀態(tài): %t 產(chǎn)生開始位,寫命令 a0 (slave 地址+write)", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q); // poll it until it is zero
            $display("狀態(tài): %t slave 地址驅(qū)動(dòng)完成!", $time);
            // 發(fā)送地址
            u0.wb_write(1, TXR, 8'h01);
            u0.wb_write(0, CR, 8'h10);
            $display("狀態(tài): %t 發(fā)送地址 01!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 地址發(fā)送完成!", $time);
            // 驅(qū)動(dòng) slave 地址,1010_0001,h'50+read
            u0.wb_write(1, TXR, 8'ha1);
            u0.wb_write(0, CR, 8'h90);
            $display("狀態(tài): %t 產(chǎn)生重復(fù)開始位, 讀地址+開始位", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 命令結(jié)束!", $time);
            // 讀數(shù)據(jù)
            u0.wb_write(1, CR, 8'h20);
            $display("狀態(tài): %t 讀+應(yīng)答命令", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 讀結(jié)束!", $time);
            // 檢查讀的內(nèi)容
            u0.wb_read(1, RXR, qq);
            if(qq !== 8'ha5)
                $display("
 錯(cuò)誤: 需要的是 a5, received %x at time %t", qq, $time);
            // 讀下一個(gè)地址內(nèi)容
            u0.wb_write(1, CR, 8'h20);
            $display("狀態(tài): %t 讀+ 應(yīng)答", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 第二個(gè)地址讀結(jié)束!", $time);
            u0.wb_read(1, RXR, qq);
            if(qq !== 8'h5a)
            $display("
 錯(cuò)誤: 需要的是 5a, received %x at time %t", qq, $time);
            // 讀
            u0.wb_write(1, CR, 8'h20);
            $display("狀態(tài): %t 讀 + 應(yīng)答", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 第三個(gè)地址讀完成!", $time);
            u0.wb_read(1, RXR, qq);
            $display("狀態(tài): %t 第三個(gè)地址內(nèi)容是 %x !", $time, qq);
            // 讀
            u0.wb_write(1, CR, 8'h28);
            $display("狀態(tài): %t 讀 + 不應(yīng)答!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 第四個(gè)地址讀完成!", $time);
            u0.wb_read(1, RXR, qq);
            $display("狀態(tài): %t 第四個(gè)地址內(nèi)容為 %x !", $time, qq);
            // 檢查不存在的 slave 地址
            // drive slave address
            u0.wb_write(1, TXR, 8'ha0);
            u0.wb_write(0, CR, 8'h90);
$display("狀態(tài):%t 產(chǎn)生開始位, 發(fā)送命令 a0(slave 地址+寫). 檢查非法地址!",$time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q); // poll it until it is zero
            $display("狀態(tài): %t 命令結(jié)束!", $time);
            // 發(fā)送內(nèi)存地址
            u0.wb_write(1, TXR, 8'h10);
            u0.wb_write(0, CR, 8'h10);
            $display("狀態(tài): %t 發(fā)送 slave 內(nèi)存地址 10!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("狀態(tài): %t 地址發(fā)送完畢!", $time);
            // slave 發(fā)送不應(yīng)答
            $display("狀態(tài): %t 檢查不應(yīng)答位!", $time);
            if(!q[7])
            $display("
 錯(cuò)誤: 需要 NACK, 接收到 ACK
");
            // 從 slave 讀數(shù)據(jù)
            u0.wb_write(1, CR, 8'h40);
            $display("狀態(tài): %t 產(chǎn)生'stop'位", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q); // poll it until it is zero
            $display("狀態(tài): %t 結(jié)束!", $time);
            #25000; // wait 25us
            $display("

 狀態(tài): %t 測(cè)試結(jié)束!", $time);
            $finish;
        end
endmodule

4.4 仿真結(jié)果

在 ModelSim 中可以看到仿真的結(jié)果。如圖 7 所示是發(fā)送開始狀態(tài)并寫地址“a0”時(shí)的圖形,此時(shí)在圖上表示為 SCL 處于高時(shí) SDA 的一個(gè)下降沿,然后是數(shù)據(jù)“1010,0000”。

6cbff8cc-597a-11ed-a3b6-dac502259ad0.png

圖 7 發(fā)送開始信號(hào)并寫地址 a0 如圖 8 所示為發(fā)送數(shù)據(jù)“01”和“a5”時(shí)的圖形,在圖上表示為:數(shù)據(jù)“0000,0001”和“1010,0101”。

6cf1be5c-597a-11ed-a3b6-dac502259ad0.png

圖 8 發(fā)送數(shù)據(jù)“01”和“a5” 如圖 9 所示的是發(fā)送停止?fàn)顟B(tài)信號(hào)和數(shù)據(jù)“5a”時(shí)的圖形,在圖上表示為 SCL 處于高時(shí)SDA 的一個(gè)上升沿,然后是數(shù)據(jù)“0101,1010”。

6d0054c6-597a-11ed-a3b6-dac502259ad0.png

圖 9 發(fā)送停止?fàn)顟B(tài)信號(hào)和數(shù)據(jù)“5a” 仿真程序說明I2C程序符合I2C協(xié)議的時(shí)序和數(shù)據(jù)格式,可以實(shí)現(xiàn)模擬I2C協(xié)議的任務(wù)。

五、總結(jié)

本篇首先說明了I2C協(xié)議相關(guān)的內(nèi)容,介紹協(xié)議基本概念和數(shù)據(jù)傳輸各個(gè)命令的具體含義以及協(xié)議對(duì)時(shí)序的要求。接下來介紹模擬I2C協(xié)議程序的框架,詳細(xì)講解框架中各個(gè)模塊的功能并介紹詳細(xì)代碼。最后通過一個(gè)完成的仿真程序完成對(duì)程序的測(cè)試。I2C在應(yīng)用中有著廣泛的用途,本篇希望通過這個(gè)例子為各位大俠提供一個(gè)可行的解決方案。

審核編輯:彭靜
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)傳輸
    +關(guān)注

    關(guān)注

    9

    文章

    1805

    瀏覽量

    64416
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    6837

    瀏覽量

    88754
  • 協(xié)議
    +關(guān)注

    關(guān)注

    2

    文章

    594

    瀏覽量

    39136

原文標(biāo)題:基于FPGA的模擬 I2C協(xié)議系統(tǒng)設(shè)計(jì)(附代碼)

文章出處:【微信號(hào):HXSLH1010101010,微信公眾號(hào):FPGA技術(shù)江湖】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    關(guān)于數(shù)據(jù)傳輸接口

    單片機(jī)用于數(shù)據(jù)傳輸的外設(shè)有哪些?原諒在下孤陋寡聞,我所知道的有SPI、I2C,不知還有哪些?誰的數(shù)據(jù)傳輸速度最快?
    發(fā)表于 09-02 12:22

    基于 FPGA 的模擬 I2C協(xié)議設(shè)計(jì)使用

    和 SCL 兩條線,依靠 I2C 協(xié)議完成軟件工作。在 I2C 協(xié)議中應(yīng)理解如下的
    發(fā)表于 09-02 19:12

    單片機(jī)的基本概念

    單片機(jī)的基本概念1.1單片機(jī)的組成*由CPU、RAM(隨機(jī)存儲(chǔ)器)、ROM(只讀存儲(chǔ)器)、I/O接口、以及內(nèi)部功能部件組成。1.2單片機(jī)內(nèi)部數(shù)據(jù)傳輸*單片機(jī)內(nèi)部數(shù)據(jù)傳輸通過總線完成,輸
    發(fā)表于 07-21 08:13

    UART協(xié)議數(shù)據(jù)傳輸格式是怎樣的

    什么是UART協(xié)議?UART的工作原理是什么?UART協(xié)議數(shù)據(jù)傳輸格式是怎樣的?
    發(fā)表于 11-02 08:14

    數(shù)據(jù)傳輸介質(zhì)

    2.5  數(shù)據(jù)傳輸介質(zhì)      傳輸介質(zhì)是通信網(wǎng)絡(luò)中連接計(jì)算機(jī)的具體物理設(shè)備和數(shù)據(jù)傳輸物理通路。傳輸介質(zhì)的特性包括物理描述
    發(fā)表于 06-27 21:47 ?0次下載

    HT56R678使用I2C進(jìn)行數(shù)據(jù)傳輸的方法

    HT56R678使用I2C進(jìn)行數(shù)據(jù)傳輸的方法 HT56R678 內(nèi)建有SIM 功能,其中包括了SPI 和I2C 兩種通信接口,本文以HT56R678 為母體,介紹使用I2C 進(jìn)行
    發(fā)表于 03-27 09:25 ?16次下載

    HT56R678使用I2C進(jìn)行數(shù)據(jù)傳輸的方法

    HT56R678使用I2C進(jìn)行數(shù)據(jù)傳輸的方法HT56R678 內(nèi)建有SIM 功能,其中包括了SPI 和I2C 兩種通信接口,本文以HT56R678 為母體,介紹使用I2C 進(jìn)行
    發(fā)表于 03-28 22:39 ?7次下載

    藍(lán)牙基帶數(shù)據(jù)傳輸機(jī)理分析

    摘要:對(duì)藍(lán)牙協(xié)議體系中的基帶數(shù)據(jù)傳輸機(jī)理進(jìn)行分析,為進(jìn)一步對(duì)藍(lán)牙技術(shù)做全面深入的研究和開發(fā)應(yīng)用奠定基礎(chǔ)。在介紹了基本概念的基礎(chǔ)上,重點(diǎn)對(duì)藍(lán)牙設(shè)備
    發(fā)表于 03-11 13:37 ?747次閱讀
    藍(lán)牙基帶<b class='flag-5'>數(shù)據(jù)傳輸</b>機(jī)理分析

    數(shù)據(jù)傳輸速率是什么意思

    數(shù)據(jù)傳輸速率是什么意思 數(shù)據(jù)傳輸速率是通過信道每秒可傳輸的數(shù)字信息量的量度。數(shù)據(jù)傳輸速率也稱為吞吐率。數(shù)據(jù)傳輸速率由很
    發(fā)表于 03-18 14:45 ?4985次閱讀

    tcp_ip 協(xié)議講座:介紹數(shù)據(jù)傳輸

    介紹了tcp協(xié)議數(shù)據(jù)傳輸的問題(交互式數(shù)據(jù)傳輸,批量數(shù)據(jù)傳輸,流量控制,擁塞避免)
    的頭像 發(fā)表于 07-03 11:05 ?3416次閱讀
    tcp_ip <b class='flag-5'>協(xié)議</b>講座:介紹<b class='flag-5'>數(shù)據(jù)傳輸</b>

    4-20mA數(shù)據(jù)傳輸基本概念

    了解4-20mA數(shù)據(jù)傳輸背后的核心概念,這是環(huán)路供電傳感器發(fā)送器器件操作的基礎(chǔ)。
    的頭像 發(fā)表于 05-30 10:04 ?2709次閱讀
    4-20mA<b class='flag-5'>數(shù)據(jù)傳輸</b>的<b class='flag-5'>基本概念</b>

    I2C時(shí)鐘頻率與數(shù)據(jù)傳輸速率的理解

    在IIC協(xié)議中涉及到兩根線:SCL與SDA。SCL就是時(shí)鐘信號(hào)線,SCL線上的頻率就叫時(shí)鐘頻率。在對(duì)IIC總線進(jìn)行學(xué)習(xí)與使用時(shí),經(jīng)常會(huì)聽到設(shè)置IIC的時(shí)鐘頻率為xxxHz,或者設(shè)置IIC的數(shù)據(jù)傳輸
    的頭像 發(fā)表于 04-04 14:03 ?6075次閱讀

    如何實(shí)現(xiàn)MQTT協(xié)議數(shù)據(jù)傳輸?

    如何實(shí)現(xiàn)MQTT協(xié)議數(shù)據(jù)傳輸? 隨著物聯(lián)網(wǎng)技術(shù)的不斷發(fā)展,越來越多的設(shè)備和應(yīng)用需要實(shí)現(xiàn)互聯(lián)互通。而MQTT作為一種輕量級(jí)的發(fā)布/訂閱消息傳輸協(xié)議,在物聯(lián)網(wǎng)領(lǐng)域應(yīng)用廣泛,成為了許多設(shè)備之
    的頭像 發(fā)表于 11-15 17:23 ?1087次閱讀

    DTU的多種協(xié)議,解鎖數(shù)據(jù)傳輸的無限可能

    DTU,即數(shù)據(jù)傳輸單元,是一種在物聯(lián)網(wǎng)(IoT)網(wǎng)絡(luò)中常用的設(shè)備,主要用于在傳感器和智能設(shè)備之間進(jìn)行數(shù)據(jù)傳輸。DTU使用多種協(xié)議來實(shí)現(xiàn)這一目標(biāo),這些協(xié)議不僅提高了
    的頭像 發(fā)表于 03-01 11:00 ?719次閱讀
    DTU的多種<b class='flag-5'>協(xié)議</b>,解鎖<b class='flag-5'>數(shù)據(jù)傳輸</b>的無限可能

    3針M5公頭接口速度和數(shù)據(jù)傳輸效率怎樣

      德索工程師說道在電子設(shè)備和系統(tǒng)的連接與通信中,接口的速度和數(shù)據(jù)傳輸效率是衡量其性能的重要指標(biāo)。3針M5公頭接口作為一種常見的連接標(biāo)準(zhǔn),其速度與數(shù)據(jù)傳輸效率直接影響到設(shè)備的性能和使用體驗(yàn)。本文將對(duì)3針M5公頭接口的速度和數(shù)據(jù)傳輸
    的頭像 發(fā)表于 05-13 17:46 ?313次閱讀
    3針M5公頭接口速度<b class='flag-5'>和數(shù)據(jù)傳輸</b>效率怎樣