(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开源库添加了两个新的音频可视化功能,比以前单一的动态波形显示丰富了好多(下图后两行是不是比第一行看起来丰满些):趁热打铁写了一个音频可视化相关扩展测试代码,下面 ...
随机推荐
- Attention Is All You Need
目录 概 主要内容 Positional Encoding auto_regressive 额外的细节 代码 Vaswani A., Shazeer N., Parmar N., Uszkoreit ...
- vue项目发布后,线上运行时刷新404
修改nginx配置文件 location / { root ... index ... try_files $uri $uri/ /index.html; ---解决页面刷新404问题 } (参考官网 ...
- RTD2172方案|TYPEC转HDMI2.0转换器芯片|CS5265替代RTD2172
RTD2172 USB Type-C到HDMI转换器结合了USB Type-C输入接口和数字高清多媒体接口(HDMI)输出.嵌入式微控制器(MCU)基于工业标准8051内核.接收器端口将信道配置(CC ...
- C语言string操作
创建方式 字符数组:空间已定 字符指针:未分配空间 初始化 字符数组: 创建与赋值必须在同一行 指定大小:未填满部分用'\0'填充 用字符串初始化:末尾自动添加'\0' 不初始化赋值则乱值 字符指针: ...
- 基于Spring MVC + Spring + MyBatis的【超市会员管理系统】
资源下载: https://download.csdn.net/download/weixin_44893902/22035329 一. 语言和环境 实现语言:JAVA语言. 使用:MyEclipse ...
- emqx的acl.conf使用
allow_anonymous=true就不说了,打开这个就像开了挂 现在讨论一下allow_anonymous=false,这样的话你会发现,client连接不上了 后来发现连接和权限 是两个事.. ...
- 实践剖析.NET Core如何支持Cookie和JWT混合认证、授权
前言 为防止JWT Token被窃取,我们将Token置于Cookie中,但若与第三方对接,调用我方接口进行认证.授权此时仍需将Token置于请求头,通过实践并联系理论,我们继续开始整活!首先我们实现 ...
- 基于GO语言实现的固定长度邀请码
1. 选取数字加英文字母组成32个字符的字符串,用于表示32进制数. 2. 用一个特定的字符比如`G`作为分隔符,解析的时候字符`G`后面的字符不参与运算. 3. LEN表示邀请码长度,默认为6. g ...
- LCA/在线(倍增)离线(Tarjan)
概念 祖先 公共祖先 最近公共祖先 方法1:暴力爬山法 方法2:倍增 求公共祖先 求俩点的距离 Tarjan 概念 祖先 有根树中,一个节点到根的路径上的所有节点被视为这个点的祖先,包括根和它本身 公 ...
- oracle 之 数组、嵌套表、SQL查询式 实现多表数据for循环插入指定表
1.基础环境 创建基础表: CREATE TABLE TEST_TAB1( ID INT, NAME VARCHAR2(20) ); CREATE TABLE TEST_TAB2( ID INT, N ...