题目:
根据课堂讲授的基本原理,自己尝试编写一个 IIC 控制逻辑,FPGA 的输 入时钟为 10MHz,IIC 的通信频率为 400KHz,要求 FPGA 向 AD 芯片写入控 制指令,AD 芯片的地址为 0000123(改为十进制 123,即 01111011),AD 芯片 中有三个地址连续的寄存器, 地址为 0x48,配置数据为 0x55,地址为 0x49, 配置数据为 0xAA,地址为 0x50,配置数据为 0xCC,试画出电路连接框图、状 态转移图、完成代码编写并仿真。 分析: 根据要求,此 IIC 通信模块应该在 FPGA 与 AD 芯片间建立连接,其中最主要的就是 SCL 时钟线输出给 AD 芯片,建立通信时钟,还有 SDA 线实现双向数 据传输。 电路模块框图如下所示: 其中两个上拉电阻 R1 和 R2 保证了在空闲状态时,SCL 线与 SDA 线上均保 持高电平。 根据 IIC 的通信方法,我们按照 Moore 型状态机 进行时序编写,这是因为整个通信过程中状态的转移不受输入条件的影响,仅依赖过程中时序的变化,从而改变状态。设计思路如下: 1.按照题目意思,需要 17 个状态来表示整个通信过程,即: 不定态+起始态+发出芯片地址+从机应答+发出寄存器 1 地址+从机应答+发 出寄存器 1 数据+从机应答+发出寄存器 2 地址+从机应答+发出寄存器 2 数据+ 从机应答+发出寄存器 3 地址+从机应答+发出寄存器 3 数据+从机应答+终止态 由此在编写 Verilog 语言时应定义以上 17 个状态。当满足触发条件时将发生状态转移。 2.采用三端式状态机进行编写。除了正常的三部分之外,还应该 有一个数 据寄存器转移的部分,该部分提供了状态转移的动力和条件。 3.设计计数器产生所需频率的时钟 SCL 以及相应的变换节点,即在本设计中视为 SCL 高电平中点为稳定状态,判断起始与终止条件;将 SCL 低电平中点 看作数据变换点,即仅在此时发生数据变化。 4.SDA 的双向传输特性使用 三态门 来实现。 设计状态转移图如下:
具体代码如下:
module IIC_control(clk,rst,SCL,SDA,en);
input clk,rst,en;
output reg SCL;
inout SDA;
reg sdareg; //SDA 数据寄存器
reg sdalink; //双向端口控制
assign SDA=sdalink?sdareg:1'bz;//三态门控制双向口
parameter IIC_idle=5'D0, //起始态
IIC_start=5'D1, //开始
IIC_icaddr=5'D2, //发出芯片地址
IIC_icaddrask=5'D3, //从机应答
IIC_regaddr1=5'D4, //发出寄存器 1 地址
IIC_regaddrask1=5'D5, //从机应答
IIC_regdata1=5'D6, //寄存器 1 数据
IIC_regdataask1=5'D7, //从机应答
IIC_regaddr2=5'D8, //发出寄存器 2 地址
IIC_regaddrask2=5'D9, //从机应答
IIC_regdata2=5'D10, //寄存器 2 数据
IIC_regdataask2=5'D11, //从机应答
IIC_regaddr3=5'D12, //发出寄存器 3 地址
IIC_regaddrask3=5'D13, //从机应答
IIC_regdata3=5'D14, //寄存器 3 数据
IIC_regdataask3=5'D15, //从机应答
IIC_stop=5'D16, //终止状态
icaddr=8'b0111_1011, //芯片地址
icaddrout={icaddr[6:0],1'b0}, //发出地址
regaddr1=8'h48, //寄存器 1 地址
regdata1=8'h55, //寄存器 1 数据
regaddr2=8'h49, //寄存器 2 地址
regdata2=8'hAA, //寄存器 2 数据
regaddr3=8'h50, //寄存器 3 地址
regdata3=8'hCC, //寄存器 3 数据
freqcnt=(10000000/400000); //SCL 与 clk 的周期关系
reg [7:0] cnt; //计数器产生 SCL
reg [3:0] bytecnt; //数据或地址传输的位计数
reg [4:0] state,nstate;
assign SCL_h=(cnt==(freqcnt>>2));//SCL 时钟四分之一周期处,高电平中点
assign SCL_l=(cnt==(freqcnt>>2)*3);//SCL 时钟周期四分之三处,低电平
中点
//cnt 计数,从 0 到(freqcnt-1)
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt<=1'b0;
else if(cnt==freqcnt-1'b1)//计数到最大
cnt<=1'b0;
else
cnt<=cnt+1'b1;
end
//产生 SCL 的时钟信号
always @(posedge clk or negedge rst)//SCL 时钟跳变
begin
if(!rst)
SCL<=1'b0;
else begin
if(cnt>=1'b0&&cnt<=(freqcnt>>1)-1'b1)
SCL<=1'b1;//SCL 前半周期为高
else
SCL<=1'b0;//SCL 后半周期为低
end
end
//IIC 三段式状态机:
//第一部分:
always @(posedge clk or negedge rst)
begin
if(!rst)
state<=IIC_idle;
else
state<=nstate;
end
//第二部分 状态转移:
always @(*)
begin
nstate<=state;
case(state)
IIC_idle: if(en)
nstate<=IIC_start;
IIC_start: if(SCL_h)//SCL 为高时,SDA 跳变,进入起始
nstate<=IIC_icaddr;
IIC_icaddr: if(SCL_l==1'b1&&bytecnt==3'b0)//地址发送完成
nstate<=IIC_icaddrask;
IIC_icaddrask: if(SCL_l)//SCL 为低时数据变化
nstate<=IIC_regaddr1;
IIC_regaddr1:if(SCL_l==1'b1&&bytecnt==3'b0)
nstate<=IIC_regaddrask1;
IIC_regaddrask1:if(SCL_l)
nstate<=IIC_regdata1;
IIC_regdata1:if(SCL_l==1'b1&&bytecnt==3'b0)
nstate<=IIC_regdataask1;
IIC_regdataask1:if(SCL_l)
nstate<=IIC_regaddr2;
IIC_regaddr2:if(SCL_l==1'b1&&bytecnt==3'b0)
nstate<=IIC_regaddrask2;
IIC_regaddrask2:if(SCL_l)
nstate<=IIC_regdata2;
IIC_regdata2: if(SCL_l==1'b1&&bytecnt==3'b0)
nstate<=IIC_regdataask2;
IIC_regdataask2: if(SCL_l)
nstate<=IIC_regaddr3;
IIC_regaddr3:if(SCL_l==1'b1&&bytecnt==3'b0)
nstate<=IIC_regaddrask3;
IIC_regaddrask3:if(SCL_l)
nstate<=IIC_regdata3;
IIC_regdata3:if(SCL_l==1'b1&&bytecnt==3'b0)
nstate<=IIC_regdataask3;
IIC_regdataask3:if(SCL_l)
nstate<=IIC_stop;
IIC_stop: if(SCL_h)
nstate<=IIC_stop;
default: nstate<=state;
endcase
end
//第三部分 数据输出控制:
always @(posedge clk or negedge rst)
begin
if(!rst) begin
sdareg<=1;
sdalink<=1;
end
else begin
case(state)
IIC_idle: begin
sdareg<=1;
sdalink<=1;
end
IIC_start:begin
if(SCL_h) begin
sdareg<=0;
sdalink<=1;
end
end
IIC_icaddr:begin //输出芯片地址
if(SCL_l) begin
sdareg<=icaddr[bytecnt];
sdalink<=1;
end
end
IIC_icaddrask,IIC_regaddrask1,IIC_regaddrask2,
IIC_regaddrask3,IIC_regdataask1,IIC_regdataask2,
IIC_regdataask3:begin //输入应答信号
if(SCL_l)begin
sdareg<=0;
sdalink<=0;
end
end
IIC_regaddr1:begin
if(SCL_l) begin
sdareg<=regaddr1[bytecnt];
sdalink<=1;
end
end
IIC_regaddr2:begin
if(SCL_l) begin
sdareg<=regaddr2[bytecnt];
sdalink<=1;
end
end
IIC_regaddr3:begin
if(SCL_l) begin
sdareg<=regaddr3[bytecnt];
sdalink<=1;
end
end
IIC_regdata1:begin
if(SCL_l) begin
sdareg<=regdata1[bytecnt];
sdalink<=1;
end
end
IIC_regdata2:begin
if(SCL_l) begin
sdareg<=regdata2[bytecnt];
sdalink<=1;
end
end
IIC_regdata3:begin
if(SCL_l) begin
sdareg<=regdata3[bytecnt];
sdalink<=1;
end
end
IIC_stop:begin
if(SCL_h) begin
sdareg<=1;
sdalink<=1;
end
end
endcase
end
end
//数据寄存器控制:
always @(posedge clk or negedge rst)
begin
if(!rst)
bytecnt<=3'b0;
else
case(state)
IIC_icaddr,IIC_regaddr1,IIC_regaddr2,
IIC_regaddr3,IIC_regdata1,IIC_regdata2,
IIC_regdata3://传输 8 位数据或地址
if(SCL_l)
bytecnt<=bytecnt-1;
default: bytecnt<=3'd7;
endcase
end
最后设计仿真文件,得到如下波形图,进行结果的分析:
1.起始态与芯片地址发送
:
图中当使能 en 为 1 且复位 rst 打开时,可以开始传输,由图中黄线标注处可以看到,当 SCL 为高时,SDA 由高变低,传输开始。
接下来传输芯片地址,SDA 输出,由图中红框处可以看到,11110110
,正是我们的发出地址(芯片地址七位+0)。然后是 SDA 的输入状态,此时为高阻,手动输入 0,视为应答信号。
2.寄存器 1 地址和寄存器 1 数据:
接上图,芯片地址传输完成后,传输寄存器 1 地址,图中红框 1 处,为01001000,即0x48,然后高阻应答
,继续传输寄存器 1 数据,图中红框 2 处,为 01010101,即 0x55,然后高阻应答
。可见,与之前设计的数据一样,仿真设计成功。
3.寄存器 2 地址和寄存器 2 数据:
接上图,同理传输寄存器 2 的地址和数据:地址 01001001, 即
0x49
,数据 10101010,即 0xAA
。与设计一致。
4.寄存器 3 地址和寄存器 3 数据及终止态:
接上图,同理传输寄存器 3 的地址和数据:地址 01010000, 即
0x50
,数据 11001100,即 0xCC
。与设计一致。然后在下一个 SCL 为高时,
SDA 恢复高电
平,终止传输。
仿真结束,结果与预期一致,仿真成功,以下为 testbench:
`timescale 1ns / 1ps
module IIC_control_tb( );
reg clk,rst,en;
wire SCL,SDA;
always #50 clk=~clk; //产生 10MHz 时钟
initial begin
rst<=0;
clk<=0;
en<=0;
#2000 rst<=1;
#1000 en<=1;
end
IIC_control iic(
.clk(clk),
.rst(rst),
.SCL(SCL),
.SDA(SDA),
.en(en)
);
endmodule