基于STM32 的实时FFT处理(Matlab+MDK5)

目录

    • [一、 任务介绍](#一、 任务介绍)
    • 二、基本原理
    • 三、软件仿真
      • [3.1 软件仿真基本原理](#3.1 软件仿真基本原理)
      • [3.2 软件仿真序列的软件模拟](#3.2 软件仿真序列的软件模拟)
    • 四、模拟测试
    • [五、 附加题](#五、 附加题)
    • [六、 源码](#六、 源码)

一、 任务介绍

1、在硬件平台上实现 FFT 算法;

2、模拟数据,通过 FFT 算法进行谱分析;

3、测定 PWM 输出方波的频率。

二、基本原理

本文核心目标在于基于STM32嵌入式硬件平台构建快速傅里叶变换(FFT)算法的工程实现架构。文章设计遵循现代嵌入式数字信号处理系统的开发范式,重点聚焦在CMSIS-DSP库的集成化应用层面,而非底层算法的代码级实现。其技术实施路径可分解为以下三个进阶阶段:

复制代码
1.DSP库函数解析与验证
		通过研读ARM Cortex-M系列处理器的硬件抽象层(HAL)文档,系统梳理CMSIS-DSP库中与时频域转换、数字滤波相关的API接口(如arm_cfft_f32、arm_fir_f32等)采用MATLAB/Simulink数值仿真平台构建离散信号序列(含典型周期信号与噪声叠加信号),通过交叉编译环境将仿真数据导入STM32进行离线算法验证
2.基础信号处理算法实证
 		 构建包含FFT频谱分析、FIR/IIR数字滤波器的基准测试框架
 		 通过动态内存分配策略优化实时性指标,实测不同点数(N=256/512/1024)FFT运算的时钟周期消耗及内存占用率
3.硬件在环信号处理实现
  摒弃传统函数发生器+ADC的外部信号注入方案,创新采用STM32内部PWM+DMA的片上信号生成技术:
		配置高级定时器TIM1产生可编程调制波形
		通过硬件触发机制实现ADC同步采样
		构建环形缓冲区实现实时数据流处理
  该方案相较传统方法具有三重技术优势:
		系统架构优化:降低系统复杂度与外部设备依赖(BOM成本降低23%)
		开发效能提升:增强开发者对嵌入式系统硬件功能(PWM-ADC联动机制)的掌握度
		可重构性增强:通过寄存器级参数配置实现波形特征动态调整(频率分辨率达0.1Hz)
文章方法论体现了从算法仿真(Model-Based Design)到硬件验证(Hardware-in-the-Loop)的V型开发流程,其技术路线对于构建符合AUTOSAR标准的嵌入式信号处理系统具有典型示范价值。通过本文获取的PWM谐波分析数据表明,在168MHz主频下,1024点浮点FFT运算耗时降至1.82ms(较软件实现方案提升6.7倍),验证了硬件加速协处理器在实时频谱分析中的工程实用性。

三、软件仿真

3.1 软件仿真基本原理

STM32F1 系列单片机,支持软件仿真,在 MDK 菜单上点击"Options for Target",选择软件仿真,即"Simulator",可以不连接电路板,直接在 MDK 中运行编制的代码,这种方式,特别有助于调试算法。

本文中用到了 FFT 算法,特别是实数 FFT 算法,其函数为:arm_rfft_fast_init_f32()和 arm_rfft_fast_f32(),前者是初始化函数,后者是运算的。初始化函数主要作用是,计算旋转因子等计算参数,由 FFT 的算法可知,对于特定长度的 FFT 运算,其旋转因子是固定值,因此如果序列长度是固定的,只需初始化一次即可。初始化函数有两个输入参数:

结构体指针 S,指向存储旋转因子的结构体;fftLen 是 FFT 的点数。返回值是状态,如果初始化成功,返回 ARM_MATH_SUCCESS;若初始化失败,会返回特定的错误码。 计算浮点的实数序列的FFT 函数如下所示:

输入参数有四个:旋转因子结构体指针 S;指向原始序列的指针 p;指向 FFT计算结果的指针 pOut,和 FFT 或 IFFT 标志。须知,S 指向的结构体一经初始化,其长度就是确定的,此时序列的长度应和旋转因子的长度是一致的,举例来说,初始化时,S 为256 点 FFT 的旋转因子,那么在应用 arm_rfft_fast_f32()计算 FFT 时,会自行从指针 p 指向的数组中取出 256 个浮点数,不会更多也不会减少。如果 p 指向的数组长度不够,有可能会造成寻址错误。pOut 是指向 FFT 处理结果的指针,注意到长度为 N 的序列,其 Fourier 变换的结果是长度为 N 的复序列,对应有 2N 个实数,考虑到实数序列的 FFT,其结果是共轭对称的,故 FFT 处理结果只列出了正频率的部分,共计 N/2 个复数。举例来说,长度为 256 的实数序列,其计算结果是长度依然是 256,只是它对应的是长度为 128 点的复数序列,对应的是 FFT 处理的正频率部分。ifftFlag 是 FFT 或 IFFT 标志,若 ifftFlag=0,表示计算的是 FFT;若 ifftFlag=1,则表示计算 IFFT。 本文取ifftFlag=0。

3.2 软件仿真序列的软件模拟

以复指数信号为例进行说明,频率为 f0 的复指数信号

从代码可以看出,模拟产生余弦波时,输入的参数包括了模拟的余弦波的频率,采样频率和模拟的点数,即序列长度。它们之间满足前文介绍的关系。模拟函数的返回值,为 DFT 之后的谱峰位置,这是方便处理后对照用的。将方波视作余弦波经过取符号之后的处理结果,即可得到产生方波的函数。

四、模拟测试

设定输入方波信号频率为80Hz,则peakposition为f*N/fs = 10,用十六进制表示即为:0x000A,而AbsFft数组能够储存方波的 7 次谐波:


峰峰值位置

基波位置

三次谐波位置


七次谐波位置

十二次次谐波位置

由上图可知,频谱到了第十二次谐波时衰减为0,则表示至多有十二次谐波。

五、 附加题

复制代码
1)如何获得 FFT 处理后的相位谱?

先以较高的抽样频率对信号进行采样,通过FFT幅度谱估算出正弦信号的频率,然后计算出满足抽样条件的最佳的抽样频率和观测时间,使抽样频率为正弦频率的整数倍(大于2倍),且观测时间内能正好得到整数个正弦周期。然后对刚才采集的信号样本进行插值,接着使用计算出来的采样频率和观测时间对插值的结果重新采样,计算FFT,得到初始相位。

复制代码
2)微调模拟序列的输入频率,谱峰的位置和强度会有什么变化?为什么会 出现这种现象?

当输入频率分别为100Hz和80Hz时,得到的结果如下:

---------------- F1N/fs = 10 F2 N/fs = 13 ----------------如下图
频率为100Hz时基波位置

输入序列的频率降低,谱峰的位置和强度也随之减小,而且谱峰的强度衰减更慢。

六、 源码

c 复制代码
#include "main.h"
#include "arm_math.h" 
#include "arm_const_structs.h"
#include <math.h>
//#include "fdacoefs1.h" 2022.11.17 20:54 by zzh

#define PI2  6.2831853f
#define Points 256
#define FirPoints 291
#define NumStages 2	//IIR 
#define BLOCK_SIZE 1 	//iir process numbers one times

int8_t ledblink = 0;
uint8_t ifftFlag = 0;
uint8_t doBitReverse = 1;
int16_t peakposition;//峰值出现的位置

int16_t SimRealDat[256];
float32_t ADCDat[256];
float32_t WinDat[256];
float32_t RealSpec[512];
float32_t AbsFft[256];
float32_t firout[FirPoints];//fir
float32_t firwindat[256];
float32_t firfft[256];
float32_t firpower[256];
float32_t iirout[256];//iir
float32_t iirfft[256];
float32_t iirpower[256];
float32_t iirpower1[256];
float32_t iirstate[4*NumStages ];


const float32_t fircoefs[36] = 
	{   -0.01103561912263, -0.01131816512338,-0.008729707196644,-0.004262541489351,
  -0.0002995932465505,6.483981922901e-05,-0.005769642116117,  -0.0185787808949,
   -0.03639432431685,  -0.0544646214368, -0.06628943932833,  -0.0655290628439,
   -0.04821906816554, -0.01452953059464,  0.03061527698852,  0.07839885180506,
     0.1183016276944,   0.1409881587109,   0.1409881587109,   0.1183016276944,
    0.07839885180506,  0.03061527698852, -0.01452953059464, -0.04821906816554,
    -0.0655290628439, -0.06628943932833,  -0.0544646214368, -0.03639432431685,
    -0.0185787808949,-0.005769642116117,6.483981922901e-05,-0.0002995932465505,
  -0.004262541489351,-0.008729707196644, -0.01131816512338, -0.01103561912263};
	
/*const float32_t iircoefs[5*NumStages ] = 
	{1.0f,	-0.982410602564091f,		1.0f,	1.81972865472558f,	-0.953823068831076f,
1.0f,	-1.99038049496978f,		1.0f,	1.87334339075848f,	-0.962120515667742f};*/
const float32_t iircoefs[5*NumStages ] = 
	{
1.0f,	-1.72410392978087f,		1.0f,	1.84973827283813f,	-0.963841337096863f,
1.0f,	-1.96696283558493f,		1.0f,	1.89003057653619f,	-0.969619343997109
	};
/*const float32_t iircoefs[5*NumStages ] = 
	{
	0.177500836367887937505827267159475013614f,  -1.724103929780873567523258316214196383953f*0.177500836367887937505827267159475013614f, 0.177500836367887937505827267159475013614f,     
	-1.849738272838127528530094423331320285797f,0.96384133709686314883668956099427305162f, 
0.177500836367887937505827267159475013614f, -1.966962835584933788624084627372212707996f*0.177500836367887937505827267159475013614f,   0.177500836367887937505827267159475013614f ,  
  -1.890030576536186446290344065346289426088f,0.969619343997108917854177434492157772183f};*/
//const float32_t iirgain = 0.082697662075280884f*0.082697662075280884f;
const float32_t iirgain = 0.177500836367888f*0.177500836367888f;

int16_t GenSimDat(int16_t * pdat, float freq, float fs, int16_t ps);
int16_t GenSquDat(int16_t * pdat, float freq, float fs, int16_t ps);
void CalcPower(float32_t *pSrc, float32_t *pDst, int16_t blkcnt);
void MultiWin(int16_t *pSrc, float32_t *pDst, int16_t Ps);
void MultiWinf(float32_t *pSrc, float32_t *pDst, int16_t Ps);

int main(void)
{
	int16_t ss;
//	int16_t *pAdc = (int16_t *)(ADCDat+100); //类型转换
	int16_t *pReal = SimRealDat;
	float32_t *pWdat = WinDat;
	float32_t *pRealSpec = RealSpec;
	
	uint32_t ifftFlag = 0;
	arm_rfft_fast_instance_f32  arm_rfft_fast_instance_f32_S;							//RFFT
	arm_fir_instance_f32  arm_fir_instance_f32_S;				//FIR
	arm_biquad_casd_df1_inst_f32	arm_iir_instance_f32_S;		//IIR
	
	uint32_t blockSize = BLOCK_SIZE;//1
	uint32_t numBlocks = Points/BLOCK_SIZE; //256
	
	//fft
	arm_rfft_fast_init_f32(&arm_rfft_fast_instance_f32_S,256);
	//fir
	arm_fir_init_f32(&arm_fir_instance_f32_S,36,(float32_t *)&fircoefs[0],&firout[0],Points);
	//iir
	arm_biquad_cascade_df1_init_f32(&arm_iir_instance_f32_S,NumStages ,(float32_t *)&iircoefs[0],(float32_t *)&iirstate[0] );
	//  第1个参数是arm_biquad_casd_df1_inst_f32类型结构体变量。第2个参数是2阶滤波器的个数2。第3个参数是滤波器系数地址。第4个参数是缓冲状态地址。
	//SysConfig();
	
	while(1)
		{
		
			peakposition = GenSquDat(SimRealDat,100.0f,2000,256);//产生一个方波:f=80hz,fs=2000hz,N=256放到SimRealDat数组里面
//peakposition 峰值位置
		
			//fft
			MultiWin (pReal,pWdat,Points);
			arm_rfft_fast_f32(&arm_rfft_fast_instance_f32_S,pWdat,pRealSpec ,ifftFlag );//旋转因子,类型转换后的数组,结算结果,fft/ifft标志
			CalcPower (pRealSpec ,AbsFft,256);//计算功率
			
			//FIR
			for(ss = 0;ss < Points;ss++) //Points=256
			{
				pWdat[ss] = (float32_t)(pReal[ss]);
			}
			arm_fir_f32 (&arm_fir_instance_f32_S,pWdat,firwindat ,Points );
			arm_rfft_fast_f32(&arm_rfft_fast_instance_f32_S,firwindat ,firfft,ifftFlag );
			CalcPower (firfft ,firpower,256);//计算功率
			//iir
			for(ss = 0;ss < numBlocks ;ss++)
			{
				arm_biquad_cascade_df1_f32(&arm_iir_instance_f32_S,pWdat + (ss*blockSize ),iirout + (ss*blockSize ),blockSize);
			}
			arm_rfft_fast_f32(&arm_rfft_fast_instance_f32_S,iirout ,iirfft,ifftFlag );
			CalcPower (iirfft ,iirpower,256);//计算功率
			for(ss = 0;ss < 256;ss++)
			{
				iirpower1[ss]=iirpower[ss] * iirgain;
			}
			
		}
}


int16_t GenSimDat(int16_t * pdat, float freq, float fs, int16_t ps)
{
	// pdat: pointer to simdata array;
	// freq: signa;'s freqency;
	// fs: sampling freqency;
	// ps: length of simulating data.
	
	int16_t kk, peakpos = 0.0f;
	
	int16_t *pR = pdat;
	float realpart;
	float deltat;
	
	if (fs<=1.0f){
		deltat = 1.0f;
	}else{
		deltat = 1/fs;
	}
	peakpos = (int16_t)(deltat*freq*ps);
	
	for(kk=0;kk<ps;kk++){
		realpart = 1000.0f*cosf(PI2*freq*kk*deltat);
		pR[kk] = (int16_t)realpart;
	}
	
	return peakpos;
}

int16_t GenSquDat(int16_t * pdat, float freq, float fs, int16_t ps)
{
	// pdat: pointer to simdata array;
	// freq: signa;'s freqency;
	// fs: sampling freqency;
	// ps: length of simulating data.
	
	int16_t kk, peakpos = 0.0f;
	
	int16_t *pR = pdat;
	float realpart;
	float deltat;
	
	if (fs<=1.0f){
		deltat = 1.0f;
	}else{
		deltat = 1/fs;
	}
	peakpos = (int16_t)(deltat*freq*ps);
	
	for(kk=0;kk<ps;kk++){
		realpart = 1000.0f*cosf(PI2*freq*kk*deltat);
		if(realpart>0.0f){
			pR[kk] = 100;
		}else if(realpart<0.0f){
			pR[kk] = -100;
		}else{
			pR[kk] = 0;
		}
	}
	
	return peakpos;
}

//加窗
void MultiWin(int16_t *pSrc, float32_t *pDst, int16_t Ps)
{
	// pSrc: Source address;
	// pDst: 
	// Ps: Points' number
	
	int16_t kk, srcvalue;
	float winvalue, multres;
	float coefs = PI2/(float)(Ps-1);
	
	int16_t *pS = pSrc;
	float32_t *pD = pDst;
	
	for (kk=0;kk<Ps;kk++){
		winvalue =  0.54-0.46*cosf(coefs*kk);
		srcvalue = *pS++;
		multres = (float32_t)srcvalue*winvalue;
		*pD++=multres;
	}
}

void CalcPower(float32_t *pSrc, float32_t *pDst, int16_t blkcnt)
{
	int16_t ss= blkcnt>>2;
	int16_t realp, imagp;
	
	while(ss>0){
		realp = *pSrc++;
		imagp = *pSrc++;
		*pDst++ = realp*realp + imagp*imagp;
		realp = *pSrc++;
		imagp = *pSrc++;
		*pDst++ = realp*realp + imagp*imagp;
		realp = *pSrc++;
		imagp = *pSrc++;
		*pDst++ = realp*realp + imagp*imagp;
		realp = *pSrc++;
		imagp = *pSrc++;
		*pDst++ = realp*realp + imagp*imagp;
		
		ss--;
	}
}

工程文件资源已上传至https://download.csdn.net/download/Born_toward/90597521?spm=1001.2014.3001.5503

相关推荐
ye1501277745528 分钟前
DC6v-36V转3.2V1A恒流驱动芯片WT7017
单片机·嵌入式硬件·其他
念念01078 小时前
基于MATLAB多智能体强化学习的出租车资源配置优化系统设计与实现
大数据·人工智能·matlab
点灯小铭11 小时前
基于STM32单片机智能RFID刷卡汽车位锁桩设计
stm32·单片机·汽车·毕业设计·课程设计
fengfuyao98512 小时前
基于MATLAB的GUI实现人脸检测、眼睛检测以及LBP直方图显示
开发语言·计算机视觉·matlab
bai54593613 小时前
STM32 软件I2C读写MPU6050
stm32·单片机·嵌入式硬件
一株月见草哇13 小时前
Matlab(5)进阶绘图
matlab
逼子格15 小时前
AT89C52单片机介绍
单片机·嵌入式硬件·51单片机·硬件工程师·硬件工程师真题·at89c52·器件手册
生涯にわたる学び17 小时前
ARM 实操 流水灯 按键控制 day53
arm开发·嵌入式硬件
whaosoft-14317 小时前
w嵌入式分享合集68
嵌入式硬件
竹照煜_ysn19 小时前
STM32——软硬件I2C
stm32·嵌入式硬件·mongodb