Verilog 代码编写 IIC通信-主到从向芯片写入数据

题目:

        根据课堂讲授的基本原理,自己尝试编写一个 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

 

 

 

 

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Verilog是一种硬件描述语言(HDL),用于设计和描述数字电路。IIC(Inter-Integrated Circuit)是一种串行通信协议,常用于连接芯片与外设之间的通信IIC slave是指在IIC总线中作为从设备与主设备进行通信的硬件模块。主设备在总线上发出读取或写入的命令,而从设备会响应并执行相应的操作。 在Verilog实现IIC slave可以按照以下步骤进行: 1. 编写IIC slave的模块声明,包括输入信号(例如时钟和复位信号)和输出信号(例如数据线、时钟线、应答信号)。 2. 定义IIC slave的状态机,通过状态机来实现对主设备命令的响应。可以使用case语句或if-else语句来实现状态机。 3. 在状态机中,根据IIC协议来解析主设备的命令。当接收到读取命令时,从设备需要将对应的数据发送到数据线上;当接收到写入命令时,从设备需要接收主设备发送的数据。 4. 控制时钟信号,确保从设备的输出与主设备的通信同步。需要根据IIC协议的时序要求来控制时钟信号的生成和延迟。 5. 实现应答信号的逻辑,根据IIC协议的要求,从设备需要在收到读取命令或写入命令后,向主设备发送应答信号。 6. 编写仿真测试代码,对IIC slave模块进行功能验证。 通过以上步骤可以实现一个基本的Verilog IIC slave模块。进一步扩展可以增加更多的功能,例如处理多字节数据和完善状态机的错误处理等。根据具体的应用场景,还可以与其他硬件模块进行接口对接,实现更复杂的系统功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值