(DDS)正弦波形發生器——幅值、頻率、相位可調(一)


(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。那么基礎頻率為:

    \[% 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從零開始進行處理。
      • 算法本身帶來的一定誤差。
  • 而低位向高位進位得到的計數器:

    • 從零開始幾乎不影響最終效果因為不參與循環不會累計誤差
    • 不會舍棄余數而是累加了起來
  • 傳統方法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/

本文版權歸作者所有,如需轉載請保留此段聲明。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM