当前位置:   article > 正文

用FPGA设计DDS信号发生器_dds信号发生器设计

dds信号发生器设计

DDS介绍:直接数字式频率合成器(Direct Digital Synthesizer)的英文缩写,与传统的频率合成器相比,DDS具有低成本、低功耗、高分辨率和快速转换时间等优点,习惯称它为信号发生器。产生不同波形或不同频率的信号波形

DDS原理:

当我们对正弦波形进行采样时,每个完整周期被均匀地划分为m个采样点,这些点按顺序标记为1至m。为了输出这样的波形,每当参考时钟f_c产生一个脉冲时,就会输出一个采样点。完成一个完整的正弦波周期输出需要m个时钟周期,因此输出的波形频率f_a可以表示为f_c除以m,即f_a = f_c/m

在这个采样和输出过程中,相位累加器起着关键的作用。它类似于一个计数器,每当时钟脉冲到来时,相位累加器的值就会增加。在基础情况下,相位累加器的步进为1,并输出对应的采样点。当达到m时,相位累加器会回绕到1,并开始下一个周期的输出。

改变相位累加器的步进值,输出的波形频率也会相应地改变。例如,如果步进值设置为2,那么相位累加器就会在每个时钟周期跳过一个采样点,输出的是第2i个采样点(i1m/2,然后重新开始)。这种输出方式产生的波形频率是原始频率的两倍,即f_b = 2f_a

当相位累加器的步进为B时,输出的波形频率将是基础频率f_aB倍。这里的f_a,即最小的输出频率,通常被称为频率分辨率或步进间隔。

因此,给定一个特定的频率控制字B,我们就可以通过调整相位累加器的步进值来输出不同频率的正弦波。频率输出的通用公式为:f_a = (f_c/m) * B。根据该公式可以精确地控制输出波形的频率。

当累加器的长度为N时,它可以拥有2^N个存储单元来存储采样数据。对一个周期的波形进行2^N个点的采样,即m = 2^N,此时输出频率f_a、系统时钟频率f_c、累加器长度N以及频率控制字B之间的关系可以用公式表示:f_a = f_c × B / 2^N

为了确保波形输出不失真,根据奈奎斯特采样定理,系统时钟频率f_c必须至少是被采样信号最高频率的两倍。因此,在这个情况下,对一个完整周期进行了2^N个点的采样,理论上B的最大值可以达到2^N - 1,以充分利用整个频率范围。

为了提高DDS的精度,期望分母(即采样点的数量)尽可能大,以更准确地逼近原始波形。然而,由于硬件资源的限制,无法提供无限多的存储空间来存储所有采样点。因此,我们需要对采样点进行量化处理。

量化是一种通过减少表示每个采样点所需的位数来减少存储空间需求的技术。如果量化单位为K,则前K个点的值将相同,并等于第一个采样值;接下来的K个点将具有第二个采样值,依此类推。通过这种方式,只需要m/K个存储单元来存储m/K个独特的采样点值,从而显著减少了所需的存储空间。这种量化方法可以在一定程度上平衡存储需求与波形精度之间的关系。

DDS 的杂散噪音来源之一就是相位累加器相位舍位造成的杂散。现有的频率控制主要采用二进制频率控制原理。这是因为 FPGA 采用二进制的数据处理机制以及波形存储器的二进制寻址方式,通常情况下mK,m,K都是二进制整数,如m = 2^N,这就要求对计算结果进行十进制近似取舍,同时在查表过程中,相位累加器的低BB = N - W会被舍去,舍位操作会引入误差,造成波形发生器的输出频率以及频率分辨率存的误差。

DDS运用:

DDS的基本结构主要由相位累加器、相位调制器、波形数据表ROM、D/A转换器 四大结构组成

相位累加器

系统时钟CLK为整个系统的工作时钟,频率为f_{CLK};频率字输入F_{WORD},一般为整数,数值大小控制输出信号的频率大小,数值越大输出信号频率越高,反之,输出信号频率越低。相位字输入P_{WORD}为整数,数值大小控制输出信号的相位偏移,主要用于相位的信号调制。

相位累加器的输入为频率字输入K,表示相位增量,设其位宽为N,满足等式K = 2N * f_{OUT }/ f_{CLK }。其在输入相位累加器之前,在系统时钟同步下做数据寄存,数据改变时不会干扰相位累加器的正常工作。

