zl程序教程

您现在的位置是:首页 >  其他

当前栏目

数字设计项目一:直接信号发生器(DDS)产生爱心波

项目 设计 数字 直接 信号 产生 爱心 发生器
2023-09-14 09:16:16 时间

前言

         DDS,即Direct Digital Synthesis ,直接数字频率合成器。我们常常用DDS配合DAC芯片来产生信号波形,其原理也十分简单:就是把目标波形上每个点的值逐个输出到DAC芯片上去以此产生模拟波形。

        DDS主要有两种合成方式:查表法和计算法。打个比方就像用Oled显示一个圆圈一样,一种方法是把装在内存里的圆圈图片,一行一行地输出到显示屏上显示,这就是所谓查表法,计算法就是利用程序实时计算出每个点的数值,然后在Oled上打点显示。

        下面我们直接来看DDS的基本结构:

 图片来源网络,侵删。

        初看有些迷糊,但是其实结构很简单,主要的部件就是虚线框里的相位累加器。把相位累加器当成一个黑盒,它的输入是频率控制字,相位控制字,输出是波形存储器地址。

频率控制字和相位控制字的作用是什么?举个例子就明白了:

波形存储器(一个周期)
101112131415161718192021
地址01234567891011

         频率控制字是每个时钟周期反复累加的,即 K(x = t) = Kt(t为时间)假设相位控制字P为0,K=2,那么一个周期内,输出到波形存储器的地址的变换就为:0、2、4、6、8、10,输出的值为10、12、14、16、18、20。这样输出一个周期的波形就要6个时钟周期,很容易计算出实际输出的周期T和K的关系为: T = (12/K)*Tclk,同理当波形存储器有n个数据时,T = (n/K)*Tclk。

        这时如果相位控制字为1的话,那么相应地址变换就为 0+1 = 1,2+1 = 3,4+1 =5 ,9,11。可以看到,相位控制字并不随时间增加而增加。因此可以得到总的地址索引数 = K*t + P,同时我们也可以理解,相位控制字并不会影响输出波形的频率(此时步进间距依然为Δ = K),但整体的波形就好像左移或者右移动了,这就是相位控制字控制相位的原理。

        从上面的分析,可以得到几个重要的结论:

  • 最小的频率Tmin  = n *Tclk (n为查找表元素个数,Tclk为时钟周期,此时K = 1)
  • 输出波形频率与K的关系: Tout = (n/K)*Tclk

实战

        先利用matlab采集波形信号,保存成mif文件,再由quartus IV ROM IP 核读取,并由modelsim 仿真实现。

%matlab 爱心波产生程序

% 数据深度2^12
N=4069;
% 数据宽度2^7
P=128;
a=10;
a5(1:1:N)=0;
b5=1:1:N;
figure(5);
a5(b5) = real(round((P/4-4)*(abs((b5-N/2)/1000).^(2/3)+(0.9*sqrt((3.3-((b5-N/2)/1000).^2))).*sin(a*pi*((b5-N/2))/1000)+1.8)));
plot(b5,a5,'r','LineWidth',2);
axis([0, N, 0, P]); 

%创建 mif 文件
fild = fopen('sin_wave_4096x8.mif','wt');
 %写入 mif 文件头
fprintf(fild, '%s\n','WIDTH=8;'); %位宽
 fprintf(fild, '%s\n\n','DEPTH=4096;'); %深度
 fprintf(fild, '%s\n','ADDRESS_RADIX=UNS;'); %地址格式
 fprintf(fild, '%s\n\n','DATA_RADIX=UNS;'); %数据格式
 fprintf(fild, '%s\t','CONTENT'); %地址
fprintf(fild, '%s\n','BEGIN'); %开始
for i = 1:N
s0(i) = round(a5(i)); %对小数四舍五入以取整
 if s0(i) <0 %负 1 强制置零
 s0(i) = 0
 end
 fprintf(fild, '\t%g\t',i-1); %地址编码
fprintf(fild, '%s\t',':'); %冒号
 fprintf(fild, '%d',s0(i)); %数据写入
 fprintf(fild, '%s\n',';'); %分号,换行
end
fprintf(fild, '%s\n','END;'); %结束
fclose(fild);


运行之后可以看见产生如下图像,以及一个mif文件。 

 下面给出整体架构的Verilog代码

module sin_test(
input wire clk,
input wire reset,
input wire [31:0] Fword,
output wire [7:0] sin_out
);

wire [11:0] Pword;

assign Pword = 12'd0;

wire [7:0] wave_data;
reg [7:0] wave_data_r;
reg [31:0] Fcnt;
wire [11:0] rom_addr;

assign sin_out = wave_data_r;

always@(posedge clk or posedge reset )begin
	if(reset == 1'b1)
		Fcnt <= 32'd0;
	else
		Fcnt <= Fcnt + Fword;
end
assign rom_addr = Fcnt[31:20] + Pword;

love_rom rom(
.address(rom_addr),
.clock(clk),
.q(wave_data)
);

always@(posedge clk)begin
	wave_data_r <= wave_data;
end
endmodule

其中的love_rom需要例化quartus的IP核,这一部分我会在下一篇文章中介绍,不懂的可以去网上查,这里不作详细展开。下面是仿真测试的代码

`timescale 1ns/1ps

module tb_sin_test();

reg clk;
reg reset;
reg [31:0] Fword;
wire [7:0] sin_out;

initial begin
	clk = 1'b0;
	reset = 1'b0;
    Fword = 32'd8589933;
	#20 reset = 1'b1;
	#20 reset = 1'b0;

end

always begin
	#10 clk = ~clk;
end

sin_test sin1(
.clk(clk),
.reset(reset),
.sin_out(sin_out),
.Fword(Fword)
);

endmodule

        在modelsim内运行后,观察波形,将sin_out变量用图像显示,即可得到下面的波形输出:

        至此,功能得以实现;

路漫漫其修远兮,吾将上下而求索。