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

news2025/4/15 3:21:54

目录

    • 一、 任务介绍
    • 二、基本原理
    • 三、软件仿真
      • 3.1 软件仿真基本原理
      • 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 F2N/fs = 13 ----------------如下图
频率为100Hz时基波位置
在这里插入图片描述
输入序列的频率降低,谱峰的位置和强度也随之减小,而且谱峰的强度衰减更慢。

六、 源码

#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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2333170.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RVOS-3.实现内存管理

3.内存管理 3.1 实验目的 对内存进一步的管理&#xff0c;实现动态的分配和释放。 实现 Page 级别的内存分配和释放。 在 page 分配的基础上实现更细颗粒度的&#xff0c;精确到字节为单位的内存管理。 &#xff08;练习8.1&#xff09; void *malloc(size_t size); void fr…

MySQL 约束(入门版)

目录 一、约束的基本概念 二、约束演示 三、外键约束 &#xff08;一&#xff09;介绍 &#xff08;二&#xff09;外键约束语法 &#xff08;三&#xff09;删除/更新行为 一、约束的基本概念 1、概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储…

系统与网络安全------Windows系统安全(11)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 制作U启动盘 U启动程序 下载制作U启程序 Ventoy是一个制作可启动U盘的开源工具&#xff0c;只需要把ISO等类型的文件拷贝到U盘里面就可以启动了 同时支持x86LegacyBIOS、x86_64UEFI模式。 支持Windows、L…

蓝桥杯 小蓝的操作(一维差分)

问题描述 一个数组 aa 中共包含 nn 个数&#xff0c;问最少多少次操作&#xff0c;可以让 aa 数组所有数都变成 11 。 操作的内容是&#xff1a;每次操作可以任选一个区间使得区间内的所有数字减 11 。 数据保证一定有解。 输入格式 第一行一个整数 nn 表示有 nn 个整数。 …

阿里云服务迁移实战: 02-服务器迁移

ECS 迁移 最简单的方式是 ECS 过户&#xff0c;不过这里有一些限制&#xff0c;如果原账号是个人账号&#xff0c;那么目标账号无限制。如果原账号是企业账号&#xff0c;则指定过户给相同实名认证的企业账号。 具体操作步骤可以参考官方文档 ECS过户 进行操作。 本文重点介绍…

protobuf的应用

1.版本和引用 syntax "proto3"; // proto2 package tutorial; // package类似C命名空间 // 可以引用本地的&#xff0c;也可以引用include里面的 import "google/protobuf/timestamp.proto"; // 已经写好的proto文件是可以引用 我们版本选择pr…

C++字符串操作详解

引言 字符串处理是编程中最常见的任务之一&#xff0c;而在C中&#xff0c;我们有多种处理字符串的方式。本文将详细介绍C中的字符串操作&#xff0c;包括C风格字符串和C的string类。无论你是C新手还是想巩固基础的老手&#xff0c;这篇文章都能帮你梳理字符串处理的关键知识点…

原理图设计准备:页面栅格模板应用设置

一、页面大小的设置 &#xff08;1&#xff09;单页原理图页面设置 首先&#xff0c;选中需要更改页面尺寸的那一页原理图&#xff0c;鼠标右键&#xff0c;选择“Schmatic Page Properties”选项&#xff0c;进行页面大小设置。 &#xff08;2&#xff09;对整个原理图页面设…

100道C++ 高频经典面试题带解析答案

100道C 高频经典面试题带解析答案 C作为一种功能强大且广泛应用的编程语言&#xff0c;在技术面试中经常被考察。掌握高频经典面试题不仅能帮助求职者自信应对面试&#xff0c;还能深入理解C的核心概念。以下整理了100道高频经典C面试题&#xff0c;涵盖基础知识、数据结构、面…

vue实现中英文切换

