(DDS)正弦波形发生器——幅值、频率、相位可调(一)
(DDS)正弦波形发生器——幅值、频率、相位可调
一、项目任务:
- 设计一个幅值、频率、相位均可调的正弦波发生器。
- 频率每次增加1kHz。
- 相位每次增加 2*PI/256
- 幅值每次增加两倍
二、文章内容:
- DDS的核心原理。
- 分别使用两种方式完成频率可调(a、b),并且进行对比(c),最后对b进行优化(d)。
- 完成赋值、频率、相位可调的正弦波形发生器。(文章二)
1、DDS核心原理:
读取ROM中存储的波形数据获得一个基础波形(基频),之后不断进行循环读取。
幅值——ROM中取得数据使用乘法进行放大。
相位——改变从ROM中读取时,地址的初值。
调频——ROM时钟固定,控制读取ROM的地址来控制输出频率:
- 系统时钟为50MHz,ROM位宽为8,深度256。
- 有一个思路就是:先确定一个最小的频率,然后不断的对该频率进行放大,即可以控制频率的大小。反过来讲,就是先使用一种最慢的控制地址的读取ROM数据的方式,然后缩小读取的时间即可放大频率。
- 那么如何确定基频的大小呢?根据采取方法不同基频大小不定,但是为了频率的准确有以下几点需要注意:
- 基频小些比较好
- 基频越接近1、0.1、0.01、0.001······越好
2、两种产生基频的方式
a、32位寄存器基频:
使用一个32为寄存器,将高8位作为ROM的地址位,其余24位无特殊作用,寄存器按照系统时钟进行自加,此时高8位的变化频率与用于自加的系统时钟频率有了成倍的关系,很像计数器计数频率变化但存在区别,之后会讨论到。
系统时钟为50MHz,ROM位宽为8,深度256。那么基础频率为:
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaI1aGaaGimaiabgEna0kaaig
% dacaaIWaWaaWbaaSqabeaacaaI2aaaaaGcbaGaaGOmamaaCaaaleqa
% baGaaGOmaiaaisdaaaGccqGHxdaTcaaIYaGaaGynaiaaiAdaaaGaey
% ypa0JaaGimaiaac6cacaaIWaGaaGymaiaaigdacaaI2aGaaGinaiaa
% igdacaaI1aGaamisaiaadQhaaaa!5ABC!
{f_b} = \frac{{50 \times {{10}^6}}}{{{2^{24}} \times 256}} = 0.0116415Hz
\]所以产生一个1kHz的正弦波(1kHz的ROM地址变化速度)只需要将基频放大1000/0.0116415 = 85899.34592,即扩大85899倍。
所以若想频率每次增加1kHz,在低24位每次自加85899即可,此时85899为频率控制字。
module addr_ctrl(
input clk,
input rst_n, output [7:0] addr
); reg [31:0] address;
assign addr = address[31:24]; always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
address <= 32'd0;
else
//address <= address + 32'd858996; //10kHz
address <= address + 32'd85899;
end endmodule

