进阶项目(5)DDS程序设计

写在前面的话

DDS是直接数字式频率合成器(Direct Digital Synthesizer)的英文缩写。与传统的频率合成器相比,DDS具有低成本、低功耗、高分辨率和快速转换时间等优点,广泛使用在电信与电子仪器领域,是实现设备全数字化的一个关键技术。

项目需求

设计一个相位和频率可调的波形(正弦波)发生器。

项目分析

问题1:什么是波形发生器?

波形发生器是一种数据信号发生器,在调试硬件时,常常需要加入一些信号,以观察电路工作是否正常。加入的信号有:正弦波、三角波、方波和任意波形等等。

问题2:什么是相位可调?

相位(phase)是对于一个,特定的时刻在它循环周期中的位置:一种它是否在波峰波谷或它们之间的某点的标度。相位描述信号波形变化的度量,通常以度 (角度)作为单位,也称作相角。 当信号波形以周期的方式变化,波形循环一周即为360°。那么相位可调也可以简单的理解为:改变初始相位。

问题3:什么是频率可调?

频率,是单位时间内完成周期性变化次数,是描述周期运动频繁程度的量,常用符号fν表示,单位为秒分之一,符号为s-1。频率可调也就是改变单位时间内完成周期性变化的次数。

系统架构

我们应该先把完整的波形数据放在rom里面,然后用一个控制器把rom里面的数据读出来,设计架构图如下:

进阶项目(5)DDS程序设计

模块功能介绍

模块名

功能描述

rom

存储波形数据

control

输出有效地址

dds

负责顶层的连接

8.9.6端口和内部连线描述

顶层端口

端口名

端口说明

clk

系统时钟输入

rst_n

系统复位

num[7:0]

波形数据输出

内部连线

连线名

连线说明

addr[7:0]

有效地址

波形数据的由来

我们应用Mif_Maker2010(mif文件生成软件)来生成我们的波形数据:

第一步:打开Mif_Maker2010

进阶项目(5)DDS程序设计第二步:设定波形为正弦波

进阶项目(5)DDS程序设计第三步:设置全局参数

 

 

进阶项目(5)DDS程序设计

进阶项目(5)DDS程序设计第四步:再一次设定波形为正弦波即可

进阶项目(5)DDS程序设计

 

保存mif文件。

我们将正弦波分成了256个点,每个点对应一个数据,生成mif文件。

波形发生器(不可调频和调相)

代码解释

control模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 输出有效地址

*****************************************************/

00  module control (

01                  clk, //外部输入时钟

02                  rst_n,//系统复位

03                  addr//有效地址

04              );

05      //模块输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //模块输出

09      output reg [7:0] addr;//有效地址

10

11      always @ (posedge clk or negedge rst_n)

12          begin

13              if (!rst_n) //复位的时候,addr清零

14                  addr <= 0;

15              else

16                  if (addr < 255)

17                      addr <= addr + 1;//地址在0~255之间循环

18                  else

19                      addr <= 0;

20          end

21          

22  endmodule 

16~19行控制地址在0~255 之间循环,以此读出ROM中所有波形数据。

 

dds模块

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   E_mail        :   zxopenwjf@126.com

 *   The module function: 负责顶层连接

*****************************************************/

00  module dds (

01              clk, //外部输入时钟

02              rst_n,//系统复位

03              num //波形数据输出

04          );

05      //系统输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //系统输出

09      output [7:0] num;//波形数据输出

10      //定义中间连线

11      wire [7:0] addr;//有效地址

12      //实例化control

13      control control (

14                  .clk(clk), //外部输入时钟

15                  .rst_n(rst_n), //系统复位

16                  .addr(addr)//有效地址

17              );

18      //调用ip核

19      rom rom_inst (

20                  .address ( addr ),

21                  .clock ( clk ),

22                  .q ( num )

23              );

24

25  endmodule 

 

 

dds_tb模块

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   E_mail        :   zxopenwjf@126.com

 *   The module function: 测试dds模块

*****************************************************/

