当前位置:   article > 正文

Verilog 实现 i2c 协议

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221

仿真文件 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

通常直接使用上面的 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; 于是有如下波形

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/599659
推荐阅读
相关标签
  

闽ICP备14008679号