原创 小梅哥和你一起深入学习FPGA之DAC驱动

2014-12-27 14:32 3110 18 23 分类: FPGA/CPLD 文集: FPGA深入学习

本实验中,我们使用FPGA来驱动了一片DAC芯片TLC5620,该芯片的特性如下所示:

 

TLC5620特性:

     4路8位电压输出;

     单电源5V供电;

     串行接口;

     参考电压输入高阻;

     可编程的1次或2次输出范围;

     同时更新的能力;

     内部自带上电复位功能;

     低功耗;

     半缓冲输出。

 

    小梅哥设计的该芯片的驱动模块的接口如下所示:



   各个端口定义如下:

以下是代码片段:

input Clk;

input Rst_n;

input Do_DA; /*使能单次转换*/

input [10:0]Data;/*{Addr1,Addr0,Range,Data_bit[7:0]}*/

output reg DAC_Dout; /*DAC数据线*/

output reg DAC_Clk; /*DAC时钟线,最高速度1M*/

output reg DAC_LDAC; /**/

output reg DAC_LOAD; /**/

output reg DA_Done; /*单次转换完成标志信号*/

    该芯片提供了类似于SPI的数字接口,因此,我们只需要使用该接口与芯片进行通信,再配合LOAD和LDAC两个控制线,即可实现对该DAC芯片的控制。TLC5620一次转换的操作时序如下:

 

图1 TLC5620单次转换时序图

 

    TLC5620每次写入的数据为11位,其中前两位为DAC选择位A1、A0,通过不同的组合可以选择不同通道的DAC,具体分配为:

20141227142335346.jpg

   表1 DAC通道选择位与对应通道关系

 

    第三位是电压输出增益位,0代表不变,1代表两倍,当设定参考电压为2.5V时,取这一位为1就可以得到最高5V的输出电压。后面8位是数据位,其中第四位是数据的最高位。对于TLC5620的输出电压公式是:

VO=VREF ×CODE/256×(1+RNG)

    VREF是参考电压,CODE是待转换的8位二进制代码,RNG是增益倍数。

 

    写入数据时,首先LOAD和LDAC写高电平,这样在CLK的每个下降沿写入的每位数据被锁存到DATA端,当11位数据传送完毕后,拉低LOAD,芯片根据前两位数据,判断是哪一路DAC通道,然后将8位数据移入相应的通道,进行DA转换,这时拉低LDAC,再拉高LDAC,就可以再下次转化之前,保持此次的模拟输出。

    TLC5620正常工作时的具体电压和时间参数如下表所示,通过该表,可知该芯片串行数字接口的时钟信号(CLK)最高为1MHz。该参数将作为我们采用FPGA产生TLC5620数字接口时钟的依据。同时,还有输入数据建立时间tsu(data-clk)为50ns,即,FPGA数据送出,到能够被TLC5620正常读取,至少需要50ns,因此FPGA单位数据输出保持时间不得少于50ns。tv(data-clk)为时钟下降沿到来后多久时间数据线上的数据才能被芯片内部采集,该时间确定了,时钟下降沿出现多久后,数据线上的数据可以被更新。tsu(LOAD-LDAC)为LOAD的上升沿到LDAC下降沿的建立时间,这里最小为0ns,因此忽略,即两者同时发生即可。tw(LDAC)为LDAC低电平所需的最短时间,为250ns。

表2 TLC5620关键参数

 

    通过对TLC5620一次完整转换的时序进行分析,列出以下序列机对应的序列点:该序列机总共包含26个点,其中,当Cnt1=0(ST0)时,为空闲态,ST1—ST22为数据发送状态,ST23时拉低LOAD,即将数据加载入对应通道的DAC中,ST24时释放LOAD,同时拉低LDAC,以产生LDAC的下降沿,将对应通道的模拟输出保持住。ST25拉高LDAC,完成一次转换。

 

ST0

Cnt1 == 0

DAC_Dout = 1; DAC_Clk = 0; DAC_LOAD = 1; DAC_LDAC = 1; DA_Done = 1;

ST1

Cnt1 == 1

DAC_Dout = Data_r[10]; DAC_Clk = 1; DA_Done = 0;

ST2

Cnt1 == 2

DAC_Clk = 0;

ST3

Cnt1 == 3

DAC_Dout = Data_r[9]; DAC_Clk = 1;

ST4

Cnt1 == 4

DAC_Clk = 0;

ST5

Cnt1 == 5

DAC_Dout = Data_r[8]; DAC_Clk = 1;

ST6

Cnt1 == 6

DAC_Clk = 0;

