一:DA控制原理
http://www.dzsc.com/data/2016-8-16/110442.html
二,DA通信协议
在TLC5615原理图中,DIN引脚为串行二进制输入端口,SCLK引脚是串行时钟输入端,CS是片选信号,DOUT引脚是用于级联的串行数据输出,AGND引脚是模拟地,REFIN引脚是基准电压输入端,OUT引脚是DA模拟电压输出端,VCC是电源电压输入端。
从图中可以知道,第一种工作模式是12位的,主要分为10位有效位和2位填充位,这2位填充位数据可以任意。第二种工作模式是16位,主要分为高4位虚拟位,10位有效位以及低2位填充位,这种模式一般用于级联方式,可以将本品的DOUT接到下一片的DIN.
从图中可以知道:当CS为低电平时,在每一个SCLK时钟的上升沿,将串行二进制输入端口DIN输入的数据,一位一位的移入到16位移位寄存器中,二进制最高位首先被移入进去,当DIN中的所有数据移入完了以后,片选信号CS将会被拉高,为10位DAC电路提供转的二进制数据进行转换。CS的上升和下降必须发生在SCLK为低电平期间,并且当CS为高电平时,串行输入数据不能被移入16位移位寄存器中。
DA的串行时钟和更新速率:
从图中可以知道,f(sclk)max可以通过1/(tw(ch)+tw(cl))这个公式进行计算的,这个公式中的tw(ch)和tw(cl)就是时序图中SCLK高电平和低电平持续的时间,通过查找TLC5615芯片手册可以知道他们都是25ns。因此f(sclk) max大约为14MHZ,TLC5615有两种工作方式,第一种是12位,第二种是16位,用16位的模式,可以计算出TLC5615转换一次需要16*(25+25)+20=820ns,然后可以算出f(sclk)max为1/820ns=1.21MHZ(理想情况),但是对于DA来说,信号从一个高变成一个低的陡变过程,它需要一定的建立时间,也就是12.5us,理想情况下它从一个电压跳到另一个电压需要80khz,实际上严格计算,只能达到75khz,也就是1/(820ns+12.5us)。想要生成一个完整周期信号,至少需要fmax的两倍,这样的话TCL5615的极限速度就是75KHZ/2=37.5KHZ。
三,DA实际应用
(1)将rom中的正弦波数据送给DA模块进行数模转换,使用示波器测量DA输出管脚,可以看到输出的1KHZ的正弦波形。
(3)verilog代码:
module Da_Data_Module
(
//输入端口
CLK_50M,RST_N,
//输出端口
da_data,da_start
);
//---------------------------------------------------------------------------
//-- 外部端口声明
//---------------------------------------------------------------------------
input CLK_50M; //时钟端口,开发板用的50M晶振
input RST_N; //复位端口,低电平复位
output [9:0] da_data; //从ROM中读出的DA数据
output da_start; //DA模块的开始标志位
//---------------------------------------------------------------------------
//-- 内部端口声明
//---------------------------------------------------------------------------
reg [7:0] time_cnt; //计数器
reg [7:0] time_cnt_n; //time_cnt的下一个状态
reg [9:0] rom_addr; //rom的地址端口
reg [9:0] rom_addr_n; //rom_addr的下一个状态
reg da_start; //DA模块的开始标志位
reg da_start_n; //da_start的下一个状态
//---------------------------------------------------------------------------
//-- 逻辑功能实现
//---------------------------------------------------------------------------
//例化ROM模块
ROM ROM_Init
(
.address (rom_addr ), //rom的地址端口
.clock (CLK_50M ), //rom的时钟端口
.q (da_data[9:2] ) //rom的数据端口
);
//时序电路,用来给time_cnt寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
time_cnt <= 8'h0; //初始化time_cnt值
else
time_cnt <= time_cnt_n; //用来给time_cnt赋值
end
//1KHz的正弦波,周期为1ms,即1ms送出一个完整的512个点的波形
//每个点的时间是1 / 512 * 1000000(ns) 我们采用开发板上的50Mhz晶振系统周期为20ns
//FPGA内部的计数器值为 1 / 512 * 1000000(ns) / 20 = 97
always @ (*)
begin
if(time_cnt == 8'd96) //判断计数器是否到97,从0开始即96
time_cnt_n = 8'h0; //如果到97就将time_cnt_n置0
else
time_cnt_n = time_cnt + 8'h1; //否则,time_cnt_n加1
end
//时序电路,用来给rom_addr寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
rom_addr <= 10'h0; //初始化time_cnt值
else
rom_addr <= rom_addr_n; //用来给time_cnt赋值
end
//组合电路,发送一个完整的512个点的波形,我们的mif文件中有1024个点
always @ (*)
begin
if(time_cnt == 8'd96) //判断计数器是否到97,从0开始即96
rom_addr_n = rom_addr + 10'h2;//如果到97,rom_addr_n加2,1024 / 2 = 512
else
rom_addr_n = rom_addr; //否则保持不变
end
//时序电路,用来给da_start寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
da_start <= 1'h0; //初始化da_start值
else
da_start <= da_start_n; //用来给da_start赋值
end
//组合电路,生成DA工作开始标识
always @ (*)
begin
if(time_cnt == 8'h0) //判断计数器的值
da_start_n = 1'h1; //如果计数器为0,标识为1
else
da_start_n = 1'h0; //否则计数器则为0
end
assign da_data[1:0] = 2'h0; //给da_data低两位赋值
endmodule
module Da_Module
(
//输入端口
CLK_50M,RST_N,
//TLC5615输出管脚
DA_CLK,DA_DIN,DA_CS,
//用户逻辑输入与输出
DA_DATA,send_start,send_finish
);
//---------------------------------------------------------------------------
//-- 外部端口声明
//---------------------------------------------------------------------------
input CLK_50M; //时钟的端口,开发板用的50M晶振
input RST_N; //复位的端口,低电平复位
output reg DA_CLK; //DA时钟端口
output DA_DIN; //DA数据输出端口
output reg DA_CS; //DA片选端口
input [ 9:0] DA_DATA; //DA数据的输入
input send_start; //DA工作开始标识
output send_finish; //DA工作完成标识
//---------------------------------------------------------------------------
//-- 内部端口声明
//---------------------------------------------------------------------------
reg [ 3:0] FSM_CS; //状态机的当前状态
reg [ 3:0] FSM_NS; //状态机的下一个状态
reg [ 3:0] bit_cnt; //用来记录数据发送个数的计数器
reg [ 3:0] bit_cnt_n; //bit_cnt的下一个状态
reg [11:0] shift_reg; //移位寄存器,将最高位数据移给DA_DIN
reg [11:0] shift_reg_n; //shift_reg的下一个状态
reg [ 3:0] time_cnt; //用于记录时钟个数的计数器
reg [ 3:0] time_cnt_n; //time_cnt的下一个状态
reg DA_CLK_N; //DA_CLK的下一个状态
reg DA_CS_N; //DA_CS的下一个状态
parameter FSM_IDLE = 4'h0; //状态机的空闲状态
parameter FSM_READY = 4'h1; //状态机的准备状态,将CS拉低
parameter FSM_SEND = 4'h2; //状态机的发送状态,发送12个数据
parameter FSM_FINISH= 4'h4; //状态的完成状态,将CS拉高
//---------------------------------------------------------------------------
//-- 逻辑功能实现
//---------------------------------------------------------------------------
//时序电路,用来给time_cnt寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
time_cnt <= 4'h0; //初始化time_cnt值
else
time_cnt <= time_cnt_n; //用来给time_cnt赋值
end
//组合电路,用于记录时钟个数的计数器
always @ (*)
begin
if(FSM_CS != FSM_NS) //判断状态机的当前状态
time_cnt_n = 4'h0; //如果当前的状态不等于下一个状态,计数器就清零
else if(DA_CLK != DA_CLK_N) //判断时钟的当前状态
time_cnt_n = 4'h0; //如果当前的时钟不等于下一个时钟状态,计数器清零
else
time_cnt_n = time_cnt + 4'h1;//否则计数器就加1
end
//时序电路,用来给bit_cnt寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
bit_cnt <= 4'h0; //初始化bit_cnt值
else
bit_cnt <= bit_cnt_n; //用来给bit_cnt赋值
end
//组合电路,用来记录数据发送个数的计数器
always @ (*)
begin
if(FSM_CS == FSM_FINISH) //判断状态机的当前状态
bit_cnt_n = 4'h0; //如果当前的状态不等于完成状态,bit_cnt_n就置0
else if(DA_CLK && (!DA_CLK_N)) //判断时钟的当前状态
bit_cnt_n = bit_cnt + 4'h1;//如果当前的时钟等于下一个时钟取非的状态,bit_cnt_n就加1
else
bit_cnt_n = bit_cnt; //否则保持不变
end
//时序电路,用来给shift_reg寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
shift_reg <= 12'h0; //初始化shift_reg值
else
shift_reg <= shift_reg_n; //用来给shift_reg赋值
end
//组合电路,移位寄存器,将DA_DATA的数据依次移给DA_DIN
always @ (*)
begin
if(send_start) //判断开始标识
shift_reg_n = {DA_DATA,2'h0};//如果标志为1,则将DA_DATA的数据赋值给移位寄存器
else if(DA_CLK && (time_cnt == 4'h0))//判断DA_CLK的状态
shift_reg_n = {shift_reg[10:0] , 1'h0};//如果DA_CLK为高,移位寄存器开始移位
else
shift_reg_n = shift_reg; //否则保持不变
end
//---------------------------------------------------------------------------
//-- 状态机
//---------------------------------------------------------------------------
//时序电路,用来给FSM_CS寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
FSM_CS <= FSM_IDLE; //初始化FSM_CS值
else
FSM_CS <= FSM_NS; //用来给FSM_CS赋值
end
//组合电路,用来实现状态机
always @ (*)
begin
case(FSM_CS) //判断状态机的当前状态
FSM_IDLE:
if(send_start) //判断开始标识
FSM_NS = FSM_READY; //如果标识符为1,则进入准备状态
else
FSM_NS = FSM_CS; //否则保持原状态不变
FSM_READY:
if(time_cnt == 4'h1) //等待两个时钟
FSM_NS = FSM_SEND; //两个时钟到了便进入发送状态
else
FSM_NS = FSM_CS; //否则保持原状态不变
FSM_SEND:
if((bit_cnt == 4'hC)&&(!DA_CLK))//发送数据12个
FSM_NS = FSM_FINISH; //发送完成进入完成状态
else
FSM_NS = FSM_CS; //否则保持原状态不变
FSM_FINISH:
if(time_cnt == 4'h2) //等待三个时钟
FSM_NS = FSM_IDLE; //完成一次数据转换,进入下一次转换
else
FSM_NS = FSM_CS; //否则保持原状态不变
default:FSM_NS = FSM_IDLE;
endcase
end
//时序电路,用来给DA_CS寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
DA_CS <= 1'h1; //初始化DA_CS值
else
DA_CS <= DA_CS_N; //用来给DA_CS赋值
end
//组合电路,用来生成DA的片选波形
always @ (*)
begin
if(FSM_CS == FSM_READY) //判断状态机的当前状态
DA_CS_N = 1'h0; //如果当前的状态等于准备状态,DA_CS_N就置0
else if((FSM_CS == FSM_FINISH) && (time_cnt == 4'h1))//判断状态机的当前状态
DA_CS_N = 1'h1; //如果当前的状态等于完成状态并且时钟计数器等于1,DA_CS_N就置1
else
DA_CS_N = DA_CS; //否则保持不变
end
//时序电路,用来给DA_CLK寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
DA_CLK <= 1'h0; //初始化DA_CLK值
else
DA_CLK <= DA_CLK_N; //用来给DA_CLK赋值
end
//组合电路,用来生成DA的时钟波形
always @ (*)
begin
if((FSM_CS == FSM_SEND) && (!DA_CLK) && (time_cnt == 4'h1))//判断状态机的当前状态
DA_CLK_N = 1'h1; //如果符合上述条件,每两个时钟就会生成一个高电平的DA_CLK
else if((FSM_CS == FSM_SEND) && (DA_CLK) && (time_cnt == 4'h1))//判断状态机的当前状态
DA_CLK_N = 1'h0; //如果符合上述条件,每两个时钟就会生成一个低电平的DA_CLK
else
DA_CLK_N = DA_CLK; //否则保持不变
end
assign DA_DIN = shift_reg[11]; //将移位寄存器的最高位赋值给DA_DIN
assign send_finish = (FSM_CS == FSM_IDLE); //标识发送完成
endmodule