跳转到主要内容

DDS原理及FPGA实现

guanxiao_505740 提交于

<font color="#FF8000">作者:雷凌峻毅</font>

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36854651/article/details/104433727

一个按一定速度沿x轴行进,同时半径按一定频率在圆周上滑动的圆,最后留下的痕迹就是一个正余弦波。

DDS全称直接数字频率合成(Direct Digital Synthesis),简单来讲,分以下几步:

<strong>1.抽样</strong>
既然是数字频率合成,那么从模拟信号变成数字信号的过程必不可少。这个过程就是抽样的过程。根据奈奎斯特采样定律,我们在采样过程要保留源信号的信息,那么采样率至少为源信号频率的两倍。换句话说,我们想要在数字合成出来的波形还能还原原始信号的信息的话,我们一个周期中至少要有两个以上的“点”。

但一般来讲,为了波形的完整,我们一个周期中最少保留的点还会多一些。如果假设一个周期最小4个点,采样频率为为100MSPS,那我们可以还原的源信号的频率最大为25M

我们通常对一个周期采样的点数为2^N个,在这里,我采样为2的8次方,即256个。

采样的过程可以通过matlab进行模拟,设置好采样的位宽和深度便可以生成采样数据。
<pre> width=10; %rom的位宽
depth=1024; %rom的深度
x=linspace(0,2*pi,depth); %在一个周期内产生1024个采样点
y_sin=sin(x); %生成正弦数据
y_sin=round(y_sin*(2^(width-1)-1))+2^(width-1)-1; %将余弦数据全部转换为整数

fid=fopen('C:\Users\Leixx\Desktop\sin_txt.txt','w'); %创建.txt文件
fprintf(fid,'%d;\n',y_sin); %向.txt文件中写入数据
fclose(fid); %关闭.txt文件
</pre>

得到的部分采样数据如下:
<pre>511;
514;
517;
520;
524;
527;
530;
533;
536;
539;
542;
545;
549;
552;
555;
558;
561;
564;
567;
570;
574;
577;
580;
583;
586;
589;
592;
595;
598;
602;
605;
608;
611;
614;
617;
620;
623;
626;
629;
632;
635;
638;
641;
644;
647;
650;
653;
656;
</pre>

<strong>2.合成</strong>
DDS技术的核心,简单来说就是将我们的抽样数据还原成模拟信号。还原的方式和文章讲到的一样:以一定的频率将抽样数据依次输出,就可以还原波形。

假设,以100M的频率输出我们的1024个抽样数据,则将会得到一个频率为
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

的正弦波。这就达到了最初的信号输出。

那如何调频呢?
调频的方案有两种:一种是改变我们的时钟频率,将我们读取抽样数据的速度变快或者变慢,这样就可以改变频率。这种方法对于当下很多开发板固定的晶振频率来说比较难以实现。

另一种方案就是减少我们输出的抽样数据,输出的抽样数据越少,按照上面的公式,频率便会越高。

比如说,我们最开始查数据是按照依次加一的方式,那我们改成依次加二,显然,这样做之后,输出频率便会提高。但也会带来一个问题,我们输出的点数少了,那么点与点之间不再平滑,输出的波形会变得阶梯化。

显然加一会得到一个频率,加二会得到另一个频率,但这两个频率都不是我想要的怎么办?

如何精准调频?
假设我们需要得到一个频率很低的信号,1KHZ,而我们的时钟频率为100M,我们在一个周期内输出1024个点也达不到这样的频率。因此我们就需要在输出的每个数据之间进行等待,可以通过设置计数器来解决这个问题。为了使输出的信号尽可能的低,我们设置一个32位的累加器。将高10位作为查表的地址。对于1KHZ,有
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

其余频率对应的累加值均可以按此公式计算。

如何解决阶梯化?
从数字上来看,波形变得阶梯化是因为我们输出的抽样数据减少,点与点之间不再平滑。但是从另一个角度理解,波形变得阶梯化的原因是因为叠加了其他杂波

我们简单地波形变得阶梯化理解为趋近于方波。下图是方波的合成。
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

由方波的傅里叶级展开式
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