第一步&#xff1a;安装插件vue-i18n&#xff0c;npm install vue-i18n 第二步&#xff1a;在src下新建locales文件夹&#xff0c;并在locales下新建index.js、EN.js、CN.js文件 第三步&#xff1a;在EN.js和CN.js文件下配置你想要的字段&#xff0c;例如&#xff1a; //CN.js…

day31-贪心__56. 合并区间__ 738.单调递增的数字__968.监控二叉树 (可跳过)

56. 合并区间 合并区间&#xff0c;这道题和昨天的452. 用最少数量的箭引爆气球和435. 无重叠区间 也是类似的思路&#xff0c;我们需要先对所有vector按照左端点或者右端点进行排序。本题按照左端点进行排序。之后&#xff0c;如果前一段的右端点<后一段的左端&#xff0c…

【antd + vue】Modal 对话框:修改弹窗标题样式、Modal.confirm自定义使用

一、标题样式 1、目标样式&#xff1a;修改弹窗标题样式 2、问题&#xff1a; 直接在对应css文件中修改样式不生效。 3、原因分析&#xff1a; 可能原因&#xff1a; 选择器权重不够&#xff0c;把在控制台找到的选择器直接复制下来&#xff0c;如果还不够就再加&#xff…

GStreamer开发笔记(一):GStreamer介绍,在windows平台部署安装,打开usb摄像头对比测试

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/147049923 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、O…

UE5,LogPackageName黄字警报处理方法

比如这个场景&#xff0c;淘宝搜索&#xff0c;ue5 T台&#xff0c;转为ue5.2后&#xff0c;选择物体&#xff0c;使劲冒错。 LogPackageName: Warning: DoesPackageExist called on PackageName that will always return false. Reason: 输入“”为空。 2. 风险很大的删除法&…

unity曲线射击

b站教程 using UnityEngine; using System.Collections;public class BallLauncher : MonoBehaviour {public float m_R;public NewBullet m_BulletPre;public Transform m_Target;private void Start(){StartCoroutine(Attack());}private void OnDestroy(){StopAllCoroutine…

freecad内部python来源 + pip install 装包

cmake来源&#xff1a; 只能find默认地址&#xff0c;我试过用虚拟的python地址提示缺python3config.cmake 源码来源&#xff1a; pip install 装包&#xff1a; module_to_install "your pakage" import os import FreeCAD import addonmanager_utilities as util…

Spring Boot 中集成 Knife4j:解决文件上传不显示文件域的问题

Spring Boot 中集成 Knife4j&#xff1a;解决文件上传不显示文件域的问题 在使用 Knife4j 为 Spring Boot 项目生成 API 文档时&#xff0c;开发者可能会遇到文件上传功能不显示文件域的问题。本文将详细介绍如何解决这一问题&#xff0c;并提供完整的解决方案。 Knife4j官网…

信噪比(SNR)的基本定义

噪比&#xff08;SNR&#xff09;是衡量信号质量的核心指标&#xff0c;定义为有效信号与背景噪声的比值&#xff0c;广泛应用于电子、通信、医学及生物学等领域。 一、定义 基本定义‌ SNR 是信号功率&#xff08;或电压&#xff09;与噪声功率&#xff08;或电压&#xff…

SpringBoot集成阿里云文档格式转换实现pdf转换word,excel

一、前置条件 1.1 创建accessKey 如何申请&#xff1a;https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair 1.2 开通服务 官方地址&#xff1a;https://docmind.console.aliyun.com/doc-overview 未开通服务时需要点击开通按钮&#xff0c;然后才能调用…

STM32 模块化开发指南 · 第 5 篇 STM32 项目中断处理机制最佳实践:ISR、回调与事件通知

本文是《STM32 模块化开发实战指南》第 5 篇,聚焦于 STM32 裸机开发中最核心也最容易被忽视的部分——中断服务机制。我们将介绍如何正确、高效地设计中断处理函数(ISR),实现数据与事件从中断上下文传递到主逻辑的通道,并构建一个清晰、可维护、非阻塞的事件通知机制。 一…