学习笔记:FPGA学习之DA通信协议

一: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的正弦波形。

(2)功能模块:

(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

猜你喜欢

转载自blog.csdn.net/Archar_Saber/article/details/82530780