当前位置: 首页 > news >正文

自主设计一个DDS信号发生器

DDS发生器

      DDS信号发生器是直接数字频率合成技术,采用直接数字频率合成(Direct Digital Synthesis,简称DDS)技术,把信号发生器的频率稳定度、准确度提高到与基准频率相同的水平,并且可以在很宽的频率范围内进行精细的频率调节。采用这种方法设计的信号源可工作于调制状态,可对输出电平进行调节,也可输出各种波形。

工作原理

相位累加 :在基准时钟的驱动下,频率控制字与累加器输出的累加相位数据相加,结果送至相位寄存器的数据输入端,相位寄存器将累加器在上一个时钟作用后产生的新相位数据反馈到累加器的输入端,使相位累加器进行线性相位累加,当累加满时产生一次溢出,完成一个周期性动作,溢出频率即为输出信号频率。

波形查找与转换 :相位寄存器的输出与相位控制字相加,结果作为正弦查找表的地址,查找表由 ROM 构成,存有一个完整周期正弦波的数字幅度信息,每个地址对应正弦波的一个相位点,把输入地址信息映射成正弦波幅度信号,输出到 D/A 转换器,经转换成模拟量形式信号,再通过低通滤波器衰减和滤除不需要的取样分量,输出频谱纯净的正弦波信号。

基本结构

DDS 主要由相位累加器、相位调制器、波形数据表以及 D/A 转换器构成。

相位累加器:相位寄存器同样由FPGA内部的寄存器资源构成,用于暂存相位累加器的输出结果。其位数与相位累加器相匹配,以便准确地反馈相位累加结果。

在相位累加器完成一次累加后,其输出结果会被送入相位寄存器。在下一个时钟周期,相位寄存器将存储的相位数据反馈到相位累加器的输入端,以实现连续的相位累加操作。

波形存储器(ROM):在FPGA中,波形存储器通常由只读存储器(ROM)实现。可利用FPGA内部的ROM资源,预先将波形的一个周期内的幅度信息以数字形式存储在ROM中。例如,对于正弦波,可以预先计算出一个周期内各个相位点的幅度值,并将其存储到ROM中。

相位寄存器的输出与相位控制字相加后,结果作为波形存储器的地址。FPGA根据该地址从ROM中读取预先存储的波形幅度值,并将其输出到数模转换器(DAC)。波形存储器的容量和精度决定了输出信号的精度和质量,容量越大,可存储的波形点数越多,输出信号的波形越平滑;存储精度越高,输出信号的幅度精度越高。

数模转换器(DAC):FPGA本身不直接包含数模转换器,通常需要外接DAC芯片。通过FPGA的I/O引脚与DAC芯片的接口相连,将数字波形数据传输给DAC进行数模转换。

FPGA按照一定的时序控制,将波形存储器输出的数字幅度信号通过I/O引脚发送到DAC芯片。DAC芯片根据接收到的数字信号,将其转换为对应的模拟信号。转换后的模拟信号通常是一个阶梯状的波形,需要经过后续的低通滤波处理。

低通滤波器(LPF):低通滤波器一般由外部的无源或有源滤波电路实现,FPGA主要负责控制和配置滤波器的相关参数。例如,通过FPGA的I/O引脚输出控制信号来调整滤波器的截止频率等参数。

DAC输出的阶梯状模拟信号包含丰富的高频分量,低通滤波器会滤除这些高频分量,使输出信号变得更加平滑,接近理想的正弦波或其他目标波形。