00 `timescale 1ns/1ps //定义时间单位和精度

01

02 module dds_tb;

03 //系统输入

04 reg clk;//外部输入时钟

05 reg rst_n;//系统复位

06 //系统输出

07 wire [7:0] num;//波形数据输出

08

09 initial begin

10 clk = 1;

11 rst_n = 0;

12 # 200.1

13 rst_n = 1;

14 end

15

16 always # 10 clk = ~clk;//50M的时钟

17

18 dds dds (

19 .clk(clk), //外部输入时钟

20 .rst_n(rst_n), //系统复位

21 .num(num)//波形数据输出

22 );

23

24 endmodule

 

仿真分析

 将num设置为无符号类型的数据。

进阶项目(5)DDS程序设计将num设置成模拟的数据。

进阶项目(5)DDS程序设计具体的参数如下:

进阶项目(5)DDS程序设计经过以上几步,我们就可以看到想要的正弦波。

进阶项目(5)DDS程序设计

 

 

 进阶项目(5)DDS程序设计

 

放大后,我们测量了正弦波的一些参数,初始相位0度(梦翼师兄自己定义的),频率是195.31KHz。那么这些参数都是那里来的呢?

正弦波在相位为0度的时候开始出波形,是因为我们的地址是从0递增到255的,而0地址的数据恰好就是正弦波相位为0度的数据,故而初始相位为0度。

我们所使用的时钟为50MHz,而正弦波被分成了256个点,我们来计算一下: 

结果很显然,结果等于195312.5HZ,也就是195.31KHz。

 波形发生器(不可调频,但可以调相)

经过上一小节的分析,改变地址的初值就可以改变初始的相位,下面我们来验证。

代码解释

control模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 输出有效地址

*****************************************************/

00  module control (

01                  clk, //外部输入时钟

02                  rst_n,//系统复位

03                  addr//有效地址

04              );      

05      //模块输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //模块输出

09      output reg [7:0] addr;//有效地址

10      //定义相位控制字

11      parameter pword = 64;

12      

13      always @ (posedge clk or negedge rst_n)

14          begin

15              if (!rst_n) //复位的时候,addr被赋值为相位控制字

16                  addr <= pword;

17              else

18                  if (addr < 255)

19                      addr <= addr + 1;//地址在0~255之间循环

20                  else

21                      addr <= 0;

22          end

23          

24  endmodule 

 

 

第11行,定义了相位控制字。这里采用了参数的形式,读者也可以直接采用数据输入的形式。

第16行,在复位的时候,我们给地址的初值是64。正弦波被分成了256个点,第64个点刚好对应的初始相位为90度。我们可以按照下面的公式来进行计算初始相位和地址初值的关系: 

dds模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 负责顶层连接

*****************************************************/

00  module dds (

01              clk, //外部输入时钟

02              rst_n,//系统复位

03              num //波形数据输出

04          );

05      //系统输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //系统输出

09      output [7:0] num;//波形数据输出

10      //定义相位控制字

11      parameter pword = 64;

12      //定义中间连线

13      wire [7:0] addr;//有效地址

14      //实例化control

15      control#(.pword(pword))//传递相位控制字

16              control (

17                  .clk(clk), //外部输入时钟

18                  .rst_n(rst_n), //系统复位

19                  .addr(addr)//有效地址

20              );

21      //调用ip核

22      rom rom_inst (

23                  .address ( addr ),

24                  .clock ( clk ),

25                  .q ( num )

26              );

27

28  endmodule 

 

第15行,实例化control模块时,可以选择改变control模块内部的参数,而不需要去底层模块进行修改,这样就可以一级一级的传递参数下去。

 

dds_tb模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 测试dds模块

*****************************************************/

00  `timescale 1ns/1ps //定义时间单位和精度

01

02  module dds_tb;

03      //系统输入

04      reg clk;//外部输入时钟

05      reg rst_n;//系统复位

06      //系统输出

07      wire [7:0] num;//波形数据输出

08      //定义相位控制字

09      parameter pword = 64;

10  

11      initial begin

12          clk = 1;

13          rst_n = 0;

14          # 200.1

15          rst_n = 1;

16      end

17

18      always # 10 clk = ~clk;//50M的时钟

19      

20      dds #(.pword(pword))//传递相位控制字

21          dds(

22              .clk(clk), //外部输入时钟

23              .rst_n(rst_n), //系统复位

24              .num(num)//波形数据输出

25          );

26

27  endmodule

 

第20行,在编写测试文件的时候,同样可以改变参数。

有兴趣的同学可以将底层的参数设置成和测试文件中不一样的数据,然后仿真对比一下,其结果都是最顶层的参数起作用。

仿真分析

经过设置以后,我们再一次测量频率和相位这两个参数。

进阶项目(5)DDS程序设计

 

 

 

初始相位变成了90度,但是频率还是195.31KHz。通过改变相位控制字,就可以改变相位,达到了可调相的功能。 

 

波形发生器(可以调频,可以调相)

 

经过前面两小节的分析,我们了解到,如果采用本地时钟是50M的话,那么频率就是195.31KHZ,根据我们的公式:

进阶项目(5)DDS程序设计

 

 

 

 

