jjzjj

(DDS)正弦波形发生器——幅值、频率、相位可调(一)

野客居 2023-11-06 原文

(DDS)正弦波形发生器——幅值、频率、相位可调(一)

一、项目任务:

  • 设计一个幅值、频率、相位均可调的正弦波发生器。
    • 频率每次增加1kHz。
    • 相位每次增加 2*PI/256
    • 幅值每次增加两倍

二、文章内容:

  1. DDS的核心原理。
  2. 分别使用两种方式完成频率可调(a、b),并且进行对比(c),最后对b进行优化(d)。
  3. 完成赋值、频率、相位可调的正弦波形发生器。(见文章二)

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。那么基础频率为:
    f b = 50 × 10 6 2 24 × 256 = 0.0116415 H z % 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 fb=224×25650×106=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的基频,方便之后扩频计算
    f b = 10 9 20 × 1024 × 48828 = 1.0000025600065 H z % 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 fb=20×1024×48828109=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,说以实际上计算为:
      f = 10 12 20 × 1024 × ( 49 + 1 ) × 1000 = 976.5625 H z % 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 f=20×1024×(49+1)×10001012=976.5625Hz

  • 该计算结果和仿真得到的结果相同,并且可以看到仿真保留了三位有效数字。

  • 也就是说b法误差有两点原因:

    • 忘记对cnt从零开始进行处理。
      • 算法本身带来的一定误差。
  • 而低位向高位进位得到的计数器:

    • 从零开始几乎不影响最终效果因为不参与循环不会累计误差
    • 不会舍弃余数而是累加了起来
  • 传统方法b还是好用一些,,,

  • 我竟然品出了一点连续和离散的感觉出来。


d、对b进行修改
  • 理论计算:
    f b = 10 12 20 × 1024 × 49 × 1000 = 996.4923469 H z % 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 fb=20×1024×49×10001012=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也没有示波器没办法探究真实情况。

作者:野客居

出处:https://blog.csdn.net/weixin_41890387

本文版权归作者所有,如需转载请保留此段声明。