通过仿真可以看到准确的产生了1kHz的正弦波
//顶层
module digital_adds(
input clk,
input rst_n, output [7:0] data
); wire [7:0] addr; addr_ctrl addr_ctrl_inst(
.clk (clk),
.rst_n(rst_n), .addr (addr)
); rom1 rom1_inst (
.address ( addr ),
.clock ( clk ),
.q ( data )
); endmodule
//测试文件
`timescale 1ns/1ps
module digital_adds_tb();
reg clk;
reg rst_n; wire [7:0] data; digital_adds digital_adds_inst(
.clk (clk),
.rst_n(rst_n), .data (data)
); initial clk = 1;
always #10 clk = !clk; initial begin //同步复位信号需要时钟上升沿检测
rst_n = 0;
#200
rst_n = 1;
#5000000 $stop;
end endmodule
b、计数器产生基频计算:
系统时钟为50MHz,ROM位宽为8,深度1024。
使用计数器产生1Hz的基频,方便之后扩频计算
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaIXaGaaGimamaaCaaaleqaba
% GaaGyoaaaaaOqaaiaaikdacaaIWaGaey41aqRaaGymaiaaicdacaaI
% YaGaaGinaiabgEna0kaaisdacaaI4aGaaGioaiaaisdacaaI4aaaai
% abg2da9iaaigdacaGGUaGaaGimaiaaicdacaaIWaGaaGimaiaaicda
% caaIYaGaaGynaiaaiAdacaaIWaGaaGimaiaaicdacaaI2aGaaGynai
% aadIeacaWG6baaaa!6128!
{f_b} = \frac{{{{10}^9}}}{{20 \times 1024 \times 48828}} = 1.0000025600065Hz
\]使用1024个数据,每个为20ns,共计48848次。
则频率控制字为1000时即可产生1kHz的正弦波。
//频率控制模块
module addr_ctrl(
input clk,
input rst_n, output reg [9:0] addr
); reg [15:0] cnt; always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
cnt <= 16'd0;
else
if(16'd48848 <= cnt)
cnt <= 16'd0;
else
cnt <= cnt + 16'd1000; //频率控制字1kHz的基频
end always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
addr <= 10'd0;
else
if(16'd48848 <= cnt)
addr <= addr + 10'd1;
else
addr <= addr;
end endmodule

图中可以看到,得到了977Hz的正弦波,存在着较大的误差,实际效果与理论计算严重不符,这是为什么呢?
c、对比
32位寄存器使用低位向高位进位可以确保每次加的数据都产生了效果,都向高位产生了进位。
相比使用计数器产生的基频很准确但是有着非常致命的缺点:
当频率控制字不能被计数器最大值整除,即当频率控制字即将累加到计数器最大值时,由于不能整除。可能还差一点点计数器就符合判断要求了,即已经非常接近我们预设的地址变化频率了,但是仍然不满足判断标准,必须等到下一次频率控制字的累加才可以使地址加一,这里就产生了理论计算和实际情况不同的问题。
其关键就是计数器进位是严格按照条件执行的少一点会不执行而多一点会被吞掉直接置零,舍弃掉了余数,并且由于cnt的初值从零开始且每次增加1000,说以实际上计算为:
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaIXaGaaGimamaaCaaaleqaba
% GaaGyoaaaaaOqaaiaaikdacaaIWaGaey41aqRaaGymaiaaicdacaaI
% YaGaaGinaiabgEna0kaacIcacaaI0aGaaGyoaiabgUcaRiaaigdaca
% GGPaGaey41aqRaaGymaiaaicdacaaIWaGaaGimaaaacqGH9aqpcaaI
% WaGaaiOlaiaaiMdacaaI3aGaaGOnaiaaiwdacaaI2aGaaGOmaiaaiw
% dacaWGibGaamOEaaaa!6290!
{f} = \frac{{{{10}^{12}}}}{{20 \times 1024 \times (49 + 1) \times 1000}} = 976.5625Hz
\]
该计算结果和仿真得到的结果相同,并且可以看到仿真保留了三位有效数字。
也就是说b法误差有两点原因:
- 忘记对cnt从零开始进行处理。
- 算法本身带来的一定误差。
- 忘记对cnt从零开始进行处理。
而低位向高位进位得到的计数器:
- 从零开始几乎不影响最终效果因为不参与循环不会累计误差
- 不会舍弃余数而是累加了起来
传统方法b还是好用一些,,,
我竟然品出了一点连续和离散的感觉出来。
d、对b进行修改
理论计算:
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaIXaGaaGimamaaCaaaleqaba
% GaaGymaiaaikdaaaaakeaacaaIYaGaaGimaiabgEna0kaaigdacaaI
% WaGaaGOmaiaaisdacqGHxdaTcaaI0aGaaGyoaiabgEna0kaaigdaca
% aIWaGaaGimaiaaicdaaaGaeyypa0JaaGyoaiaaiMdacaaI2aGaaiOl
% aiaaisdacaaI5aGaaGOmaiaaiodacaaI0aGaaGOnaiaaiMdacaWGib
% GaamOEaaaa!61D7!
{f_b} = \frac{{{{10}^{12}}}}{{20 \times 1024 \times 49 \times 1000}} = 996.4923469Hz
\]
可以看到经过改进后:
- 理论计算和仿真验真结果相符。
- 产生波形的频率精度尚可。
module addr_ctrl(
input clk,
input rst_n, output reg [9:0] addr
); reg [15:0] cnt; always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
cnt <= 16'd0;
else
if(16'd48 <= cnt) //只要取倍数就可以了,并且要注意cnt初值为零已经多循环了一次
cnt <= 16'd0;
else
cnt <= cnt + 16'd1; //频率控制字1kHz的基频
end always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
addr <= 10'd0;
else
if(16'd48 <= cnt)
addr <= addr + 10'd1;
else
addr <= addr;
end endmodule
至此文章1、2部分已经完成,第三部分的整体代码见下文
备注:
- ROM可以通过时钟和地址两种控制方式来使用。
- 第一个方法的图中我们可以看到准确的产生1kHz的正弦波,但是基频并非整数,为什么能够准确的产生1kHz的正弦波呢?是在哪一步忽略的,由于没有AD/DA也没有示波器没办法探究真实情况。
作者:野客居/13tree
出处:https://www.cnblogs.com/13tree/
本文版权归作者所有,如需转载请保留此段声明。
(DDS)正弦波形发生器——幅值、频率、相位可调(一)的更多相关文章
- (DDS)正弦波形发生器——幅值、频率、相位可调(二)
(DDS)正弦波形发生器--幅值.频率.相位可调(二) 主要关于调相方面 一.项目任务: 设计一个幅值.频率.相位均可调的正弦波发生器. 频率每次增加10kHz 相位每次增加 PI/2 幅值每次增加两 ...
- 基于FPGA的DDS任意波形发生器设计
一.简介 DDS技术最初是作为频率合成技术提出的,由于其易于控制,相位连续,输出频率稳定度高,分辨率高, 频率转换速度快等优点,现在被广泛应用于任意波形发生器(AWG).基于DDS技术的任 ...
- 基于DDS的任意波形发生器
实验原理 DDS的原理 DDS(Direct Digital Frequency Synthesizer)直接数字频率合成器,也可叫DDFS. DDS是从相位的概念直接合成所需波形的一种频率合成技术. ...
- FFT之频率与幅值的确定(转)
FFT之后得到的是什么数 FFT之后得到的那一串复数是波形对应频率下的幅度特征,注意这个是幅度特征不是复制,下面要讲两个问题:1.如何获取频率,2.如何获取幅值 获取频率 FFT变换如何获取频率?傅里 ...
- STM32 基DMA的DAC波形发生器
DAC是STM32系列的一个基本外设,可以将数字信号转化成模拟信号,这次我将使用DAC来输出一个特定波形. 首先确定工作方法,由于我目前在做的简易示波器在输出波形的同时还需要显示输入信号,所以不能占用 ...
- 在Modelsim波形中查看值
在Modelsim的波形中查看值时,可以利用右键选择变量的数据类型.如果变量值为0,可以选择unsigned类型观察,可以1位显示0. 长度较大的数据以十六进制显示时,即使值为0,也依然显示为长度较长 ...
- Matlab绘制幅值谱和相位谱
1. 对于直接给出频响函数的情况 这里以滑动平均的频响函数作为例子,滑动窗口为[0, 4]. 上式中M2=4. >> w=0:0.001:2*pi; >> h1=1-exp(- ...
- numpy 傅立叶得到幅值和频率
做个备份,对 numpy 不熟,每次都找函数找半天. 代码里分几块: 1. 从 argc[1] 的文档中读取数据,并转化为 float.文档中有 2001 行,第一行为头,后面 2000 个是采样数据 ...
- H5录音音频可视化-实时波形频谱绘制、频率直方图
这段时间给GitHub Recorder开源库添加了两个新的音频可视化功能,比以前单一的动态波形显示丰富了好多(下图后两行是不是比第一行看起来丰满些):趁热打铁写了一个音频可视化相关扩展测试代码,下面 ...
随机推荐
- Redis 实现了自己的 VM
Redis的VM(虚拟内存)机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据). Redis提高数据库容量的办法有两种: 1.一种是可以 ...
- Adaptive gradient descent without descent
目录 概 主要内容 算法1 AdGD 定理1 ADGD-L 算法2 定理2 算法3 ADGD-accel 算法4 Adaptive SGD 定理4 代码 Malitsky Y, Mishchenko ...
- CS5211替代PS8625|设计DP转LVDS转接板|替代PS8625方案
1.CS5211与PS8625功能概述 CS5211是一个eDP到LVDS转换器,配置灵活,适用于低成本显示系统.CS5211与eDP 1.2兼容,支持1通道和2通道模式,每通道速度为1.62Gbps ...
- 编写Java程序,前方有 3km 的道路障碍,4 辆普通车不能通过,必须等到清障车完成作业离开后,才能继续行驶。用程序来模拟这一过程的发生
查看本章节 查看作业目录 需求说明: 前方有 3km 的道路障碍,4 辆普通车不能通过,必须等到清障车完成作业离开后,才能继续行驶.用程序来模拟这一过程的发生 实现思路: 创建清障车Wrecker类和 ...
- RabbitMQ开启SSL与SpringBoot连接测试
楔子 近期公司程序被安全扫描出 远程主机允许明文身份验证 中风险漏洞,查了下修复方案,RabbitMQ官方提供了SSL连接方式,而且 SpringBoot AMQP 也支持 SSL 连接.以下将配置R ...
- 远程连接PostgreSQL
在华为云上安装了PostgreSQL,本地使用pgAdmin客户端来访问PostgreSQL 首先,需要在华为云服务器上,放开访问PostgreSQL的5432端口,否则会报请求超时 通过创建安全组来 ...
- vs2017 快捷键 - 总结
1.格式化代码 先选中需要格式的代码,一般是全选[Ctrl+A]后,Ctrl+K+F[按定Ctrl不动,依序点击 K和F,然后再放开 Ctrl ] 2.多行注释 注释: 先CTRL+K,然后CTRL+ ...
- 学习笔记--Java字面值
Java 字面值 /** * 关于字面值: * * - 字面值:10.100."abc"."a".true.false * * - 字面值就是数据 * * - ...
- JQuery iframe 刷新效果
假如有一个选项卡.tab-content,里面有多个iframe 只刷新显示的那个iframe,所以要用到:visible $('.tab-content iframe:visible')[0].co ...
- sql审核-避免离线sql导致的db集群故障
关键词: sql审核.sql审批.sql检查.sql检测.sql执行 离线sql可能会导致的问题 首先,什么是离线sql呢?就是说手动触发执行的这种sql:相对的还有在线sql,位于我们的程序代码中, ...