设想:如果想要改变输出的频率,我们可以选择改变时钟或者输出点的个数!显而易见,我们的设计不能够时时刻刻去改变时钟的频率,那么想要输出别的频率,我们只能改变输出的点的个数,也就是改变有效地址的数量。之前的设计是每个时钟沿地址增加1,频率是195.31KHz;如果我们每个时钟沿地址增加2,根据公式,输出频率应该是390.62KHz。下面我们就来验证:

 

代码解释

 

control模块

 

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 输出有效地址

*****************************************************/

00  module control (

01                  clk, //外部输入时钟

02                  rst_n,//系统复位

03                  addr//有效地址

04              );      

05      //模块输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //模块输出

09      output reg [7:0] addr;//有效地址

10      //定义相位控制字

11      parameter pword = 64;

12      //定义频率控制字

13      parameter fword = 2;

14  

15      always @ (posedge clk or negedge rst_n)

16          begin

17              if (!rst_n) //复位的时候,addr清零

18                  addr <= pword;

19              else

20                  addr <= addr + fword;//地址在0~255之间循环

21          end

22          

23  endmodule 

 

 

 

dds模块代码

 

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 负责顶层连接

*****************************************************/

00 module dds (

01 clk, //外部输入时钟

02 rst_n,//系统复位

03 num //波形数据输出

04 );

05 //系统输入

06 input clk;//外部输入时钟

07 input rst_n;//系统复位

08 //系统输出

09 output [7:0] num;//波形数据输出

10 //定义相位控制字

11 parameter pword = 64;

12     //定义频率控制字

13     parameter fword = 3;

14 //定义中间连线

15 wire [7:0] addr;//有效地址

16 //实例化control

17 control#(.pword(pword),//传递相位控制字

18  .fword(fword)//传递频率控制字

19 )

20 control (

21 .clk(clk), //外部输入时钟

22 .rst_n(rst_n), //系统复位

23 .addr(addr)//有效地址

24 );

25 //调用ip核

26 rom rom_inst (

27 .address ( addr ),

28 .clock ( clk ),

29 .q ( num )

30 );

31

32 endmodule

 

 

 

dds_tb模块代码

 

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 测试dds模块

*****************************************************/

