第三十九章:DDS信号发生器实验

本文对应原书第1021页起,第五篇「实战篇」。

本章在讲什么?(先看这个)

第三十七章的波形发生器有个明显局限:想换波形就得重新生成 ROM 初始化文件重新烧写,想调频率只能改 FREQ_ADJ 参数重新编译。本章用 DDS(Direct Digital Synthesis,直接数字频率合成) 架构彻底解决这个问题:一个工程里装好正弦波、方波、三角波、锯齿波四种波形,通过两个按键就能随时切换波形种类和输出频率,最高输出 1MHz 正弦波。

DDS 是信号发生器、软件无线电、通信调制解调的核心技术,学会本章的"相位累加器 + 相位调制器 + ROM 查表"三件套,就掌握了它的全部精髓。

核心概念拆解

DDS 的三段式结构

图39.1.1 DDS基本结构图

  1. 相位累加器:每个时钟周期把频率控制字(本设计即 FREQ_ADJ 类似的步进值)累加一次,累加器溢出即代表一个完整波形周期。累加得越快,输出频率越高。
  2. 相位调制器:把累加器高位作为 ROM 地址,相当于把"相位"翻译成"波形表里的位置"。本设计用累加值的高位 + 波形选择信号组合寻址。
  3. ROM(幅度表):存波形数据,按相位查出一个幅度值送 DAC。

输出频率公式:fo = 时钟频率 × 频率控制字 / 2^N(N 是相位累加器位宽)。控制字越小频率越低、分辨率越细。

MatLab 生成四种波形的 COE 文件

本设计的 ROM 一共存 400 个点、每点 8 位,按顺序拼接四种波形各 100 点:

地址区间 波形
0~99 正弦波
100~199 方波
200~299 三角波
300~399 锯齿波

生成参数:Fs=100(每波形 100 点)、N=100、幅度 A=2^7(128)、ADC=2^7-1。书上给出了四段 MatLab 脚本,以正弦波为例:

Fs = 100; N = 100; A = 2^7; ADC = 2^7 - 1;
t = 0 : 2*pi/Fs : 2*pi;
sin_data = sin(t);
sin_data = round(sin_data * A);
plot(sin_data);
fid = fopen('sin.coe','w');
fprintf(fid,'memory_initialization_radix = 10;\n');
fprintf(fid,'memory_initialization_vector =\n');
for i = 1 : N
    if (i == N)
        fprintf(fid,'%d;',sin_data(i));     % 最后一个数加分号
    else
        fprintf(fid,'%d,',sin_data(i));     % 其余加逗号
    end
    if mod(i,10) == 0 && i ~= N
        fprintf(fid,'\n');
    end
end
fclose(fid);

图39.4.3 MatLab生成的正弦波信号波形

四个 COE 的数据首尾拼接后再导入 Block Memory Generator,生成 rom_400x8b(深度 400、宽 8 位)。

时钟规划:为什么要 100MHz

PLL 输出三路时钟:clk_100m(驱动 DA 发送)、clk_25mclk_25m_deg120(ILA 采样)。用 100MHz 的原因很直接:输出 1MHz 的波形时,一个周期只有 1000ns,要在这 1000ns 里把 100 个点全部发出去,每个点只有 10ns,恰好对应 100MHz——用 25MHz 时钟只能输出 250KHz。

按键控制:波形切换 + 频率切换

两个按键接到 DA 发送模块(dds 版 da_wave_send):

  • L14(key_wave):切换波形种类,wave_select 是 2 位寄存器,0~3对应正弦/方波/三角/锯齿;
  • K16(key_freq):切换频率档位,freq_select 也是 2 位寄存器。

按键都需要消抖 + 下降沿检测

assign key_wave_filter_neg = (~key_wave_filter_d0) & key_wave_filter_d1;  // 下降沿

频率控制参数 FREQ_ADJ0 与输出频率的对应关系(实测):0→1MHz、1→500KHz、3→250KHz、7→125KHz。规律是每加一档频率减半,即分频系数变成 (FREQ_ADJ0+1)×2 倍量级。波形切换时 ROM 地址直接跳到对应区段(0/100/200/300 起点)。

图39.4.2 DDS系统框图

顶层与验证

顶层 dds 例化 PLL、ROM IP、da_wave_send、ILA。DA 部分与 ch37 相同(AD9708,da_clk = ~clk 锁存技巧)。仿真时重点看 wave_select/freq_select 切换瞬间 rom_addr 区间和输出数据的变化;上板后用示波器分别观察四种波形,再按频率键验证 1MHz→125KHz 的切换。

图39.5.4 正弦波测量图

图片对照

  • 原理:图 39.1.1(DDS 基本结构)、图 39.1.2(相位-幅度变换原理)、图 39.1.3(信号流程)。
  • MatLab 波形:图 39.4.3~39.4.6 依次是正弦、方波、三角、锯齿的生成波形,图 39.4.7/39.4.8 是 COE 文件的查看与编辑。
  • IP 配置:图 39.4.9~39.4.13 是 Block Memory Generator 与 Clocking Wizard 的配置选项卡。
  • 模块框图与波形:图 39.4.14(da_wave_send 框图)、图 39.4.15/39.4.16(按键控制波形/频率的端口波形)。
  • 仿真:图 39.4.17~39.4.24 是频率/种类切换的分步仿真,图 39.4.25 是总体仿真波形。
  • 上板:图 39.5.1 实物连接,图 39.5.4~39.5.8 是四种波形的示波器实测与频率切换图。

注:本工程中还有 4 张图(扩展口与原理图)与第三十七章相同,可回看 ch37 的对应图片:/images/zynq/ch37/ch37_p0982_00937_db3955cefd0832ae.png(开发板扩展口)、ch37_p0992_00940ch37_p0992_00941ch37_p0993_00942(AD/DA 原理图部分)。

初学者容易踩的坑

  1. COE 文件格式写错:第一行必须是 memory_initialization_radix = 10;,数据之间逗号、结尾分号,多一个少一个 ROM 都无法初始化。
  2. 四种波形数据拼接顺序搞混:正确顺序是正弦(0~99)→方波(100~199)→三角(200~299)→锯齿(300~399)。顺序错了,按键切出来的波形对不上号。
  3. 用 25MHz 时钟追求 1MHz 输出:25MHz下每周期只有 25 个点,波形严重失真。1MHz 输出必须用 100MHz 时钟。
  4. 按键没有做消抖和下降沿检测:机械按键抖动会导致一次按键切换好几档波形。(~d0)&d1 的下降沿取法不能省。
  5. ROM 深度写成 256:上一章的 ROM 是 256 点,本章是 400 点,Block Memory Generator 配置时深度要改成 400,否则波形缺段。
  6. 忘了波形切换是按 100 点跳地址:切换波形时 rom_addr 应直接置为 0/100/200/300,如果只是简单加减会跨波形区间输出"杂交"波形。

小结 & 下一步

DDS 的三段式结构(相位累加器→相位调制→ROM 查表)是本章的全部核心。配合 MatLab 造表、100MHz 高速时钟、按键消抖与档位切换,我们做出了一个功能完整的信号发生器。频率分辨率与最高频率取决于累加器位宽和时钟频率,这是 DDS 设计中永恒的权衡。

下一步进入第四十章:把单路 DA 升级成双路高速 DA,用 10 位的 3PD5651E 同时输出两路波形。