一、TLV5618时序图
1.时序图
TLV5618是低功耗双路12位的电压输出DAC,兼容SPI串行接口。软件部分FPGA需要提供给TLV5618芯片的信号包括SPI时钟SCLK、片选信号CS_N、16位串行数据DIN。
时序图中一些参数的说明:
t
s
u
(
C
S
−
C
K
)
t_{su(CS-CK)}
tsu(CS−CK):CS低电平到SCLK第一个下降沿的最短时间,VDD=5V时为5ns。
t
w
(
H
)
t_{w(H)}
tw(H):SCLK高电平宽度,最短25ns。
t
w
(
L
)
t_{w(L)}
tw(L):SCLK低电平宽度,最短25ns。
t
s
u
(
D
)
t_{su(D)}
tsu(D):数据在SCLK下降沿到来前稳定建立的最短时间。
t
h
(
D
)
t_{h(D)}
th(D): 数据在SCLK下降沿到来后保持稳定的最短时间。
t
h
(
C
S
H
)
t_{h(CSH)}
th(CSH):两次传输之间片选信号保持高电平的最短时间。
2.设计要点
根据上述时序,得出以下设计要点:
(1)SCLK下降沿时,读取数据,数据保持稳定,DIN应在SCLK上升沿时变化。
(2)SCLK高低电平时间不低于25ns,即SCLK一个时钟周期应大于50ns,频率小于20Mhz。以系统时钟为50Mhz为例,取四分频12.5Mhz作为SCLK时钟。
(3)DIN发送串行数据时,先发送高位,后发送低位。
3.DIN数据格式
16位DIN串行数据由两部分组成:编程数据位(控制位)和数据位。
D15-D12为编程数据位:
D14位为速度控制位,1为快速模式,转换时间3us,0为低速模式,转换时间10us。
D13为电源控制位,1为待机模式,0为正常模式。
D15和D12组成寄存器选择位,00表示写数据到通道B和BUFFER;01表示写数据到BUFFER;10表示写数据到通道A,并将BUFFER的数据更新到通道B,这个模式可以同时对A、B两通道进行操作。
4.输出电压计算
TLV5618功能框图:
结合功能框图和芯片手册,可得出输出电压计算公式:
输出电压
=
2
∗
R
E
F
∗
C
O
D
E
2
n
输出电压=2*REF*\frac{CODE}{2^n}
输出电压=2∗REF∗2nCODE
REF为参考电压,由硬件电路决定;CODE为软件中设定的数值,即D11-D0;n为DAC的位数,取12位。
二、TLV5618代码设计
1.模块端口说明
端口名称 | 方向 | 说明 |
---|---|---|
Clk | input | 系统时钟 |
Rst_n | input | 系统复位 |
DAC_DATA[15:0] | input | 并行数据输入端 |
Start | input | DAC开始标志位 |
Conv_done | output | DAC完成标志位 |
DAC_CS_N | output | DAC片选 |
DAC_DIN | output | 串行数据输出端 |
DAC_SCLK | output | DAC工作时钟 |
data为并行数据输入端,用于配置DAC参数,设置输出电压值。DAC_CS_N、DAC_DIN、DAC_SCLK为FPGA与DAC芯片的接口。
2.代码设计
模块采用线性序列机编写SPI时序,可分为以下5个部分:数据缓存、分频计数器、生成序列计数器、SPI线性序列机、标志位赋值。
(1)数据缓存
接收到Start开始转换信号时,数据开始发送,为避免在发送过程中由于干扰导致输入的数据变化,在Start为高电平时,将输入数据data缓存到r_DAC_data中。
reg [15:0] r_DAC_DATA; //数据缓存
always@(posedge Clk or negedge Rst_n)//数据缓存
if(!Rst_n)
r_DAC_DATA <= 16'd0;
else if(Start)
r_DAC_DATA <= DAC_DATA;
else
r_DAC_DATA <= r_DAC_DATA;
(2)分频计数器
前面提到SCLK频率应小于20Mhz,取系统时钟的四分频12.5Mhz为SCLK的时钟。由于数据在SCLK上升沿时变化,下降沿时被DAC芯片读取,需计算SCLK二倍频以区分上升沿和下降沿。
parameter DIV_PARAM = 4; //分频系数 50/4 = 12.5Mhz
reg [7:0] DIV_cnt; //分频计数器
reg SCLK2X; //SCLK二倍频
always@(posedge Clk or negedge Rst_n)//分频计数器
if(!Rst_n)
DIV_cnt <= 8'd0;
else if(DAC_State) begin
if(DIV_cnt == DIV_PARAM/2 - 1)
DIV_cnt <= 8'd0;
else
DIV_cnt <= DIV_cnt + 1'b1;
end
else
DIV_cnt <= 8'd0;
always@(posedge Clk or negedge Rst_n)//SCLK二倍频
if(!Rst_n)
SCLK2X <= 1'b0;
else if(DAC_State && (DIV_cnt == DIV_PARAM/2 - 1))
SCLK2X <= 1'b1;
else
SCLK2X <= 1'b0;
(3)生成序列计数器
线性序列机在写代码时,应根据时序图列出信号在不同时刻的状态,这里根据时序图将SPI划分为34个状态,故生成序列计数器SCLK_GEN_CNT计数到33,对SCLK的边沿进行计数。
always@(posedge Clk or negedge Rst_n)//生成序列计数器,对SCLK脉冲进行计数
if(!Rst_n)
SCLK_GEN_CNT <= 6'd0;
else if(DAC_State)
if(SCLK2X) begin
if(SCLK_GEN_CNT == 33)
SCLK_GEN_CNT <= 6'd0;
else
SCLK_GEN_CNT <= SCLK_GEN_CNT + 1'b1;
end
else
SCLK_GEN_CNT <= SCLK_GEN_CNT;
else
SCLK_GEN_CNT <= 6'd0;
(4)SPI线性序列机
SCLK_GEN_CNT为偶数时,SCLK为上升沿,更改DIN数据;SCLK_GEN_CNT为奇数时,SCLK为下降沿,芯片读取DIN数据。16个SCLK时钟结束后,根据时序图,需要将SCLK和CS拉高。
always@(posedge Clk or negedge Rst_n)//线性序列机发送数据
if(!Rst_n) begin
DAC_SCLK <= 1'b1;
DAC_CS_N <= 1'b1;
DAC_DIN <= 1'b1;
end
else if(SCLK2X)
case(SCLK_GEN_CNT)
0:begin DAC_CS_N <= 1'b0; DAC_DIN <= r_DAC_DATA[15]; DAC_SCLK <= 1'b1; end
1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31: DAC_SCLK <= 1'b0;
2 :begin DAC_DIN <= r_DAC_DATA[14]; DAC_SCLK <= 1'b1; end
4 :begin DAC_DIN <= r_DAC_DATA[13]; DAC_SCLK <= 1'b1; end
6 :begin DAC_DIN <= r_DAC_DATA[12]; DAC_SCLK <= 1'b1; end
8 :begin DAC_DIN <= r_DAC_DATA[11]; DAC_SCLK <= 1'b1; end
10:begin DAC_DIN <= r_DAC_DATA[10]; DAC_SCLK <= 1'b1; end
12:begin DAC_DIN <= r_DAC_DATA[9]; DAC_SCLK <= 1'b1; end
14:begin DAC_DIN <= r_DAC_DATA[8]; DAC_SCLK <= 1'b1; end
16:begin DAC_DIN <= r_DAC_DATA[7]; DAC_SCLK <= 1'b1; end
18:begin DAC_DIN <= r_DAC_DATA[6]; DAC_SCLK <= 1'b1; end
20:begin DAC_DIN <= r_DAC_DATA[5]; DAC_SCLK <= 1'b1; end
22:begin DAC_DIN <= r_DAC_DATA[4]; DAC_SCLK <= 1'b1; end
24:begin DAC_DIN <= r_DAC_DATA[3]; DAC_SCLK <= 1'b1; end
26:begin DAC_DIN <= r_DAC_DATA[2]; DAC_SCLK <= 1'b1; end
28:begin DAC_DIN <= r_DAC_DATA[1]; DAC_SCLK <= 1'b1; end
30:begin DAC_DIN <= r_DAC_DATA[0]; DAC_SCLK <= 1'b1; end
32:DAC_SCLK <= 1'b1;
33:begin DAC_SCLK <= 1'b1; DAC_CS_N <= 1'b1; end
default:DAC_CS_N <= 1'b1;
endcase
(5)标志位赋值
对DAC_State和Conv_done进行赋值。开始转换时,DAC_State为高电平;结束转换时,DAC_State为低电平,Conv_done置1。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
DAC_State <= 1'b0;
else if(Start)
DAC_State <= 1'b1;
else if((SCLK_GEN_CNT == 33) && SCLK2X && DAC_State)
DAC_State <= 1'b0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Conv_done <= 1'b0;
else if((SCLK_GEN_CNT == 33) && SCLK2X && DAC_State)
Conv_done <= 1'b1;
else
Conv_done <= 1'b0;
DAC驱动源代码
module tlv5618(
input Clk, //系统时钟
input Rst_n, //系统复位
input [15:0] DAC_DATA, //并行数据输入端
input Start, //DAC开始标志位
output reg Conv_done, //DAC完成标志位
output reg DAC_CS_N, //DAC片选
output reg DAC_DIN, //串行数据输出端
output reg DAC_SCLK //DAC工作时钟
);
reg DAC_State; //DAC转换状态标志
reg [15:0] r_DAC_DATA; //数据缓存
parameter DIV_PARAM = 4; //分频系数 50/4 = 12.5Mhz
reg [7:0] DIV_cnt; //分频计数器
reg SCLK2X; //SCLK二倍频
reg [5:0] SCLK_GEN_CNT; //SCLK序列生成计数器
always@(posedge Clk or negedge Rst_n)//数据缓存
if(!Rst_n)
r_DAC_DATA <= 16'd0;
else if(Start)
r_DAC_DATA <= DAC_DATA;
else
r_DAC_DATA <= r_DAC_DATA;
always@(posedge Clk or negedge Rst_n)//分频计数器
if(!Rst_n)
DIV_cnt <= 8'd0;
else if(DAC_State) begin
if(DIV_cnt == DIV_PARAM/2 - 1)
DIV_cnt <= 8'd0;
else
DIV_cnt <= DIV_cnt + 1'b1;
end
else
DIV_cnt <= 8'd0;
always@(posedge Clk or negedge Rst_n)//SCLK二倍频
if(!Rst_n)
SCLK2X <= 1'b0;
else if(DAC_State && (DIV_cnt == DIV_PARAM/2 - 1))
SCLK2X <= 1'b1;
else
SCLK2X <= 1'b0;
always@(posedge Clk or negedge Rst_n)//生成序列计数器,对SCLK脉冲进行计数
if(!Rst_n)
SCLK_GEN_CNT <= 6'd0;
else if(DAC_State)
if(SCLK2X) begin
if(SCLK_GEN_CNT == 33)
SCLK_GEN_CNT <= 6'd0;
else
SCLK_GEN_CNT <= SCLK_GEN_CNT + 1'b1;
end
else
SCLK_GEN_CNT <= SCLK_GEN_CNT;
else
SCLK_GEN_CNT <= 6'd0;
always@(posedge Clk or negedge Rst_n)//线性序列机发送数据
if(!Rst_n) begin
DAC_SCLK <= 1'b1;
DAC_CS_N <= 1'b1;
DAC_DIN <= 1'b1;
end
else if(SCLK2X)
case(SCLK_GEN_CNT)
0:begin DAC_CS_N <= 1'b0; DAC_DIN <= r_DAC_DATA[15]; DAC_SCLK <= 1'b1; end
1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31: DAC_SCLK <= 1'b0;
2 :begin DAC_DIN <= r_DAC_DATA[14]; DAC_SCLK <= 1'b1; end
4 :begin DAC_DIN <= r_DAC_DATA[13]; DAC_SCLK <= 1'b1; end
6 :begin DAC_DIN <= r_DAC_DATA[12]; DAC_SCLK <= 1'b1; end
8 :begin DAC_DIN <= r_DAC_DATA[11]; DAC_SCLK <= 1'b1; end
10:begin DAC_DIN <= r_DAC_DATA[10]; DAC_SCLK <= 1'b1; end
12:begin DAC_DIN <= r_DAC_DATA[9]; DAC_SCLK <= 1'b1; end
14:begin DAC_DIN <= r_DAC_DATA[8]; DAC_SCLK <= 1'b1; end
16:begin DAC_DIN <= r_DAC_DATA[7]; DAC_SCLK <= 1'b1; end
18:begin DAC_DIN <= r_DAC_DATA[6]; DAC_SCLK <= 1'b1; end
20:begin DAC_DIN <= r_DAC_DATA[5]; DAC_SCLK <= 1'b1; end
22:begin DAC_DIN <= r_DAC_DATA[4]; DAC_SCLK <= 1'b1; end
24:begin DAC_DIN <= r_DAC_DATA[3]; DAC_SCLK <= 1'b1; end
26:begin DAC_DIN <= r_DAC_DATA[2]; DAC_SCLK <= 1'b1; end
28:begin DAC_DIN <= r_DAC_DATA[1]; DAC_SCLK <= 1'b1; end
30:begin DAC_DIN <= r_DAC_DATA[0]; DAC_SCLK <= 1'b1; end
32:DAC_SCLK <= 1'b1;
33:begin DAC_SCLK <= 1'b1; DAC_CS_N <= 1'b1; end
default:DAC_CS_N <= 1'b1;
endcase
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
DAC_State <= 1'b0;
else if(Start)
DAC_State <= 1'b1;
else if((SCLK_GEN_CNT == 33) && SCLK2X && DAC_State)
DAC_State <= 1'b0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Conv_done <= 1'b0;
else if((SCLK_GEN_CNT == 33) && SCLK2X && DAC_State)
Conv_done <= 1'b1;
else
Conv_done <= 1'b0;
endmodule
三、仿真
1.仿真文件
在仿真文件中发送16’habcd和16’hbbcc。
`timescale 1ns/1ns
module tlv5618_tb();
reg Clk,Rst_n,Start;
reg [15:0] DAC_DATA;
wire DAC_CS_N,DAC_DIN,DAC_SCLK;
wire Conv_done;
tlv5618 tlv5618(
.Clk(Clk),
.Rst_n(Rst_n),
.DAC_DATA(DAC_DATA),
.Start(Start),
.Conv_done(Conv_done),
.DAC_CS_N(DAC_CS_N),
.DAC_DIN(DAC_DIN),
.DAC_SCLK(DAC_SCLK)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 0;
DAC_DATA = 16'h0;
Start = 0;
#201;
Rst_n = 1;
#20;
DAC_DATA = 16'habcd;
Start = 1;
#20;
Start = 0;
wait(Conv_done);
#20000;
DAC_DATA = 16'hbbcc;
Start = 1;
#20;
Start = 0;
wait(Conv_done);
#20000;
$stop;
end
endmodule
2.仿真结果