|
本帖最后由 正点原子 于 2023-2-20 09:46 编辑
1)实验平台:正点原子紫光PGL22G开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340253-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:435699340
第三十五章DDS信号发生器实验
DDS(Direct Digital Synthesizer)即直接数字式频率合成器,是一种新型的频率合成技术。与传统的频率合成器相比,DDS具有相对带宽大,频率转换时间短,稳定性好,分辨率高,可灵活产生多种信号等优点。较容易实现频率、相位及幅度的数控调制,因此,在现代电子系统及设备的频率源设计中,尤其在通信领域,直接数字频率合成器的应用越来越广泛。作为设计人员,我们习惯称它为信号发生器,一般用它产生正弦、锯齿、方波等不同波形或不同频率的信号波形,在电子设计和测试中得到广泛应用。本章我们将利用高速ad/da模块去设计实现一个简易的DDS信号发生器。
本章包括以下几个部分:
35.1简介
35.2实验任务
35.3硬件设计
35.4软件设计
35.5下载验证
35.1DDS简介
DDS( Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有低成本、低功耗、高分辨率、频率转换时间短、相位连续性好等优点,对数字信号处理及其硬件实现有着很重要的作用。DDS的基本结构主要由相位累加器、相位调制器、波形数据表ROM、D/A转换器等四大结构组成,其中较多设计还会在数模转换器之后增加一个低通滤波器(LPF)。DDS基本结构图如图 35.1.1所示。
图 35.1.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位二进制数值固化在波形存储器当中。按照地址的不同可以输出相应相位的正弦信号幅值。相位—幅度变换原理图如下图所示:
图 35.1.2 相位-幅度变换原理图
数模转换器(D/A)的作用是把合成的正弦波数字量转化为模拟量。正弦幅度量化序列经数模转换器转换后变成了包络为正弦波的阶梯波。频率合成器对数模转换器的分辨率有一定的要求,其分辨率越高,合成的正弦波台阶数就越多,输出的波形精度也就越高。DDS信号流程图如下图所示:
图 35.1.3 DDS信号流程图
35.2实验任务
本节实验任务是使用ATK-DFPGL22G开发板及高速AD-DA扩展模块(ATK_HS_AD_DA模块)实现数模及模数的转换。首先由FPGA产生正弦波、方波、三角波、锯齿波变化的数字信号,经过DA芯片后转换成模拟信号,将DA的模拟电压输出端连接至AD的模拟电压输入端,AD芯片将模拟信号转换成数字信号,通过按下按键key0和key1可以实现波形种类和波形频率的切换,然后通过示波器观察DA端模拟信号的波形是否出现了相应变化,也可以通过Debugger界面去进行观察。
35.3硬件设计
本章节中硬件设计与“高速AD/DA实验”完全相同,此处不在赘述。本实验中,各端口信号的管脚分配如下表所示。
表 35.3.1 DDS实验管脚分配
对应的fdc约束语句如下所示:
define_attribute {p:da_data[7]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[7]} {PAP_IO_LOC} {J17}
define_attribute {p:da_data[7]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[7]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[7]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[7]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[7]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[6]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[6]} {PAP_IO_LOC} {H17}
define_attribute {p:da_data[6]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[6]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[6]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[6]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[6]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[5]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[5]} {PAP_IO_LOC} {J18}
define_attribute {p:da_data[5]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[5]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[5]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[5]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[5]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[4]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[4]} {PAP_IO_LOC} {H18}
define_attribute {p:da_data[4]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[4]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[4]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[4]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[4]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[3]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[3]} {PAP_IO_LOC} {L17}
define_attribute {p:da_data[3]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[3]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[3]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[3]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[3]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[2]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[2]} {PAP_IO_LOC} {K17}
define_attribute {p:da_data[2]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[2]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[2]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[2]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[2]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[1]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[1]} {PAP_IO_LOC} {L18}
define_attribute {p:da_data[1]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[1]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[1]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[1]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[1]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_data[0]} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_data[0]} {PAP_IO_LOC} {K18}
define_attribute {p:da_data[0]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_data[0]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_data[0]} {PAP_IO_DRIVE} {2}
define_attribute {p:da_data[0]} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_data[0]} {PAP_IO_SLEW} {SLOW}
define_attribute {p:ad_data[7]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[7]} {PAP_IO_LOC} {G18}
define_attribute {p:ad_data[7]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[7]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[7]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[6]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[6]} {PAP_IO_LOC} {J15}
define_attribute {p:ad_data[6]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[6]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[6]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[5]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[5]} {PAP_IO_LOC} {G17}
define_attribute {p:ad_data[5]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[5]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[5]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[4]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[4]} {PAP_IO_LOC} {J14}
define_attribute {p:ad_data[4]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[4]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[4]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[3]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[3]} {PAP_IO_LOC} {F18}
define_attribute {p:ad_data[3]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[3]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[3]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[2]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[2]} {PAP_IO_LOC} {H16}
define_attribute {p:ad_data[2]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[2]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[2]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[1]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[1]} {PAP_IO_LOC} {F17}
define_attribute {p:ad_data[1]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[1]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[1]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_data[0]} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_data[0]} {PAP_IO_LOC} {J16}
define_attribute {p:ad_data[0]} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_data[0]} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_data[0]} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_clk} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:ad_clk} {PAP_IO_LOC} {H13}
define_attribute {p:ad_clk} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_clk} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_clk} {PAP_IO_DRIVE} {2}
define_attribute {p:ad_clk} {PAP_IO_NONE} {TRUE}
define_attribute {p:ad_clk} {PAP_IO_SLEW} {SLOW}
define_attribute {p:da_clk} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:da_clk} {PAP_IO_LOC} {H14}
define_attribute {p:da_clk} {PAP_IO_VCCIO} {3.3}
define_attribute {p:da_clk} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:da_clk} {PAP_IO_DRIVE} {2}
define_attribute {p:da_clk} {PAP_IO_NONE} {TRUE}
define_attribute {p:da_clk} {PAP_IO_SLEW} {SLOW}
define_attribute {p:ad_otr} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:ad_otr} {PAP_IO_LOC} {K15}
define_attribute {p:ad_otr} {PAP_IO_VCCIO} {3.3}
define_attribute {p:ad_otr} {PAP_IO_STANDARD} { LVCMOS33}
define_attribute {p:ad_otr} {PAP_IO_NONE} {TRUE}
define_attribute {p:key0} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:key0} {PAP_IO_LOC} {H5}
define_attribute {p:key0} {PAP_IO_VCCIO} {3.3}
define_attribute {p:key0} {PAP_IO_STANDARD} {LVCMOS33}
define_attribute {p:key0} {PAP_IO_NONE} {TRUE}
define_attribute {p:key1} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:key1} {PAP_IO_LOC} {H6}
define_attribute {p:key1} {PAP_IO_VCCIO} {3.3}
define_attribute {p:key1} {PAP_IO_STANDARD} {LVCMOS33}
define_attribute {p:key1} {PAP_IO_NONE} {TRUE}
define_attribute {p:sys_clk} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:sys_clk} {PAP_IO_LOC} {B5}
define_attribute {p:sys_clk} {PAP_IO_VCCIO} {3.3}
define_attribute {p:sys_clk} {PAP_IO_STANDARD} {LVCMOS33}
define_attribute {p:sys_clk} {PAP_IO_NONE} {TRUE}
define_attribute {p:sys_rst_n} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:sys_rst_n} {PAP_IO_LOC} {G5}
define_attribute {p:sys_rst_n} {PAP_IO_VCCIO} {1.5}
define_attribute {p:sys_rst_n} {PAP_IO_STANDARD} {LVCMOS15}
define_attribute {p:sys_rst_n} {PAP_IO_NONE} {TRUE}
35.4程序设计
根据本章的实验任务,FPGA需要连续输出正弦波、方波、三角波、锯齿波波形的数据,才能使AD9708连续输出相应波形的模拟电压,如果使用三角函数公式运算的方式来编写代码,然后输出正弦波、方波、三角波、锯齿波的波形数据,那么程序设计会变得非常复杂。在工程应用中,一般将波形数据存储在RAM或者ROM中,由于本次实验并不需要写数据到RAM中,因此我们将波形数据存储在只读的ROM中,直接读取ROM中的数据发送给DA转换芯片即可。
图 35.4.1是根据本章实验任务画出的系统框图。ROM里面事先存储好了波形的数据,DA数据发送模块从ROM中读取数据,将数据和时钟送到AD9708的输入数据端口和输入时钟端口;AD数据接收模块给AD9280输出驱动时钟信号和使能信号,并采集AD9280输出模数转换完成的数据。
DDS实验的系统框图如下图所示:
图 35.4.1 DDS系统框图
顶层模块的原理图如下图所示:
图 35.4.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波形存储模块由软件自带的Distributed ROM 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 wire [7:0] ad_data_out/* synthesis PAP_MARK_DEBUG="1" */; //这里使用防优化语句,便于在线调试工具可以捕获到数据
25
26
27 //*****************************************************
28 //** main code
29 //*****************************************************
30
31 //例化按键消抖模块
32 key_debounce u_key0_debounce(
33 .sys_rst_n (sys_rst_n),
34 .clk (clk_100M),
35 .key (key0),
36 .key_value (key0_value),
37 .key_flag (key0_flag)
38 );
39
40 //例化按键消抖模块
41 key_debounce u_key1_debounce(
42 .sys_rst_n (sys_rst_n),
43 .clk (clk_100M),
44 .key (key1),
45 .key_value (key1_value),
46 .key_flag (key1_flag)
47 );
48
49 //DA数据发送
50 da_wave_send u_da_wave_send(
51 .clk (clk_100M),
52 .rst_n (sys_rst_n),
53 .key0_value (key0_value),
54 .key0_flag (key0_flag),
55 .key1_value (key1_value),
56 .key1_flag (key1_flag),
57 .rd_data (rd_data),
58 .rd_addr (rd_addr),
59 .da_clk (da_clk),
60 .da_data (da_data)
61 );
62 clk_wiz_0 u_clk_wiz_0
63 (
64 // Clock out ports
65 .clkout0(clk_100M), // output clk_out0
66 // Status and control signals
67 .pll_rst(~sys_rst_n), // input reset
68 // Clock in ports
69 .clkin1(sys_clk) // input clk_in1
70 );
71
72
73 //ROM存储波形
74 rom_400x8b u_rom_400x8b (
75 .clk(clk_100M), // input wire clka
76 .addr(rd_addr), // input wire [8 : 0] addra
77 .rst(sys_rst_n),
78 .rd_data(rd_data) // output wire [7 : 0] douta
79 );
80
81 //AD数据接收
82 ad_wave_rec u_ad_wave_rec(
83 .clk (sys_clk),
84 .rst_n (sys_rst_n),
85 .ad_data (ad_data),
86 .ad_otr (ad_otr),
87 .ad_data_out(ad_data_out),
88 .ad_clk (ad_clk)
89 );
90
91 endmodule
DA数据发送模块输出的读ROM地址(rd_addr)连接至ROM模块的地址输入端,ROM模块输出的数据(rd_data)连接至DA数据发送模块的数据输入端,从而完成了从ROM中读取数据的功能。
在顶层模块代码的第62至70行例化了PLL的ip核,用于生成100M时钟作为读rom地址的时钟和AD模块的驱动时钟。注意,rom模块的端口时钟和按键消抖模块的时钟也是100M。PLL核的配置如下图所示:
图 35.4.3 “Input Clock Information”选项卡的设置
接下来切换至“Basic Configuration”选项卡,在“Basic Configuration”选项卡中,勾选Enable clkout0时钟,并且将“Desired Frequency“设置为100Mhz,其他设置保持默认即可,如下图所示。
图 35.4.4 “Output Clocks”选项卡的设置
其余的设置保持默认,最后直接点击“OK”按钮即可。
在顶层模块代码的第74至79行例化了ROM模块,由Distributed ROM IP核配置生成。
关于调试方式,这里我们直接采用PDS集成的Debugger进行在线调试,详细的使用方法可以参考软件使用篇的Debugger在线调试工具,具体Inserter端口设置如下图所示:
图 35.4.5 Inserter的NetConnections配置
我们在前面说过,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); % 写完了,关闭文件
图 35.4.6 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); % 写完了,关闭文件
图 35.4.7 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); % 写完了,关闭文件
图 35.4.8 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); % 写完了,关闭文件
图 35.4.9 MatLab 生成的锯齿波信号波形
使用 MatLab对4种波形进行采样后,生成4个coe文件,分别对应4种波形,我们通过调用一个深度为100*4,位宽为8bit的 ROM,将四个coe文件提取修改整合为一个DAT文件。在配置rom的ip核时将整合的DAT文件导入到rom中。
使用Notepad++代码编辑器打开生成的COE文件,提取数据部分,并将其全部转换为16进制的数据,并另存为后缀为.dat的文件,如下图所示:
图 35.4.10 dat文件打开界面
工程中创建一个单端口ROM,并命名为“rom_400x8b”,配置如下图所示:
图 35.4.11 Distributed ROM IP核的基础配置页面
我们将AddressWidth的位宽设置为9,DataWidth数据位宽设置为8,以存储matlab生成的400个数据,其他保持默认。
接着点击InitFile加载数据文件,Initial Data Format Type选择HEX类型,最后点击“Generate”按钮完成IP核的配置。
DA数据发送模块的代码如下:
1 module da_wave_send(
2 input rst_n , //复位信号,低电平有效
3 input clk,
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,
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 assign da_clk = clk;
39 assign da_data = rd_data; //将读到的ROM数据赋值给DA数据端口
40
41 //切换波形种类
42 always @(posedge clk or negedge rst_n) begin
43 if(rst_n == 1'b0)
44 wave_select <= 2'd0;
45 else if((key0_flag == 1) && (key0_value == 0)) begin //确保按键key0确实被有效按下
46 if(wave_select < 2'd3)
47 wave_select <= wave_select+1'd1;
48 else
49 wave_select <= 0;
50 end
51 else
52 wave_select <= wave_select;
53 end
54
55 //切换波形频率
56 always @(posedge clk or negedge rst_n) begin
57 if(rst_n == 1'b0)
58 freq_select <= 2'd0;
59 else if((key1_flag ==1) && (key1_value ==0)) begin //确保按键key1确实被有效按下
60 if(freq_select < 2'd3)
61 freq_select <= freq_select+1'd1;
62 else
63 freq_select <= 0;
64 end
65 else
66 freq_select <= freq_select;
67 end
68 always @(posedge clk or negedge rst_n) begin
69 if(rst_n == 1'b0)
70 freq_adj <= 8'd0;
71 else case(freq_select)
72 2'd0:freq_adj <= FREQ_ADJ0;
73 2'd1:freq_adj <= FREQ_ADJ1;
74 2'd2:freq_adj <= FREQ_ADJ2;
75 2'd3:freq_adj <= FREQ_ADJ3;
76 default:freq_adj <= FREQ_ADJ0;
77 endcase
78 end
79
80 //频率调节计数器
81 always @(posedge clk or negedge rst_n) begin
82 if(rst_n == 1'b0)
83 freq_cnt <= 8'd0;
84 else if(freq_cnt == freq_adj)
85 freq_cnt <= 8'd0;
86 else
87 freq_cnt <= freq_cnt + 8'd1;
88 end
89
90 //读ROM地址,按照100M的频率去读
91 always @(posedge clk or negedge rst_n) begin
92 if(rst_n == 1'b0)
93 rd_addr <= 9'd0;
94 else if(freq_cnt == freq_adj) begin
95 case(wave_select)
96 2'd0:
97 if(rd_addr >= sine_wave_addr && rd_addr <= sine_wave_addr+9'd99)
98 if(rd_addr == sine_wave_addr+9'd99)
99 rd_addr <= sine_wave_addr;
100 else
101 rd_addr <= rd_addr+9'd1;
102 else
103 rd_addr <= sine_wave_addr;
104 2'd1:
105 if(rd_addr >= square_wave_addr && rd_addr <= square_wave_addr+9'd99)
106 if(rd_addr == square_wave_addr+9'd99)
107 rd_addr <= square_wave_addr;
108 else
109 rd_addr <= rd_addr+9'd1;
110 else
111 rd_addr <= square_wave_addr;
112 2'd2:
113 if(rd_addr >= triangle_wave_addr && rd_addr <= triangle_wave_addr+9'd99)
114 if(rd_addr == triangle_wave_addr+9'd99)
115 rd_addr <= triangle_wave_addr;
116 else
117 rd_addr <= rd_addr+9'd1;
118 else
119 rd_addr <= triangle_wave_addr;
120 2'd3:
121 if(rd_addr >= sawtooth_wave_addr && rd_addr <= sawtooth_wave_addr+9'd99)
122 if(rd_addr == sawtooth_wave_addr+9'd99)
123 rd_addr <= sawtooth_wave_addr;
124 else
125 rd_addr <= rd_addr+9'd1;
126 else
127 rd_addr <= sawtooth_wave_addr;
128 default:
129 if(rd_addr >= sine_wave_addr && rd_addr <= sine_wave_addr+9'd99)
130 if(rd_addr == sine_wave_addr+9'd99)
131 rd_addr <= sine_wave_addr;
132 else
133 rd_addr <= rd_addr+9'd1;
134 else
135 rd_addr <= sine_wave_addr;
136 endcase
137 end
138 else rd_addr <= rd_addr;
139 end
140 endmodule
141
在代码的第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。
35.5下载验证
将高速AD-DA模块插入开发板的扩展口,连接时注意扩展口电源引脚方向和开发板电源引脚方向一致,然后将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。
硬件连接实物图如图 35.5.1所示:
图 35.5.1 FPGA开发板硬件连接实物图
将工程生成的比特流文件下载到FPGA中后,然后使用示波器测量DA输出通道的波形。首先将示波器带夹子的一端连接到开发板的GND位置(可使用杜邦线连接至开发板上的任一的GND管脚),然后将另一端探针插入高速AD-DA模块的DA通道中间的金属圆圈内(注意将红色的保护套拿掉),如图 35.5.2
所示。或者也可以直接测试高速AD-DA模块的TP引脚,如图 35.5.3所示。
图 35.5.2 DA模拟电压测量孔位
图 35.5.3 DA模拟电压测量点(TP)
此时观察Debuger窗口的波形:
通过按下按键key0可实现波形的的切换:
图 35.5.4 正弦波测量图
图 35.5.5 方波测量图
图 35.5.6 三角波测量图
图 35.5.7 锯齿波测量图
通过按下按键key1可实现频率的切换:
图 35.5.8 频率切换图(一)
图 35.5.9 频率切换图(二)
在Debuger上观察到正确的波形后,说明DDS的功能已经实现了。关于生成的模拟波形除了可以在Debuger上观察,也可以通过示波器去进行观察。 |
|