目录
1. 什么是DDS?
直接数字频率合成(Direct Digital Frequency Synthesis,简称DDS或DDFS)是一种应用数字技术产生信号波形的方法,它是由美国学者J. Tierncy、C.M. Rader和B. Gold在1971年提出的,他们以数字信号处理理论为基础,从相位概念出发提出了一种新的直接合成所需波形的全数字频率合成方法。
1.1 功能要求
①利用DDS技术合成正弦波和方波
②输出信号的频率范围为10Hz ~ 5MHz,最小频率分辨率小于1kHz
1.2DDS产生波形的原理
下面以正弦信号波形的产生为例,说明DDS的工作原理。
虽然正弦波的幅度不是线性的,但是它的相位却是线性增加的,DDS正是利用了这一特点来产生正弦信号。因为一个连续的正弦信号其相位是时间的线性函数,相位对时间的导数为ω,即
当角频率ω为一定值时,其相斜率dθ/dt也是一个确定值。此时,正弦波形信号的相位与时间呈线性关系,即Δθ = ω×Δt。根据这一基本关系,利用采样定理,通过查表法就能够产生波形。
下图是产生正弦信号的原理框图。图中CPi为系统基准时钟源,其周期为Ti,在CPi的作用下,地址计数器(从0~(2^N−1)计数)产生数据存储器所需的地址信号。在时钟作用下,周期性地读出正弦波形存储器中的正弦幅度值,经过D/A转换器及低通滤波器就可以合成模拟波形。
1.2.1 如何获取正弦波存储器中的数据?
我们知道,某一个频率的正弦信号可以表示为:
由于 A 和 θ 0不随时间而变化,可以令A=1,θ 0=0,得到归一化的正弦信号表达式:
将上述正弦信号一个周期内的相位 0∼2π 的变化用单位圆表示,其相位与幅度一一对应,即单位圆上的每一点均对应输出一个特定的幅度值,如下图所示。例如,在圆上取16个相位点就有16种幅度值与之对应,如果在圆上取 2N 个相位点,则相位分辨率为 Δ=2π/2N。根据奈奎特定理,以等量的相位间隔对其进行相位/幅度抽样得到一个周期性的正弦信号的离散相位的幅度序列,并且对模拟幅度进行量化,量化后的幅值采用相应的二进制数据编码。这样就把一个周期的正弦波连续信号转换成为一系列离散的二进制数字量,然后通过一定的手段固化在只读存储器ROM中,每个存储单元的地址即相位取样地址,存储单元的内容是已经量化的正弦波幅值。这样的一个只读存储器就构成了一个与 2π 周期内相位取样相对应的正弦函数表,因它存储的是一个周期的正弦波波形幅值,因此称其为正弦波形存储器,又称作查找表。
1.2.2 如何改变输出信号的频率?
假设ROM用2^n 个存储单元来存储正弦信号一个周期的数据,则输出信号的周期、基准时钟周期与读出的数据点之间的关系可以用下图表示,图中的空心圆点表示从ROM中读出的数据点。
每来一个基准时钟脉冲(周期为 T i),地址计数器的地址加1,存储器输出一个数据点,直到存储器最后一个地址 (2^n−1) 单元中的数据被读出,此时能够得到一个周期的正弦信号。此后,地址计数器在基准时钟的作用下,继续从0到2 ^n−1计数,并重复读出ROM中的数据,于是得到周期性的正弦波形。因此,输出信号的周期 Tout与基准时钟周期 Ti存在如下关系:
式中,fout为输出正弦信号的频率,f i为计数器时钟信号的频率。根据采样定理,输出信号频率不能超过系统时钟频率的一半。在实际运用中,为了保证信号的输出质量,输出信号的频率不要高于时钟频率的1/3,以避免混叠或谐波落入有用输出频带内。
1.3 DDS设计分析
在FPGA中,常用下图所示框图实现DDS。其中,m为地址加法器的数据宽度,其大小取决于基准时钟频率和所需步进精度。ROMaddr为n位存储器地址,n取决于波形样本个数。r为存储器输出数据的宽度。
地址计数器也称为相位累加器,由地址加法器和地址寄存器组成。加法器有两个数据输入端:一个输入端B与地址寄存器的输出相连;另一个输入端为相位增量K,因为K是决定DDS输出频率的参量,所以被称为频率数据或频率控制字(Frequency Control Word, FCW),存放K的寄存器称为频率控制寄存器。频率控制字K是一个二进制数据,K+B(B的初值为0)就是相位累加器的输出Addr,相位控制字P用来调节相对于相位累加器输出相位的一个固定增量,对于一路信号来说没有任何意义,不过对于多路信号来说,可以通过相位控制字来调节多路信号的相位差。
存储器地址ROMaddr截取的是PAddr的高n位,其变化速度取决于K的大小,从而实现了跳跃读数。当相位累加器为最大值时,再来一个时钟脉冲,其输出地址数据出现溢出,自动地从0开始重复先前的过程,这样就可以实现波形的连续输出。因此通过改变K值来调节输出频率。
2. DDS设计
2.1 相位累加器的设计
相位累加器在时钟的驱动下,将输入的频率控制字转换为地址并输出,它决定了频率的范围和分辨率。本设计不使用相位调制器,相位累加器采用 m=17 位的二进制累加器和寄存器构成,其结果直接送到后面的存储器。其代码如下:
//相位累加器和数据锁存器
module addr_cnt(CPi,K,ROMaddr,Address);
input CPi;
input [12:0] K;//13位频率控制字
output reg [9:0] ROMaddr;//10位ROM地址
output reg [16:0] Address;//17位相位累加器地址信号
always @(posedge CPi)
begin
Address = Address + K;
ROMaddr = Address[16:7];
end
endmodule
输入上述代码,选择主菜单中的File→Create/Update→Create Symbol Files for Current File
命令,生成该模块的符号,如下图所示。
2.2 波形存储器ROM的设计
在本设计中,要求DDS系统能输出正弦波和方波。可以调用FPGA内部的LPM_ROM模块制作两张ROM表,分别存储正弦波和方波的波形数据,地址计数器可以同时访问这两张表,再使用数据选择器输出指定的波形。产生方波的另一种方法是在正弦波的输出端使用一个过零比较器。
2.2.1 方波模块
由于方波的实现算法相对简单,可以不用ROM表,直接用寄存器来保存方波的输出值。方波只有高、低电平两种状态,因此只需要在一个周期的中间位置翻转电平即可。其实现原理如下:由于相位累加器的值是线性累加的,因此地址值(Address)也是线性累加的,对地址值Address进行判断,当地址值的最高位为0时,便将存储波形幅值的存储器的每一位赋值为1,否则赋值为0。具体源程序如下:
// === 方波产生模块: squwave.v ===
module squwave(CPi, RSTn, Address, Qsquare);
input CPi; // 系统基准时钟 (100MHz)
input RSTn; // 同步清零
input [16:0] Address; // 17位地址输入信号
output reg [11:0] Qsquare; // 输出方波信号,12位宽,送至DAC
always @(posedge CPi)
if (!RSTn) Qsquare = 12'h000; // 同步清零
else begin
if (Address <= 17'h0FFFFF)
Qsquare = 12'hFF; // 输出高电平
else Qsquare = 12'h000; // 输出低电平
end
endmodule
方波模块的符合框图如下图所示
2.2.2 正弦波形存储器模块
根据下面的式子和采样定理对周期为 T 的正弦信号以等间隔时间采样(采样周期为 Ts),可以得到正弦序列:
简记为:
式中,n 表示各函数值的序列号。如果令 T/T s =N(正整数)为总的抽样点数,当 n 取值为 0, 1, 2, …, N−1 时,就能将时间连续的正弦信号离散为一组序列值的集合。
根据上式编写C语言程序,将正弦波的一个周期离散成1024个相位/幅值点,每个点的数据宽度为12位。编译、运行程序后,得到存储器的初始化文件Sine1024.mif。
下面是完整的C语言程序。
#include <stdio.h>
#include <math.h>
#define PI 3.141592
#define DEPTH 1024
#define WIDTH 12
int main(void)
{
int n, temp;
float v;
FILE *fp;
/* 建立文件名为Sine1024.mif的新文件,允许写入数据,对文件名没有特殊要求,但扩展名必须为.mif */
fp = fopen("Sine1024.mif", "w+");
if (NULL == fp)
printf("Can not creat file!\r\n");
else
{
printf("File created successfully!\n");
/* 生成文件头,注意不要忘了“:” */
fprintf(fp, "DEPTH =%d;\n", DEPTH);
fprintf(fp, "WIDTH =%d;\n", WIDTH);
fprintf(fp, "ADDRESS_RADIX=HEX;\n");
fprintf(fp, "DATA_RADIX=HEX;\n");
fprintf(fp, "CONTENT\n");
fprintf(fp, "BEGIN\n");
/* 以十六进制输出地址和数据 */
for (n = 0; n < DEPTH; n++)
{
/* 周期为1024个点的正弦波 */
v = sin(2 * PI * n / DEPTH);
/* 将-1~1之间的正弦波的值扩展到0~4095之间 */
temp = (int)((v + 1) * 4095 / 2); // v+1将数值平移到0~2之间
/* 以十六进制输出地址和数据 */
fprintf(fp, "%x\t:\t%x;\n", n, temp);
}
fprintf(fp, "END;\n");
fclose(fp); // 关闭文件
}
}
接着,使用Quartus Prime软件调用宏模块LPM_ROM定制正弦波形存储器。其过程是:选择主菜单中的Tool→IP Catalog命令,启动IP Catalog工具,按照提示生成ROM,其中存储深度为1024,数据宽度为12位,并加载上面生成的文件Sine1024.mif初始化ROM,最后得到正弦波形的数据存储器模块SineROM.v,模块符号如下图所示。
具体配置过程如下:
- 配置数据宽度和存储深度
- 默认即可
- 配置.mif文件
- 默认,next
- 勾选方框中的Plug-In,finish
2.2.3 锁相倍频电路设计
使用Quartus Prime软件调用宏模块定制一个100MHz的锁相环模块。其过程是:选择Tool→IP Catalog命令,启动IP Catalog工具,搜索选择ALTPLL(嵌入式锁相环),定制一个名称为PLL100M_CP的时钟模块,该模块的输入inclk0为50MHz时钟信号,输出c0为100MHz的脉冲信号,占空比为50%,带有相位锁定指示输出端locked,模块符号如下图所示。
配置inclk0频率为50MHz,选择normal模式
默认,next
默认,next
默认,next
默认,next
设置c0输出为100MHz,占空比50%
c1到c4时钟均不启用,next到语法Plug-In选择,勾选方框中的Plug-In
2.3 顶层电路设计
将以上各个模块逐个级联起来就可以得到波形产生器的顶层模块,其代码如下:
// === DDS的顶层模块: DDS_top.v ===
module DDS_top(CLOCK_50, RSTn, WaveSel, K, WaveValue, LEDG, CLOCK_100);
input CLOCK_50; // 50MHz时钟
input RSTn; // 控制方波清零等,低电平有效
input [1:0] WaveSel; // 波形选择:SW[17:16]=10时为方波;SW[17:16]=01时为正弦波
input [12:0] K; // 频率控制字K12.5W0
output reg [11:0] WaveValue; // 输出波形数据
wire [9:0] ROMaddr; // 波形存储器地址
wire [16:0] Address; // 17位相位累加器地址
wire [11:0] Qsine, Qsquare; // 正弦、方波数据输出
output [0:0] LEDG; // 锁相环相位锁定指示灯,亮表示锁定
output CLOCK_100; // 锁相环输出时钟,频率为100MHz
wire CPi = CLOCK_100;
// 实例引用锁相环子模块
PLL100M_CP PLL100M_CP_inst (
.inclk0 ( CLOCK_50 ), // 50MHz时钟输入
.c0 ( CLOCK_100 ), // 100MHz时钟输出
.locked ( LEDG[0] ) // 相位锁定指示
);
// 实例引用地址累加器
addr_cnt U0_instance(CPi, K, ROMaddr, Address);
// 实例引用正弦LPM_ROM子模块
SineROM ROM_inst (
.address ( ROMaddr ),
.clock ( CPi ),
.q ( Qsine )
);
// 实例引用方波子模块
squwave U1(CPi, RSTn, Address, Qsquare);
always @(posedge CPi)
begin
case(WaveSel)
2'b01: WaveValue = Qsine; // 输出正弦波
2'b10: WaveValue = Qsquare; // 输出方波
default: WaveValue = Qsine;
endcase
end
endmodule
2.4 设计实现
使用DE2-115开发板来验证上述设计。用板上的50MHz晶振作为时钟输入,用KEY3控制方波清零,用SW12~SW0设置频率控制字,SW17、SW16用来选择输出波形的种类,用LEDG0作为PLL的相位锁定指示。其操作步骤如下:
- 为了方便导入文件DE2_115_pin_assignments.csv进行引脚分配,将使用该文件中的端口名称代替上述DDS_top.v(代码如下)中的信号名称。为此再编写一个顶层文件DE2_DDS_top.v(代码如下),建立一个新的工程项目,并导入
DE2_115_pin_assignments.csv
文件。然后进行全编译。
// === 在开发板上运行的DDS的顶层模块: DE2_115_DDS_top.v ===
module DE2_115_DDS_top(CLOCK_50, KEY, SW, GPIO_0, LEDG);
input CLOCK_50; // 50MHz时钟
input [3:3] KEY; // 按键KEY3,控制方波清零等
input [17:0] SW; // 拨动开关
output [12:0] GPIO_0; // 扩展接口,送出波形数据给DAC
output [0:0] LEDG; // 绿色LED指示相位是否锁定
wire CLOCK_100; // 100MHz时钟
assign GPIO_0[12] = CLOCK_100; // 送给DAC的时钟
wire RSTn = KEY[3]; // 控制方波清零,低电平有效
wire [1:0] WaveSel = SW[17:16]; // 选择输出波形
wire [12:0] K = SW[12:0]; // 设置频率控制字,最小值必须为1
wire [11:0] WaveValue; // 输出波形数据
assign GPIO_0[11:0] = WaveValue; // 输出波形数据
DDS_top DE2(
.CLOCK_50(CLOCK_50),
.RSTn(RSTn),
.WaveSel(WaveSel),
.K(K),
.WaveValue(WaveValue),
.LEDG(LEDG),
.CLOCK_100(CLOCK_100)
);
endmodule
使用嵌入式逻辑分析仪SignalTap II实时测试输出波形的离散数据。选择Tools→SignalTap II Logic Analyzer命令,选择锁相环输出的100MHz信号(GPIO_0[12])作为采样时钟,添加待测节点信号,并设置ROM地址为0时触发采样,如下图所示。然后保存设置,并将该模块添加到当前工程项目中。
编译整个设计,并下载设计文件到FPGA芯片中。
在SignalTap II窗口中单击按钮并选择View→Fit in Window命令,配合拨动开关,可以看到DDS输出波形如下图所示。单击Stop analysis按钮停止分析。注意,右击所要观察的总线信号名,在右键菜单上可以选择Bus Display Format(总线显示格式)命令,选择Unsigned Line Chart才能形成模拟波形。
心得
通过这次实验我学到了DDS是如何去产生波形的以及DDS的基础的模块设计,提高了我对IP核的调用的熟练度。