超级版主 
   
	- 积分
 - 5083
 
        - 金钱
 - 5083 
 
       - 注册时间
 - 2019-5-8
 
      - 在线时间
 - 1269 小时
 
 
 
 | 
 
1)实验平台:正点原子领航者V2FPGA开发板 
2)  章节摘自【正点原子】《领航者ZYNQ之FPGA开发指南 V2.0》 
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975 
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html 
5)正点原子官方B站:https://space.bilibili.com/394620890 
6)正点原子FPGA技术交流QQ群:712557122 
 
 
 
 
 
 
 
 
 
 
 
第二十七章  DDS信号发生器实验  
  
  
DDS(Direct Digital Synthesizer)即直接数字式频率合成器,是一种新型的频率合成技术。与传统的频率合成器相比,DDS具有相对带宽大,频率转换时间短,稳定性好,分辨率高,可灵活产生多种信号等优点。较容易实现频率、相位及幅度的数控调制,因此,在现代电子系统及设备的频率源设计中,尤其在通信领域,直接数字频率合成器的应用越来越广泛。作为设计人员,我们习惯称它为信号发生器,一般用它产生正弦、锯齿、方波等不同波形或不同频率的信号波形,在电子设计和测试中得到广泛应用。本章我们将利用高速ad/da模块去设计实现一个简易的DDS信号发生器。 
本章包括以下几个部分: 
2727.1简介 
27.2实验任务 
27.3硬件设计 
27.4软件设计 
27.5下载验证 
 
 
 
