☰
基于FPGA的DDS信号发生器设计:参数推导、Verilog实现与上板验收
2026/10/26 5:01:36 网站建设 项目流程

简介:面向FPGA数字逻辑、电子信息类课程设计与入门学习者,这份资源提供一套基于Verilog的DDS信号发生器工程,可用于理解相位累加器、频率控制字、分频比调节、MIF波形查表与DAC接口等核心环节,并覆盖仿真验证到板级调试的完整流程。压缩包共315个文件,约5.7MB,以.v和.sv源码、.mif查找表数据、Quartus工程文件(.qpf、.qsf、.sof)及.cdb、.hdb等编译数据库为主,另含约束、报告和日志,打开后便于对照模块结构与工程配置,其中.sof位流可直接用于板级验证。已有902人学习下载,适合作为课程设计、实验课或自学DDS的参考工程;通过调整分频系数和相位初值,可观察输出频率、相位连续性与幅度线性度的变化,也能借鉴相位累加、波形查表、DAC输出和硬件调试的实现思路,并复用顶层结构与MIF配置快速搭建自己的波形发生器。

1. 从一台"频率调不准"的信号源说起:为什么用 FPGA 做 DDS

某同学抱着一块入门级 FPGA 开发板来找我,说要做一台能出正弦、方波、三角波、锯齿波的信号发生器,频率要连续可调,答辩时还得讲清楚每个参数是怎么来的。他最初的方案是调一个现成的 DDS IP 核,把频率字往端口上一接就收工——导师只问了一句"ROM 为什么是 4096 深",整个方案就站不住了。用 FPGA 做 DDS 的价值从来不在调 IP,而在你能把"相位到幅度"这条数字链路捏在手里:频率控制字取多少、相位截掉几位、DAC 前面要不要插一级输出寄存器、重构滤波器截止频率卡在哪,每一步都算得出来、测得出来,也答得出来。这套结构适合正在做毕设或课程设计的人,也适合想把抽屉里那块开发板改造成一台真能用的小信号源的人。下面按参数推导、模块划分、Verilog 实现、上板排查、验收答辩五段走一遍。

2. 原理与参数换算:相位累加器位宽、频率字与杂散怎么定

DDS 的骨架小得有点反直觉:一个加法器、一个寄存器、一张查找表,再加一个 DAC。寄存器里存的不是电压,是"相位",每个时钟周期往前推一步,推满一圈就是一个完整波形周期。所有参数都是在给这"一步"定标,标错了,输出频率就是错的,而且错得很隐蔽——示波器上波形长得挺像,频率却差了一个固定比例。

2.1 相位累加器的工作方式与两个核心公式

相位累加器位宽记作 N,每个采样时钟把频率控制字 K 加上去,寄存器溢出一次就代表波形走完一个 2π 周期。由此得到两条贯穿全篇的公式:

输出频率Fout = K × Fclk / 2^N,频率分辨率Δf = Fclk / 2^N。

反过来,给定想要的频率,频率字就是K = round(Fout × 2^N / Fclk)。这两条式子决定了后面所有的选型:N 定分辨率,Fclk 定上限,K 定当下输出。很多人做方案时先选 DAC 再回头算 K,结果发现分辨率不够、低频段调不动,只能推翻重来。顺序应该反过来,先按最低输出频率和调节步进确定 N,再按最高输出频率确定 Fclk,最后才挑 DAC 的位数和建立时间。

举个具体配置:Fclk 取 50 MHz(也就是 DAC 的采样时钟),N 取 32,分辨率就是 50e6 / 2^32 ≈ 0.0116 Hz。要输出 1 kHz,K = round(1000 × 2^32 / 50e6) = 85899。如果 K 不小心写成 85900,输出就是 1000.0116 Hz,误差约 11.6 ppm;如果 K 少写一位变成 8589,输出直接掉到 100 Hz 左右。这就是为什么调试时第一件事永远是用频率计核一遍低频点,而不是先看波形好不好看。

