实战篇:双通道幅频相可调的DDS信号发生器

目录

1. 功能

2. 输入信号

3. 输出信号

4. 模块划分


1. 功能

通过电脑串口发出命令,FPGA处理后连接DA转换器产生幅度、频率及相位可调的模拟信号。

2. 输入信号

电脑通过串口输入的命令或数据。

1)需要多字节输入:由于输入到FPGA的数据应该包含3种信息,其中,频率所实现的可调范围较广,从hz--->Mhz,光传输50MHZ这个频率的信息需要26位二进制表示,而串口协议一次传输8bit,因此需要传输4个字节(32bit),因此需要输入多个字节;

2)需要地址:由于PC通过串口输入,无法判断每次输入的信息是幅度还是频率,因此需要不同的标记信号,这里称为地址表示,而最少需要1个字节数据表示;

3)需要开始和结束标志:为了区别一次传输的开始和结束,需要至少2个字节来区别。

        输入信号的格式如下:

Byte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
帧头0帧头1寄存器地址数据数据数据数据帧尾
0xAA0x03AddrData[7:0]Data[15:8]Data[23:16]Data[31:24]0x88

3. 输出信号

供DA转换模块工作的DA_CLK,DA_CS,DA_DIN;

4. 模块划分

UART_CTRL模块:

        主要将电脑输入的8bit的串口信号接收并解码成两个通道的幅度、频率和相位数据输出。

难点:

1. 多个字节接收如何实现?

2. 目前常见的是uart传输8bit信息,但是如果要传输一帧,多个字节时,他们之间的间隔将多长?

        为很好的解决这个问题,先对uart_rev这部分先编程、仿真并调试。 然后在上面的基础上进行连续串口信号的接收、仿真及调试。

参考博文:实战篇:UART串口连续接收实现_weixin_41155462的博客-CSDN博客

DDS_CTRL模块:

        主要用解码输出的频率、相位、幅度信号,控制rom内寄存的正弦波曲线采样输出需要的曲线波形。

1)通过自定义正弦波曲线的.mif文件,生成rom存储器。

2)主要是将正弦波分成2^{N}个相位,然后利用相位累加器输出每个相位对应的幅值。输出频率主要通过频率控制位B,N以及系统时钟决定,f_{out}=B*f_{clk}/2^{N}.

        因此,需要三个输入变量,N位相位累加器,频率控制位B,以及模块时钟,来实现调频。调相,只需要在输出时增加一个偏移量。调幅,只需要对正弦波的幅值进行乘除运算。

        以上,只需要在ROM中存入一个正弦波,通过控制ROM的地址,使其输出不同的值,即可实现对该波形的调频调相和调幅。

3)由于使双通道,因此输入到DA模块的信号应该有两路不同的波形,即两路DDS模块应该有不同的两路控制信号的输入。为区分两路不同的控制信号,给两路dds信号进行地址区分,如下:

地址(十六进制)寄存器名寄存器位宽寄存器功能
10Fword032通道0频率控制字寄存器
11Fword132通道1频率控制字寄存器
12Pword012通道0频率相位字寄存器
13Pword112通道1频率相位字寄存器
14Aword04通道0频率幅度字寄存器
15Aword14通道1频率幅度字寄存器

  DA_CTRL_TOP模块

         由于DA模块采用TLV5618,其是双通道的数模转换,且通过SPI协议进行通讯,输入包含SCLK, DIN, CS三个信号。其中通道的选择在DIN的第13和第16Bit位控制。因此,除上面的对控制信号进行划分通道外,还需要产生DA模块通讯的时钟信号和不同通道所需的DIN通道选择和电源、速度的控制信号。

        此部分,通道的选择放在DA模块,专门用于两路信号输入,转换成串口信号输出到DA模块,代码如下:

module DA_CTRL_TOP(
	input clk,rst_n,
	input [9:0]data_a,data_b, 
	output wire SCLK,CS,DIN,
	output wire da_done);

reg [15:0]da_data;
wire DA_state;  //DA模块的转换状态,=1表示模块正在转换成串口信号输出,=0表示空闲,可以继续往DA模块中输入并行数据

//开始信号的产生
reg start;     //当输入的10bit信号转换为不同通道的da_data时,生成开始信号,da模块收到start信号后,开始将并行数据转换为串行数据输出//
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) start<=0;
	else if(start==0) start<=1;  //start =1,表示可以开始输出需要转换的数据给DA模块
	else if(da_done) start<=0;
	end

// 4bit控制字产生
wire [3:0]ctrl_world;
assign ctrl_world=(channel)?4'b0101:4'b1100;
// 通道选择
reg channel;
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) channel<=0;     
	else if(da_done) channel<=channel+1'b1;
	end
