DDS 信号发生器实验

news2024/11/9 2:16:49

目录

DDS 信号发生器实验

1、DDS 简介

2、实验任务

3、程序设计

3.1、DDS 顶层模块代码

3.2、clk_wiz IP 核

3.3、ILA IP 核(集成逻辑分析器:Integrated Logic Analyzer,ILA)

3.4、各波形参考代码

3.4.1、正弦信号波形采集参考代码(sin_wave.m):

3.4.2、方波信号波形采集参考代码(square_wave.m):

3.4.3、三角波信号波形采集参考代码(triangle_wave.m):

3.4.4、锯齿波信号波形采集参考代码(sawtooth_wave.m):

3.4.5、将四个 coe 文件整合为一个 coe 文件

3.5、ROM IP 核

ROM IP 核,加载生成的.coe 文件

3.6、DA 数据发送模块代码

3.7、AD 数据接收模块代码

3.8、按键消抖模块代码

4、硬件设计

4.1、添加约束文件.dxc

5、下载验证


DDS 信号发生器实验

       DDS(Direct Digital Synthesizer)即直接数字式频率合成器,是一种新型的频率合成技术。与传统的频率合成器相比,DDS 具有相对带宽大,频率转换时间短,稳定性好,分辨率高,可灵活产生多种信号等优点。较容易实现频率、相位及幅度的数控调制,因此,在现代电子系统及设备的频率源设计中,尤其在通信领域,直接数字频率合成器的应用越来越广泛。作为设计人员,我们习惯称它为信号发生器,一般用它产生正弦、锯齿、方波等不同波形或不同频率的信号波形,在电子设计和测试中得到广泛应用。本章我们将利用高速 ad/da 模块去设计实现一个简易的 DDS 信号发生器。