ST7

Cnt1 == 7

DAC_Dout = Data_r[7]; DAC_Clk = 1;

ST8

Cnt1 == 8

DAC_Clk = 0;

ST9

Cnt1 == 9

DAC_Dout = Data_r[6]; DAC_Clk = 1;

ST10

Cnt1 == 10

DAC_Clk = 0;

ST11

Cnt1 == 11

DAC_Dout = Data_r[5]; DAC_Clk = 1;

ST12

Cnt1 == 12

DAC_Clk = 0;

ST13

Cnt1 == 13

DAC_Dout = Data_r[4]; DAC_Clk = 1;

ST14

Cnt1 == 14

DAC_Clk = 0;

ST15

Cnt1 == 15

DAC_Dout = Data_r[3]; DAC_Clk = 1;

ST16

Cnt1 == 16

DAC_Clk = 0;

ST17

Cnt1 == 17

DAC_Dout = Data_r[2]; DAC_Clk = 1;

ST18

Cnt1 == 18

DAC_Clk = 0;

ST19

Cnt1 == 19

DAC_Dout = Data_r[1]; DAC_Clk = 1;

ST20

Cnt1 == 20

DAC_Clk = 0;

ST21

Cnt1 == 21

DAC_Dout = Data_r[0]; DAC_Clk = 1;

ST22

Cnt1 == 22

DAC_Clk = 0;

ST23

Cnt1 == 23

DAC_LOAD = 0;

ST24

Cnt1 == 24

DAC_LOAD = 1; DAC_LDAC = 0;

ST25

Cnt1 == 25

DAC_LDAC = 1; DA_Done = 1;

表3 TLC5620单次转换控制序列机

 

    序列机的计数器计数条件如下,

 

Cnt_state == 0 | Cnt1 == 25

Cnt1 <= 0;

Cnt_state == 1 & Cnt1 < 25

Cnt1 <= Cnt1 + 1’b1

表4 TLC5620序列机计数器计数条件

    线性序列机计数器Cnt1的控制代码如下:

以下是代码片段:
 always @(posedge Clk or negedge Rst_n)
 if(!Rst_n)
  Cnt1 <= 5'd0;
 else if(Cnt_State == DO_CNT)
 begin
  if(Cnt1 == 5'd25)
   Cnt1 <= 5'd0;
  else if(Cnt2 == Cnt2_Top)
   Cnt1 <= Cnt1 + 1'b1;
  else
   Cnt1 <= Cnt1; 
 end
 else
  Cnt1 <= 5'd0;

    其中,涉及到了两个状态,当Cnt_State = 0时,表示没有转换请求,即系统处于空闲状态,DAC不工作,当外部有转换请求时,则系统进入转换状态,每当计数使能信号到来时,Cnt1自加一,当Cnt1=25后,表明一次转换完成,将计数器清零,同时状态跳回空闲态,等待下一次使能信号的到来。具体的状态转移图如下所示:

图2 系统状态转移图

   该状态机的代码对应如下:

以下是代码片段:
 always @(posedge Clk or negedge Rst_n)
 if(!Rst_n)
  Cnt_State <= IDEL;
 else
 begin
  case(Cnt_State)
   IDEL:
    if(Do_DA)
     Cnt_State <= DO_CNT;
    else
     Cnt_State <= IDEL;
   
   DO_CNT:
    if(Cnt1 == 5'd25)
     Cnt_State <= IDEL;
    else
     Cnt_State <= DO_CNT;
   default:;
  endcase 
 end
    因此,我们,只需要将Do_DA给出1个时钟周期的高脉冲,即可启动一次转换。同时,在检测到该脉冲时,模块内部会将数据端口Data上的数据读入到内部数据寄存器中,代码如下:

以下是代码片段:
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)
  Data_r <= 10'd0;
 else if(Do_DA)
  Data_r <= Data;
 else
  Data_r <= Data_r;

    同时,为了产生1MHz的时钟,系统中使用了一个计数器Cnt2来专门产生该信号,该计数器对系统时钟进行计数,如当系统时钟为50M(周期为20ns)时,Cnt2计数到24,即计数了500ns,产生一个时钟周期的标志信号,则Cnt1在检测到这个标志信号后,便会自加1,因此,该标志信号出现两次则表明计时1000ns,对应时钟频率为1Mhz,即DAC芯片数字接口的时钟频率。该部分代码如下:

以下是代码片段:
 always @ (posedge Clk or negedge Rst_n)
 if(!Rst_n)
  Cnt2 <= 5'd0;
 else if(Cnt_State == DO_CNT)
 begin
  if(Cnt2 == Cnt2_Top)
   Cnt2 <= 5'd0;
  else
   Cnt2 <= Cnt2 + 1'b1; 
 end
 else
  Cnt2 <= 5'd0;

    为了兼容不同的系统时钟,这里采用参数化定制,得出对应的计数最大值,具体代码如下:

以下是代码片段:

Localparam system_clk = 50_000_000; /*系统时钟*/

Localparam Cnt2_Top = system_clk / 1_000_000 / 2 - 1; /*500ns技术器计数最大值*/

    系统时钟设置为50M,则计数最大值为50000000/1000000/2– 1 = 24,当系统时钟改变后,只需要修改system_clk的值,即可保证Cnt2计数一次的时间为500ns。

    最后,附上主序列中的操作代码:

以下是代码片段:
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)
 begin
  DAC_Dout <= 1;
  DAC_Clk <= 0;
  DAC_LOAD <= 1;
  DAC_LDAC <= 1;
  DA_Done <= 1; 
 end
 else
 begin
  case(Cnt1)
   0:
    begin
     DAC_Dout <= 1;
     DAC_Clk <= 0;
     DAC_LOAD <= 1;
     DAC_LDAC <= 1;
     DA_Done <= 1; 
    end
   1:begin DAC_Dout <= Data_r[10]; DAC_Clk <= 1;DA_Done <= 0;end
   2:DAC_Clk <= 0;
   3:begin DAC_Dout <= Data_r[9]; DAC_Clk <= 1;end
   4:DAC_Clk <= 0;
   5:begin DAC_Dout <= Data_r[8]; DAC_Clk <= 1;end
   6:DAC_Clk <= 0;
   7:begin DAC_Dout <= Data_r[7]; DAC_Clk <= 1;end
   8:DAC_Clk <= 0;
   9:begin DAC_Dout <= Data_r[6]; DAC_Clk <= 1;end
   10:DAC_Clk <= 0;
   11:begin DAC_Dout <= Data_r[5]; DAC_Clk <= 1;end
   12:DAC_Clk <= 0;
   13:begin DAC_Dout <= Data_r[4]; DAC_Clk <= 1;end
   14:DAC_Clk <= 0;
   15:begin DAC_Dout <= Data_r[3]; DAC_Clk <= 1;end
   16:DAC_Clk <= 0;
   17:begin DAC_Dout <= Data_r[2]; DAC_Clk <= 1;end
   18:DAC_Clk <= 0;
   19:begin DAC_Dout <= Data_r[1]; DAC_Clk <= 1;end
   20:DAC_Clk <= 0;
   21:begin DAC_Dout <= Data_r[0]; DAC_Clk <= 1;end
   22:DAC_Clk <= 0;
   23:DAC_LOAD <= 0;
   24:begin DAC_LOAD <= 1; DAC_LDAC <= 0; end
   25:begin DAC_LDAC <= 1; DA_Done <= 1; end
   default:;
  endcase   
 end

   该设计的仿真结果如下如所示:

 

 

    由该仿真结果可知,时钟频率为1MHz,满足芯片工作要求,其它时序均与手册给出的时序保持一致。为了设计简洁,这里将LOAD和LDAC的低电平脉冲时间都设置为了500ns,而非最小时间250ns,这里主要是为了方便序列机的设计。当然,如此设计在一定程度上会影响DAC 的转换速率,不过在大多数应用场合已经足够,如需更加高效的设计,只需要对代码稍加修改即可。

 

    本驱动的testbench编写较为简单,这里只附上对应代码,不做详细解释:

以下是代码片段:

