第一次接触DDS(直接数字频率合成)技术是在大学电赛期间,当时为了完成信号发生器的题目,熬了几个通宵才搞明白这个看似简单实则精妙的设计。DDS的核心思想其实很直观——用数字方式"拼凑"出我们想要的模拟波形。
想象你手里有一本画满正弦波的笔记本,每页纸上都画着波形的一个小片段。现在你需要快速翻动这本笔记,让这些片段连贯起来形成完整的波形。DDS的工作原理与此类似:ROM里存储着波形的数字样本(就像笔记本里的画),相位累加器负责决定翻到哪一页(相位控制),而频率控制字则决定了翻页的速度(频率控制)。
具体到技术实现,DDS系统包含三个关键部件:
在实际工程中,我们常用这个公式计算输出频率:
code复制fout = (M × fclk) / 2^N
其中M是频率控制字,fclk是系统时钟频率,N是相位累加器位数。比如使用100MHz时钟和32位累加器时,要产生1kHz信号,M值约为42950。这个简单的公式背后,藏着数字信号处理的精髓。
在电赛H题中,我们团队最初尝试用STM32实现DDS,结果发现输出波形存在明显抖动。后来改用FPGA方案后,性能直接提升了一个数量级。这里分享下我们的FPGA实现框架:
相位累加器模块是整个设计的核心。我们采用32位累加器,确保在100MHz时钟下频率分辨率能达到0.023Hz。关键代码如下:
verilog复制always @(posedge clk) begin
if(reset)
phase_acc <= 32'd0;
else
phase_acc <= phase_acc + freq_word;
end
波形存储模块需要特别注意存储深度与位宽的平衡。我们最终选择12位宽、4096深度的ROM,存储了经过量化的正弦波表。在Quartus中可以用Megawizard插件轻松生成初始化文件。
时钟管理模块是工程化时最容易忽视的部分。我们采用了Xilinx MMCM生成系统主时钟,并为DAC芯片提供同步时钟,确保时序严格对齐。实测表明,独立的时钟域设计能让SFDR(无杂散动态范围)提升15dB以上。
经过多次实测,我们总结出几个关键参数的经验值:
这里有个实用技巧:在资源受限的FPGA上,可以只存储1/4周期的正弦波表,然后通过相位映射生成完整波形,能节省75%的存储空间。具体实现时需要注意相位高2位的处理:
verilog复制wire [9:0] rom_addr;
assign rom_addr = phase_acc[31:22]; // 取高10位作为地址
// 象限判断逻辑
always @(*) begin
case(phase_acc[31:30])
2'b00: rom_data = rom_q;
2'b01: rom_data = rom_q;
2'b10: rom_data = -rom_q;
2'b11: rom_data = -rom_q;
endcase
end
从电赛原型到工业级产品,第一个要攻克的就是时钟同步问题。我们遇到过这样的情况:FPGA内部生成的波形很完美,但通过DAC输出后却出现了周期性毛刺。经过逻辑分析仪抓取发现,这是FPGA主时钟与DAC采样时钟不同步导致的。
解决方案是采用时钟数据恢复(CDR)技术:
实测表明,当时钟偏移控制在5%以内时,输出波形的THD(总谐波失真)可以优于-70dB。
DDS输出的阶梯状波形包含高频分量,必须通过滤波才能获得纯净信号。我们对比了三种滤波方案:
| 滤波器类型 | 截止频率 | 硬件复杂度 | 相位线性度 |
|---|---|---|---|
| 巴特沃斯 | 20MHz | 中等 | 一般 |
| 切比雪夫 | 30MHz | 复杂 | 较差 |
| 椭圆滤波器 | 25MHz | 简单 | 优秀 |
最终选择7阶椭圆滤波器,在保证30MHz带宽的同时,将带内纹波控制在0.1dB以内。这里有个省钱技巧:可以用FPGA内部的FIR滤波器先做数字预滤波,降低对模拟滤波器的要求。
在通信应用中,相位噪声是衡量信号源质量的关键指标。我们通过以下措施将相位噪声优化到-110dBc/Hz@10kHz:
特别要注意的是,FPGA内部布线延迟会导致相位误差。我们的解决方案是在布局约束文件中添加:
code复制set_clock_groups -asynchronous -group {CLK_DA} -group {CLK_FPGA}
工业应用经常需要实时调整频率和相位。我们设计了一套基于AXI总线的控制接口,支持μs级的参数更新:
verilog复制// AXI Lite从机接口
always @(posedge s_axi_aclk) begin
if(s_axi_aresetn) begin
freq_word <= 32'd0;
phase_offset <= 16'd0;
end else if(s_axi_wvalid) begin
case(s_axi_awaddr[7:0])
8'h00: freq_word <= s_axi_wdata;
8'h04: phase_offset <= s_axi_wdata[15:0];
endcase
end
end
配合Python上位机,可以实现扫频、跳频等复杂操作。测试数据显示,频率切换时间小于5μs,完全满足大多数工业场景需求。
在实验室调试阶段,我们踩过不少坑。这里分享几个典型问题的解决方法:
问题1:输出波形有周期性失真
问题2:高频输出时幅度下降
问题3:频率控制字改变后输出不稳定
有个记忆犹新的调试经历:当时输出正弦波在特定频率总会出现谐波,最后发现是PCB布局时把数字信号线走在了模拟区域附近。重新布线后问题立即解决。这个教训告诉我们,硬件设计必须严格区分模拟和数字地。