00  `timescale 1ns/1ps //定义时间单位和精度

01

02  module dds_tb;

03      //系统输入

04      reg clk;//外部输入时钟

05      reg rst_n;//系统复位

06      //系统输出

07      wire [7:0] num;//波形数据输出

08      //定义相位控制字

09      parameter pword = 64;

10  //定义频率控制字

11  parameter fword = 2;

12  

13      initial begin

14          clk = 1;

15          rst_n = 0;

16          # 200.1

17          rst_n = 1;

18      end

19

20      always # 10 clk = ~clk;//50M的时钟

21      

22      dds #(.pword(pword),//传递相位控制字

23            .fword(fword)//传递频率控制字

24           )

25          dds  (

26              .clk(clk), //外部输入时钟

27              .rst_n(rst_n), //系统复位

28              .num(num)//波形数据输出

29          );

30

31  endmodule

 

仿真分析

进阶项目(5)DDS程序设计

查看波形的频率为390.62KHz,证明我们的设计和思路是正确的。

 

最终设计

 

上一小节的设计,只能设定195.31KHz的整数倍,那就失去了我们设计的意义。为了提高精度,我们可以定义一个位宽为N(N>8)的地址计数器,让地址计数器每次增加一定的值,然后把高八位当作有效地址输送给rom,这样的话,就实现了降低地址改变的频率,进而达到降低输出波形的频率。下面我们就分析一下,这样可不可以改变频率。

 

地址计数器的原理就是先将pword的值作为地址的初值,然后每来一个时钟,地址计数器的值就等于地址当前值加上频率控制字fword,如此循环。例如刚开始fword = 1(假设pword=0) ,那么第一个时钟周期地址计数器的输出就是1,第二个时钟周期输出的就是2,第三个时钟周期输出的就是3。再例如,我们的频率控制字fword 刚开始等于2,那么地址计数器输出的就依次是0,2,4.....也就是说频率控制字fword 越大,地址计数器的输出值间隔也就越大,那么我们假设地址计数器的输出是32位的,如果fword越大,那么地址计数值到2^32 的时间就越短。这样的话,我们来算一下:

 

我们使用的是50MHz的晶振,周期为20ns,假设fword为1,地址计数器的输出为N位的,那么每20ns,地址计数器加1,要加到2^N,需要20ns x 2^N 时间,这个时间就是输出一个完整信号的周期,那么我们可以知道,输出信号的频率为 Fout = Fclk /2^N,其中,Fclk为我们的晶振频率,再假如,fword = B 的时候,地址间隔提高 B 倍,因此计满一个周期的时间缩小了 B 倍,频率提高的 B 倍。综上所述,我们得出了输出信号的频率计算公式:

 

 

 进阶项目(5)DDS程序设计

 

我们来计算一下:当B=1,F大约是0.012Hz。所以我们改变fword的值就基本实现了所有的低于最快频率以下所有的频率值(频率值只能是0.012的倍数,因为0.012太小了,所以基本可以实现所有的频率,若这个精度还是达不到要求的话,大家可以继续增大N的值,根据公式就可以得出最小精度,也可以根据最小精度计算N的值)。

我们取地址计数器的前八位,相当于把一个波形的相位分成了256个点,每个点对应一个数据,正好和我们的波形数据点的个数是一样的。

 代码分析

control模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 输出有效地址

*****************************************************

00  module control (

01                  clk, //外部输入时钟

02                  rst_n,//系统复位

03                  addr//有效地址

04              );      

05      //模块输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //模块输出

09      output [7:0] addr;//有效地址

10      //定义相位控制字

11      parameter pword = 128;

12      //定义频率控制字

13      parameter fword = 10;

14      //定义中间寄存器

15      reg [31:0] addr_num;//地址计数器

16  

17      assign addr = addr_num[31:24];//地址计数器的

18  

19      always @ (posedge clk or negedge rst_n)

20          begin

21              if (!rst_n) //复位的时候,addr清零

22                  begin

23                      addr_num[31:24] <= pword;

24                      addr_num[23:0] <= 24'd0;

25                  end

26              else

27                  addr_num <= addr_num + fword;//地址在0~255之间循环

28          end

29          

30  endmodule 

第11行,相位控制字是128,初始相位应该是180度。

第13行,频率控制字是10,波形的频率是0.12Hz。

 

dds模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 负责顶层连接

*****************************************************/

00  module dds (

01              clk, //外部输入时钟

02              rst_n,//系统复位

03              num //波形数据输出

04          );

05      //系统输入

06      input clk;//外部输入时钟

07      input rst_n;//系统复位

08      //系统输出

09      output [7:0] num;//波形数据输出

10      //定义相位控制字

11      parameter pword = 64;

12      //定义频率控制字

13      parameter fword = 100000;

14      //定义中间连线

15      wire [7:0] addr;//有效地址

16      //实例化control

17      control#(.pword(pword),//传递相位控制字

18           .fword(fword)//传递频率控制字

19          )

20              control (

21                  .clk(clk), //外部输入时钟

22                  .rst_n(rst_n), //系统复位

23                  .addr(addr)//有效地址

24              );

25      //调用ip核

26      rom rom_inst (

27                  .address ( addr ),

28                  .clock ( clk ),

29                  .q ( num )

30              );

31

32  endmodule 

 

第11行,相位控制字是64,初始相位应该是90度。

第13行,频率控制字是100000,波形的频率是1200Hz。

 

dds_tb模块代码

/****************************************************          

 *   Engineer      :   梦翼师兄

 *   QQ             :   761664056

 *   The module function: 测试dds模块

*****************************************************/

00  `timescale 1ns/1ps //定义时间单位和精度

01

02  module dds_tb;

03      //系统输入

04      reg clk;//外部输入时钟

05      reg rst_n;//系统复位

06      //系统输出

07      wire [7:0] num;//波形数据输出

08      //定义相位控制字

09      parameter pword = 64;

10  //定义频率控制字

11  parameter fword = 10000;

12  

13      initial begin

14          clk = 1;

15          rst_n = 0;

16          # 200.1

17          rst_n = 1;

18      end

19

20      always # 10 clk = ~clk;//50M的时钟

21      

22      dds #(.pword(pword),//传递相位控制字

23            .fword(fword)//传递频率控制字

24           )

25          dds (

26              .clk(clk), //外部输入时钟

27              .rst_n(rst_n), //系统复位

28              .num(num)//波形数据输出

29          );

30

31  endmodule

 

第11行,相位控制字是64,初始相位应该是90度。

第13行,频率控制字是10000,波形的频率是120Hz。

仿真分析

 进阶项目(5)DDS程序设计

经过测试输出波形的频率和相位,证明我们的设计可以调频和调相,同时也验证了,最顶层的参数才能起作用。

利用上述的设计我们完成了输出波形数据的调频和调相,然后将数据经过DA转换就可以实现真实模拟信号的输出了。将读出的数据通过DA转换时,一定要注意DA芯片的转换频率,读出数据的频率必须和DA的数据转换速度相匹配。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

上一篇:赋值语句的运用


下一篇:新买的ESP32 LyraTD MSC 不能进入烧写模式