最近博主马不停蹄的学习FPGA就是为了可以尽快用FPGA来实现图像处理,好让我们的博客恢复正常内容哈哈哈,我说几句题外话,其实关于图像处理,工程应用和写论文是完全不一样的,如果是以找工作为目的,在牛逼的算法其实很难进行实际运用,归结到底就是速度以及实际效果。所以如果是以找工作为目的的小伙伴们其实不妨把时间花在积累工程经验中来,毕业论文啥的随便找两个算法复现了调调参就可以了。当然我这么说,读博的朋友肯定很看不起哈哈哈。。。
好了进入正题,什么是iic通信,我来讲一下它的通信的基本协议。简单来讲,任何数据你要发送或者接受需要两根基本的总线,即时钟和数据,那么对于iic协议来说,基本流程如下:
1.起始信号的判断,从机什么时候知道要开始发送或者接受数据呢
很简单,就是在slk时钟信号还是高电平的时候,此时数据线拉低,则代表起始信号,即下降沿触发,需要注意的是必须在slk时钟信号是高电平拉低数据线信号。
2.那么开始信号产生后,我们首先要确定是哪个从机响应,那么需要先通过数据线输入从机的地址,以EEPROM 24LC04为例,它的地址为10100000,其中高四位数据固定,低四位的前面三位代表是哪个eeprom,如果只有一个,默认是000,最多可以八个。最后一位是读写位,0代表写数据,1代表读数据。那么怎么样才能把设备地址写入呢
就是这样,在clk每来一个上升沿的时候数据会锁存,所以需要提前将数据输入给sda信号线
3.将地址写完之后呢?此时从机会产生一个应答信号,那么这个应答信号我们什么时候取才是最好的,当然是时钟高电平的一半啊,如下图所示:
画的很抽象哈哈,但是应该可以理解,就是在最中间的时候将应答信号输出出来,理由还是高电平触发。。但是需要注意一点的就是,此时sda信号不在作为写数据,而是读数据了,所以说sda信号线不再由我们控制,所以应该把它设为高阻态。然后在高电平的最中间时刻去捕获数据总线上的应答值。
4.送完地址信号,然后就是写数据地址了,即那个地址存放你要的数据需要让从机知道。过程很相似,重复2,3两步即可
5.最后就是写你要的数据,同样重复2,3两个步骤。
6.最后就是结束信号,需要在时钟信号为高的时候,拉高数据总线,如下:
好了至此,iic协议如何发送一个字节就结束了,下面来讲如何读一个字节
1.首先还是起始信号的判断,和之前一样
2.依然是写入从机的地址,注意,这里是写,所以地址的最低位还是0,写完检测应答信号然后继续。还是注意应答信号由于是数据总线输出,所以应提前置为高阻态。
3.然后是写入需要读哪个数据的地址,过程参考写数据。也是讲要读的地址写入后,通过下一个时钟高电平去检测应答信号。
4.然后再来一个起始信号
5.接着读从机地址,注意这里是读,所以最后一位是1,然后继续检测应答信号
6.然后就是从sda数据线读数据,读完之后可以不用检测应答信号
7.输入结束信号
通过verilog博主编写了一个简单的数据发送和接受模块,为了验证准确性,将发送出来的数据接到led灯来验证,下面给出代码
module iic_test(
clk,
rst,
led,
slk,
sda
);
input clk;
input rst;
output reg [3:0]led;
output slk;
inout sda;
wire isdone;
wire [7:0]rdata;
iic(
.clk(clk),
.rst(rst),
.choose(choose),
.device(device),
.address(address),
.data(data),
.rdata(rdata),
.slk(slk),
.sda(sda),
.isdone(isdone)
);
reg [1:0]choose;
reg [1:0]i;
reg [7:0]device;
reg [7:0]address;
reg [7:0]data;
always@(posedge clk or negedge rst)
if(!rst)
begin
choose<=2'b00;
i<=2'b0;
device<=8'b0;
address<=8'b0;
data<=8'b0;
led<=4'd0;
end
else
case(i)
0: if(isdone)
begin
choose<=2'b00;
i<=i+1'b1;
end
else
begin
choose<=2'b01;
device<=8'b10100000;
address<=8'b00000000;
data<=8'h4e;
end
1: if(isdone)
begin
choose<=2'b00;
i<=i+1'b1;
end
else
begin
choose<=2'b10;
device<=8'b10100000;
address<=8'b00000000;
//data<=8'h2a;
end
2: led<=rdata[3:0];
endcase
//assign led=rdata[3:0];
endmodule
module iic(
clk,
rst,
choose,
device,
address,
data,
rdata,
slk,
sda,
isdone
);
input clk;
input rst;
input [1:0]choose; //选择读、写
input [7:0]device;
input [7:0]address;
input [7:0]data;
output reg [7:0]rdata;
output reg slk;
inout reg sda;
output reg isdone;
parameter r100K = 9'd500;
reg [5:0]i;
reg [15:0]clkcount;
reg isask;
always@(posedge clk or negedge rst)
if(!rst)
begin
rdata<=8'b0;
slk<=1'b1;
sda<=1'b1;
i<=6'd0;
clkcount<=16'd0;
isask<=1'b0;
isdone<=1'b0;
end
else if(choose[0]) //代表开始写一个数的地址
begin
case(i)
0:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b1;
else if(clkcount==16'd350)
slk<=1'b0;
if(clkcount==16'd0)
sda<=1'b1;
else if(clkcount==16'd250)
sda<=1'b0;
end
//写设备地址
1,2,3,4,5,6,7,8:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=device[8-i];
end
//释放总线设为高阻态
9:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
//总线重新让主机掌握
sda<=1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'bz;
if(clkcount==16'd200)
isask<=sda;
end
10: if(isask!=0)
i<=6'd0;
else
i<=i+1'b1;
//写字节地址
11,12,13,14,15,16,17,18:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=address[18-i];
end
//释放总线设为高阻态
19:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
//总线重新让主机掌握
sda<=1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'bz;
if(clkcount==16'd200)
isask<=sda;
end
20: if(isask!=0)
i<=6'd0;
else
i<=i+1'b1;
21,22,23,24,25,26,27,28: begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=data[28-i];
end
//释放总线设为高阻态
29:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
//总线重新让主机掌握
sda<=1'b0;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'bz;
if(clkcount==16'd200)
isask<=sda;
end
30: if(isask!=0)
i<=6'd0;
else
i<=i+1'b1;
31:begin
if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd200)
slk<=1'b1;
if(clkcount==16'd0)
sda<=1'b0;
else if(clkcount==16'd300)
sda<=1'b1;
end
32: begin i<=i+1'b1;
isdone<=1'b1;
end
33: begin i<=6'd0;
isdone<=1'b0;
end
endcase
end
else if(choose[1])
begin
case(i)
0: begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b1;
else if(clkcount==16'd350)
slk<=1'b0;
if(clkcount==16'd0)
sda<=1'b1;
else if(clkcount==16'd250)
sda<=1'b0;
end
//写设备地址
1,2,3,4,5,6,7,8:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=device[8-i];
end
//释放总线设为高阻态
9:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
//总线重新让主机掌握
sda<=1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'bz;
if(clkcount==16'd200)
isask<=sda;
end
10: if(isask!=0)
i<=6'd0;
else
i<=i+1'b1;
//写字节地址
11,12,13,14,15,16,17,18:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=address[18-i];
end
//释放总线设为高阻态
19:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
//总线重新让主机掌握
sda<=1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'bz;
if(clkcount==16'd200)
isask<=sda;
end
20: if(isask!=0)
i<=6'd0;
else
i<=i+1'b1;
//再来一次起始信号
21: begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b1;
else if(clkcount==16'd350)
slk<=1'b0;
if(clkcount==16'd0)
sda<=1'b1;
else if(clkcount==16'd250)
sda<=1'b0;
end
22,23,24,25,26,27,28: begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=device[29-i];
end
29: begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
//最后一位表示读
sda<=1'b1;
end
//释放总线设为高阻态
30:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
//总线重新让主机掌握
//sda<=1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'bz;
if(clkcount==16'd200)
isask<=sda;
end
31: if(isask!=0)
i<=6'd0;
else
i<=i+1'b1;
//开始读数据
32,33,34,35,36,37,38,39: begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
if(clkcount==16'd200)
rdata[39-i]<=sda;
sda<=1'bz;
end
//释放总线设为高阻态
40:begin if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd100)
slk<=1'b1;
else if(clkcount==16'd300)
slk<=1'b0;
sda<=1'b0;
end
41: begin
if(clkcount==16'd499)
begin
clkcount<=16'd0;
i<=i+1'b1;
end
else
clkcount<=clkcount+1'b1;
if(clkcount==16'd0)
slk<=1'b0;
else if(clkcount==16'd200)
slk<=1'b1;
if(clkcount==16'd0)
sda<=1'b0;
else if(clkcount==16'd300)
sda<=1'b1;
end
42: begin i<=i+1'b1;
isdone<=1'b1;
end
43: begin i<=6'd0;
isdone<=1'b0;
end
endcase
end
endmodule