在时钟(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) beginif (~rst_n) beginclk_cnt <= 16'd0;scl_clk <= 1'd0;end else beginif (clk_cnt == (DIV_CLK >> 1) - 1) beginclk_cnt <= 16'd0;scl_clk = ~scl_clk;end else beginclk_cnt <= clk_cnt + 16'd1;endendendlocalparam 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) beginif (~rst_n) beginisack <= 0;busy <= 0;rddata <= 0;end else begincase (state)ST_IDLE: beginsda_r <= 1;if (enable) beginbusy <= 1;endif (busy && clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginstate <= ST_START;endend// 产生开始信号, 即在 scl_clk 为高电平时 sda 由高变低ST_START: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginsda_r <= 0;state <= ST_WR_DATA;byte_no <= wrlen;bit_no <= 4'd0;isack <= 0;endend// 写入数据, wrdata 的最高位字节是器件的 i2c 地址以及读写标志ST_WR_DATA: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginif (bit_no == 4'd8) beginstate <= ST_WR_READ_ACK;byte_no <= byte_no - 1'b1;bit_no <= 4'd0;// 这里切换 sda 为读模式以便在下一个 scl_clk 高电平时接收应答sda_oe <= 0;end else beginbit_no <= bit_no + 1'b1;sda_r <= wrdata[byte_no*8-1-bit_no-:1];sda_oe <= 1;endendend// scl_clk 为高时读取应答信号ST_WR_READ_ACK: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginif (sda) isack <= 1;state <= ST_WR_CHECK_ACK;endend// scl_clk 为低时检测应答信号并做出状态转移ST_WR_CHECK_ACK: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin// 数据还没写完则继续进入写数据状态if (isack && byte_no != 0) beginstate <= 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) beginstate <= ST_RD_START;sda_oe <= 1;sda_r <= 1;// 没有应答或数据写完了且不需要读模式则停止传输end else beginstate <= ST_STOP;sda_r <= 0;sda_oe <= 0;endendend// 在 scl_clk 为高时将 sda 输出为低进入读状态ST_RD_START: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginsda_r <= 0;// 同时在 scl_clk 为低时将 sda 切到读模式, 以便下一个 scl_clk 为高时将 sda 读取end else if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginsda_oe <= 0;state <= ST_RD_DATA;bit_no <= 0;byte_no <= rdlen;endend// 在 scl_clk 为高时读取 sda 的数据ST_RD_DATA: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginbit_no <= bit_no + 1'b1;rddata[byte_no*8-1-bit_no-:1] <= sda;if (bit_no == 4'd7) beginstate <= ST_RD_ACK_REPLY;byte_no <= byte_no - 1'b1;bit_no <= 4'd0;endendend// 在 scl_clk 为低时读取产生应答信号, 以便在下一个高电平被对方采集到ST_RD_ACK_REPLY: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginsda_oe = 1;sda_r <= 0;state <= ST_RD_ACK_DONE;endend// 经过了一个高电平, 在该低电平处判断数据有没有传完以进行状态转移ST_RD_ACK_DONE: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginif (byte_no == 0) beginsda_oe = 1;state <= ST_STOP;end else beginsda_oe = 0;state <= ST_RD_DATA;endendend// 产生停止信号, 即在 scl_clk 为高电平时将 sda 由低变高ST_STOP: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginsda_oe <= 1;sda_r <= 1;state <= ST_IDLE;busy <= 0;endendendcaseendendendmodule
仿真文件 tb.v
`timescale 1ns / 1nsmodule tb;// -------------------------------------------------------------------------// clk and reset signal // -------------------------------------------------------------------------reg clk;reg rst_n;initial clk = 0;always #10 clk = ~clk;initial beginrst_n = 0;#100;rst_n = 1;endinitial begin$dumpvars;#5000000;$finish;endreg enable;wire busy;initial beginenable = 0;@(posedge rst_n);enable = 1;@(negedge busy);enable = 0;#10000;enable = 1;@(negedge busy);enable = 0;#10000;endwire [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) 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;
于是有如下波形