一、实现环境
软件: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。因此理论上由以上三个参数就可以得出任意的 fo 输出频率,且可以得出频率分辨率由时钟频率和累加器的位数决定的结论。当参考时钟频率越高,累加器位数越高,输出频率分辨率就越高。
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
(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
(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
(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
四、上板展示
五、不足之处
1:算输出频率的公式没有算出本实验中的输出信号的频率(懒);
2:通过按键计数来实现输出信号频率,相位的切换这部分没有进行按键消抖,fword 和 pword 设置也有一定的问题(懒);
3:没有编写滤波模块的代码,还是有一丢丢的毛刺信号(懒);
4:没有编写振幅控制的模块(懒);
5:没有驱动DAC芯片的两个通道(懒);
6:......
六、参考
1、小梅哥的《基于ac620的fpga系统设计与验证实战指南20190516》相关章节;
2、明德扬的《FPGA至简设计原理与应用》;