赞
踩
目录
本文记录了自己基于STM32F4单片机DSP库实现“AD采集+FFT频谱分析”的项目。在例程基础上实现了fftshift、频率换算、串口打印等功能,并对100Hz方波信号进行了相应测试,效果良好。
FFT(快速傅里叶变换)是DFT(离散傅里叶变换)的一种快速算法,基于采样和FFT算法可以进行模拟信号的频谱分析。本次分享不介绍FFT和DFT的基本原理,而希望直接以Matlab仿真的方式展示模拟信号频谱分析的过程。
如图,设置仿真时间为 T_max=0.1s, 采样率为 fs=1000Hz, 信号频率 fm=50Hz(周期)在仿真时间内信号共有个周期。其频谱结果如右图所示,在±50Hz频率都有尖峰,表示在50Hz频率处有较强的频率分量。
(另外,还可以看到频谱图的幅值较大,因此在实际中需要对幅度进行“归一化”,对于周期信号而言,要将fft运算结果除以采样点数N; 对于非周期信号,要将fft运算结果除以采样频率fs, 具体可见《数字信号处理》相关教材)
- %% Matlab实现频谱分析
- fs = 1000; % 采样率
- fm = 50; % 信号频率
- dt = 1/fs; % 采样时间间隔
- Tmax = 0.1; % 仿真结束时间
- N = floor(Tmax / dt); % 抽样点数
- t = 0 : dt : (N - 1) * dt; % 时间向量
- y = cos(2 * pi * fm * t); % 余弦信号
-
- figure()
- plot(t,y,'LineWidth',1.2);
- xlabel('时间/s')
- ylabel('幅度')
- title('时域波形')
-
- df = fs / N; % 频率间隔
- f_index = [-N/2 : (N/2 - 1)] * df; % 频率向量
- freq = fftshift(fft(y)); % 频谱
-
- figure();
- plot(f_index, abs(freq),'LineWidth',1.2);
- ylim([0 , 1.2 * max(abs(freq))])
- xlabel('频率/Hz')
- ylabel('幅度')
- title('频谱图')
在DSP库中,主要提供了以下几个函数实现fft运算
函数具体解释如下(转自[1])
- /*
- * 函数1 初始化FFT运算相关参数
- * fftLen 用于指定 FFT长度(16/64/256/1024/4096)本章设置为1024
- * ifftFlag 用于指定是傅里叶变换(0)还是反傅里叶变换(1) 本章设置为0
- * bitReverseFlag 用于设置是否按位取反 本章设置为 1
- * */
- arm_status arm_cfft_radix4_init_f32(
- arm_cfft_radix4_instance_f32 * S,
- uint16_t fftLen,uint8_t ifftFlag,uint8_t bitReverseFlag)
-
-
- /*
- * 函数2 执行基 4 浮点FFT运算
- * S结构体指针参数 先由 arm_cfft_radix4_init_f32 函数设置好 然后传入该函数的
- * pSrc 传入采集到的输入信号数据(实部+虚部形式)同时FFT变换后的数据也按顺序存放在pSrc里面pSrc 必须大于等于2倍fftLen长度
- * */
- void arm_cfft_radix4_f32(const arm_cfft_radix4_instance_f32 * S,float32_t * pSrc)
-
-
- /*
- * 函数3 计算复数模值 对 FFT 变换后的结果数据,执行取模操作
- * pSrc 复数输入数组(大小为 2*numSamples)指针,指向 FFT 变换后的结果
- * pDst 输出数组(大小为 numSamples)指针,存储取模后的值
- * numSamples 就是总共有多少个数据需要取模
- * */
- void arm_cmplx_mag_f32(float32_t * pSrc,float32_t * pDst,uint32_t numSamples)
简言之,基本流程是
但是,本文作者在实际测试中发现函数3得不到相应结果,于是自定义了一个计算幅值的函数cmplx_mag。
在本项目中,主要涉及以下几个外设
在本项目中,考虑使用“TIM2定时器中断 + ADC手动采集”的方式控制采样速率,并通过TIM3对一帧数据的采集时间进行记录,便于后续对于真实采样率进行计算,其主要代码如下:
- //TIM2中断程序
- if(num <= FFT_LENGTH - 1) //采集一帧数据
- {
- if(num == 0) //刚开始采集
- {
- TIM_Cmd(TIM3,DISABLE); //T3定时器失能
- T3_over_time = 0; //溢出clear
- TIM_SetCounter(TIM3, 0); //计数器clear
- TIM_Cmd(TIM3,ENABLE); //T3定时器使能
- }
- fft_inputbuf[2*num] = get_advalue(); //读取ADC采集寄存器的数值(实部)
- fft_inputbuf[2*num + 1] = 0; //虚部设置为0
- if(num == FFT_LENGTH - 1) //采集结束
- {
- TIM_Cmd(TIM3,DISABLE); //T3定时器失能
- T3_clock = TIM_GetCounter(TIM3)|(T3_over_time<<16); //采集FFT_LENGTH个点需要TIM3的计数值
- SAMPLE_RATE = FFT_LENGTH * 1000000 /(1.0*T3_clock); //计算实际采样率
- }
- }
- num ++
在T2中断程序中,对num值进行累加。当num = 0时表示当前为采样的第一个数据点,因此需要将TIM3计数状态清零,随后将AD采集数据写入fft_inputbuf数组的实部(虚部置0);当num = FFT_LENGTH – 1表示采集结束,计算采样频率。
对于采样频率计算,采样一帧数据(FFT点数)所花的时间
其中表示一帧数据采样的时间,表示这段时间内TIM3的计数值,表示TIM3的计数频率。在本项目中,设置TIM3分频系数为84,因此有
其中为系统时钟频率168MHz。
则有采样间隔
采样频率
完成数据采集之后,需要调用DSP库函数计算FFT,主要代码如下:
- arm_cfft_radix4_f32(&scfft,fft_inputbuf); // FFT计算(基4)
- cmplx_mag(fft_inputbuf,FFT_LENGTH); // 计算幅值
- my_fftshift(FFT_LENGTH); // 移位
- cal_f_index(SAMPLE_RATE,FFT_LENGTH); // 计算频率分量
-
- // describe: 计算复数幅度
- // *src : 输入复试数据,实部 + 虚部交替排列
- // numSamples: 复数数据长度 = length(scr)/2
- void cmplx_mag(float *src, unsigned int numSamples)
- {
- float real;
- float imag;
- uint16_t i;
- for ( i = 0; i < numSamples; i++)
- {
- real = src[2*i]; // 实部
- imag = src[2*i + 1]; // 虚部
- fft_outputbuf[i] = sqrtf(real*real + imag*imag); // 幅度
- }
- }
-
- // describe: FFTSHIFT(FFT移位)
- // numSamples: 采样点数(FFT点数)
- void my_fftshift(unsigned int numSamples)
- {
- uint16_t i;
- for ( i = 0; i < numSamples/2; i++) // 前半部分向后平移
- {
- fft_output_shift[i + numSamples/2] = fft_outputbuf[i];
- }
- for ( i = numSamples/2; i < numSamples; i++) // 后半部分向前平移
- {
- fft_output_shift[i - numSamples/2] = fft_outputbuf[i];
- }
- }
-
- // describe: 计算频率分量
- // sample_rate : 实际采样率/Hz
- // numSamples: 采样点数(FFT点数)
- void cal_f_index(int32_t sample_rate,int numSamples)
- {
- int16_t i;
- float df = sample_rate / (1.0*numSamples); // 频率分辨率
- for ( i = -(numSamples/2); i < numSamples/2; i++)
- {
- f_index[i + numSamples/2] = df * i; // 计算频率向量
- }
- }
首先调用arm_cfft_radix4_f32函数进行FFT运算,结果保存在fft_inputbuf复数数组中(实部加虚部交替) ,
其次调用cmplx_mag函数计算fft_inputbuf复数数组的赋值,结果保存在全局变量fft_outputbuf中 ,
然后调用my_fftshift函数对fft_outputbuf结果进行移位,结果保存在全局变量fft_output_shift中,
最后调用cal_f_index函数计算频率分辨率,结果保存在全局变量f_index中。
使用STM32F4单片机对100Hz矩形信号进行AD采集,通过LCD显示相关结果,通过串口向PC端打印数据。
从图中可以看出,采样一帧数据(FFT点数)TIM3计数值为102304,计算可得实际采样频率
此外可以通过串口将频率、频谱、采样波形(逗号分隔)数据发送至PC端,如下图所示
随后将串口接收数据复制到.txt文件,另存为.csv文件,使用Matlab读取数据并画图,结果如下:
由频谱图可得,信号频率为97.74Hz, 和实际数据误差较小(后续若要提高精度,可以自行增大FFT点数或者采样率)。
本文对于AD数据的采集,只读取了ADC数据寄存器中的值(0-4095),若要对电压数据进行FFT变换,需要进行相应转换,公式如下:
其中V为真实电压值,为ADC数据寄存器中的数值,为参考电压(一般为3.3V)。
此外, 如有特殊需求,可以通过前文内容进行频谱归一化操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。