4、ADC采样与FFT:采样定理、窗函数选择对功率测量的影响
好,咱们进入第四章。这一章,我打算聊聊ADC采样和FFT。
说实话,很多做射频的工程师,对频谱仪的原理一知半解。会用,但不知道里面怎么算的。尤其是采样定理和窗函数,这两个坑,我当年都踩过。今天我把经验掰开揉碎了讲给你听。
4.1 采样定理:别让假信号骗了你
先说说采样定理。奈奎斯特采样定理,大家都知道:采样频率要大于信号最高频率的两倍。但实际做嵌入式频谱仪,你会发现事情没那么简单。
为什么?
因为ADC不是理想的。它有孔径抖动、有量化噪声。你想想看,如果采样率刚好是信号频率的两倍,那信号刚好落在奈奎斯特边界上。这时候,一点点抖动就会让信号混叠到带内。
我个人习惯,采样率至少取信号最高频率的2.5倍到3倍。比如我要测100MHz的信号,我会用250MHz以上的采样率。留点余量,心里踏实。
关键点:
- 理论最低采样率:fs ≥ 2fmax
- 工程推荐采样率:fs ≥ 2.5fmax ~ 3fmax
- 抗混叠滤波器必须放在ADC之前,截止频率设为fs/2
我在项目中遇到过一件事。有次测一个2.4G的蓝牙信号,采样率设了5G,按理说够了吧?结果频谱上总有个诡异的尖峰。查了半天,发现是前端滤波器没做好,带外噪声混叠进来了。从那以后,我对抗混叠滤波器的设计就特别上心。
4.2 FFT与功率测量:点数、分辨率、噪声
采样完了,接下来就是FFT。FFT把时域信号变到频域,我们才能看到各个频率上的功率。
FFT有几个参数很重要:
- FFT点数(N):点数越多,频率分辨率越高。分辨率 = fs / N。
- 频率分辨率:决定了你能区分多近的两个频率。
- 噪声基底:FFT点数每翻一倍,噪声基底降低约3dB。这叫处理增益。
举个例子。采样率100MHz,做1024点FFT,频率分辨率大约是97.7kHz。如果你要测一个10kHz带宽的信号,这个分辨率就不够用。你得增加点数,或者降低采样率。
我的经验:
做窄带信号测量时,我习惯先降采样,再做FFT。比如用CIC滤波器把采样率降下来,这样同样的FFT点数,分辨率就高多了。这招在FPGA里特别实用。
4.3 窗函数:为什么非用不可?
好,现在到了重点——窗函数。
你可能会问:我直接拿采样数据做FFT不行吗?
嗯,这里要注意。FFT假设信号是周期无限的。但实际我们只截取了一段有限长度的数据。这就相当于在时域上加了一个矩形窗。矩形窗的频谱旁瓣很高,大概-13dB。这意味着强信号会泄漏到相邻的频率点上,把小信号给淹没了。
窗函数的作用,就是压低旁瓣,减少频谱泄漏。
我刚开始做频谱仪的时候,没太在意窗函数。结果测一个-60dBm的小信号,旁边有个-20dBm的大信号,小信号根本看不见。后来加了汉宁窗,一下就出来了。
4.4 汉宁窗 vs 布莱克曼窗:怎么选?
常用的窗函数有好几种。我重点讲两个:汉宁窗和布莱克曼窗。
| 窗函数 | 主瓣宽度 | 最高旁瓣 | 旁瓣衰减速率 | 适用场景 |
|---|---|---|---|---|
| 矩形窗 | 1 | -13 dB | -6 dB/oct | 瞬态信号、频率精确已知 |
| 汉宁窗 | 2 | -32 dB | -18 dB/oct | 一般频谱分析、功率测量 |
| 布莱克曼窗 | 3 | -58 dB | -18 dB/oct | 强信号附近测弱信号 |
汉宁窗:我最常用的窗。旁瓣-32dB,够用。主瓣宽度是矩形窗的两倍,但可以接受。对于大多数功率测量场景,汉宁窗是首选。
布莱克曼窗:旁瓣低到-58dB,非常干净。但代价是主瓣宽了三倍。这意味着频率分辨率会下降。我只有在测强信号旁边的微弱信号时才会用它。
注意:
窗函数会影响功率测量的准确性。因为加窗会改变信号的能量。你需要做幅度校正或能量校正。具体来说:
- 测量单频正弦波的幅度,用幅度校正因子
- 测量宽带信号的功率,用能量校正因子
忘了做校正,测出来的功率会偏小。我曾经因为这个被领导批评过,后来就长记性了。
4.5 代码示例:加窗与功率计算
下面给一段C代码,演示怎么加汉宁窗,然后计算功率。
// 汉宁窗系数生成
void hanning_window(float *window, int N) {
for (int i = 0; i < N; i++) {
window[i] = 0.5 * (1 - cos(2 * M_PI * i / (N - 1)));
}
}
// 加窗并计算功率谱
void compute_power_spectrum(float *data, int N, float *power) {
float window[N];
float energy_correction = 0.0;
// 生成汉宁窗
hanning_window(window, N);
// 计算能量校正因子
for (int i = 0; i < N; i++) {
energy_correction += window[i] * window[i];
}
energy_correction /= N;
// 加窗
for (int i = 0; i < N; i++) {
data[i] *= window[i];
}
// 执行FFT(假设已有fft函数)
fft(data, N);
// 计算功率谱,并做能量校正
for (int i = 0; i < N/2; i++) {
float real = data[2*i];
float imag = data[2*i+1];
power[i] = (real*real + imag*imag) / (N * energy_correction);
}
}
这段代码里,我特别加了能量校正。你看那个energy_correction,就是用来补偿加窗损失的能量。不做这一步,测出来的功率会偏低。
4.6 实战建议:窗函数选择流程
说了这么多,到底怎么选?我总结了一个简单的流程:
- 先判断信号类型:是单频信号还是宽带信号?
- 再看动态范围:信号之间有没有大的功率差?
- 最后考虑分辨率:需要多高的频率分辨率?
一般情况,我直接上汉宁窗。如果发现频谱泄漏严重,再换布莱克曼窗。如果信号很干净,频率也已知,用矩形窗也行,但记得做幅度校正。
避坑指南:
我曾经在测OFDM信号时用了布莱克曼窗,结果主瓣太宽,把子载波之间的细节全抹平了。后来换成汉宁窗,效果就好多了。所以,窗函数不是越高级越好,得看场景。
4.7 小结
这一章我们聊了:
- 采样定理的工程实践,留余量很重要
- FFT点数与频率分辨率、噪声基底的关系
- 窗函数的作用:压旁瓣、减泄漏
- 汉宁窗和布莱克曼窗的对比与选择
- 加窗后的功率校正,这个千万别忘
下一章,我们会讲如何用这些知识,搭建一个完整的嵌入式频谱仪。到时候,我会把硬件和软件串起来,给你看一个能跑起来的系统。
好,今天就到这里。有问题随时找我。