600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 基于FPGA的波 幅 频 相可调DDS信号发生器的设计

基于FPGA的波 幅 频 相可调DDS信号发生器的设计

时间:2022-01-09 20:32:47

相关推荐

基于FPGA的波 幅 频 相可调DDS信号发生器的设计

声明:本文只对设计原理和过程作粗略的阐述,详细可以研究我贴出来的完整源代码,也可以私信交流。

若干略缩语解释:

FPGA(Field Programmable Gate Array):现场可编程逻辑门阵列

DDS(Direct Digital Synthesizer):直接数字频率合成技术

ROM(Read-Only Memory):只读存储器

DAC(Digital to Analog Convertor):数字模拟转换器

MUX(multiplexer​​​​​​​):数据选择器

​​​​​​​

一、设计思路:

DDS(Direct Digital Synthesizer)即直接数字频率合成技术,其具体原理在这就不赘述了,不了解的可以自己查阅资料。

利用DDS来实现信号发生器,首先就是要把波形数据存在ROM里,然后在时钟信号的控制下,用ROM的地址对ROM里的波形数据进行查表,从而将ROM里的波形数据读出,最后通过DA转换输出模拟信号的波形。

要实现波形可调,信号发生器就要能分时输出多种波形,本次设计选用了4种常用的模拟信号:正弦波、锯齿波、方波、三角波,那么就需要4个ROM来分别存储4种波形的波形数据,在写Verilog代码时,直接调用BLOCK ROM的IP核即可。

要实现波、幅、频、相可调,就需要在输入端采用某种方式来控制波形、幅度、频率和相位的改变,这个方式可以是按键、矩阵键盘、上位机等等,本次设计选择最简单的按键方式(按下时为高电平):每次按键按下时,改变波形、幅度、频率和相位。既然使用了按键,那么为了消除按键按下和松开时的机械干扰,在写Verilog代码时,按键消抖模块就必不可少。

消抖后的按键信号进入一个DDS控制模块,该模块根据相应按键的次数计算波形、幅度、频率和相位的值(本次设计可实现幅度1到15倍整数可调、频率1到50倍整数倍频、相位15度的整数倍可调),从而产生2位波形控制信号、4位幅度控制信号、6位频率控制信号、9位相位控制信号。这些信号控制ROM的地址,从而控制对ROM里的数据的读操作。

最后,利用一个4选1的MUX(用波形控制信号来做地址)即可实现12位数字波形数据的输出。

根据以上分析,作出本设计功能框图如图1所示:

图1

由于我个人的开发板上没有DAC,所以没有设计DAC模块,不过可以在仿真时,用Vivado软件自带的仿真工具直接把12位数字波形输出显示为模拟形式就能看到模拟波形。(这里说一句Vivado牛批)

二、设计过程:

2.1 按键消抖模块:

2.1.1 基本原理:

由于机械按键里有一个复位弹簧,相当于一个欠阻尼二阶振荡系统,具有一定的惯性,所以当按键按下和松开时,会先振荡一段时间,然后才稳定到一个确定的电平。图2为理想和实际的按键电平时序图:

图2

这些振荡就称为按键抖动,毫无疑问,这种意料之外的电平抖动会对逻辑造成干扰,必须滤除(消抖)。常用的按键消抖方式有硬件消抖和软件消抖,前者是在按键后加一个硬件滤波电路,如RC电路等等;后者是通过类似中断的方式,当按键按下或松开时,先经过一定时间的延时(一般为20ms),之后再判断按键的状态是否已经稳定。

本次设计采用软件消抖,并利用Verilog代码编写状态机来实现。具体是将按键消抖的整个过程分为4个状态:

状态S0:即空闲态,该状态等待按键按下,若按键按下,则跳转到S1;若按键未按下,则不跳转。

状态S1:该状态在按键按下时控制一个计时器进行计时,若计时器还未计到20ms,按键电平就拉低,说明产生了一次按下抖动,状态不跳转且计时器清零;若计时器计到20ms,按键电平还未拉低,说明按下已经稳定,计时器清零且状态跳转到S2。

状态S2:该状态等待按键松开,若按键松开,则跳转到S3;若按键未松开,则不跳转。

状态S3:该状态在按键松开时控制一个计时器进行计时,若计时器还未计到20ms,按键电平就拉高,说明产生了一次松开抖动,状态不跳转且计时器清零;若计时器计到20ms,按键电平还未拉高,说明松开已经稳定,计时器清零且状态跳转到S0。

