搞懂SPI时序后再来跟着小梅哥学习I2C时序,想要详细理解的可以看下http://training.eeworld.com.cn/video/22863。I2C时序也花了一两天的时间才从搞懂时序到看懂代码。这里记录一下学习过程中的迷惑。
I2C总线是由两根线组成:一根SCL,一根SDA,可以实现少量数据的读写,例如对某一外设进行配置,这种情况是对该外设某些寄存器进行配置。不宜进行大量数据的读写。
I2C时序的特点:在SCL位高电平时对数据采样,低电平时数据发生变化。
I2C的读写形式是多种多样的:
1)1个地址1字节的数据写时序;START可参考图1。控制位高四位位1010,第0位是控制I2C读或写的,为1时读,为0时写。ACK信号为应答位,可以理解为收到传输的8bit数据后从机反馈给主机的信号,告诉主机已收到主机发送的信号。1个字节的地址位,1个字节的数据位,在主机发送1个字节的数据后从机都会反馈给主机一个讯息。最后有一位停止位,时主机发给从机的,时序可参考图1.
I2C的难处在于它的灵活,当从机的地址位数超过8位时需要调整地址位的宽度,当数据位超过1个自己时也要调整数据位的宽度。如下图:从机的寄存器地址位宽大于256,需要更大的地址来寻址,所以调整为2字节的地址为。同意,在从机接收到信息后都会反馈给主机一个ACK信号,告诉主机信号接收成功。
同样还有多字节的数据,如下图所示:
以上是对从机进行写数据,下面来介绍从从机中读数据:
先来接收1字节地址和1字节数据的情况。同样具有开始位、结束位和应答位,此时的应答位是由主机向从机发出的。主机在发送控制位和要从什么寄存器中读取数据之后发出一个ACK,此时没有停止位,而是发送控制位,告诉从机此时要读该地址的数据了。读完从机的1字节数据后任然有一位空余,不过不在关心该空余位的状态,之后再产生停止位。在读的过程中需要两次发送控制位,第一次发送写信号,告诉从机,我要读这个地址的数据。在发送控制位,告诉从机可以开始读了。
当然I2C总线还可以读取地址大于1个字节的多字节数据。
经过以上分析,可以写代码了,首先我们可能想到的是序列机,当然序列机可以实现某一特定情况下的I2C功能,但是如使用序列机则不宜移植,功能单一。那么就需要总结以上都有什么样的共同点。
1、开始位
2、8位数据位(控制位(读或写)、数据为、地址位)
3、ACK(产生或检查,含NOACK)
4、停止位
这么多状态,应该没有漏掉的吧。
所以使用状态机的可移植性比序列及的可移植性要强。
下面给出小梅哥的代码:
module i2c_bit_shift(
Clk,
Rst_n,
Cmd,
Go,
Rx_DATA,
Tx_DATA,
Trans_Done,
ack_o,
i2c_sclk,
i2c_sdat
);
input Clk;
input Rst_n;
input [5:0]Cmd;
input Go;
output reg[7:0]Rx_DATA;
input [7:0]Tx_DATA;
output reg Trans_Done;
output reg ack_o;
output reg i2c_sclk;
inout i2c_sdat;
reg i2c_sdat_o;
//系统时钟采用50MHz
parameter SYS_CLOCK = 50_000_000;
//SCL总线时钟采用400kHz
parameter SCL_CLOCK = 400_000;
//产生时钟SCL计数器最大值
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1;
reg i2c_sdat_oe;
localparam
WR = 6'b000001, // 写请求
STA = 6'b000010, //起始位请求
RD = 6'b000100, //读请求
STO = 6'b001000, //停止位请求
ACK = 6'b010000, //应答位请求
NACK = 6'b100000; //无应答请求
reg [19:0]div_cnt;
reg en_div_cnt;
//计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 20'd0;
else if(en_div_cnt)begin
if(div_cnt < SCL_CNT_M)
div_cnt <= div_cnt + 1'b1;
else
div_cnt <= 0;
end
else
div_cnt <= 0;
wire sclk_plus = div_cnt == SCL_CNT_M;
assign i2c_sdat = i2c_sdat_oe?i2c_sdat_o:1'bz;
reg [7:0]state;
localparam
IDLE = 8'b00000001,
GEN_STA = 8'b00000010,
WR_DATA = 8'b00000100,
RD_DATA = 8'b00001000,
CHECK_ACK = 8'b00010000,
GEN_ACK = 8'b00100000,
GEN_STO = 8'b01000000;
reg [4:0]cnt;
//状态机
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
Rx_DATA <= 0;
i2c_sdat_oe <= 1'd0;
en_div_cnt <= 1'b0;
i2c_sdat_o <= 1'd1;
Trans_Done <= 1'b0;
ack_o <= 0;
state <= IDLE;
cnt <= 0;
end
else begin
case(state)
IDLE:
begin
Trans_Done <= 1'b0;
i2c_sdat_oe <= 1'd1;
if(Go)begin
en_div_cnt <= 1'b1;
if(Cmd & STA)
state <= GEN_STA;
else if(Cmd & WR)
state <= WR_DATA;
else if(Cmd & RD)
state <= RD_DATA;
else
state <= IDLE;
end
else begin
en_div_cnt <= 1'b0;
state <= IDLE;
end
end
GEN_STA:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)//序列机
0:begin i2c_sdat_o <= 1; i2c_sdat_oe <= 1'd1;end
1:begin i2c_sclk <= 1;end
2:begin i2c_sdat_o <= 0; i2c_sclk <= 1;end
3:begin i2c_sclk <= 0;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
if(Cmd & WR)
state <= WR_DATA;
else if(Cmd & RD)
state <= RD_DATA;
end
end
end
WR_DATA:
begin
if(sclk_plus)begin
if(cnt == 31)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0,4,8,12,16,20,24,28:begin i2c_sdat_o <= Tx_DATA[7-cnt[4:2]]; i2c_sdat_oe <= 1'd1;end //set data;
1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end //sclk posedge
2,6,10,14,18,22,26,30:begin i2c_sclk <= 1;end //sclk keep high
3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end //sclk negedge
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 31)begin
state <= CHECK_ACK;
end
end
end
RD_DATA:
begin
if(sclk_plus)begin
if(cnt == 31)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0,4,8,12,16,20,24,28:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end //set data;
1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end //sclk posedge
2,6,10,14,18,22,26,30:begin i2c_sclk <= 1; Rx_DATA <= {Rx_DATA[6:0],i2c_sdat};end //sclk keep high
3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end //sclk negedge
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 31)begin
state <= GEN_ACK;
end
end
end
CHECK_ACK:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end
1:begin i2c_sclk <= 1;end
2:begin ack_o <= i2c_sdat; i2c_sclk <= 1;end
3:begin i2c_sclk <= 0;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
end
end
GEN_ACK:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin
i2c_sdat_oe <= 1'd1;
i2c_sclk <= 0;
if(Cmd & ACK)
i2c_sdat_o <= 1'b0;
else if(Cmd & NACK)
i2c_sdat_o <= 1'b1;
end
1:begin i2c_sclk <= 1;end
2:begin i2c_sclk <= 1;end
3:begin i2c_sclk <= 0;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
if(Cmd & STO)
state <= GEN_STO;
else begin
state <= IDLE;
Trans_Done <= 1'b1;
end
end
end
end
GEN_STO:
begin
if(sclk_plus)begin
if(cnt == 3)
cnt <= 0;
else
cnt <= cnt + 1'b1;
case(cnt)
0:begin i2c_sdat_o <= 0; i2c_sdat_oe <= 1'd1;end
1:begin i2c_sclk <= 1;end
2:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
3:begin i2c_sclk <= 1;end
default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
endcase
if(cnt == 3)begin
Trans_Done <= 1'b1;
state <= IDLE;
end
end
end
default:state <= IDLE;
endcase
end
endmodule
module i2c_control(
Clk,
Rst_n,
wrreg_req,
rdreg_req,
addr,
addr_mode,
wrdata,
rddata,
device_id,
RW_Done,
ack,
i2c_sclk,
i2c_sdat
);
input Clk;
input Rst_n;
input wrreg_req;//可写
input rdreg_req;//可读
input [15:0]addr;
input addr_mode;//地址发送模式,先发高位或先发低位
input [7:0]wrdata;
output reg[7:0]rddata;
input [7:0]device_id;
output reg RW_Done;
output reg ack;
output i2c_sclk;
inout i2c_sdat;
reg [5:0]Cmd;
reg [7:0]Tx_DATA;
wire Trans_Done;
wire ack_o;
reg Go;
wire [15:0] reg_addr;
assign reg_addr = addr_mode?addr:{addr[7:0],addr[15:8]};
wire [7:0]Rx_DATA;
localparam
WR = 6'b000001, // 写请求
STA = 6'b000010, //起始位请求
RD = 6'b000100, //读请求
STO = 6'b001000, //停止位请求
ACK = 6'b010000, //应答位请求
NACK = 6'b100000; //无应答请求
i2c_bit_shift i2c_bit_shift(
.Clk(Clk),
.Rst_n(Rst_n),
.Cmd(Cmd),
.Go(Go),
.Rx_DATA(Rx_DATA),
.Tx_DATA(Tx_DATA),
.Trans_Done(Trans_Done),
.ack_o(ack_o),
.i2c_sclk(i2c_sclk),
.i2c_sdat(i2c_sdat)
);
reg [6:0]state;
reg [7:0]cnt;
localparam
IDLE = 7'b0000001,
WR_REG = 7'b0000010,
WAIT_WR_DONE = 7'b0000100,
WR_REG_DONE = 7'b0001000,
RD_REG = 7'b0010000,
WAIT_RD_DONE = 7'b0100000,
RD_REG_DONE = 7'b1000000;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
Cmd <= 6'd0;
Tx_DATA <= 8'd0;
Go <= 1'b0;
rddata <= 0;
state <= IDLE;
ack <= 0;
end
else begin
case(state)
IDLE:
begin
cnt <= 0;
ack <= 0;
RW_Done <= 1'b0;
if(wrreg_req)
state <= WR_REG;
else if(rdreg_req)
state <= RD_REG;
else
state <= IDLE;
end
WR_REG:
begin
state <= WAIT_WR_DONE;
case(cnt)
0:write_byte(WR | STA, device_id);//cmd=000001|000010=000011
1:write_byte(WR, reg_addr[15:8]);//cmd=000001
2:write_byte(WR, reg_addr[7:0]);//cmd=000001
3:write_byte(WR | STO, wrdata);//cmd=000001|001000=001001
default:;
endcase
end
WAIT_WR_DONE:
begin
Go <= 1'b0;
if(Trans_Done)begin
ack <= ack | ack_o;
case(cnt)
0: begin cnt <= 1; state <= WR_REG;end
1:
begin
state <= WR_REG;
if(addr_mode)
cnt <= 2;
else
cnt <= 3;
end
2: begin
cnt <= 3;
state <= WR_REG;
end
3:state <= WR_REG_DONE;
default:state <= IDLE;
endcase
end
end
WR_REG_DONE:
begin
RW_Done <= 1'b1;
state <= IDLE;
end
RD_REG:
begin
state <= WAIT_RD_DONE;
case(cnt)
0:write_byte(WR | STA, device_id);
1:if(addr_mode)
write_byte(WR, reg_addr[15:8]);
else
write_byte(WR | STO, reg_addr[15:8]);
2:write_byte(WR | STO, reg_addr[7:0]);
3:write_byte(WR | STA, device_id | 8'd1);
4:read_byte(RD | ACK | STO);
default:;
endcase
end
WAIT_RD_DONE:
begin
Go <= 1'b0;
if(Trans_Done)begin
if(cnt <= 3)
ack <= ack | ack_o;
case(cnt)
0: begin cnt <= 1; state <= RD_REG;end
1:
begin
state <= RD_REG;
if(addr_mode)
cnt <= 2;
else
cnt <= 3;
end
2: begin
cnt <= 3;
state <= RD_REG;
end
3:begin
cnt <= 4;
state <= RD_REG;
end
4:state <= RD_REG_DONE;
default:state <= IDLE;
endcase
end
end
RD_REG_DONE:
begin
RW_Done <= 1'b1;
rddata <= Rx_DATA;
state <= IDLE;
end
default:state <= IDLE;
endcase
end
task read_byte;
input [5:0]Ctrl_Cmd;
begin
Cmd <= Ctrl_Cmd;
Go <= 1'b1;
end
endtask
task write_byte;
input [5:0]Ctrl_Cmd;
input [7:0]Wr_Byte_Data;
begin
Cmd <= Ctrl_Cmd;
Tx_DATA <= Wr_Byte_Data;
Go <= 1'b1;
end
endtask
endmodule