4、FFT基本原理:离散傅里叶变换到FFT、蝶形运算、旋转因子、按时间抽取与按频率抽取

在CMSIS-DSP定点FFT的实现与优化中,理解FFT(快速傅里叶变换)的数学本质与运算结构是掌握缩放策略与溢出控制的前提。FFT并非一种新的变换,而是离散傅里叶变换(DFT)的一种高效计算算法。本章将从DFT的定义出发,剖析其计算瓶颈,进而引出FFT的核心思想——蝶形运算与旋转因子,并详细对比按时间抽取(DIT)与按频率抽取(DIF)两种经典分解路径。这些原理直接决定了定点实现中数据位宽的增长模式、缩放因子的插入位置以及溢出风险的分布特征。

DFT 直接计算 FFT 蝶形分解 旋转因子 W_N^k X[k] = Σ x[n]·W_N^{nk} n=0..N-1, k=0..N-1 复杂度: O(N²) N=1024 → 约100万次复数乘加 定点实现: 每级累加位宽增长 N点DFT → 2×(N/2)点DFT → 递归分解至2点蝶形 复杂度: O(N·log₂N) N=1024 → 约1万次复数乘加 每级蝶形: 1次复数乘 + 2次复数加 W_N^k = e^{-j2πk/N} = cos(2πk/N) - j·sin(2πk/N) 对称性: W_N^{k+N/2} = -W_N^k 周期性: W_N^{k+N} = W_N^k 定点查表: 存储cos/sin值 FFT 分解路径对比 按时间抽取 (DIT) 输入: 按奇偶分组 (bit-reverse) 输出: 自然顺序 蝶形: 输入位宽增长 → 需缩放 CMSIS-DSP: arm_cfft_radix4 按频率抽取 (DIF) 输入: 自然顺序 输出: 按奇偶分组 (bit-reverse) 蝶形: 先乘后加 → 溢出风险不同 常用于基-2/基-4混合实现 分解策略不同 但计算量相同

上图展示了DFT直接计算与FFT分解的核心差异,以及旋转因子的数学特性。DFT的O(N²)复杂度在嵌入式实时信号处理中是不可接受的,而FFT通过分治策略将复杂度降至O(N·log₂N)。对于CMSIS-DSP定点实现而言,理解蝶形运算的数值行为比单纯记忆公式更为重要——因为定点数的有限字长效应会在每一级蝶形中累积,而缩放策略正是基于这种累积规律设计的。

4.1 离散傅里叶变换的计算瓶颈与FFT的诞生

4.1.1 核心原理:DFT的数学定义与计算量分析

对于长度为N的有限长序列x[n],其N点DFT定义为:

X[k] = Σ_{n=0}^{N-1} x[n] · W_N^{nk},  k = 0, 1, ..., N-1

其中W_N = e^{-j2π/N}为旋转因子。直接计算每个X[k]需要N次复数乘法和(N-1)次复数加法,计算全部N个频点则需要N²次复数乘法和N(N-1)次复数加法。当N=1024时,复数乘法次数超过100万次,这在Cortex-M系列处理器上即使以CMSIS-DSP优化库运行,也需要数毫秒量级,无法满足实时频谱分析需求。

FFT的核心洞察在于:旋转因子W_N^{nk}具有周期性和对称性,使得DFT运算中存在大量重复计算。通过将N点DFT分解为多个较短的DFT,可以显著减少乘法次数。以基-2 FFT为例,N点DFT可分解为两个N/2点DFT,每个N/2点DFT再分解为两个N/4点DFT,如此递归直至2点DFT。整个分解过程形成log₂N级蝶形运算,每级包含N/2个蝶形单元。

4.1.2 判断方法:何时必须使用FFT而非直接DFT

在实际工程中,选择FFT还是直接DFT取决于以下因素:

  • 变换点数N:当N≥32时,FFT的优势已非常明显;N<16时直接DFT可能更简单。
  • 实时性要求:若系统要求频谱更新率高于1kHz,N=256以上的DFT几乎不可能实时完成,必须使用FFT。
  • 定点实现约束:CMSIS-DSP提供定点FFT函数(如arm_cfft_q15),其内部已针对Cortex-M内核的SIMD指令和饱和运算进行了优化,直接调用库函数比手写DFT更可靠。