`timescale 1ns/1ns

module TLC5620_Driver_tb;

 reg Clk;
 reg Rst_n;
 reg Do_DA; /*使能单次转换*/
 reg [10:0]Data;/*{Addr1,Addr0,Range,Data_bit[7:0]}*/
 
 wire DAC_Dout; /*DAC数据线*/
 wire DAC_Clk; /*DAC时钟线,最高速度1M*/
 wire DAC_LDAC; /**/
 wire DAC_LOAD; /**/
 
 wire DA_Done; /*单次转换完成标志信号*/
 
 TLC5620_Driver u1(
  .Clk(Clk),
  .Rst_n(Rst_n),
  .Do_DA(Do_DA),
  .Data(Data),
  .DAC_Dout(DAC_Dout),
  .DAC_Clk(DAC_Clk),
  .DAC_LDAC(DAC_LDAC),
  .DAC_LOAD(DAC_LOAD),
  .DA_Done(DA_Done)
 );
 
 initial begin
  Clk = 1;
  Rst_n = 0;
  Do_DA = 0;
  Data = 11'd0;
  #200;
  Rst_n = 1;
  #400;
  Data = 11'b110_1011_1001;
  Do_DA = 1;
  @(posedge DA_Done)
  Data = 11'b110_0000_1111;
  #20
  Do_DA = 1;
  #20;
  Do_DA = 0;
  @(posedge DA_Done)
  Data = 11'b110_1111_0000;
  #20
  Do_DA = 1;
  #20;
  Do_DA = 0;
  @(posedge DA_Done)
  #400;
  $stop; 
 end 
 
 always #10 Clk = ~Clk;
 
endmodule

    因为时间关系,这里只开发了该芯片的驱动,并用modelsim对该驱动进行了仿真,详细的调试和应用,小梅哥将在下一个实验中介绍。

PARTNER CONTENT

文章评论5条评论)

登录后参与讨论

用户1850535 2015-9-5 15:39

好东西

用户1767368 2015-1-4 16:43

还可以,楼主有心了

用户1790382 2014-12-12 11:55

不错,mark

用户1007969 2014-12-12 11:25

很实用

用户1711475 2014-11-27 13:59

内容有点长,但排版很清爽,看起来给人感觉不错,支持!!!

用户377235 2014-8-23 22:10

正在学习中。。。。

启芯 2014-8-11 12:23

SMBus和IIC还是有区别的,不然为什么现在服务器和笔记本里面挂的都是SMBus,而不是IIC呢?但不可否认的是,SMBus是由IIC发展而来的,但二者在直流特性、时序和协议等是不一样的,举个简单例子,IIC的最大漏电流是10uA,但SMBus只有1-5uA,具体的可以找个文档看看

用户377235 2014-8-9 11:10

有用

用户1773117 2014-8-9 08:42

敢问和IIC有什么区别。呵呵。

用户377235 2014-8-4 23:46

还不错
相关推荐阅读
小梅哥 2019-09-04 22:10
小梅哥FPGA时序分析笔记(6.2)深入现象看本质——庖丁解牛之FPGA内数据传输模型
通过上一节,我们了解了FPGA内部数据的传输形式,接下来我们就可以根据上一节的内容来总结一下FPGA内部的数据传输模型了。 时钟和数据传输路径 通过上一节内容中,我绘制的那个FPGA内部数据在逻辑...
小梅哥 2019-09-01 21:28
小梅哥FPGA时序分析笔记(6.1)深入现象看本质——庖丁解牛之FPGA可编程原理
上一次发博客,已经是2个月前了,这中间两个月,干了件很有意义的事情,尤其是对于自己来说,感觉学到了非常多的知识和经验,每天都很忙,忙到没时间逛网站博客,终于忙完闲下来了,连载的事情可不能忘,终于可以书...
小梅哥 2019-07-02 08:57
小梅哥FPGA时序分析笔记(五)I/O约束显神威——深入龙潭
大家一定对我上一节的突然结尾表示一脸茫然:我是来学习时序约束的,然后你告诉我时序约束里面IO约束很重要,然我又跟着你的文章继续往下看,本以为你就要讲如何进行IO约束了,结果呢,你一个取反时钟就把我们打...
小梅哥 2019-06-30 11:07
小梅哥FPGA时序分析笔记(四)I/O时序定成败——化险为夷
小梅哥FPGA时序分析从遥望到领悟系列没有遇见过I/O时序问题,没有通过I/O约束方式实际解决过I/O时序问题,就很难明白I/O约束的重要性,也很难相信各种EDA软件真的有那么的傻白甜。 我遇到的最...
小梅哥 2019-06-22 10:32
小梅哥FPGA时序分析笔记(三)时钟约束真重要——事实说话
小梅哥FPGA时序分析从遥望到领悟系列以前,那是在以前,经常有网友(原谅我行文动不动就是网友说,网友问,毕竟我是卖开发板的,正面接触学FPGA的网友相对多一些,所以这些也都是事实存在的事情)问我:小梅...
小梅哥 2019-06-21 10:33
小梅哥FPGA时序分析笔记(二)时钟质量是生命——初遇时序
小梅哥FPGA时序分析从遥望到领悟系列第一次遇到时序问题并通过相应的手段解决问题,算是2年前做百兆以太网图像传输的时候了吧。当时遇到的问题为:同一个工程,每次编译结果的效果都不一样,有的时候编译了,下...
EE直播间
更多
我要评论
5
18
关闭 站长推荐上一条 /1 下一条