因此,若我们的波形也是叠加了高频的谐波导致波形阶梯化,那么就通过低通滤波器来滤除高频谐波,得到平滑的波形。

DDS的原理图如下:
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

<strong>3.实现</strong>
基于FPGA的DDS,就是按照上面的原理来实现的。

(1)储存波形
<pre>reg [9:0] wave_sin_buf;
always @(*) begin
case (addr)
8'd0 : begin wave_sin_buf&lt;=511; end
8'd1 : begin wave_sin_buf&lt;=514; end
8'd2 : begin wave_sin_buf&lt;=517; end
8'd3 : begin wave_sin_buf&lt;=520; end
8'd4 : begin wave_sin_buf&lt;=524; end
8'd5 : begin wave_sin_buf&lt;=527; end
8'd6 : begin wave_sin_buf&lt;=530; end
8'd7 : begin wave_sin_buf&lt;=533; end
8'd8 : begin wave_sin_buf&lt;=536; end
8'd9 : begin wave_sin_buf&lt;=539; end
8'd10 : begin wave_sin_buf&lt;=542; end
8'd11 : begin wave_sin_buf&lt;=545; end
8'd12 : begin wave_sin_buf&lt;=549; end
8'd13 : begin wave_sin_buf&lt;=552; end
8'd14 : begin wave_sin_buf&lt;=555; end
8'd15 : begin wave_sin_buf&lt;=558; end
8'd16 : begin wave_sin_buf&lt;=561; end
8'd17 : begin wave_sin_buf&lt;=564; end
</pre>

我这里只储存了256个数据,至于原因,后面会讲到。

(2)累加
设置一个32位相位控制字和频率控制字,进行累加。
<pre>always @(posedge clk_165m or negedge rst_n) begin
if (!rst_n) begin
addr_r0 &lt; = 0;
addr_r1 &lt; = 0;
end
else begin
addr_r0 &lt; = addr_r0 + fre_word; //N位累加器
addr_r1 &lt; = addr_r0 + pha_word; //N位寄存器,带相位偏移
end
end
</pre>

(3)查表
<pre>/查表,按照不同的相位
always @(*) begin
case (addr_cache[9:8])
2'b00:begin addr_r&lt;=addr_cache[7:0]; end
2'b01:begin addr_r&lt;=addr_cache[7:0]^8'b1111_1111; end
//异或可以求得原值的8位补值(原值与现值相加得到256)
2'b10:begin addr_r&lt;=addr_cache[7:0]; end
2'b11:begin addr_r&lt;=addr_cache[7:0]^8'b1111_1111; end
default: begin addr_r &lt;= 8'd0; end
endcase
end
</pre>

这里我只储存了2^8个波形数据,是为了节省寄存器资源。因为一个周期的正弦波的四个相位实际上数据是有关联的,知道第一相位的数据,便可推导出另外三个相位的数据。

代码中,00表示第一相位,此时按照正常的查表顺序即可。

01表示第二相位,此时,查表的顺序应当是2^8-地址值。但实际上这个减法的操作就是异或的操作。

相减后,地址值8位中原本的“1”变为0,原本的“0”变为1,正好和异或的原理相同。这里为了方便,就直接写了异或。实际上写256-addr_cache[7:0]也是一个效果。

其余两个相位,查表的方式类似。

(4)转换
因为我这里的256个数据是第一相位的,而第三第四相位的数据是等于2^10减去第一相位的值,因此这里需要将输出的数据转换一下。
<pre>reg [9:0]dac_data_r;
always @(*) begin
case (addr_cache[9])
1'b0 : begin dac_data_r &lt;= wave_sin_buf; end
1'b1 : begin dac_data_r &lt;= wave_sin_buf^10'b11_1111_1111; end
default : begin dac_data_r &lt;= 10'd0; end
endcase
end
</pre>

(5)测试
通过串口分别发送01999b60和031a5f60,由上面公式
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

可以算出分别是1M和2M的频率控制字
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>
<center><img src="http://xilinx.eetrend.com/files/2021-03/%E5%8D%9A%E5%AE%A2/100063224-12…; alt=""></center>

以上便是DDS的FPGA实现。