提示:K 必须是整数,取整带来的误差是 DDS 的固有频率误差,大小约为0.5 / K。K 越小(输出频率越低)相对误差越大,所以低频段要用频率计而不是示波器时基来判定精度。

2.2 N、Fclk 与 ROM 深度之间的三方拉扯

N 越大分辨率越高,但 ROM 不可能做 2^32 深。真实设计里,送到查找表的地址只有低 A 位(或者高 A 位,取决于你怎么数),中间被砍掉的那截就是相位截断。三者关系是ROM 深度 = 2^A,截断位数 B = N − A。

常见取值对比如下(Fclk = 50 MHz):

累加器位宽 N频率分辨率12 位地址下的截断位数4096 点 ROM 的地址线
242.98 Hz1212
280.186 Hz1612
320.0116 Hz2012

N = 24 时分辨率接近 3 Hz,做 1 Hz 步进的调节会明显卡顿;N = 32 时分辨率绰绰有余,代价是多 8 位加法器。对入门级器件来说,一个 32 位加法器占用的逻辑资源完全可以接受,所以我一般直接上 32 位,把余量留给频率细调。ROM 深度则卡在 4096 或 8192,再大就吃块 RAM 的容量了,而 12 位地址对应的理论杂散已经能压在 −70 dBc 量级,对教学板和普通实验足够。

这里有个容易忽略的细节:相位截断不是"随便砍低位"。累加器高位向 ROM 地址进位时会有多位同时翻转,如果 ROM 是异步读,这些翻转会在输出上叠出一串窄毛刺。正确做法是 ROM 用同步读、地址打一拍,让毛刺落在时钟沿之间的稳定区。这一点在第 4 章的代码里会体现。

2.3 杂散与信噪比:两个来源要分开算

输出频谱干不干净,取决于两个完全独立的源头:相位截断带来的杂散,和 DAC 幅度量化带来的噪声。

相位截断的杂散,工程上按"地址每多 1 位,最坏杂散改善约 6 dB"来估。12 位地址大约对应 −72 dBc 量级,10 位地址就只有 −60 dBc 左右,在频谱仪上能明显看到主频两侧的杂散峰。这也是我把地址定在 12 位而不是 10 位的直接理由——不是 ROM 便宜,是频谱仪上的脸面问题。

幅度量化部分,理想 D 位 DAC 的信噪比约为6.02D + 1.76 dB,12 位对应约 74 dB。可以看到 12 位地址的杂散和 12 位 DAC 的噪声刚好在一个量级,配置是匹配的:再往上加地址位宽,瓶颈就转移到 DAC 上了;往下减地址位宽,DAC 的位数就浪费了。

还有一项绕不开的:DAC 的零阶保持特性会带来 sin(x)/x 滚降。在 0.4 × Fclk 处衰减约 2.4 dB,在 0.5 × Fclk 处衰减约 3.9 dB。这意味着即使不做任何滤波,高频段幅度也会天然往下掉。所以输出上限一般定在 Fclk 的 1/10 左右(50 MHz 采样对应 5 MHz 输出),一是留够重构滤波器的过渡带,二是避开滚降最陡的区间。答辩被问到"为什么不做 25 MHz",答案就在这里。

3. 总体架构设计:六个模块的职责划分与时钟方案

把公式变成电路,最忌讳的是一上来就写顶层。先画清楚数据从相位到引脚经过谁、每个模块在哪个时钟域、跨域的地方有没有握手,代码写起来才不会边写边改。这套方案按六个模块切分,每个模块只干一件事,接口窄到一眼能看完。

3.1 顶层模块与数据通路划分

