集成电路技术分享

 找回密码
 我要注册

QQ登录

只需一步,快速开始

搜索
查看: 2547|回复: 5

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

[复制链接]
小梅哥 发表于 2014-12-27 13:20:10 | 显示全部楼层 |阅读模式
小梅哥和你一起深入学习FPGA之DAC驱动
本实验中,我们使用FPGA来驱动了一片DAC芯片TLC5620,该芯片的特性如下所示:

TLC5620特性:
4路8位电压输出;
单电源5V供电;
串行接口;
参考电压输入高阻;
可编程的1次或2次输出范围;
同时更新的能力;
内部自带上电复位功能;
低功耗;
半缓冲输出。

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

各个端口定义如下:
  1. input Clk;
  2.         input Rst_n;
  3.         input Do_DA;        /*使能单次转换*/
  4.         input [10:0]Data;/*{Addr1,Addr0,Range,Data_bit[7:0]}*/
  5.        
  6.         output reg DAC_Dout;        /*DAC数据线*/
  7.         output reg DAC_Clk;        /*DAC时钟线,最高速度1M*/
  8.         output reg DAC_LDAC;        /**/
  9.         output reg DAC_LOAD;        /**/
  10.        
  11.         output reg DA_Done;        /*单次转换完成标志信号*/
复制代码


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

图1  TLC5620单次转换时序图

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

表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,完成一次转换。

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

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