DFS信号发生器中,相位累加器是核心部件之一,它由N位加法器和N位寄存器组成。每当有一个时钟脉冲到来时,加法器就会把频率控制字和寄存器里当前存着的相位数据加起来,得出的结果又会送回寄存器的输入端。这样,在下一个时钟脉冲来的时候,加法器会再次把频率控制字和寄存器里的新数据相加。这个过程不断重复,相位累加器就在时钟信号的驱动下,持续地对频率控制字进行相加操作。每次相加后,累加器输出的数据就代表了合成信号的相位信息,而且相位累加器发生溢出的频率,就是DFS输出信号的频率。简单来说,相位累加器的作用就是在时钟的作用下,不断累加频率控制字,从而得到合成信号的相位数据,并且这个相位数据会被用作波形存储器的地址,去查出对应的波形采样值,实现从相位到幅度的转换,最后波形存储器输出的数据会被送到D/A转换器,转换成模拟信号输出。

特点

频率分辨率高:输出信号的频率与相位累加器的增量值有关,可以实现非常细微的频率调节。

波形种类丰富:能够生成正弦波、方波、三角波等多种波形,通过改变查找表中的数据即可快速切换。

频率稳定度和准确度高:频率稳定度和准确度可提高到与基准频率相同的水平。

快速转换时间:信号频率转换时间短。

可调制性:可工作于调制状态,对输出电平进行调节。

自己动手设计一个DDS信号发生器

思路

通过查阅相关资料,我们可以从以下几个步骤实现DDS信号发生器的设计:

一、系统设计

确定设计目标

输出信号类型:正弦波和方波。

频率范围:10Hz~5MHz。

最小频率分辨率:小于1kHz。

系统架构划分

相位累加器模块:负责生成相位信息。

波形查找表模块:存储正弦波和方波的波形数据。

数模转换器(DAC)接口模块:将数字信号转换为模拟信号。

控制模块:用于设置频率控制字、波形选择等参数。

时钟模块:提供系统时钟信号。

二、模块设计

相位累加器模块

选择合适的相位累加器位数(如32位)。

根据频率范围和分辨率要求,计算频率控制字的范围。

波形储存器模块

使用ROM IP核存储正弦波和方波的波形数据。

初始化查找表,填充正弦波和方波的数字样本。

控制模块

提供用户接口,用于设置频率控制字、选择波形类型(正弦波或方波)等参数。

将用户输入的参数传递给相位累加器和波形查找表模块。

三、仿真验证

使用ModelSim或其他仿真工具搭建仿真环境。将设计的各个模块集成到仿真环境中,设置仿真时钟信号和测试激励。

实现步骤

根据实验指导,我们可以根据以下结构设计以便理解:

所需正弦波生成

在仿真设计之前,我们需要通过MATLAB生成一个正弦波文件添加到我们的项目中,以便我们后续的操作。

clc, clear, close allF1=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:Ns2(i) = round(s(i)); %对小数四舍五入以取整if s2(i) <0 %负 1 强制置零s2(i) = 0endfprintf(fild, '%d',s2(i)); %数据写入if i==Nfprintf(fild, '%s\n',';'); %最后一个数据用;elsefprintf(fild,',\n'); % 其他数据用,endendfclose(fild); % 写完了,关闭文件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:Ns2(i) = round(s(i)); %对小数四舍五入以取整if s2(i) <0 %负 1 强制置零s2(i) = 0endfprintf(fild, '%d',s2(i)); %数据写入if i==Nfprintf(fild, '%s\n',';'); %最后一个数据用分号elsefprintf(fild,',\n'); % 其他数据用 ,endendfclose(fild); % 写完,关闭文件

1、相位累加器模块

// 模块声明,名称为squwave// CPi为时钟输入信号,RSTn为复位输入信号,Address为17位地址输入,Qsquare为12位输出寄存器module squwave(CPi, RSTn, Address, Qsquare);input CPi;                  // 时钟信号输入input RSTn;                 // 复位信号输入(低电平有效)input [16:0] Address;       // 17位地址输入output reg [11:0] Qsquare;  // 12位输出寄存器// 时序逻辑块,检测CPi的上升沿触发always @(posedge CPi) begin// 当复位信号RSTn为低电平时,Qsquare输出全0if (!RSTn) beginQsquare = 12'h000;  // 复位时输出12位全0end// 正常工作时,根据Address的值设置Qsquare的输出else begin// 如果Address小于等于17'h0FFFF(即十进制的131071),Qsquare输出全1if (Address <= 17'h0FFFF) beginQsquare = 12'hFFF;  // 输出12位全1end// 否则,Qsquare输出全0else beginQsquare = 12'h000;  // 输出12位全0endendendendmodule

