快速傅里叶变换(FFT)是一种数字信号处理中常用的技术,用于将快速序列转换为频域表示。在嵌入式系统中,如基于STM32的微控制器,实现FFT可以帮助解决信号处理的需求,例如声音处理、图像处理等。本文将介绍基于STM32的离散傅里叶变换的原理、实现方法和应用。* u$ a- \- Z9 p$ Y
# D5 W: \$ [" u, N& D) \0 w; F
) o# h" F8 z- t! d0 q$ r9 h
: P. y8 D) G( W5 sFFT是一种将时域序列转换为频域表示的技术,它将一个序列的N个采样点映射到频域中N个频率分量。其数学表达式如下:
* }* r# }9 F: a; B4 z+ W( t4 s: q" l, k2 ]1 S' C: l5 ~
' }' E% w& D+ F# C8 V
2 u% p v8 m* r; S" q7 @其中,x(n) 是输入序列,X(k) 是输出的频域表示。( q2 `; u% n- B' q
/ U6 x4 [9 k. _& U$ P( A准备工作:/ q1 c) i6 D( S
q4 i' o; U3 l- L0 n+ N5 f
2 i' w. ^! F& R* z
% b* D5 j" M* I6 i; o" [* F+ _
# r7 S" G4 K; W ~9 k W
: W! @: }4 N, }
z( O" m" h$ m( A6 `, qKeil中的DSP库(Digital Signal Processing Library,数字信号处理库)是针对ARM Cortex-M处理器系列的一组软件库,用于提供各种数字信号处理功能的支持。这些库提供了一系列优化过的算法,可以帮助开发人员在嵌入式系统中高效地实现音频处理、图像处理、通信系统等各种信号处理应用。
3 w0 X: x* S' B. m8 \# O& F* u9 S
因此我们需要在Keil中安装我们的DSP库。
/ \+ e" m- e$ D# |* [- #include "arm_math.h" // 包含DSP库
复制代码 : d# B3 M! d# S* S& O
首先包含我们的DSP库。2 `; J4 i5 v) e/ P
+ l/ Q! O/ A8 _' E+ R% M6 Q. X5 O+ K' m
- #define FFT_LENGTH 100$ o) Z3 z8 I6 o8 w" g
- // 输入序列
8 S' [: n! z/ b R- e - float32_t inputSignal[FFT_LENGTH*2];
# q+ b7 A" e4 S+ x5 G1 l4 _9 N# e - ! x2 m0 ]6 k: n$ [
- // 输出序列,存储变换后的结果
- l6 l7 c5 K. S5 j8 s - float32_t outputSignal[FFT_LENGTH];
复制代码
0 `. k9 l- p& b% L( f5 j2 J2 E3 T定义FFT的的输入和输出数组还有数组长度
4 m8 E( r: r" \# y" D2 |- arm_status status;
( a/ p/ f2 L0 w1 Y - arm_cfft_radix4_instance_f32 fft_inst;6 x& F+ j G$ K
- status = arm_cfft_radix4_init_f32(&fft_inst, FFT_LENGTH,0,1);
复制代码- void arm_cfft_radix4_init_f32(
, a x2 Y6 ]2 Y$ }/ w- w1 q - arm_cfft_radix4_instance_f32 * S,
6 O. O: \1 Q- }# l$ l$ M% S - uint16_t fftLen,
1 d- M: l Z k: G3 e, y# h - uint8_t ifftFlag,$ g( C: w6 }2 L, Z7 c% p) b
- uint8_t bitReverseFlag
. f' {7 ]3 i/ q. F" D. x& V, q: J - );
复制代码 5 f% o9 i9 x# O4 `0 @) @ ~
定义一个状态变量用来显示FFT的初始化是否成功。4 y b3 p' b% E# P6 u! ? y8 v$ v
定义一个FFT的配置变量。
: i- }: Q9 u- b; [% U初始化FFT。& Q" K2 b/ G6 ^$ B {5 C
S:指向 arm_cfft_radix4_instance_f32 结构体的指针,该结构体定义了 FFT 实例的状态信息。8 i* e7 E T5 X/ s( {
& e% f2 z0 q4 f: p; U- jfftLen:FFT 的长度。
w3 f% Z8 D4 P& O
2 W( H+ Y" z6 @, i; SifftFlag:指定是否进行逆变换。如果为 1,则表示初始化的是逆变换的 FFT;如果为 0,则表示初始化的是正变换的 FFT。2 p# `- i, }: i! m
9 g9 f+ u# Y4 s/ R7 {5 C
bitReverseFlag:指定是否进行比特翻转。如果为 1,则表示进行比特翻转;如果为 0,则表示不进行比特翻转。
" u1 Y" [+ d5 H! P+ N# e+ w
: x5 x$ ^2 D1 g在FFT算法中,比特(bit)反转是一种关键的步骤,用于将输入数据重新排列为正确的顺序,以便在后续的计算中进行有效处理。
1 V/ p9 ~5 ` c* N$ D$ D+ ? F2 Z% Z3 t& ^- o
当进行快速傅立叶变换时,算法要求输入数据的顺序是按照特定的方式排列的。特别是在使用基于分治法的算法(如Cooley-Tukey算法)时,输入数据的顺序必须满足按照一定规律的排列。5 {3 W; ~6 M6 }- K% `& r+ ?
8 u1 P5 I8 i' x- E/ m' e' U- w在实际的FFT实现中,最常见的方式是通过比特反转来重新排列输入数据。比特反转就是将输入数据的比特位(二进制位)的顺序进行颠倒。这是因为在FFT算法中,数据会被分组,并按照一定规则进行反转,以便在每个阶段的运算中,数据可以正确地与其它组合进行配对。
' _& y0 R* I$ G' P
0 y0 x1 g$ g% F, T- ]* s举个简单的例子,假设有一个长度为8的数据序列,按照0到7的顺序排列:
) e3 ]% U0 B( x. d7 b
7 t! y9 C f! o7 ~* z; Y& N8 K6 k0 1 2 3 4 5 6 7
' V* e2 Z7 ~" E) \) d* i n+ O0 s' x
在进行FFT时,需要按照一定规则重新排列这些数据。比特反转操作将会对这个数据序列进行如下的重新排列:
+ h) j2 R/ {0 y5 V* k. P% a
# S; s' | d( {5 j0 4 2 6 1 5 3 7
& {5 V5 w( [( @+ f! J( W1 G5 A& o# P/ O
在FFT算法的每个阶段中,这种重新排列都会使得数据正确地与其它组合进行配对,从而实现快速傅立叶变换的计算。
4 r1 E) C1 M& _3 @8 d( r9 A7 p8 {" j3 k% t
进行FFT并转换为模值
3 J( c. }# [# x$ ~3 g3 a2 \& w- arm_cfft_radix4_f32(&fft_inst,inputSignal); //FFT计算6 M3 f. t: G* O6 {$ a
- arm_cmplx_mag_f32(inputSignal,outputSignal,FFT_LENGTH); //取模得幅值
复制代码
# `$ u# s8 [7 `; e; J B! `/ R对输入数组进行FFT变换,并将FFT的结果转化为模值。% L. [& j8 E1 ] o1 L i, T
- l$ R6 e7 w1 N8 O测试2 Y! \ e6 m" k7 f+ u
我们进行一个简单的测试
! U7 Z, I4 R) Q4 H- #define FFT_SIZE 1024: F+ v3 T [# Y$ r, V4 U8 l! u
- #define SAMPLE_RATE 1000) W! s3 D: \# @' v
- #define NUM_SAMPLES 10000 Y+ E+ {+ E' A3 [* ]- | Y
- #define FREQ_OF_INTEREST 100
4 I z& ^5 @' ]* h1 G8 ^3 J/ s) ^9 [ - for (int i = 0; i < NUM_SAMPLES; i++) {
2 S- Q+ e& s. [! w, b0 f4 a9 [ - float32_t t = (float32_t)i / SAMPLE_RATE;3 N+ t( v, c1 L7 f2 b2 R' o
- float32_t sin_value = sinf(2 * PI * FREQ_OF_INTEREST * t); // 计算正弦波值
9 [, ]. B$ H. a+ [; M& B% K1 e - inputSignal[i * 2] = sin_value; // 实部
* A/ q9 j$ o* ]* p - inputSignal[i * 2 + 1] = 0; // 虚部
# f3 _" t% O! d - }
复制代码 ' ]% i! E9 E+ P" J7 X
一千个点的采样值,频率假设为100HZ作为输入信号。' j' d! ~" a' G* r6 ^- _4 e3 }' P
- for (int i = 0; i < FFT_SIZE; i++) {0 }$ y# B2 f. T( i, L
- // 计算复数的模值
' r0 l/ |' B: ]$ | - float32_t real = inputSignal[2 * i];
5 o' a& f8 n2 M' D - float32_t imag = inputSignal[2 * i + 1];* V# i+ u& o4 C6 b: ]
- float32_t magnitude = sqrtf(real * real + imag * imag);9 e# B. H6 c# T
- " V4 A2 i3 ?2 B. Q5 ^" v) d
- // 打印每个频率分量的模值
" X! y1 N% m% P& c8 S - printf("Magnitude: %f\n", magnitude);
/ r" w6 U+ ]! Z" A) E - }
复制代码 , e) I( g7 z/ f
进行傅里叶变换后打印模值。6 b. O: f/ y: ~2 K
& {6 F5 c9 D& U5 h
; j2 b% y9 y) N! ^& W E" l- P
可以看到傅里叶变换执行成功。* Z& S! w* q( B8 Z* f
- for (int i = 0; i < NUM_SAMPLES; i++) {
X. |2 T4 F8 ~$ |9 Q - float32_t t = (float32_t)i / SAMPLE_RATE;2 L0 u! q' A- M* \2 {7 A6 N
- 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); // 计算正弦波值
5 L. T! G0 Y h' s% Q - inputSignal[i * 2] = sin_value; // 实部7 Q' W5 O9 P: l3 j* ]
- inputSignal[i * 2 + 1] = 0; // 虚部
/ c# P3 N/ @: \" B - }
复制代码 * |6 v7 `% t# e
我们将信号制作成100HZ+200HZ+300HZ的信号。2 C8 {* M$ _6 z6 y8 K# W. w
- F, o5 A: {* P) w
: M- E+ l- t& v! U8 b* b4 F" b1 w
, T. c' K/ P- f, n: s% U( S
; D! X" h) ^7 h, J) D" F+ ^
3 L5 L) C- ^! k* A0 m& c( e6 i转载自:电路小白8 g+ q. L% s8 q5 p% x
如有侵权请联系删除
# u) E" M5 Q' e3 Q# s4 c+ ^: P. v+ S4 F; N) ~
|
这个FFT不错,学习参考一下