表4  TLC5620序列机计数器计数条件
线性序列机计数器Cnt1的控制代码如下:
  1. always @(posedge Clk or negedge Rst_n)
  2.         if(!Rst_n)
  3.                 Cnt1 <= 5'd0;
  4.         else if(Cnt_State == DO_CNT)
  5.         begin
  6.                 if(Cnt1 == 5'd25)
  7.                         Cnt1 <= 5'd0;
  8.                 else if(Cnt2 == Cnt2_Top)
  9.                         Cnt1 <= Cnt1 + 1'b1;
  10.                 else
  11.                         Cnt1 <= Cnt1;       
  12.         end
  13.         else
  14.                 Cnt1 <= 5'd0;
复制代码

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

图2  系统状态转移图
该状态机的代码对应如下:
  1. localparam         IDEL = 1'b0,
  2.                                 DO_CNT        = 1'b1;

  3.         always @(posedge Clk or negedge Rst_n)
  4.         if(!Rst_n)
  5.                 Cnt_State <= IDEL;
  6.         else
  7.         begin
  8.                 case(Cnt_State)
  9.                         IDEL:
  10.                                 if(Do_DA)
  11.                                         Cnt_State <= DO_CNT;
  12.                                 else
  13.                                         Cnt_State <= IDEL;
  14.                        
  15.                         DO_CNT:
  16.                                 if(Cnt1 == 5'd25)
  17.                                         Cnt_State <= IDEL;
  18.                                 else
  19.                                         Cnt_State <= DO_CNT;
  20.                         default:;
  21.                 endcase       
  22.         end
复制代码

因此,我们,只需要将Do_DA给出1个时钟周期的高脉冲,即可启动一次转换。同时,在检测到该脉冲时,模块内部会将数据端口Data上的数据读入到内部数据寄存器中,代码如下:
  1. always@(posedge Clk or negedge Rst_n)
  2.         if(!Rst_n)
  3.                 Data_r <= 10'd0;
  4.         else if(Do_DA)
  5.                 Data_r <= Data;
  6.         else
  7.                 Data_r <= Data_r;
复制代码

同时,为了产生1MHz的时钟,系统中使用了一个计数器Cnt2来专门产生该信号,该计数器对系统时钟进行计数,如当系统时钟为50M(周期为20ns)时,Cnt2计数到24,即计数了500ns,产生一个时钟周期的标志信号,则Cnt1在检测到这个标志信号后,便会自加1,因此,该标志信号出现两次则表明计时1000ns,对应时钟频率为1Mhz,即DAC芯片数字接口的时钟频率。该部分代码如下:
  1. always @ (posedge Clk or negedge Rst_n)
  2.         if(!Rst_n)
  3.                 Cnt2 <= 5'd0;
  4.         else if(Cnt_State == DO_CNT)
  5.         begin
  6.                 if(Cnt2 == Cnt2_Top)
  7.                         Cnt2 <= 5'd0;
  8.                 else
  9.                         Cnt2 <= Cnt2 + 1'b1;       
  10.         end
  11.         else
  12.                 Cnt2 <= 5'd0;
复制代码

为了兼容不同的系统时钟,这里采用参数化定制,得出对应的计数最大值,具体代码如下:
  1. Localparam   system_clk = 50_000_000;        /*系统时钟*/
  2.         Localparam   Cnt2_Top = system_clk / 1_000_000 / 2 - 1;        /*500ns技术器计数最大值*/
复制代码


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

最后,附上主序列中的操作代码:
  1. always@(posedge Clk or negedge Rst_n)
  2.         if(!Rst_n)
  3.         begin
  4.                 DAC_Dout <= 1;
  5.                 DAC_Clk <= 0;
  6.                 DAC_LOAD <= 1;
  7.                 DAC_LDAC <= 1;
  8.                 DA_Done <= 1;       
  9.         end
  10.         else
  11.         begin
  12.                 case(Cnt1)
  13.                         0:
  14.                                 begin
  15.                                         DAC_Dout <= 1;
  16.                                         DAC_Clk <= 0;
  17.                                         DAC_LOAD <= 1;
  18.                                         DAC_LDAC <= 1;
  19.                                         DA_Done <= 1;       
  20.                                 end
  21.                         1:begin DAC_Dout <= Data_r[10]; DAC_Clk <= 1;DA_Done <= 0;end
  22.                         2:DAC_Clk <= 0;
  23.                         3:begin DAC_Dout <= Data_r[9]; DAC_Clk <= 1;end
  24.                         4:DAC_Clk <= 0;
  25.                         5:begin DAC_Dout <= Data_r[8]; DAC_Clk <= 1;end
  26.                         6:DAC_Clk <= 0;
  27.                         7:begin DAC_Dout <= Data_r[7]; DAC_Clk <= 1;end
  28.                         8:DAC_Clk <= 0;
  29.                         9:begin DAC_Dout <= Data_r[6]; DAC_Clk <= 1;end
  30.                         10:DAC_Clk <= 0;
  31.                         11:begin DAC_Dout <= Data_r[5]; DAC_Clk <= 1;end
  32.                         12:DAC_Clk <= 0;
  33.                         13:begin DAC_Dout <= Data_r[4]; DAC_Clk <= 1;end
  34.                         14:DAC_Clk <= 0;
  35.                         15:begin DAC_Dout <= Data_r[3]; DAC_Clk <= 1;end
  36.                         16:DAC_Clk <= 0;
  37.                         17:begin DAC_Dout <= Data_r[2]; DAC_Clk <= 1;end
  38.                         18:DAC_Clk <= 0;
  39.                         19:begin DAC_Dout <= Data_r[1]; DAC_Clk <= 1;end
  40.                         20:DAC_Clk <= 0;
  41.                         21:begin DAC_Dout <= Data_r[0]; DAC_Clk <= 1;end
  42.                         22:DAC_Clk <= 0;
  43.                         23:DAC_LOAD <= 0;
  44.                         24:begin DAC_LOAD <= 1; DAC_LDAC <= 0; end
  45.                         25:begin DAC_LDAC <= 1; DA_Done <= 1; end
  46.                         default:;
  47.                 endcase                       
  48.         end
复制代码

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

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

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

  2. module TLC5620_Driver_tb;

  3.         reg Clk;
  4.         reg Rst_n;
  5.         reg Do_DA;        /*使能单次转换*/
  6.         reg [10:0]Data;/*{Addr1,Addr0,Range,Data_bit[7:0]}*/
  7.        
  8.         wire DAC_Dout;        /*DAC数据线*/
  9.         wire DAC_Clk;        /*DAC时钟线,最高速度1M*/
  10.         wire DAC_LDAC;        /**/
  11.         wire DAC_LOAD;        /**/
  12.        
  13.         wire DA_Done;        /*单次转换完成标志信号*/
  14.        
  15.         TLC5620_Driver u1(
  16.                 .Clk(Clk),
  17.                 .Rst_n(Rst_n),
  18.                 .Do_DA(Do_DA),
  19.                 .Data(Data),
  20.                 .DAC_Dout(DAC_Dout),
  21.                 .DAC_Clk(DAC_Clk),
  22.                 .DAC_LDAC(DAC_LDAC),
  23.                 .DAC_LOAD(DAC_LOAD),
  24.                 .DA_Done(DA_Done)
  25.         );
  26.        
  27.         initial begin
  28.                 Clk = 1;
  29.                 Rst_n = 0;
  30.                 Do_DA = 0;
  31.                 Data = 11'd0;
  32.                 #200;
  33.                 Rst_n = 1;
  34.                 #400;
  35.                 Data = 11'b110_1011_1001;
  36.                 Do_DA = 1;
  37.                 @(posedge DA_Done)
  38.                 Data = 11'b110_0000_1111;
  39.                 #20
  40.                 Do_DA = 1;
  41.                 #20;
  42.                 Do_DA = 0;
  43.                 @(posedge DA_Done)
  44.                 Data = 11'b110_1111_0000;
  45.                 #20
  46.                 Do_DA = 1;
  47.                 #20;
  48.                 Do_DA = 0;
  49.                 @(posedge DA_Done)
  50.                 #400;
  51.                 $stop;       
  52.         end       
  53.        
  54.         always #10 Clk = ~Clk;
  55.        
  56. endmodule
复制代码

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?我要注册

x
 楼主| 小梅哥 发表于 2014-12-27 13:27:51 | 显示全部楼层
这里附上小梅哥的整个工程,供需要的同学学习。另外,虽然这里小梅哥没有将下板验证的部分贴上来,但是小梅哥自己是做了板级验证的,该代码绝对可用。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?我要注册

x
gcrasy 发表于 2014-12-28 23:08:34 | 显示全部楼层
很好的资料,谢谢楼主的分享
sevil 发表于 2016-12-5 14:26:49 | 显示全部楼层
很有用,谢谢分享
vvivv 发表于 2016-12-5 15:37:55 | 显示全部楼层
很好的资源,谢谢楼主的分享
雷磊 发表于 2022-7-9 16:24:15 | 显示全部楼层
SDRAM时序控制技术文档
http://www.fpgaw.com/forum.php?m ... 7&fromuid=54563
(出处: fpga论坛|fpga设计论坛)
您需要登录后才可以回帖 登录 | 我要注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|小黑屋|手机版|Archiver|fpga论坛|fpga设计论坛 ( 京ICP备20003123号-1 )

GMT+8, 2024-11-16 15:54 , Processed in 0.075591 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表