0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

I²C协议基本概念和数据传输

FPGA技术江湖 来源:FPGA技术江湖 作者:FPGA技术江湖 2022-11-01 09:18 次阅读

今天给大侠带来基于FPGA模拟I²C协议设计,由于篇幅较长,分三篇。今天带来第三篇,下篇,程序的仿真与测试。话不多说,上货。

之前也有相关文章介绍,这里超链接一下,仅供各位大侠参考。

源码系列:基于FPGA的 IIC 设计(附源工程)

这里也给出前两篇的超链接:

基于 FPGA 的模拟 I²C协议设计(上)

基于 FPGA 的模拟 I²C协议设计(中)

导读

I²C(Inter-Integrated Circuit),其实是I²CBus 简称,中文就是集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”("I-squared-C"),而“I二C”("I-two-C")则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用 I²C 协议已经不需要支付专利费,但制造商仍然需要付费以获取 I²C 从属设备地址。

I²C简单来说,就是一种串行通信协议,I²C的通信协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于I²C协议占用的 IO 资源特别少,连接方便,所以工程中也常选用I²C接口做为不同芯片间的通信协议。I²C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I²C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。

在现代电子系统中,有为数众多的 IC 需要进行相互之间以及与外界的通信。为了简化电路的设计,Philips 公司开发了一种用于内部 IC 控制的简单的双向两线串行总线I²C(Intel-Integrated Circuit bus)。1998 年当推出I²C总线协议 2.0 版本时,I²C协议实际上已经成为一个国际标准。

在进行 FPGA 设计时,经常需要和外围提供I²C接口的芯片通信。例如低功耗的 CMOS 实时时钟/日历芯片 PCF8563、LCD 驱动芯片 PCF8562、并行口扩展芯片 PCF8574、键盘/LED 驱动器 ZLG7290 等都提供I²C接口。因此在 FPGA 中模拟I²C接口已成为 FPGA 开发必要的步骤。

本篇将详细讲解在 FPGA 芯片中使用 VHDL/Verilog HDL 模拟I²C协议,以及编写 TestBench仿真和测试程序的方法。

第三篇内容摘要:本篇会介绍程序的仿真与测试,包括主节点的仿真、从节点的仿真、仿真主程序、仿真结果以及总结等相关内容。

四、程序的仿真与测试

I²C 协议的模拟程序完成后,还需要通过仿真程序对程序的功能进行测试。对本程序的仿真包括 3 个部分:第一部分是主节点的仿真,模拟数据读/写;第二部分是从节点的仿真,模拟数据的接收和应答;第三部分是仿真主程序,负责整个仿真过程的控制。

4.1 主节点的仿真

主节点仿真的内容包括读数据、写数据和比较数据 3 部分,代码如下:

`include "timescale.v"
//模块定义
module wb_master_model(clk, rst, adr, din, dout, cyc, stb, we, sel, ack, err, rty);
    //参数
    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;
    
    // 存储逻辑
    //初始化
    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
        
    // 写数据周期
    task wb_write;
        input delay;
        integer delay;
        input [awidth -1:0] a;
        input [dwidth -1:0] d;
        begin
            // 延迟
            repeat(delay) @(posedge clk);
            // 设置信号值
            #1;
            adr = a;
            dout = d;
            cyc = 1'b1;
            stb = 1'b1;
            we = 1'b1;
            sel = {dwidth/8{1'b1}};
            @(posedge clk);
            // 等待从节点的应答信号
            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
    
    // 读数据周期
    task wb_read;
        input delay;
        integer delay;
        input [awidth -1:0]a;
        output [dwidth -1:0] d;
        begin
            // 延迟
            repeat(delay) @(posedge clk);
            // 设置信号值
            #1;
            adr = a;
            dout = {dwidth{1'bx}};
            cyc = 1'b1;
            stb = 1'b1;
            we = 1'b0;
            sel = {dwidth/8{1'b1}};
            @(posedge clk);
            // 等待从节点应答信号
            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
    
    // 比较数据
    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 从节点的仿真

从节点仿真程序需要模拟从主节点接收数据,并发出应答信号,代码如下:

`include "timescale.v"
//模块定义
module i2c_slave_model (scl, sda);
    // 参数
    // 地址
    parameter I2C_ADR = 7'b001_0000;
    
    // 输入、输出
    input scl;
    inout sda;
    
    // 变量申明
    wire debug = 1'b1;
    reg [7:0] mem [3:0]; // 初始化内存
    reg [7:0] mem_adr; // 内存地址
    reg [7:0] mem_do; // 内存数据输出
    reg sta, d_sta;
    reg sto, d_sto;
    reg [7:0] sr; // 8 位移位寄存器
    reg rw; // 读写方向
    wire my_adr; // 地址
    wire i2c_reset; // RESET 信号
    reg [2:0] bit_cnt;
    wire acc_done; // 传输完成
    reg ld;
    reg sda_o;
    wire sda_dly;
    
    // 状态机的状态定义
    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
        
    // 产生移位寄存器
    always @(posedge scl)
        sr <= #1 {sr[6:0],sda};
        
    //检测到访问地址与从节点一致
    assign my_adr = (sr[7:1] == I2C_ADR);
    
    //产生位寄存器
    always @(posedge scl)
        if(ld)
            bit_cnt <= #1 3'b111;
        else
            bit_cnt <= #1 bit_cnt - 3'h1;
            
    //产生访问结束标志
    assign acc_done = !(|bit_cnt);
    
    // sda 延迟
    assign #1 sda_dly = sda;
    
    //检测到开始状态
    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;
    
    // 检测到停止状态信号
    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;
            
    //产生 I2C 的 RESET 信号
    assign i2c_reset = sta || sto;
    
    // 状态机
    always @(negedge scl or posedge sto)
        if (sto || (sta && !d_sta) )
            begin
                state <= #1 idle; // reset 状态机
                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 状态
                    if (acc_done && my_adr)
                        begin
                            state <= #1 slave_ack;
                            rw <= #1 sr[0];
                            sda_o <= #1 1'b0; // 产生应答信号
                            #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: // 等待内存地址
                        if(acc_done)
                            begin
                                state <= #1 gma_ack;
                                mem_adr <= #1 sr; // 保存内存地址
                                sda_o <= #1 !(sr <= 15); // 收到合法地址信号后发出应答信号
                            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: // 接收数据
                        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
                        
    // 从内存读数据
    always @(posedge scl)
    if(!acc_done && rw)
    mem_do <= #1 {mem_do[6:0], 1'b1};
    
    // 产生三态
    assign sda = sda_o ? 1'bz : 1'b0;
    
    // 检查时序
    wire tst_sto = sto;
    wire tst_sta = sta;
    wire tst_scl = scl;
    
    //指定各个信号的上升沿和下降沿
    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); // 开始状态信号
        $setup(posedge scl, posedge sda &&& scl, normal_tsu_sto); // 停止状态信号
        $setup(posedge tst_sta, posedge tst_scl, normal_sta_sto);
    endspecify
    
endmodule

4.3 仿真主程序

仿真主程序完成主节点数据到从节点的控制,代码如下:

