基于FPGA的DDS信号发生器的设计与实现

一、实现环境
  软件:Quartus II 13.0
  硬件:MP801

二、DDS基本原理
  DDS(Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有相对带宽大,频率转换时间短,分辨率高和相位连续性好等优点。较容易实现频率、相位及幅度的数控调制,广泛应用于通信领域。DDS的实现示意图如下图所示:

  1、将需要合成的信号的数据存储在rom中,合成待输出信号的方法请参考:https://www.cnblogs.com/qidaiymm/p/5692253.html

  2、dds_control实现的功能是将存储在rom中的待合成的信号的数据按照一定的规则取出来:

    dds_control主要由相位累加和频率累加来实现,简单的说,通过控制相位累加和频率累加来实现从rom中取出不同时刻的数据。

    (1)相位累加器位数为N位(24~32),相位累加器把正弦信号在相位上的精度定义为N位,其分辨率位1/2N ,决定一个波形的起始时刻在哪个点;

    (2)频率累加器用来控制每隔几个点从rom中取一个数据,决定一个波形的频率;

    (3)若DDS的时钟频率为Fclk ,频率控制字fword = 1,则输出频率为 Fout = Fclk/2N,这个频率相当于“基频”,若fword = B,则输出频率  Fout = B * Fclk/2N。因此理论上由以上三个参数就可以得出任意的 f输出频率,且可以得出频率分辨率由时钟频率和累加器的位数决定的结论。当参考时钟频率越高,累加器位数越高,输出频率分辨率就越高。

  3、从FPGA中出来的信号都是数字信号(dds_control输出的信号都为数字信号),需要通过dac芯片来将数字信号转换为模拟信号,这样将dac芯片输出的信号接入到示波器中,才能看到波形;

  4、举例说明频率控制和相位控制:

    如上图所示,这个是一个由33个点构成的正弦波信号,(rom_addr,rom_data),纵坐标为存储在rom中的正弦波信号,横坐标为 dds_control 生成的地址信号。fre_acc = fre_acc + fword,当 fword = 1 时,fre_acc 则会将从 1 -33 全部地址取到(如 1、2、3、4、5、6......33),而将对应的纵坐标的点全部输出到DAC芯片,假设此时DAC芯片输出的正弦波信号频率为  f。那么,当 fword = 2,则每隔一个地址单位输出一个存储在rom中的正弦波数据(如1、3、5、7、9......33),此时DAC芯片输出的正弦波信号的频率则变为 2f。

    相位累加器则是将整个波形按照一定的地址信号的位置向右平移来改变相位,体现在数值上则是加上每个地址信号加上一个pword。

三、工程实现

  设计一个单通道的DDS信号发生器,根据改变FPGA开发板上的拨码开关来控制DAC输出的信号是正弦波、方波、三角波、锯齿波,通过按键来控制DAC芯片输出的信号的频率和相位。

  1、RTL视图

   2、按照RTL视图分析各个模块的代码

  (1)select 模块:

      拨码开关 sw0: 往上拨则输出  正弦波      key1 : 调节频率,有四个档位,每按一下换一个频率;      sclk:系统时钟50MHz

      拨码开关 sw1: 往上拨则输出  方波       key2:调节相位,有四个档位,每按一下换一个相位;      s_rst_n:系统复位

      拨码开关 sw2: 往上拨则输出  锯齿波            en :使能信号

      拨码开关 sw3: 往上拨则输出  三角波  

  select 代码

 1 // *********************************************************************************
 2 // Project Name : select
 3 // Email        : 
 4 // Create Time  : 2020/06/24  18:44
 5 // Module Name  : select.v
 6 // editor         : qing
 7 // Version         : 
 8 // *********************************************************************************
 9 
10 module select(
11     input                sclk                    ,
12     input                s_rst_n                ,
13         
14     input                sw0                    ,
15     input                sw1                    ,
16     input                sw2                    ,
17     input                sw3                    ,
18         
19     input                key1                    ,
20     input                key2                    ,
21 
22     output    reg[ 3:0]    rom_select                    ,
23     output    reg[31:0]    fword                    ,
24     output    reg[31:0]    pword                
25     );
26 
27 //========================================================================
28 // =========== Define Parameter and Internal signals =========== 
29 //========================================================================/
30 
31 reg            [3:0]        cnt1        ;
32 reg            [3:0]        cnt2        ;
33 
34 //=============================================================================
35 //****************************     Main Code    *******************************
36 //=============================================================================
37 
38 always @(posedge sclk or negedge s_rst_n) begin  // cnt1
39     if(!s_rst_n)
40         cnt1 <= 0;
41     else if(cnt1 == 3)
42         cnt1 <= 0;
43     else if(key1 == 1'b0)
44         cnt1 <= cnt1 + 1'b1;
45 end
46 
47 always @(posedge sclk or negedge s_rst_n) begin  // cnt2
48     if(!s_rst_n)
49         cnt2 <= 0;
50     else if(cnt2 == 3)
51         cnt2 <= 0;
52     else if(key2 == 1'b0)
53         cnt2 <= cnt2 + 1'b1;
54 end
55 
56 always @(*) begin  // rom_select
57     case({sw3,sw2,sw1,sw0})
58         4'b0001:rom_select = 4'd0;
59         4'b0010:rom_select = 4'd1;
60         4'b0100:rom_select = 4'd2;
61         4'b1000:rom_select = 4'd3;
62         default:rom_select = 4'd0;
63     endcase
64 end
65 
66 always @(*) begin  // fword
67     case(cnt1)
68         0:fword = 2000;
69         1:fword = 3000;
70         2:fword = 4000;
71         3:fword = 5000;
72         default:fword = 5000;
73     endcase
74 end
75 
76 
77 always @(*) begin  // pword
78     case(cnt2)
79         0:pword = 0;
80         1:pword = 64;
81         2:pword = 128;
82         3:pword = 192;
83         default:pword = 0;
84     endcase
85 end
86 
87 endmodule
88 
89             
View Code

  (2)dds_control模块:这个模块主要是按照一定的规则将存储在rom中的四种信号的数据取出来,代码比较简单,这里就不详细讲解了。

    几个注意事项:

      a、在本次实验中,rom 的位宽为 8 ,存储为 256,在用 Mif_Maker2010 生成  . mif 文件之后,一定要打开这个新生成的文件,看里面是否有数据,数据是不是全部为零,如果没有数据或者数据为                零,则需重新用 Mif_Maker2010 重新生成数据(位宽为 8 bit,数据量为 256);

      b、新生成的 .mif 文件要放在 工程文件下面,如下图所示,不然,可能会无法提取存储在 rom 中的数据;

   dds_control代码:

 1 // *********************************************************************************
 2 // Project Name : dds
 3 // Email        : 
 4 // Create Time  : 2020/06/24  09:54
 5 // Module Name  : dds_control
 6 // editor         : qing
 7 // Version         : Rev1.0.0
 8 // *********************************************************************************
 9 
10 module dds_control(
11     input                    sclk            ,        // 50M
12     input                    s_rst_n        ,
13     input                    en                ,
14 
15     input        [31:0]    fword           ,        // 频率控制字
16     input        [11:0]    pword           ,        // 相位控制字
17     input        [ 3:0]    rom_select    ,
18 
19     output                da_clk        ,
20     output reg[ 7:0]  rom_data    
21 );
22 
23 //========================================================================
24 // =========== Define Parameter and Internal signals =========== 
25 //========================================================================/
26 
27 reg        [31:0]        fre_acc        ;
28 reg        [ 7:0]        rom_addr        ;
29 
30 wire        [ 7:0]        q1                ;
31 wire        [ 7:0]        q2                ;
32 wire        [ 7:0]        q3                ;
33 wire        [ 7:0]        q4                ;
34 
35 //=============================================================================
36 //****************************     Main Code    *******************************
37 //=============================================================================
38 
39 /*
40 parameter         fword = 5000        ;
41 parameter          pword = 0            ;
42 */
43 
44 always @(posedge sclk or negedge s_rst_n) begin  // fword
45     if(!s_rst_n)
46         fre_acc <= 0;
47     else if(en == 1'b0)
48         fre_acc <= 0;
49     else
50         fre_acc <= fre_acc + fword;
51 end
52 
53 always @(posedge sclk or negedge s_rst_n) begin  // rom_addr
54     if(!s_rst_n)
55         rom_addr <= 0;
56     else if(en == 1'b0)
57         rom_addr <= 0;
58     else
59         rom_addr <= fre_acc[31:24] + pword;
60 end
61 
62 assign da_clk = en ? sclk : 1'b1 ;
63 
64 always @(*) begin
65     case(rom_select)
66         0:rom_data = q1;
67         1:rom_data = q2;
68         2:rom_data = q3;
69         3:rom_data = q4;
70         default:rom_data = q1;
71     endcase
72 end
73 
74 rom u1 (
75     .address ( rom_addr     ),
76     .clock   ( sclk            ),
77     .q        ( q1           )
78     );
79     
80 fagnbo u2 (
81     .address ( rom_addr    ),
82     .clock   ( sclk        ),
83     .q       ( q2          )
84     );
85     
86 jucibo u3 (
87     .address ( rom_addr    ),
88     .clock   ( sclk        ),
89     .q       ( q3          )
90     );
91     
92 sanjiao u4 (
93     .address ( rom_addr    ),
94     .clock   ( sclk        ),
95     .q       ( q4              )
96     );
97     
98 
99 endmodule
View Code

  (3)ad9709_driver模块:AD9709是一个DAC芯片,双端口、高速、双通道、8位CMOS  DAC。在本次实验中只使用了通道 A。这个DAC芯片的驱动是目前见过的最简单的一款DAC。

    dac_clka  :控制通道 A 工作时钟;

    dac_mode:AD9709的工作模式选择。为高电平时,表示双通道模式;为低电平时,表示为单通道模式;

    dac_sleep :为高电平时,AD9709进入睡眠模式,此时不工作;为低电平时,AD9709为正常工作模式;

               dac_wra :通道 A的写使能信号;

    注意事项:时钟要同步,dds_control 模块输出数据的速率和提供给 ad9709_driver 模块数据转换的速度要保持一致;

  ad9709_driver  代码:

 1 // *********************************************************************************
 2 // Project Name : ad9709_driver
 3 // Email        : 
 4 // Create Time  : 2020/06/24
 5 // Module Name  : ad9709_driver
 6 // editor         : qing
 7 // Version         : 
 8 // *********************************************************************************
 9 
10 module ad9709_driver(
11     input                  da_clk        ,
12     input                  rst_n            ,
13     input       [7:0]       rom_data        ,
14    
15     output               dac_mode        ,
16     output               dac_clka        ,
17     output reg[7:0]   dac_da        ,
18     output               dac_wra        ,
19     output               dac_sleep
20     );
21 
22 always @ (posedge da_clk or negedge rst_n) begin  // dac_da
23     if(rst_n == 1'b0)
24         dac_da <= 0;
25     else    
26       dac_da <= rom_data;  // 255 - rom_data
27 end
28 
29 assign dac_sleep = 0 ;
30 assign dac_wra   = dac_clka ;
31 assign dac_clka  = -da_clk;
32 assign dac_mode  = 1;
33 
34 endmodule
View Code

  (4)顶层模块:

  顶层模块代码:例化各个子模块

 1 // *********************************************************************************
 2 // Project Name : dds
 3 // Email        : 
 4 // Create Time  : 2020/06/24 10:26
 5 // Module Name  : dds
 6 // editor         : qing
 7 // Version         : Rev1.0.0
 8 // *********************************************************************************
 9 
10 module dds(
11     input                    sclk                ,
12     input                    s_rst_n            ,
13     input                    en                    ,
14     
15     input                    sw0                ,
16     input                    sw1                ,
17     input                    sw2                ,
18     input                    sw3                ,
19     input                    key1                ,
20     input             key2                ,
21                             
22     output                dac_mode            ,
23     output                dac_clka            ,
24     output                dac_wra            ,
25     output                dac_sleep        ,
26 
27     output    [7:0]        dac_da
28 );
29 
30 wire                da_clk                    ;
31 wire    [7:0]        rom_data                   ;
32 
33 wire    [31:0]    fword                        ;
34 wire    [31:0]    pword                        ;
35 wire    [3:0]        rom_select                ;
36 
37 dds_control u0(
38     .sclk            (sclk            ),        // 50M
39     .s_rst_n        (s_rst_n        ),
40     .en            (en            ),
41     .fword        (fword        ),
42     .pword        (pword        ),
43     .rom_select (rom_select ),
44     .da_clk        (da_clk        ),
45     .rom_data    (rom_data    )
46 );
47 
48 ad9709_driver u1(
49     .da_clk       (da_clk       ),
50     .rst_n       (s_rst_n       ),
51     .rom_data   (rom_data   ),
52     .dac_mode   (dac_mode   ),
53     .dac_clka   (dac_clka   ),
54     .dac_da       (dac_da       ),
55     .dac_wra       (dac_wra       ),
56     .dac_sleep  (dac_sleep  )
57     );
58     
59 select u2(
60     .sclk            ( sclk        ),
61     .s_rst_n        ( s_rst_n    ),
62     .sw0            ( sw0            ),
63     .sw1            ( sw1            ),
64     .sw2            ( sw2            ),
65     .sw3            ( sw3            ),    
66     .key1            ( key1        ),
67     .key2            ( key2        ),
68     .rom_select    ( rom_select),
69     .fword        ( fword        ),
70     .pword        ( pword        )
71     );
72 
73 endmodule
View Code

  

四、上板展示

     

          

五、不足之处

  1:算输出频率的公式没有算出本实验中的输出信号的频率(懒);

  2:通过按键计数来实现输出信号频率,相位的切换这部分没有进行按键消抖,fword  和  pword 设置也有一定的问题(懒);

  3:没有编写滤波模块的代码,还是有一丢丢的毛刺信号(懒);

  4:没有编写振幅控制的模块(懒);

  5:没有驱动DAC芯片的两个通道(懒);

  6:......

六、参考

  1、小梅哥的《基于ac620的fpga系统设计与验证实战指南20190516》相关章节;

  2、明德扬的《FPGA至简设计原理与应用》;

  

    

  

原文地址:https://www.cnblogs.com/571328401-/p/13188571.html