快速傅里叶变换(FFT)是一种数字信号处理中常用的技术,用于将快速序列转换为频域表示。在嵌入式系统中,如基于STM32的微控制器,实现FFT可以帮助解决信号处理的需求,例如声音处理、图像处理等。本文将介绍基于STM32的离散傅里叶变换的原理、实现方法和应用。5 J, U! J, ]" @$ e2 u5 J2 r
! Y* L: ]- q- |& e9 H* v
6 H7 H6 F3 a0 B
8 |5 U9 R8 G/ {FFT是一种将时域序列转换为频域表示的技术,它将一个序列的N个采样点映射到频域中N个频率分量。其数学表达式如下:- m. ?* j7 I1 t" W2 q
8 u# j3 U# `3 Q9 b
x/ ~5 [- Y1 V7 e3 H& k$ E- Y1 y
3 v$ s r, f8 f% w1 y其中,x(n) 是输入序列,X(k) 是输出的频域表示。
5 _8 m+ c* M. I- w) S* l) {0 W* G+ B
准备工作:1 I5 T( ? B& y2 m2 |
* S" E5 v1 V$ M3 z* X
5 Y% w/ ^. P4 J* y
4 O S3 f: U9 b0 W
8 E+ ^& a9 z$ r' ~; B3 Q
2 l& X2 F, i( M; E4 X: W5 t* u& Q% B! W/ i- l$ n$ q
Keil中的DSP库(Digital Signal Processing Library,数字信号处理库)是针对ARM Cortex-M处理器系列的一组软件库,用于提供各种数字信号处理功能的支持。这些库提供了一系列优化过的算法,可以帮助开发人员在嵌入式系统中高效地实现音频处理、图像处理、通信系统等各种信号处理应用。: a+ `5 v7 m& [6 M/ {
5 ~: I6 I4 y) a6 }因此我们需要在Keil中安装我们的DSP库。
- B6 W( z. h4 N. t5 ?2 h5 ?% R- #include "arm_math.h" // 包含DSP库
复制代码 . d. [9 {5 Z; y U: `, `1 x
首先包含我们的DSP库。/ v0 M/ F' k9 N9 C6 \
% d8 M% W; Q6 v- b
/ i- q* @+ d7 R' l; X0 g; S
- #define FFT_LENGTH 100) A+ Q& v4 l/ W/ a3 G* |: H
- // 输入序列; x/ X" m8 e; z6 |8 L" v, ^
- float32_t inputSignal[FFT_LENGTH*2];
' E2 N5 K. |/ W8 v - 2 O, s( S4 C) @) s: Y
- // 输出序列,存储变换后的结果
1 k; D6 q1 M- p! s6 N/ i6 ] - float32_t outputSignal[FFT_LENGTH];
复制代码 8 P5 a. C' x1 X9 Z5 I e
定义FFT的的输入和输出数组还有数组长度
P/ ?! p+ P8 [- arm_status status;! W3 j9 d! W, k0 B6 }$ X
- arm_cfft_radix4_instance_f32 fft_inst;
; q: g+ E4 d7 V( G - status = arm_cfft_radix4_init_f32(&fft_inst, FFT_LENGTH,0,1);
复制代码- void arm_cfft_radix4_init_f32() T' ]! _7 [5 B# q0 U7 b
- arm_cfft_radix4_instance_f32 * S,- H5 E/ ^5 Y+ H
- uint16_t fftLen,
3 D) ?# q6 B( j4 Z9 O. C# k - uint8_t ifftFlag,: C8 R2 z! V: X
- uint8_t bitReverseFlag* h! [: n. F0 \& q( Z' O8 r. H
- );
复制代码 ) A/ ] ?9 \' X) F$ a
定义一个状态变量用来显示FFT的初始化是否成功。5 J6 H. S0 U& W* T- _
定义一个FFT的配置变量。$ i* b' [9 Q2 a+ l; c# I9 Z y) S
初始化FFT。& y2 J: _' } O9 [7 @& T
S:指向 arm_cfft_radix4_instance_f32 结构体的指针,该结构体定义了 FFT 实例的状态信息。
( W5 G9 n- F# M0 b2 e! A4 }5 x, p! x6 M1 @ w7 o0 M
fftLen:FFT 的长度。# v$ _, _2 x) C4 A: t
2 F8 R% c: j/ B, d' ]ifftFlag:指定是否进行逆变换。如果为 1,则表示初始化的是逆变换的 FFT;如果为 0,则表示初始化的是正变换的 FFT。
) ~/ n8 ]0 ^7 O; C& g: ~% j
* V4 G% A# `9 h K+ E5 A- r% \* P% VbitReverseFlag:指定是否进行比特翻转。如果为 1,则表示进行比特翻转;如果为 0,则表示不进行比特翻转。% N+ _4 |3 U3 t
1 h8 q1 r# V) w7 o
在FFT算法中,比特(bit)反转是一种关键的步骤,用于将输入数据重新排列为正确的顺序,以便在后续的计算中进行有效处理。
$ B( _4 c, e* k }2 |2 `; ~+ ~+ _0 B- N
当进行快速傅立叶变换时,算法要求输入数据的顺序是按照特定的方式排列的。特别是在使用基于分治法的算法(如Cooley-Tukey算法)时,输入数据的顺序必须满足按照一定规律的排列。
9 v3 C% m& k0 D1 M2 a+ e
& C" [9 ~/ ~$ G/ B, Q, h在实际的FFT实现中,最常见的方式是通过比特反转来重新排列输入数据。比特反转就是将输入数据的比特位(二进制位)的顺序进行颠倒。这是因为在FFT算法中,数据会被分组,并按照一定规则进行反转,以便在每个阶段的运算中,数据可以正确地与其它组合进行配对。
) e- B0 O* F3 C$ y7 w+ g, A8 ]2 A. L- Y. M; y9 s' {
举个简单的例子,假设有一个长度为8的数据序列,按照0到7的顺序排列:* Y" m& c: U- t% D
# e. |, B* F/ O2 \3 z
0 1 2 3 4 5 6 7
2 R5 Z# M$ P( M9 J8 C# v
7 a- Z- u0 I3 t3 h在进行FFT时,需要按照一定规则重新排列这些数据。比特反转操作将会对这个数据序列进行如下的重新排列:
3 X% j& h- R: z0 L6 a, k; r, o! |0 V8 ~+ H
0 4 2 6 1 5 3 72 x7 ^. K% d" P- V1 t# `9 i
5 \; S% q( n, _4 _1 B1 R在FFT算法的每个阶段中,这种重新排列都会使得数据正确地与其它组合进行配对,从而实现快速傅立叶变换的计算。; @1 s) G" q* p
5 T/ B- s5 f4 c进行FFT并转换为模值1 `: e6 a9 J9 N/ \( ~! X# K
- arm_cfft_radix4_f32(&fft_inst,inputSignal); //FFT计算
7 |$ h# v! J! I - arm_cmplx_mag_f32(inputSignal,outputSignal,FFT_LENGTH); //取模得幅值
复制代码 % \/ ?& k8 w0 a* R: ^
对输入数组进行FFT变换,并将FFT的结果转化为模值。% V% Q p1 ?- q) U5 x) R% `
( K& O' Q! p' p
测试
3 K& _, a( k5 G5 z9 X我们进行一个简单的测试
0 v2 i+ L/ c2 w9 g, N- #define FFT_SIZE 1024: f! b9 A# M; }9 F( b
- #define SAMPLE_RATE 10005 P$ L6 @3 j7 N& k) R
- #define NUM_SAMPLES 1000- L, {4 b# a! I" ], x* V2 B
- #define FREQ_OF_INTEREST 100
2 p2 F: R! O1 i, a$ ~, g6 C - for (int i = 0; i < NUM_SAMPLES; i++) {( {8 X' S- n, C
- float32_t t = (float32_t)i / SAMPLE_RATE;
% v$ P0 c) c$ | - float32_t sin_value = sinf(2 * PI * FREQ_OF_INTEREST * t); // 计算正弦波值% W! L# @/ Q" g+ e2 B8 F$ M! ?
- inputSignal[i * 2] = sin_value; // 实部1 G: _( S% z o) \5 h
- inputSignal[i * 2 + 1] = 0; // 虚部 s- T$ G# ^# g" Z* p$ y5 |9 N/ v
- }
复制代码
: i6 X' t! t4 H7 W9 h一千个点的采样值,频率假设为100HZ作为输入信号。3 I& o/ y0 A9 D; \6 x% i% h; `
- for (int i = 0; i < FFT_SIZE; i++) {1 H! r( E# d* F( r7 ?
- // 计算复数的模值- M$ I' w( a. L6 O* ]. ~, D
- float32_t real = inputSignal[2 * i];
3 C t. \9 C& l/ U - float32_t imag = inputSignal[2 * i + 1];
. H1 ^7 {, L( f7 s4 P - float32_t magnitude = sqrtf(real * real + imag * imag);3 S6 I5 ^9 M* R) c( y- O
-
o2 e( c- A8 u - // 打印每个频率分量的模值; X+ G1 k8 @. a! w1 P5 c4 K
- printf("Magnitude: %f\n", magnitude);
$ e7 c5 B6 ~& u. i8 h, G - }
复制代码 ( _$ k) A' K1 v: u x: ?* w2 ?
进行傅里叶变换后打印模值。
9 K# S" m7 Y/ ?- D7 T: ^2 @: M6 i/ G7 B0 k# _4 p( w2 @
/ y/ H. O* Z8 h
. @% p! c& A A可以看到傅里叶变换执行成功。! t# K0 ^) W* n7 `# E7 t
- for (int i = 0; i < NUM_SAMPLES; i++) {! z; [- ]- ~: L
- float32_t t = (float32_t)i / SAMPLE_RATE;
' h K, |6 ~5 ]% N3 @/ ]% O7 y, A - 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); // 计算正弦波值# S: t7 O* w4 I
- inputSignal[i * 2] = sin_value; // 实部
2 d- o$ i) P" s& }5 ? - inputSignal[i * 2 + 1] = 0; // 虚部9 N% q: S. F- h2 m
- }
复制代码 , b. Y) ~% m7 p9 v
我们将信号制作成100HZ+200HZ+300HZ的信号。
1 J/ D4 h( X) m. L' F4 s6 K, k8 B: v0 {6 O8 o/ G
z5 ?) }0 G4 `" K& z2 M# M. U
- f9 D9 f* m+ H: E: O K# \5 n
' E0 P( `8 x, f' E9 D7 n' m- p
6 i8 }' W t( ]2 y
转载自:电路小白
/ c6 I4 K7 t" ?如有侵权请联系删除7 s( ]# F5 S3 h/ X! v
$ N1 B+ }2 _3 |( @1 j |
这个FFT不错,学习参考一下