本文有参考【精品博文】IIC 通信协议的Verilog实现作者的一些思想,并尝试补充eeprom一端的代码,并不完美,主要是
一eeprom完全按照scl上升沿或下降沿采取动作(写数据或读数据),很难在scl低电平中间点使sda线发生变化(似乎不太符合iic协议要求),
二另外在FPGA放弃sda线控制权和eeprom取得sda线控制权之间会有一小段高阻态(衔接并不连续),以下代码
`timescale 1 ns / 100ps
module exx( clk,rst_n,keywr,keyrd,sda,scl); //fpga端代码
input clk;
input rst_n;
input keywr;
input keyrd;
output reg scl;
inout sda;
reg[3:0] cnt;
reg[3:0] count;
reg wr;
reg rd;
reg link;
reg sdabuf;
reg[8:1] buffer;
reg[3:0] state;
parameter
idl=0,
st1=1,
wdv=2,
ac1=3,
wwd=4,
ac2=5,
wdt=6,
ac3=7,
st2=8,
rdv=9,
ac4=10,
rdt=11,
nac=12,
stp=13;
assign sda=link?sdabuf:1'bz; //fpga控制或放弃sda线
always @(negedge clk or negedge rst_n)
if(!rst_n) scl<=1;
else if(state>st1)scl<=~scl;
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
state<=idl;
wr<=0;
rd<=0;
count<=8;
link<=1;
sdabuf<=1;
buffer<=0;
end
else
case(state)
idl: //空闲状态
if(!keyrd||!keywr)
begin
if(!keywr)wr<=1;
if(!keyrd)rd<=1;
sdabuf<=0;
state<=st1;
buffer<=8'b10100000;
end
st1: state<=wdv; //发送启动信号
wdv:if(!scl&&count>0) //发送器件地址8'b10100000;
begin
sdabuf<=buffer[count];
count<=count-1;
end
else if(!scl&&count==0)
begin
count<=8;
link<=0; //放弃sda线控制权,等待检测应答信号
state<=ac1;
end
ac1: //接收第一次应答
begin
if(scl)
begin
if(!sda)
begin
buffer<=8'b1000_0001; //准备写字地址8'b1000_0001=d'129;
sdabuf<=0;
link<=1; //接收到应答信号后重新获取sda线控制权,准备写字地址
state<=wwd;
end
end
end
wwd:if(!scl&&count>0) //发送字地址
begin
sdabuf<=buffer[count];
count<=count-1;
end
else if(!scl&&count==0)
begin
count<=8;
link<=0;
state<=ac2;
end
ac2: //接收第二次应答
begin
if(scl)
begin
if(!sda)
begin
buffer<=8'b1000_0010; //准备写数据8'b1000_0010=d'130;
sdabuf<=0;
link<=1;
if(wr) state<=wdt; //如果写信号wr则进入wdt状态
if(rd) state<=st2; //如果读信号rd则进入st2状态
end
end
end
wdt:if(!scl&&count>0) //发送写数据
begin
sdabuf<=buffer[count];
count<=count-1;
end
else if(!scl&&count==0)
begin
count<=8;
link<=0;
state<=ac3;
end
ac3: //接收第三次应答
begin
if(scl)
begin
if(!sda)
begin
buffer<=8'b1000_0001;
sdabuf<=0;
link<=1;
state<=stp;
end
end
end
st2: //restart
begin
sdabuf<=1;
link<=1;
if(scl)
begin
sdabuf<=0;
buffer<=8'b10100001;//器件地址8'b10100001赋给buffer;
state<=rdv;
end
end
rdv:if(!scl&&count>0) //发送器件地址8'b10100001;
begin
sdabuf<=buffer[count];
count<=count-1;
end
else if(!scl&&count==0)
begin
count<=8;
link<=0;
state<=ac4;
end
ac4: //检测应答信号
begin
if(scl)
begin
if(!sda)
begin
//buffer<=8'b1000_0001;
sdabuf<=0;
//link<=0;
state<=rdt;
end
end
end
rdt: //读取eeprom数据
if(scl==1&&count>0)
begin
buffer[count]<=sda;
count<=count-1;
end
else if(scl==0&&count==0)
begin
count<=8;
sdabuf=1;
link<=1;
state<=nac;
end
nac: //发送非应答信号
if(scl==0)
begin
sdabuf=0;
state<=stp;
end
stp: //发送停止信号
begin
link<=1;
if(scl)
begin
sdabuf<=1;
wr <=0;
state<=idl;
end
end
default:state<=0;
endcase
endmodule
module eeprom(rst_n,scl,sda); //eeprom端代码
input rst_n;
input scl;
inout sda;
reg[8:1] buffer;
reg[3:0] count;
reg link;
reg sdabuf;
reg wr;
reg[3:0] state;
reg[7:0] addr; //eeprom内部存储器的地址寄存器
reg[7:0] mema[255:0]; //eeprom内部存储器,其它为控制电路
parameter
idl=0,
st1=1,
wdv=2,
ac1=3,
wwd=4,
ac2=5,
wdt=6,
ac3=7,
st2=8,
rdv=9,
ac4=10,
rdt=11,
nac=12,
stp=13;
assign sda=link?sdabuf:1'bz; //eeprom控制或放弃sda线
always @(scl or negedge rst_n) //eeprom根据scl上升沿或下降沿采取动作
if(!rst_n) begin
state<=idl;
buffer<=0;
addr<=0;
count<=8;
link<=0;
sdabuf<=0;
end
else
case(state)
idl: if(scl==0) state<=wdv; //eeprom检测到scl下降沿直接进入wdv状态
wdv: //eeprom接收器件地址
if(scl==1&&count>=1)
begin
buffer[count]<=sda;
count<=count-1;
end
else if(scl==1&&count==0&&buffer[8:1]==8'b10100000) //判断接收到的器件地址是否等于8'b10100000
begin
count<=8;
link<=1; //eeprom获取sda控制权以发送应答信号
state<=ac1;
end
ac1: //eeprom第一次应答
if(scl==0)
begin
link<=0; //eeprom放弃sda控制权
buffer<=8'b00000000;
state<=wwd;
end
wwd: //eeprom接收写字地址8'b1000_0001=d'129;
if(scl==1&&count>0)
begin
buffer[count]<=sda;
count<=count-1;
end
else if(scl==1&&count==0)
begin
count<=8;
addr<=buffer; //接收到的字地址赋给addr
link<=1;
state<=ac2;
end
ac2: //eeprom第二次应答
if(scl==0)
begin
link<=0;
buffer<=8'b00000000;
end
else if(scl==1)
begin
if(sda==0)begin buffer[8]<=0; count<=7; state<=wdt; end//scl上升沿发现sda是低电平,说明肯定接下来是写数据信号
if(sda==1)state<=st2; //scl上升沿发现sda也已经升了,说明有可能是restart信号进入虚假的st2状态待进一步判断
end
wdt: //eeprom接收写数据8'b1000_0010=d'130;
if(scl==1&&count>0)
begin
buffer[count]<=sda;
count<=count-1;
end
else if(scl==1&&count==0)
begin
count<=8;
mema[addr]<=buffer; //接收到的buffer数据写入存储器mema addr地址
link<=1;
state<=ac3;
end
ac3: //eeprom第三次应答
if(scl==0)
begin
link<=0;
buffer<=8'b00000000;
state<=stp;
end
st2: //eeprom检测restart信号
if(scl==0)
begin
if(sda==1) begin buffer[8]<=1; count<=7; state<=wdt; end//scl下降沿发现sda仍然是高电平,说明是写数据
else if(sda==0)state<=rdv; //scl下降沿发现sda也降了,说明是restart信号
end
rdv: //接收器件地址8'b10100001末位为1
if(scl==1&&count>0)
begin
buffer[count]<=sda;
count<=count-1;
end
else if(scl==1&&count==0)
begin
count<=8;
link<=1;
state<=ac4;
end
ac4: //eeprom读数据的第三次应答
if(scl==0)
begin
buffer<=mema[addr];
state<=rdt;
end
rdt: //eeprom发送存储器mema在addr 8'b1000_0001(129)地址的数据8'b1000_0010(130);
if(scl==1&&count>0)
begin
sdabuf<=buffer[count];
count<=count-1;
end
else if(scl==0&&count==0)
begin
count<=8;
link<=0;
state<=nac;
end
nac: //检测非应答信号
if(scl==1)
begin
if(sda==1)state<=stp;
end
stp: //检测停止信号
begin
link<=0;
state<=idl; //回到空闲idl状态
end
default:state<=idl;
endcase
endmodule
module exx_tst();
reg clk;
reg rst_n;
reg keywr;
reg keyrd;
wire scl;
wire sda;
exx myexx( clk,rst_n,keywr,keyrd,sda,scl);
eeprom myeeprom(rst_n,scl,sda);
initial
begin
clk=0;
rst_n=0;
keywr=1;
keyrd=1;
#10;
rst_n=1;
#10;
keywr=0; //按下写按钮
#10;
keywr=1;
#600;
keyrd=0; //按下读按钮
#10;
keyrd=1;
end
always #5 clk=~clk;
endmodule
以下FPGA向eeprom写数据
以下FPGA从eeprom读数据