快速傅里叶变换(FFT)是一种数字信号处理中常用的技术,用于将快速序列转换为频域表示。在嵌入式系统中,如基于STM32的微控制器,实现FFT可以帮助解决信号处理的需求,例如声音处理、图像处理等。本文将介绍基于STM32的离散傅里叶变换的原理、实现方法和应用。
$ i* c) v3 R2 x
' }$ T: O2 N, m( u- A( F! [2 ~
- p+ @9 Z0 I) [+ a, Y; L$ c
5 h! S) h8 O$ l+ i# V- {
FFT是一种将时域序列转换为频域表示的技术,它将一个序列的N个采样点映射到频域中N个频率分量。其数学表达式如下:) F% o* n! v: W% `
* O) O" A5 s3 y# F! J$ s* i- v
3 {9 A8 O; Q+ `+ D
6 I9 k. ~+ F. J8 i+ Z2 G其中,x(n) 是输入序列,X(k) 是输出的频域表示。
7 ~0 ?# F9 |, C/ e2 |* {" @/ K9 c; v/ g4 _4 D9 k4 Y8 B
准备工作:; ]' M) S1 w4 H. M" e
/ Q) w+ u0 k( D. l9 H5 m
3 D1 r( Y/ k" S# R0 Q
7 l8 d U G, e
' y6 a+ J0 h J+ N" G
2 a. ^& a2 Y% v w) B* a$ q
`2 N* ~, o! x. s% N( `- b0 @
Keil中的DSP库(Digital Signal Processing Library,数字信号处理库)是针对ARM Cortex-M处理器系列的一组软件库,用于提供各种数字信号处理功能的支持。这些库提供了一系列优化过的算法,可以帮助开发人员在嵌入式系统中高效地实现音频处理、图像处理、通信系统等各种信号处理应用。6 n1 g% `+ y6 [4 N* b9 A4 y# J7 Q2 w+ X
+ x$ x. | o q4 T( e* { v- |0 ?6 G. p因此我们需要在Keil中安装我们的DSP库。
, o- L8 G! Z5 O8 Y i) {- #include "arm_math.h" // 包含DSP库
复制代码 8 v! p, G- S7 L! |' v: g+ ~2 g3 n
首先包含我们的DSP库。# I2 X7 m% J9 p: p3 u9 ~1 M1 Q
7 T8 \5 t, k6 t) P8 `# s
. U! \* P, x. i
- #define FFT_LENGTH 1008 O; H$ n, @# u) t; n0 d
- // 输入序列
" d3 j& S, l3 s) ?2 l - float32_t inputSignal[FFT_LENGTH*2];
9 B" h5 R, i, d# X ? - R0 B- m% n9 v- \
- // 输出序列,存储变换后的结果/ c' ^, ^! ^% Z/ t8 ~
- float32_t outputSignal[FFT_LENGTH];
复制代码 ) }/ t G- j6 `) Z5 _
定义FFT的的输入和输出数组还有数组长度
) F0 r4 |% z& c$ t' {% U3 E- arm_status status;
' G$ [2 G0 G2 L' Q - arm_cfft_radix4_instance_f32 fft_inst;
) p$ s) `3 m1 f - status = arm_cfft_radix4_init_f32(&fft_inst, FFT_LENGTH,0,1);
复制代码- void arm_cfft_radix4_init_f32(! A- R; V u$ T" Q
- arm_cfft_radix4_instance_f32 * S,
/ |( U8 d6 Z5 q5 f. U - uint16_t fftLen,
5 _4 T- [; }8 g3 T - uint8_t ifftFlag,
% m- n5 R; @2 z - uint8_t bitReverseFlag7 p) l: I/ H. W4 E: r( ~( m# s) h
- );
复制代码
7 D# l4 Y3 ]8 y# P定义一个状态变量用来显示FFT的初始化是否成功。
, d$ g0 S# v _! v( G定义一个FFT的配置变量。( P9 ?0 {9 `& }, b0 D
初始化FFT。. r0 N+ `1 D1 i' W9 O0 E8 g
S:指向 arm_cfft_radix4_instance_f32 结构体的指针,该结构体定义了 FFT 实例的状态信息。
' c; y8 a) e: t+ N# z
. f! s+ D8 t9 s) H& jfftLen:FFT 的长度。6 b7 n5 k/ W# u* `
3 J, _0 s! e2 H# |0 Q, K
ifftFlag:指定是否进行逆变换。如果为 1,则表示初始化的是逆变换的 FFT;如果为 0,则表示初始化的是正变换的 FFT。
2 F2 S3 `8 O0 F f2 p5 R M, f) c+ M6 w
bitReverseFlag:指定是否进行比特翻转。如果为 1,则表示进行比特翻转;如果为 0,则表示不进行比特翻转。
* j5 `- Y0 Y4 u; O, p2 H+ y' R3 I- @* V! Q3 P
在FFT算法中,比特(bit)反转是一种关键的步骤,用于将输入数据重新排列为正确的顺序,以便在后续的计算中进行有效处理。
/ ~( _# Z3 n" V6 Q+ k- N6 W; Z$ z4 v0 Z G& c- Y
当进行快速傅立叶变换时,算法要求输入数据的顺序是按照特定的方式排列的。特别是在使用基于分治法的算法(如Cooley-Tukey算法)时,输入数据的顺序必须满足按照一定规律的排列。
9 x! d# x" m3 \' K; f4 ]) f* ~* {- Z5 G2 D
在实际的FFT实现中,最常见的方式是通过比特反转来重新排列输入数据。比特反转就是将输入数据的比特位(二进制位)的顺序进行颠倒。这是因为在FFT算法中,数据会被分组,并按照一定规则进行反转,以便在每个阶段的运算中,数据可以正确地与其它组合进行配对。5 W6 y1 e6 a8 `+ E9 g! Z
3 t6 K% h5 }- ?" G2 `
举个简单的例子,假设有一个长度为8的数据序列,按照0到7的顺序排列:
3 H/ n6 c1 o- W$ T4 S0 d8 {5 a5 W; O n8 b+ N9 N
0 1 2 3 4 5 6 7, f- l; x; }( b5 C, C7 q' X* L
; ^ w1 [3 g4 R! R1 x$ q B. B在进行FFT时,需要按照一定规则重新排列这些数据。比特反转操作将会对这个数据序列进行如下的重新排列:& ]1 \8 L8 U- P. ^
/ X9 M4 \6 V8 [, K* x1 z0 4 2 6 1 5 3 7) X4 D; c i1 E2 Q2 e8 ~' j
- o7 ^ L0 m ~& _在FFT算法的每个阶段中,这种重新排列都会使得数据正确地与其它组合进行配对,从而实现快速傅立叶变换的计算。* o N) R0 R+ \1 ^* v+ {
4 M4 q: x& M' f% z进行FFT并转换为模值
/ E/ _* t; y: ]& g" ?3 }- arm_cfft_radix4_f32(&fft_inst,inputSignal); //FFT计算
% c; Z) Z, W+ z4 ?% \/ J; z - arm_cmplx_mag_f32(inputSignal,outputSignal,FFT_LENGTH); //取模得幅值
复制代码 2 [+ ]$ {' L( D$ ~6 |5 n8 B
对输入数组进行FFT变换,并将FFT的结果转化为模值。/ Y6 T- m% W6 I7 V8 }; `# O
$ s' Q" l$ ?4 \! `, O测试 f: z! g$ ~! W5 ~- n
我们进行一个简单的测试
& p! S9 N4 d5 L* Y- #define FFT_SIZE 1024
+ r9 V% N3 K8 h' g+ S$ w6 B7 l - #define SAMPLE_RATE 1000: q; y+ A6 |6 ?/ r
- #define NUM_SAMPLES 10008 k \0 Z" h& r# G
- #define FREQ_OF_INTEREST 1007 R: x2 [3 J5 V% e. {* @
- for (int i = 0; i < NUM_SAMPLES; i++) {4 {+ _& H8 d( H. `
- float32_t t = (float32_t)i / SAMPLE_RATE;
1 e! W. k3 [4 F" x }0 Y: ] - float32_t sin_value = sinf(2 * PI * FREQ_OF_INTEREST * t); // 计算正弦波值
: C$ H0 X E; ?. Q6 d - inputSignal[i * 2] = sin_value; // 实部, d( {* r3 A9 n" l1 s. x
- inputSignal[i * 2 + 1] = 0; // 虚部; ?/ l; F9 S4 A, ~# J5 J
- }
复制代码
' C- k9 h3 C0 k* Q' I3 R5 _一千个点的采样值,频率假设为100HZ作为输入信号。
, i8 V; x6 _, L: K$ g) }- for (int i = 0; i < FFT_SIZE; i++) {
" r% z6 c4 a o7 N - // 计算复数的模值4 T6 f- K# c ]: N. {9 e
- float32_t real = inputSignal[2 * i];
4 u7 x& s" N1 g8 l. V* S4 S - float32_t imag = inputSignal[2 * i + 1];5 J* X" d$ U8 Y5 e& k
- float32_t magnitude = sqrtf(real * real + imag * imag);
, o. M- I; y& |& `4 t. F" P& R" M -
% U7 B% J5 P1 t - // 打印每个频率分量的模值
6 E2 y' }& B" N: L$ q - printf("Magnitude: %f\n", magnitude);
9 u& c4 O0 p. f1 u; C9 s7 q! Y - }
复制代码
* h7 L( D: \, T8 }% N进行傅里叶变换后打印模值。. ^" b; }- @. @) W
5 R4 {( C3 Z: i+ t
4 I) f+ H/ p7 S& D5 J" s8 [
& E2 ^, y5 n1 l" x% P+ b
可以看到傅里叶变换执行成功。
5 h8 [; h/ f9 S- for (int i = 0; i < NUM_SAMPLES; i++) {; V2 } w- P, A. x \
- float32_t t = (float32_t)i / SAMPLE_RATE;+ y V8 x& G0 f4 ^5 H
- 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); // 计算正弦波值
( F3 x" _. B* E0 i6 t - inputSignal[i * 2] = sin_value; // 实部
# t0 w% V2 N- j T4 \8 M - inputSignal[i * 2 + 1] = 0; // 虚部
, t3 r2 `" S" [7 N' c! k! j0 w - }
复制代码
0 a5 X9 x8 X5 P5 N8 ]6 j我们将信号制作成100HZ+200HZ+300HZ的信号。
- l, z1 s2 j9 s% F5 m$ f: }4 F/ u8 i
7 [7 n: i2 b- I1 P' n
! x+ v% ^. {% }( ]( w. P; k |8 _1 ~( d* s% n, h
Y- E1 S, u& |转载自:电路小白
0 E T# n. Q% V2 s1 ~5 m如有侵权请联系删除: @2 B9 V( Y3 z
1 Q/ W5 ^) f$ W/ `
|
这个FFT不错,学习参考一下