模块职责关键信号时钟域
clk_rst_genPLL 产生 50 MHz 采样时钟,同步释放复位clk_50m、rst_n全局
phase_acc32 位相位累加,输出 12 位查表地址freq_word、rom_addrclk_50m
wave_rom4 种波形 × 4096 点,同步读rom_addr、wave_dataclk_50m
amp_scaleQ8 增益缩放,围绕中点收缩幅度amp_gain、dac_codeclk_50m
dac_if输出打拍、数据对齐dac_data、dac_clkclk_50m
cfg_ctrl按键消抖 + 串口收帧,产生 freq_word / amp_gain / wave_seluart_rx、key_inclk_50m(使能分频)

数据通路是一条直线:phase_acc 出地址 → wave_rom 出波形码 → amp_scale 缩放 → dac_if 打拍送引脚。控制通路是另一条:按键或串口命令解析成频率字、增益、波形选择三组寄存器,写进数据通路。两条通路唯一的交点是寄存器,这样调频、调幅、换波形都不会打断相位累加,波形在切换瞬间是连续的,听感上、示波器上都不会"咔"一下。

3.2 时钟方案:一个主时钟加一堆使能,别随便分频

入门项目最容易踩的坑是用计数器分频出"串口时钟""按键时钟""DAC 时钟",然后让不同模块跑在不同时钟上。这么做在功能仿真里往往没事,一上板就开始出玄学问题:跨时钟域的信号偶发丢拍,综合工具报一堆未约束路径,时序报告里全是红色。

我一般的做法是只有一个 PLL 输出的 50 MHz 主时钟,所有逻辑都挂在这个时钟的上升沿。串口 115200 波特率用使能计数实现(50e6 / 115200 ≈ 434,每 434 个周期产生一个采样使能),按键 10 ms 消抖同样是计数器产生使能。这样全设计只有一个时钟域,时序约束只需要写一条 create_clock,跨域问题从源头上消失。

DAC 的采样时钟单独走一根:从 PLL 直接输出到专用时钟引脚,不经过任何逻辑。数据侧在时钟上升沿更新、DAC 在上升沿采样,建立时间刚好卡在边缘,稳妥的做法是让数据在下降沿输出、DAC 在上升沿锁存,或者干脆用 PLL 的相移把时钟挪 180°。这一条在时序余量紧张时是救命稻草。

注意:不要把主时钟取反后当 DAC 时钟用。反相时钟在布局布线后可能走的是普通布线资源,偏斜不可控,表现为输出波形上出现规律的窄脉冲,频率越高越明显。

3.3 波形 ROM 的组织方式与切换策略

四种波形共用一套地址,有两种组织办法:四张独立 ROM 并起来,或者一张大 ROM 用地址高位选 bank。前者浪费块 RAM 的端口,后者只需要把深度从 4096 扩到 16384,地址拼成{wave_sel[1:0], phase_addr[11:0]}。我选后者,因为综合工具会把 16384 × 12 的存储体推断成一块块 RAM,占一个端口,资源干净。

波形切换的关键是"换表不换相位"。如果把相位累加器清零再切波形,输出会从一个固定相位重新开始,示波器上表现为波形跳变,作频谱看是一片宽带冲击。正确做法是相位累加器永远不停,只改 wave_sel,输出在新相位的下一个采样点接着走。这样切波形时只有一次不连续(新旧波形的幅度差),没有相位重建过程。

幅度调节用 Q8 定点增益,0 到 255 表示 0 到 255/256 的满幅比例。为什么不用浮点或者百分比整数?因为 Q8 在 Verilog 里就是一次乘法和一次右移,能直接映射到 DSP 硬核,不需要任何 IP。缩放要围绕中点 2048 做,公式是out = 2048 + (raw − 2048) × gain / 256,这样增益调小时波形是围绕中心收缩的,直流偏置不会跟着漂。

4. 关键模块的 Verilog 实现:累加器、波形 ROM 与 DAC 接口

模块划分定了,接下来是三个必须自己写、也最容易被追问的模块。代码不长,但每一行都有对应的设计理由,注释里写清楚比事后补文档管用。

4.1 相位累加器:位宽、截断与相位偏移

