加载中...

DDS原理及简单应用

DDS原理学习

何为DDS

DDS定义与优点

DDS: Direct digital synthesis 直接数字频率合成,可以用来合成周期性的信号序列。

优点:频率转换时间短;频率分辨率极高;相位变化连续;输出波形灵活

DDS原理

在参考时钟的驱动下,相位累加器对频率控制字进行线性累加,得到的相位码对波形存储器寻址,使之输出相应的幅度码,经过模数转换器得到相应的阶梯波。

也就是,我们事先将想要的波形所对应的数值按照一定的要求存储于ROM 中,然后在硬件工作的时候,对 ROM 进行寻址,得到相应的波形数据,再通过 D/A 输出,以此获得我们目标波形。

角度与波形

从 DDS 的原理可以看出,DDS 可以产生任意周期性的波形,比如 sine,cosine,三角波等。

以 sine 为例,可以看出来,波形电平最大值等于圆的半径,产生的波形的频率等价于扫过 2π 角度的速度。

频率控制字

从上图可以看出来,输出信号的频率与扫描的角速度是有关系的。

$$
频率=\frac{2π}{角速度}
$$

但由于波形数据是存储在 ROM 中的,这就意味着其相位的步进是离散的,这时候需要引入频率控制字的概念。

  • 举例

    • 频率控制字与输出信号频率之间的关系

      当前你在 ROM 中存储了 1024 个 sine 波形数据,而你要读取哪个波形数据是完全取决于相位累加器的输出的,比如当前相位累加器输出是 10,则你会读取 ROM 中地址为 10 的数据。相位累加器的步进值又取决于频率控制字 M,如果 M = 1,则系统会按照顺序读取所有 ROM 中的数据,那如果频率控制字 M = 10,则系统会在 ROM 中每 10 个数据读取一次,可想而知,波形没那么好看了(可能会产生锯齿),但是由于扫描一周的时间变短了,所以对应频率更高。

硬件实现

思考

  1. 求 $131.072MHz $系统时钟,要求精度为 $1kHz$ 时的 $N$。输出精度相当于$M = 1$$ $时的输出频率。

    答:$N = 17$,意味着 $ROM$ 核的地址有 $17bit$,因此深度为$2 ^ {17} = 131, 072$。若要求精度为 $1Hz$,深度就会更大。

  2. 如果我想获得更大的可调频率范围,那频率控制字 $M$ 有可能为小数吗?或者是当前 $ROM $的寻址范围有限,那我有可能产生比取 $M = 1$还低的输出信号频率吗?如果可以,那频率控制字$ M = 0.5, M = 0.25$ 在$ Verilog$ 中该怎么实现呢?

    答:可以通过对相位累加器的输出进行截断来实现。

地址截断

一般舍去较低为位数,只取 N 的高 A 位(如高 10 位或 14 位)做为存储器地址,使得相位的低位被截断(即相位截尾)。可以参考如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   reg    [19:0] addr = 20'd0;
parameter add_M = 20'd1;
//addr
always @(posedge clk) begin
if (rst_n == 1'b1) begin
addr[19:0] <= 20'd0;
end
else begin
addr[19:0] <= addr[19:0] + add_M;
end
end
sine_rom sin_rom_m (
.clka(clk), // input wire clka
.addra(addr[19:10]), // input wire [9 : 0] addra
.douta(dds_out) // output wire [13 : 0] douta
);

具体实现步骤

仿真设计实现

MATLAB

生成coe文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
clear;clc;close all;
t = 0:1/2^10:1-1/2^10;
sin_1 = sin(2*pi*t);
sin_14bit = round((2^13-1)*sin_1/max(sin_1));
fid = fopen('sin_14bit.coe', 'w'); %创建COE文件
fprintf(fid, 'memory_initialization_radix=10;\n');
fprintf(fid, 'memory_initialization_vector=\n');
for i = 1:length(sin_14bit)
if(i < length(sin_14bit))
fprintf(fid,'%d\n',sin_14bit(i));
else
fprintf(fid,'%d',sin_14bit(i));
end
end
% fprintf(fid,'%d\n',sin_14bit);
fclose(fid);
figure;
plot(sin_14bit);

上述代码,$N = 1024$,正弦函数,量化时乘以$2^{13}-1$,其原因在于量化$14Bit$,有符号数的范围为$-2^{13}$~$2^{13}-1$;正数少一个,所以只能量化到$2^{13}-1$

Vivado

仿真中遇到的问题及注意事项

无地址截断仿真

  • Design文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    `timescale 1ns / 1ps
    module dds_work(
    input clk,
    input rst_n,
    output [13:0] dds_out
    );
    reg [9:0] addr = 10'd0;
    //addr
    always @(posedge clk or negedge rst_n) begin
    if (rst_n) begin
    addr <= 10'd0;
    end
    else begin
    addr <= addr + 10'd1;
    end
    end
    sin_rom sin_rom_m (
    .clka(clk), // input wire clka
    .addra(addr), // input wire [9 : 0] addra
    .douta(dds_out) // output wire [13 : 0] douta
    );

    endmodule
  • testbench文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    `timescale 1ns / 1ps
    module tb_sinrom(
    );
    reg clk;
    reg rst_n;
    wire [13:0] dds_out;

    initial begin
    clk = 1'b0;
    end
    always #10 clk = ~clk; //50MHz clock
    initial begin
    rst_n = 1'b1;
    #200
    rst_n = 1'b0;
    end

    dds_work dds_work_m (
    .clk(clk),
    .rst_n(rst_n),
    .dds_out(dds_out)
    );
    endmodule
    • 仿真波形

      $T = 20.66us$(稍有误差,手拖动的)
      $$
      f_o={M×f_{clk} \over 2^N}
      $$
      即$fo={1×50×10^6 \over 2^{10} }$; 所以 $T =1/f_o=1024/50us=20.48us $
      故而可以认为输出正确

有地址截断仿真

  • Design文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    module dds_top(
    input clk,
    input rst_n,
    output [13:0] dds_out
    );
    reg [19:0] addr = 20'd0;
    parameter add_M = 20'd1;
    //addr
    always @(posedge clk) begin
    if (rst_n == 1'b1) begin
    addr[19:0] <= 20'd0;
    end
    else begin
    addr[19:0] <= addr[19:0] + add_M;
    end
    end
    sine_rom sin_rom_m (
    .clka(clk), // input wire clka
    .addra(addr[19:10]), // input wire [9 : 0] addra
    .douta(dds_out) // output wire [13 : 0] douta
    );

    endmodule
  • Testbench文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    module tb_sinrom( 
    );
    reg clk;
    reg rst_n;
    wire [13:0] dds_out;

    initial begin
    clk = 1'b0;
    end
    always #10 clk = ~clk; //50MHz clock
    initial begin
    rst_n = 1'b1;
    #200
    rst_n = 1'b0;
    end

    dds_top dds_top_m (
    .clk(clk),
    .rst_n(rst_n),
    .dds_out(dds_out)
    );
    endmodule
    • 仿真输出

      上述图看到,周期大约为21ms

      计算验证:$ T = 1/f_o = 2 ^ {20} \times1 \times10 ^ 3 / 50 \times 10 ^ 6 ms = 20.97152 ms $

      该设计是为了证明可通过截取地址来实现更低频率的信号,解决了 $N$ 有限导致的问题