按照I2C标准的官方时序

可以看出时序看起来很简单,不过它严格的按照时序要求来传送数据,马虎不得的,特别是起始和停止的条件,起始必须要时钟线SCL为高电平时数据线SDA拉低;而停止时必须要时钟线SCL为高电平时数据线SDA拉高;中间的数据的每一位传送都是必须要求在时钟线SCL为高定平时完成;
Verilog HDL程序采用基于状态机的时序设计实现,I2C速度为100KHz,本人开发板的晶振20Mhz。代码有点长,就截取状态机部分好了
`define DEVICE_WRITE 8'b1010_1010 //the data;
reg[7:0] db_r;
parameter IDLE = 4'd0;
parameter START1 = 4'd1;
parameter DATA = 4'd2;
parameter ACK1 = 4'd3;
parameter STOP1 = 4'd11;
parameter STOP2 = 4'd12;
reg[3:0] cstate;
reg sda_r;
reg sda_link;
reg[3:0] num;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cstate <= IDLE;
sda_r <= 1'b1;
sda_link <= 1'b0; //input
num <= 4'd0;
end
else
case (cstate)
IDLE: begin
sda_link <= 1'b1; //output
sda_r <= 1'b1;
db_r <= `DEVICE_WRITE;
cstate <= START1;
end
START1: begin
if(`SCL_HIG) begin
sda_r <= 1'b0;
cstate <= DATA;
num <= 4'd0;
end
else cstate <= START1;
end
DATA: begin
if(`SCL_LOW) begin
if(num == 4'd8) begin
num <= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0; //(input)
cstate <= ACK1;
end
else begin
cstate <= DATA;
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
end
end
else cstate <= DATA;
end
ACK1: begin
if(`SCL_NEG) begin
cstate <= STOP1;
end
else cstate <= ACK1;
end
STOP1: begin
if(`SCL_LOW) begin
sda_link <= 1'b1;
sda_r <= 1'b0;
cstate <= STOP1;
end
else if(`SCL_HIG) begin
sda_r <= 1'b1;
cstate <= STOP2;
end
else cstate <= STOP1;
end
STOP2: begin
if(`SCL_LOW) sda_r <= 1'b1;
else if(cnt_20ms==20'hffff0) cstate <= IDLE;
else cstate <= STOP2;
end
default: cstate <= IDLE;
endcase
end
assign sda = sda_link ? sda_r:1'bz;
经过综合,给激励源并仿真后截图

可以看出,时序完全正确,还要注意的就是传完8位数据之后读取的响应位ACK;