// 信号输出	
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) da_data<=16'd0;
	else begin
		case(channel)
		0: da_data<={ctrl_world,2'b00,data_a};
		1: da_data<={ctrl_world,2'b00,data_b};
		endcase
		end
	end
	
da_ctrl da_ctrl(
	.clk(clk),
	.rst_n(rst_n),
	.data_in(da_data),
	.start(start),
	.SCLK(SCLK),
	.CS(CS),
	.DIN(DIN),
	.da_done(da_done),
	.DA_state(DA_state));
	
endmodule

问题点:

1. CLK和EN应该如何选取?

        因为EN=1时,从ROM中依次读取数据,而DA模块输出串行的数字信号需要的时钟最大为20M,这样的话从DDS模块输出的data频率不宜太快,否则数据来不及转换。 

        因此,在DA转换模块输出每次转换结束的标志为da_done,收到此信号后,dds模块从rom中提取一个数据并进行频率相位幅度的转换,然后输出给DA模块。所以CLK为系统的时钟即可,所有的模块可以公用这一个时钟信号,EN为DDS片选信号使能,EN=1,选择DDS模块,是这一路数据成功输出并传给DA模块。

2. 代码如何区分两个dds的地址输入?

        通过decoder模块,将输入的信号通过地址解析出2个通道6个信号的数据,然后分别连接到两个dds模块。

decoder模块

         代码如下:

module decoder(
input clk,rst_n,
input [63:0]uart_fram_din,
input uart_fram_begin,uart_fram_end,
output reg [31:0]Fword0,Fword1,
output reg [11:0]Pword0,Pword1,
output reg [3:0]Aword0,Aword1,
output reg data_error,data_valid);

  // data  address
parameter Fword0_addr=8'h10,
			 Fword1_addr=8'h11,
			 Pword0_addr=8'h12,
			 Pword1_addr=8'h13,
			 Aword0_addr=8'h14,
			 Aword1_addr=8'h15;
			 
parameter  IDLE=4'b0000,
			  fram_begin=4'b0001,
			  fram_data=4'b0010,
			  fram_wait=4'b0100,
			  fram_end=4'b1000;
reg [3:0]main_state;

//input data register
reg [63:0]uart_fram_din_r;
reg uart_fram_end_r,uart_fram_begin_r;
wire uart_fram_end_flag,uart_fram_begin_flag;
wire [7:0]Addr;       //输入地址寄存,用于判断输入数据的类型
assign Addr=uart_fram_din_r[23:16];
assign uart_fram_end_flag=(~uart_fram_end_r & uart_fram_end)? 1'b1:1'b0;
assign uart_fram_begin_flag=(~uart_fram_begin_r & uart_fram_begin)? 1'b1:1'b0;

always @(posedge clk or negedge rst_n)begin
	if(~rst_n)begin
		uart_fram_din_r<=64'd0;
		uart_fram_end_r<=0;
		uart_fram_begin_r<=0;
		end
	else begin
		uart_fram_din_r<=uart_fram_din;
		uart_fram_end_r<=uart_fram_end;
		uart_fram_begin_r<=uart_fram_begin;
		end
	end		
	
//输入数据判断,并产生频率、相位、幅度控制信号	
always @(posedge clk or negedge rst_n)begin
	if(~rst_n) main_state<=IDLE;		
	else case (main_state) 
		IDLE:begin
			if(uart_fram_begin_flag) main_state<=fram_begin;
			else main_state<=IDLE;
			end
		fram_begin:begin
			if (uart_fram_din_r[15:0]==16'h0x03AA)	main_state<=fram_data;
			else main_state<=IDLE;
			end
		fram_data:begin
			main_state<=fram_wait;
			end
		fram_wait:begin
			if(uart_fram_din_r[63:56]==8'h0x88)  main_state<=fram_end;
			else main_state<=IDLE;
			end
		fram_end:begin
			main_state<=IDLE;
			end
		endcase
	end
always @(*)begin		
	if(~rst_n)begin
		Fword0<=32'd0;
		Fword1<=32'd0;
		Pword0<=12'd0;
		Pword1<=12'd0;
		Aword0<=4'd0;
		Aword1<=4'd0;
		end
	else begin
		case(main_state)
			IDLE:begin
				data_valid<=0;
				data_error<=0;
				end
			fram_begin:begin
				if(uart_fram_din_r[15:0]==16'h0x03AA) data_error<=1;
				else data_error<=data_error;
				end
			fram_data:begin
				data_valid<=1;
				case(Addr) 
					Fword0_addr: begin Fword0<=uart_fram_din_r[55:24]; end
					Fword1_addr: begin Fword1<=uart_fram_din_r[55:24]; end
					Pword0_addr: begin Pword0<=uart_fram_din_r[35:24]; end
					Pword1_addr: begin Pword1<=uart_fram_din_r[35:24]; end
					Aword0_addr: begin Aword0<=uart_fram_din_r[27:24]; end
					Aword1_addr: begin Aword1<=uart_fram_din_r[27:24]; end
					endcase
				end
			fram_wait:begin
				if(uart_fram_din_r[63:56]==8'h0x88)   data_valid<=1;
				else begin
					data_error<=1;
					data_valid<=data_valid;
					end
				end
			endcase
		end
	end
endmodule

DA_CTRL模块:

这里采用双通道的DA模块TL5618,通过两个DDS_CTRL模块提供两路数据。

问题点:

1. DA模块开始信号如何提供?DA什么时候处于工作状态?

        在DA_CTRL_TO模块中,复位后生成开始start信号给DA模块,DA模块收到起始标志信号后开始进行数据转换输出,在输出结束后,产生结束标志信号反馈给DDS模块。DDS模块收到反馈信号再次读取波形数据,传给DA模块。形成闭环。

此部分代码如下:

module da_ctrl(
	input clk,rst_n,
	input [15:0]data_in,   //通过同一个总线输入两路信号,经过DA模块转换输出两路模拟信号
	input start,
	output reg SCLK,CS,  
	output reg DIN,
	output wire da_done,
	output reg DA_state); //DA模块的转换状态,=1表示模块正在输出,=0表示空闲,可以继续接收信号

	
reg [15:0]data_in_r;
reg [1:0]start_r;
reg [5:0]clk_cnt;
reg [5:0]da_clk_cnt;
reg da_clk;    //产生2.5M的时钟
wire start_flag;
parameter [4:0]da_fre_cnt_num=5'd20;   //
assign start_flag = (!start_r[1] & start_r[0])? 1'b1:1'b0;  
assign da_done = (da_clk_cnt==6'd33)?1'b1:1'b0;

// input data register		//当start_flag=1,才开始寄存输入的信号,并转换为DA模块所需要的数据输出,没有开始标志信号,输入信号维持不变	 
always @(posedge clk or negedge rst_n)begin
	if(~rst_n)  data_in_r<=0;		
	else if (start_flag) data_in_r<=data_in;
	else  data_in_r<=data_in_r;		
	end

always @(posedge clk or negedge rst_n)begin
	if(~rst_n)begin
		start_r<=0;
		end
	else begin
		start_r[0]<=start;
		start_r[1]<=start_r[0];
		end
	end

always @(posedge clk or negedge rst_n)begin
	if(~rst_n) DA_state<=0;	
	else if(start_flag) DA_state<=1;
	else if(da_done) DA_state<=0;
	else DA_state<=DA_state;
	end
		
	
always @(posedge clk or negedge rst_n)begin
	if(~rst_n) clk_cnt<=0;		
	else if(DA_state) begin
		if(clk_cnt==da_fre_cnt_num-1'b1) clk_cnt<=0;
		else clk_cnt<=clk_cnt+1'b1;
		end
	else clk_cnt<=0;
	end
	
always @(posedge clk or negedge rst_n)begin
	if(~rst_n) da_clk<=0;
	else if(DA_state &(clk_cnt<=da_fre_cnt_num/2-1)) da_clk<=1;
	else da_clk<=0;
	end
	
always @(posedge clk or negedge rst_n)begin
	if(~rst_n) da_clk_cnt<=0;
	else if(da_clk_cnt>=6'd33) da_clk_cnt<=0;
	else begin
		if(DA_state & (clk_cnt==da_fre_cnt_num-1)) da_clk_cnt<=da_clk_cnt+1'b1;
		else da_clk_cnt<=da_clk_cnt;
		end
	end
		 
	
always @(posedge clk or negedge rst_n)begin
	if(~rst_n)begin
		SCLK<=0;
		DIN<=0;
		CS<=1;
		end
	else begin
		case(da_clk_cnt)
			6'd0: begin SCLK<=0; DIN<=data_in_r[15]; CS<=1; end
			6'd1: begin SCLK<=1; DIN<=data_in_r[15]; CS<=0; end
			6'd3: begin SCLK<=1; DIN<=data_in_r[14]; end
			6'd5: begin SCLK<=1; DIN<=data_in_r[13]; end
			6'd7: begin SCLK<=1; DIN<=data_in_r[12]; end
			6'd9: begin SCLK<=1; DIN<=data_in_r[11]; end
			6'd11: begin SCLK<=1; DIN<=data_in_r[10]; end
			6'd13: begin SCLK<=1; DIN<=data_in_r[9]; end
			6'd15: begin SCLK<=1; DIN<=data_in_r[8]; end
			6'd17: begin SCLK<=1; DIN<=data_in_r[7]; end
			6'd19: begin SCLK<=1; DIN<=data_in_r[6]; end
			6'd21: begin SCLK<=1; DIN<=data_in_r[5]; end
			6'd23: begin SCLK<=1; DIN<=data_in_r[4]; end
			6'd25: begin SCLK<=1; DIN<=data_in_r[3]; end
			6'd27: begin SCLK<=1; DIN<=data_in_r[2]; end
			6'd29: begin SCLK<=1; DIN<=data_in_r[1]; end
			6'd31: begin SCLK<=1; DIN<=data_in_r[0]; end
			
			6'd2,6'd4,6'd6,6'd8,6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32,6'd34:
				begin SCLK<=0; end			
			6'd33: begin SCLK<=0; CS<=1;end
         default: ;
		
		endcase			
	end
end	

endmodule

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值