LinCoding

【原创】详细解析基于FPGA(DDS)的正弦波发生器

1
阅读(14503)

【主题】:详细解析基于FPGA(DDS)的正弦波发生器

【作者】:LinCoding

【时间】:2016.12.27

【声明】:转载、引用,请注明出处

       我们都知道FPGA是一个数字器件,那么在通信领域,很多时候,要产生正弦波信号作为调制信号。在FPGA中产生正弦波一般用的是:DDS。

       DDS(Direct Dignal Synthesizer):即直接数字式频率合成器。

       DDS的主要组成部分:频率计数器(相位累加器)、相位调制器、波形数据表、DAC和低通滤波器组成,如下图所示。

图片.png

       DDS原理: 首先ROM中要存放好要显示的正弦波数据。然后由频率计数器一直累加,根据这个累加器的值作为ROM的地址,然后DAC根据ROM输出的数据输出相应的电压值,但是由于数字的离散性,DA输出的也将是离散的电压值,也就构成了不平滑的正弦波,这时,只需在后级加一个低通滤波器就可以输出完美的正弦波。

      先看图:正弦波

图片.png  

     但是,DDS原理中最麻烦的是实现频率可调的原理:下面笔者一一道来。

     首先我们要考虑以下两个问题:

      1、相位累加器(计数器)的位宽是多少?

      2、ROM的数据位宽和深度(深度:2^地址位宽)是多少?

      第一个问题:相位累加器的位宽为24~32位,一般选32位,至于为什么笔者也不清楚,网上也没找到答案,可能是经验吧,这样的话频率可调的范围可满足大部分应用。

      第二个问题:

      (1)ROM的数据位宽选择要看DAC模块,比如我的DAC模块的数据输入数据范围是0~1023,那么我的ROM的数据位宽就要选择为10位;

      (2)ROM的深度也取决于你DAC模块,因为ROM中只能存储整数,这样的话,即使ROM每相邻单元存储的间隔是1(也就是1,2,3,4,5……),这样的话由于最大只能到1023,而这1024个数据仅仅为半个周期,一个周期的话就是2048,所以标准ROM的深度是2^(DAC数据位宽)*2。但这是可变的,我们可以每两个点存储相同的数字(比如1,1,2,2,3,3,4,4,5,5,……)这样的话ROM深度也可以是4096,当然我也可以间隔一个点存储(比如1,3,5,7,9,……),这样我的ROM深度是1024,所以这么说来ROM的深度是灵活的,但数据位宽一定是确定的。这里我使用2048个数据作为一个周期。

       搞清楚这两个问题以后,就是如何实现频率可调了?

       好多人确实感觉这里不好理解,但是其实这很简单,就是玩了个数字游戏,使用低位来改变高位的变化速度。举例如下:

//-----------------------------------
//phase adder
reg    [10:0]    fre_cnt;
always @ ( posedge clk or negedge rst_n )
begin
    if ( ! rst_n )
        fre_cnt        <= 11'd0;
    else if ( DDS_en )
        fre_cnt        <= fre_cnt + 1'b1;
    else
        fre_cnt        <= 11'd0;
end

       这就是所谓的相位累加器,在DDS_en使能以后就一直计数,直到计满,然后重新又开始计数,这里我们的clk是系统时钟50Mhz,周期就是20ns。

DDS_rom u_DDS_ddsrom
(    
    .clock            (clk),
    .address          (fre_cnt),
    .q                (DAC_data)
);

       然后,我们把计数的值作为ROM的地址送给ROM,ROM输出相应的正弦波数据,这时,会将2048个点全部输出。而2048个点全部输出需要2048*20ns=40960ns,1/(40960ns)=24414.0625Hz,这就是DDS的基本频率,我们将其称为基频。

      好了,下面开始改变频率了。

     (1)首先我想将频率翻倍,我们可以这样:

//-----------------------------------
//phase adder
reg    [10:0]    fre_cnt;
always @ ( posedge clk or negedge rst_n )       //clk为50Mhz
begin
    if ( ! rst_n )
        fre_cnt        <= 11'd0;
    else if ( DDS_en )
        fre_cnt        <= fre_cnt + 2'd2;
    else
        fre_cnt        <= 11'd0;
end

