赞
踩
DDS原理:
当我们对正弦波形进行采样时,每个完整周期被均匀地划分为个采样点,这些点按顺序标记为1至。为了输出这样的波形,每当参考时钟产生一个脉冲时,就会输出一个采样点。完成一个完整的正弦波周期输出需要个时钟周期,因此输出的波形频率可以表示为除以,即 在这个采样和输出过程中,相位累加器起着关键的作用。它类似于一个计数器,每当时钟脉冲到来时,相位累加器的值就会增加。在基础情况下,相位累加器的步进为1,并输出对应的采样点。当达到m时,相位累加器会回绕到1,并开始下一个周期的输出。 改变相位累加器的步进值,输出的波形频率也会相应地改变。例如,如果步进值设置为2,那么相位累加器就会在每个时钟周期跳过一个采样点,输出的是第个采样点(从到,然后重新开始)。这种输出方式产生的波形频率是原始频率的两倍,即。 当相位累加器的步进为时,输出的波形频率将是基础频率的倍。这里的,即最小的输出频率,通常被称为频率分辨率或步进间隔。 因此,给定一个特定的频率控制字B,我们就可以通过调整相位累加器的步进值来输出不同频率的正弦波。频率输出的通用公式为:。根据该公式可以精确地控制输出波形的频率。 当累加器的长度为N时,它可以拥有个存储单元来存储采样数据。对一个周期的波形进行个点的采样,即,此时输出频率、系统时钟频率、累加器长度N以及频率控制字B之间的关系可以用公式表示: 为了确保波形输出不失真,根据奈奎斯特采样定理,系统时钟频率必须至少是被采样信号最高频率的两倍。因此,在这个情况下,对一个完整周期进行了个点的采样,理论上B的最大值可以达到,以充分利用整个频率范围。 为了提高DDS的精度,期望分母(即采样点的数量)尽可能大,以更准确地逼近原始波形。然而,由于硬件资源的限制,无法提供无限多的存储空间来存储所有采样点。因此,我们需要对采样点进行量化处理。 量化是一种通过减少表示每个采样点所需的位数来减少存储空间需求的技术。如果量化单位为,则前个点的值将相同,并等于第一个采样值;接下来的个点将具有第二个采样值,依此类推。通过这种方式,只需要个存储单元来存储个独特的采样点值,从而显著减少了所需的存储空间。这种量化方法可以在一定程度上平衡存储需求与波形精度之间的关系。 DDS 的杂散噪音来源之一就是相位累加器相位舍位造成的杂散。现有的频率控制主要采用二进制频率控制原理。这是因为 FPGA 采用二进制的数据处理机制以及波形存储器的二进制寻址方式,通常情况下都是二进制整数,如,这就要求对计算结果进行十进制近似取舍,同时在查表过程中,相位累加器的低位会被舍去,舍位操作会引入误差,造成波形发生器的输出频率以及频率分辨率存的误差。 |
DDS运用:
DDS的基本结构主要由相位累加器、相位调制器、波形数据表ROM、D/A转换器 四大结构组成
相位累加器 | 系统时钟CLK为整个系统的工作时钟,频率为;频率字输入,一般为整数,数值大小控制输出信号的频率大小,数值越大输出信号频率越高,反之,输出信号频率越低。相位字输入为整数,数值大小控制输出信号的相位偏移,主要用于相位的信号调制。 相位累加器的输入为频率字输入K,表示相位增量,设其位宽为N,满足等式。其在输入相位累加器之前,在系统时钟同步下做数据寄存,数据改变时不会干扰相位累加器的正常工作。 |
相位调制器 | 相位调制器接收相位累加器输出的相位码, 在这里加上一个相位偏移值,主要用于信号的相位调制,如应用于通信方面的相移键控等,不使用此部分时可以去掉,或者将其设为一个常数输入,同样相位字输入也要做寄存。 |
波形数据表ROM | 波形数据表ROM中存有一个完整周期的正弦波信号。假设波形数据ROM的地址位宽为12位,存储数据位宽为8位,即ROM有212 = 4096个存储空间,每个存储空间可存储1字节数据。将一个周期的正弦波信号,沿横轴等间隔采样212 = 4096次,每次采集的信号幅度用1字节数据表示,最大值为255,最小值为0。将4096次采样结果按顺序写入ROM的4096个存储单元,一个完整周期正弦波的数字幅度信号写入了波形数据表ROM中。波形数据表ROM以相位调制器传入的相位码为ROM读地址,将地址对应存储单元中的电压幅值数字量输出。 |
D/A转换器 | D/A转换器将输入的电压幅值数字量转换为模拟量输出,就得到输出信号。 输出信号的信号频率。当K = 1时,可得DDS最小分辨率为:,此时输出信号频率最低。根据采样定理,K的最大值应小于。 |
结构框图:
实验目的:产生可以变化波形的输出信号
模块框图:
dds模块代码(该代码参考了野火的DDS信号发生器的设计与验证)
链接:3. 简易DDS信号发生器的设计与验证 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档 (embedfire.com)
- module dds
- (
- input wire clk_50Mhz , //系统时钟,50MHz
- input wire rst_n , //复位信号,低电平有效
- input wire flag_data , //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
- input wire [3:0] data , //输入按键的特征编码
- output wire [7:0] data_out //波形输出
- );
-
-
- parameter sin_wave = 4'b0000 , //正弦波
- squ_wave = 4'b0001 , //方波
- tri_wave = 4'b0010 , //三角波
- saw_wave = 4'b0011 ; //锯齿波
- parameter FREQ_CTRL = 32'd42949 , //相位累加器单次累加值
- PHASE_CTRL = 12'd1024 ; //相位偏移量
-
-
- reg [31:0] fre_add ; //相位累加器
- reg [11:0] rom_addr_reg; //相位调制后的相位码
- reg [13:0] rom_addr ; //ROM读地址
-
- //fre_add:相位累加器
- always@(posedge clk_50Mhz or negedge rst_n) begin
- if(rst_n == 1'b0) begin
- fre_add <= 32'd0;
- end
- else begin
- fre_add <= fre_add + FREQ_CTRL;
- end
- end
-
-
- //rom_addr:ROM读地址
- always@(posedge clk_50Mhz or negedge rst_n) begin
- if(rst_n == 1'b0) begin
- rom_addr <= 14'd0;
- rom_addr_reg <= 11'd0;
- end
- else if(flag_data == 1'b1) begin
- case(data)
- sin_wave:
- begin
- rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
- rom_addr <= rom_addr_reg;
- end //正弦波
- squ_wave:
- begin
- rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
- rom_addr <= rom_addr_reg + 14'd4096;
- end //方波
- tri_wave:
- begin
- rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
- rom_addr <= rom_addr_reg + 14'd8192;
- end //三角波
- saw_wave:
- begin
- rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
- rom_addr <= rom_addr_reg + 14'd12288;
- end //锯齿波
- default:
- begin
- rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
- rom_addr <= rom_addr_reg;
- end //正弦波
- endcase
- end
- end
- rom_wave rom_wave_inst
- (
- .address (rom_addr ), //ROM读地址
- .clock (clk_50Mhz ), //读时钟
- .q (data_out ) //读出波形数据
- );
-
- endmodule
key4x4模块代码,见我的这一篇文章 :用FPGA设计矩阵键盘-CSDN博客
top_dds模块代码
- module top_dds(
- input wire clk_50Mhz , //系统时钟,50MHz
- input wire rst_n , //复位信号,低电平有效
- input wire [3:0]data_in , //输入逐行扫描信号
- output wire [3:0]data_out , //输出逐列扫描信号
- output wire dac_clk , //输出DAC模块时钟
- output wire [7:0]dac_data //输出DAC模块波形数据
- );
-
-
- wire flag_data ; //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
- wire [3:0]data ; //按键的特征编码
-
- //dac_clka:DAC模块时钟
- assign dac_clk = ~clk_50Mhz;
-
- dds dds_inst
- (
- .clk_50Mhz (clk_50Mhz ) , //系统时钟,50MHz
- .rst_n (rst_n ) , //复位信号,低电平有效
- .flag_data (flag_data ) , //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
- .data (data ) , //输入按键的特征编码
- .data_out (dac_data ) //波形输出
- );
-
-
- key4x4 key4x4_inst
- (
- .clk_50Mhz (clk_50Mhz) , //系统时钟50Mhz
- .rst_n (rst_n ) , //全局复位
- .data_in (data_in ) , //输入逐行扫描信号
- .flag_data (flag_data) , //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
- .data (data ) , //data_out为1时表示当前按键的特征编码
- .data_out (data_out ) //输出逐列扫描信号
- );
-
- endmodule
用于生成波形数据表的mif文件的python代码
- import numpy as np
- from scipy.signal import sawtooth
- import matplotlib.pyplot as plt
- from scipy.signal import square
-
- # 参数定义
- F1 = 1 # 信号频率
- Fs = 2 ** 12 # 采样频率
- P1 = 0 # 信号初始相位
- N = 2 ** 12 # 采样点数
- ADC = 2 ** 7 - 1 # 直流分量
- A = 2 ** 7 # 信号幅度
-
- # 生成时间向量
- t = np.arange(0, N / Fs, 1 / Fs)
-
- # 生成信号
- s1=A*np.sin(2*np.pi*F1*t + np.pi*P1/180) + ADC; # 正弦波信号
- s2=A*square(2*np.pi*F1*t + np.pi*P1/180) + ADC; # 方波信号
- s3=A*sawtooth(2*np.pi*F1*t + np.pi*P1/180,0.5) + ADC; # 三角波信号
- s4=A*sawtooth(2*np.pi*F1*t + np.pi*P1/180) + ADC; # 锯齿波信号
-
- # 绘制信号
- plt.figure(figsize=(10, 6))
-
-
- plt.plot(t, s1, label='Sinusoidal Wave')
- plt.plot(1+t, s2, label='Square Wave')
- plt.plot(2+t, s3, label='Triangular Wave')
- plt.plot(3+t, s4, label='Sawtooth Wave')
-
- plt.xlabel('Time (s)')
- plt.ylabel('Amplitude')
- plt.title('Generated Waveforms')
- plt.legend()
- plt.grid(True)
- plt.show()
-
- # 写入mif文件
- mif_filename = 'wave_16384x8.mif'
- with open(mif_filename, 'w') as fild:
- # 写入mif文件头
- fild.write('WIDTH=8;\n')
- fild.write('\n')
- fild.write('DEPTH=16384;\n')
- fild.write('\n')
- fild.write('ADDRESS_RADIX=UNS;\n')
- fild.write('\n')
- fild.write('DATA_RADIX=UNS;\n')
- fild.write('\n')
- fild.write('CONTENT\tBEGIN\n')
-
- # 对信号进行四舍五入并处理负值(负值强制置零)
- s0_sin = np.round(s1).astype(int)
- s0_sin[s0_sin < 0] = 0
- s0_square = np.round(s2).astype(int)
- s0_square[s0_square < 0] = 0
- s0_triangle = np.round(s3).astype(int)
- s0_triangle[s0_triangle < 0] = 0
- s0_sawtooth = np.round(s4).astype(int)
- s0_sawtooth[s0_sawtooth < 0] = 0
-
- # 写入mif文件内容
- for j, (signal, offset) in enumerate([(s0_sin, 0), (s0_square, N), (s0_triangle, 2*N), (s0_sawtooth, 3*N)], start=1):
- for i, value in enumerate(signal, start=1):
- address = i - 1 + offset
- fild.write(f'\t{address}\t:')
- fild.write(f'{value}\n')
-
- fild.write('END;\n')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。