有关(DDS)正弦波形发生器——幅值、频率、相位可调(一)的更多相关文章

  1. ruby - Ruby 中的波形可视化 - 2

    我即将开始一个将录制和编辑音频文件的项目,我正在寻找一个好的库(最好是Ruby,但会考虑Java或.NET以外的任何库)以进行实时可视化波形。有人知道我应该从哪里开始搜索吗? 最佳答案 要流入浏览器的数据量很大。Flash或Flex图表可能是唯一能提高内存效率的解决方案。Javascript图表往往会因大型数据集而崩溃。 关于ruby-Ruby中的波形可视化,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

  2. ruby-on-rails - 启用 Rack::Deflater 时 ETag 发生变化 - 2

    在启用Rack::Deflater来gzip我的响应主体时偶然发现了一些奇怪的东西。也许我遗漏了一些东西,但启用此功能后,响应被压缩,但是资源的ETag在每个请求上都会发生变化。这会强制应用程序每次都响应,而不是发送304。这在没有启用Rack::Deflater的情况下有效,我已经验证页面源没有改变。我正在运行一个使用thin作为Web服务器的Rails应用程序。Gemfile.lockhttps://gist.github.com/2510816有没有什么方法可以让我从Rack中间件获得更多的输出,这样我就可以看到发生了什么?提前致谢。 最佳答案

  3. ruby - 当 attr_accessor 在类方法中时会发生什么? - 2

    所以我想到了这个,想知道当下面的一些事情完成后会发生什么。classTestdefself.abcattr_accessor:Johnendendobject=Test.newputs"beforecallingclassmethodabc:#{object.class.instance_methods(false)}"Test.abcputs"aftercallingclassmethodabc:#{object.class.instance_methods(false)}"这里我检查的是,getter和setter方法是否以这种方式创建。如果是这样,是那些实例方法或类方法。首先我创

  4. ruby - 当你有一个没有参数的 case 语句并且 when 子句是 lambda 时会发生什么? - 2

    这段代码没有像我预期的那样执行:casewhen->{false}then"why?"else"ThisiswhatIexpect"end#=>"why?"这也不是casewhen->(x){false}then"why?"else"ThisiswhatIexpect"end#=>"why?"第一个then子句在两种情况下都被执行,这意味着我提供给when子句的lambda没有被调用。我知道无论when子句的主题是什么,都应该调用大小写相等运算符===。我想知道当没有为case提供参数时,===的另一边会发生什么。我在想它可能是nil,但它不可能是:->{false}===nil#=>

  5. ruby-on-rails - 获取最近发生的星期三? - 2

    如何使用Ruby(和Rails,如果有相关的辅助方法)获取最近发生的星期三?最终需要实际日期(5/1/2013)。 最佳答案 time=Time.nowdays_to_go_back=(time.wday+4)%7last_wed=days_to_go_back.days.ago 关于ruby-on-rails-获取最近发生的星期三?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions

  6. ruby - 按数组中出现的频率排序 - 2

    有没有一种有效的方法来做到这一点。我有一个数组a=[1,2,2,3,1,2]我想按升序输出出现的频率。示例[[3,1],[1,2],[2,3]]这是我的ruby​​代码。b=a.group_by{|x|x}out={}b.eachdo|k,v|out[k]=v.sizeendout.sort_by{|k,v|v} 最佳答案 a=[1,2,2,3,1,2]a.each_with_object(Hash.new(0)){|m,h|h[m]+=1}.sort_by{|k,v|v}#=>[[3,1],[1,2],[2,3]]

  7. ruby-on-rails - 发生异常时发送电子邮件不起作用,使用 exception_notification - 2

    我正在从rails2.3迁移到rails3.1,我试图在生成异常时发送电子邮件。我正在使用exception_notificationgem。我的其余电子邮件都在工作。但是异常邮件不会被解雇。以下是我的staging.rb文件中的设置。config.action_mailer.perform_deliveries=trueconfig.action_mailer.raise_delivery_errors=true下面是application.rb中的代码C::Application.config.middleware.useExceptionNotification::Rack,:e

  8. 一个非常明显的现象,正在发生——元宇宙正在被越来越多的人所推崇 - 2

      一个非常明显的现象,正在发生——元宇宙正在被越来越多的人所推崇,无论是科技巨头,还是资本巨头,几乎都是如此。同时,区块链则正在一点一点地回归理性与客观。对于区块链来讲,这是一个好现象。它告诉我们,人们对于区块链的狂热而激进的认识,正在被一步又一步的校正和纠偏。由此,区块链行业的发展,将会真正进入到一个全新的发展阶段。  同以往人们仅仅只是将区块链看成是一个概念,并以此来获取资本和流量不同。当人们对于区块链的认识变得深入,资本和流量反倒不再是区块链玩家们真正关心的问题。至少从当下情况来看,那些依然还在区块链行业里坚守的玩家们,更多地在坚持长期主义,更多地在寻求区块链与行业结合的正确的方式和方

  9. ruby - 在 Ruby 中声明 "private"/"protected"时实际发生了什么? - 2

    在Ruby类定义中声明private/protected时实际发生了什么?他们不是keywords,这意味着它们必须是方法调用,但我找不到它们的定义位置。它们似乎没有记录在案。声明private/protected方法(如下所示)的两种不同实现方式是否不同?(第二种方式显然是方法调用,但在第一种方式中并不那么明显。)classFooprivatedefi_am_private;enddefso_am_i;endendclassFoodefi_am_private;enddefso_am_i;endprivate:i_am_private,:so_am_iend

  10. ruby - 处理在 keyup 事件上发生的 javascript 弹出窗口 - 2

    我在HTML页面上有一个文本字段,用于检查您是否输入了1到365之间的值。如果用户输入了无效值,如非数字字符或不在范围内的值,它显示一个弹出窗口。我在watirwiki上看到有一个select_no_wait方法,用于在您从列表中选择无效值时关闭弹出窗口。处理键盘事件时出现的弹出窗口的好方法是什么?我是否需要按照select_no_wait方法的实现方式进行操作,或者我们是否可以启动一个不同的进程来消除调用set方法时可能出现的弹出窗口。带有Javascript验证函数的HTML文件示例如下:varnum=0functionvalidate(e){varcharPressed=Stri

随机推荐