经验提醒:在CMSIS-DSP中,FFT函数要求输入数据按特定格式排列(如q15格式的实部/虚部交替存储),且变换长度必须是2的幂次(如256、512、1024)。使用前务必查阅CMSIS-DSP文档确认数据排列方式,否则即使算法正确,输出也会错乱。

4.1.3 案例拆解:从DFT到FFT的复杂度对比

假设一个电机振动监测系统需要以40kHz采样率采集1024点数据进行频谱分析。直接DFT需要约100万次复数乘法,在180MHz的Cortex-M4上每次复数乘法约需10个周期,总耗时约55ms。而基-2 FFT只需约5120次复数乘法(1024×log₂1024/2),耗时约0.28ms,性能提升近200倍。这一差距在定点实现中更为关键——DFT的累加次数多,定点溢出风险远高于FFT。

4.1.4 常见误区:忽略FFT的位宽增长特性

初学者常误以为FFT减少了运算量,也同时减少了定点溢出的风险。实际上,FFT的每级蝶形运算都会产生数据位宽增长。以Q15格式为例,输入数据为16位有符号数,经过一级蝶形后,加法结果可能达到17位,乘法结果可能达到31位。CMSIS-DSP的q15 FFT函数内部采用饱和运算和可选的缩放因子来管理这种增长,但用户必须理解缩放因子的作用,否则输出可能严重失真。

4.2 蝶形运算:FFT的基本计算单元

4.2.1 核心原理:蝶形运算的数学结构与数据流

基-2蝶形运算是FFT的最小计算单元,其输入为两个复数a和b,输出为两个复数A和B:

A = a + b·W_N^k
B = a - b·W_N^k

其中W_N^k为旋转因子。从计算角度看,每个蝶形需要1次复数乘法(b×W_N^k)和2次复数加法/减法。在定点实现中,复数乘法涉及4次实数乘法和2次实数加减法,而复数加减法各涉及2次实数加减法。CMSIS-DSP的蝶形运算针对Cortex-M内核的饱和算术指令进行了优化,能够在单个周期内完成16位饱和加减法。

蝶形运算的关键特性是原位计算(in-place):输出A和B可以直接覆盖输入a和b的存储位置,无需额外缓冲区。这一特性使得FFT的内存占用仅为输入数据的2倍(实部+虚部),非常适合内存受限的嵌入式系统。

4.2.2 操作流程:蝶形运算的定点实现步骤

在Q15定点格式下,蝶形运算的实现流程如下:

  1. 加载操作数:从内存读取a_re, a_im, b_re, b_im(均为q15格式)。
  2. 旋转因子查表:从预计算的旋转因子表中读取W_N^k的实部(cos值)和虚部(-sin值),同样为q15格式。
  3. 复数乘法:计算temp_re = b_re×cos - b_im×(-sin),temp_im = b_re×(-sin) + b_im×cos。乘法结果需进行饱和左移(通常左移1位以保留符号位)。
  4. 复数加减:A_re = a_re + temp_re,A_im = a_im + temp_im;B_re = a_re - temp_re,B_im = a_im - temp_im。加减法使用饱和指令防止溢出。
  5. 存储结果:将A和B写回原地址。
风险提示:在定点蝶形运算中,复数乘法后的中间结果位宽可能达到31位(Q15×Q15=Q30),必须进行适当的右移或饱和处理。CMSIS-DSP的q15 FFT函数默认在每级蝶形后执行一次右移1位的操作(相当于乘以0.5),以防止数据溢出。如果输入信号幅度较大,可能需要额外的预缩放。

4.2.3 案例拆解:8点FFT的蝶形运算全流程

以8点基-2 FFT为例,共需3级蝶形运算(log₂8=3),每级4个蝶形。第一级蝶形使用旋转因子W_8^0=1,第二级使用W_8^0和W_8^2,第三级使用W_8^0、W_8^1、W_8^2、W_8^3。下表展示了各级蝶形的旋转因子分布:

级数蝶形索引旋转因子旋转因子值(Q15近似)
第1级0,1,2,3W_8^032767 + j0
第2级0,1W_8^032767 + j0
第2级2,3W_8^20 - j32767
第3级0W_8^032767 + j0
第3级1W_8^123170 - j23170
第3级2W_8^20 - j32767
第3级3W_8^3-23170 - j23170

在定点实现中,旋转因子表通常以const数组形式存储在Flash中,避免运行时计算三角函数。CMSIS-DSP提供了arm_cfft_q15函数,内部自动处理旋转因子查表和蝶形运算,用户只需提供输入数组和变换长度。

4.2.4 常见误区:忽视蝶形运算中的饱和处理

许多开发者在定点FFT实现中只关注算法逻辑,却忽略了饱和处理的重要性。当输入信号接近满量程(如Q15格式的±32767)时,蝶形运算的加法结果可能超过16位范围。CMSIS-DSP的饱和指令(如SSAT)会自动将结果钳位到最大/最小值,但这会引入非线性失真。正确的做法是在FFT前对输入数据进行适当的缩放(如右移2位),确保整个变换过程中数据不会频繁触及饱和边界。

4.3 旋转因子:FFT的相位旋转核心

4.3.1 核心原理:旋转因子的数学性质与查表优化

旋转因子W_N^k = e^{-j2πk/N} = cos(2πk/N) - j·sin(2πk/N)是FFT运算中唯一的乘法系数。其核心性质包括:

  • 周期性:W_N^{k+N} = W_N^k,因此只需存储k=0到N/2-1的N/2个值。
  • 对称性:W_N^{k+N/2} = -W_N^k,W_N^{N/2-k} = -j·W_N^k(共轭对称)。
  • 可约性:W_{2N}^{2k} = W_N^k,用于混合基FFT。

在定点实现中,旋转因子以Q15格式存储。例如W_8^1 = cos(π/4) - j·sin(π/4) ≈ 0.7071 - j0.7071,Q15近似值为23170 - j23170。CMSIS-DSP的旋转因子表按特定顺序排列,以支持基-2、基-4和混合基FFT算法。

4.3.2 判断方法:旋转因子表的存储策略

选择旋转因子表的存储方式需考虑以下因素:

  • Flash容量:N点FFT需要N/2个复数旋转因子,每个复数占4字节(2×q15),N=1024时需2KB Flash。对于资源紧张的MCU,可只存储1/4周期(N/4个值),其余通过对称性推导。
  • 计算速度:查表比实时计算三角函数快约10-20倍。CMSIS-DSP默认使用全表存储,以换取最高执行速度。
  • 精度需求:Q15格式的旋转因子精度约为0.00003,对于大多数信号处理应用足够。若需要更高精度,可使用Q31格式,但运算速度会下降。

4.3.3 案例拆解:旋转因子在CMSIS-DSP中的实际应用

在CMSIS-DSP的arm_cfft_q15函数中,旋转因子表由arm_cfft_q15_init函数初始化。用户需要提供旋转因子表数组和变换长度。例如,初始化1024点FFT:

arm_cfft_instance_q15 fft_inst;
arm_cfft_q15_init(&fft_inst, 1024, 0, 1);
// 参数: 实例指针, FFT长度, 正变换/反变换标志, 位反转标志

该函数内部会生成或引用预定义的旋转因子表。对于q15格式,CMSIS-DSP使用16位旋转因子,并在蝶形运算中通过饱和乘法指令(如SMULBB)实现复数乘法。值得注意的是,CMSIS-DSP的旋转因子表是按位反转顺序排列的,这是为了配合DIT算法的输入位反转要求。

4.3.4 常见误区:旋转因子精度不足导致的频谱泄漏