module phase_acc #( parameter PHASE_W = 32, // 相位累加器位宽,决定频率分辨率 parameter ADDR_W = 12 // 送 ROM 的地址位宽,决定截断位数 )( input wire clk, input wire rst_n, input wire en, // 采样率使能,1 时每拍累加 input wire [PHASE_W-1:0] freq_word, // 频率控制字 K input wire [PHASE_W-1:0] phase_offset, // 相位偏移,做双通道时用 output wire [ADDR_W-1:0] rom_addr ); reg [PHASE_W-1:0] phase_reg; always @(posedge clk or negedge rst_n) begin if (!rst_n) phase_reg <= {PHASE_W{1'b0}}; else if (en) phase_reg <= phase_reg + freq_word; // 溢出即一个完整周期 end // 取高位作为地址:高位变化慢,截断误差对波形影响最小 assign rom_addr = phase_reg[PHASE_W-1 -: ADDR_W] + phase_offset[PHASE_W-1 -: ADDR_W]; endmodule

复制代码时有三个参数要按项目实际改。PHASE_W 定频率分辨率,取 32 时在 50 MHz 时钟下分辨率约 0.0116 Hz;如果只做 1 Hz 步进,24 位也够,能省下 8 级进位链。ADDR_W 必须和 ROM 深度对应,改 ROM 深度忘了改这里,综合会报位宽不匹配,或者更糟——高位被隐式截掉,波形周期全乱。phase_reg[PHASE_W-1 -: ADDR_W]是 Verilog-2001 的降序部分选择语法,含义是从最高位往下取 ADDR_W 位,等价于右移PHASE_W − ADDR_W位。

en这个使能端是为将来留的口子。如果 DAC 时钟和逻辑时钟不同频(比如 DAC 跑 125 MHz、逻辑跑 50 MHz),可以用它把累加节拍控制在采样率上。当前设计单时钟同频,en 恒为 1。相位偏移那一项做单通道信号源时可以直接删掉,省一个 12 位加法器;做双通道正交输出时它必须保留,第二路的 offset 填2^(N−2)(即 90°)。

4.2 波形 ROM 与初始化文件生成

ROM 用同步读,输出比地址晚一拍。这一拍延迟必须在 dac_if 里对齐,否则地址和数据错位,波形会在每个周期内被"斜切"一次。

