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

news2025/6/7 1:47:17

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 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); % 写完了,关闭文件

  

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); % 写完,关闭文件

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输出全0

        if (!RSTn) begin

            Qsquare = 12'h000;  // 复位时输出12位全0

        end

        // 正常工作时,根据Address的值设置Qsquare的输出

        else begin

            // 如果Address小于等于17'h0FFFF(即十进制的131071),Qsquare输出全1

            if (Address <= 17'h0FFFF) begin

                Qsquare = 12'hFFF;  // 输出12位全1

            end

            // 否则,Qsquare输出全0

            else begin

                Qsquare = 12'h000;  // 输出12位全0

            end

        end

    end

endmodule

该代码定义了一个名为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) begin

        q <= mem[address];  // 根据输入地址,从存储器数组中读取对应的正弦波形数据,并赋值给输出q

    end

endmodule



// 定义一个名为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) begin

        q <= mem[address];  // 根据输入地址,从存储器数组中读取对应的方波形数据,并赋值给输出q

    end

endmodule

这两个模块分别实现了正弦波和方波的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,并输出锁定指示LEDG

    PLL100M_CP PLL100M_CP_inst (

        .inclk0(CLOCK_50),

        .c0(CLOCK_100),

        .locked(LEDG[0])

    );

    // 实例化地址计数器模块,根据频率控制字K和内部时钟CPi生成ROM地址ROMaddr和地址Address

    addr_cnt U0_instance (

        .CPi(CPi),

        .K(K),

        .ROMaddr(ROMaddr),

        .Address(Address)

    );

    // 实例化正弦波ROM模块,根据ROM地址ROMaddr和内部时钟CPi输出正弦波形数据Qsine

    SineROM ROM_inst (

        .address(ROMaddr),

        .clock(CPi),

        .q(Qsine)

    );

    // 实例化方波模块,根据地址Address和内部时钟CPi输出方波形数据Qsquare

    squwave U1 (

        .CPi(CPi),

        .RSTn(RSTn),

        .Address(Address),

        .Qsquare(Qsquare)

    );

    // 根据波形选择信号WaveSel选择输出的波形类型

    always @(posedge CPi) begin

        case (WaveSel)

            2'b01: WaveValue = Qsine;   // 如果选择正弦波,则输出Qsine

            2'b10: WaveValue = Qsquare; // 如果选择方波,则输出Qsquare

            default: WaveValue = Qsine; // 默认输出正弦波

        endcase

    end

endmodule

部分Ip核配置

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

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

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

相关文章

鸿蒙UI(ArkUI-方舟UI框架)- 使用弹框

返回主章节 → 鸿蒙UI&#xff08;ArkUI-方舟UI框架&#xff09; 文章目录 弹框概述使用弹出框(Dialog)弹出框概述不依赖UI组件的全局自定义弹出框(openCustomDialog)(推荐)生命周期自定义弹出框的打开与关闭更新自定义弹出框内容更新自定义弹出框的属性完整示例 基础自定义弹…

学习笔记(24): 机器学习之数据预处理Pandas和转换成张量格式[2]

学习笔记(24): 机器学习之数据预处理Pandas和转换成张量格式[2] 学习机器学习&#xff0c;需要学习如何预处理原始数据&#xff0c;这里用到pandas&#xff0c;将原始数据转换为张量格式的数据。 学习笔记(23): 机器学习之数据预处理Pandas和转换成张量格式[1]-CSDN博客 下面…

在不同型号的手机或平板上后台运行Aidlux

在不同型号的手机或平板上后台运行Aidlux 一、鸿蒙/HarmonyOS手机与平板 二、小米手机与平板 三、OPPO手机与平板 四、vivo手机与平板 一、鸿蒙/HarmonyOS手机与平板 &#xff08;系统版本有差异&#xff0c;但操作原理相通&#xff09; 第一步&#xff1a;点击设置——应用和…

【SSM】SpringBoot学习笔记1:SpringBoot快速入门

前言&#xff1a; 文章是系列学习笔记第9篇。基于黑马程序员课程完成&#xff0c;是笔者的学习笔记与心得总结&#xff0c;供自己和他人参考。笔记大部分是对黑马视频的归纳&#xff0c;少部分自己的理解&#xff0c;微量ai解释的内容&#xff08;ai部分会标出&#xff09;。 …

1.企业可观测性监控三大支柱及开源方案的横评对比

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] &#x1f4e2; 大家好&#xff0c;我是 WeiyiGeek&#xff0c;一名深耕安全运维开发&#xff08;SecOpsDev&#xff09;领域的技术从业者&#xff0c;致力于探索DevOps与安全的融合&#xff08;De…

双空间知识蒸馏用于大语言模型

Dual-Space Knowledge Distillation for Large Language Models 发表&#xff1a;EMNLP 2024 机构&#xff1a;Beijing Key Lab of Traffic Data Analysis and Mining 连接&#xff1a;https://aclanthology.org/2024.emnlp-main.1010.pdf 代码&#xff1a;GitHub - songmz…

OpenCV CUDA模块特征检测------角点检测的接口createMinEigenValCorner()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数创建一个 基于最小特征值&#xff08;Minimum Eigenvalue&#xff09;的角点响应计算对象&#xff0c;这是另一种经典的角点检测方法&…

