一、DDS简介
直接数字合成技术(Direct Digital Synthesizer,DDS)诞生于 20 世纪 70 年代,该技术融合数字信号处理理论和方法,从相位的角度进行数字化处理以获得所需要的正余弦波。
DDS结构框图如下所示。其基本结构包括 N 位加法器、N 位相位寄存器、波形存储器、数模转换器、低通滤波器和工作时钟;其中 N 位加法器和 N 位相位寄存器构成 DDS 的相位累加器。
基本原理:
在参考时钟的驱动下,DDS 模块开始工作,当每一个参考时钟来临时,相位累加器增加一个频率控制字K,当相位累加器的值大于等于之后,就会对
求余数并从头开始循环累加,即可生成对应频率的信号。当频率控制字改变时,N 位相位累加器的单次累加值也会对应改变,输出信号的频率就会对应改变,通过改变频率控制字,即可生成任意频率的信号,DDS 输出信号的频率满足公式
。DDS 在参考时钟的作用下,通过对频率控制字不停地进行累加,用相位累加器输出的数据作为地址在波形存储器中通过查找地址所对应的幅值表,就可以完成其从相位到幅值之间的转化。通过改变 ROM 表中存放的数据,即可生成任意波形。
频率控制字和相位控制字
相位和幅值的一一对应关系就好比存储器中地址和存储内容的关系,如果把一个周期内每个相位对应的幅度值存入存储器当中,那么对于任意频率的正弦信号,在任意时刻,只要已知相位Φ(t),也就知道地址,就可通过查表得到s(t)。
比如一个频率为fc的正弦信号:
其时域表达式为:,
其相位表达式:,
频率控制字相当于Φ(t)中的2πfc,相位控制字相当于Φ(t)中的。
相位累加器在每个时钟脉冲输入时,把频率控制字累加一次,相位累加器的输出数据就是信号的相位,用输出的数据作为波形存储器(ROM)的相位取样地址,这样就可以把存取在波形存储器内的波形抽样值经查找表查处,完成相位到幅值的转换。
由于相位累加器字长的限制,相位累加器累加到一定值后,其输出将会溢出,这样波形存储器的地址就会循环一次,即意味着输出波形循环一周。故改变频率控制字即相位增量,就可以改变相位累加器的溢出时间,在时钟频率不变的条件下就可以改变输出频率。改变查表寻址的时钟频率,同样也可以改变输出波形的频率。
相位截断误差
为了获得较高的频率分辨率,则只有增加相位累加器的字长N,故一般N都取值较大。但是受存储器容量的限制,存储器地址线的位数W不可能很大,一般都要小于N。这样存储器的地址线一般都只能接在相位累加器输出的高w位,而相位累加器输出余下的(N-W)个低位则只能被舍弃,这就是相位截断误差的来源。
输出频率和分辨率
DDS模块的输出频率fout是系统工作频率fc、相位累加器位数N及频率控制字K满足如下关系
频率分辨率,即频率的变化间隔
二、实现设计
设计一个单通道的DDS信号发生器,通过改变FPGA开发板上的按键来控制输出的正弦波的频率和相位,输出1MHz,2MHz,....10MHz的波形,最后通DAC芯片输出。
RTL视图如下:
功能实现:
通过“BUTRST”按键可以实现系统的复位,通过“BUTDOWN”和“BUTUP”按键可以改变频率字,输出1MHz,2MHz......10MHz的正弦波。
设计思路:
整体分为按键模块,上电复位模块,频率字生成模块,dds核心模块。
上电复位模块
说明:
1.第一个进程用来延时,当上电后,延时100ms,以保证FPGA内部达到稳定状态;此时RSTOUT始终为1(高电平有效),也就是系统时钟处于复位状态中;
2.当100ms延时结束后,RSTOUT与系统时钟同步释放,即RSTOUT拉低,复位结束,系统开始正常工作。
代码如下:
//power up reset
module powerup_reset(
CLK , //clk_50MHz input
RSTOUT ); //powerup_reset
input CLK;
output RSTOUT;
reg RSTOUT;
parameter CNTMAX = 5_000_000 - 1;
reg [31:0] counterR;
always @ (posedge CLK) begin
if(counterR < CNTMAX) begin
counterR <= counterR + 1'b1;
end
else begin
counterR <= CNTMAX;
end
end
always @ (counterR) begin
if(counterR < CNTMAX)
RSTOUT = 1'b1; // rst enable 处于上电复位状态
else
RSTOUT = 1'b0; // rst 持续拉低
end
endmodule
按键模块
module button_in_out(
CLK , // clock
IN , // input button signal
OUT ); // output button signal
// 50MHz clock, T = 20ns = 20*1E-6 ms
// button check time = 20*1E-6 * 2^20, about 20ms
// set the CNT_WL with your system clock freq
parameter CNT_WL = 20 ; // counter word length
parameter CNT_MAX = 20'h7A120 ; // counter max value
parameter CNT_MAX68 = (CNT_MAX >> 3) * 6 ; // 6/8 counter max value
parameter CNT_MAX78 = (CNT_MAX >> 3) * 7 ; // 7/8 counter max value
input CLK, IN;
output OUT;
reg button_in_d1R, button_in_d2R;
reg [CNT_WL-1:0] counterR;
reg buttonIn0R, buttonIn1R, buttonIn2R, buttonIn3R, buttonOutR;
assign OUT = buttonOutR;
always @ (posedge CLK) begin
button_in_d1R <= IN;
button_in_d2R <= button_in_d1R;
end
always @ (posedge CLK) begin
if(counterR == 0)
buttonIn0R <= button_in_d2R;
else
buttonIn0R <= buttonIn0R;
if(counterR == CNT_MAX68)
buttonIn1R <= button_in_d2R;
else
buttonIn1R <= buttonIn1R;
if(counterR == CNT_MAX78)
buttonIn2R <= button_in_d2R;
else
buttonIn2R <= buttonIn2R;
if(counterR == CNT_MAX)
buttonIn3R <= button_in_d2R;
else
buttonIn3R <= buttonIn3R;
end
// different values of button input buffers will start the counter
always @(posedge CLK) begin
if(counterR < CNT_MAX)
counterR <= counterR + 1'b1;
else
counterR <= 0;
end
always @(posedge CLK) begin
if(buttonIn0R == buttonIn1R == buttonIn2R == buttonIn3R )
buttonOutR <= buttonIn3R;
else
buttonOutR <= buttonOutR;
end
endmodule // module button_in_out()
频率字生成模块
module generate_freq_word(
CLK , // input clock
RST , // input reset
BUTUP , // input button upward
BUTDOWN , // input button downward
FQWD , // output frequency word
FWEN ); // output frequency word update enable
parameter VAL_FREQ_BASE = 32'h051E_B851; // 50MHz clock, 1MHz out wave freqword value
parameter VAL_FREQ_MAX = (VAL_FREQ_BASE * 10); // 50MHz clock, MAX 10MHz out wave freqword value
input CLK,RST;
input BUTUP, BUTDOWN;
output [31:0] FQWD;
output FWEN;
// when push button will cause these DFFs 1 clock cycle high level
reg butup_enR, butdown_enR;
reg butup_d1R, butdown_d1R;
reg [31:0] FQWD;
reg FWEN;
/
// pipeline align
// BUTUP | butup_d1R | butup_enR | FWEN
// BUTDOW | butdown_d1R | butdown_enR | FQWD
/
always @ (posedge CLK or posedge RST) begin
if(RST) begin
butup_d1R <= 1'b0;
butdown_d1R <= 1'b0;
butup_enR <= 1'b0;
butdown_enR <= 1'b0;
end
else begin
butup_d1R <= BUTUP ;
butdown_d1R <= BUTDOWN ;
if((~butdown_d1R) && (BUTDOWN))
butdown_enR <= 1'b1;
else
butdown_enR <= 1'b0;
if((~butup_d1R) && (BUTUP))
butup_enR <= 1'b1;
else
butup_enR <= 1'b0;
end
end
always @ (posedge CLK or posedge RST) begin
if(RST) begin
FQWD <= VAL_FREQ_BASE;
FWEN <= 1'b1;
end
else begin
// BUTTON UP
if(butup_enR &&(~butdown_enR)) begin
if(FQWD == VAL_FREQ_MAX)
FQWD <= VAL_FREQ_BASE;
else
FQWD <= FQWD + VAL_FREQ_BASE;
end
// BUTTON DOWN
else if((~butup_enR) &&butdown_enR) begin
if(FQWD == VAL_FREQ_BASE)
FQWD <= VAL_FREQ_MAX;
else
FQWD <= FQWD - VAL_FREQ_BASE;
end
// no valid button push
else begin
FQWD <= FQWD; // hold old value
end
FWEN <= (butup_enR || butdown_enR);
end
end
endmodule
DDS核心模块
module dds_core_sin(
CLK , // clock, posedge valid
RST , // reset, high level reset
FWEN , // frequency word update enable, high level enable
FWIN , // input frequency word
CLKOUT, // output clock
SINOUT); // sine signal output, 2's complement format
input CLK;
input RST;
input FWEN;
input [32-1:0] FWIN;
output[12-1:0] SINOUT;
output CLKOUT;
parameter FW_WL = 32; // frequency word word length in bit
parameter RA_WL = 10; // rom address word length in bit
parameter RD_WL = 12; // rom data word word length in bit
reg [FW_WL -1:0] fwin_R; // freq word DFF
reg [FW_WL -1:0] acc_R; // phase ACC DFF
reg [RA_WL -1:0] addr_R; // rom address DFF
reg [RD_WL -1:0] sinout_R; // sin wave output DFF
wire [RD_WL -1:0] romout_W; // rom data output wire
always @ (posedge CLK or posedge RST) begin
if(RST) begin
fwin_R <= 0;
acc_R <= 0;
addr_R <= 0;
sinout_R <= 0;
end
else begin
// update fwin_R DFF
if(FWEN)
fwin_R <= #1 FWIN;
else
fwin_R <= #1 fwin_R;
// update acc_R
acc_R <= #1 fwin_R + acc_R;
// update addr_R, the acc_R high RA_WL is rom address
addr_R <= acc_R[FW_WL-1:FW_WL-1-(RA_WL-1)];
// update output DFF
sinout_R <= #1 romout_W;
end
end
DDS_CORE_ROM u_sinrom(
.CLK (CLK ), // clock
.RA (addr_R ), // read address
.RD (romout_W )); // read data
assign SINOUT = sinout_R;
assign CLKOUT = CLK;
endmodule // module dds_core
其中正弦波存储的幅度表可以由MATLAB实现
% %生成12位单周期正弦波信号,4096个数据
% N = 4096;
% i = 1:1:4096;
% y = 2048*(sin(2*pi/4096*i)+1);
% y1 = dec2bin(y,12);
% fid=fopen('D:\FPGA\FPGA_program\quartus\DDS\rtl\sin.txt','w'); %需要改文件名称的地方
% y2 = zeros(1,N);
% for i=1:1:4096
% % y2(i)=str2num(y1(i));
% fprintf(fid,'%s\n',y1(i)); %data:需要导出的数据名称,10位有效数字,保留3位小数(包含小数点),f为双精度,g为科学计数法
% end
% fclose(fid);
clc;
clear;
i=0:2*pi/4095:2*pi;
y=(sin(i)+1)*4095/2;
k=dec2bin(y,12);
fid=fopen('H:\FPGA\FPGA_program\quartus\DDS\rtl\sin.txt','wt');
for i=1:4096%1024行
for j=1:12%11列
fprintf(fid,'%s',k(i,j));%输出
if mod(j,12)==0%判断是否输出了11个字符
fprintf(fid,'\n');%每输出11个字符也就是输出了一行,输出一个回车
end;
end;
end;
fclose(fid);
顶层模块
//正弦波产生
module top_sin_wave(
CLK , // clock, posedge valid
BUTUP , // input button freq upward setting
BUTDOWN , // input button freq downward setting
BUTRST , // reset button
CLKOUT , // clock output
SINOUT ); // DDS sine wave out, to DAC
input CLK;
input BUTUP, BUTDOWN, BUTRST;
output CLKOUT;
output[12-1:0] SINOUT;
wire butup_W, butdown_W, butrst_W;
wire butup_no_jitter_W, butdown_no_jitter_W, butrst_no_jitter_W, rst_W;
wire [32-1:0] fqwd_W;
wire fwen_W;
wire dds_clkout_W;
wire [12-1:0] dds_out_2c_W; // dds output in 2's complement format
// fpga board button is pull-up to VCC
assign butup_W = ~BUTUP ;
assign butdown_W = ~BUTDOWN ;
assign butrst_W = ~BUTRST ;
assign rst_W = butrst_no_jitter_W | pu_rst_W;
powerup_reset U_PURST(
.CLK (CLK ), // clock
.RSTOUT (pu_rst_W )); // power up reset
button_in_out U_BUTUP(
.CLK (CLK ), // clock
.IN (butup_W ), // input button signal
.OUT (butup_no_jitter_W )); // output button signal
button_in_out U_BUTDOWN(
.CLK (CLK ), // clock
.IN (butdown_W ), // input button signal
.OUT (butdown_no_jitter_W)); // output button signal
button_in_out U_BUTRST(
.CLK (CLK ), // clock
.IN (butrst_W ), // input button signal
.OUT (butrst_no_jitter_W )); // output button signal
generate_freq_word U_GFQWD(
.CLK (CLK ), // input clock
.RST (rst_W ), // input reset
.BUTUP (butup_no_jitter_W ), // input button upward
.BUTDOWN (butdown_no_jitter_W ), // input button downward
.FQWD (fqwd_W ), // output frequency word
.FWEN (fwen_W )); // output frequency word update enable
dds_core_sin U_DDS(
.CLK (CLK ), // clock, posedge valid
.RST (rst_W ), // reset, high level reset
.FWEN (fwen_W ), // frequency word update enable, high level enable
.FWIN (fqwd_W ), // input frequency word
.CLKOUT (dds_clkout_W ), // output clock
.SINOUT (dds_out_2c_W )); // sine signal output, 2's complement format
assign SINOUT = dds_out_2c_W;
assign CLKOUT = ~dds_clkout_W;
endmodule // module dds_core
三、实验测试
signaltap分析
由逻辑分析仪结果可知,基本实现实验要求。从FPGA产生的数字信号,在经过dac转换以及低通滤波,可以实现正弦信号的产生。
示波器测量
FPGA输出的10位数字信号经过DAC器件进行D/A转换 输出模拟信号,再用示波器测量,测量结果如下:
1MHz正弦波输出
2MHz输出
从上面测量结果,得到的频率基本正确,但是得到的波形并不是正弦波形
原因:AD9762是无符号的DAC器件,当有符号补码需要先把高位取反再送给DAC。
对于二进制补码来说,把它看作有符号数,对最高位取反;相当于把它看作无符号数时,加2^(N-1)
将最高位取反 ,即将DAC的12位输出与'1_000_0000_0000'进行异或运算
assign SINOUT = SINOUT ^ 12'b1_000_0000_0000;
再次通过示波器观察,随机选取了1MHz,2MHz,10MHz的正弦信号输出:
由示波器可以看出输出的正弦波频率和波形基本正确。但在高频部分有较为明显的失真。
后续可以将DAC输出再加一次滤波电路,应该能够得到比较好的波形,有待进一步测试。