1、DDS 简介

       DDS(Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有低成本、低功耗、高分辨率、频率转换时间短、相位连续性好等优点,对数字信号处理及其硬件实现有着很重要的作用。DDS 的基本结构主要由相位累加器、相位调制器、波形数据表 ROMD/A 转换器等四大结构组成,其中较多设计还会在数模转换器之后增加一个低通滤波器(LPF)。DDS 基本结构图如图 27.1.1 所示。

       由上图可以看出,DDS 主要由相位累加器、相位调制器、波形数据表以及 D/A 转换器构成。其中相位累加器由 N 位加法器与 N 位寄存器构成。每来一个时钟,加法器就将频率控制字与累加寄存器输出的相位数据相加,相加的结果又反馈至累加寄存器的数据输入端,以使加法器在下一个时钟脉冲的作用下继续与频率控制字相加。这样,相位累加器在时钟的作用下,不断对频率控制字进行线性相位累加。即在每一个时钟脉冲输入时,相位累加器便把频率控制字累加一次。当相位累加器累加满量时就会产生一次溢出,完成一个周期的动作。相位累加器输出的数据就是合成信号的相位。相位累加器的溢出频率,就是 DDS 输出的信号频率。

       通过改变相位控制字 P_WORD 可以控制输出信号的相位参数。令相位加法器的字长为 M,当相位控制字由 0 跃变为 P_WORD 时,波形存储器(ROM)的输入为相位累加器的输出与相位控制字 P_WORD 之和,因而其输出的幅度编码相位会增加 P_WORD/2M,从而使输出的信号产生相移。

       用相位调制器输出的数据,作为波形存储器的相位采样地址,这样就可以把存储在波形存储器里的波形采样值经查表找出,完后相位到幅度的转换。N 位的寻址 ROM 相当于把 0°-360°的正弦信号离散成具有2 N 个样值的序列。若波形存储器中有 D 位数据位,则 2 N 个样值的幅值以 D 位二进制数值固化在波形存储器当中。按照地址的不同可以输出相应相位的正弦信号幅值。相位—幅度变换原理图如下图所示:

       数模转换器(D/A)的作用是把合成的正弦波数字量转化为模拟量。正弦幅度量化序列经数模转换器转换后变成了包络为正弦波的阶梯波。频率合成器对数模转换器的分辨率有一定的要求,其分辨率越高,合成的正弦波台阶数就越多,输出的波形精度也就越高。DDS 信号流程图如下图所示:

2、实验任务

       本节实验任务是使用 ZYNQ 开发板及高速 AD-DA 扩展模块(ATK_HS_AD_DA 模块)实现数模及模数的转换。首先 ZYNQ PL 端产生正弦波、方波、三角波、锯齿波变化的数字信号,经过 DA 芯片后转换成模拟信号,将 DA 的模拟电压输出端连接至 AD 的模拟电压输入端,AD 芯片将模拟信号转换成数字信号,通过按下按键 key0 key1 可以实现波形种类和波形频率的切换,然后通过示波器观察 DA 端模拟信号的波形是否出现了相应变化,也可以通过 Ila 界面去进行观察。

3、程序设计

      根据本章的实验任务,ZYNQ PL 端需要连续输出正弦波、方波、三角波、锯齿波波形的数据,才能使 AD9708 连续输出相应波形的模拟电压,如果使用三角函数公式运算的方式来编写代码,然后输出正弦波、方波、三角波、锯齿波的波形数据,那么程序设计会变得非常复杂。在工程应用中,一般将波形数据存储在 RAM 或者 ROM 中,由于本次实验并不需要写数据到 RAM 中,因此我们将波形数据存储在只读的 ROM 中,直接读取 ROM 中的数据发送给 DA 转换芯片即可。

       图 27.4.1 是根据本章实验任务画出的系统框图。ROM 里面事先存储好了波形的数据,DA 数据发送模块从 ROM 中读取数据,将数据和时钟送到 AD9708 的输入数据端口和输入时钟端口;AD 数据接收模块给 AD9280 输出驱动时钟信号和使能信号,并采集 AD9280 输出模数转换完成的数据。

DDS 实验的系统框图如下图所示:

           FPGA 顶层模块(dds)例化了以下六个模块:时钟模块(clk_wiz_0两个按键消抖模块(key_debounceDA 数据发送模块(da_wave_sendROM 波形存储模块(rom_400x8bAD 数据接收模块(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 实验,这里我们重点讲解其余的几个模块。

3.1、DDS 顶层模块代码

顶层模块的代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/06/25 10:44:45
// Design Name: 
// Module Name: dds
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module dds(
    input              sys_clk,      //系统时钟
    input              sys_rst_n,    //系统复位,低电平有效
    input              key0,         //按键key0
    input              key1,         //按键key1   
    //DA芯片接口
    output            da_clk,        //DA(AD9708)驱动时钟,最大支持125Mhz 时钟
    output  [7:0]     da_data,       //输出给 DA 的数据  
    //AD芯片接口
    input  [7:0]      ad_data,       //AD输入数据
    //模拟输入电压超出量程标志
    input             ad_otr,        //0:在量程范围内; 1:超出量程
    output            ad_clk         //AD(AD9280)驱动时钟,最大支持32Mhz时钟
    );

//wire define
wire  [8:0]          rd_addr;       //ROM 读地址
wire  [7:0]          rd_data;       //ROM 读出的数据
wire                 key0_value;    //key0 消抖后的按键值
wire                 key0_flag;     //key0 消抖后的按键值的有效标志
wire                 key1_value;    //key1 消抖后的按键值
wire                 key1_flag;     //key1 消抖后的按键值的有效标志
wire                 clk_100M;      //da 芯片的驱动时钟

//**************************************************
//**          main code
//**************************************************

//例化按键消抖模块
key_debounce u_key0_debounce(
    .clk            (clk_100M   ),
    .sys_rst_n      (sys_rst_n  ),
    
    .key            (key0      ),
    .key_flag       (key0_flag  ),
    .key_value      (key0_value )
);

//例化按键消抖模块
key_debounce u_key1_debounce(
    .clk            (clk_100M   ),
    .sys_rst_n      (sys_rst_n  ),
    
    .key            (key1      ),
    .key_flag       (key1_flag  ),
    .key_value      (key1_value )
);

//DA 数据发送
da_wave_send u_da_wave_send(
    .clk               (clk_100M  ),
    .rst_n             (sys_rst_n ),
    .key0_value        (key0_value),
    .key0_flag         (key0_flag ),
    .key1_value        (key1_value),
    .key1_flag         (key1_flag ),
    .rd_data           (rd_data   ),
    .rd_addr           (rd_addr   ),
    .da_clk            (da_clk    ),
    .da_data           (da_data   )
);

clk_wiz_0 u_clk_wiz_0
(
    // Clock out ports
    .clk_out1   (clk_100M   ), // output clk_out1
    // Status and control signals
    .reset      (~sys_rst_n ), // input reset
    .locked     (locked     ), // output locked
    // Clock in ports
    .clk_in1    (sys_clk    ) // input clk_in1
); 

//ROM 存储波形
rom_400x8b u_rom_400x8b (
    .clka      (clk_100M  ), // input wire clka
    .addra     (rd_addr   ), // input wire [8 : 0] addra
    .douta     (rd_data   ) // output wire [7 : 0] douta
);

//AD 数据接收
ad_wave_rec u_ad_wave_rec(
    .clk         (sys_clk    ),
    .rst_n       (sys_rst_n  ),
    .ad_data     (ad_data    ),
    .ad_otr      (ad_otr     ),
    .ad_clk      (ad_clk     )
); 

//ILA 采集 AD 数据
ila_0 ila_0 (
    .clk    (ad_clk  ), // input wire clk
    .probe0 (ad_otr  ), // input wire [0:0] probe0 
    .probe1 (ad_data ) // input wire [7:0] probe0 
);

endmodule

       DA 数据发送模块输出的读 ROM 地址(rd_addr)连接至 ROM 模块的地址输入端,ROM 模块输出的数据(rd_data)连接至 DA 数据发送模块的数据输入端,从而完成了从 ROM 中读取数据的功能。

       在顶层模块代码的第 85 94 行例化了 clk_wiz ip 核,用于生成 100M 时钟作为读 rom 地址的时钟。 注意,rom 模块的端口时钟和按键消抖模块的时钟也是 100M

3.2、clk_wiz IP 核

clk_wiz IP 核的配置如下图所示:

      接下来切换至“Output Clocks”选项卡,在“Output Clock”选项卡中,勾选第 1 个时钟,并且将其“Output Freq(MHz)”分别设置为 100,其他设置保持默认即可,如下图所示。

       其余的设置保持默认,最后直接点击“OK”按钮即可。

       在顶层模块代码的第 96 101 行例化了 ROM 模块,由 Block Memory Generator IP 核配置生成。

       在顶层模块代码的第 112  行例化了一个 ILA IP 核,用于捕获 ad_otr ad_data 的数据。 需要注意的是,ILA 的采样时钟必须使用 ad_clk,否则数据可能采集错误。

3.3、ILA IP 核(集成逻辑分析器:Integrated Logic Analyzer,ILA)

ILA   IP 核的配置如下图所示:

      我们把探针数量设置为 2,并且把采样深度设置为 4096。探针宽度的设置如下图所示:

       我们将两个探针的位宽设置成 1 8,分别对应 ad_otr ad_data 的位宽,设置完成后点击“OK”按钮即可。

      我们在前面说过,ROM 中存储的波形数据可以使用 MatLab 软件生成,使用 MatLab 绘制 4 种信号波形,对波形进行等间隔采样,以采样次数作为 ROM 存储地址,将采集的波形幅值数据做为存储数据写入存储地址对应的存储空间。在本次实验中我们对 4 种信号波形进行分别采样,采样次数为 100 次,采集的波形幅值数据位宽为 8bit,将采集数据保存为 coe 文件。

3.4、各波形参考代码

3.4.1、正弦信号波形采集参考代码(sin_wave.m):

% 正弦信号波形采集参考代码(sin_wave.m):

clc, clear, close all
F1=1; %信号频率
Fs=10^2; %采样频率
P1=0; %信号初始相位
N=10^2; %采样点数
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); %绘制图形
%创建 coe 文件
fild = fopen('sin_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;');
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector ='); 
for i = 1:N
    s2(i) = round(s(i)); %对小数四舍五入以取整
    if s2(i) <0 %负 1 强制置零
        s2(i) = 0
    end
    fprintf(fild, '%d',s2(i)); %数据写入
    
    if i==N
        fprintf(fild, '%s\n',';'); %最后一个数据用;
    else
        fprintf(fild,',\n'); % 其他数据用,
    end
end
fclose(fild); % 写完了,关闭文件

3.4.2、方波信号波形采集参考代码(square_wave.m):

% 方波信号波形采集参考代码(square_wave.m):

F1 = 1;    %信号频率
Fs = 10^2; %采样频率
P1 = 0;    %信号初始相位
N = 10^2;  %采样点数
t = [0:1/Fs:(N-1)/Fs]; %采样时刻
ADC = 2^7 - 1; %直流分量
A = 2^7;       %信号幅度

%生成方波信号
s = A*square(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形

%创建 coe 文件
fild = fopen('squ_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;');
%固定写法,下面开始写入数据 
fprintf(fild, '%s\n\n','memory_initialization_vector =');
for i = 1:N
    s2(i) = round(s(i)); %对小数四舍五入以取整
    if s2(i) <0 %负 1 强制置零
        s2(i) = 0
    end
    fprintf(fild, '%d',s2(i)); %数据写入
    if i==N
        fprintf(fild, '%s\n',';'); %最后一个数据用分号
    else
        fprintf(fild,',\n'); % 其他数据用 ,
    end
end
fclose(fild); % 写完了,关闭文件

27.4.8 MatLab 生成的方波信号波形

3.4.3、三角波信号波形采集参考代码(triangle_wave.m):

% 三角波信号波形采集参考代码(triangle_wave.m):

F1 = 1;    %信号频率
Fs = 10^2; %采样频率
P1 = 0;    %信号初始相位
N = 10^2;  %采样点数
t = [0:1/Fs:(N-1)/Fs]; %采样时刻
ADC = 2^7 - 1; %直流分量
A = 2^7; %信号幅度

%生成三角波信号
s = A*sawtooth(2*pi*F1*t + pi*P1/180,0.5) + ADC;
plot(s); %绘制图形

%创建 coe 文件
fild = fopen('tri_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;'); 
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector =');

for i = 1:N
    s2(i) = round(s(i)); %对小数四舍五入以取整
    if s2(i) <0 %负 1 强制置零
        s2(i) = 0
    end
    fprintf(fild, '%d',s2(i)); %数据写入
    if i==N
        fprintf(fild, '%s\n',';'); %最后一个数据用分号
    else
        fprintf(fild,',\n'); %其他数据用 ,
    end
end
fclose(fild); % 写完了,关闭文件

3.4.4、锯齿波信号波形采集参考代码(sawtooth_wave.m):

% 锯齿波信号波形采集参考代码(sawtooth_wave.m):

F1 = 1; %信号频率
Fs = 10^2; %采样频率
P1 = 0; %信号初始相位
N = 10^2; %采样点数
t = [0:1/Fs:(N-1)/Fs]; %采样时刻
ADC = 2^7 - 1; %直流分量
A = 2^7; %信号幅度

%生成锯齿波信号
s = A*sawtooth(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形
%创建 coe 文件
fild = fopen('saw_wave_100x8.coe','wt');

%写入 coe 文件头
%固定写法,下面开始写入数据
fprintf(fild, '%s\n','memory_initialization_radix=10;'); 
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector =');

for i = 1:N
    s2(i) = round(s(i)); %对小数四舍五入以取整
    if s2(i) <0 %负 1 强制置零
        s2(i) = 0
    end
    fprintf(fild, '%d',s2(i)); %数据写入
    if i==N
        fprintf(fild, '%s\n',';'); %最后一个数据用分号
    else
        fprintf(fild,',\n'); % 其他数据用 ,
    end
end
fclose(fild); % 写完了,关闭文件

       使用 MatLab 4 种波形进行采样后,生成 4 coe 文件,分别对应 4 种波形,我们通过调用一个深度为 100*4,位宽为 8bit ROM,将四个 coe 文件整合为一个 coe 文件。在配置 rom ip 核时将整合的 coe 文件导入到 rom 中。

3.4.5、将四个 coe 文件整合为一个 coe 文件

整体信号波形采集参考代码(wave_100x8.m)

clc; %清除命令行命令
clear; %清除工作区变量,释放内存空间
close 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; %信号幅度

F1 = 1; %信号频率
Fs = 10^2; %采样频率
P1 = 0; %信号初始相位
N = 10^2; %采样点数
t = [0:1/Fs:(N-1)/Fs]; %采样时刻
ADC = 2^7 - 1; %直流分量
A = 2^7; %信号幅度

s1 = A*sin(2*pi*F1*t + pi*P1/180) + ADC; %正弦波信号
s2 = A*square(2*pi*F1*t + pi*P1/180) + ADC; %方波信号
s3 = A*sawtooth(2*pi*F1*t + pi*P1/180,0.5) + ADC; %三角波信号
s4 = A*sawtooth(2*pi*F1*t + pi*P1/180) + ADC; %锯齿波信号
%创建 coe 文件
% fild = fopen('wave_16384x8.coe','wt');
fild = fopen('wave_400x8.coe','wt');
%写入 coe 文件头
fprintf(fild, '%s\n','MEMORY_INITIALIZATION_RADIX=10;'); %10 进制数
fprintf(fild, '%s\n','MEMORY_INITIALIZATION_VECTOR=');

for j = 1:4
    for i = 1:N
        if j == 1 %打印正弦信号数据
            s0(i) = round(s1(i)); %对小数四舍五入以取整
        end
        
        if j == 2 %打印方波信号数据
            s0(i) = round(s2(i)); %对小数四舍五入以取整
        end
        
        if j == 3 %打印三角波信号数据
            s0(i) = round(s3(i)); %对小数四舍五入以取整
        end
        
        if j == 4 %打印锯齿波信号数据
            s0(i) = round(s4(i)); %对小数四舍五入以取整
        end
        
        if s0(i) <0 %负 1 强制置零
            s0(i) = 0;
        end
        
        if j == 4 && i == N
            fprintf(fild, '%d',s0(i)); %数据写入
            fprintf(fild, '%s',';'); %最后一个数使用分号结束
        else
            fprintf(fild, '%d',s0(i)); %数据写入
            fprintf(fild, '%s\n',','); %逗号,换行
        end
    end
end
fclose(fild);

使用 Notepad++代码编辑器打开生成的 COE 文件后如下图所示:

 

3.5、ROM IP 核

ROM IP 核,加载生成的.coe 文件

        工程中创建了一个单端口 ROM,并命名为“rom_400x8b”,在调用 Block Memory Generator IP 核时, “Basic”选项也配置如下图所示:

        我们将其接口类型设置为“Native”、Memory Type 设置为“Single Port ROM”,即单端口 ROM “Port A Options”选项页的配置页面如下图所示:

 27.4.13 Block Memory Generator IP 核的 PortA Options 配置页面

       我们将 PortA 的位宽设置为 8,深度设置为 400,以存储 matlab 生成的 400 个数据。此外,将使能引脚的类型设置为 “Always Enabled” ,即 ROM 一直处于使能的状态。

接下来配置 “Other Options” 选项页,加载刚才生成的.coe 文件,如下图所示:

最后点击“OK”按钮完成 IP 核的配置。

3.6、DA 数据发送模块代码

DA 数据发送模块的代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/06/25 14:05:47
// Design Name: 
// Module Name: da_wave_send
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module da_wave_send(
    input             rst_n , //复位信号,低电平有效
    input             clk ,   //连接到 clk_100M
    input             key0_value, //消抖后的按键值
    input             key0_flag , //消抖后的按键值的有效标志
    input             key1_value, //消抖后的按键值
    input             key1_flag , //消抖后的按键值的有效标志
    
    input [7:0]       rd_data , //ROM 读出的数据
    output reg [8:0]  rd_addr , //读 ROM 地址
    //DA 芯片接口 
    output            da_clk , //DA(AD9708)驱动时钟,最大支持 125Mhz 时钟
    output [7:0]      da_data  //输出给 DA 的数据 
    );

//parameter
//波形调节控制
parameter sine_wave_addr = 9'd0; // 正弦波起始位置
parameter square_wave_addr = 9'd100; // 方波起始位置
parameter triangle_wave_addr = 9'd200; // 三角波起始位置
parameter sawtooth_wave_addr = 9'd300; // 锯齿波起始位置

//频率调节控制,FREQ_ADJ 的越大,最终输出的频率越低,范围 0~255
parameter FREQ_ADJ0 = 8'd0; //参数 0 对应输出 1Mhz 波形频率
parameter FREQ_ADJ1 = 8'd1; //参数 1 对应输出 500khz 波形频率
parameter FREQ_ADJ2 = 8'd3; //参数 3 对应输出 250khz 波形频率
parameter FREQ_ADJ3 = 8'd7; //参数 7 对应输出 125khz 波形频率

//reg define 
reg [7:0] freq_adj ; //频率调节参数寄存器
reg [7:0] freq_cnt ; //频率调节计数器
reg [1:0] wave_select ; //切换波形地址寄存器
reg [1:0] freq_select ; //切换波形频率寄存器

//*****************************************************
//** main code
//*****************************************************

//数据 rd_data 是在 clk_100M 的上升沿更新的,
//所以 DA 芯片在 clk_100M 的下降沿锁存数据是稳定的时刻。
//而 DA 实际上在 da_clk 的上升沿锁存数据,所以时钟取反,
//这样 clk_100M 的下降沿相当于 da_clk 的上升沿。 
assign da_clk =~clk; 
assign da_data = rd_data; //将读到的 ROM 数据赋值给 DA 数据端口

//切换波形种类
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        wave_select <= 2'd0;
    else if((key0_flag == 1) && (key0_value == 0)) begin //确保按键 key0 确实被有效按下
        if(wave_select < 2'd3)
            wave_select <= wave_select+1'd1;
        else 
            wave_select <= 0;
    end
    else
        wave_select <= wave_select;
end

//切换波形频率
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        freq_select <= 2'd0;
    else if((key1_flag ==1) && (key1_value ==0)) begin //确保按键 key1 确实被有效按下
        if(freq_select < 2'd3)
            freq_select <= freq_select+1'd1;
        else 
            freq_select <= 0;
    end
    else
    freq_select <= freq_select;
end

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        freq_adj <= 8'd0;
    else case(freq_select)
             2'd0:freq_adj <= FREQ_ADJ0;
             2'd1:freq_adj <= FREQ_ADJ1; 
             2'd2:freq_adj <= FREQ_ADJ2;
             2'd3:freq_adj <= FREQ_ADJ3;
             default:freq_adj <= FREQ_ADJ0;
         endcase
end

//频率调节计数器
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        freq_cnt <= 8'd0;
    else if(freq_cnt == freq_adj) 
        freq_cnt <= 8'd0;
    else 
        freq_cnt <= freq_cnt + 8'd1;
end

//读 ROM 地址,按照 100M 的频率去读
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        rd_addr <= 9'd0;
    else if(freq_cnt == freq_adj) begin
        case(wave_select)
            2'd0:
                if(rd_addr >= sine_wave_addr && rd_addr <= sine_wave_addr+9'd99)
                    if(rd_addr == sine_wave_addr+9'd99) 
                        rd_addr <= sine_wave_addr;
                    else
                        rd_addr <= rd_addr+9'd1;
                else
                    rd_addr <= sine_wave_addr; 
            2'd1:
                if(rd_addr >= square_wave_addr && rd_addr <= square_wave_addr+9'd99) 
                    if(rd_addr == square_wave_addr+9'd99)
                        rd_addr <= square_wave_addr;
                    else 
                        rd_addr <= rd_addr+9'd1;
                else
                    rd_addr <= square_wave_addr;
            2'd2:
                if(rd_addr >= triangle_wave_addr && rd_addr <= triangle_wave_addr+9'd99)
                    if(rd_addr == triangle_wave_addr+9'd99)
                        rd_addr <= triangle_wave_addr;
                    else
                        rd_addr <= rd_addr+9'd1; 
                else 
                    rd_addr <= triangle_wave_addr; 
            2'd3:
                if(rd_addr >= sawtooth_wave_addr && rd_addr <= sawtooth_wave_addr+9'd99) 
                    if(rd_addr == sawtooth_wave_addr+9'd99)
                        rd_addr <= sawtooth_wave_addr;
                    else
                        rd_addr <= rd_addr+9'd1; 
                else 
                    rd_addr <= sawtooth_wave_addr; 
            default:
                if(rd_addr >= sine_wave_addr && rd_addr <= sine_wave_addr+9'd99) 
                    if(rd_addr == sine_wave_addr+9'd99) 
                        rd_addr <= sine_wave_addr;
                    else
                        rd_addr <= rd_addr+9'd1;
                else
                    rd_addr <= sine_wave_addr; 
        endcase
    end
    else rd_addr <= rd_addr; 
end

endmodule

//在代码的第 52 行定义了一个参数寄存器 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,
//通过锁相环倍频可以得到 100Mhz 的 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。

3.7、AD 数据接收模块代码

AD 数据接收代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/06/25 14:29:35
// Design Name: 
// Module Name: ad_wave_rec
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ad_wave_rec(
     input               clk,        //50Mhz系统时钟(达芬奇Pro开发板)
     input               rst_n,      //复位信号,低电平有效
     
     input    [7:0]      ad_data,     //AD输入数据
     //模拟输入电压超出量程标志(本次试验未用到)
     input               ad_otr,      //0:在量程范围; 1:超出量程
     output  reg         ad_clk       //AD(TLC5510)驱动时钟,最大支持20Mhz时钟(达芬奇Pro开发板) 
    );

//*****************************************
//**  main   code
//******************************************

//时钟分频(2分频,时钟频率为25Mhz),产生AD时钟(50Mhz系统时钟,达芬奇Pro开发板)
always @(posedge clk or negedge rst_n) begin
     if(rst_n==1'b0)
         ad_clk <=1'b0;
     else
         ad_clk <= ~ad_clk;
end

endmodule

3.8、按键消抖模块代码

按键消抖模块代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/06/25 14:28:31
// Design Name: 
// Module Name: key_debounce
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


//按键消抖模块
module key_debounce(
     input          sys_clk,        //50MHz时钟
     input          sys_rst_n,     //复位信号,低频有效
     
     input           key,           //外部按键输入
     output  reg    key_flag,      //按键数据有效信号
     output  reg    key_value      //按键消抖后的数据
);

//reg define
reg  [31:0]   delay_cnt;
reg           key_reg;

//(系统时钟50MHz,一个时钟周期是1/50MHz=0.02us=20ns)(0.2s需计数:0.2s/20ns=10_000_000次)
//(系统时钟200MHz,一个时钟周期是1/200MHz=0.005us=5ns)(20ms需计数:20ms/5ns=4_000_000次)
//*************************************************************
//**              main code
//*************************************************************
always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        key_reg    <= 1'b1;
        delay_cnt  <= 32'd0;
    end
    else begin
        key_reg <= key;
        if (key_reg != key) begin //一旦检测到按键状态发生变化(有按键按下或者释放)
            //delay_cnt <= 32'd1000_000;//给延时计数器重新装载初始值(计数时间为20ms)
            delay_cnt <= 32'd4; //仅用于仿真
        end
        else begin //按键状态稳定时,计数器递减,开始20ms倒计时
            if (delay_cnt >32'd0)
                 delay_cnt <= delay_cnt - 1'b1;
            else 
                 delay_cnt <= 32'd0;
        end
    end
end

always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        key_flag <= 1'b0;
        key_value <= 1'b1;
    end
    else if (delay_cnt == 32'd1) begin //当计数器递减到1时,说明按键稳定状态维持了20ms
            key_flag <= 1'b1; //此时消抖过程结束,给出一个时钟周期的标志信号
            key_value <= key; //并寄存此时按键的值
        end
    else begin
            key_flag <= 1'b0;
            key_value <= key_value;
    end
end

endmodule

4、硬件设计

       本章节中硬件设计与“高速 AD/DA 实验”完全相同,此处不在赘述。本实验中,各端口信号的管脚分配如下表所示。

 

4.1、添加约束文件.dxc

对应的 .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]

5、下载验证

       将高速 AD-DA 模块插入 ZYNQ 领航者开发板的扩展口(靠近 TF_CARD 接口的位置),连接时注意扩展口电源引脚方向和开发板电源引脚方向一致,然后将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。

       领航者开发板硬件连接实物图如图 27.5.1 所示:

       将工程生成的比特流文件下载到 ZYNQ 中后,然后使用示波器测量 DA 输出通道的波形。首先将示波器带夹子的一端连接到开发板的 GND 位置(可使用杜邦线连接至开发板上的任一的 GND 管脚),然后将另一端探针插入高速 AD-DA 模块的 DA 通道中间的金属圆圈内(注意将红色的保护套拿掉),如图 27.5.2 所示。或者也可以直接测试高速 AD-DA 模块的 TP 引脚,如图 27.5.3 所示。

       此时观察示波器可以看到模拟波的波形,如果观察不到波形,可查看示波器设置是否正确,可以尝试按下示波器的“AUTO”,再次观察示波器波形。示波器的显示界面如下所示:

通过按下按键 key0 可实现波形的的切换:

 

 

通过按下按键 key1 可实现频率的切换:

 

        在示波器上观察到正确的波形后,说明 DDS 的功能已经实现了。关于生成的模拟波形除了可以在示波器上观察,也可以通过 Ila 去进行观察,在 ILA 中观察 ad_data 数据的具体的做法可以参照高速 AD/DA 实验里的步骤。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/683578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

身份识别与访问管理(IAM)工具

AD360 是一款企业 IAM 解决方案&#xff0c;可帮助管理身份、保护访问并确保合规性。它具有强大的功能&#xff0c;例如自动化身份生命周期管理、安全 SSO、自适应 MFA、基于审批的工作流、UBA 驱动的身份威胁防护和历史审计报告。AD360 直观的界面和强大的功能使其成为满足现代…

行业云统领2023十大技术趋势,新华三把脉数实融合演进路径

“每一年的科技突破与环境变局&#xff0c;有一定的随机性又有一定的必然性&#xff0c;导致人类社会永远处于动态塑形的过程。”中国工程院院士陈晓红在想像未来的技术变化时认为&#xff1a;“无论怎么变化&#xff0c;人类未来图景仍然源于社会生活与经济发展的真实需求”。…

了解MySQL配置文件:位置、结构和选项

目录 1 MySQL配置文件的位置2 MySQL配置文件的结构3 MySQL配置选项4 [mysqld]部分&#xff1a;5 [client]部分&#xff1a;6 MySQL配置文件的重要性7 总结 本文详细介绍了MySQL配置文件的位置、结构和常用选项。了解如何使用MySQL配置文件来管理和配置MySQL服务器的行为和属性。…

5. QT环境下使用OPenCV(基于TCP实现摄像头图像数据的多线程传输)

1. 说明 通常情况下对于图像数据的采集可以放在后端进行,采集到的图像数据如果有需要可以通过通信将数据传输到前端进行显示,这其中需要使用到TCP数据传输协议和QT下的多线程开发技术。QT当中主线程一般是界面层次的,在主线程中执行耗时较长的数据操作,会引起界面的卡顿,…

React学习9 Router6

使用 userRoutes路由表 注册路由 Outlet指定路由组件呈现位置 ***很重要&#xff0c;userNavigate()实现编程式路由导航 函数式组件接收params参数的hook useParams() 接受search参数 接收state参数 userNavigate 实现前进后退 判断是否处于路由组件中&#xff0c;在路由器管理…

网站加密防止拷贝的php域名授权方法

1 public function getdata()2 $method DES-ECB;//加密方法3 $passwd qq496631085;//加密密钥4 $options 0;//数据格式选项&#xff08;可选&#xff09;5 $iv ;//加密初始化向量&#xff08;可选&#xff09;6 $url ba…

双因素身份验证在远程访问中的重要性

在快速发展的数字环境中&#xff0c;远程访问计算机和其他设备已成为企业运营的必要条件。无论是在家庭办公室运营的小型初创公司&#xff0c;还是团队分散在全球各地的跨国公司&#xff0c;远程访问解决方案都能保证工作效率和连接性&#xff0c;能够跨越距离和时间的阻碍。 …

MQTT(二)Java整合MQTT

Java整合MQTT 上一节知道MQTT是一个通信协议&#xff0c;需要一个代理服务Broker&#xff1b;通信设备作为客户端Client&#xff0c;后台系统服务器也作为客户端Client。 经过了解选用EMQX作为代理服务Broker&#xff08;支持WEB界面查看&#xff09; 后台服务使用Spring In…

【教学类-36-01】Midjounery生成的四张图片切片成四张小图

作品展示&#xff1a; 把一张正方形图片的四个等大小图切割成四张图片 背景需求 最近在学习ChatGPT的绘画&#xff08;midjounery AI艺术&#xff09; 我想给中班孩子找卡通动物图片&#xff08;黑白线条&#xff09;&#xff0c;打印下来&#xff0c;孩子们练习描边、涂色…

【发布】ChatGLM2-6B:性能大幅提升,8-32k上下文,推理提速42%

自3月14日发布以来&#xff0c; ChatGLM-6B 深受广大开发者喜爱&#xff0c;截至 6 月24日&#xff0c;来自 Huggingface 上的下载量已经超过 300w。 为了更进一步促进大模型开源社区的发展&#xff0c;我们再次升级 ChatGLM-6B&#xff0c;发布 ChatGLM2-6B 。 在主要评估LLM模…

《C++ Primer》--学习7

顺序容器 容器库概览 迭代器 与容器一样&#xff0c;迭代器有着公共的接口&#xff1a;如果一个迭代器提供某个操作&#xff0c;那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。 迭代器范围 一个迭代器范围是由一对迭代器表示&#xff0c;两个迭代器分别指向…

剪辑必备技巧:轻松去除视频中的多余物体

在视频剪辑过程中&#xff0c;有时我们需要去除视频中的多余物体&#xff0c;以提升视觉效果和观赏体验。今天将为您介绍一些实用的技巧&#xff0c;帮助您轻松去除视频中的多余物体&#xff0c;让您的剪辑作品更加精彩。 一、选择适当的剪辑软件进行剪辑操作 一些专业的剪辑…

基于MATLAB实现KECA、PCA和KPCA的多阶段发酵过程监测方法毕业设计(完整源码+说明文档+PPT+开题报告+数据)

文章目录&#xff0c;完整源码在文末 1. 研究目标2. 主要研究内容3. 技术路线4. 预期成果5. 功能说明6. 参考文献7. 完整仿真源码下载 1. 研究目标 实现基于KECA的青霉素发酵过程故障监测 2. 主要研究内容 1.针对KPCA监测算法在数据降维过程中簇结构信息丢失的问题&#xff…

BootStrap案例

BootStrap是已经写好的css样式 &#xff08;1&#xff09;下载BootStrap 解压后放在 static文件夹–>plugins(存放插件)–>bootstrap-3.4.1 &#xff08;2&#xff09;使用 在页面上引入BootStrap 编写HTML时&#xff0c;按照BootStrap的规定来编写自定制 开发版本(一…

Web服务器群集:部署LNMP平台(yum方式安装)

目录 一、理论 1.yum安装与源码安装的区别 二、实验 1.Nginx安装&#xff08;yum方式&#xff09; 2.MySQL安装&#xff08;yum方式&#xff09; 3.PHP安装&#xff08;yum方式&#xff09; 4.Nginx 配置 三、问题 1.客户端 404 报错 四、总结 一、理论 1.yum安装与…

转行网络安全,报班之后就万事大吉了吗?

最近在网上看到很多人问&#xff0c;“是不是报了培训班就可以高枕无忧&#xff0c;坐等毕业之后拿高工资了&#xff1f;”“是不是学了网络安全&#xff0c;就一定能够实现月入过万了&#xff1f;” 其实&#xff0c;无论你是选择网络安全也好&#xff0c;还是选择其他的Java、…

自我管理型团队:企业组织力提升利器

近年来&#xff0c;软件项目的规模和复杂性在以前所未有的速度增长。因此&#xff0c;快速响应需求变化已经成为互联网行业的常态。在这样的环境下&#xff0c;软件产品的快速开发和迭代对于公司迅速占领市场、抢占商机来说具有至关重要的意义。 所以&#xff0c;越来越多的研…

Mysql高阶语句(二)

Mysql高阶语句&#xff08;二&#xff09; 1、别名2、子查询3、EXISTS4、连接查询5、CREATE VIEW 视图6、UNION 联集7、交集值8、无交集值9、CASE10、算排名12、算累积总计13、算总合百分比14、算累计总合百分比15、空值&#xff08;null&#xff09;和无值&#xff08;’’&am…

大中型灌区信息化监测系统-智慧灌区

系统概述 大中型灌区信息化监测系统主要对对灌区的水情、雨情、土壤墒情、气象等信息进行监测&#xff0c;对重点区域进行视频监控&#xff0c;同时对泵站、闸门进行远程控制&#xff0c;实现了信息的测量、统计、分析、控制、调度等功能。为灌区管理部门科学决策提供了依据&a…

从0到1精通自动化测试,pytest自动化测试框架,skip跳过用例(八)

一、前言 pytest.mark.skip可以标记无法在某些平台上运行的测试功能&#xff0c;或者希望自己失败的测试功能 skip意味着只有在满足某些条件时才希望测试通过&#xff0c;否则pytest应该跳过运行测试。 常见示例是在非Windows平台上跳过仅限Windows的测试&#xff0c;或跳过测…