STM32的ADC配合DMA采样以及STM32利用DSP库实现快速傅里叶变换,而当二者真正的结合到一起实现一个信号采样以及频域分析才可以发挥出很强大的功能。
' k" c3 J2 Q z, D8 U' _- k3 P3 p5 q7 R/ }' u m( h( N5 ]7 o
本期我们就来演示,利用DMA+ADC+FFT实现信号采集与频域分析。
$ R/ y& G3 B& ~9 B& P" A8 a$ k' E' k& u, I* k4 V
CUBEMX配置6 e7 `+ E5 p7 s( V. b
G7 X$ t" Y5 P+ ?) r7 f6 W
( r% [- a# y8 L
+ W5 v6 j# n$ z: f
ADC配置这边除了之前的配置之外有一点区别,就是我们把循环采样给关掉,因为我们打算利用定时器来触发采样,这样子的话可以精准的控制采样率。4 S5 O* P6 [6 p+ M0 A* T/ s8 Y& d
( @* j- _+ y# R% B3 `* I, X9 q
6 @7 C* r2 k+ X2 S+ ]' E: O/ H& U+ R; Z* V8 W3 z
这里我们选用TIM1的输入捕获来触发,当TIM1输出PWM波的时候,会选取上升沿来触发ADC采样,将采样的数据存储到DMA中。, b" Y, N0 _- S+ z
- |3 K; u- \- `+ ?
3 r3 x7 J2 ]2 a, q. V2 T4 M, X$ {! i& V8 I2 N% _7 Y# c
定时器中我们设置PWM的频率为500HZ,这代表着我们的采样率为500HZ,之后生成我们的代码。(这里非常奇怪,我明明觉得这么算好像是1000HZ但实际只有500HZ,我哪里算错了?)
- o( U) {9 }( {0 ]& |1 R- Q* n8 G; H1 t. ?3 s( r1 w
代码编写+ M# x3 t; X2 M
- #define FFT_LENGTH 1024//采样长度
4 V; } R$ R) w( R+ ]: O9 X7 I2 ? - arm_cfft_radix4_instance_f32 scfft;//定义scfft结构体
( t& y e; S: X. t3 l - float FFT_InputBuf[FFT_LENGTH*2]; //FFT输入数组% d4 O9 d) M2 a7 p6 L
- float FFT_OutputBuf[FFT_LENGTH]; //FFT输出数组2 X- M* [ w5 M% G8 I" j
- uint16_t AD_Value[1024] = {0};//存放ADC的值
复制代码
0 _* P+ x7 e) Z' G' j" J上述代码需要定义在全局,否则可能会因为栈溢出(因为临时变量的定义是存放在栈中的)导致代码崩溃。6 v: f/ b# _5 f( |7 [7 z
- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); //用来触发adc采样 9 W* M4 y( Z. a( [" O j4 h. R
- HAL_ADC_Start_DMA(&hadc1, (uint32_t *)AD_Value, FFT_LENGTH);//开启ADC8 x5 j" x! _- q* e* z/ c. `
- arm_cfft_radix4_init_f32(&scfft, FFT_LENGTH,0,1);
3 W0 i2 v6 j e8 g1 H6 W/ d) f - HAL_Delay(1000);//稍微等待一会ADC一轮转换结束, K2 ^, {! @9 ]9 ]! H( W$ Z# V3 R
- for(int i=0; i < FFT_LENGTH; i++)
2 n. B6 L: G! s+ o8 ^1 n& l7 k - {
% ]4 i; o, i; M; W5 L0 M i - FFT_InputBuf[2*i]=AD_Value[i]*3.3/4096; //实部, G' e+ t% n$ {3 ?, y! S: {
- FFT_InputBuf[2*i+1]=0; //虚部/ h$ D; U' N, P+ S
- }5 ]2 r8 J' H1 f1 Y; X" }
- arm_cfft_radix4_f32(&scfft,FFT_InputBuf); 6 ~3 b( K& g1 ]
- //arm_cmplx_mag_f32(FFT_InputBuf,FFT_OutputBuf,FFT_LENGTH); //取模得幅值$ c+ O% A; i: Q' }
- for(int i = 0;i<FFT_LENGTH;i++)3 c# B* |3 H9 T, ?9 h
- {
2 t v) L5 K7 [( t/ B* `7 B - float32_t real = FFT_InputBuf[2 * i];
& P* N/ z- H' ^% J% O - float32_t imag = FFT_InputBuf[2 * i + 1];
! B- c5 [. h" T" o" G9 t6 A - float32_t magnitude = sqrtf(real * real + imag * imag);4 U1 f; y8 w! j6 T6 Z5 f ~# B
-
* A9 }8 q) [0 o* P4 o! x& x - // 打印每个频率分量的模值) _' }. ?6 M; P& ^! j: H, N( {
- printf("Fre: %f \r\n", magnitude);
6 P4 L& ]% ^# r6 M, @& n - }
复制代码
3 k# z% p0 }0 q( }: I生成代码,我们由定时器触发采样,原始信号来自于一个250HZ的方波。- l) [, g& y8 j2 T% H5 R
+ Y4 |$ ?/ T s
4 g& H5 z% P5 V' T
% P$ U0 Z# J( W. K) n6 v
之后我们执行我们的FFT代码,首先我们先看一下采集的波形。
2 _7 Z1 d- X3 t! }- B3 i* t, t- J. l6 q" i8 T, p
; r# f8 b* H: U; J' G4 B, u% g2 T5 t% C5 K
这里可以看到外面采集的方波。7 e- |1 X6 x$ Y1 E4 } ?
. ^" M. m0 a: _* D: n9 @注意事项
2 K- O5 S& o. S: n; D' F(这里我的问题,根据奈奎斯特采样定理,采样频率应该至少是信号频率的两倍,否则会出现采样失真,因此为了能够顺利的采样,我将PWM的频率修改为100HZ)
8 C( u- n: k& a) @) t* j6 N! N0 l' Z0 B
* C/ P1 g. D: o( ]
0 y. ~& m" Z/ ^) y7 r9 q0 f2 {2 r7 H这里也非常奇怪,我觉得PWM的频率应该是100HZ的,但是不知道为什么测出来是50HZ(难不成我又算错了?)
3 ~/ I m3 J( K
0 F; _9 s. S& N4 w后来发现是我的分时系数算错啦~0 c2 U" E' v; e( g4 O8 B" r
$ L# B1 a) l6 K: l$ [变换结果
$ ?0 b% `- d/ R然后来看看傅里叶变换之后的结果。
% m7 Y( E7 y; V) ^0 d( W6 V8 c) |! E9 e9 Z( p9 S
# ]! ^/ a2 o4 y @( h& G1 u, o4 y
2 G. S5 A+ i3 i$ {9 y
(这个软件是我自己写的串口示波器)! @6 P2 v, e2 t! j! q4 {2 Q. {6 J
8 C) X$ f, X% m' h+ K
每个FFT点对应的频率(f)与采样频率(Fs)和FFT的点数(N)之间的关系可以用以下的数学公式表示:
9 T2 j0 j5 B% e+ ]' S Jf = k × (Fs / N)5 y* @+ u9 H6 Y8 `2 [' R/ z
' a& u# o" z/ _7 Q% t/ r8 r$ w其中,k 是FFT结果的索引(从0到N-1),Fs是采样频率,N是FFT的点数(与采样点数相同)。. g' I$ f2 n$ N" g" `( r& F
$ c3 d8 R1 U" i& [: p: k% f这里外设定的采样长度是1024,采样频率是500HZ,因此每个点之间的频率差是500/1024(这里按理来说1000更加方便计算但是不知道为什么1000就会卡死,1024就不会,这个也等我研究一下)。
) m. O- U0 u4 q8 P: k/ ]# V. H, a- t% r
然后用Vofa+来算一下频率(自己的串口示波器不能显示下标,真是失败,我这两天就加上去这个功能)
1 ~% g( N5 n% l& F) `" u0 E# [- N* u" M8 |6 ~6 c+ i
0 s8 ~8 `+ R. s5 s7 h' R3 ^) _- A0 ^
首先是开始的这个大信号,毫无疑问就是是直流信号(低频)所带来的幅度。* y4 f" A2 L0 _; }. C. Z! A
7 Y. R3 F$ _" V z+ Z' l( s+ Z这里我给数据加一个直流滤波,就可以把这个很大的直流量给滤掉了。, S# P+ `8 c. A: t9 m i' q
- for(int i = 0;i<FFT_LENGTH;i++) H F: U. S! ?
- {& \! A' [" F" @# j. M# j( S
- sum+=AD_Value[i]; d, k; I+ a- z
- }+ F B: J! R% m @
- sum/=1024;
/ ]* I9 H0 c* n" S& o) s* F0 S) r - for(int i=0; i < FFT_LENGTH; i++)
2 @; J8 a7 Q4 M - {
( A+ h3 s6 V E# V, z: m - FFT_InputBuf[2*i]=(AD_Value[i]-sum)*3.3/4096; //实部
( I% [2 \2 M/ S8 t* `+ D3 f - FFT_InputBuf[2*i+1]=0; //虚部/ v l3 p! F2 F+ D/ T7 i/ D
- }
复制代码 ' M& u/ P+ O! V3 f: q( U8 b
从图中也可以看到,FFT的结果不是一个单边频谱,而是一个双边频谱。7 Q2 B* N1 r, L8 \$ i$ N$ B
$ H$ @$ ?4 y: }& ZFFT(快速傅里叶变换)的结果默认是双边频谱,这主要是因为FFT是一种处理复数信号的方法,而复数信号在频域中既有正频率分量也有负频率分量。即使你输入的是实数信号,FFT算法也会将其视为复数信号来处理,并返回包括正频率和负频率在内的双边频谱。! \6 C& z. l+ \: w; h
. {+ z2 T1 P' E/ }
" B+ `5 n" }, L" D) \: ]* O
: p& c/ O; k4 q7 W: O两个峰值之间相差100个点,也就是存在50HZ的倍数波。
8 b5 l0 ^* n. f) f1 m
+ j# M1 o2 _. n# q0 c
, t/ o( L) X8 l/ K, B7 u3 k8 |& S6 K( d6 u4 b' \2 G
这是因为方波是由它的基波频率以及高次谐波所组成。
7 n4 R1 \6 Z' j. t7 d0 d4 M
. K; G7 Q4 k5 b5 h5 z
1 k# D. r; m* Y/ w1 I5 |' K- u9 e& O4 H: |8 O
1 d" m6 ?1 {; N6 O8 f, y2 m9 |
S* |2 z( n0 |5 ^
% Q2 U; z; \9 x% _转载自:电路小白 |0 n$ {' m) [. d- ^' Z2 }2 R
如有侵权请联系删除
/ c0 r/ Z: K' ^; |; |
+ V& u6 x; }, L
( }9 J# r6 K* B3 L4 F2 d0 f/ O9 L8 N, W7 B
|
学习学习,正好需要FFT频谱分析