Verilog 实现 i2c 协议

在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,所以数据总线(SDA)在时钟(SCL)为低电平的时候才能改变。

在时钟(SCL)为高电平的时候,数据总线(SDA)由高到低的跳变为总线起始信号,在时钟(SCL)为高电平的时候,数据总线(SDA)由低到高的跳变为总线停止信号。

应答,当 IIC 主机(不一定是发送端还是接受端)将 8 位数据或命令传出后,会将数据总线(SDA)释放,即设置为输入,然后等待从机应答(低电平 0 表示应答,1 表示非应答),此时的时钟仍然是主机提供的。

数据帧格式,I2C 器件通讯的时候首先是要发送“起始信号”,紧跟着就是七位器件地址,第八位是数据传送方向位(0:代表写,1:代表读),再后面就是等待从机的应答。当然传送结束后,“终止信号”也是由主机来产生的。发送数据的时候是高位先发送。


module iic #(
    parameter DIV_CLK = 100,
    parameter WR_MAX  = 8'd1,
    parameter RD_MAX  = 8'd1
) (
    input clk,
    input rst_n,

    input enable,
    output reg busy,

    input [7:0] rdlen,
    input [7:0] wrlen,

    output reg [RD_MAX*8-1:0] rddata,
    input [WR_MAX*8-1:0] wrdata,

    output reg isack,

    output scl,
    inout  sda
);

    // -------------------------------------------------------------------------
    //                           信号分频
    // -------------------------------------------------------------------------
    reg [$clog2(DIV_CLK):0] clk_cnt;
    reg scl_clk;
    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            clk_cnt <= 16'd0;
            scl_clk <= 1'd0;
        end else begin
            if (clk_cnt == (DIV_CLK >> 1) - 1) begin
                clk_cnt <= 16'd0;
                scl_clk = ~scl_clk;
            end else begin
                clk_cnt <= clk_cnt + 16'd1;
            end

        end
    end

    localparam  ST_IDLE = 0, 
                ST_START = 1, 
                ST_WR_DATA = 2, 
                ST_WR_READ_ACK = 3, 
                ST_WR_CHECK_ACK = 4, 
                ST_RD_START = 5, 
                ST_RD_DATA = 6, 
                ST_RD_ACK_REPLY = 7, 
                ST_RD_ACK_DONE = 8, 
                ST_STOP = 9;

    reg [3:0] state = ST_IDLE;
    assign scl = (state == ST_IDLE || state == ST_START) ? 1 : scl_clk;

    // SDA 输入输出切换
    reg sda_r = 1;
    reg sda_oe = 1;
    assign sda = sda_oe ? sda_r : 1'bz;

    // 写入和读取数据时使用的中间变量
    reg [7:0] byte_no = 0;
    reg [3:0] bit_no = 0;

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            isack <= 0;
            busy <= 0;
            rddata <= 0;
        end else begin

            case (state)
                ST_IDLE: begin
                    sda_r <= 1;

                    if (enable) begin
                        busy <= 1;
                    end

                    if (busy && clk_cnt == DIV_CLK >> 2 && scl_clk == 1) begin
                        state <= ST_START;
                    end

                end

                // 产生开始信号, 即在 scl_clk 为高电平时 sda 由高变低
                ST_START: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) begin
                        sda_r <= 0;
                        state <= ST_WR_DATA;
                        byte_no <= wrlen;
                        bit_no <= 4'd0;
                        isack <= 0;
                    end
                end

                // 写入数据, wrdata 的最高位字节是器件的 i2c 地址以及读写标志
                ST_WR_DATA: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin

                        if (bit_no == 4'd8) begin
                            state   <= ST_WR_READ_ACK;
                            byte_no <= byte_no - 1'b1;
                            bit_no  <= 4'd0;
                            // 这里切换 sda 为读模式以便在下一个 scl_clk 高电平时接收应答
                            sda_oe  <= 0;
                        end else begin
                            bit_no <= bit_no + 1'b1;
                            sda_r  <= wrdata[byte_no*8-1-bit_no-:1];
                            sda_oe <= 1;
                        end
                    end
                end

                // scl_clk 为高时读取应答信号
                ST_WR_READ_ACK: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) begin

                        if (sda == 0) isack <= 1;

                        state <= ST_WR_CHECK_ACK;
                    end
                end

                // scl_clk 为低时检测应答信号并做出状态转移
                ST_WR_CHECK_ACK: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin

                        // 数据还没写完则继续进入写数据状态
                        if (isack && byte_no != 0) begin
                            state <= ST_WR_DATA;
                            bit_no <= bit_no + 1'b1;
                            sda_r <= wrdata[byte_no*8-1-bit_no-:1];
                            sda_oe <= 1;
                            isack <= 0;
                        // 否则进入读状态
                        end else if (isack && rdlen > 0) begin
                            state  <= ST_RD_START;
                            sda_oe <= 1;
                            sda_r  <= 1;
                        // 没有应答或数据写完了且不需要读模式则停止传输
                        end else begin
                            state  <= ST_STOP;
                            sda_r  <= 0;
                            sda_oe <= 0;
                        end

                    end
                end

                // 在 scl_clk 为高时将 sda 输出为低进入读状态
                ST_RD_START: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) begin
                        sda_r <= 0;

                        // 同时在 scl_clk 为低时将 sda 切到读模式, 以便下一个 scl_clk 为高时将 sda 读取
                    end else if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin
                        sda_oe  <= 0;
                        state   <= ST_RD_DATA;
                        bit_no  <= 0;
                        byte_no <= rdlen;
                    end
                end

                // 在 scl_clk 为高时读取 sda 的数据
                ST_RD_DATA: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) begin

                        bit_no <= bit_no + 1'b1;
                        rddata[byte_no*8-1-bit_no-:1] <= sda;

                        if (bit_no == 4'd7) begin
                            state   <= ST_RD_ACK_REPLY;
                            byte_no <= byte_no - 1'b1;
                            bit_no  <= 4'd0;
                        end

                    end
                end

                // 在 scl_clk 为低时读取产生应答信号, 以便在下一个高电平被对方采集到
                ST_RD_ACK_REPLY: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin
                        sda_oe = 1;
                        sda_r <= 0;
                        state <= ST_RD_ACK_DONE;
                    end
                end

                // 经过了一个高电平, 在该低电平处判断数据有没有传完以进行状态转移
                ST_RD_ACK_DONE: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin

                        if (byte_no == 0) begin
                            sda_oe = 1;
                            state <= ST_STOP;
                        end else begin
                            sda_oe = 0;
                            state <= ST_RD_DATA;
                        end
                    end
                end

                // 产生停止信号, 即在 scl_clk 为高电平时将 sda 由低变高
                ST_STOP: begin
                    if (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) begin
                        sda_oe <= 1;
                        sda_r  <= 1;
                        state  <= ST_IDLE;
                        busy   <= 0;
                    end
                end

            endcase
        end
    end

endmodule

仿真文件 tb.v

`timescale 1ns / 1ns

module tb;

    // -------------------------------------------------------------------------
    //                    clk and reset signal 
    // -------------------------------------------------------------------------
    reg clk;
    reg rst_n;
    initial clk = 0;
    always #10 clk = ~clk;

    initial begin
        rst_n = 0;
        #100;
        rst_n = 1;
    end

    initial begin
        $dumpvars;
        #5000000;
        $finish;
    end

    reg enable;
    wire busy;

    initial begin
        enable = 0;
        @(posedge rst_n);

        enable = 1;
        @(negedge busy);
        enable = 0;
        #10000;

        enable = 1;
        @(negedge busy);
        enable = 0;
        #10000;
    end

    wire [15:0] rddata;
    wire isack;

    iic #(
        .DIV_CLK(100),
        .WR_MAX (8'd2),
        .RD_MAX (8'd2)
    ) u_iic (
        .clk   (clk),
        .rst_n (rst_n),

        .enable(enable),
        .busy  (busy),

        .rdlen (8'd2),
        .wrlen (8'd2),
        .rddata(rddata),
        .wrdata(16'hd552),

        .isack (isack),
        .scl   (scl),
        .sda   (sda)
    );

endmodule

通常直接使用上面的 testbench 由于没有接实际的设备,没有收到应答后 iic 模块会自动停止传输,并拉低 busy 标志, 同时 isack 也为 0

为了方便查看波形,以下是将 iic.v 的 if (sda == 0) isack <= 1; 改为 if (1) isack <= 1; 即在 读取 ack 信号时, 总是假设能读到

并且 将读信号 rddata[byte_no*8-1-bit_no-:1] <= sda; 总是写读到 1,rddata[byte_no*8-1-bit_no-:1] <= 1; 于是有如下波形

在这里插入图片描述

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: I2C(Inter-Integrated Circuit)是一种串行通信协议,用于连接微处理器和外部器件。在Verilog实现I2C协议可以通过设计I2C主机模块和从机模块来完成。 在Verilog实现I2C主机模块,首先需要定义输入和输出端口。输入端口包括时钟信号(clk)、复位信号(rst)以及数据线(sda)和时钟线(scl)。输出端口包括数据输出(sda_out)、使能信号(sda_oe)和应答信号(ack)。 然后,在主机模块中,需要定义一些状态变量,以控制I2C通信过程。例如,需要定义一些状态如IDLE、START、READ、WRITE等,通过状态机的方式来控制通信的不同阶段。 接下来,根据I2C协议的要求,通过组合逻辑和状态机,实现I2C主机模块的相关功能,例如生成START和STOP信号、发送和接收数据等。在发送数据时,需要将数据位和时钟位同时发送给从机模块。在接收数据时,通过监测从机的应答信号,判断数据是否成功接收。 在Verilog实现I2C从机模块,也需要定义输入和输出端口,包括时钟信号(clk)、复位信号(rst)、数据线(sda)和时钟线(scl)。输出端口包括数据输出(sda_out)、使能信号(sda_oe)和应答信号(ack)。 在从机模块中,同样需要定义一些状态变量和状态机,以控制I2C通信的不同阶段。通过组合逻辑和状态机,实现从机模块的相关功能,例如接收和发送数据,并根据主机的控制信号进行相应的应答。 在Verilog实现I2C协议,需要根据具体的设计需求,完成主机模块和从机模块的设计和编码。最后,可以通过仿真和验证,确保I2C协议Verilog中的实现是正确和可靠的。 总之,通过Verilog实现I2C协议可以实现微处理器和外部器件之间的串行通信。通过设计主机模块和从机模块,并根据I2C协议的要求,实现相关的功能和时序控制,最终可以完成I2C协议Verilog实现。 ### 回答2: I2C(Inter-Integrated Circuit,即集成电路之间的串行通信总线)是一种常用的通信协议,用于在不同的集成电路之间进行串行数据传输。要实现I2C协议Verilog代码,需要以下步骤: 1. 将Verilog代码中的信号和端口与I2C协议的时序要求相匹配。I2C协议有两条线路,即数据线(SDA)和时钟线(SCL),因此需要在代码中定义这两个信号。 2. 实现I2C协议的起始和停止条件。起始条件是在时钟线为高电平时,数据线从高电平跳变至低电平。停止条件是在时钟线为高电平时,数据线从低电平跳变至高电平。 3. 实现I2C协议的数据传输。数据传输是在时钟线为高电平时进行的,每个数据位的传输都需要保持稳定直到时钟线变为低电平。数据传输是以字节为单位进行的,每个字节包含8个数据位。 4. 实现I2C协议的应答机制。在I2C协议中,接收方需在每个字节的传输结束后发送一个应答位,表示是否接收到数据。如果接收到数据,应答位为低电平;如果未接收到数据,应答位为高电平。 5. 添加其他功能。根据具体需求,还可以实现其他附加功能,如地址匹配、多主设备和从设备的切换等。 总之,要实现I2C协议Verilog代码,需要考虑时序要求、起始和停止条件、数据传输、应答机制以及其他附加功能。通过正确的时序和数据的传输,可以实现I2C协议Verilog代码中的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值