3. 简易DDS信号发生器的设计与验证¶
DDS是直接数字式频率合成器(Direct Digital Synthesizer)的英文缩写,是一项关键的数字化技术。与传统的频率合成器相比,DDS具有低成本、低功耗、高分辨率和快速转换时间等优点,广泛使用在电信与电子仪器领域,是实现设备全数字化的一个关键技术。作为设计人员,我们习惯称它为信号发生 器,一般用它产生正弦、锯齿、方波等不同波形或不同频率的信号波形,在电子设计和测试中得到广泛应用。
本章节,我们将带领读者学习一下DDS的相关知识,运用所学知识,设计实现一个简易的DDS信号发生器,并上板验证。
3.1. 理论学习¶
DDS技术是一种全新的频率合成方法,其具有低成本、低功耗、高分辨率和快速转换时间等优点,对数字信号处理及其硬件实现有着很重要的作用。
DDS的基本结构主要由相位累加器、相位调制器、波形数据表ROM、D/A转换器等四大结构组成,其中较多设计还会在数模转换器之后增加一个低通滤波器。DDS结构示意图,具体见图 32‑1。
图 32‑1 DDS结构示意图(N、M表示位宽)
图 32‑1中是不含低通滤波器的DDS结构示意图,图中主要包括相位累加器、相位调制器、波形存储器、数模(D/A)转换器等四大结构。接下来我们会结合DDS结构示意图,为大家讲解一下DDS的工作原理。
在讲解之前,先来对其中各参数做一下说明。系统时钟CLK为整个系统的工作时钟,频率为fCLK;频率字输入F_WORD,一般为整数,数值大小控制输出信号的频率大小,数值越大输出信号频率越高,反之,输出信号频率越低,后文中用K表示;相位字输入P_WORD,为整数,数值大小控制输出信号的相位偏移,主要用于相位的信号调制,后文用P表示;设输出信号为CLK_OUT,频率为fOUT。
图中所展示的四大结构中,相位累加器是整个DDS的核心,在这里完成相位累加,生成相位码。相位累加器的输入为频率字输入K,表示相位增量,设其位宽为N,满足等式K = 2N * fOUT / fCLK 。其在输入相位累加器之前,在系统时钟同步下做数据寄存,数据改变时不会干扰相位累加器的正常工作。
相位调制器接收相位累加器输出的相位码, 在这里加上一个相位偏移值P,主要用于信号的相位调制,如应用于通信方面的相移键控等,不使用此部分时可以去掉,或者将其设为一个常数输入,同样相位字输入也要做寄存。
波形数据表ROM中存有一个完整周期的正弦波信号。假设波形数据ROM的地址位宽为12位,存储数据位宽为8位,即ROM有212 = 4096个存储空间,每个存储空间可存储1字节数据。将一个周期的正弦波信号,沿横轴等间隔采样212 = 4096次,每次采集的信号幅度用1字节数据表示,最大值为255,最小值为0。将4096次采样结果按顺序写入ROM的4096个存储单元,一个完整周期正弦波的数字幅度信号写入了波形数据表ROM中。波形数据表ROM以相位调制器传入的相位码为ROM读地址,将地址对应存储单元中的电压幅值数字量输出。
D/A转换器将输入的电压幅值数字量转换为模拟量输出,就得到输出信号CLK_OUT。
输出信号CLK_OUT的信号频率fOUT = K * fCLK / 2N。当K = 1时,可得DDS最小分辨率为:fOUT = fCLK / 2N,此时输出信号频率最低。根据采样定理,K的最大值应小于2N / 2。
讲到这里,读者会心存疑虑,相位累加器得到的相位码是如何实现ROM寻址的呢?
对于N位的相位累加器,它对应的相位累加值为2N,如果正弦ROM中存储单元的个数也是2N的话,这个问题就很好解决,但是这对ROM的对存储容量的要求较高。在实际操作中,我们使用相位累加值的高几位对ROM进行寻址,也就是说并不是每个系统时钟都对ROM进行数据读取,而是多个时钟读取一次,因为这样能保证相位累加器溢出时,从正弦ROM表中取出正好一个正弦周期的样点。
因此,相位累加器每计数2N次,对应一个正弦周期。而相位累加器1秒钟计数fCLK次,在k=1时,DDS输出的时钟频率就是频率分辨率。频率控制字K增加时,相位累加器溢出的频率增加,对应DDS输出信号CLK_OUT频率变为K倍的DDS频率分辨率。
举个例子:
设:ROM存储单元个数为4096,每个存储数据用8位二进制表示。即,ROM地址线宽度为12,数据线宽度为8;相位累加器位宽N = 32。
根据上述条件可以知道,相位调制器位宽M = 12,那么根据DDS原理。那么在相位调制器中与相位控制字进行累加时,应用相位累加器的高12位累加。而相位累加器的低20位只与频率控制字累加。
我们以频率控制字K = 1为例,相位累加器的低20位一直会加1,直到低20位溢出向高12位进位,此时ROM为0,也就是说,ROM的0地址中的数据被读了220次,继续下去,ROM中的4096个点,每个点都将会被读220次,最终输出的波形频率应该是参考时钟频率的1 / 220,周期被扩大了220 倍。同样当频率控制字为100时,相位累加器的低20位一直会加100,那么,相位累加器的低20位溢出的时间比上面会快100倍,则ROM中的每个点相比于上面会少读100次,所以最终输出频率是上述的10倍。
自波形数据表ROM输出的波形数据传入D/A转换器转换为模拟信号。D/A转换器即数/模转换器,简称DAC(Digital to Analog Conver),是指将数字信号转换为模拟信号的电子元件或电路。
DAC内部电路构造无太大差异,大多数DAC由电阻阵列和n个电流开关(或电压开关)构成,按照输入的数字值进行开关切换,输出对应电流或电压。因此,按照输出信号类型可分为电压型和电流型,也可以按照DAC能否做乘法运算进行分类。若将DAC分为电压型和电流型两大类,电压型DAC中又有权电阻网络、T形电阻网络、 树形开关网络等分别;电流型DAC中又有权电流型电阻网络和倒T形电阻网络等。
电压输出型DAC一般采用内置输出放大器以低阻抗输出,少部分直接通过电阻阵列进行电压输出。直接输出电压的DAC仅用于高阻抗负载,由于无输出放大器部分的延迟,故常作为高速DAC使用。
电流输出型DAC很少直接利用电流输出,大多外接电流 - 电压转换电路进行电压输出。实现电流 - 电压转换,方法有二:一是只在输出引脚上接负载电阻而进行电流- 电压转换,二是外接运算放大器。
DAC的主要技术指标包括分辨率、线性度、转换精度和转换速度。
分辨率指输出模拟电压的最小增量,即表明DAC输入一个最低有效位(LSB)而在输出端上模拟电压的变化量。
线性度在理想情况下,DAC的数字输入量作等量增加时,其模拟输出电压也应作等量增加,但是实际输出往往有偏离。
D/A转换器的转换精度与D/A转换器的集成芯片的结构和接口电路配置有关。如果不考虑其他D/A转换误差时,D/A的转换精度就是分辨率的大小,因此要获得高精度的D/A转换结果,首先要保证选择有足够分辨率的D/A转换器。同时D/A转换精度还与外接电路的配置有关,当外部电路器件或电源误差较大时,会造成较大的 D/A转换误差,当这些误差超过一定程度时,D/A转换就产生错误。
转换速度一般由建立时间决定。建立时间是将一个数字量转换为稳定模拟信号所需的时间,也可以认为是转换时间。DA中常用建立时间来描述其速度,而不是AD中常用的转换速率。一般地,电流输出DA建立时间较短,电压输出DA则较长。
3.2. 实战演练¶
3.2.1. 实验目标¶
使用FPGA开发板和外部挂载的高速AD/DA板卡,设计并实现一个简易DDS信号发生器,可通过按键控制实现正弦波、方波、三角波和锯齿波的波形输出,频率相位可调。
3.2.2. 硬件资源¶
如图 32‑2所示,我们使用外载AD/DA板卡的DA部分完成本次实验设计;如图 32‑3所示为外载AD/DA板卡DA部分原理图,包括高速DA芯片和外围电路。
图 32‑2 外载AD/DA板卡外观图
图 32‑3 外载AD/DA板卡DA部分原理图
外载AD/DA板卡的DA部分使用高速DA芯片AD9708,AD9708由ANALOG公司生产,属于TxDAC™系列高性能、低功耗CMOS数模转换器(DAC)的8位分辨率产品。
TxDAC™系列由引脚兼容的8、10、12、14位DAC组成,并专门针对通信系统的发射信号路径进行了优化。所有器件都采用相同的接口选项、小型封装和引脚排列,因而可以根据性能、分辨率和成本,向上或向下选择适合的器件。
AD9708提供出色的交流和直流性能,同时支持最高125 MSPS的更新速率。具有灵活的单电源工作电压范围(2.7 V至5.5 V)和低功耗特性,非常适合便携式和低功耗应用。通过降低满量程电流输出,可以将功耗进一步降至45 mW,而性能不会明显下降,此外,在省电模式下,待机功耗可降至约20 mW。采用先进的CMOS工艺制造。分段电流源架构与专有开关技术相结合,可减小杂散分量,并增强了动态性能。该器件还集成边沿触发式输入锁存器和一个温度补偿带隙基准电压源,可提供一个完整的单芯片DAC解决方案。灵活的电源选项支持+3 V和+5 V CMOS逻辑系列。
AD9708是一款电流输出DAC,标称满量程输出电流为20 mA,输出阻抗大于100 kΩ。它提供差分电流输出,以支持单端或差分应用。电流输出可以直接连至一个输出电阻,以提供两路互补的单端电压输出。可兼容输出电压范围为1.25 V。内置一个1.2 V片内基准电压源和基准电压控制放大器,只需用单个电阻便可轻松设置满量程输出电流。该器件可以采用多种外部基准电压驱动。其满量程电流可以在2 mA至20 mA范围内调节,动态性能不受影响。因此,AD9708能够以低功耗水平工作,或在20 dB范围内进行调节,进一步提供增益范围调整能力。
AD9708采用28引脚SOIC封装,引脚图及内部结构图如下图 32‑4、图 32‑5所示。关于AD9708的更多详细资料可查阅数据手册。
图 32‑4 AD9708引脚图
图 32‑5 AD9708内部结构图
3.2.3. 程序设计¶
学习了DDS的相关知识,了解了硬件资源,根据要求进行实验工程的程序设计,接下来我们会先对实验工程进行整体说明,随后对相关子功能模块做系统讲解。
3.2.3.1. 整体说明¶
由理论知识小节可知,要实现DDS信号发生器需要4部分,D/A转换器交由外部挂载的高速AD/DA板卡处理,其他3部分,相位累加器、相位调制器、波形数据表ROM由FPGA负责。所以我们要建立一个单独的模块对DDS部分进行处理;实验目标还提到要使用按键实现4种波形的切换,按键消抖模块必不可少;同时也要声明 一个按键控制模块对4个输入按键进行控制,子功能模块已经足够了,最后再加一个顶层模块。
综上所述,得到实验工程整体框图如图 32‑6所示。
图 32‑6 实验工程整体框图
时钟、复位和代表波形选择的4个按键信号通过顶层传入按键控制模块(key_control),按键控制模块内部实例化4个按键消抖模块,对输入的4路按键信号分别进行消抖处理;消抖处理后的4路按键信号组成波形选择信号输入dds模块(dds), dds模块中实例化一个ROM IP核,按顺序存入了一个完整周期的 正弦波、方波、三角波、锯齿波的信号波形,根据输入波形选择信号对rom中对应信号波形进行读取,将读出波形的幅度数字值输出,传入外部挂载的高速AD/DA板卡的DA端,板卡根据输入的数字信号生成对应波形的模拟信号。其中,输出信号的频率和相位的调节可在dds模块中通过修改参数实现。
3.2.3.2. ROM内波形数据写入¶
我们设计的DDS简易信号发生器想要实现正弦波、方波、三角波和锯齿波4种波形的输出,需要事先在波形数据表ROM中存入4种波形信号各自的完整周期波形数据。ROM作为只读存储器,在进行IP核设置时需要指定初始化文件,我们将波形数据作为初始化文件写入其中,文件格式为MIF文件。
使用MatLab绘制4种信号波形,对波形进行等间隔采样,以采样次数作为ROM存储地址,将采集的波形幅值数据做为存储数据写入存储地址对应的存储空间。在本次实验中我们对4种信号波形进行分别采样,采样次数为212 = 4096次,采集的波形幅值数据位宽为8bit,将采集数据保存为MIF文件。
各波形采样参考代码如下。
代码清单 32‑1 正弦信号波形采集参考代码(sin_wave.m)
01 clc; %清除命令行命令
02 clear all; %清除工作区变量,释放内存空间
03 F1=1; %信号频率
04 Fs=2^12; %采样频率
05 P1=0; %信号初始相位
06 N=2^12; %采样点数
07 t=[0:1/Fs:(N-1)/Fs]; %采样时刻
08 ADC=2^7 - 1; %直流分量
09 A=2^7; %信号幅度
10 %生成正弦信号
11 s=A*sin(2*pi*F1*t + pi*P1/180) + ADC;
12 plot(s); %绘制图形
13 %创建mif文件
14 fild = fopen(‘sin_wave_4096x8.mif’,’wt’);
15 %写入mif文件头
16 fprintf(fild, ‘%sn’,’WIDTH=8;’); %位宽
17 fprintf(fild, ‘%snn’,’DEPTH=4096;’); %深度
18 fprintf(fild, ‘%sn’,’ADDRESS_RADIX=UNS;’); %地址格式
19 fprintf(fild, ‘%snn’,’DATA_RADIX=UNS;’); %数据格式
20 fprintf(fild, ‘%st’,’CONTENT’); %地址
21 fprintf(fild, ‘%sn’,’BEGIN’); %开始
22 for i = 1:N
23 s0(i) = round(s(i)); %对小数四舍五入以取整
24 if s0(i) <0 %负1强制置零
25 s0(i) = 0
26 end
27 fprintf(fild, ‘t%gt’,i-1); %地址编码
28 fprintf(fild, ‘%st’,’:’); %冒号
29 fprintf(fild, ‘%d’,s0(i)); %数据写入
30 fprintf(fild, ‘%sn’,’;’); %分号,换行
31 end
32 fprintf(fild, ‘%sn’,’END;’); %结束
33 fclose(fild);
图 32‑7 MatLab生成的正弦信号波形
代码清单 32‑2 方波信号波形采集参考代码(squ_wave.m)
01 clc; %清除命令行命令
02 clear all; %清除工作区变量,释放内存空间
03 F1=1; %信号频率
04 Fs=2^12; %采样频率
05 P1=0; %信号初始相位
06 N=2^12; %采样点数
07 t=[0:1/Fs:(N-1)/Fs]; %采样时刻
08 ADC=2^7 - 1; %直流分量
09 A=2^7; %信号幅度
10 %生成方波信号
11 s=A*square(2*pi*F1*t + pi*P1/180) + ADC;
12 plot(s); %绘制图形
13 %创建mif文件
14 fild = fopen(‘squ_wave_4096x8.mif’,’wt’);
15 %写入mif文件头
16 fprintf(fild, ‘%sn’,’WIDTH=8;’); %位宽
17 fprintf(fild, ‘%snn’,’DEPTH=4096;’); %深度
18 fprintf(fild, ‘%sn’,’ADDRESS_RADIX=UNS;’); %地址格式
19 fprintf(fild, ‘%snn’,’DATA_RADIX=UNS;’); %数据格式
20 fprintf(fild, ‘%st’,’CONTENT’); %地址
21 fprintf(fild, ‘%sn’,’BEGIN’); %开始
22 for i = 1:N
23 s0(i) = round(s(i)); %对小数四舍五入以取整
24 if s0(i) <0 %负1强制置零
25 s0(i) = 0
26 end
27 fprintf(fild, ‘t%gt’,i-1+N); %地址编码
28 fprintf(fild, ‘%st’,’:’); %冒号
29 fprintf(fild, ‘%d’,s0(i)); %数据写入
30 fprintf(fild, ‘%sn’,’;’); %分号,换行
31 end
32 fprintf(fild, ‘%sn’,’END;’); %结束
33 fclose(fild);
图 32‑8 MatLab生成的方波信号波形
代码清单 32‑3 三角波信号波形采集参考代码(tri_wave.m)
01 clc; %清除命令行命令
02 clear all; %清除工作区变量,释放内存空间
03 F1=1; %信号频率
04 Fs=2^12; %采样频率
05 P1=0; %信号初始相位
06 N=2^12; %采样点数
07 t=[0:1/Fs:(N-1)/Fs]; %采样时刻
08 ADC=2^7 - 1; %直流分量
09 A=2^7; %信号幅度
10 %生成三角波信号
11 s=A*sawtooth(2*pi*F1*t + pi*P1/180,0.5) + ADC;
12 plot(s); %绘制图形
13 %创建mif文件
14 fild = fopen(‘tri_wave_4096x8.mif’,’wt’);
15 %写入mif文件头
16 fprintf(fild, ‘%sn’,’WIDTH=8;’); %位宽
17 fprintf(fild, ‘%snn’,’DEPTH=4096;’); %深度
18 fprintf(fild, ‘%sn’,’ADDRESS_RADIX=UNS;’); %地址格式
19 fprintf(fild, ‘%snn’,’DATA_RADIX=UNS;’); %数据格式
20 fprintf(fild, ‘%st’,’CONTENT’); %地址
21 fprintf(fild, ‘%sn’,’BEGIN’); %开始
22 for i = 1:N
23 s0(i) = round(s(i)); %对小数四舍五入以取整
24 if s0(i) <0 %负1强制置零
25 s0(i) = 0
26 end
27 fprintf(fild, ‘t%gt’,i-1+(2*N)); %地址编码
28 fprintf(fild, ‘%st’,’:’); %冒号
29 fprintf(fild, ‘%d’,s0(i)); %数据写入
30 fprintf(fild, ‘%sn’,’;’); %分号,换行
31 end
32 fprintf(fild, ‘%sn’,’END;’); %结束
33 fclose(fild);
图 32‑9 MatLab生成的三角波信号波形
代码清单 32‑4 锯齿波信号波形采集参考代码(saw_wave.m)
01 clc; %清除命令行命令
02 clear all; %清除工作区变量,释放内存空间
03 F1=1; %信号频率
04 Fs=2^12; %采样频率
05 P1=0; %信号初始相位
06 N=2^12; %采样点数
07 t=[0:1/Fs:(N-1)/Fs]; %采样时刻
08 ADC=2^7 - 1; %直流分量
09 A=2^7; %信号幅度
10 %生成锯齿波信号
11 s=A*sawtooth(2*pi*F1*t + pi*P1/180) + ADC;
12 plot(s); %绘制图形
13 %创建mif文件
14 fild = fopen(‘saw_wave_4096x8.mif’,’wt’);
15 %写入mif文件头
16 fprintf(fild, ‘%sn’,’WIDTH=8;’); %位宽
17 fprintf(fild, ‘%snn’,’DEPTH=4096;’); %深度
18 fprintf(fild, ‘%sn’,’ADDRESS_RADIX=UNS;’); %地址格式
19 fprintf(fild, ‘%snn’,’DATA_RADIX=UNS;’); %数据格式
20 fprintf(fild, ‘%st’,’CONTENT’) ;%地址
21 fprintf(fild, ‘%sn’,’BEGIN’); %开始
22 for i = 1:N
23 s0(i) = round(s(i)); %对小数四舍五入以取整
24 if s0(i) <0 %负1强制置零
25 s0(i) = 0
26 end
27 fprintf(fild, ‘t%gt’,i-1+(3*N)); %地址编码
28 fprintf(fild, ‘%st’,’:’); %冒号
29 fprintf(fild, ‘%d’,s0(i)); %数据写入
30 fprintf(fild, ‘%sn’,’;’); %分号,换行
31 end
32 fprintf(fild, ‘%sn’,’END;’); %结束
33 fclose(fild);
图 32‑10 MatLab生成的锯齿波信号波形
使用MatLab对4种波形进行采样后,生成4个MIF文件,分别对应4种波形,我们可以调用4个深度为4096,位宽为8bit的ROM IP核,将4种波形对应的MIF文件分别写入,生成4个波形数据表ROM。
也可以调用一个深度为4096*4,位宽为8bit的ROM,生成一个MIF文件,这个MIF文件位上述4个MIF文件的集合波形数据按照正弦波、方波、三角波、锯齿波的顺序写入。总的MIF文件生成代码如下所示。
代码清单 32‑5 整体信号波形采集参考代码(wave_16384x8.m)
01 clc; %清除命令行命令
02 clear all; %清除工作区变量,释放内存空间
03 F1=1; %信号频率
04 Fs=2^12; %采样频率
05 P1=0; %信号初始相位
06 N=2^12; %采样点数
07 t=[0:1/Fs:(N-1)/Fs]; %采样时刻
08 ADC=2^7 - 1; %直流分量
09 A=2^7; %信号幅度
10 s1=A*sin(2*pi*F1*t + pi*P1/180) + ADC; %正弦波信号
11 s2=A*square(2*pi*F1*t + pi*P1/180) + ADC; %方波信号
12 s3=A*sawtooth(2*pi*F1*t + pi*P1/180,0.5) + ADC; %三角波信号
13 s4=A*sawtooth(2*pi*F1*t + pi*P1/180) + ADC; %锯齿波信号
14 %创建mif文件
15 fild = fopen(‘all_wave_16384x8.mif’,’wt’);
16 %写入mif文件头
17 fprintf(fild, ‘%sn’,’WIDTH=8;’); %位宽
18 fprintf(fild, ‘%snn’,’DEPTH=16384;’); %深度
19 fprintf(fild, ‘%sn’,’ADDRESS_RADIX=UNS;’); %地址格式
20 fprintf(fild, ‘%snn’,’DATA_RADIX=UNS;’); %数据格式
21 fprintf(fild, ‘%st’,’CONTENT’); %地址
22 fprintf(fild, ‘%sn’,’BEGIN’); %开始
23 for j = 1:4
24 for i = 1:N
25 if j == 1 %打印正弦信号数据
26 s0(i) = round(s1(i)); %对小数四舍五入以取整
27 fprintf(fild, ‘t%gt’,i-1); %地址编码
28 end
29
30 if j == 2 %打印方波信号数据
31 s0(i) = round(s2(i)); %对小数四舍五入以取整
32 fprintf(fild, ‘t%gt’,i-1+N); %地址编码
33 end
34
35 if j == 3 %打印三角波信号数据
36 s0(i) = round(s3(i)); %对小数四舍五入以取整
37 fprintf(fild, ‘t%gt’,i-1+(2*N)); %地址编码
38 end
39
40 if j == 4 %打印锯齿波信号数据
41 s0(i) = round(s4(i)); %对小数四舍五入以取整
42 fprintf(fild, ‘t%gt’,i-1+(3*N)); %地址编码
43 end
44
45 if s0(i) <0 %负1强制置零
46 s0(i) = 0
47 end
48
49 fprintf(fild, ‘%st’,’:’); %冒号
50 fprintf(fild, ‘%d’,s0(i)); %数据写入
51 fprintf(fild, ‘%sn’,’;’); %分号,换行
52 end
53 end
54 fprintf(fild, ‘%sn’,’END;’); %结束
55 fclose(fild);
本次实验采用第二种方法,生成了一个波形数据表ROM,将4种信号波形数据按照正弦波、方波、三角波、锯齿波的顺序写入。
3.2.3.3. 按键控制模块¶
模块设计
本实验设计的DDS信号发生器,可以实现4种信号波形的输出,使用外部物理按键实现波形的切换,一个按键控制一种波形,共使用4个按键。外部物理按键的触发信号通过顶层模块输入按键控制模块,按键控制模块内部实例化4个按键消抖消抖模块,分别对4路按键信号做消抖处理。消抖处理后的4路按键信号组成位宽为4bit的波 形选择信号并输出至DDS模块。波形选择信号初值为4’b0000,当某一按键按下,波形选择信号对应位电平拉高。模块框图,具体见图 32‑11。
图 32‑11 按键控制模块框图
波形图绘制
图 32‑12 按键控制模块整体波形图
由图可知,输入的信号有3路,时钟、复位和未消抖的按键控制,为了方便观察,将未消抖的按键控制信号拆开绘制,每一位按键信号代表一种信号波形的选择,但有一点要注意,每次只能按下一个按键。
首先要对4路未消抖的按键信号进行消抖处理,这里需要调用4个按键消抖模块,如图 32‑11所示。消抖完毕之后我们需要声明4个变量将消抖后的按键信号引出,即波形图中的黄色变量key0、key1、key2、key3。
接下来,使用引出的消抖后的按键信号key0、key1、key2、key3为约束条件,产生我们的波形选择信号wave_select,给他一个初值4’b0000,当按键信号key0为高电平,wave_select赋值为4’b0001,表示输出波形为正弦波;当按键信号key1、key2、key3各自为高电 平时,wave_select分别赋值为4’b0010、4’b0100、4’b1000,表示输出波形分别为方波、三角波、锯齿波,其他状态保持原值不变。
代码编写
参照波形图编写模块参考代码,参考代码如代码清单 32‑6所示。
代码清单 32‑6 按键控制模块参考代码(key_control.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | module key_control
(
input wire sys_clk , //系统时钟,50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [3:0] key , //输入4位按键
output reg [3:0] wave_select //输出波形选择
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter sin_wave = 4'b0001, //正弦波
squ_wave = 4'b0010, //方波
tri_wave = 4'b0100, //三角波
saw_wave = 4'b1000; //锯齿波
parameter CNT_MAX = 20'd999_999; //计数器计数最大值
//wire define
wire key3 ; //按键3
wire key2 ; //按键2
wire key1 ; //按键1
wire key0 ; //按键0
////
//\* Main Code \//
////
//wave:按键状态对应波形
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wave_select <= 4'b0000;
else if(key0 == 1'b1)
wave_select <= sin_wave;
else if(key1 == 1'b1)
wave_select <= squ_wave;
else if(key2 == 1'b1)
wave_select <= tri_wave;
else if(key3 == 1'b1)
wave_select <= saw_wave;
else
wave_select <= wave_select;
////
//\* Instantiation \//
////
//------------- key_fifter_inst3 --------------
key_filter
#(
.CNT_MAX (CNT_MAX ) //计数器计数最大值
)
key_filter_inst3
(
.sys_clk (sys_clk ) , //系统时钟50Mhz
.sys_rst_n (sys_rst_n) , //全局复位
.key_in (key[3] ) , //按键输入信号
.key_flag (key3 ) //按键消抖后标志信号
);
//------------- key_fifter_inst2 --------------
key_filter
#(
.CNT_MAX (CNT_MAX ) //计数器计数最大值
)
key_filter_inst2
(
.sys_clk (sys_clk ) , //系统时钟50Mhz
.sys_rst_n (sys_rst_n) , //全局复位
.key_in (key[2] ) , //按键输入信号
.key_flag (key2 ) //按键消抖后标志信号
);
//------------- key_fifter_inst1 --------------
key_filter
#(
.CNT_MAX (CNT_MAX ) //计数器计数最大值
)
key_filter_inst1
(
.sys_clk (sys_clk ) , //系统时钟50Mhz
.sys_rst_n (sys_rst_n) , //全局复位
.key_in (key[1] ) , //按键输入信号
.key_flag (key1 ) //按键消抖后标志信号
);
//------------- key_fifter_inst0 --------------
key_filter
#(
.CNT_MAX (CNT_MAX ) //计数器计数最大值
)
key_filter_inst0
(
.sys_clk (sys_clk ) , //系统时钟50Mhz
.sys_rst_n (sys_rst_n) , //全局复位
.key_in (key[0] ) , //按键输入信号
.key_flag (key0 ) //按键消抖后标志信号
);
endmodule
|
仿真验证
仿真文件编写
模块代码编写完成,编写仿真文件对模块代码进行仿真验证,仿真文件参考代码如下代码清单 32‑7所示。
代码清单 32‑7 按键控制模块仿真参考代码(tb_key_control.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | \`timescale 1ns/1ns
module tb_key_control();
////
//\* Parameter and Internal Signal \//
////
parameter CNT_1MS = 20'd19 ,
CNT_11MS = 21'd69 ,
CNT_41MS = 22'd149 ,
CNT_51MS = 22'd199 ,
CNT_60MS = 22'd249 ;
//wire define
wire [3:0] wave_select ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg [21:0] tb_cnt ;
reg key_in ;
reg [1:0] cnt_key ;
reg [3:0] key ;
//defparam define
defparam key_control_inst.CNT_MAX = 24;
////
//\* Main Code \//
////
//sys_rst_n,sys_clk,key
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
key <= 4'b0000;
#200;
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tb_cnt <= 22'b0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 22'b0;
else
tb_cnt <= tb_cnt + 1'b1;
//key_in:产生输入随机数,模拟按键的输入情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1;
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
\|\| (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2;
else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
key_in <= 1'b0;
else
key_in <= 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_key <= 2'd0;
else if(tb_cnt == CNT_60MS)
cnt_key <= cnt_key + 1'b1;
else
cnt_key <= cnt_key;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key <= 4'b1111;
else
case(cnt_key)
0: key <= {3'b111,key_in};
1: key <= {2'b11,key_in,1'b1};
2: key <= {1'b1,key_in,2'b11};
3: key <= {key_in,3'b111};
default:key <= 4'b1111;
endcase
////
//\* Instantiation \//
////
//------------- key_control_inst -------------
key_control key_control_inst
(
.sys_clk (sys_clk ), //系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.key (key ), //输入4位按键
.wave_select (wave_select) //输出波形选择
);
endmodule
|
仿真波形分析
仿真参考代码编写完成,使用Modelsim对按键控制模块进行仿真验证,仿真波形如下图 32‑13、图 32‑14所示。由图可知,各信号仿真波形与绘制波形图波形变化一致,模块仿真验证通过。
图 32‑13 按键控制模块整体仿真波形
图 32‑14 按键控制模块局部仿真波形
3.2.3.4. DDS模块¶
模块框图
波形数据表ROM生成完毕,按键控制模块也做了说明,我们开始本实验工程的核心模块DDS模块的讲解。DDS模块框图,具体见图 32‑15。
图 32‑15 DDS模块框图
DDS模块有3路输入信号,1路输出信号,其内部例化了前面生成的波形数据表ROM;输入信号中有时钟sys_clk、复位sys_rst_n和按键控制模块输入的波形选择信号wave_select。输入的波形选择信号有4种状态,分别对应4中波形,根据输入的波形选择信号的不同,对ROM中波形选择信号对应波形的 存储位置进行数据读取,将读出波形幅值数据通过输出信号data_out输出到外部挂载DA板块,进行数模转换。
波形图绘制
DDS模块的各输入输出信号和模块功能讲解完毕,我们开始模块波形图的绘制,对波形图各信号进行详细说明,讲解一下模块功能实现方法。DDS模块整体波形图,具体见图 32‑16。
图 32‑16 DDS模块波形图
本模块包含3路输入信号,1路输出信号,内部声明3个寄存器变量。输入信号包括时钟、复位和输出波形选择信号wave_select。波形选择信号有4个状态,对应4种波形。
内部声明3个寄存器变量。其中fre_add表示相位累加器输出值,位宽为32位,系统上电后,fre_add信号一直执行自加操作,每个时钟周期自加参数FREQ_CTRL,参数FREQ_CTRL就是在之前理论知识部分提到的频率字输入K,它的具体数值可通过公式计算得到,前面我们也讲到过。
寄存器变量rom_addr_reg表示相位调制器输出值,将相位累加器输出值的高12位与相位偏移量PHASE_CTRL相加,参数PHASE_CTRL就是我们之前提到过的相位字输入P。之所以使用高12位,与存储波形的ROM深度有关。按理论讲,将得到的变量rom_addr_reg,可直接作为ROM读地址输 入波形数据表进行数据读取,但是我们将4中波形存储在了同一ROM中,所以还需要对读数据地址做进一步计算。
ROM读地址rom_addr是输入波形数据表的ROM读地址,是在rom_addr_reg的基础上计算得到。我们之前将4种信号波形数据按照正弦波、方波、三角波、锯齿波的顺序写入ROM。若需要读取正弦波波形数据,rom_addr_reg可直接赋值给rom_addr;但是要进行方波波形数据的读取,rom_ addr_reg需要再加上正弦波存储单元个数才能赋值给rom_addr;剩余两信号同理。
对于参数FREQ_CTRL和PHASE_CTRL,我们可以修改其参数值,实现不同频率、不同初相位波形的输出。
本实验,我们希望输出一个频率为500Hz,初相位为π/2的正弦波信号。
计算参数FREQ_CTRL,即频率输入字K。
FREQ_CTRL = K = 2N * fOUT / fCLK,其中N = 32(相位累加器输出值fre_add的位宽)、fOUT = 500Hz,fCLK = 50MHz,带入公式,FREQ_CTRL = K = 42949.67296 ,取整数部分为42949;
计算参数PHASE_CTRL,即相位输入字P。
PHASE_CTRL = P = θ / (2π / 2M),其中M =12(输入ROM地址位宽)、θ = π / 2,带入公式,PHASE_CTRL = P = 1024。
ROM读地址rom_addr写入波形数据表ROM中,读出地址对应波形波形数据,通过输出端口data_out输入到外部挂载板卡数模转换部分。
代码编写
参照绘制波形图,编写模块参考代码如下代码清单 32‑8所示。
代码清单 32‑8 DDS模块参考代码(dds.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | module dds
(
input wire sys_clk , //系统时钟,50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [3:0] wave_select , //输出波形选择
output wire [7:0] data_out //波形输出
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter sin_wave = 4'b0001 , //正弦波
squ_wave = 4'b0010 , //方波
tri_wave = 4'b0100 , //三角波
saw_wave = 4'b1000 ; //锯齿波
parameter FREQ_CTRL = 32'd42949 , //相位累加器单次累加值
PHASE_CTRL = 12'd1024 ; //相位偏移量
//reg define
reg [31:0] fre_add ; //相位累加器
reg [11:0] rom_addr_reg; //相位调制后的相位码
reg [13:0] rom_addr ; //ROM读地址
////
//\* Main Code \//
////
//fre_add:相位累加器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
fre_add <= 32'd0;
else
fre_add <= fre_add + FREQ_CTRL;
//rom_addr:ROM读地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
rom_addr <= 14'd0;
rom_addr_reg <= 11'd0;
end
else
case(wave_select)
sin_wave:
begin
rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
rom_addr <= rom_addr_reg;
end //正弦波
squ_wave:
begin
rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
rom_addr <= rom_addr_reg + 14'd4096;
end //方波
tri_wave:
begin
rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
rom_addr <= rom_addr_reg + 14'd8192;
end //三角波
saw_wave:
begin
rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
rom_addr <= rom_addr_reg + 14'd12288;
end //锯齿波
default:
begin
rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
rom_addr <= rom_addr_reg;
end //正弦波
endcase
////
//\* Instantiation \//
////
//------------------------- rom_wave_inst ------------------------
rom_wave rom_wave_inst
(
.address (rom_addr ), //ROM读地址
.clock (sys_clk ), //读时钟
.q (data_out ) //读出波形数据
);
endmodule
|
参考代码编写完成,其中各信号相关知识在波形图绘制小节已经做了详细讲解,此处不再赘述。
仿真验证
仿真文件编写
模块代码编写完成,编写仿真文件对模块代码进行仿真验证,仿真文件参考代码如下代码清单 32‑9所示。
代码清单 32‑9 DDS模块仿真参考代码(tb_dds.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | \`timescale 1ns/1ns
module tb_dds();
////
//\* Parameter and Internal Signal \//
////
//wire define
wire [7:0] data_out ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg [3:0] wave_select ;
////
//\* Main Code \//
////
//sys_rst_n,sys_clk,key
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
wave_select <= 4'b0000;
#200;
sys_rst_n <= 1'b1;
#10000
wave_select <= 4'b0001;
#8000000;
wave_select <= 4'b0010;
#8000000;
wave_select <= 4'b0100;
#8000000;
wave_select <= 4'b1000;
#8000000;
wave_select <= 4'b0000;
#8000000;
end
always #10 sys_clk = ~sys_clk;
////
//\* Instantiation \//
////
//------------- top_dds_inst -------------
dds dds_inst
(
.sys_clk (sys_clk ), //系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.wave_select (wave_select), //输出波形选择
.data_out (data_out ) //波形输出
);
endmodule
|
仿真波形分析
仿真参考代码编写完成,使用Modelsim对DDS模块进行仿真验证,仿真波形如下图 32‑17所示。由图可知,各信号仿真波形与绘制波形图波形变化一致,模块仿真验证通过。
图 32‑17 DDS模块整体仿真波形
DDS模块仿真波形图中,两调波形参考线之间的时间间隔约为200_0000ns,表示一个方波周期,频率约为500Hz;将仿真信号波形与MatLab生成波形对比,可以发现信号波形相位平移了π/2,模块通过仿真验证。
3.2.3.5. 顶层模块¶
模块框图
顶层模块较为简单,内部例化了各子功能模块,连接各对应信号;外部有3路输入信号、2路输出信号。输入有时钟、复位信号和控制信号波形切换的4路按键信号;输出2路信号中,信号dac_data为DDS模块输出的,自波形数据表ROM中读取的波形数据;信号dac_clk为输入至外载板卡的时钟信号,DA模块使用此时 钟进行数据处理,该信号由系统时钟sys_clk取反得到。
波形数据表ROM的读时钟为系统时钟sys_clk,在系统时钟上升沿时对ROM进行数据读取,而DA模块也使用时钟上升沿进行数据处理,将系统时钟sys_clk取反得到dac_clk,dac_clk的上升沿刚好采集到波形数据dac_data的稳定数据。
顶层模块框图,具体见图 32‑18。
图 32‑18 顶层模块框图
代码编写
对于顶层模块,我们不需要进行波形图的绘制,直接编写代码。模块参考代码,具体见代码清单 32‑10。
代码清单 32‑10 顶层模块参考代码(top_dds.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | module top_dds
(
input wire sys_clk , //系统时钟,50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [3:0] key , //输入4位按键
output wire dac_clk , //输入DAC模块时钟
output wire [7:0] dac_data //输入DAC模块波形数据
);
////
//\* Parameter and Internal Signal \//
////
//wire define
wire [3:0] wave_select ; //波形选择
//dac_clka:DAC模块时钟
assign dac_clk = ~sys_clk;
////
//\* Instantiation \//
////
//-------------------------- dds_inst -----------------------------
dds dds_inst
(
.sys_clk (sys_clk ), //系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.wave_select (wave_select), //输出波形选择
.data_out (dac_data ) //波形输出
);
//----------------------- key_control_inst ------------------------
key_control key_control_inst
(
.sys_clk (sys_clk ), //系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.key (key ), //输入4位按键
.wave_select (wave_select) //输出波形选择
);
endmodule
|
3.2.3.6. RTL视图¶
顶层模块介绍完毕,使用Quartus II软件对实验工程进行编译,工程通过编译后查看实验工程RTL视图。工程RTL视图,具体见图 32‑19。由图可知,实验工程的RTL视图与实验整体框图相同,各信号线均已正确连接。
图 32‑19 RTL视图
3.2.3.7. 仿真验证¶
仿真代码编写
编写仿真代码,对工程进行整体仿真。顶层模块仿真参考代码,具体见代码清单 32‑11。
代码清单 32‑11 顶层模块仿真参考代码(tb_top_dds.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | \`timescale 1ns/1ns
module tb_top_dds();
////
//\* Parameter and Internal Signal \//
////
parameter CNT_1MS = 20'd19000 ,
CNT_11MS = 21'd69000 ,
CNT_41MS = 22'd149000 ,
CNT_51MS = 22'd199000 ,
CNT_60MS = 22'd249000 ;
//wire define
wire dac_clk ;
wire [7:0] dac_data ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg [21:0] tb_cnt ;
reg key_in ;
reg [1:0] cnt_key ;
reg [3:0] key ;
//defparam define
defparam top_dds_inst.key_control_inst.CNT_MAX = 24;
////
//\* Main Code \//
////
//sys_rst_n,sys_clk,key
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
key <= 4'b0000;
#200;
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tb_cnt <= 22'b0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 22'b0;
else
tb_cnt <= tb_cnt + 1'b1;
//key_in:产生输入随机数,模拟按键的输入情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1;
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
\|\| (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2;
else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
key_in <= 1'b0;
else
key_in <= 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_key <= 2'd0;
else if(tb_cnt == CNT_60MS)
cnt_key <= cnt_key + 1'b1;
else
cnt_key <= cnt_key;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key <= 4'b1111;
else
case(cnt_key)
0: key <= {3'b111,key_in};
1: key <= {2'b11,key_in,1'b1};
2: key <= {1'b1,key_in,2'b11};
3: key <= {key_in,3'b111};
default:key <= 4'b1111;
endcase
////
//\* Instantiation \//
////
//------------- top_dds_inst -------------
top_dds top_dds_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key (key ),
.dac_clk (dac_clk ),
.dac_data (dac_data )
);
endmodule
|
仿真波形分析
使用ModelSim软件对顶层模块进行仿真,由图可知,输入与输出信号额能正常传入传出顶层模块,顶层模块通过仿真验证。
图 32‑20 顶层模块仿真波形
3.3. 上板验证¶
3.3.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 32‑1所示。
表格 32‑1 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
Input |
E1 |
输入系统时钟 |
sys_rst_n |
Input |
M15 |
复位信号 |
key[3] |
Input |
E16 |
按键KEY4 |
key[2] |
Input |
E15 |
按键KEY3 |
key[1] |
Input |
M1 |
按键KEY2 |
key[0] |
Input |
M2 |
按键KEY1 |
dac_clk |
Input |
E10 |
输出至DAC_CLK |
dac_data[7] |
Output |
F10 |
输出至DAC_D7 |
dac_data[6] |
Output |
C9 |
输出至DAC_D6 |
dac_data[5] |
Output |
B13 |
输出至DAC_D5 |
dac_data[4] |
Output |
D9 |
输出至DAC_D4 |
dac_data[3] |
Output |
A10 |
输出至DAC_D3 |
dac_data[2] |
Output |
A14 |
输出至DAC_D2 |
dac_data[1] |
Output |
B10 |
输出至DAC_D1 |
dac_data[0] |
Output |
E11 |
输出至DAC_D0 |
下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 32‑21所示。
图 32‑21 管脚分配
3.3.1.1. 结果验证¶
如图 32‑22所示,开发板连接12V直流电源和USB-Blaster下载器JTAG端口;连接外载AD/DA板卡与开发板底板,连接SMA信号线。线路正确连接后,打开开关为板卡上电。
图 32‑22 程序下载连线图
如图 32‑23所示,使用“Programmer”为开发板下载程序。
图 32‑23 程序下载窗口
程序下载完成后,使用示波器对AD/DA板卡输出信号进行测量,如图 32‑24至图 32‑27所示,使用按键可进行输出波形切换,通过电位器可实现输出信号幅值调节。
图 32‑24 示波器测量图(一)
图 32‑25 示波器测量图(二)
图 32‑26 示波器测量图(三)
图 32‑27 示波器测量图(四)
3.4. 章末总结¶
在本章节中,我们实现了简易DDS信号发生器的设计与验证,并通过对简易DDS信号发生器的设计与验证这一实验工程讲解,为读者介绍了DDS信号发生器的相关内容和DAC芯片的相关知识,希望读者认真学习、切实掌握相关知识。
3.5. 拓展训练¶
尝试按照公式计算并修改代码中的参数FREQ_CTRL(频率输入字K)和参数PHASE_CTRL(相位输入字P),实现输出不同频率、不同初相位的信号波形。