该代码定义了一个名为squwave的数字电路模块,该模块的主要功能是根据输入地址Address的值来生成一个12位的输出信号Qsquare。模块接收一个时钟信号CPi和一个低电平有效的复位信号RSTn。当复位信号RSTn被激活(即处于低电平状态)时,输出Qsquare会被立即清零,以确保模块从一个已知的初始状态开始工作。

在正常工作状态下,模块会在每个时钟周期的上升沿检查Address输入的值。如果Address的值小于或等于0xFFFF(即十进制的65535),则输出Qsquare会被设置为全1(0xFFF),这通常表示一个高电平状态或激活状态。相反,如果Address的值大于0xFFFF,则输出Qsquare会被设置为全0(0x000),表示一个低电平状态或非激活状态。

2、ROM模块

// 定义一个名为rom_sine的模块,用于生成正弦波形的只读存储器(ROM)module rom_sine(input [10:0] address,  // 11位地址输入,用于访问ROM中的波形数据input clock,           // 时钟信号输入,用于同步数据读取output reg [11:0] q    // 12位输出,用于输出ROM中存储的正弦波形数据);// 声明一个12位宽、2048个元素的存储器数组,用于存储正弦波形数据reg [11:0] mem [0:2047];// 初始化块,用于从MIF文件中读取正弦波形数据到存储器数组initial begin$readmemb("Sine1024.mif", mem);  // 从Sine1024.mif文件中读取数据到mem数组end// 时序逻辑块,用于在时钟上升沿更新输出q的值always @(posedge clock) beginq <= mem[address];  // 根据输入地址,从存储器数组中读取对应的正弦波形数据,并赋值给输出qendendmodule// 定义一个名为rom_square的模块,用于生成方波形的只读存储器(ROM)module rom_square(input [10:0] address,  // 11位地址输入,用于访问ROM中的波形数据input clock,           // 时钟信号输入,用于同步数据读取output reg [11:0] q    // 12位输出,用于输出ROM中存储的方波形数据);// 声明一个12位宽、2048个元素的存储器数组,用于存储方波形数据reg [11:0] mem [0:2047];// 初始化块,用于从MIF文件中读取方波形数据到存储器数组initial begin$readmemb("Square1024.mif", mem);  // 从Square1024.mif文件中读取数据到mem数组end// 时序逻辑块,用于在时钟上升沿更新输出q的值always @(posedge clock) beginq <= mem[address];  // 根据输入地址,从存储器数组中读取对应的方波形数据,并赋值给输出qendendmodule

这两个模块分别实现了正弦波和方波的ROM生成器。它们都使用了一个12位宽、2048个元素的存储器数组来存储波形数据,并在时钟信号的上升沿根据输入地址读取对应的波形数据并输出。初始化块使用readmemb系统任务从MIF文件中读取波形数据到存储器数组中。这种设计可以用于数字信号处理应用中,生成所需的波形信号。

3、顶层模块