DDS_rom u_DDS_ddsrom
(    
    .clock            (clk),
    .address          (fre_cnt),
    .q                (DAC_data)
);

      这样的话,由于clk仍为50Mhz,计数器周期还是20ns,但是我们相当于将ROM中数据隔点输出,一共只输出了1024个点,这样的话输出波形的周期是1024*20ns=20480ns,频率是1/(20480ns)=48848.125Hz,轻松实现了频率翻倍。当然我这里为了大家理解选择了整数倍,当然1.3倍,1,6倍是同样的道理,也就是改变计数器的计数间隔。

     (2)下面我们将频率减半,频率减半的原理就是每两个20ns(每40ns)输出相同的数据,但由于此时我们无法再减小计数器的计数间隔了(已经是1了),因此迫不得已必须改变计数的时钟,我们我可以这样:

//-----------------------------------
//phase adder
reg    [10:0]    fre_cnt;
always @ ( posedge clk_ref or negedge rst_n )    //clk_ref为25Mhz
begin
    if ( ! rst_n )
        fre_cnt        <= 11'd0;
    else if ( DDS_en )
        fre_cnt        <= fre_cnt + 1'b1;
    else
        fre_cnt        <= 11'd0;
end

DDS_rom u_DDS_ddsrom
(    
    .clock            (clk),
    .address          (fre_cnt),
    .q                (DAC_data)
);

       代码和第一个代码一样,但是clk我使用的是经过PLL的25Mhz的clk_ref,这样的话周期就是40ns,在40ns内将2048个点输出,这样的话正弦波周期为2048*40ns=81920ns,频率为1/(81920ns)=12207.0315Hz,任务完成。


     但是,如果这样一来,在基频的基础上,频率增大和较小的代码不一样,而且需要改变计数器的clk,这是我们无法容忍的。因此改良代码如下:

//-----------------------------------
//phase adder
reg    [31:0]    fre_cnt;
always @ ( posedge clk or negedge rst_n )    //clk为50Mhz
begin
    if ( ! rst_n )
        fre_cnt        <= 32'd0;
    else if ( DDS_en )
        fre_cnt        <= fre_cnt + fre_value;
    else
        fre_cnt        <= 32'd0;
end

wire    [11:0]    rom_addr    = fre_cnt[31:20];

DDS_rom u_DDS_ddsrom
(    
    .clock            (clk),
    .address          (rom_addr),
    .q                (DAC_data)
);

      首先,这时,clk为固定50Mhz,fre_value我们称为频率控制字,如果fre_value是1的话,每次要计数满20'h1_00000之后,rom_addr才会增加1,也就是说每20'h1_00000*20ns,ROM的输出值才会变化,这样一来,周期为20'h1_00000*20ns*2048=42.94967296s,频率为1/43s≈0.023283Hz,这也就是DDS可以达到的最小频率,并且DDS可达到的所有频率也均为这个最小频率的整数倍。

      如果fre_value等于20'h1_00000的话,这样每20ns,ROM可改变一次输出值,达到的就是我们的基频,这里就不再计算了。

       那么DDS最大的频率是多少呢?我们可以让fre_value等于32'h8_000_0000,这样的话ROM只会输出第一个值,最中间的值和最后一个值,频率为1/40ns=25Mhz,但是这三个点的ROM数据都为0,因此波形已经是一条直线了。

     

      最后,为了完善DDS,我们可以给他增加一个调节相位的功能。

//-----------------------------------
//phase adder
reg    [31:0]    fre_cnt;
always @ ( posedge clk or negedge rst_n )    //clk为50Mhz
begin
    if ( ! rst_n )
        fre_cnt        <= 32'd0;
    else if ( DDS_en )
        fre_cnt        <= fre_cnt + fre_value;
    else
        fre_cnt        <= 32'd0;
end

wire    [11:0]    rom_addr    = fre_cnt[31:20] + pha_value;

DDS_rom u_DDS_ddsrom
(    
    .clock            (clk),
    .address          (rom_addr),
    .q                (DAC_data)
);

      其实很简单,就是加了个pha_value相位控制字。它的位宽需与DAC模块的数据位宽相同。

     

      总结:

      1、频率计数器的位宽为24~32位,一般选32位;

      2、频率控制字位宽与频率计数器位宽相同;

      3、ROM的数据位宽选择取决于DAC模块(8位DAC,则ROM输出数据位宽为[7:0]);

      4、ROM的深度(2^地址位宽)有标准深度,但可任意;

      5、相位控制字位宽选择取决于ROM深度2^地址位宽