上述状态机的状态转换,是由按键按下时产生的上升沿和按键松开时产生的下降沿来触发的。因此,首先就需要采集这两个边沿,本设计采用一个2位寄存器来实现边沿采集,如图3:

图3

由上图可知,若某一时刻,D1为“0”且D0为“1”,则采集到按键输入的一个上升沿;若某一时刻,D1为“1”且D0为“0”,则采集到按键输入的一个下降沿。

至此,可以得到按键消抖状态转换图如图4(I和O代表按键输入和消抖输出的逻辑值):

图4

2.1.2 具体描述:

根据状态机,就可以用RTL语言将按键消抖模块描述出来,编写的Verilog代码如下,状态机部分采用三段式描述:

`timescale 1ms / 1psmodule KEY_filter(input clk, //50MHz主时钟input rst, //复位信号,高电平有效input key_in, //输入的按键信号,按下时为高电平output key_filter //消抖后的按键信号);reg [1:0] key_test; //按键边沿检测寄存器reg key_filter_R; //消抖后的按键信号寄存器/*************检测边沿************/ always @ (posedge clk)key_test <= {key_test[0],key_in};reg [1:0] c_sta = 2'd0; //现态reg [1:0] n_sta = 2'd0; //次态reg [19:0] cnt = 20'd0; //计时器/**********计时器控制**********/always @ (posedge clk or negedge rst)beginif (rst)cnt <= 20'd0;else if (cnt == 20'd999999)cnt <= 20'd0;elsecase (n_sta)2'd0:cnt <= 20'd0;2'd1:beginif ( (key_test == 2'b10 && cnt < 20'd999999) || (key_test != 2'b10 && cnt == 20'd999999) )cnt <= 20'd0;elsecnt <= cnt + 1'd1;end2'd2:cnt <= 20'd0;2'd3:beginif ( (key_test == 2'b01 && cnt < 20'd999999) || (key_test != 2'b01 && cnt == 20'd999999) )cnt <= 20'd0;elsecnt <= cnt + 1'd1;enddefault:;endcaseend/*************状态的转换***********/always @ (posedge clk or negedge rst)beginif (rst)c_sta <= 2'd0;elsec_sta <= n_sta;end/*************各状态转换条件***********/always @ (*)begincase(c_sta)2'd0:beginif (key_test == 2'b01)n_sta = 2'd1;elsen_sta = 2'd0;end2'd1:beginif (key_test == 2'b10 && cnt < 20'd999999)n_sta = 2'd1;else if (key_test != 2'b10 && cnt == 20'd999999)n_sta = 2'd2;elsen_sta = 2'd1;end2'd2:beginif (key_test == 2'b10)n_sta = 2'd3;elsen_sta = 2'd2;end2'd3:beginif (key_test == 2'b01 && cnt < 20'd999999)n_sta = 2'd2;else if (key_test != 2'b01 && cnt == 20'd999999)n_sta = 2'd0;elsen_sta = 2'd3;enddefault:;endcaseend/*************各状态的输出***********/always @ (posedge clk or negedge rst)beginif (rst)key_filter_R <= 1'd0;elsecase (n_sta)2'd0:key_filter_R <= 1'd0;2'd2:key_filter_R <= 1'd1;default:;endcaseend/*************给消抖后的按键信号赋值***********/assign key_filter = key_filter_R;endmodule

2.1.3 仿真测试:

编写的testbeach如下:

`timescale 1ms / 1psmodule TB_KEY_filter();reg clk; //50MHz主时钟reg rst; //复位信号,高电平有效reg key_in; //输入的按键信号,按下时为高电平wire key_filter; //消抖后的按键信号KEY_filter uut_KEY_filter(.clk(clk), //50MHz主时钟.rst(rst), //复位信号,高电平有效.key_in(key_in), //输入的按键信号,按下时为低电平.key_filter(key_filter) //消抖后的按键信号 );/**************产生50MHz时钟**************/ always #0.00001 clk = ~clk;/**************输入初始化**************/ initial beginclk = 1'd0;rst = 1'd1;key_in = 1'd0;/**************按键第一次按下**************/ #0.1rst = 1'd0;key_in = 1'd1;/**************模拟按下时的抖动**************/ #0.1key_in = 1'd0;#0.2key_in = 1'd1;#0.1key_in = 1'd0;#0.2key_in = 1'd1;/**************按键第一次松开**************/ #50key_in = 1'd0;/**************模拟松开时的抖动**************/ #0.1key_in = 1'd1;#0.1key_in = 1'd0;#0.2key_in = 1'd1;#0.1key_in = 1'd0;endendmodule

在Vivado自带的仿真器上跑出的按键消抖模块仿真结果如图5至图8所示:

图5

由上图可以看到,在按键按下抖动期间,按键消抖模块的输出依然为“0”。

图6

由上图可以看到,在按键松开抖动期间,按键消抖模块的输出依然为“1”。

图7

由上图可以看到,按键按下后,从最后一次抖动到按键消抖模块的输出变为“1”,中间的时间就是20ms。

图8

由上图可以看到,按键松开后,从最后一次抖动到按键消抖模块的输出变为“0”,中间的时间也是20ms。

至此,按键消抖模块设计成功。

2.2波形数据的存储:

2.2.1生成COE文件:

首先要生成每种波形的数字波形数据,这可以使用MATLAB、Guagle等软件生成一个MIF文件或者COE文件的方式来实现。MIF文件和COE文件都是用来列出存储器里存放的所有数据的文件,但两者的文件格式不同。本次设计利用Guagle软件先生成各种波形的MIF文件,再将MIF文件改成COE文件,图9是生成的正弦波COE文件的一部分:

图9

2.2.2调用BLOCK ROM IP核:

Xilinx的FPGA中具有很多不同功能的IP核,调用这些IP核可以大大提高设计效率。上面说到,本次设计利用BLOCK ROM的IP核来存放波形数据,具体是调用数据宽度为8,数据深度为512的单口ROM,IP配置如图10所示:

图10

之后,将生成的波形COE文件,放到IP核里即完成了波形数据的存储。

2.3DDS控制模块:

2.3.1波形调整:

波形调整实现是利用输入的波形控制键控制2位波形控制信号,从而改变4种输出波形。具体过程为:当波形控制键按下时,产生波形按键输入信号,该信号经过按键消抖后存入一个边沿检测寄存器(原理同按键边沿检测寄存器),每当边沿检测寄存器检测到一个上升沿,波形控制信号就加1,加到3时清零,最后,波形控制信号再去控制一个4选1的数据选择器即可实现4种输出波形的调整。

2.3.2幅度调整:

实现幅度调整是比较简单的,只需利用一个乘法器,把波形输出先乘上一个放大倍数再输出即可。但采用这种方式只能实现放大倍数较低的放大,因为随着放大倍数的增加,输出的位宽势必也会增加,不过在实际应用中,可以在本设计的输出端加上模拟放大器,从而实现高增益放大。本设计能实现的最高增益是15倍,而在ROM里存放的8位波形数据最大为“0xFF”,故波形输出的最大数据为“0xEF1”,故波形输出位宽应该为12位。

具体过程为:当幅度控制键按下时,产生幅度按键输入信号,该信号经过按键消抖后存入一个幅度边沿检测寄存器,每当检测到一个上升沿,幅度控制信号就加1,加到15时清零,最后,每个从ROM里输出的波形数据乘上幅度控制信号即实现幅度调整。

2.3.3相位调整:

由于存放在ROM里的波形数据是512个离散的数据点,若从地址为0的数据开始输出,那么输出的波形相位就为0°,由此不难想到,只要改变ROM的初始输出地址,就能实现相位调整。

设需要产生的相移为N°,则对应的相位控制信号P为:

基于以上算法,本设计实现相位15°的整数倍可调,那么每次产生的相位控制信号就应该为21的整数倍,具体过程为:当相位控制键按下时,产生相位按键输入信号,该信号经过按键消抖后存入一个相位边沿检测寄存器,每当检测到一个上升沿,相位控制信号就加21,加到504时清零,然后将原来的ROM初始输出地址赋值为新的相位控制信号,从而实现相位调整。

2.3.4频率调整:

利用波形数据的离散性,也能实现频率调整。在一定的初始输出地址下,若每间隔一个数据点输出一次,那么输出的波形频率就为原来的2倍,依此类推,只要改变ROM数据采样的间隔,就能实现频率调整。但是,根据奈奎斯特采样定理(采样频率必须大于等于被采样信号最高频率的2倍,否则采样后信号会失真),采样间隔不可能无限增大。

本次设计实现频率1到50倍整数倍频。具体过程为:当频率控制键按下时,产生频率按键输入信号,该信号经过按键消抖后存入一个频率边沿检测寄存器,每当检测到一个上升沿,频率控制信号就加1,加到50时清零,然后在主时钟的控制下,每次主时钟到来,原来的ROM初始输出地址就增加一个频率控制信号,从而实现频率调整。