8天Python从入门到精通【itheima】-69~70(字符串的常见定义和操作+案例练习)

目录 69节-字符串的定义和操作 1.学习目标 2.数据容器视角下的字符串 3.字符串的下标索引 4.字符串是一个无法修改的数据容器 5.字符串的常用操作 【1】index方法 【2】replace方法&#xff1a;进过替换&#xff0c;得到一个新的字符串 【3】split方法&#xff1a;将字…

GC1809:高性能音频接收与转换芯片

GC1809 是一款高性能音频接收与转换芯片&#xff0c;适用于多种音频设备&#xff0c;如 A/V 接收器、多媒体音响设备、机顶盒等。本文将简要介绍该芯片的主要特性、性能参数及应用。 主要特性 多协议兼容&#xff1a;兼容 IEC60958、S/PDIF、EIAJ CP1201 和 AES3 协议。 多种…

项目实战——C语言扫雷游戏

这是一款9*9的扫雷游戏 扫雷游戏 1.需求分析2.程序框架设计3.分函数实现打印游戏菜单界面游戏主逻辑函数程序主入口初始化游戏棋盘随机布置地雷显示当前棋盘状态计算指定位置周围的地雷数量玩家排雷主逻辑 4.分文件实现&#xff08;1&#xff09;test.c&#xff08;2&#xff0…

【Java】CopyOnWriteArrayList

一&#xff0c;概述 CopyOnWriteArrayList作为List接口的实现之一&#xff0c;它区分于ArrayList在于它是线程安全的。如它名字一样&#xff0c;所有的写操作均复制了原数组的值&#xff0c;虽说代价较大&#xff0c;但读多写少的环境下&#xff0c;是可接受的。笔者在此简单看…

C#入门学习笔记 #8(委托)

欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。 笔记内容会持续更新~~ 本章介绍C#中的委托,本章难度较大... 委托 C#中的委托是C语言、C++中函数指针的升级版。接下来介绍一个概念—…

CSS 3D 变换中z-index失效问题

CSS 3D 变换中 z-index 失效问题 1. z-index 失效了 在 CSS 中&#xff0c;z-index 通常用于控制元素的层叠顺序&#xff0c;数值越大&#xff0c;元素越靠前显示。在 3D 变换&#xff08;如 rotateX、translateZ&#xff09; 中使用 z-index 时&#xff0c;可能会发现z-inde…

Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(七):消息框交互功能添加

Tailwind CSS 实战&#xff0c;基于Kooboo构建AI对话框页面&#xff08;一&#xff09; Tailwind CSS 实战&#xff0c;基于Kooboo构建AI对话框页面&#xff08;二&#xff09;&#xff1a;实现交互功能 Tailwind CSS 实战&#xff0c;基于 Kooboo 构建 AI 对话框页面&#x…

【计算机网络】网络层IP协议与子网划分详解:从主机通信到网络设计的底层逻辑

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a; 【计算机网络】传输层TCP协议——协议段格式、三次握手四次挥手、超时重传、滑动窗口、流量控制、 &…

基于WSL搭建Ubnutu 20.04.6 LTS(二)-部署Docker环境

Docker是一组平台即服务&#xff08;PaaS&#xff09;的产品。它基于操作系统层级的虚拟化技术&#xff0c;将软件与其依赖项打包为容器。托管容器的软件称为Docker引擎。Docker能够帮助开发者在轻量级容器中自动部署应用程序&#xff0c;并使得不同容器中的应用程序彼此隔离&a…

【图像处理入门】6. 频域图像处理:傅里叶变换与滤波的奥秘

摘要 频域图像处理通过傅里叶变换将图像从空间域转换到频率域,为图像增强、去噪、压缩等任务提供全新视角。本文将深入解析傅里叶变换原理,介绍低通、高通滤波的实现方式,结合OpenCV和Python代码展示频域滤波在去除噪声、增强边缘中的应用,帮助读者掌握图像频域处理的核心…

基于开源AI智能名片链动2+1模式S2B2C商城小程序的生态农庄留存运营策略研究

摘要&#xff1a;本文聚焦于生态农庄运营中的游客留存问题&#xff0c;以村长与乡亲们吸引游客进村为背景&#xff0c;深入探讨如何借助开源AI智能名片链动2 1模式S2B2C商城小程序实现游客的有效留存。通过分析该小程序在信息传递、服务整合、营销激励等方面的优势&#xff0c…

Jenkins实现自动化部署Springboot项目到Docker容器(Jenkinsfile)

Jenkins实现自动化部署Springboot项目到Docker容器 引言:为什么需要自动化部署? 在软件开发中,频繁的手动部署既耗时又容易出错。通过 Docker + Jenkins + Git 的组合,您可以实现: ✅ 一键部署:代码推送后自动构建和部署🐳 环境一致性:Docker 确保开发、测试、生产环…

【Linux】Git原理与使用

编程不仅是解决问题的艺术&#xff0c;更是对复杂性进行优雅管理的哲学。 前言 这是我自己学习Linux系统编程的第三篇笔记。后期我会继续把Linux系统编程笔记开源至博客上。 上一期笔记是关于Vim文本编辑器知识&#xff1a; 【Linux】Vim文本编辑器-CSDN博客https://blog.csdn…