IIC协议的简单介绍
1.IIC通讯设备的链接图
注:一个IIC总线可以挂载多个设备,一个IIC总线有两条线,一个是数据线,一个是时钟线。主机通过访问不同的从机地址来进行不同设备之间的通信。细节请自己百度,这里不做过多介绍。
2.IIC协议的时序
2.1整体时序图
注:图片纯手画,有些丑,不喜勿喷。
由图中可以看出,整体的时序图由A,B,C,D分割。下面我将详细介绍这四部分。
A:表示空闲状态,此时SCL和SDA都为高电平。
B:表示开始状态,当SCL为高电平时,SDA出现下降沿之后,表示进入了开始状态,数据将要发送或者接受。
C:表示数据读写状态,其中的一段时序波形如下图所示:
D:表示结束状态,当SCL为高电平时,SDA由低电平变为高电平,表示结束。
3.IIC的读写操作以及状态机的实现
3.1 写操作时序
IIC单字节写操作时序
IIC双字节写操作时序
注:这个只介绍2字节写操作,因为掌握了2字节的读写操作,一个字节的读写操作也是轻而易举的。这里不在做过多的解释。
3.1.1状态机的实现
图片太长我分两部分,由上面的写操作的时序,我抽象出不同的状态,状态1,空闲状态,状态2,开始状态,状态3,发送器件地址状态(send_device_addr),状态4,发送存储地址高位(send_storage_addr_H),状态5,发送存储地址高位(send_storage_addr_L),
状态6,写数据(wr_data),状态7,停止状态(stop)。
3.2读操作时序
3.2.1状态机的实现
图片太长我分三部分,由上面的读操作的时序,我抽象出不同的状态,状态1,空闲状态,状态2,开始状态,状态3,发送器件地址状态(send_device_addr),状态4,发送存储地址高位(send_storage_addr_H),
状态5,发送存储地址高位(send_storage_addr_L),
状态6,开始二状态(start_2),状态7,读数据状态(RD-DATA),状态8,停止状态(STOP).
由于二者之间有相同的部分,故我在一个状态转移图中表示所有状态的转移,整体的状态转移图如下图所示:
下面开始介绍如何实现这些状态机,在第一节中我们知道,状态跳转最重要的知道跳转的条件是什么,同时也要对系统的输入输出有清晰的认识。再此之前我先将状态跳转的程序附上。
/***************************组合逻辑表示状态转移*********************************/
always @(*) begin
next_state = IDLE;
case(current_state)
IDLE:
if(i2c_start == 1'b1)
next_state = START;
else
next_state = IDLE;
START:
if(cnt_driver_clk == 2'd3)
next_state = SEND_DEVICE_ADDR;
else
next_state = START;
SEND_DEVICE_ADDR:
if(addr_num == 1'b1) begin
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 ))
next_state = SEND_STORAGE_ADDR_H;
else
next_state = SEND_DEVICE_ADDR;
end
else begin
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 ))
next_state = SEND_STORAGE_ADDR_L;
else
next_state = SEND_DEVICE_ADDR;
end
SEND_STORAGE_ADDR_H:
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 ))
next_state = SEND_STORAGE_ADDR_L;
else
next_state = SEND_STORAGE_ADDR_H;
SEND_STORAGE_ADDR_L:
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 )&&(i2c_rh_wl == 1'b0))
next_state = WR_DATA;
else if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 )&&(i2c_rh_wl == 1'b1))
next_state = START_2;
else
next_state = SEND_STORAGE_ADDR_L;
WR_DATA:
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 ))
next_state = STOP;
else
next_state = WR_DATA;
STOP:
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd3))
next_state = IDLE ;
else
next_state = STOP ;
START_2:
if(i2c_rh_wl == 1'b1) begin
if(cnt_driver_clk == 2'd3)
next_state = SEND_DEVICE_ADDR_RD ;
else
next_state = START_2;
end
else
next_state = START_2 ;
SEND_DEVICE_ADDR_RD:
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8)&&( sda_in == 1'b0 ))
next_state = RD_DATA ;
else
next_state = SEND_DEVICE_ADDR_RD;
RD_DATA:
if((cnt_driver_clk == 2'd3)&&(cnt_bit == 4'd8))
next_state = STOP ;
else
next_state = RD_DATA ;
default:next_state = IDLE;
endcase
end
/*****************************************************************************
模块说明:状态机中的时序逻辑表示输出
输出参数:i2c_scl
*****************************************************************************/
always @(posedge driver_clk or negedge sys_rst) begin
if(!sys_rst )
i2c_scl <= 1'b1;
else if((current_state ==START )||(current_state == SEND_DEVICE_ADDR)
||(current_state == SEND_STORAGE_ADDR_H)||(current_state == SEND_STORAGE_ADDR_L)
||(current_state == WR_DATA)||(current_state == START_2)||(current_state == SEND_DEVICE_ADDR_RD)
||(current_state == RD_DATA))
begin
if(cnt_driver_clk == 2'd2 )
i2c_scl <= 1'b0;
else if(cnt_driver_clk == 2'd0)
i2c_scl <= 1'b1;
else
i2c_scl <= i2c_scl;
end
else
i2c_scl <= 1'b1;
end
解释:在IDLE状态下,当出现一个有效的下降沿时,也就是开始信号(i2c_start),状态跳转到START状态。当经过一个iic的时钟周期之后(cnt_driver_clk == 2‘d3),表示START状态持续了一个时钟周期,此时跳转到SEND_DEVICE_ADDR状态,在这里我参考的野火和原子的教程,对存储地址的字节数进行了判断,当addr_num=1时,表示是两个字节,当(cnt_driver_clk=3,cnt_bit=8,sda_in=0时),表示器件地址写入完成,此时进入下一个状态SEND_STORAGE_ADDR_H。相反 addr_num = 0时为一个字节的存储器地址,此时直接跳到SEND_STORAGE_ADDR_L状态。在SEND_STORAGE_ADDR_H状态时,当写入8个字节的高八位存储器地址时,跳入SEND_STORAGE_ADDR_L状态,在SEND_STORAGE_ADDR_L状态下,在写入的八位低存储器地址,此时若i2c_rh_wl= 0则进入写数据状态WR_DATA,若此时i2c_rh_wl = 1则进入读状态START_2。在WR_DATA状态的完成时,进入STOP状态,在时钟的上升沿的同时数据拉高,状态结束。在START_2状态下,再次经过一个IIc时钟周期,进入SEND_DEVICE_ADDR_RD状态,在这个状态下,当发送完第八个字节,切iic从机的相应为0时,进入读数据状态。无论是在WR_DATA还是RD_DATA状态他们的状态转移条件都是,写或者读满8个字节,之后进行状态的跳转。
下面附上不同状态的输入输出程序:
/*****************************************************************************
模块说明:状态机中的组合逻辑表示输出
输出参数:sda_out
*****************************************************************************/
always @(*) begin
if(! sys_rst ) begin
sda_out = 1'b1 ;
out_flag = 1'b1 ;
end
else begin
case(current_state)
IDLE:begin
sda_out = 1'b1 ;
out_flag = 1'b1 ;
end
START:
if(cnt_driver_clk <= 2'b0)
sda_out = 1'b1 ;
else
sda_out = 1'b0 ;
SEND_DEVICE_ADDR:
if(cnt_bit <= 4'd6 )
sda_out = DIVICE_ADDR[6-cnt_bit];
else if(cnt_bit == 3'd7)
sda_out = 1'b0; //写数据
else begin//if(cnt_bit == 3'd8)
out_flag = 1'b0;
sda_out = 1'b1;
end
SEND_STORAGE_ADDR_H: begin
out_flag = 1'b1 ;
if(cnt_bit <= 3'd7)
sda_out = byte_addr[15-cnt_bit];
else begin//if(cnt_bit == 3'd8)
out_flag = 1'b0;
sda_out = 1'b1;
end
end
SEND_STORAGE_ADDR_L: begin
out_flag = 1'b1 ;
if(cnt_bit <= 3'd7)
sda_out = byte_addr[7-cnt_bit];
else begin//if(cnt_bit == 3'd8)
out_flag = 1'b0;
sda_out = 1'b1;
end
end
WR_DATA: begin
out_flag = 1'b1 ;
if(cnt_bit <= 3'd7)
sda_out = wr_data[7-cnt_bit];
else begin //if(cnt_bit == 3'd8)
sda_out = 1'b1;
out_flag = 1'b0;
end
end
STOP: begin
out_flag = 1'b1 ;
if((cnt_bit == 3'd0)&&(cnt_driver_clk == 2'b0))
sda_out = 1'b0;
else if((cnt_bit == 3'd3)&&(cnt_bit == 3'd3))
sda_out = 1'b1;
else
sda_out = sda_out ;
end
START_2:begin
out_flag = 1'b1 ;
if(cnt_driver_clk < 2'd2)
sda_out = 1'b1 ;
else
sda_out = 1'b0 ;
end
SEND_DEVICE_ADDR_RD:
if(cnt_bit <= 3'd6)
sda_out = DIVICE_ADDR[6-cnt_bit];
else if(cnt_bit == 3'd7)
sda_out = 1'b1;
else begin //if(cnt_bit == 3'd8)
sda_out = 1'b1;
out_flag = 1'b0;
end
RD_DATA:
if(cnt_bit <= 3'd7)
rd_data_reg[7-cnt_bit] = sda_in ;
else begin //if(cnt_bit == 3'd8)
out_flag = 1'b1;
sda_out = 1'b1; //非应答信号
end
default:;
endcase
end
end
再次之前我先将一部分波形图附上:
1.写操作的波形图
2.读操作的波形图
cnt_driver_clk 先解释一下,这个变量的意思:本实验输出的IIC频率为250k,fpga的时钟输入时钟为50mhz,先进行分频将1mhz时钟driver_clk,这时采用一个cnt_driver_clk 计数器,计数到3的时候就说明iic经过一个时钟周期。cnt_bit,字节计数器。
大家这里可以参考波形图,理解代码,这里要写的太多了,如果有需要建议私信,我们好友联系。
最后一个知识点:数据io可以是双向的,这个可以将io口设计为三态模式的。文章来源:https://www.toymoban.com/news/detail-786794.html
/************************三态门的使用**************************************/
assign i2c_sda = out_flag ? sda_out : 1'bz ; //当out_flag输出为高时,表示输出
assign sda_in = i2c_sda ;
这段代码的解释就是,当out_flag为高电平时,i2c_sda = sda_out,当为低电平时,i2c_sda为高阻态。out_flag和sda_out的赋值,详细请看上面的代码,里面详细写了不同状态下,sda_out和out_flag是如何赋值的。当out_flag为低电平时,i2c_sda为输入作用,又sda_in输入。sda_in的赋值也在上面的代码中有详细的介绍。
IIC的介绍就到这里,如果需要工程源码,私信即可,欢迎大家交流沟通。
过段时间更新以太网的状态机实现,至此状态机将不再做更新。文章来源地址https://www.toymoban.com/news/detail-786794.html
到了这里,关于FPGA IIC的状态机实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!