module wave_rom ( input wire clk, input wire [13:0] addr, // [13:12] 选波形,[11:0] 相位地址 output reg [11:0] dout ); reg [11:0] rom [0:16383]; initial $readmemh("wave_4x4096.hex", rom); // 综合时被推断为块 RAM 初值 always @(posedge clk) dout <= rom[addr]; // 同步读,输出延迟一拍 endmodule

初始化文件用一段 Python 生成,比手写或者用工具导出更可控,改深度和位宽只动两个常量:

import math DEPTH, HALF = 4096, 2048 def code(v): # 浮点 -1.0~1.0 映射到 12 位无符号码 return max(0, min(4095, int(round(v * 2047 + 2048)))) def sine(i): return math.sin(2 * math.pi * i / DEPTH) def square(i): return 1.0 if i < HALF else -1.0 def tri(i): x = i / DEPTH return 4 * x - 1 if x < 0.5 else 3 - 4 * x def saw(i): return 2 * i / DEPTH - 1 rows = [] for fn in (sine, square, tri, saw): # 顺序必须与 wave_sel 编码一致 rows += [code(fn(i)) for i in range(DEPTH)] with open("wave_4x4096.hex", "w") as f: for v in rows: f.write("%03X\n" % v) # 12 位 = 3 个十六进制字符

生成的文件共 16384 行,每行 3 个十六进制字符,$readmemh按行填充。这里有两个必须对齐的地方:一是for fn in (...)的顺序要和 RTL 里wave_sel的编码一致,顺序写反了波形会张冠李戴,而且仿真时很难一眼看出来;二是code()里把 −1.0 映射到 1、把 1.0 映射到 4095,给两端各留了 1 个码字的余量,避免满幅时被 DAC 削顶。

方波用 0 和 4095 直接跳变,理论上频谱是无穷多次谐波。实际输出必须靠后级重构滤波器压掉,否则高频分量会在采样后混叠回带内,示波器上看到的是方波边沿上挂着振铃。这也是为什么方波和正弦波共用同一个低通滤波器时,方波的边沿会变圆——滤波器带宽是按正弦波最高频率定的,鱼和熊掌的关系,方案里提前说清楚就行。

4.3 增益缩放与 DAC 输出对齐

module amp_scale ( input wire clk, input wire rst_n, input wire signed [11:0] wave_data, // ROM 输出,0~4095 视为无符号 input wire [7:0] amp_gain, // Q8 增益,255 约等于满幅 output reg signed [11:0] dac_code ); wire signed [12:0] delta = $signed({1'b0, wave_data}) - 13'sd2048; reg signed [20:0] scaled; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin scaled <= 21'sd0; dac_code <= 12'sd2048; // 复位时输出中点,DAC 端不跳变 end else begin scaled <= delta * $signed({1'b0, amp_gain}); // 13b × 8b → 21b dac_code <= scaled[19:8] + 12'sd2048; // 右移 8 位后回加偏置 end end endmodule

这段的关键在于把"缩放"变成"围绕中点的收缩"。先减掉 2048 得到有符号的偏差量,乘上 Q8 增益,右移 8 位还原,再加回 2048。如果直接拿原始码乘增益,直流工作点会随增益一起变,示波器上表现为波形整体上下平移,用交流耦合看是"幅度没变、位置在动",排查半天找不到原因。

位宽要算清楚:偏差量范围 −2048 到 2047,占 13 位有符号;增益 8 位无符号;乘积 21 位,最大值约 ±522240,不会溢出。右移 8 位后落在 ±2040 以内,加 2048 后是 8 到 4088,仍在 12 位范围内。这套位宽在综合时会被映射到 DSP 硬核,一个乘法器就够,不会占用逻辑资源。

输出打了两级寄存器(scaled 和 dac_code),这是故意加的流水线。乘法器组合路径较长,直接接到引脚会让时序余量变紧,尤其在 50 MHz 以上。因此从 ROM 地址到 DAC 数据引脚总共是 3 拍延迟,仿真时按这个延迟对齐期望波形,测试平台才不会误判。

4.4 串口改频:帧格式与频率字换算

按键改频只能做步进加减,真正的"连续可调"走串口。帧格式定成 6 字节:帧头 0xAA、命令字、4 字节频率字(小端)、校验和。命令字 0x01 写频率字,0x02 写增益,0x03 切波形。接收端用状态机逐字节收,收满一帧核对校验和,通过才更新寄存器。

上位机侧用一段脚本把目标频率换算成频率字,直接发十六进制:

FCLK = 50_000_000 N = 32 def freq_word(fout): return int(round(fout * (1 << N) / FCLK)) & 0xFFFFFFFF k = freq_word(1234.5) # 目标 1234.5 Hz frame = bytes([0xAA, 0x01]) + k.to_bytes(4, "little") frame += bytes([sum(frame) & 0xFF]) print(frame.hex())

1 << N就是 2^32,& 0xFFFFFFFF保证结果落在 32 位内。这段脚本放在上位机里,调频时改一个数字就行,不用反复烧板子。频率字更新要遵循一个原则:新值写进影子寄存器,等相位累加器溢出(也就是相位归零)的那一刻再切换。虽然 DDS 本身允许任意时刻改频率字且相位连续,但在示波器上做频率扫描时,同步切换能让波形更干净,也方便截图写报告。

5. 避坑与排查:DDS 上板后最容易翻车的五件事

仿真通过、综合通过、下载进去没输出,是这类项目最常见的剧本。下面五条都是从"现象"倒推到"原因"再给"解决"的,按出现频率排序,遇到问题按顺序对照着查,能省掉大半天时间。

5.1 输出频率正好是设定值的一半或两倍

现象:程序中写 1 kHz,频率计读出来 500 Hz 或者 2 kHz,波形形状完全正常。

原因基本只有一个——公式里的 Fclk 用错了。有人把 PLL 输入时钟(比如 25 MHz 晶振)当成采样时钟代进公式,实际 PLL 输出是 50 MHz;或者反过来,逻辑跑在 50 MHz 但 DAC 时钟被分频成了 25 MHz。还有一种隐蔽情况:phase_acc 的 en 使能每两拍才有效一次,等效采样率减半,代码里看不出来,只有数周期才能发现。

解决:先把 DAC 时钟引脚接示波器量一次实际频率,确认它和参数表里写的 Fclk 一致;再在 phase_acc 里加一个计数器,对 en 有效的次数计数,用逻辑分析仪抓 1 ms 内的计数增量,反推真实累加节拍。两处对齐之后再改 K,频率立刻就准了。

5.2 波形上有规律的窄毛刺,频率越高越密

现象:低频时波形干净,频率升到几百 kHz 以上,正弦波上开始出现周期性的窄尖刺,频谱上表现为主频两侧成对的杂散峰。

原因是 ROM 地址的二进制进位造成多位同时翻转。相位地址从 0x0FF 加到 0x100 时,12 根地址线同时变化,如果 ROM 是异步读或者地址到数据是纯组合路径,这些翻转的传播延迟不同,输出上就会叠出一串毛刺。另一种可能是 ROM 被综合成了分布式寄存器而不是块 RAM,读写路径完全组合化,问题会更严重。

解决:ROM 必须用同步读(always @(posedge clk) dout <= rom[addr];),并在地址送出后打一拍再进 ROM;综合后看资源报告,确认 ROM 落在块 RAM 而不是 LUT 里,如果落错了,加(* rom_style = "block" *)之类的综合属性强制推断。再退一步,把相位地址转成格雷码也能缓解,代价是多一级异或逻辑。

5.3 高频段幅度明显下降,方波边沿发圆

现象:同一个增益设置下,100 kHz 时峰峰值 2 V,2 MHz 时掉到 1.4 V,方波的上升沿从陡直变成圆弧。

这是 DAC 零阶保持的 sin(x)/x 滚降叠加后级滤波器带宽不足的结果,不是代码问题。2 MHz 在 50 MHz 采样下对应 0.04 × Fclk,滚降只有 0.03 dB,理论上可以忽略,所以幅度掉这么多一定是滤波器截止频率定低了,或者是 DAC 输出端的运放压摆率不够。

解决:重构低通滤波器的截止频率按 0.4 × Fclk 设计,阶数够用就行,二阶巴特沃斯在通带内的平坦度已经能压到 ±0.5 dB 以内;运放选增益带宽积至少是最高输出频率 10 倍的型号。验收时把 10 Hz 到 5 MHz 扫一遍,记录每个频点的峰峰值,做一条平坦度曲线放进报告,比只报一个"输出正常"有说服力得多。

5.4 调频或切波形时波形跳变、有咔哒声

现象:用按键或串口改频率的瞬间,示波器上波形从一个相位突然跳到另一个相位,作频谱看是一片宽带能量。

原因是频率字或波形选择寄存器在相位累加器运行中途被异步改写,且改写发生在非零相位时刻。更糟的一种是把相位累加器一起复位了——很多模板代码在收到新频率时会顺手把 phase_reg 清零,这一下相位直接归零,跳变不可避免。

解决:第一,绝不复位相位累加器,只改 freq_word;第二,freq_word 用影子寄存器暂存,等检测到相位溢出(phase_reg 的低 N 位全零)时再装载,这样切换点永远落在相位零点;第三,切波形只是改 ROM 地址的高 2 位,相位不动,新旧波形在同一相位上衔接。做完这三步,用示波器的单次触发抓切换瞬间,应该只看到幅度的变化,看不到相位断裂。

5.5 综合后资源爆掉或时序不收敛

现象:综合报告显示块 RAM 用了 0 块、LUT 用了 60% 以上;时序报告里从乘法器到 DAC 引脚的路径出现负裕量,频率上到 40 MHz 就报错。

前者是 ROM 没被推断成块 RAM,16384 × 12 的存储体全变成 LUT,逻辑资源瞬间见底。后者通常是乘法器和加法器串在一条组合路径上没有打拍,加上输出直接接引脚,布线延迟叠在一起。

解决:ROM 加综合属性强制推断,代码里保持"同步读 + 单独 always 块"的写法;乘法器前后各打一拍,把路径切成三段;DAC 输出寄存器用 IO 单元里的专用寄存器(在约束里把它锁到 I/O 位置),避免数据从逻辑阵列绕到引脚;如果器件支持,把 DAC 时钟走专用时钟网络,偏斜能压到 ps 级。改完之后看时序报告里最差路径的 slack,正数才算过。

6. 验收与答辩:指标表、测量方法与追问应对

方案能不能交,不看你讲得多顺,看你有没有一组自己测出来的数据。验收前把下面这张表填满,每一项都写清楚测量条件和仪器,答辩时被追问就翻表,比临场解释可靠得多。

指标设计值测量方法判定依据
输出频率范围1 Hz ~ 5 MHz频率计逐点测量,示波器看波形完整性全范围内波形无明显失真
频率分辨率0.0116 Hz(N=32, Fclk=50 MHz)写入相邻两个频率字,观察频率计末位变化可分辨出最小步进
频率准确度取决于板载晶振,编程误差 < 0.1 ppm1 kHz、100 kHz、1 MHz 三点用频率计比对与理论值偏差在晶振精度内
波形种类正弦、方波、三角、锯齿逐一切换,示波器截图四种波形形状正确、无错位
幅度调节0 ~ 满幅,Q8 步进固定频率下扫增益,记录峰峰值单调变化,中点不漂移
无杂散动态范围≥ 60 dBc(1 kHz 载波)频谱分析仪或采集卡做 FFT主频外最大杂散低于主频 60 dB
输出平坦度±1 dB(10 Hz ~ 1 MHz)扫频记录各频点峰峰值曲线落在 ±1 dB 带内

测频率准确度这一步有个小技巧:不要用示波器的自动测量功能读频率,时基误差和光标精度会把几百 ppm 的偏差淹掉,必须用频率计,或者用高稳定度参考源做比对。测杂散时窗函数选汉宁窗,采样点数取 16384 以上,否则底噪会盖住杂散峰,误判成"干净"。

答辩被追问的高频问题就那么几个,提前把答案写成一句话。问"为什么不用专用 DDS 芯片",答"要的是参数可控和可复现,芯片内部的截断位宽和滤波器特性是黑匣子";问"相位截断为什么是 12 位",答"12 位对应约 −72 dBc 的理论杂散,和 12 位 DAC 的 74 dB 信噪比匹配,再高就浪费";问"最高频率为什么只到 5 MHz",答"采样率的 1/10 留出重构滤波器过渡带,同时避开 sin(x)/x 滚降最陡的区间";问"改频率时相位为什么连续",答"只改频率字、不复位累加器,切换点同步在相位零点"。每个答案后面都跟着一个数字,这就是方案和作业的区别。

我自己的习惯是:每次上板前先把"频率、幅度、波形、切换"四个动作的期望现象写在一张纸上,测完一项划一项,测到哪项对不上就停下来查,绝不带着一个问题往下走。有次为了赶进度跳过了低频点核对,结果整套数据都是按错了一半的时钟算出来的,返工时示波器截图全部重拍。从那以后我每次做这类项目都强制走一遍这张验收表,哪怕时间再紧也不省。希望这套结构能帮到你,把自己的那一版稳稳当当地交出去。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询