(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开源库添加了两个新的音频可视化功能,比以前单一的动态波形显示丰富了好多(下图后两行是不是比第一行看起来丰满些):趁热打铁写了一个音频可视化相关扩展测试代码,下面 ...
随机推荐
- gojs 实用高级用法
大家,新年好! 历史文章: 数据可视化 gojs 简单使用介绍 gojs 如何实现虚线(蚂蚁线)动画? 本文介绍的是在使用 gojs 制作图的过程中,你可能会碰到的问题的一些解决方案. gojs 是一 ...
- 【MySQL作业】DDL 和 DML——美和易思使用 DML 新增和更新表数据应用习题
点击打开所使用到的数据库>>> 1.添加 easyShopping 客户数据. insert into customer values('abc111','111',' 刘一鸣 ', ...
- C#WPF数据绑定模板化操作四步走
前言:WPF数据绑定对于WPF应用程序来说尤为重要,本文将讲述使用MVVM模式进行数据绑定的四步走用法: 具体实例代码如下: 以下代码仅供参考,如有问题请在评论区留言,谢谢 1 第一步:声明一个类用来 ...
- 使用.NET 6开发TodoList应用(19)——处理OPTION和HEAD请求
系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在HTTP请求中,我们还剩下两类不常使用的请求没有讲到,本文就来实现以下关于OPTIONS和HEAD请求.OPTIONS请求用 ...
- MYSQL 自定义序列函数
代码如下: DROP TABLE SEQNUM; -- 创建序列表 CREATE TABLE SEQNUM( ID BIGINT ); -- 插入初识值 insert INTO SEQNUM valu ...
- Jenkins_构建任务提示找不到命令的处理方法
问题现象 部署pytest环境后,在linux上能执行命令,但是使用jenkins构建就提示找不到命令. 问题分析 可能是...jenkins中执行时,默认使用的是linux中的jenkins用户权限 ...
- 不用下载Axure RP Extension for Chrome插件查看原型文件的方法
Axure RP Extension for Chrome是一款谷歌插件,主要可以用来查看原型文件.以前安装插件的时候总是找半天资源,很麻烦,最近发现了一种新方法可以不用下载插件资源.其实在原型文件中 ...
- C# string.Format 和 String.Format 的区别
string.Format 和 String.Format ,不论是用法还是意思,都是一样的 怎么使用? 通过 占位符来替换 ,类似于 Replace 的操作 string s = string.F ...
- 用Less 的 js方式替代 bootStrap 里 [class*=”span”]
Bootstrap 里的 grid system 里面 (源代码) 有这么一段, [class*="span"] { float: left; margin-left: @grid ...
- 微服务架构 | 3.2 Alibaba Nacos 注册中心
目录 前言 1. Nacos 基础知识 1.1 Nacos 命名方式 1.2 Nasoc 是什么 1.3 Nacos 的 4 个关键特性 1.4 Nacos 生态图 1.5 Nacos 架构图 1.6 ...