1. 项目概述:口袋里的DDS信号发生器
最近在实验室折腾FPGA时,突然萌生了一个想法:能不能做个能塞进口袋的信号发生器?不是那种笨重的台式设备,而是可以随手揣走、随时调试的小玩意儿。经过两周的奋战,终于用Cyclone IV FPGA实现了一个支持六种波形、频率实时可调的DDS信号发生器,整个设计只用了三个按键控制,逻辑资源占用不到70%。
这个项目的核心价值在于:
- 便携性:整板尺寸仅5x3cm,可直接用USB供电
- 多功能:支持ASK/FSK调制波和四种基础波形
- 实时性:频率调节响应时间<10ms,波形切换无断点
- 低失真:正弦波THD<2%,矩形波上升时间<5ns
2. 核心设计思路解析
2.1 DDS基础架构选择
直接数字频率合成(DDS)采用相位累加器+波形查表的结构,相比传统PLL方案有三大优势:
- 频率分辨率高:32位相位累加器在50MHz时钟下,理论分辨率可达0.012Hz
- 切换速度快:频率/波形切换只需更新控制字,无锁相环捕捉过程
- 相位连续:改变频率时不会产生相位跳变
关键参数计算示例:
- 输出频率公式:fout = (fclk × FTW) / 2^N
- 当fclk=50MHz,N=32时:
- 生成1kHz信号需要的频率控制字(FTW) = (1k × 2^32)/50M ≈ 85,899
- 10MHz对应的FTW ≈ 858,993,459
2.2 波形生成方案对比
| 波形类型 | 实现方案 | 存储深度 | 特点 |
|---|---|---|---|
| 正弦波 | ROM查表 | 256点 | THD与点数相关,需插值 |
| 矩形波 | 相位高位 | 无 | 占空比可调需额外计数器 |
| 三角波 | 相位折叠 | 128点 | 线性度依赖DAC性能 |
| 锯齿波 | 相位截断 | 无 | 需防溢出处理 |
| ASK | 正弦波×开关 | 256点 | 调制深度可编程 |
| FSK | 双累加器 | 256点 | 跳频速率可调 |
实测发现,用MATLAB生成.coe文件时,采用分段线性插值比直接查表能降低约40%的谐波失真。例如正弦波的量化数据生成代码:
matlab复制n = 0:255;
sine = round(127.5 + 127.5*sin(2*pi*n/256));
fid = fopen('sine.coe','w');
fprintf(fid,'memory_initialization_radix=16;\n');
fprintf(fid,'memory_initialization_vector=\n');
for i = 1:255
fprintf(fid,'%02X,\n',sine(i));
end
fprintf(fid,'%02X;',sine(256));
fclose(fid);
3. 关键模块实现细节
3.1 相位累加器设计
相位累加器是DDS的核心,其Verilog实现需要注意三个要点:
verilog复制reg [31:0] phase_acc;
always @(posedge clk) begin
phase_acc <= phase_acc + freq_word; // 无阻塞赋值避免竞争
end
// 频率控制字计算模块
wire [31:0] freq_word = (base_freq * (10 + freq_step)) / 10;
参数设计经验:
- 累加器位宽决定频率分辨率,32位可满足大多数场景
- 采用流水线加法器可提升最高工作频率
- 频率控制字需做边界检查,防止相位累加器溢出
3.2 按键交互系统
三键控制方案通过状态机实现多功能:
verilog复制localparam
IDLE = 3'd0,
DEBOUNCE = 3'd1,
CONFIRM = 3'd2,
HOLD = 3'd3;
always @(posedge clk) begin
case(state)
IDLE:
if(key_press) begin
debounce_cnt <= 0;
state <= DEBOUNCE;
end
DEBOUNCE:
if(debounce_cnt == 20'd500_000) begin // 10ms@50MHz
state <= key_hold ? HOLD : CONFIRM;
end
CONFIRM:
begin
case(key_num)
1: wave_sel <= wave_sel + 1;
2: freq_step <= freq_step + 1;
3: freq_step <= freq_step - 1;
endcase
state <= IDLE;
end
HOLD:
if(!key_hold) state <= IDLE;
endcase
end
防抖设计要点:
- 消抖时间通常取10-20ms,需根据按键机械特性调整
- 长按检测阈值建议300-500ms
- 按键扫描频率应大于1kHz避免漏检
4. 波形生成优化技巧
4.1 调制波形实现
ASK和FSK需要特殊处理:
ASK调制:
verilog复制assign ask_out = (modulation_en) ? sine_rom[phase_acc[31:24]] : 0;
FSK调制:
verilog复制reg fsk_selector;
always @(posedge mod_clk) begin // 建议用独立PLL生成
fsk_selector <= ~fsk_selector;
current_freq <= fsk_selector ? freq_high : freq_low;
end
实测发现,调制时钟与系统时钟不同源时,会出现周期性的相位跳变。解决方法:
- 使用FPGA内部的PLL生成相关时钟
- 对调制时钟进行同步处理
- 在时钟域交叉处插入双缓冲
4.2 非对称波形处理
锯齿波和三角波需要特殊算法:
verilog复制// 锯齿波生成
wire [7:0] sawtooth = phase_acc[31:24];
// 三角波生成
wire [7:0] triangle = (phase_acc[31]) ?
~phase_acc[30:23] :
phase_acc[30:23];
波形质量优化:
- 增加输出DAC位数可改善动态范围
- 在DAC后加入抗镜像滤波器
- 对于高频信号,建议使用电流输出型DAC
5. 实测问题与解决方案
5.1 高频失真问题
当输出频率>1/4采样率时,波形出现明显失真。解决方法:
- 增加波形ROM的采样点数(实测512点比256点THD改善15dB)
- 采用抖动注入技术(dithering)
- 在FPGA内部实现插值滤波器
5.2 按键响应延迟
初期方案中按键响应有约50ms延迟,优化措施:
- 将消抖计数器改为递减模式
- 使用优先级编码器处理多键同时按下
- 对频率调节键实现加速功能(长按增速)
5.3 资源占用优化
原始设计占用85%的LEs,通过以下方法降至68%:
- 将ROM改用分布式RAM实现
- 共享相位累加器资源
- 对不用的波形生成模块动态禁用
6. 性能测试数据
测试环境:
- FPGA: Cyclone IV EP4CE6E22C8N
- 时钟: 50MHz有源晶振
- 供电: USB 5V/500mA
| 测试项 | 指标 | 实测结果 |
|---|---|---|
| 频率范围 | 1Hz-10MHz | 0.5Hz-12.5MHz |
| 频率误差 | ±0.1% | ±0.05% |
| 正弦波THD | <3% | 1.8%@1kHz |
| 矩形波上升时间 | <10ns | 4.7ns |
| 波形切换时间 | <1ms | 0.3ms |
| 整机功耗 | <500mW | 370mW@5V |
这个设计后续还可以扩展:
- 增加USB接口实现PC控制
- 加入扫频功能
- 实现任意波形加载
- 添加LCD显示屏实时显示参数