module DDS_top (input CLOCK_50,       // 输入50MHz时钟信号input RSTn,           // 输入复位信号,低电平有效input [1:0] WaveSel,  // 输入波形选择信号,2位宽,用于选择输出的波形类型input [12:0] K,       // 输入频率控制字,13位宽,用于控制输出波形的频率output reg [11:0] WaveValue, // 输出波形值,12位宽,输出实际的波形数据wire [9:0] ROMaddr,  // 定义一个10位宽的ROM地址信号wire [16:0] Address, // 定义一个17位宽的地址信号wire [11:0] Qsine,   // 定义一个12位宽的正弦波形数据信号wire [11:0] Qsquare,// 定义一个12位宽的方波形数据信号output [0:0] LEDG,   // 输出LED指示信号,1位宽,用于指示PLL锁定状态output CLOCK_100    // 输出100MHz时钟信号);// 定义内部时钟信号CPi,等于输出的100MHz时钟wire CPi = CLOCK_100;// 实例化PLL模块,将50MHz时钟倍频至100MHz,并输出锁定指示LEDGPLL100M_CP PLL100M_CP_inst (.inclk0(CLOCK_50),.c0(CLOCK_100),.locked(LEDG[0]));// 实例化地址计数器模块,根据频率控制字K和内部时钟CPi生成ROM地址ROMaddr和地址Addressaddr_cnt U0_instance (.CPi(CPi),.K(K),.ROMaddr(ROMaddr),.Address(Address));// 实例化正弦波ROM模块,根据ROM地址ROMaddr和内部时钟CPi输出正弦波形数据QsineSineROM ROM_inst (.address(ROMaddr),.clock(CPi),.q(Qsine));// 实例化方波模块,根据地址Address和内部时钟CPi输出方波形数据Qsquaresquwave U1 (.CPi(CPi),.RSTn(RSTn),.Address(Address),.Qsquare(Qsquare));// 根据波形选择信号WaveSel选择输出的波形类型always @(posedge CPi) begincase (WaveSel)2'b01: WaveValue = Qsine;   // 如果选择正弦波,则输出Qsine2'b10: WaveValue = Qsquare; // 如果选择方波,则输出Qsquaredefault: WaveValue = Qsine; // 默认输出正弦波endcaseendendmodule

部分Ip核配置

配置输出位宽、存储容量和存储器类型等:

http://www.xdnf.cn/news/766531.html

相关文章:

  • [ Qt ] | 与系统相关的操作(一):鼠标相关事件
  • Go整合Redis2.0发布订阅
  • 通过《哪吒》看人生百态
  • 数据结构与算法:图论——拓扑排序
  • GMDCMonitor企业版功能分享0602
  • Qt OpenGL 实现交互功能(如鼠标、键盘操作)
  • leetcode90.子集II:排序与同层去重的回溯优化策略
  • 【leetcode】459.重复的子字符串
  • MyBatis源码解析:从 Mapper 接口到 SQL 执行的完整链路
  • 正则表达式在Java中的应用(补充)
  • 初识CSS3
  • OIer常用的软件
  • 【001】利用github搭建静态网站_essay
  • 并发编程的源头
  • Flink CDC将MySQL数据同步到数据湖
  • C++ 标准输入输出 -- <iostream>
  • 【深度学习新浪潮】多模态模型如何处理任意分辨率输入?
  • LazyOwn RedTeam/APT 框架是第一个具有人工智能驱动的 CC 的 RedTeam 框架
  • 6.linux文本内容显示cat,more,less
  • 第七部分:第五节 - 数据关系与进阶查询 (TypeORM):仓库里复杂的配料组合
  • 第1篇:数据库中间件概述:架构演进、典型方案与应用场景
  • 微服务常用日志追踪方案:Sleuth + Zipkin + ELK
  • SCAU8642--快速排序
  • C++ 内存泄漏检测器设计
  • 7.文本内容处理sort,uniq,out,cat,comm,diff
  • NX869NX874美光固态颗粒NX877NX883
  • [HTML5]快速掌握canvas
  • 在 Linux 服务器上无需 sudo 权限解压/打包 .7z 的方法
  • C++ - 数据处理之数值转不同进制的字符串(数值转十进制字符串、数值转八进制字符串、数值转二进制字符串、数值转十六进制字符串)
  • 黑马程序员C++核心编程笔记--4 类和对象--多态