相位调制器相位调制器接收相位累加器输出的相位码, 在这里加上一个相位偏移值P_{WORD},主要用于信号的相位调制,如应用于通信方面的相移键控等,不使用此部分时可以去掉,或者将其设为一个常数输入,同样相位字输入也要做寄存。
波形数据表ROM波形数据表ROM中存有一个完整周期的正弦波信号。假设波形数据ROM的地址位宽为12位,存储数据位宽为8位,即ROM有212 = 4096个存储空间,每个存储空间可存储1字节数据。将一个周期的正弦波信号,沿横轴等间隔采样212 = 4096次,每次采集的信号幅度用1字节数据表示,最大值为255,最小值为0。将4096次采样结果按顺序写入ROM的4096个存储单元,一个完整周期正弦波的数字幅度信号写入了波形数据表ROM中。波形数据表ROM以相位调制器传入的相位码为ROM读地址,将地址对应存储单元中的电压幅值数字量输出。
D/A转换器

D/A转换器将输入的电压幅值数字量转换为模拟量输出,就得到输出信号OUT

输出信号OUT的信号频率f_{OUT }= K * f_{CLK} / 2N。当K = 1时,可得DDS最小分辨率为:f_{OUT} = f_{CLK} / 2N,此时输出信号频率最低。根据采样定理,K的最大值应小于2N / 2

结构框图:

 实验目的:产生可以变化波形的输出信号

模块框图:

dds模块代码(该代码参考了野火的DDS信号发生器的设计与验证)

