FPGA IIC 总线实现及仿真(一)
IIC总线简介
IIC(Inter-Integrated Circuit)总线是由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信。IIC一共有只有两个总线: 一条是双向的串行数据线SDA(Serial data),一条是串行时钟线SCL(Serial clock line)
IIC总线上必须有一个主设备来控制数据的读写,I2C总线上的每个设备都自己一个唯一的地址。
一、IIC协议
I2C 总线在传送数据过程中共有三种类型信号:开始信号、数据/地址信号、应答信号和结束信号,如下图所示。
开始(Start)信号:SCL 为高电平时,SDA 由高电平向低电平跳变。
数据(Data)/地址(Addr)信号:SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB)。
应答(ACK)信号:接收数据设备 在接收到 8bit 数据后,向发送数据的 设备发出低电平脉冲,表示已收到数据。发送数据的设备在收到应答信号后决定是否继续传输数据。
非应答(NOP)信号:由主机发送,当主机收到数据后,后续不在接收数据,则发送非应答信号,通知从机不在发送数据。非应答信号是主机在应答信号周期内发送高电平脉冲,结束数据传输。
结束(Stop)信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
二、IIC读写数据
1.写数据时序
IIC写数据时序如下图所示:
a)主机首先产生起始(START)信号
b)然后发送从机设备地址(Addr),这个地址共有7位,紧接着的第8位是读写位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)。主机发送地址时,总线上的从机将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
c)地址发送完毕后,主机等待从机的应答(ACK)信号
d)当主机收到应答(ACK)信号后,发送要访问从机的数据(DataAddr)地址, 再等待从机的应答信号
e)当主机收到应答信号后,发送数据(Data),每次发送完8bit数据,都要等待从机的应答信号
f)主机写数据完毕,产生停止信号,结束传送过程。
2.读数据时序
IIC读数据时序如下图所示:
a)主机首先产生起始(START)信号
b)然后发送从机设备地址(Addr),此时该地址的第8位为0,表明是向从机写命令
c)地址发送完毕后,主机等待从机的应答(ACK)信号
d)当主机收到应答(ACK)信号后,发送要访问从机的数据(DataAddr)地址, 再等待从机的应答信号
e)当主机收到应答信号后,主机重新发送一个开始Restart信号,然后紧跟着发送一个从机设备地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据。
f)这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答(NOP)信号,表示不在接收数据
g)主机进而产生停止信号,结束传送过程。
三、AT24C02
以读写EEPROM AT24C02为例,说明IIC读写信号时序。
AT24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。如下图所示。
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线
AT24C02设备地址前四位固定为1010,A2~A0为由管脚电平(接地),最后一位表示读写操作,所以AT24C02的读地址为0xA1,写地址为0xA0(A2、A1和A0接地)。
AT24C02总共256字节数据,其寻址范围为00~FF,使用8bit数据地址可对内部256B中的任一个进行读/写操作。当地址大于2K时,需要使用页寻址,例如4k总共有512字节数据,使用P0作为页地址:
当P0=0时,可操作0~255地址的字节数据;
当P0=1时,可操作256~511地址的字节数据。
AT24C02系列EEPROM设备地址如下图所示。
四、FPGA IIC总线实现代码
IIC模块的输入输出,由外部给IIC模块读写指令及要写入的数据
module IIC_Master(
input clk, //工作时钟
input rst_p, //复位 1:复位 0:正常工作
output SCL, //IIC 时钟信号
inout SDA, //IIC 数据信号
input WR, //WR=1时 从EEPRO读数据 WR=0时 向EEPROM写数据
input WR_start, //读写数据起始信号
input [7:0] data_in, //需要写入的数据
output Write_ready, //写数据有效信号
output Write_done, //写数据结束信号
//input [7:0] Write_num, //写数据长度
output [7:0] data_out,//读出的数据
output Read_ready, //读出数据有效信号
output Read_done //读数据结束信号
//input [7:0] Read_num //读数据长度
);
parameter write_addr = 8'b1010_000_0; //IIC 从设备的读地址
parameter read_addr = 8'b1010_000_1; //IIC 从设备的写地址
parameter DataAddr_start = 8'h00; //数据起始地址
parameter Baud_clk = 16'd480; //IIC 时钟速率=clk/Baud_clk
parameter Send_num = 8'd10; //一次发送的字节数
parameter Recv_num = 8'd10; //一次接收的字节数
IIC总线状态机
reg IIC_SCL = 0;
reg IIC_SDAin = 0;
reg IIC_SDAout = 0;
reg is_out = 0;
reg WR_bit = 0; //0:写数据 1:读数据
reg [15:0] clk_cnt = 0;//时钟分频计数器
reg [3:0] Bit_cnt = 0;//接收bit数 计数器
reg [7:0] WRdata_cnt = 0;//发送字节数计数器
reg [7:0] RDdata_cnt = 0;//接收字节数计数器
reg [7:0] Write_cnt = 0;
reg [7:0] Read_cnt = 0;
reg [7:0] data_addr = 0;
reg [7:0] send_data = 0;//写入的数据
reg [7:0] recv_data = 0;//读出的数据
reg [3:0] IIC_state = 0;
reg [3:0] IIC_next_state = 0;
localparam [3:0] Idle_state = 0,
Start_state = 1,//start信号
WrAddr_state = 2,//设备写地址
DataAddr_state = 3,//设备读地址
ReStart_state = 4,//读数据时,重新发送start信号
RdAddr_state = 5,//数据地址
WrData_state = 6,//写数据
RdData_state = 7,//读数据
sACK_state = 8,//从设备响应
mACK_state = 9,//主设备响应
NOP_state = 10,//主设备非应答信号
Stop_state = 11;//stop信号
assign SCL = IIC_SCL;
assign SDA = (is_out==0) ? IIC_SDAout : 1'bz; //is_out=0 时发送,is_out=1时 时输入
//assign IIC_SDAin = (is_out==1) ? SDA : 0;
/IIC 状态机///
always @ (posedge clk) begin
case(IIC_state)
Idle_state:begin
if(WR_start==1)
IIC_state<= Start_state;
else
IIC_state<= Idle_state;
end
Start_state:begin
if(clk_cnt==Baud_clk-1 )
IIC_state<= WrAddr_state;
else
IIC_state<= Start_state;
end
WrAddr_state:begin //写8bit设备写地址
if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
begin
IIC_state<= sACK_state;
IIC_next_state<=DataAddr_state;
end
else
IIC_state<= WrAddr_state;
end
DataAddr_state:begin //写8bit设备读地址
if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
begin
IIC_state<= sACK_state;
if( WR_bit==1) //如果读数据,则重新发送start信号
IIC_next_state<=ReStart_state;
else //如果写数据,则开始发送数据
IIC_next_state<=WrData_state;
end
else
IIC_state<= DataAddr_state;
end
ReStart_state:begin
if(clk_cnt==Baud_clk-1 )
IIC_state<= RdAddr_state;
else
IIC_state<= ReStart_state;
end
RdAddr_state:begin //写8bit设备读地址
if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
begin
IIC_state<= sACK_state;
IIC_next_state<=RdData_state;
end
else
IIC_state<= RdAddr_state;
end
WrData_state:begin //读数据
if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
begin
IIC_state<= sACK_state;
if( WRdata_cnt==Write_cnt-1)
IIC_next_state<=Stop_state;
else
IIC_next_state<=WrData_state;
end
else
IIC_state<= WrData_state;
end
RdData_state:begin //读数据
if(clk_cnt==Baud_clk/2)
IIC_SDAin<=SDA;
//IIC_SDAin<=0;
if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
begin
if( RDdata_cnt==Read_cnt-1)
IIC_state<=NOP_state;
else
begin
IIC_state<= mACK_state;
IIC_next_state<=RdData_state;
end
end
else
IIC_state<= RdData_state;
end
sACK_state:begin //等待响应
if(clk_cnt==Baud_clk/2)
IIC_SDAin<=SDA;
//IIC_SDAin<=0;
if(clk_cnt==Baud_clk-1 ) begin
if( IIC_SDAin==0 ) //如果响应,则发送读数据地址
IIC_state<= IIC_next_state;
else
IIC_state<= Stop_state; //如果未响应,则结束此次读写操作
end
end
mACK_state:begin //等待响应
if(clk_cnt==Baud_clk-1 ) //如果响应,则发送读数据地址
IIC_state<= IIC_next_state;
else
IIC_state<= mACK_state; //如果未响应,则结束此次读写操作
end
NOP_state:begin
if(clk_cnt==Baud_clk-1 )
IIC_state<= Stop_state;
else
IIC_state<= NOP_state;
end
Stop_state:begin
if(clk_cnt==Baud_clk-1 )
IIC_state<= Idle_state;
else
IIC_state<= Stop_state;
end
endcase
end
SCL时序及SDA时序控制
always @ (posedge clk) begin
if(IIC_state== Idle_state)
clk_cnt<=0;
else begin
if(clk_cnt==Baud_clk-1)
clk_cnt<=0;
else
clk_cnt<=clk_cnt+1;
end
end
SCL 时序/
always @ (posedge clk) begin
if(IIC_state== Idle_state || IIC_state== Start_state || IIC_state== ReStart_state)
IIC_SCL<=1;
else begin
if(clk_cnt<=Baud_clk/2)
IIC_SCL<=0;
else
IIC_SCL<=1;
end
end
SDA 时序/
always @ (posedge clk) begin
if(IIC_state==sACK_state || IIC_state==RdData_state)
is_out<=1;
else
is_out<=0;
end
always @ (posedge clk) begin
case(IIC_state)
Idle_state:
IIC_SDAout<=1;
Start_state,ReStart_state:begin
if(clk_cnt<=Baud_clk/2)
IIC_SDAout<=1;
else
IIC_SDAout<=0;
end
mACK_state:
IIC_SDAout<=0;
NOP_state:
IIC_SDAout<=1;
Stop_state:begin
if(clk_cnt<=Baud_clk/4*3)
IIC_SDAout<=0;
else
IIC_SDAout<=1;
end
WrAddr_state:begin
if(clk_cnt<=Baud_clk/4)
begin
case(Bit_cnt)
0:IIC_SDAout<=write_addr[7]; //先发高位
1:IIC_SDAout<=write_addr[6];
2:IIC_SDAout<=write_addr[5];
3:IIC_SDAout<=write_addr[4];
4:IIC_SDAout<=write_addr[3];
5:IIC_SDAout<=write_addr[2];
6:IIC_SDAout<=write_addr[1];
7:IIC_SDAout<=write_addr[0];
endcase
end
end
RdAddr_state:begin
if(clk_cnt<=Baud_clk/4)
begin
case(Bit_cnt)
0:IIC_SDAout<=read_addr[7]; //先发高位
1:IIC_SDAout<=read_addr[6];
2:IIC_SDAout<=read_addr[5];
3:IIC_SDAout<=read_addr[4];
4:IIC_SDAout<=read_addr[3];
5:IIC_SDAout<=read_addr[2];
6:IIC_SDAout<=read_addr[1];
7:IIC_SDAout<=read_addr[0];
endcase
end
end
DataAddr_state:begin
if(clk_cnt<=Baud_clk/4)
begin
case(Bit_cnt)
0:IIC_SDAout<=DataAddr_start[7]; //先发高位
1:IIC_SDAout<=DataAddr_start[6];
2:IIC_SDAout<=DataAddr_start[5];
3:IIC_SDAout<=DataAddr_start[4];
4:IIC_SDAout<=DataAddr_start[3];
5:IIC_SDAout<=DataAddr_start[2];
6:IIC_SDAout<=DataAddr_start[1];
7:IIC_SDAout<=DataAddr_start[0];
endcase
end
end
WrData_state:begin
send_data<=data_in;
if(clk_cnt<=Baud_clk/4)
begin
case(Bit_cnt)
0:IIC_SDAout<=send_data[7]; //先发高位
1:IIC_SDAout<=send_data[6];
2:IIC_SDAout<=send_data[5];
3:IIC_SDAout<=send_data[4];
4:IIC_SDAout<=send_data[3];
5:IIC_SDAout<=send_data[2];
6:IIC_SDAout<=send_data[1];
7:IIC_SDAout<=send_data[0];
endcase
end
end
default:
IIC_SDAout<=1;
endcase
end
数据的读写时序
reg WR_start_r = 0;
reg Write_ready_r=0;
reg Read_ready_r =0;
always @ (posedge clk) begin
WR_start_r<=WR_start;
if(WR_start==1 && WR_start_r==0)
WR_bit<=WR;
Write_cnt<=Send_num;
Read_cnt<=Recv_num;
end
assign Write_ready = ( Bit_cnt==7 && clk_cnt==Baud_clk-1 && WR_bit==0 && IIC_state==WrData_state) ? 1 :0;
assign Write_done = ( IIC_state==Stop_state && WR_bit==0) ? 1 :0;
always @ (posedge clk) begin
if(IIC_state==WrAddr_state||IIC_state==DataAddr_state
||IIC_state==RdAddr_state||IIC_state==WrData_state||IIC_state==RdData_state)
begin
if(clk_cnt==Baud_clk-1)
Bit_cnt<=Bit_cnt+1;
end
else
Bit_cnt<=0;
end
always @ (posedge clk) begin
Write_ready_r<=Write_ready;
if(Write_ready_r==0 && Write_ready==1 )begin
if( WRdata_cnt==Write_cnt-1 && WR_bit==0 )
WRdata_cnt<=0;
else
WRdata_cnt<=WRdata_cnt+1;
end
end
读数据 时序/
assign Read_ready = ( Bit_cnt==7 && clk_cnt==Baud_clk-1 && WR_bit==1 && IIC_state==RdData_state) ? 1 :0;
assign Read_done = ( IIC_state==Stop_state && WR_bit==1) ? 1 :0;
assign data_out = recv_data;
always @ (posedge clk) begin if(IIC_state==RdData_state)
begin
//if(clk_cnt==Baud_clk/2)
//begin
case(Bit_cnt)
0:recv_data[7]<=IIC_SDAin; //先收高位
1:recv_data[6]<=IIC_SDAin;
2:recv_data[5]<=IIC_SDAin;
3:recv_data[4]<=IIC_SDAin;
4:recv_data[3]<=IIC_SDAin;
5:recv_data[2]<=IIC_SDAin;
6:recv_data[1]<=IIC_SDAin;
7:recv_data[0]<=IIC_SDAin;
endcase
//end
end
end
always @ (posedge clk) begin
Read_ready_r<=Read_ready;
if(Read_ready_r==0 && Read_ready==1 )begin
if( RDdata_cnt==Read_cnt-1 && WR_bit==1 )
RDdata_cnt<=0;
else
RDdata_cnt<=RDdata_cnt+1;
end
end
endmodule
下篇讲解IIC总线的时序仿真