1. DDS 原理简介
DDS 技术是从波形相位概念出发,直接对相应的波形数据进行抽样,得到不同的相位,通过 DAC 转换成模拟波形,最后通过低通滤波器平滑输出所需频率的波形。

两个重要参数解释:
频率控制字 FTW:一般为整数,数值大小控制输出信号的频率大小,数值越大输出信号频率越高。实际输出的信号的频率与频率控制字关系为:
频率控制字 = 脉冲频率 * 2^N / 时钟频率
相位控制字 P_WORD:一般为整数,数值大小控制输出信号的相位偏移,主要用于相位的信号调制。
一个普通的 DDS 设计应该由:相位累加器,相位调制器,波形数据表,DAC 转换组成。
相位累加器:是整个 DDS 的核心,用于控制波形的相位累加,组成完整的波形显示。 相位累加器的输入为频率控制字,表示相位增量,其位宽为 N。

相位调制器:接收相位累加器输出的相位码,在这里加上一个相位偏移值 P,主要用于信号的相位调制。
波形数据表:波形数据表 ROM 中存有一个完整周期的正弦波信号。例如:假设波形数据 ROM 的地址位宽为 12 位,储存数据位宽为 8 位,即 ROM 有 2^12=4096 个存储空间,每个存储空间可以存储 1 字节数据。将一个周期的正弦波信号,沿横轴等间隔采样 4096 次,每次采集的信号幅度用 1 字节数据表示,最大值为 255,最小值为 0。将 4096 次采样结果按顺序写入 ROM 的 4096 个存储单元,一个完整周期正弦波的数字幅度信号就写入了波形数据表 ROM 中。波形数据表 ROM 以相位调制器传入的相位码为 ROM 读地址,将地址对应存储单元的电压幅值数字量进行输出。

DAC 转换:将输入的电压幅值数字量转换为模拟量输出。根据奈奎斯特采样定理,频率控制字的最大值应小于 2^N / 2。

运行流程为:

相位累加器得到的相位码是如何实现 ROM 寻址的呢?
对于 N 位的相位累加器,其对应的相位累加值为 2^N,如果正弦 ROM 中存储单元的个数也是 2^N 的话,那这个问题就很容易解决,但是这对 ROM 的存储容量要求较高。
在实际中,我们使用相位累加器的高几位来对 ROM 进行寻址,也就是说并不是每个系统时钟都对 ROM 进行数据读取,而是多个时钟读取一次,因为这样能保证相位累加器溢出时,从正弦 ROM 表中取出正好一个正弦周期的样点。
因此,相位累加器每计数 2^N 次,对应一个正弦周期。而相位累加器 1 秒钟计数 fclk 次(工作时钟频率),当频率控制字 FTW 为 1 时,DDS 输出的时钟频率就是频率分辨率。当 FTW 增加时,相位累加器溢出的频率增加,对应 DDS 输出信号频率就变成了 FTW 倍的 DDS 频率分辨率。
例如: 假设 ROM 的存储单元个数为 4096,每个存储数据用 8 位二进制表示。即 ROM 地址线宽为 12,数据线宽为 8;相位累加器位宽设置为 N=32。 由于 ROM 地址线位宽为 12,所以相位调制器的输出位宽也应该为 12。(因为 DDS 原理就是通过相位调制器的输出去 ROM 寻址)在相位调制器中与相位控制字进行累加时,用相位累加器的高 12 位累加,而低位只与频率控制字累加。
以频率控制字 FTW=1 为例,相位累加器的低 20 位一直加 1,直到低 20 位溢出而向高 12 位进位,此时 ROM 为 0,也就是说,ROM 的 0 地址中的数据被读了 2^20 次,继续往下,ROM 的 4096 个点全部都会读 2^20 次,最终输出的波形频率应该是参考时钟频率的 1/2^20,周期被扩大了 2^20 倍。同样当频率控制字为 100 时,所以最终输出频率是上述的 100 倍。
2. 代码实现
本次实验的硬件平台为 AC101-EDA,开发软件为 Quartus13.1。

由上节对 DDS 原理的介绍,可以分为以下几个模块。

2.1 控制模块
频率控制模块、相位控制模块、幅度控制模块这三个模块的逻辑一模一样,即:指定一个按键,通过按键的值将对应的参数进行改变。下面以幅度控制模块进行举例说明。
当系统检测到按键被按下后,将变量 cnt 进行计数:

即用 cnt 来表示按键的按下的次数,这里是当 cnt 按下 5 次后,被清零。其中 key_flag 表示按键按下,由按键消抖模块输出。
amplitude 的值由 cnt 的值决定,用一个 case 语句即可实现。

其他两个模块几乎一模一样,这里不再赘述。
2.2 DDS 实现模块


还需要设计一个波形选择的功能,可按如下实现。

其中 dac_data0、dac_data1、dac_data2、dac_data3 分别对应四个 rom 的输出。

2.3 ip 核 ROM 的实现
在 Quartus13.1 中,rom 使用 ip 核进行实现:由于实验所用的开发板的 DAC 为 8 位,所以 ROM 的数据位宽这里也设置为 8。


总共使用 4 个 ROM,分别对应正弦波、三角波、方波、锯齿波,ROM 所需的.mif 文件由 matlab 实现。如正弦波文件的 matlab 代码如下:
cclc; %清除命令行命令 clear all; %清除工作区变量,释放内存空间 F1=1; %信号频率 Fs=2^12; %采样频率 P1=0; %信号初始相位 N=2^12; %采样点数 t=[0:1/Fs:(N-1)/Fs]; %采样时刻 ADC=2^7 - 1; %直流分量 A=2^7; %信号幅度 %生成正弦信号 s=A*sin(2*pi*F1*t + pi*P1/180) + ADC; plot(s); %绘制图形 %创建 mif 文件 fild = fopen('sin_wave_4096x8.mif','wt'); %写入 mif 文件头 fprintf(fild, '%s\n','WIDTH=8;'); %位宽 fprintf(fild, '%s\n\n','DEPTH=4096;'); %深度 fprintf(fild, '%s\n','ADDRESS_RADIX=UNS;'); %地址格式 fprintf(fild, '%s\n\n','DATA_RADIX=UNS;'); %数据格式 fprintf(fild, '%s\t','CONTENT'); %地址 fprintf(fild, '%s\n','BEGIN'); %开始 for i = 1:N s0(i) = round(s(i)); %对小数四舍五入以取整 if s0(i) <0 %负 1 强制置零 s0(i) = 0 end fprintf(fild, '\t%g\t',i-1); %地址编码 fprintf(fild, '%s\t',':'); %冒号 fprintf(fild, '%d',s0(i)); %数据写入 fprintf(fild, '%s\n',';'); %分号,换行 end fprintf(fild, '%s\n','END;'); %结束 fclose(fild);
注意确保生成的.mif 文件位于 Quartus 的工程目录下。

DAC 驱动模块:
开发板上的 DAC 是 MCP4802,采用的 SPI 通信。代码部分参考相关 SPI 通信文档。
在 Quartus 中查看 RTL 视图:

3. 实验结果
3.1 仿真结果

3.2 实际输出结果
3.2.1 波形选择

3.2.2 频率控制

3.2.3 幅度控制



