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

写在前面的话

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

项目需求

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

项目分析

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

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

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

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

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

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

系统架构

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

模块功能介绍

模块名

功能描述

rom

存储波形数据

control

输出有效地址

dds

负责顶层的连接

8.9.6端口和内部连线描述

顶层端口

端口名

端口说明

clk

系统时钟输入

rst_n

系统复位

num[7:0]

波形数据输出

内部连线

连线名

连线说明

addr[7:0]

有效地址

波形数据的由来

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

第一步:打开Mif_Maker2010

第二步:设定波形为正弦波

第三步:设置全局参数

第四步:再一次设定波形为正弦波即可

保存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        :   [email protected]

 *   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        :   [email protected]

 *   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设置为无符号类型的数据。

num设置成模拟数据

具体的参数如下:

经过以上几步,我们就可以看到想要的正弦波。

 

放大后,我们测量了正弦波的一些参数,初始相位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行,在编写测试文件的时候,同样可以改变参数

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

仿真分析

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

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

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

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

设想:如果想要改变输出的频率,我们可以选择改变时钟或者输出点的个数显而易见,我们的设计不能够时时刻刻去改变时钟的频率,那么想要输出别的频率我们只能改变输出的点的个数,也就是改变有效地址的数量。之前的设计是每个时钟沿地址增加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

仿真分析

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

最终设计

上一小节的设计,只能设定195.31KHz的整数倍,那就失去了我们设计意义。为了提高精度,我们可以定义一个位宽为NN>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 倍。综上所述,我们得出了输出信号的频率计算公式:

 

我们来计算一下:当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

仿真分析

 

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

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

 

 

猜你喜欢

转载自www.cnblogs.com/mengyi1989/p/11517744.html