综上,每次主时钟到来,ROM地址W的算法为(W1为上一时刻的地址,P为相位控制信号,F为频率控制信号):

2.4顶层描述:

根据以上的设计思路,编写的Verilog顶层代码如下:

`timescale 1ms / 1psmodule DDS_top(input clk, //50MHz主时钟input rst, //复位信号,高电平有效input W_ctrl, //波形控制键input A_ctrl, //幅度控制键input P_ctrl, //相位控制键input F_ctrl, //频率控制键output reg [11:0] wave_out //波形输出信号);wire W_ctrl_key; //消抖后的波形控制键wire A_ctrl_key; //消抖后的幅度控制键wire P_ctrl_key; //消抖后的相位控制键wire F_ctrl_key; //消抖后的频率控制键wire [7:0] wave_sin; //正弦波输出信号wire [7:0] wave_Sawtooth; //锯齿波输出信号wire [7:0] wave_square; //方波输出信号wire [7:0] wave_Triangular; //三角波输出信号reg [1:0] r_M; //波形边沿检测寄存器reg [1:0] r_A; //幅度边沿检测寄存器reg [1:0] r_P; //相位边沿检测寄存器reg [1:0] r_F; //频率边沿检测寄存器reg [1:0] WAVE = 2'd0; //波形控制信号,实现输出波形相位超前15度的整数倍可调reg [3:0] AMPLITUDE = 4'd1; //幅值控制信号,实现输出波形幅值1到15整数倍可调reg [8:0] PHASE = 9'd0; //相位控制信号,实现输出波形相位超前15度的整数倍可调reg [5:0] FREQUENCY = 6'd1; //频率控制信号,实现输出波形频率倍频1到50倍整数可调reg [8:0] wave_add = 9'd0; //波形数据的地址/*****************调用按键消抖模块实现波形选择信号消抖*****************/KEY_filter key_WAVE(.key_filter(W_ctrl_key),.clk (clk),.rst (rst),.key_in (W_ctrl));/*****************调用按键消抖模块实现幅度控制字消抖*****************/KEY_filter key_AMPLITUDE(.key_filter(A_ctrl_key),.clk (clk),.rst (rst),.key_in (A_ctrl));/*****************调用按键消抖模块实现相位控制字消抖*****************/KEY_filter key_PHASE(.key_filter(P_ctrl_key),.clk (clk),.rst (rst),.key_in (P_ctrl));/*****************调用按键消抖模块实现频率控制字消抖*****************/KEY_filter key_FREQUENCY(.key_filter(F_ctrl_key),.clk (clk),.rst (rst),.key_in (F_ctrl));/*****************波形边沿检测*****************/always @ (posedge clk)r_M <= {r_M[0],W_ctrl_key};/*****************幅度边沿检测*****************/always @ (posedge clk)r_A <= {r_A[0],A_ctrl_key};/*****************相位边沿检测*****************/always @ (posedge clk)r_P <= {r_P[0],P_ctrl_key};/*****************频率边沿检测*****************/always @ (posedge clk)r_F <= {r_F[0],F_ctrl_key};/*****************波形控制*****************/always @ (posedge clk or negedge rst)beginif (rst)WAVE <= 2'd0;else if (r_M == 2'b01)if (WAVE == 2'd3)WAVE <= 2'd0;elseWAVE <= WAVE + 1'b1;else WAVE <= WAVE;end/*****************幅度控制*****************/always @ (posedge clk or negedge rst)beginif (rst)AMPLITUDE <= 6'd1;else if (r_A == 2'b01)if (AMPLITUDE == 4'd15)AMPLITUDE <= 6'd1;elseAMPLITUDE <= AMPLITUDE + 1'b1;else AMPLITUDE <= AMPLITUDE;end/*****************相位和地址控制*****************/always @ (posedge clk or negedge rst)beginif (rst)beginPHASE <= 9'd0;wave_add <= 9'd0;endelse if (r_P == 2'b01)if (PHASE == 9'd504)PHASE <= 9'd0;elsebeginPHASE = PHASE + 15*511/360;wave_add = PHASE;endelsebegin PHASE <= PHASE;wave_add <= wave_add + FREQUENCY;endend/*****************频率控制*****************/always @ (posedge clk or negedge rst)beginif (rst)FREQUENCY <= 6'd1;else if (r_F == 2'b01)if (FREQUENCY == 6'd50)FREQUENCY <= 6'd1;elseFREQUENCY <= FREQUENCY + 1'b1;else FREQUENCY <= FREQUENCY;end/*****************输出波形*****************/always @ (posedge clk)begincase (WAVE)2'd0:wave_out <= wave_sin * AMPLITUDE;2'd1:wave_out <= wave_Sawtooth * AMPLITUDE;2'd2:wave_out <= wave_square * AMPLITUDE;2'd3:wave_out <= wave_Triangular * AMPLITUDE;default:wave_out <= 12'd0;endcaseend/*****************调用正弦波块ROM*****************/sin u1 (.clka(clk), .ena(1'd1),.addra(wave_add), .douta(wave_sin) );/*****************调用锯齿波块ROM*****************/Sawtooth u2 (.clka(clk), .ena(1'd1),.addra(wave_add), .douta(wave_Sawtooth) );/*****************调用方波块ROM*****************/square u3 (.clka(clk), .ena(1'd1),.addra(wave_add), .douta(wave_square) );/*****************调用三角波块ROM*****************/Triangular u4 (.clka(clk), .ena(1'd1),.addra(wave_add),.douta(wave_Triangular) );endmodule

2.5顶层仿真测试:

编写的testbeach如下:

`timescale 1ms / 1psmodule TB_DDS_top();reg clk; //系统主时钟reg rst; //复位信号,高电平有效reg W_ctrl; //波形控制键reg A_ctrl; //幅度控制键reg P_ctrl; //相位控制键reg F_ctrl; //频率控制键wire [11:0] wave_out; //波形输出信号/**************模块例化**************/DDS_top TB_UUT(.clk(clk), .rst(rst),.W_ctrl(W_ctrl),.wave_out(wave_out),.A_ctrl(A_ctrl),.P_ctrl(P_ctrl),.F_ctrl(F_ctrl));/**************产生50MHz时钟**************/ always #0.00001 clk = ~clk;/**************输入初始化**************/ initial beginclk = 1'd0;rst = 1'd0;W_ctrl = 1'd0;A_ctrl = 1'd0;P_ctrl = 1'd0;F_ctrl = 1'd0;/**************4个控制键第一次按下**************/ #1W_ctrl = 1'd1;A_ctrl = 1'd1;P_ctrl = 1'd1;F_ctrl = 1'd1;/**************4个控制键第一次松开**************/ #21W_ctrl = 1'd0;A_ctrl = 1'd0;P_ctrl = 1'd0;F_ctrl = 1'd0;/**************4个控制键第二次按下**************/ #21W_ctrl = 1'd1;A_ctrl = 1'd1;P_ctrl = 1'd1;F_ctrl = 1'd1;/**************4个控制键第二次松开**************/ #21W_ctrl = 1'd0;A_ctrl = 1'd0;P_ctrl = 1'd0;F_ctrl = 1'd0;/**************4个控制键第三次按下**************/ #21W_ctrl = 1'd1;A_ctrl = 1'd1;P_ctrl = 1'd1;F_ctrl = 1'd1;/**************4个控制键第三次松开**************/#21W_ctrl = 1'd0;A_ctrl = 1'd0;P_ctrl = 1'd0;F_ctrl = 1'd0;/**************4个控制键第四次按下**************/#21W_ctrl = 1'd1;A_ctrl = 1'd1;P_ctrl = 1'd1;F_ctrl = 1'd1;/**************4个控制键第四次松开**************/ #21W_ctrl = 1'd0;A_ctrl = 1'd0;P_ctrl = 1'd0;F_ctrl = 1'd0;endendmodule

顶层仿真结果如图11至图15所示:

图11

由上图可以看到,当4个控制键第一次按下时,波形由正弦波变为锯齿波,同时相移15°,倍频2倍,幅度放大2倍。

图12

由上图可以看到,当4个控制键第二次按下时,波形由锯齿波变为方波(图中模拟波形显示的是三角波,但是根据图13所示,转为数字显示后确实是方波的波形数据,所以在这里我怀疑应该是Vivado仿真器分辨率的问题),同时相较一开始的正弦波,相移30°,倍频3倍,幅度放大3倍。

图13

图14

由上图可以看到,当4个控制键第三次按下时,波形由方波变为三角波,同时相较一开始的正弦波,相移45°,倍频4倍,幅度放大4倍。

图15

由上图可以看到,当4个控制键第四次按下时,波形由三角波变回正弦波,同时相较一开始的正弦波,相移60°,倍频5倍,幅度放大5倍。

至此,整个设计完成。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。