基于STM32 HAL库的FFT算法:频率与幅度测量详解
stm32-fft教程
目录
stm32-fft教程
前言:
FFT介绍:
正文:
基础知识:
DSP库调用:
代码示例:
结语:
前言:
学习单片机,就避不开stm32,学习stm32,就避不开ADC采样,学习ADC采样,就避不开fft-快速傅里叶变换。本文将给大家分享fft的使用,本教程使用stm32f407VET6,基于cubemx与keil5软件开发,将逐步带领大家实现fft测量采样信号的频率和幅度。(由于fft测量相位差需要用到ADC同步采样,所以将在下篇博客与大家分享哈)
FFT介绍:
stm32—fft 究其根本其实就是三个函数的使用,下面也就是教大家如何去用。
arm_cfft_radix2_instance_f32 scfft;
arm_cfft_f32(&arm_cfft_sR_f32_len1024,fft_inputbuf,0,1);
arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH);
快速傅里叶变换(FFT)是一种有效计算离散傅里叶变换(DFT)的算法,它将信号从时域转换到频域。通过频域分析,可以更容易地理解和处理信号的频率成分。在嵌入式系统中,特别是在像STM32F407这样的微控制器上,FFT常用于音频处理、振动分析和通信等领域。
STM32F407是一款基于ARM Cortex-M4内核的微控制器,具有DSP(数字信号处理)指令集,非常适合进行快速傅里叶变换等复杂的信号处理操作。使用CubeMX配置STM32硬件外设,通过Keil5搭配HAL库开发,使得我们可以在嵌入式系统中轻松实现FFT功能。
正文:
基础知识:
本文将直接进入fft的使用,关于ADC的配置与介绍,大家可以看看这两篇博客。
——《stm32-HAL 电赛信号教程》
——《STM32——三重ADC交替采样—极限采样率7.2M》
DSP库调用:
(1)先添加DSP库
(2)还得要添加下面三个宏
ARM_MATH_CM4,__CC_ARM,ARM_MATH_M
(3)再最后添加上这些头文件 ,OK,调用步骤完成。
#include "arm_math.h"
#include "arm_const_structs.h"
代码示例:
(1)先进行采样数据个数,采样率,fft数据处理个数的定义。(注意:FFT_LENGTH需要与ADC1_DMA_Size 大小一致,采样率定义需要与cubemx中配置相同,具体请看前面提到的两篇博客)
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define FFT_LENGTH 1024
#define ADC1_DMA_Size 1024
int SAM_FRE = 2000000; //采样率
(2)再定义数组, fft_inputbuf [FFT_LENGTH*2] 是fft 输入数组;fft_outputbuf[FFT_LENGTH] 是fft 输出数组;ADC1_ConvertedValue[ ADC1_DMA_Size ]是ADC采集到的数据,这里先以1024个点为例。
/* USER CODE BEGIN PV */
float fft_inputbuf [FFT_LENGTH*2];
float fft_outputbuf[FFT_LENGTH];
uint32_t ADC1_ConvertedValue[ ADC1_DMA_Size ];
(3)调用函数 arm_cfft_radix2_instance_f32 scfft ,定义结构体scfft。
调用函数arm_cfft_f32(&arm_cfft_sR_f32_len1024,fft_inputbuf,0,1);
arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH) 进行计算。
我这里直接生成一个频率200k与400k,电压为0.825V的混合信号进行处理,添加了1.65V的抬升信号。
arm_cfft_radix2_instance_f32 scfft;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
while(flag==1)
{
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC1_ConvertedValue,ADC1_DMA_Size); //开启ADC_dma采集数据
HAL_Delay(1000);
for(i=0;i<FFT_LENGTH;i++)
{
//fft_inputbuf[2*i] = (float)ADC1_ConvertedValue[ i ]*3.3f/4095.0f; //数组实部放采集到的数据
fft_inputbuf[2*i] = ((sin(2*PI*200000*i/SAM_FRE)+1)*1024+ (sin(2*PI*400000*i/SAM_FRE)+1)*1024)*3.3f/4095.0f;
fft_inputbuf[2*i+1]=0; //虚部都放0
}
// for(i=0;i<FFT_LENGTH;i++)
// {
// printf("%f\r\n",fft_inputbuf[2*i]*3.3f/4095.0f);
// }
arm_cfft_f32(&arm_cfft_sR_f32_len1024,fft_inputbuf,0,1); //其中1024表示需要处理的数据个数,这里可以是1024,2048,4096
arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH); //计算幅度
for(i=0;i<FFT_LENGTH;i++)
{
printf("%d,%f\r\n",i,fft_outputbuf[ i ]); //未加窗
}
FFT结果如下图:
如果大家有了解FFT的原理,会知道,fft_outputbuf中的值并不是我们最终结果,这1024个点中,索引为0的点代表直流偏置信号,剩下的会关于第512个点对称,由于对称,我们只需要去分析前面512个点。那么我们怎么去计算信号的频率和幅度呢?
(1)直流偏置信号幅度:
由于单片机不能采集负电压,所以我们往往会先加上一个直流偏置。U表示索引为0的点的值,FFT_length表示FFT数据处理的个数。
Ud=1692/1024=1.6523
(2)频率:
N表示数据索引即1——512,FS表示采样率,FFT_length表示FFT数据处理的个数。(大家可以看到,我们计算出的频率是一个离散的范围)。
F1=102*2000000/1024=199218 HZ,F2=205*2000000/1024=400390 HZ
(3)幅度:
Un表示索引为n的点的值,FFT_length表示FFT数据处理的个数。
结语:
FFT处理往往会有频谱泄露,大家可以进行加窗处理(如下)。对了,提一嘴,反快速傅里叶变化——IFFT和这差不多,只不过改了个参数,也放在下面。好啦,希望这篇博客对大家有所帮助,有疑问或建议欢迎留言!如果需要完整代码,可以私我或留言。
for(i=0;i<FFT_LENGTH;i++)
{
fft_inputbuf_win [i*2] *= (0.5-0.5*cos((2*PI*i)/FFT_LENGTH-1));
}
arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,1,1);//反fft---ifft
arm_cfft_f32(&arm_cfft_sR_f32_len1024,fft_inputbuf,1,1);
for(i=0;i<FFT_LENGTH;i++)
{
printf("%f\r\n",fft_inputbuf[ 2*i ]);
}
作者:哦呜呜呜呜