本帖最后由 凔海 于 2016-5-22 10:32 编辑
(二)bala一下Verilog综合的AT24c04
上面介绍了IIC协议的一些概念,下面,就开始真二八经的用Verilog综合出来吧。首先来确定要实现的功能:向AT24C04写入123456,然后读出来送给6位数码管依次显示。
接下来,先来说说大体的框架。
smg_display_module.v数码管显示模块,这块就不多说了吧,留着以后再bala吧。
IIC_function_module.v实现对AT24c04的读写时序的综合
IIC_ctrl.v实现对写、读AT24C04的控制和对数码管显示的控制。
这三个模块也就干这点活了
iAddr、iData分别是由IIC_ctrl模块发送给IIC_function_module地址地址和数据,oData是读出来的数据,共24位,一并发给数码管显示给我们看。iCall实现读写控制,oDone是完成信号,控制IIC_ctrl的工作启停。
在这还是插句话吧,咱知道AT24c04的数据线是双向口,所以要用isQ来对数据线的输入输出状态进行选择。所以用实现,inout SDA;assign SDA = isQ ? rSDA : 1'bz;isQ=1时为输出,即将数据写入AT24C04,isQ=0时为输入,即读出AT24C04数据。那为什么在输入时要设为高阻态呢?
答:处于高阻抗状态时,输出电阻很大,相当于开路,没有任何逻辑控制功能。即可以认为是没有输出,对下级电路没有任何影响。而一个输出端口在高阻态的时候,其状态是由于其相连的其他电路决定的,可以将其看作是输入。当三态门的输出处于高阻状态的时候,取值由外部电路决定,也就是说,这一时刻是可以作为输入。
是否还记得,我们上节说了读写流程,就是下面这幅图,
仔细端详这幅图我们不难发现,我们要写的就是起始位,写数据,读数据,应答位,结束位,其他的
接下来开始苦干人生了
IIC_function_module.v该模块要完成与AT 24C04的读写通讯,也就是IIC协议。说白了就是完成写字节和读字节,所以要直勾勾的盯着下面这张图来写一写
该图给出了AT24C04对时序的要求,高电平多少,低电平多少都有规范。可见,想实现度AT24C04的控制,要求的还是蛮多的。下面我们采用400KHz的时钟周期进行读写操作。
1、起始信号,也就是通知AT24c04要干活的指令是要求在时钟线拉高的情况下数据线产生一个负跳变(高电平变为低电平)。而且要求起始信号的保持直接不少于600ns.所以我们可以这样描述
begin
isQ <= 1'b1;
rSCL <= 1'b1;
if(C1 == 8'd0)
rSDA <= 1'b1;
else if(C1 == (TR+20)) //上升沿再保持400ns
rSDA <= 1'b0;
if(C1 == TCLK-1) //一个时钟周期后进入下一步骤
begin C1 <= 8'd0; i <= i + 1'b1; end
else
C1 <= C1 + 1'b1;
end
说一下哈,else if(C1 == (TR+20))那个20不是规定,使我想让它保持20的,其实也不需要一个时钟周期,只怪我愿意
2、应答位可以看做是读取一位数据,主机写完成或读取一字节时,从机都会产生应答位,在主机拉低SCL那一刻,从机便会发送应答位,主机借上升沿读取应答,应答信号低电平有效。
5'd15:
begin
isQ <= 1'b0;
if(C1 ==TF+8'd20) isAck <= SDA;
if(C1 == 8'd0) rSCL <= 1'b0;
else if(C1 == TF+8'd20) rSCL <= 1'b1;
if(C1 == TCLK -1'b1)
begin C1 <= 8'd0; i <= i + 1'b1; end
else C1 <= C1 + 1'b1;
end
5'd16:
if(isAck != 1'b0)
i <= 5'd0;
else
i <= jump;
3、结束位
在SCL保持高电平时,SDA被释放,使得SDA返回高电平,即为停止信号,这也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,该信号建立后,IIC返回空闲状态。
begin
isQ <= 1'b1;
if(C1 == 0) rSCL <= 1'b0;
else if(C1 == 8'd31) rSCL <= 1'b1;
if(C1==0) rSDA <= 1'b0;
else if(C1 == 8'd76) rSDA <= 1'b1;
if(C1 == 8'd155)
begin C1 <= 8'd0; i <= i + 1'b1; end
else C1 <= C1 + 1'b1;
end
4、写数据SCL下降沿更新数据,上升沿锁存数据
5'd7,5'd8,5'd9,5'd10,5'd11,5'd12,5'd13,5'd14:
begin
isQ <= 1'b1; //写入AT24c04
rSDA <= oData[5'd14 - i];
if(C1 == 0) rSCL <= 1'b0;
else if(C1 == (TF+TLOW)) rSCL <= 1'b1;
if(C1 == TCLK -1'b1)
begin C1 <= 8'b0; i <= i + 1'b1; end
else C1 <= C1 + 1'b1;
end
5、读数据,权当读取八个应答信号吧
5'd19,5'd20,5'd21,5'd22,5'd23,5'd24,5'd25,5'd26:
begin
isQ <= 1'b0;
if(C1 == 8'd62) rData[26-i] <=SDA;
if(C1 == 0) rSCL <= 1'b0;
else if(C1 == 8'd62) rSCL <= 1'b1;
if(C1 == TCLK-1’b1)
begin C1 <= 8'd0; i <= i + 1'b1;end
else C1 <= C1 + 1'b1;
end
最后呢?就是IIC_ctrl中的内容了
case(i)
3'd0:
if(DoneU1)
begin isCall <= 2'b00;i <= i + 1'b1;end
else
begin isCall <= 2'b10;rAd <= 8'd6;rDa <= 8'h21;end
3'd1:
if(DoneU1)
begin isCall<= 2'b00;i<= i + 1'b1;end
else
begin isCall <= 2'b10;rAd <= 8'd16;rDa <= 8'h43;end
3'd2:
if(DoneU1)
begin isCall <= 2'b00;i <= i + 1'b1;end
else
begin isCall <= 2'b10;rAd <= 8'd66;rDa <= 8'h65;end
3'd3:
if(DoneU1)
begin rNum[7:0] <= oData;isCall <= 2'b00;i <=i+1'b1; end
else
begin isCall <= 2'b01;rAd <= 8'd6;end
3'd4:
if(DoneU1)
begin rNum[15:8] <= oData;isCall <= 2'b00;i<=i + 1'b1;end
else
begin isCall <= 2'b01;rAd <= 8'd16;end
3'd5:
if(DoneU1)
begin rNum[23:16] <= oData;isCall <= 2'b00;i<= i + 1'b1;end
else
begin isCall <= 2'b01;rAd <= 8'd66;end
3'd6:
i <= i;
endcase
在地址6、16、66分别写入12、34、56。说实话我挺喜欢黑金这样的写法的,虽然不知道这样写算不算好的代码风格,但挺容易懂的。在完成信号来之前,重复进行一个动作,例如begin isCall <= 2'b10;rAd <= 8'd6;rDa <= 8'h21;end写操作,在地址6写入数据12,等完成信号来了,就进入下一个操作。挺好,黑金给你32个赞
山寨源:黑金动力社区
学识浅薄出拙文,如察错误望赐教,小弟在此感涕零。
|