链接:3. 简易DDS信号发生器的设计与验证 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档 (embedfire.com)

  1. module dds
  2. (
  3. input wire clk_50Mhz , //系统时钟,50MHz
  4. input wire rst_n , //复位信号,低电平有效
  5. input wire flag_data , //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
  6. input wire [3:0] data , //输入按键的特征编码
  7. output wire [7:0] data_out //波形输出
  8. );
  9. parameter sin_wave = 4'b0000 , //正弦波
  10. squ_wave = 4'b0001 , //方波
  11. tri_wave = 4'b0010 , //三角波
  12. saw_wave = 4'b0011 ; //锯齿波
  13. parameter FREQ_CTRL = 32'd42949 , //相位累加器单次累加值
  14. PHASE_CTRL = 12'd1024 ; //相位偏移量
  15. reg [31:0] fre_add ; //相位累加器
  16. reg [11:0] rom_addr_reg; //相位调制后的相位码
  17. reg [13:0] rom_addr ; //ROM读地址
  18. //fre_add:相位累加器
  19. always@(posedge clk_50Mhz or negedge rst_n) begin
  20. if(rst_n == 1'b0) begin
  21. fre_add <= 32'd0;
  22. end
  23. else begin
  24. fre_add <= fre_add + FREQ_CTRL;
  25. end
  26. end
  27. //rom_addr:ROM读地址
  28. always@(posedge clk_50Mhz or negedge rst_n) begin
  29. if(rst_n == 1'b0) begin
  30. rom_addr <= 14'd0;
  31. rom_addr_reg <= 11'd0;
  32. end
  33. else if(flag_data == 1'b1) begin
  34. case(data)
  35. sin_wave:
  36. begin
  37. rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
  38. rom_addr <= rom_addr_reg;
  39. end //正弦波
  40. squ_wave:
  41. begin
  42. rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
  43. rom_addr <= rom_addr_reg + 14'd4096;
  44. end //方波
  45. tri_wave:
  46. begin
  47. rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
  48. rom_addr <= rom_addr_reg + 14'd8192;
  49. end //三角波
  50. saw_wave:
  51. begin
  52. rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
  53. rom_addr <= rom_addr_reg + 14'd12288;
  54. end //锯齿波
  55. default:
  56. begin
  57. rom_addr_reg <= fre_add[31:20] + PHASE_CTRL;
  58. rom_addr <= rom_addr_reg;
  59. end //正弦波
  60. endcase
  61. end
  62. end
  63. rom_wave rom_wave_inst
  64. (
  65. .address (rom_addr ), //ROM读地址
  66. .clock (clk_50Mhz ), //读时钟
  67. .q (data_out ) //读出波形数据
  68. );
  69. endmodule

key4x4模块代码,见我的这一篇文章 :用FPGA设计矩阵键盘-CSDN博客

top_dds模块代码

  1. module top_dds(
  2. input wire clk_50Mhz , //系统时钟,50MHz
  3. input wire rst_n , //复位信号,低电平有效
  4. input wire [3:0]data_in , //输入逐行扫描信号
  5. output wire [3:0]data_out , //输出逐列扫描信号
  6. output wire dac_clk , //输出DAC模块时钟
  7. output wire [7:0]dac_data //输出DAC模块波形数据
  8. );
  9. wire flag_data ; //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
  10. wire [3:0]data ; //按键的特征编码
  11. //dac_clka:DAC模块时钟
  12. assign dac_clk = ~clk_50Mhz;
  13. dds dds_inst
  14. (
  15. .clk_50Mhz (clk_50Mhz ) , //系统时钟,50MHz
  16. .rst_n (rst_n ) , //复位信号,低电平有效
  17. .flag_data (flag_data ) , //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
  18. .data (data ) , //输入按键的特征编码
  19. .data_out (dac_data ) //波形输出
  20. );
  21. key4x4 key4x4_inst
  22. (
  23. .clk_50Mhz (clk_50Mhz) , //系统时钟50Mhz
  24. .rst_n (rst_n ) , //全局复位
  25. .data_in (data_in ) , //输入逐行扫描信号
  26. .flag_data (flag_data) , //flag_data为1时表示消抖后检测到按键被按下,维持一个时钟的高电平
  27. .data (data ) , //data_out为1时表示当前按键的特征编码
  28. .data_out (data_out ) //输出逐列扫描信号
  29. );
  30. endmodule

用于生成波形数据表的mif文件的python代码

  1. import numpy as np
  2. from scipy.signal import sawtooth
  3. import matplotlib.pyplot as plt
  4. from scipy.signal import square
  5. # 参数定义
  6. F1 = 1 # 信号频率
  7. Fs = 2 ** 12 # 采样频率
  8. P1 = 0 # 信号初始相位
  9. N = 2 ** 12 # 采样点数
  10. ADC = 2 ** 7 - 1 # 直流分量
  11. A = 2 ** 7 # 信号幅度
  12. # 生成时间向量
  13. t = np.arange(0, N / Fs, 1 / Fs)
  14. # 生成信号
  15. s1=A*np.sin(2*np.pi*F1*t + np.pi*P1/180) + ADC; # 正弦波信号
  16. s2=A*square(2*np.pi*F1*t + np.pi*P1/180) + ADC; # 方波信号
  17. s3=A*sawtooth(2*np.pi*F1*t + np.pi*P1/180,0.5) + ADC; # 三角波信号
  18. s4=A*sawtooth(2*np.pi*F1*t + np.pi*P1/180) + ADC; # 锯齿波信号
  19. # 绘制信号
  20. plt.figure(figsize=(10, 6))
  21. plt.plot(t, s1, label='Sinusoidal Wave')
  22. plt.plot(1+t, s2, label='Square Wave')
  23. plt.plot(2+t, s3, label='Triangular Wave')
  24. plt.plot(3+t, s4, label='Sawtooth Wave')
  25. plt.xlabel('Time (s)')
  26. plt.ylabel('Amplitude')
  27. plt.title('Generated Waveforms')
  28. plt.legend()
  29. plt.grid(True)
  30. plt.show()
  31. # 写入mif文件
  32. mif_filename = 'wave_16384x8.mif'
  33. with open(mif_filename, 'w') as fild:
  34. # 写入mif文件头
  35. fild.write('WIDTH=8;\n')
  36. fild.write('\n')
  37. fild.write('DEPTH=16384;\n')
  38. fild.write('\n')
  39. fild.write('ADDRESS_RADIX=UNS;\n')
  40. fild.write('\n')
  41. fild.write('DATA_RADIX=UNS;\n')
  42. fild.write('\n')
  43. fild.write('CONTENT\tBEGIN\n')
  44. # 对信号进行四舍五入并处理负值(负值强制置零)
  45. s0_sin = np.round(s1).astype(int)
  46. s0_sin[s0_sin < 0] = 0
  47. s0_square = np.round(s2).astype(int)
  48. s0_square[s0_square < 0] = 0
  49. s0_triangle = np.round(s3).astype(int)
  50. s0_triangle[s0_triangle < 0] = 0
  51. s0_sawtooth = np.round(s4).astype(int)
  52. s0_sawtooth[s0_sawtooth < 0] = 0
  53. # 写入mif文件内容
  54. for j, (signal, offset) in enumerate([(s0_sin, 0), (s0_square, N), (s0_triangle, 2*N), (s0_sawtooth, 3*N)], start=1):
  55. for i, value in enumerate(signal, start=1):
  56. address = i - 1 + offset
  57. fild.write(f'\t{address}\t:')
  58. fild.write(f'{value}\n')
  59. fild.write('END;\n')

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/945141
推荐阅读
相关标签
  

闽ICP备14008679号