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

2017-1-9 17:04 1411 10 10

本实验中,我们使用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,具体分配为:


表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

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
10
关闭 站长推荐上一条 /3 下一条