快速傅里叶变换(FFT)是一种数字信号处理中常用的技术,用于将快速序列转换为频域表示。在嵌入式系统中,如基于STM32的微控制器,实现FFT可以帮助解决信号处理的需求,例如声音处理、图像处理等。本文将介绍基于STM32的离散傅里叶变换的原理、实现方法和应用。/ ^0 g. w% s1 v! q
* T8 `7 g M! P% e
# L# N& m, O r1 }3 G2 m" } [% [; l4 e1 Q5 P8 Q" K" c
FFT是一种将时域序列转换为频域表示的技术,它将一个序列的N个采样点映射到频域中N个频率分量。其数学表达式如下:& W+ M- c; [& T
# c# _5 H, @: L, q+ a
5 Y1 q. ~3 B& x( |' u
; T g5 Z6 X0 i; I! } `% s' B其中,x(n) 是输入序列,X(k) 是输出的频域表示。
, U% q: l5 z9 W J5 @7 u" C4 J
. H& W" Z9 D& o' j$ L6 F3 J3 B准备工作:
. q( b/ R3 T7 d, b3 G* O% o0 O- [; R, \5 Q6 t i& O
) v; m2 K( t( O/ g2 d- X2 Y
; G2 h( U, T; u: @5 S* C
5 _/ B' Q0 w% [" P$ L2 A# C
" ^, x# k9 ~$ A7 u' l* N' h
9 ~" t" u: f' _. b: N1 _( F a( gKeil中的DSP库(Digital Signal Processing Library,数字信号处理库)是针对ARM Cortex-M处理器系列的一组软件库,用于提供各种数字信号处理功能的支持。这些库提供了一系列优化过的算法,可以帮助开发人员在嵌入式系统中高效地实现音频处理、图像处理、通信系统等各种信号处理应用。3 l; @& k. [+ t+ l. k
: @+ y% \4 B' ]6 ], B/ A- s
因此我们需要在Keil中安装我们的DSP库。! j' G) a" d! G. v: M
- #include "arm_math.h" // 包含DSP库
复制代码 ! \7 q7 p3 T [5 D
首先包含我们的DSP库。
2 z0 b8 b; `- L& ~ W; |! [# X4 W% `3 Z1 J! X1 U, F& C% W
* w$ A% T" `9 Y C( u3 j E |- #define FFT_LENGTH 1004 G" m0 W- J2 ^- X7 C
- // 输入序列% }0 V# R- t8 W* {3 D1 S
- float32_t inputSignal[FFT_LENGTH*2];
3 U. C: {/ {* f- r9 q
, o) B+ t* J5 l- // 输出序列,存储变换后的结果
/ r4 [- s. r5 s7 p6 @* ^9 _ - float32_t outputSignal[FFT_LENGTH];
复制代码 2 `: w0 I( m+ h/ N
定义FFT的的输入和输出数组还有数组长度% N8 \# W, C' X5 ]' {- e- N
- arm_status status;- H* q& B5 r+ q" S# L6 L
- arm_cfft_radix4_instance_f32 fft_inst;" |. U3 L; f3 ], m! o! l y
- status = arm_cfft_radix4_init_f32(&fft_inst, FFT_LENGTH,0,1);
复制代码- void arm_cfft_radix4_init_f32(: e; m2 Z! {) N) A J
- arm_cfft_radix4_instance_f32 * S,0 K* C% ~( f! w3 F5 G r
- uint16_t fftLen,
6 W5 U. ^' U% Q0 d# a - uint8_t ifftFlag,
\1 {, n5 |/ |- s2 S - uint8_t bitReverseFlag
- `8 V+ X ]# V - );
复制代码
' r) _, F+ ?8 j5 w/ s! F; {定义一个状态变量用来显示FFT的初始化是否成功。% C; S. G; F+ G+ g- X" E0 c
定义一个FFT的配置变量。
1 ]* t4 t; V4 V3 v6 E初始化FFT。
0 V1 ]* w% [. w" ?9 F8 qS:指向 arm_cfft_radix4_instance_f32 结构体的指针,该结构体定义了 FFT 实例的状态信息。
) s1 A9 B. v6 }
4 z" f5 |! j* {$ T9 E) J9 y8 HfftLen:FFT 的长度。* v7 \6 c+ T s7 L6 Z/ ^' u
5 W8 {. J' e/ Q3 ]7 B* M3 U
ifftFlag:指定是否进行逆变换。如果为 1,则表示初始化的是逆变换的 FFT;如果为 0,则表示初始化的是正变换的 FFT。1 o: Y6 _8 p2 P
% A8 @" t! N2 B+ z* J5 w4 }
bitReverseFlag:指定是否进行比特翻转。如果为 1,则表示进行比特翻转;如果为 0,则表示不进行比特翻转。
0 \- b" {( t9 Y. C1 u+ { a( }1 A; u3 z
在FFT算法中,比特(bit)反转是一种关键的步骤,用于将输入数据重新排列为正确的顺序,以便在后续的计算中进行有效处理。
/ W) @* r6 s8 X4 F0 p- n
k2 `# n0 Q* } K/ W; H. l1 P0 }当进行快速傅立叶变换时,算法要求输入数据的顺序是按照特定的方式排列的。特别是在使用基于分治法的算法(如Cooley-Tukey算法)时,输入数据的顺序必须满足按照一定规律的排列。
; n4 H, N- V: v% b$ \. T7 [4 U! l' P) N; m) Z8 w* d+ g1 @
在实际的FFT实现中,最常见的方式是通过比特反转来重新排列输入数据。比特反转就是将输入数据的比特位(二进制位)的顺序进行颠倒。这是因为在FFT算法中,数据会被分组,并按照一定规则进行反转,以便在每个阶段的运算中,数据可以正确地与其它组合进行配对。
+ j) x5 Q) w+ d& Y+ c* ^% Y1 Z# H" n
举个简单的例子,假设有一个长度为8的数据序列,按照0到7的顺序排列:
# Z" H/ X# z8 b9 ?5 x' R' D0 d* u8 F3 y! ?! |) T* j5 R9 D( T
0 1 2 3 4 5 6 7: L7 y5 ]4 a7 Q9 B) m0 y% D: R
U! {6 E' w; p' ~; f+ ~在进行FFT时,需要按照一定规则重新排列这些数据。比特反转操作将会对这个数据序列进行如下的重新排列:
9 L- R2 V4 U& e1 {! C5 M& H/ V) o! b5 c+ z4 h% ^. \" E4 m# c
0 4 2 6 1 5 3 7
p/ Y4 K, G8 c! m O& `2 w2 N/ H9 L( _
在FFT算法的每个阶段中,这种重新排列都会使得数据正确地与其它组合进行配对,从而实现快速傅立叶变换的计算。/ ]1 ~" n* s' S* O
, d% h7 V* Y% [& g' ^进行FFT并转换为模值
* T; n( P2 Y1 c4 a0 s7 ]% v- arm_cfft_radix4_f32(&fft_inst,inputSignal); //FFT计算
9 z$ S2 O. l* W2 D& [ - arm_cmplx_mag_f32(inputSignal,outputSignal,FFT_LENGTH); //取模得幅值
复制代码
& P; R+ {) @) ^% D2 q2 |对输入数组进行FFT变换,并将FFT的结果转化为模值。; }( N2 N3 G* L. C. W+ v2 g
' ^- G; Q, I4 {5 b) a# x测试
4 d& }7 }8 F( J5 R5 w0 T我们进行一个简单的测试
( T7 h, d: d: O- F7 |- #define FFT_SIZE 1024! }. x3 c/ X+ K; N
- #define SAMPLE_RATE 1000
5 E. Y7 _* w# q2 C - #define NUM_SAMPLES 10007 z1 p- }( L& t6 u- @
- #define FREQ_OF_INTEREST 100/ }4 K) i0 R' q
- for (int i = 0; i < NUM_SAMPLES; i++) {
0 x1 a, r7 _. _6 V! G - float32_t t = (float32_t)i / SAMPLE_RATE;, W ~. \6 D8 E9 C7 i
- float32_t sin_value = sinf(2 * PI * FREQ_OF_INTEREST * t); // 计算正弦波值
* P4 v" T* t$ }* T. z& Q - inputSignal[i * 2] = sin_value; // 实部
( Q- F% }* t3 |/ i' v- v - inputSignal[i * 2 + 1] = 0; // 虚部( [0 O, ~. ^) E- L% z: Q
- }
复制代码
! p7 Q9 s9 N5 W2 A ]! r& |一千个点的采样值,频率假设为100HZ作为输入信号。: z/ F+ [; U0 J g5 j0 d& K! ?
- for (int i = 0; i < FFT_SIZE; i++) {' i8 |+ k7 ^4 S4 R) \
- // 计算复数的模值! L B+ [: T/ y9 @! u" a
- float32_t real = inputSignal[2 * i];
W& B, U/ H" F+ I - float32_t imag = inputSignal[2 * i + 1];
b" q6 I# l7 k' j, C3 k' T - float32_t magnitude = sqrtf(real * real + imag * imag);
- T! _3 a D8 U! u! n - & }" H! G4 s9 p$ P
- // 打印每个频率分量的模值
% \6 T7 g1 i; }7 G3 J# X% k - printf("Magnitude: %f\n", magnitude);" Q# S, S2 W- S8 V1 y0 o
- }
复制代码 - Q3 Y0 s0 X9 L1 ~: i/ y
进行傅里叶变换后打印模值。
% T$ y: Q9 s1 Y1 x% M
) a8 J2 v/ U: v
0 M4 ~7 s( U6 y- _2 n5 i
" W7 E" k, O, ]3 H+ [+ z可以看到傅里叶变换执行成功。
7 x9 b8 w, g8 |& Q# ]& q2 z- for (int i = 0; i < NUM_SAMPLES; i++) {, g4 c7 z8 Y V7 e n
- float32_t t = (float32_t)i / SAMPLE_RATE;
% O' w: ]( L! l( \5 D; [ - float32_t sin_value = sinf(2 * PI * FREQ_OF_INTEREST * t)+sinf(2 * PI * FREQ_OF_INTEREST * t*2)+sinf(3*2 * PI * FREQ_OF_INTEREST * t); // 计算正弦波值/ l6 N1 S5 D+ c1 ]$ Y; S8 _
- inputSignal[i * 2] = sin_value; // 实部7 h/ g. Q/ @# P+ O
- inputSignal[i * 2 + 1] = 0; // 虚部/ n8 j+ V, ~6 J9 D
- }
复制代码 ( q" P* `9 a2 q V' k" p9 I- B& Z
我们将信号制作成100HZ+200HZ+300HZ的信号。
, w& K- D" F) m
( e! k! f [$ n9 c# D5 t
: A) e6 e% a3 c) g/ D- }* t
8 m* v1 _2 o% O; C
- M5 J& q1 i# N
2 ?* T' ~1 \ P0 Q' z转载自:电路小白( y0 K; o2 M) I) X5 U
如有侵权请联系删除
- v8 f# F5 w8 d( l! R% w, ]# O
8 y2 a0 q- \6 D: v |
这个FFT不错,学习参考一下