当旋转因子以Q15格式存储时,其量化误差约为±1 LSB。对于N=1024的FFT,这种误差在各级蝶形中累积,可能导致输出信噪比下降约6dB。如果应用对频谱精度要求较高(如高精度谐波分析),建议使用Q31格式的旋转因子或浮点FFT。CMSIS-DSP同时提供arm_cfft_f32浮点版本,但执行速度约为定点版本的1/4。

4.4 按时间抽取(DIT)与按频率抽取(DIF)

4.4.1 核心原理:两种分解策略的数学等价性

DIT和DIF是FFT的两种基本分解策略,它们在数学上等价,但数据流方向不同:

  • DIT(按时间抽取):将输入序列x[n]按奇偶位置分解为两个N/2点子序列,分别进行DFT,再通过蝶形运算合并。输入需要位反转排列,输出为自然顺序。
  • DIF(按频率抽取):将输出序列X[k]按奇偶频率分解,输入为自然顺序,输出需要位反转排列。蝶形运算中先进行加减法,再进行旋转因子乘法。

两种方法的计算量完全相同(N/2·log₂N次复数乘法),但蝶形运算的结构不同。DIT的蝶形为“先乘后加”,DIF的蝶形为“先加后乘”。这一差异在定点实现中影响显著:DIT的乘法结果直接参与加法,可能产生较大的中间值;DIF的加法结果先被缩放,再进行乘法,数值范围更可控。

4.4.2 操作流程:DIT与DIF的定点实现对比

在CMSIS-DSP中,arm_cfft_q15函数默认使用DIT算法(基-4混合基)。其内部流程为:

  1. 位反转排列:将输入数据按位反转顺序重新排列。
  2. 基-4蝶形运算:每级处理4个输入,产生4个输出,使用3个旋转因子。
  3. 缩放控制:每级蝶形后执行一次右移2位的操作(相当于乘以0.25),防止溢出。

对于DIF算法,CMSIS-DSP未直接提供,但用户可通过修改旋转因子表顺序和蝶形结构自行实现。DIF的优势在于输入数据无需位反转,适合数据流式处理场景(如实时频谱分析仪)。

4.4.3 案例拆解:DIT与DIF在定点溢出控制中的差异

考虑一个输入信号幅度接近满量程的Q15数据序列。在DIT算法中,第一级蝶形的乘法结果(b×W_N^k)可能达到Q30格式,随后与a相加时需先右移15位,这可能导致精度损失。而在DIF算法中,第一级蝶形先执行a+b和a-b,结果范围扩大1倍(Q16格式),随后乘以旋转因子时需右移16位,精度损失更小。因此,对于输入信号动态范围较大的场景,DIF算法在定点实现中通常具有更好的数值稳定性。

特性DIT(按时间抽取)DIF(按频率抽取)
输入顺序位反转自然顺序
输出顺序自然顺序位反转
蝶形结构先乘后加先加后乘
定点溢出风险乘法后加法可能溢出加法后乘法范围更可控
CMSIS-DSP支持arm_cfft_q15(默认)需自定义实现
适用场景通用频谱分析流式处理、高动态范围输入

4.4.4 常见误区:混淆位反转顺序与缩放策略

一个常见错误是认为DIT和DIF的位反转顺序可以互换。实际上,位反转顺序是算法结构决定的,不能随意更改。如果使用DIT算法但输入数据未进行位反转,输出将完全错误。CMSIS-DSP的arm_cfft_q15函数内部已处理位反转,用户只需确保输入数据按自然顺序排列即可。另一个误区是忽视缩放策略的差异——DIT和DIF的每级缩放因子可能不同,直接套用会导致溢出或精度下降。

总结复盘:FFT的核心价值在于通过蝶形运算和旋转因子将DFT的O(N²)复杂度降至O(N·log₂N)。在CMSIS-DSP定点实现中,理解蝶形运算的位宽增长规律、旋转因子的Q15量化误差以及DIT/DIF的数值特性,是设计有效缩放策略的基础。实际工程中,建议优先使用CMSIS-DSP提供的arm_cfft_q15函数,并根据输入信号幅度调整预缩放因子,同时监控输出数据的饱和情况,必要时采用分段FFT或浮点FFT作为备选方案。