27.1DDS简介 
DDS( Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有低成本、低功耗、高分辨率、频率转换时间短、相位连续性好等优点,对数字信号处理及其硬件实现有着很重要的作用。DDS的基本结构主要由相位累加器、相位调制器、波形数据表ROM、D/A转换器等四大结构组成,其中较多设计还会在数模转换器之后增加一个低通滤波器(LPF)。DDS基本结构图如图 7.5.13.1所示。 
 
图 7.5.13.1 DDS基本结构图
 由上图可以看出,DDS主要由相位累加器、相位调制器、波形数据表以及D/A转换器构成。其中相位累加器由N位加法器与N位寄存器构成。每来一个时钟,加法器就将频率控制字与累加寄存器输出的相位数据相加,相加的结果又反馈至累加寄存器的数据输入端,以使加法器在下一个时钟脉冲的作用下继续与频率控制字相加。这样,相位累加器在时钟的作用下,不断对频率控制字进行线性相位累加。即在每一个时钟脉冲输入时,相位累加器便把频率控制字累加一次。当相位累加器累加满量时就会产生一次溢出,完成一个周期的动作。相位累加器输出的数据就是合成信号的相位。相位累加器的溢出频率,就是DDS输出的信号频率。 
通过改变相位控制字P_WORD可以控制输出信号的相位参数。令相位加法器的字长为M,当相位控制字由0跃变为P_WORD时,波形存储器(ROM)的输入为相位累加器的输出与相位控制字P_WORD之和,因而其输出的幅度编码相位会增加P_WORD/2M,从而使输出的信号产生相移。 
用相位调制器输出的数据,作为波形存储器的相位采样地址,这样就可以把存储在波形存储器里的波形采样值经查表找出,完后相位到幅度的转换。N位的寻址ROM相当于把0°-360°的正弦信号离散成具有2N个样值的序列。若波形存储器中有D位数据位,则2N个样值的幅值以D位二进制数值固化在波形存储器当中。按照地址的不同可以输出相应相位的正弦信号幅值。相位—幅度变换原理图如下图所示: 
 
图 7.5.13.2 相位-幅度变换原理图
 数模转换器(D/A)的作用是把合成的正弦波数字量转化为模拟量。正弦幅度量化序列经数模转换器转换后变成了包络为正弦波的阶梯波。频率合成器对数模转换器的分辨率有一定的要求,其分辨率越高,合成的正弦波台阶数就越多,输出的波形精度也就越高。DDS信号流程图如下图所示: 
 
 
图 7.5.13.3 DDS信号流程图
 27.2实验任务 
本节实验任务是使用领航者ZYNQ开发板及高速AD-DA扩展模块(ATK_HS_AD_DA模块)实现数模及模数的转换。首先ZYNQ PL端产生正弦波、方波、三角波、锯齿波变化的数字信号,经过DA芯片后转换成模拟信号,将DA的模拟电压输出端连接至AD的模拟电压输入端,AD芯片将模拟信号转换成数字信号,通过按下按键key0和key1可以实现波形种类和波形频率的切换,然后通过示波器观察DA端模拟信号的波形是否出现了相应变化,也可以通过Ila界面去进行观察。 
27.3硬件设计 
本章节中硬件设计与“高速AD/DA实验”完全相同,此处不在赘述。本实验中,各端口信号的管脚分配如下表所示。  
表 27.3.1 DDS实验管脚分配
 
 对应的XDC约束语句如下所示: 
- #时序约束
 
 - create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
 
 - #IO管脚约束
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN U18} [get_ports sys_clk]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN N16} [get_ports sys_rst_n]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN L14} [get_ports key0]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN K16} [get_ports key1]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN Y18} [get_ports {da_data[7]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN Y19} [get_ports {da_data[6]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN P15} [get_ports {da_data[5]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN P16} [get_ports {da_data[4]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN W18} [get_ports {da_data[3]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN W19} [get_ports {da_data[2]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN R16} [get_ports {da_data[1]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN R17} [get_ports {da_data[0]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN R18} [get_ports da_clk]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN U17} [get_ports {ad_data[7]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN T16} [get_ports {ad_data[6]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN Y16} [get_ports {ad_data[5]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN Y17} [get_ports {ad_data[4]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN T15} [get_ports {ad_data[3]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN T14} [get_ports {ad_data[2]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN W16} [get_ports {ad_data[1]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN V16} [get_ports {ad_data[0]}]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN V18} [get_ports ad_clk]
 
 - set_property -dict {IOSTANDARD LVCMOS33 PACKAGE_PIN V17} [get_ports ad_otr]
 
  复制代码 
 
27.4程序设计 
根据本章的实验任务,ZYNQ PL端需要连续输出正弦波、方波、三角波、锯齿波波形的数据,才能使AD9708连续输出相应波形的模拟电压,如果使用三角函数公式运算的方式来编写代码,然后输出正弦波、方波、三角波、锯齿波的波形数据,那么程序设计会变得非常复杂。在工程应用中,一般将波形数据存储在RAM或者ROM中,由于本次实验并不需要写数据到RAM中,因此我们将波形数据存储在只读的ROM中,直接读取ROM中的数据发送给DA转换芯片即可。 
图 7.5.13.1是根据本章实验任务画出的系统框图。ROM里面事先存储好了波形的数据,DA数据发送模块从ROM中读取数据,将数据和时钟送到AD9708的输入数据端口和输入时钟端口;AD数据接收模块给AD9280输出驱动时钟信号和使能信号,并采集AD9280输出模数转换完成的数据。 
DDS实验的系统框图如下图所示: 
 
图 7.5.13.1 DDS系统框图
 顶层模块的原理图如下图所示: 
 
图 7.5.13.2 顶层模块原理图
 FPGA顶层模块(dds)例化了以下六个模块:时钟模块(clk_wiz_0)、两个按键消抖模块(key_debounce)、DA数据发送模块(da_wave_send)、ROM波形存储模块(rom_400x8b)和AD数据接收模块(ad_wave_rec)。 
时钟模块(clk_wiz_0):为按键消抖模块、DA数据发送模块、ROM波形存储模块提供驱动时钟。 
按键消抖模块(key_debounce):对按键信号延时采样,将消抖后的按键信号和按键数据有效信号输出至da_wave_send模块。 
DA数据发送模块(da_wave_send):DA数据发送模块输出读ROM地址,将输入的ROM数据发送至DA转换芯片的数据端口。 
ROM波形存储模块(rom_400x8b):ROM波形存储模块由Vivado软件自带的Block Memory Generator IP核实现,其存储的波形数据可以使用matlab生成的.coe文件。 
AD数据接收模块(ad_wave_rec):AD数据接收模块输出AD转换芯片的驱动时钟和使能信号,随后接收AD转换完成的数据。 
顶层模块里的按键消抖模块可以参考按键控制蜂鸣器实验,AD数据接收模块可以参考高速AD/DA实验,这里我们重点讲解其余的几个模块。 
顶层模块的代码如下: 
1  module dds( 
2      input                 sys_clk     ,  //系统时钟 
3      input                 sys_rst_n   ,  //系统复位,低电平有效 
4      input                 key0        ,  //按键key0 
5      input                 key1        ,  //按键key1 
6      //DA芯片接口 
7      output                da_clk      ,  //DA(AD9708)驱动时钟,最大支持125Mhz时钟 
8      output    [7:0]       da_data     ,  //输出给DA的数据 
9      //AD芯片接口 
10     input     [7:0]       ad_data     ,  //AD输入数据 
11     //模拟输入电压超出量程标志(本次试验未用到) 
12     input                 ad_otr      ,  //0:在量程范围 1:超出量程 
13     output                ad_clk         //AD(AD9280)驱动时钟,最大支持32Mhz时钟  
14 ); 
15  
16 //wire define  
17 wire      [8:0]     rd_addr;            //ROM读地址 
18 wire      [7:0]     rd_data;            //ROM读出的数据 
19 wire               key0_value;          //key0消抖后的按键值 
20 wire               key0_flag;           //key0消抖后的按键值的有效标志 
21 wire               key1_value;          //key1消抖后的按键值 
22 wire               key1_flag;           //key1消抖后的按键值的有效标志 
23 wire               clk_100M;            //da芯片的驱动时钟 
24  
25 //***************************************************** 
26 //**                    main code 
27 //***************************************************** 
28  
29 //例化按键消抖模块 
30 key_debounce  u_key0_debounce( 
31     .sys_rst_n  (sys_rst_n), 
32     .clk        (clk_100M), 
33     .key        (key0), 
34     .key_value  (key0_value), 
35     .key_flag   (key0_flag)     
36     ); 
37      
38 //例化按键消抖模块 
39 key_debounce  u_key1_debounce( 
40     .sys_rst_n  (sys_rst_n), 
41     .clk        (clk_100M), 
42     .key        (key1), 
43     .key_value  (key1_value), 
44     .key_flag   (key1_flag)      
45     );   
46  
47 //DA数据发送 
48 da_wave_send u_da_wave_send( 
49     .clk         (clk_100M), 
50     .rst_n       (sys_rst_n), 
51     .key0_value  (key0_value), 
52     .key0_flag   (key0_flag), 
53     .key1_value  (key1_value), 
54     .key1_flag   (key1_flag), 
55     .rd_data     (rd_data), 
56     .rd_addr     (rd_addr), 
57     .da_clk      (da_clk),   
58     .da_data     (da_data) 
59     ); 
60  
61  clk_wiz_0 u_clk_wiz_0 
62    ( 
63     // Clock out ports 
64     .clk_out1(clk_100M),   // output clk_out1 
65     // Status and control signals 
66     .reset(~sys_rst_n),    // input reset 
67     .locked(locked),       // output locked 
68    // Clock in ports 
69     .clk_in1(sys_clk)      // input clk_in1 
70     );       
71      
72 //ROM存储波形 
73 rom_400x8b u_rom_400x8b ( 
74   .clka(clk_100M),      // input wire clka 
75   .addra(rd_addr),     // input wire [8 : 0] addra 
76   .douta(rd_data)      // output wire [7 : 0] douta 
77 ); 
78  
79 //AD数据接收 
80 ad_wave_rec u_ad_wave_rec( 
81     .clk         (sys_clk), 
82     .rst_n       (sys_rst_n), 
83     .ad_data     (ad_data), 
84     .ad_otr      (ad_otr), 
85     .ad_clk      (ad_clk) 
86     );     
87  
88 //ILA采集AD数据 
89 ila_0  ila_0 ( 
90     .clk         (ad_clk ), // input wire clk 
91     .probe0      (ad_otr ), // input wire [0:0]  probe0   
92     .probe1      (ad_data)  // input wire [7:0]  probe0  
93 ); 
94  
95 endmodule 
DA数据发送模块输出的读ROM地址(rd_addr)连接至ROM模块的地址输入端,ROM模块输出的数据(rd_data)连接至DA数据发送模块的数据输入端,从而完成了从ROM中读取数据的功能。 
在顶层模块代码的第61至70行例化了clk_wiz的ip核,用于生成100M时钟作为读rom地址的时钟。注意,rom模块的端口时钟和按键消抖模块的时钟也是100M。clk_wiz IP核的配置如下图所示: 
 
图 7.5.13.3 “Clocking Options”选项卡的设置
 接下来切换至“Output Clocks”选项卡,在“Output Clock”选项卡中,勾选第1个时钟,并且将其“Output Freq(MHz)”分别设置为100,其他设置保持默认即可,如下图所示。 
 
图 7.5.13.4 “Output Clocks”选项卡的设置
 其余的设置保持默认,最后直接点击“OK”按钮即可。 
在顶层模块代码的第73至77行例化了ROM模块,由Block Memory Generator IP核配置生成。 
在顶层模块代码的第89行例化了一个ILA的IP核,用于捕获ad_otr和ad_data的数据。需要注意的是,ILA的采样时钟必须使用ad_clk,否则数据可能采集错误。ILA IP核的配置如下图所示: 
 
图 7.5.13.5 ILA IP核的Genaral Options配置
 我们把探针数量设置为2,并且把采样深度设置为4096。探针宽度的设置如下图所示: 
 
图 7.5.13.6 ILA IP核的Probe_Ports配置
 我们将两个探针的位宽设置成1和8,分别对应ad_otr和ad_data的位宽,设置完成后点击“OK”按钮即可。 
我们在前面说过,ROM中存储的波形数据可以使用MatLab软件生成,使用 MatLab绘制4种信号波形,对波形进行等间隔采样,以采样次数作为ROM存储地址,将采集的波形幅值数据做为存储数据写入存储地址对应的存储空间。在本次实验中我们对4种信号波形进行分别采样,采样次数为100次,采集的波形幅值数据位宽为8bit,将采集数据保存为coe文件。 
各波形参考代码如下。 
正弦信号波形采集参考代码(sin_wave.m): 
1  F1=1;                %信号频率 
2  Fs=10^2;             %采样频率 
3  P1=0;                %信号初始相位 
4  N=10^2;              %采样点数 
5  t=[0:1/Fs N-1)/Fs]; %采样时刻 
6  ADC=2^7 - 1;         %直流分量 
7  A=2^7;               %信号幅度 
8  %生成正弦信号 
9  s=A*sin(2*pi*F1*t + pi*P1/180) + ADC; 
10 plot(s);             %绘制图形 
11 %创建 coe 文件 
12 fild = fopen('sin_wave_100x8.coe','wt'); 
13 %写入 coe 文件头 
14 %固定写法,表示写入的数据是10进制表示 
15 fprintf(fild, '%s\n','memory_initialization_radix=10;');  
16 %固定写法,下面开始写入数据 
17 fprintf(fild, '%s\n\n','memory_initialization_vector =');   
18 for i = 1:N 
19 s2(i) = round(s(i));          %对小数四舍五入以取整 
20 if s2(i) <0                   %负 1 强制置零 
21 s2(i) = 0 
22 end 
23 fprintf(fild, '%d',s2(i));    %数据写入 
24 if i==N 
25    fprintf(fild, '%s\n',';'); %最后一个数据用; 
26 else 
27    fprintf(fild,',\n');       % 其他数据用, 
28 end 
29 end 
30 fclose(fild);                 % 写完了,关闭文件 
 
图 7.5.13.7 MatLab 生成的正弦信号波形
 方波信号波形采集参考代码(square_wave.m): 
1  F1=1;                %信号频率 
2  Fs=10^2;             %采样频率 
3  P1=0;                %信号初始相位 
4  N=10^2;              %采样点数 
5  t=[0:1/Fs N-1)/Fs]; %采样时刻 
6  ADC=2^7 - 1;         %直流分量 
7  A=2^7;               %信号幅度 
8  %生成方波信号 
9  s=A*square(2*pi*F1*t + pi*P1/180) + ADC; 
10 plot(s);             %绘制图形 
11 %创建 coe文件 
12 fild = fopen('squ_wave_100x8.coe','wt'); 
13 %写入 coe文件头 
14 %固定写法,表示写入的数据是10进制表示 
15 fprintf(fild, '%s\n','memory_initialization_radix=10;');  
16 %固定写法,下面开始写入数据   
17 fprintf(fild, '%s\n\n','memory_initialization_vector =');  
18 for i = 1:N 
19 s2(i) = round(s(i));          %对小数四舍五入以取整 
20 if s2(i) <0                   %负 1 强制置零 
21 s2(i) = 0 
22 end 
23 fprintf(fild, '%d',s2(i));    %数据写入 
24 if i==N 
25    fprintf(fild, '%s\n',';'); %最后一个数据用分号  
26 else 
27    fprintf(fild,',\n');        % 其他数据用 , 
28 end 
29 end 
30 fclose(fild);                  % 写完了,关闭文件 
 
图 7.5.13.8 MatLab 生成的方波信号波形
 三角波信号波形采集参考代码(triangle_wave.m): 
1  F1=1;                %信号频率 
2  Fs=10^2;             %采样频率 
3  P1=0;                %信号初始相位 
4  N=10^2;              %采样点数 
5  t=[0:1/Fs N-1)/Fs]; %采样时刻 
6  ADC=2^7 - 1;         %直流分量 
7  A=2^7;               %信号幅度 
8  %生成三角波信号 
9  s=A*sawtooth(2*pi*F1*t + pi*P1/180,0.5) + ADC; 
10 plot(s);             %绘制图形 
11 %创建 coe 文件 
12 fild = fopen('tri_wave_100x8.coe','wt'); 
13 %写入coe文件头 
14 %固定写法,表示写入的数据是10进制表示 
15 fprintf(fild, '%s\n','memory_initialization_radix=10;');   
16 %固定写法,下面开始写入数据  
17 fprintf(fild, '%s\n\n','memory_initialization_vector =');  
18 for i = 1:N 
19 s2(i) = round(s(i));          %对小数四舍五入以取整 
20 if s2(i) <0                   %负 1 强制置零 
21 s2(i) = 0 
22 end 
23 fprintf(fild, '%d',s2(i));    %数据写入 
24 if i==N 
25    fprintf(fild, '%s\n',';'); %最后一个数据用分号  
26 else 
27    fprintf(fild,',\n');       %其他数据用 , 
28 end 
29 end 
30 fclose(fild);                 % 写完了,关闭文件 
 
 
图 7.5.13.9 MatLab 生成的三角波信号波形
 锯齿波信号波形采集参考代码(sawtooth_wave.m): 
1  F1=1;                %信号频率 
2  Fs=10^2;             %采样频率 
3  P1=0;                %信号初始相位 
4  N=10^2;              %采样点数 
5  t=[0:1/Fs N-1)/Fs]; %采样时刻 
6  ADC=2^7 - 1;         %直流分量 
7  A=2^7;               %信号幅度 
8  %生成锯齿波信号 
9  s=A*sawtooth(2*pi*F1*t + pi*P1/180) + ADC; 
10 plot(s);             %绘制图形 
11 %创建 coe 文件 
12 fild = fopen('saw_wave_100x8.coe','wt'); 
13 %写入 coe 文件头 
14 %固定写法,下面开始写入数据 
15 fprintf(fild, '%s\n','memory_initialization_radix=10;');   
16 %固定写法,下面开始写入数据 
17 fprintf(fild, '%s\n\n','memory_initialization_vector =');  
18 for i = 1:N 
19 s2(i) = round(s(i));          %对小数四舍五入以取整 
20 if s2(i) <0                   %负 1 强制置零 
21 s2(i) = 0 
22 end 
23 fprintf(fild, '%d',s2(i));    %数据写入 
24 if i==N 
25    fprintf(fild, '%s\n',';'); %最后一个数据用分号  
26 else 
27    fprintf(fild,',\n');       % 其他数据用 , 
28 end 
29 end 
30 fclose(fild);                 % 写完了,关闭文件 
 
图 7.5.13.10 MatLab 生成的锯齿波信号波形
 使用 MatLab对4种波形进行采样后,生成4个coe文件,分别对应4种波形,我们通过调用一个深度为100*4,位宽为8bit的 ROM,将四个coe文件整合为一个coe文件。在配置rom的ip核时将整合的coe文件导入到rom中。 
使用Notepad++代码编辑器打开生成的COE文件后如下图所示: 
 
图 7.5.13.11 COE文件打开界面
 工程中创建了一个单端口ROM,并命名为“rom_400x8b”,在调用Block Memory Generator IP核时,“Basic”选项也配置如下图所示: 
 
图 7.5.13.12 Block Memory Generator IP核的Basic配置页面
 我们将其接口类型设置为“Native”、Memory Type设置为“Single Port ROM”,即单端口ROM。 
“Port A Options”选项页的配置页面如下图所示: 
 
 
图 7.5.13.13 Block Memory Generator IP核的PortA Options配置页面
 我们将PortA的位宽设置为8,深度设置为400,以存储matlab生成的400个数据。此外,将使能引脚的类型设置为“Always Enabled”,即ROM一直处于使能的状态。 
接下来配置“Other Options”选项页,加载刚才生成的.coe文件,如下图所示: 
 
图 7.5.13.14 Block Memory Generator IP核的Other Options配置页面
 最后点击“OK”按钮完成IP核的配置。 
DA数据发送模块的代码如下: 
- 1   module da_wave_send(
 
 - 2       input                 rst_n     ,   //复位信号,低电平有效
 
 - 3       input                 clk       ,   //连接到clk_100M
 
 - 4       input                 key0_value,   //消抖后的按键值
 
 - 5       input                 key0_flag ,   //消抖后的按键值的有效标志
 
 - 6       input                 key1_value,   //消抖后的按键值
 
 - 7       input                 key1_flag ,   //消抖后的按键值的有效标志
 
 - 8                                         
 
 - 9       input        [7:0]    rd_data   ,   //ROM读出的数据
 
 - 10      output  reg  [8:0]    rd_addr   ,   //读ROM地址
 
 - 11      //DA芯片接口                        
 
 - 12      output                da_clk    ,   //DA(AD9708)驱动时钟,最大支持125Mhz时钟
 
 - 13      output       [7:0]    da_data       //输出给DA的数据  
 
 - 14      );
 
 - 15  
 
 - 16  //parameter
 
 - 17  //波形调节控制
 
 - 18  parameter  sine_wave_addr     = 9'd0;   // 正弦波起始位置 
 
 - 19  parameter  square_wave_addr   = 9'd100; // 方波起始位置  
 
 - 20  parameter  triangle_wave_addr = 9'd200; // 三角波起始位置
 
 - 21  parameter  sawtooth_wave_addr = 9'd300; // 锯齿波起始位置 
 
 - 22  
 
 - 23  //频率调节控制,FREQ_ADJ的越大,最终输出的频率越低,范围0~255
 
 - 24  parameter  FREQ_ADJ0 = 8'd0;            //参数0对应输出1Mhz波形频率
 
 - 25  parameter  FREQ_ADJ1 = 8'd1;            //参数1对应输出500khz波形频率
 
 - 26  parameter  FREQ_ADJ2 = 8'd3;            //参数3对应输出250khz波形频率
 
 - 27  parameter  FREQ_ADJ3 = 8'd7;            //参数7对应输出125khz波形频率
 
 - 28                                          
 
 - 29  //reg define                            
 
 - 30  reg    [7:0]     freq_adj    ;          //频率调节参数寄存器
 
 - 31  reg    [7:0]     freq_cnt    ;          //频率调节计数器
 
 - 32  reg    [1:0]     wave_select ;          //切换波形地址寄存器
 
 - 33  reg    [1:0]     freq_select ;          //切换波形频率寄存器
 
 - 34  
 
 - 35  //*****************************************************
 
 - 36  //**                    main code
 
 - 37  //*****************************************************
 
 - 38  
 
 - 39  //数据rd_data是在clk_100M的上升沿更新的,
 
 - 40  //所以DA芯片在clk_100M的下降沿锁存数据是稳定的时刻。
 
 - 41  //而DA实际上在da_clk的上升沿锁存数据,所以时钟取反,
 
 - 42  //这样 clk_100M 的下降沿相当于 da_clk 的上升沿。           
 
 - 43  assign  da_clk =~clk;       
 
 - 44  assign  da_data = rd_data;  //将读到的ROM数据赋值给DA数据端口
 
 - 45     
 
 - 46  //切换波形种类
 
 - 47  always @(posedge clk  or negedge rst_n) begin
 
 - 48      if(rst_n == 1'b0)
 
 - 49          wave_select <= 2'd0;
 
 - 50      else if((key0_flag == 1) && (key0_value == 0)) begin //确保按键key0确实被有效按下
 
 - 51             if(wave_select < 2'd3)
 
 - 52                 wave_select <= wave_select+1'd1;
 
 - 53             else  
 
 - 54                 wave_select <= 0;
 
 - 55            end
 
 - 56           else 
 
 - 57               wave_select <= wave_select;
 
 - 58  end
 
 - 59  
 
 - 60  //切换波形频率
 
 - 61  always @(posedge clk or negedge rst_n) begin
 
 - 62      if(rst_n == 1'b0)
 
 - 63          freq_select <= 2'd0;
 
 - 64      else if((key1_flag ==1) && (key1_value ==0)) begin //确保按键key1确实被有效按下
 
 - 65             if(freq_select < 2'd3)
 
 - 66                freq_select <= freq_select+1'd1;
 
 - 67             else  
 
 - 68                 freq_select <= 0;
 
 - 69            end
 
 - 70           else 
 
 - 71               freq_select <= freq_select;
 
 - 72  end
 
 - 73  
 
 - 74  always @(posedge clk or negedge rst_n) begin
 
 - 75      if(rst_n == 1'b0)
 
 - 76        freq_adj <= 8'd0;
 
 - 77      else case(freq_select)
 
 - 78               2'd0:freq_adj <= FREQ_ADJ0;
 
 - 79               2'd1:freq_adj <= FREQ_ADJ1;   
 
 - 80               2'd2:freq_adj <= FREQ_ADJ2;
 
 - 81               2'd3:freq_adj <= FREQ_ADJ3;
 
 - 82              default:freq_adj <= FREQ_ADJ0;
 
 - 83           endcase
 
 - 84  end
 
 - 85  
 
 - 86  //频率调节计数器
 
 - 87  always @(posedge clk or negedge rst_n) begin
 
 - 88      if(rst_n == 1'b0)
 
 - 89          freq_cnt <= 8'd0;
 
 - 90      else if(freq_cnt == freq_adj)    
 
 - 91          freq_cnt <= 8'd0;
 
 - 92      else         
 
 - 93          freq_cnt <= freq_cnt + 8'd1;
 
 - 94  end
 
 - 95  
 
 - 96  //读ROM地址,按照100M的频率去读
 
 - 97  always @(posedge clk or negedge rst_n) begin
 
 - 98      if(rst_n == 1'b0)
 
 - 99         rd_addr <= 9'd0;
 
 - 100     else if(freq_cnt == freq_adj) begin
 
 - 101         case(wave_select)
 
 - 102             2'd0:
 
 - 103                 if(rd_addr >= sine_wave_addr && rd_addr <= sine_wave_addr+9'd99)    
 
 - 104                   if(rd_addr == sine_wave_addr+9'd99)  
 
 - 105                      rd_addr <= sine_wave_addr;
 
 - 106                   else 
 
 - 107                       rd_addr <= rd_addr+9'd1; 
 
 - 108                 else 
 
 - 109                     rd_addr <= sine_wave_addr;                          
 
 - 110             2'd1:
 
 - 111                 if(rd_addr >=  square_wave_addr && rd_addr <= square_wave_addr+9'd99)   
 
 - 112                   if(rd_addr == square_wave_addr+9'd99) 
 
 - 113                       rd_addr <= square_wave_addr;
 
 - 114                   else  
 
 - 115                       rd_addr <= rd_addr+9'd1;
 
 - 116                 else 
 
 - 117                     rd_addr <= square_wave_addr; 
 
 - 118             2'd2:
 
 - 119                 if(rd_addr >= triangle_wave_addr && rd_addr <= triangle_wave_addr+9'd99) 
 
 - 120                   if(rd_addr == triangle_wave_addr+9'd99) 
 
 - 121                       rd_addr <= triangle_wave_addr; 
 
 - 122                   else 
 
 - 123                       rd_addr <= rd_addr+9'd1;  
 
 - 124                 else  
 
 - 125                     rd_addr <= triangle_wave_addr;                    
 
 - 126             2'd3:
 
 - 127                 if(rd_addr >= sawtooth_wave_addr && rd_addr <= sawtooth_wave_addr+9'd99)   
 
 - 128                   if(rd_addr == sawtooth_wave_addr+9'd99)
 
 - 129                       rd_addr <= sawtooth_wave_addr;
 
 - 130                   else 
 
 - 131                       rd_addr <= rd_addr+9'd1;    
 
 - 132                 else  
 
 - 133                     rd_addr <= sawtooth_wave_addr;   
 
 - 134             default:
 
 - 135                 if(rd_addr >= sine_wave_addr && rd_addr <= sine_wave_addr+9'd99)    
 
 - 136                    if(rd_addr == sine_wave_addr+9'd99)  
 
 - 137                        rd_addr <= sine_wave_addr;
 
 - 138                    else 
 
 - 139                        rd_addr <= rd_addr+9'd1; 
 
 - 140                 else 
 
 - 141                     rd_addr <= sine_wave_addr;            
 
 - 142         endcase
 
 - 143     end
 
 - 144          else  rd_addr <= rd_addr;             
 
 - 145 end
 
 - 146 endmodule
 
  复制代码 
 
在代码的第30行定义了一个参数寄存器freq_adj(频率调节),可以通过控制频率调节参数的大小来控制最终输出波形的频率大小,频率调节参数的值越小,波形频率越大。频率调节参数调节波形频率的方法是通过控制读ROM的速度实现的,频率调节参数越小,freq_cnt计数到频率调节参数值的时间越短,读ROM数据的速度越快,那么正弦波输出频率也就越高;反过来,频率调节参数越大,freq_cnt计数到频率调节参数值的时间越长,读ROM数据的速度越慢,那么正弦波输出频率也就越低。由于freq_cnt计数器的位宽为8位,计数范围是0~255,所以频率调节参数freq_adj支持的调节范围是0~255,可通过修改freq_cnt计数器的位宽来修改freq_adj支持的调节范围。 
通过matlab生成的coe文件的数据深度为100,而输入时钟为50 Mhz,通过锁相环倍频可以得到100 Mhz的da芯片的驱动时钟,那么一个完整的波形周期的长度为100*10ns=1000ns,当freq_adj的值为0时,即波形的的最快输出频率为1s/1000ns(1s = 1000000000ns)=1Mhz,当我们把freq_adj的值设置为1时,一个完整的波形周期长度为1000ns*(1+1) = 2000ns,频率为500Khz。当freq_adj的值设为3或7时,输出波形频率也相应的变为250khz和125khz。 
27.5下载验证  
将高速AD-DA模块插入ZYNQ领航者开发板的扩展口(靠近TF_CARD接口的位置),连接时注意扩展口电源引脚方向和开发板电源引脚方向一致,然后将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。 
领航者开发板硬件连接实物图如图 7.5.13.1所示: 
 
图 7.5.13.1 领航者开发板硬件连接实物图
 将工程生成的比特流文件下载到ZYNQ中后,然后使用示波器测量DA输出通道的波形。首先将示波器带夹子的一端连接到开发板的GND位置(可使用杜邦线连接至开发板上的任一的GND管脚),然后将另一端探针插入高速AD-DA模块的DA通道中间的金属圆圈内(注意将红色的保护套拿掉),如图 7.5.13.2 
所示。或者也可以直接测试高速AD-DA模块的TP引脚,如图 7.5.13.3所示。 
 
图 7.5.13.2 DA模拟电压测量孔位
  
 
图 7.5.13.3 DA模拟电压测量点(TP)
 此时观察示波器可以看到模拟波的波形,如果观察不到波形,可查看示波器设置是否正确,可以尝试按下示波器的“AUTO”,再次观察示波器波形。示波器的显示界面如下所示: 
通过按下按键key0可实现波形的的切换: 
 
图 7.5.13.4 正弦波测量图
 
图 7.5.13.5 方波测量图
 
图 7.5.13.6 三角波测量图
 
图 7.5.13.7 锯齿波测量图
 通过按下按键key1可实现频率的切换: 
 
图 7.5.13.8 频率切换图(一)
 
图 7.5.13.9 频率切换图(二)
 在示波器上观察到正确的波形后,说明DDS的功能已经实现了。关于生成的模拟波形除了可以在示波器上观察,也可以通过Ila去进行观察,在 ILA 中观察 ad_data 数据的具体的做法可以参照高速AD/DA实验里的步骤。 |   
 
 
 
 |