快速傅里叶变换(FFT)是一种数字信号处理中常用的技术,用于将快速序列转换为频域表示。在嵌入式系统中,如基于STM32的微控制器,实现FFT可以帮助解决信号处理的需求,例如声音处理、图像处理等。本文将介绍基于STM32的离散傅里叶变换的原理、实现方法和应用。9 n8 \% x$ q3 a0 f
# k) j+ j) O+ ^ e
! Z% y' J% S. C, m
4 D# e& W; L+ w* s. EFFT是一种将时域序列转换为频域表示的技术,它将一个序列的N个采样点映射到频域中N个频率分量。其数学表达式如下:
9 r- U c0 P2 U. l- y) p5 t c0 q- Z" v6 ?& T5 o
* n4 W" I6 k4 z: f8 T6 v
0 t }& Y; U( @
其中,x(n) 是输入序列,X(k) 是输出的频域表示。
# h* d7 U d3 k! M/ ^; g4 u; Q7 W3 l* d% w l
准备工作:' b' z+ r1 Z/ `' G' @
' T: G* z; v0 O% K) j* ~$ M$ R' ]" Y& H0 Z* c K# t( `
# [9 X( c( x* S, r2 d9 @ B5 E# w
# Y2 N/ o0 L! M: H. W9 w
: d+ G. Q6 Y) N+ M& Z( b) d# }. N
; o& `# D# s1 m( {/ H+ ?Keil中的DSP库(Digital Signal Processing Library,数字信号处理库)是针对ARM Cortex-M处理器系列的一组软件库,用于提供各种数字信号处理功能的支持。这些库提供了一系列优化过的算法,可以帮助开发人员在嵌入式系统中高效地实现音频处理、图像处理、通信系统等各种信号处理应用。
: g% c8 o8 [8 j& m' K9 U! L+ d6 ?. B0 A
因此我们需要在Keil中安装我们的DSP库。3 [$ q3 t( Z& n" N' v
- #include "arm_math.h" // 包含DSP库
复制代码 % x4 d' F( \' A j+ V
首先包含我们的DSP库。
7 t- @/ U2 l1 B" Y+ C7 c3 d1 I( P2 k4 W: b6 Q
[ x) |2 V6 {* a8 s7 g0 _" V3 T% s- #define FFT_LENGTH 100
( T/ E' G5 p. N3 G! Q7 f - // 输入序列
8 c/ }0 {9 E3 q9 d O Q - float32_t inputSignal[FFT_LENGTH*2];4 B N, {3 K1 I
- $ G4 K. d I! F2 `$ M6 O
- // 输出序列,存储变换后的结果0 z/ O, u5 Z! n. F
- float32_t outputSignal[FFT_LENGTH];
复制代码 4 N' R! [8 p0 U% [, Z
定义FFT的的输入和输出数组还有数组长度
: ~2 u0 T* |" K% T/ I- arm_status status;
7 i8 U: p% m* A+ l9 |4 e/ S - arm_cfft_radix4_instance_f32 fft_inst;, I4 e5 |% T8 y! X. B* A. T7 y
- status = arm_cfft_radix4_init_f32(&fft_inst, FFT_LENGTH,0,1);
复制代码- void arm_cfft_radix4_init_f32(; J$ f# K1 v$ h0 K) K$ |
- arm_cfft_radix4_instance_f32 * S,9 V9 J/ v% c# [% g- J/ j
- uint16_t fftLen,7 f: [4 m: {( F& a
- uint8_t ifftFlag,) V' @8 U7 X+ f
- uint8_t bitReverseFlag
2 P& G& O: K1 L! q7 v - );
复制代码
& y% w1 K' q3 a+ f4 L. O8 y定义一个状态变量用来显示FFT的初始化是否成功。
) }1 p0 L3 D: h. l. f定义一个FFT的配置变量。
( F$ j' `: `+ X m8 w) x, G初始化FFT。
/ R. S' |$ q( i" i- }S:指向 arm_cfft_radix4_instance_f32 结构体的指针,该结构体定义了 FFT 实例的状态信息。% V2 o0 O" ]* [- f9 O
9 b" h1 c8 h. mfftLen:FFT 的长度。% R$ Z* \: P9 l/ n' A2 }& o7 \. x
! q6 P: u5 x) H6 }( r0 _
ifftFlag:指定是否进行逆变换。如果为 1,则表示初始化的是逆变换的 FFT;如果为 0,则表示初始化的是正变换的 FFT。
% J/ V, M, |3 V% Y- x- Y: X9 t8 I+ Q3 ]$ I9 K, D4 [' C; i& b
bitReverseFlag:指定是否进行比特翻转。如果为 1,则表示进行比特翻转;如果为 0,则表示不进行比特翻转。$ x; B! O m i' U8 O
8 g& } C4 \5 [" C在FFT算法中,比特(bit)反转是一种关键的步骤,用于将输入数据重新排列为正确的顺序,以便在后续的计算中进行有效处理。9 O& A$ H |; m, }2 }# b( p
: ~8 E- U# u. ~ N5 z3 F当进行快速傅立叶变换时,算法要求输入数据的顺序是按照特定的方式排列的。特别是在使用基于分治法的算法(如Cooley-Tukey算法)时,输入数据的顺序必须满足按照一定规律的排列。; M- W0 J# G; h8 O) E- H
; y% W' B# b3 F
在实际的FFT实现中,最常见的方式是通过比特反转来重新排列输入数据。比特反转就是将输入数据的比特位(二进制位)的顺序进行颠倒。这是因为在FFT算法中,数据会被分组,并按照一定规则进行反转,以便在每个阶段的运算中,数据可以正确地与其它组合进行配对。3 W* @. q7 b$ F6 F" Z9 l* C
8 E& j* S9 U( l% B9 }
举个简单的例子,假设有一个长度为8的数据序列,按照0到7的顺序排列:
; l. G F3 Y0 q/ T7 }; b3 w |' r/ w) X, k
0 1 2 3 4 5 6 7/ ~( p0 ?, M) e% F4 Z: i H' c9 T0 m/ H
+ P7 j: L+ M' Z T1 a在进行FFT时,需要按照一定规则重新排列这些数据。比特反转操作将会对这个数据序列进行如下的重新排列:8 r4 } I: ~, L2 k$ A
( T* p. j) w5 W4 ]1 r6 c: \% }. { m0 4 2 6 1 5 3 7& s# z9 Y/ r0 O( e7 N. u
( R. W3 W, J3 Z' E/ [* g8 Y
在FFT算法的每个阶段中,这种重新排列都会使得数据正确地与其它组合进行配对,从而实现快速傅立叶变换的计算。
: s. J0 c" V% K$ Y* Y5 ?/ \' D: s" A/ K8 |$ T
进行FFT并转换为模值/ m% [' i3 x& Q3 x# z+ b6 c+ f m
- arm_cfft_radix4_f32(&fft_inst,inputSignal); //FFT计算
5 ?4 z, @1 B4 O, H6 O - arm_cmplx_mag_f32(inputSignal,outputSignal,FFT_LENGTH); //取模得幅值
复制代码
9 s6 o0 T" }* T D0 A$ b/ f对输入数组进行FFT变换,并将FFT的结果转化为模值。/ u" ~/ F/ f* E
7 C$ ^; D7 F1 P: B+ |. J: A u* x测试
4 Y4 j4 Z, T; Z' l& b' Y我们进行一个简单的测试! g# n; f3 B7 y. t1 B
- #define FFT_SIZE 1024
+ P* s3 W+ D% ~5 T: \ E' c - #define SAMPLE_RATE 1000
5 F; y% J- S* I% L" b0 T - #define NUM_SAMPLES 1000
8 h* y, u- @, J( L$ U - #define FREQ_OF_INTEREST 100
. s' {# s8 D6 w4 ~, V - for (int i = 0; i < NUM_SAMPLES; i++) {
0 u2 [7 ^7 j) p# h- y: A - float32_t t = (float32_t)i / SAMPLE_RATE;6 |2 e# H. Z# l% ? L. M" [& h# ~
- float32_t sin_value = sinf(2 * PI * FREQ_OF_INTEREST * t); // 计算正弦波值% E* g5 Y3 C. T" `# F- W3 R! _
- inputSignal[i * 2] = sin_value; // 实部
1 ?/ B2 V! \; Z0 t% q1 m- A - inputSignal[i * 2 + 1] = 0; // 虚部9 e- Z7 ]+ K6 [
- }
复制代码
$ O5 `1 i/ r# |( r& d+ f一千个点的采样值,频率假设为100HZ作为输入信号。
n" V/ A9 E) |" R/ [& S- for (int i = 0; i < FFT_SIZE; i++) {
% r* W4 h2 k, Q4 u - // 计算复数的模值( \8 I" a' n. N! D
- float32_t real = inputSignal[2 * i];+ O9 L% n0 A2 @8 E
- float32_t imag = inputSignal[2 * i + 1];% j0 f3 B2 j5 p! Q
- float32_t magnitude = sqrtf(real * real + imag * imag);; J1 v7 T8 I I' ]1 `1 z- B% {
- + L6 t3 _( u. e1 e2 v+ Z$ e$ P
- // 打印每个频率分量的模值; t) A e7 }$ b9 y6 I
- printf("Magnitude: %f\n", magnitude);
' b6 H5 `6 d; m. Y* i - }
复制代码
; `, ]3 u3 y9 I } M进行傅里叶变换后打印模值。
- ^% J( p% {+ O0 u2 _
( [4 w9 \. \8 }6 e
8 e6 A7 {4 s0 G) ]$ U7 Z2 q9 A0 L( y% @7 b- |" C0 v( z
可以看到傅里叶变换执行成功。& \1 f# d& Q ~# k$ a/ T
- for (int i = 0; i < NUM_SAMPLES; i++) {
s! H: g3 G" u: u - float32_t t = (float32_t)i / SAMPLE_RATE;
1 t% a& N7 U3 a$ v5 L - 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); // 计算正弦波值
; `7 ~/ m: k7 | - inputSignal[i * 2] = sin_value; // 实部2 Y8 y5 m7 f/ S7 X. T
- inputSignal[i * 2 + 1] = 0; // 虚部
U0 D, M5 q7 m6 {3 j - }
复制代码
( y! \5 Y$ U" f. P我们将信号制作成100HZ+200HZ+300HZ的信号。
! J5 ]1 C0 B2 A7 m9 [: B" t% X- e1 S; g) z ]4 w( p6 s% G
1 X8 r; l- B2 _
% o" U9 k% Q6 K( u8 W- Y, q, h; e7 j) }# G& N
1 ]6 g" m/ h: `6 B$ ?转载自:电路小白: q, i' S' z& K: }& n& V( m
如有侵权请联系删除% m" T+ J4 ?* ?, e
( A" L- t. l7 u3 I) p |
这个FFT不错,学习参考一下