赞
踩
在时钟(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;
于是有如下波形
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。