`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 保存状态寄存器内容
    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)接收到的最后一个字节的数据
    parameter TXR = 3'b011; //传输寄存器地址,(7)传输地址时最后一位为读写位,1 为读
    parameter CR = 3'b100; //命令寄存器地址,

//(7)开始|6结束|5读|4写|3应答(作为接收方时,发送应答信号,“0”为应答,“1”为不应答)|2保留位|1保留位|0中断应答位,这八位自动清除
parameterSR=3'b100;//状态寄存器地址,(7)接收应答位(“0”为接收到应答)|6忙位(产生开始信号后变为1,结束信号后变为0)|5仲裁位|4-2保留位|1传输中位(1表示正在传输数据,0表示传输结束)|中断标志位
    parameter TXR_R = 3'b101;
    parameter CR_R = 3'b110;
    
    // 产生时钟信号,一个时间单位为 1ns,周期为 10ns,频率为 100MHz。
    always #5 clk = ~clk;
    
    //连接 master 模拟模块
    wb_master_model #(8, 32) u0 (
            .clk(clk), //时钟
            .rst(rstn), //重起
            .adr(adr), //地址
            .din(dat_i), //输入的数据
            .dout(dat_o), //输出的数据
            .cyc(cyc),
            .stb(stb),
            .we(we),
            .sel(),
            .ack(ack), //应答
            .err(1'b0),
            .rty(1'b0)
        );
    
    //连接 i2c 接口
    i2c_master_top i2c_top (
            //连接到 master 模拟模块部分
            .wb_clk_i(clk), //时钟
            .wb_rst_i(1'b0), //同步重起位
            .arst_i(rstn), //异步重起
            .wb_adr_i(adr[2:0]), //地址输入
            .wb_dat_i(dat_o), //数据输入接口
            .wb_dat_o(dat_i), //数据从接口输出
            .wb_we_i(we), //写使能信号
            .wb_stb_i(stb), //片选信号,应该一直为高
            .wb_cyc_i(cyc),
            .wb_ack_o(ack), //应答信号输出到 master 模拟模块
            .wb_inta_o(inta), //中断信号输出到 master 模拟模块
            
            //输出的 i2c 信号,连接到 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 模拟模块产生 scl 和 sda 的三态缓冲
    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 I2C 接口测试开始!

", $time);
            // 初始值
            clk = 0;
            //重起系统
            rstn = 1'b1; // negate reset
            #2;
            rstn = 1'b0; // assert reset
            repeat(20) @(posedge clk);
            rstn = 1'b1; // negate reset
            $display("状态: %t 完成系统重起!", $time);
            @(posedge clk);
            // 对接口编程
            // 写内部寄存器
            // 分频 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 完成分频寄存器操作!", $time);
            //读分频寄存器内容
            u0.wb_cmp(0, PRER_LO, 8'hc8);
            u0.wb_cmp(0, PRER_HI, 8'h00);
            $display("状态: %t 完成分频寄存器确认操作!", $time);
            //接口使能
            u0.wb_write(1, CTR, 8'h80);
            $display("状态: %t 完成接口使能!", $time);
            // 驱动 slave 地址
            // h'a0=b'1010_0000,地址+写状态,写入的地址为 h'50
            u0.wb_write(1, TXR, 8'ha0);
            //命令内容为 b'1001_0000,产生开始位,并设置写状态
            u0.wb_write(0, CR, 8'h90);
            $display("状态: %t 产生开始位, 然后写命令 a0(地址+写),命令开始!", $time);
            // 检查状态位信息
            // 检查传输是否结束
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(0, SR, q);
            $display("状态: %t 地址驱动写操作完成!", $time);
            // 待写的地址为 h'01
            u0.wb_write(1, TXR, 8'h01);
            // 产生写命令 b'0001_0000
            u0.wb_write(0, CR, 8'h10);
            $display("状态: %t 待写地址为 01,命令开始!", $time);
            // 检查状态位
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(0, SR, q);
            $display("状态: %t 写操作完成!", $time);
            // 写入内容
            u0.wb_write(1, TXR, 8'ha5);
            u0.wb_write(0, CR, 8'h10);
            $display("状态: %t 写入内容为 a5,开始写入过程!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 写 a5 到地址 h'01 中完成!", $time);
            // 写入下一个地址 5a
            u0.wb_write(1, TXR, 8'h5a); // present data
            // 写入并停止
            u0.wb_write(0, CR, 8'h50); // set command (stop, write)
            $display("状态: %t 写 5a 到下一个地址,产生停止位!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q); // poll it until it is zero
            $display("状态: %t 写第二个地址结束!", $time);
            // 读
            // 驱动 slave 地址
            u0.wb_write(1, TXR, 8'ha0);
            u0.wb_write(0, CR, 8'h90);
            $display("状态: %t 产生开始位,写命令 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 slave 地址驱动完成!", $time);
            // 发送地址
            u0.wb_write(1, TXR, 8'h01);
            u0.wb_write(0, CR, 8'h10);
            $display("状态: %t 发送地址 01!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 地址发送完成!", $time);
            // 驱动 slave 地址,1010_0001,h'50+read
            u0.wb_write(1, TXR, 8'ha1);
            u0.wb_write(0, CR, 8'h90);
            $display("状态: %t 产生重复开始位, 读地址+开始位", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 命令结束!", $time);
            // 读数据
            u0.wb_write(1, CR, 8'h20);
            $display("状态: %t 读+应答命令", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 读结束!", $time);
            // 检查读的内容
            u0.wb_read(1, RXR, qq);
            if(qq !== 8'ha5)
                $display("
 错误: 需要的是 a5, received %x at time %t", qq, $time);
            // 读下一个地址内容
            u0.wb_write(1, CR, 8'h20);
            $display("状态: %t 读+ 应答", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 第二个地址读结束!", $time);
            u0.wb_read(1, RXR, qq);
            if(qq !== 8'h5a)
            $display("
 错误: 需要的是 5a, received %x at time %t", qq, $time);
            // 读
            u0.wb_write(1, CR, 8'h20);
            $display("状态: %t 读 + 应答", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 第三个地址读完成!", $time);
            u0.wb_read(1, RXR, qq);
            $display("状态: %t 第三个地址内容是 %x !", $time, qq);
            // 读
            u0.wb_write(1, CR, 8'h28);
            $display("状态: %t 读 + 不应答!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 第四个地址读完成!", $time);
            u0.wb_read(1, RXR, qq);
            $display("状态: %t 第四个地址内容为 %x !", $time, qq);
            // 检查不存在的 slave 地址
            // drive slave address
            u0.wb_write(1, TXR, 8'ha0);
            u0.wb_write(0, CR, 8'h90);
$display("状态:%t 产生开始位, 发送命令 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 命令结束!", $time);
            // 发送内存地址
            u0.wb_write(1, TXR, 8'h10);
            u0.wb_write(0, CR, 8'h10);
            $display("状态: %t 发送 slave 内存地址 10!", $time);
            u0.wb_read(1, SR, q);
            while(q[1])
                u0.wb_read(1, SR, q);
            $display("状态: %t 地址发送完毕!", $time);
            // slave 发送不应答
            $display("状态: %t 检查不应答位!", $time);
            if(!q[7])
            $display("
 错误: 需要 NACK, 接收到 ACK
");
            // 从 slave 读数据
            u0.wb_write(1, CR, 8'h40);
            $display("状态: %t 产生'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 结束!", $time);
            #25000; // wait 25us
            $display("

 状态: %t 测试结束!", $time);
            $finish;
        end
endmodule

4.4 仿真结果

在 ModelSim 中可以看到仿真的结果。如图 7 所示是发送开始状态并写地址“a0”时的图形,此时在图上表示为 SCL 处于高时 SDA 的一个下降沿,然后是数据“1010,0000”。

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

图 7 发送开始信号并写地址 a0 如图 8 所示为发送数据“01”和“a5”时的图形,在图上表示为:数据“0000,0001”和“1010,0101”。

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

图 8 发送数据“01”和“a5” 如图 9 所示的是发送停止状态信号和数据“5a”时的图形,在图上表示为 SCL 处于高时SDA 的一个上升沿,然后是数据“0101,1010”。

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

图 9 发送停止状态信号和数据“5a” 仿真程序说明I²C程序符合I²C协议的时序和数据格式,可以实现模拟I²C协议的任务。

五、总结

本篇首先说明了I²C协议相关的内容,介绍协议基本概念和数据传输各个命令的具体含义以及协议对时序的要求。接下来介绍模拟I²C协议程序的框架,详细讲解框架中各个模块的功能并介绍详细代码。最后通过一个完成的仿真程序完成对程序的测试。I²C在应用中有着广泛的用途,本篇希望通过这个例子为各位大侠提供一个可行的解决方案。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据传输
    +关注

    关注

    9

    文章

    1810

    浏览量

    64433
  • 数据
    +关注

    关注

    8

    文章

    6853

    浏览量

    88765
  • 协议
    +关注

    关注

    2

    文章

    597

    浏览量

    39139

原文标题:基于FPGA的模拟 I²C协议系统设计(附代码)

文章出处:【微信号:HXSLH1010101010,微信公众号:FPGA技术江湖】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    关于数据传输接口

    单片机用于数据传输的外设有哪些?原谅在下孤陋寡闻,我所知道的有SPI、I2C,不知还有哪些?谁的数据传输速度最快?
    发表于 09-02 12:22

    基于 FPGA 的模拟 I²C协议设计使用

    和 SCL 两条线,依靠 I²C 协议完成软件工作。在 I²C 协议中应理解如下的
    发表于 09-02 19:12

    单片机的基本概念

    单片机的基本概念1.1单片机的组成*由CPU、RAM(随机存储器)、ROM(只读存储器)、I/O接口、以及内部功能部件组成。1.2单片机内部数据传输*单片机内部数据传输通过总线完成,输
    发表于 07-21 08:13

    UART协议数据传输格式是怎样的

    什么是UART协议?UART的工作原理是什么?UART协议数据传输格式是怎样的?
    发表于 11-02 08:14

    数据传输介质

    2.5  数据传输介质      传输介质是通信网络中连接计算机的具体物理设备和数据传输物理通路。传输介质的特性包括物理描述
    发表于 06-27 21:47 0次下载

    HT56R678使用I2C进行数据传输的方法

    HT56R678使用I2C进行数据传输的方法 HT56R678 内建有SIM 功能,其中包括了SPI 和I2C 两种通信接口,本文以HT56R678 为母体,介绍使用I2C 进行
    发表于 03-27 09:25 16次下载

    HT56R678使用I2C进行数据传输的方法

    HT56R678使用I2C进行数据传输的方法HT56R678 内建有SIM 功能,其中包括了SPI 和I2C 两种通信接口,本文以HT56R678 为母体,介绍使用I2C 进行
    发表于 03-28 22:39 7次下载

    蓝牙基带数据传输机理分析

    摘要:对蓝牙协议体系中的基带数据传输机理进行分析,为进一步对蓝牙技术做全面深入的研究和开发应用奠定基础。在介绍了基本概念的基础上,重点对蓝牙设备
    发表于 03-11 13:37 749次阅读
    蓝牙基带<b class='flag-5'>数据传输</b>机理分析

    数据传输速率是什么意思

    数据传输速率是什么意思 数据传输速率是通过信道每秒可传输的数字信息量的量度。数据传输速率也称为吞吐率。数据传输速率由很
    发表于 03-18 14:45 4985次阅读

    tcp_ip 协议讲座:介绍数据传输

    介绍了tcp协议数据传输的问题(交互式数据传输,批量数据传输,流量控制,拥塞避免)
    的头像 发表于 07-03 11:05 3418次阅读
    tcp_ip <b class='flag-5'>协议</b>讲座:介绍<b class='flag-5'>数据传输</b>

    4-20mA数据传输基本概念

    了解4-20mA数据传输背后的核心概念,这是环路供电传感器发送器器件操作的基础。
    的头像 发表于 05-30 10:04 2709次阅读
    4-20mA<b class='flag-5'>数据传输</b>的<b class='flag-5'>基本概念</b>

    I2C时钟频率与数据传输速率的理解

    在IIC协议中涉及到两根线:SCL与SDA。SCL就是时钟信号线,SCL线上的频率就叫时钟频率。在对IIC总线进行学习与使用时,经常会听到设置IIC的时钟频率为xxxHz,或者设置IIC的数据传输
    的头像 发表于 04-04 14:03 6119次阅读

    如何实现MQTT协议数据传输

    如何实现MQTT协议数据传输? 随着物联网技术的不断发展,越来越多的设备和应用需要实现互联互通。而MQTT作为一种轻量级的发布/订阅消息传输协议,在物联网领域应用广泛,成为了许多设备之
    的头像 发表于 11-15 17:23 1096次阅读

    DTU的多种协议,解锁数据传输的无限可能

    DTU,即数据传输单元,是一种在物联网(IoT)网络中常用的设备,主要用于在传感器和智能设备之间进行数据传输。DTU使用多种协议来实现这一目标,这些协议不仅提高了
    的头像 发表于 03-01 11:00 723次阅读
    DTU的多种<b class='flag-5'>协议</b>,解锁<b class='flag-5'>数据传输</b>的无限可能

    3针M5公头接口速度和数据传输效率怎样

      德索工程师说道在电子设备和系统的连接与通信中,接口的速度和数据传输效率是衡量其性能的重要指标。3针M5公头接口作为一种常见的连接标准,其速度与数据传输效率直接影响到设备的性能和使用体验。本文将对3针M5公头接口的速度和数据传输
    的头像 发表于 05-13 17:46 319次阅读
    3针M5公头接口速度<b class='flag-5'>和数据传输</b>效率怎样