FPGA|CPLD|ASICwilliam hill官网
直播中

梅雪松

12年用户 1236经验值
擅长:可编程逻辑 嵌入式技术 EDA/IC设计 控制/MCU
私信 关注
[资料]

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

本帖最后由 小梅哥 于 2014-11-25 16:43 编辑

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


TLC5620特性:

48位电压输出;

电源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,具体分配为:


A1
A0
DAC通道
0
0
DACA
0
1
DACB
1
0
DACC
1
1
DACD


表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  系统状态转移图

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

localparam         IDEL              = 1'b0,               
                         DO_CNT        = 1'b1;        




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对该驱动进行了仿真,详细的调试和应用,小梅哥将在下一个实验中介绍。

回帖(22)

那些年儿ing

2014-12-1 11:32:21
好资料 感谢楼主分享!
举报

张刚

2015-4-1 17:14:59
谢谢楼主,学习了。
举报

茂陵文渊

2015-7-8 22:44:59
我就在做这个,多谢楼主,真是要早点看见就好了
举报

tutu1583

2015-7-30 22:11:42
小梅哥,我有个问题,你是如何根据TLC5620的关键参数,写出时序的。不是要根据时序图和关键时间参数来书写书序的吗?一直我就被时序困扰,望详解。
举报

tutu1583

2015-7-30 22:26:29
引用: tutu1583 发表于 2015-7-30 22:11
小梅哥,我有个问题,你是如何根据TLC5620的关键参数,写出时序的。不是要根据时序图和关键时间参数来书写书序的吗?一直我就被时序困扰,望详解。

好像明白了。。。。。。。。。。。。
举报

wjh_yw

2015-7-31 07:33:04
学习学习,谢谢分享!
举报

PCBuio

2015-8-27 15:48:57
顶起小梅哥的资料  
举报

梦翼师兄

2015-9-16 21:48:36
一定要多看英文文档,小梅哥做到了
举报

monster1111

2015-11-30 22:55:50
QAQ太感谢这样的资料,正在做开发。
举报

林泉贤

2015-12-16 16:38:49
梅总,DAC视频在哪下载呢?
举报

樊冬冬

2015-12-28 18:04:18
受到警告
提示: 作者被禁止或删除 内容自动屏蔽
举报

王先生

2016-1-3 15:42:16
谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢谢
举报

李耀

2016-1-10 23:19:52
谢谢分享!                     
举报

言尽于此33

2016-1-11 09:17:05
顶。。。。。。。。。。。。。。。。。。。。。。
举报

liyantingmuzi

2016-1-14 12:22:11
谢谢小梅哥,学习呀
举报

说的是按时

2016-1-16 11:00:44
小梅哥6666666666666666666666666666666666666666666666666
举报

格古落

2016-1-19 16:44:28
小梅哥的资料是杠杠的,在下又学习到新东西了~
举报

U201015703

2016-1-21 20:53:46
怎么没人顶贴呢?
举报

U201015703

2016-1-21 20:54:12
怎么没人顶贴呢?
举报

更多回帖

发帖
×
20
完善资料,
赚取积分