基于STM32 HAL库的FFT计算与数学运算:幅值、频率、均方根、平均值、最大值、最小值、峰峰值与标准差

news2025/1/22 15:59:19

一、用STM32进行FFT计算与数学运算的过程

1. 信号采集
首先,我们需要使用STM32的ADC模块来采集模拟信号,比如三相交流电。ADC将模拟信号(如电压或电流)转换为数字信号,供后续处理。

采样数量:FFT的计算通常需要2的整数次幂的采样点数(如1024、2048)。采样点数越多,频率分辨率越高。
  
采样频率:采样频率必须至少是信号频率的两倍(奈奎斯特定理)。例如,分析50Hz的信号时,采样频率应至少为100Hz,但通常使用更高的采样频率,下的例子采集50hz的交流信号,采用的是2500hz,以保证计算精度。

2. FFT变换
采集到的时域数据通过FFT算法进行处理,转换为频域信息。ARM-DSP库中有现成的FFT函数,可以简化计算过程。关于FFT可以查看上一篇关于FFT的介绍:STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)​​​​​​​

3. 运算结果:幅值、频率和均方根、平均值、最大值、最小值、峰峰值与标准差

幅值:信号的振幅大小,表示每个频率成分的强度。基波的幅值代表主要的电压或电流值。
频率:FFT能帮助我们识别信号中的不同频率成分,如电网中的50Hz基波及其他谐波。

均方根(RMS)运算公式:RMS = sqrt( (1/N) * Σ(x_i^2) )
其中,N 是数据点的数量,x_i 是每个数据点的值,Σ 表示求和。
 
平均值运算公式:平均值 = (1/N) * Σ(x_i)
其中,N 是数据点的数量,x_i 是每个数据点的值。
 
最大值运算公式:最大值 = max(x_i)
其中,x_i 是数据点集合中的每个值,max 表示取最大值操作。
 
最小值运算公式:
最小值 = min(x_i)
其中,x_i 是数据点集合中的每个值,min 表示取最小值操作。
 
峰峰值运算公式:
峰峰值 = 最大值 - 最小值
其中,最大值和最小值分别是从数据点集合中计算得出的。
 
标准差运算公式:
标准差 = sqrt( (1/N) * Σ((x_i - 平均值)^2) )
其中,N 是数据点的数量,x_i 是每个数据点的值,平均值是之前计算得出的数据点的平均值。

4. 处理流程概述

整个处理流程如下:

  • 信号采集:定时器触发ADC采样交流电信号。
  • 采样与ADC转换:STM32的ADC将模拟信号转为数字信号。
  • DMA传输:使用DMA自动传输采样数据到内存。
  • FFT计算:利用FFT将时域数据转换为频域数据。
  • 结果提取:从FFT结果中提取幅值、频率等信息。

二、采样频率设定-定时器3(TIM3)配置

1.时钟源:定时器3的时钟源被设置为84MHz。这意味着定时器的时钟频率是84,000,000 Hz。

2.预分频器(Prescaler):预分频器的值被设置为3(通过4-1得到,因为预分频器的值通常是通过设置其寄存器的一个字段来实现的,该字段的值是所需分频数减1)。预分频器的作用是将定时器的时钟频率降低到一个更低的频率,以便能够更容易地实现所需的计数周期。因此,经过预分频后的时钟频率为84,000,000 / 4 = 21,000,000 Hz。

3.计数器周期(AutoReload Register):AutoReload Register的值被设置为8399(通过8400-1得到)。这个寄存器定义了定时器计数到多少时会产生一个更新事件(或称为溢出事件),并重新从0开始计数。因此,定时器的计数周期是8400个时钟周期。

4.采样频率:

采样率(Fs)是指每秒对信号进行采样的次数。采样率是2500Hz,意味着每秒采样2500个点。

信号频率(F)是指信号的周期性变化的速率。测量信号频率是50Hz,意味着信号每秒变化50个周期。

为了计算FFT的采样频率,使用了两个宏定义:fft_arrfft_psc

  • fft_arr:这个宏被定义为8400,它实际上代表了AutoReload Register的值加1(即计数周期的实际值)。这个值用于计算定时器溢出一次所需的时间。

  • fft_psc:这个宏被定义为4,它代表了预分频器的值加1(即预分频的实际倍数)。以下公式来计算FFT的采样频率:


const uint32_t fft_sample_freq = 84000000 / (fft_arr * fft_psc);
将fft_arr和fft_psc的值代入公式中,得到:

fft_sample_freq = 84000000 / (8400 * 4) = 84000000 / 33600 = 2500 Hz

FFT的采样频率是2500 Hz。这意味着定时器每0.0004秒(即400微秒)溢出一次,从而为FFT提供一个新的采样点。

一个信号周期内的采样点数(N)可以通过以下公式计算:

N = Fs / F

将已知的采样率(Fs = 2500Hz)和信号频率(F = 50Hz)代入公式中,我们可以得到:

N = 2500Hz / 50Hz = 50

因此,在2500Hz的采样率下,50Hz信号一个周期内将有50个采样点。

三、ADC配置

ADC1配置,common settings :independent,当ADC配置为“independent”模式时,它意味着该ADC模块是独立工作的,不与其他ADC模块共享任何资源或配置。
​​​​​​​external trigger conversion source :timer3 tirgger out event。Timer3是一个定时器模块,它可以生成定时中断或触发输出事件。当Timer3达到某个预设的条件(如计数到某个值)时,它会触发一个事件,这个事件被用作ADC1的转换触发源。

中断DMA

四、程序实现

硬件:正点原子探索者 V3 STM32F407 开发板,下面是核心代码,代码中使用了ARM提供的数学库(arm_math.h)来实现FFT算法,以及STM32的标准库库来配置定时器、ADC和DMA。通过这些配置,系统能够高效地采集和处理模拟信号,分析其频谱特性。以下为核心代码,完整代码请在资源下载。

/*
*********************************************************************************************************
*	函 数 名: WaveProcess
*	功能说明: //WaveProcess 功能说明: 波形通道均方根,平均值,最大值,最小值,峰峰值,频率,标准差,幅值,相位的计算
*	形    参: 无。
*	返 回 值: 无
*********************************************************************************************************
*/
void WaveProcess(void)
{
	uint16_t    i; 
  data.WaveMin = 4095;
	uint16_t idex=0;	//用于将采集到的数据赋值给fft_inputbuf[2*idex]的计数	
  float tempdata=0;
	uint8_t temp[40];	
	uint16_t   freamplen; // freamp长度的一半

	
	/* 自动触发模式才计算FFT */
	arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);//初始化scfft结构体,设定FFT相关参数     //FFT_LENGTH 4096

	//adc1 pa5
	for(idex=0;idex<FFT_LENGTH;idex++) //adc1 fft1 //FFT_LENGTH==4096
	{			
		fft_inputbuf[2*idex]= (uint16_t)g_adc_dma_buf[idex]*(3.3/4096);      //生成输入信号实部
		fft_inputbuf[2*idex+1]=0;//虚部全部为0
	}
		arm_cfft_radix4_f32(&scfft,fft_inputbuf);	//FFT计算(基4)
		arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);	//把运算结果复数求模得幅值
		freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2); //寻找基波和谐波	

	
	data.WaveOffset=fft_outputbuf[0]/FFT_LENGTH;//偏置电压      
	data.WaveFreq=freamp[0];//频率
	data.WaveAmplitude=freamp[1]+data.WaveOffset;//幅度
	data.WavePhase=freamp[2];//相位
	freamp[0]=0;freamp[1]=0;freamp[2]=0;
			
	/* 求4096个数值的最大值和最小值 */
	for (i = 0; i < FFT_LENGTH; i++) 
	{
		data.WaveMean += g_adc_dma_buf[i];
	
		data.WaveRMS  += g_adc_dma_buf[i]*g_adc_dma_buf[i];
		
		if(g_adc_dma_buf[i] < data.WaveMin)
		{
			data.WaveMin = g_adc_dma_buf[i];
		}
		
		if(g_adc_dma_buf[i] > data.WaveMax)
		{
			data.WaveMax = g_adc_dma_buf[i];
		}
	}	
	/* 求RMS 均方根(RMS,Root Mean Square)*/
	data.WaveRMS = sqrt(data.WaveRMS/FFT_LENGTH)* 3.3f / 4095;
	//data.WaveMean = data.WaveMean/FFT_LENGTH;
		/* 求平均值 */
	data.WaveMean = data.WaveMean/FFT_LENGTH* 3.3f / 4095;
		/* 求标准差 */
	for (i = 0; i < FFT_LENGTH; i++) 
	{
		tempdata =g_adc_dma_buf[i];
		data.WaveStd  += pow((tempdata* 3.3f / 4095-data.WaveMean),2);	
	}	
	data.WaveStd = data.WaveStd/FFT_LENGTH;
	data.WaveStd =sqrt(data.WaveStd);

	
	/* 求最大值 */
	data.WaveMax =  data.WaveMax * 3.3f / 4095;
	
	/* 求最小值 */
	data.WaveMin = data.WaveMin *3.3f / 4095;
	
	/* 求峰峰值 */
	data.WavePkPk = data.WaveMax - data.WaveMin;		
	
	printf("fft_sample_freq:%d\r\n",fft_sample_freq);    //fft采样频率 	
	printf("WaveRMS:%.3f\r\n", data.WaveRMS); //数据打印,查看RMS结果
	printf("WaveMean:%.3f\r\n",data.WaveMean);//数据打印,查看平均值
	printf("WaveStd:%.3f\r\n",data.WaveStd);//数据打印,查看标准差
	printf("WaveMax:%.3f\r\n", data.WaveMax);//数据打印,查看最大值
	printf("WaveMin:%.3f\r\n", data.WaveMin);//数据打印,查看最小值 
	printf("WavePkPk:%.3f\r\n", data.WavePkPk);//数据打印,查看峰峰值
	printf("WaveOffset:%.3f\r\n",data.WaveOffset); //偏置电压 
	printf("WaveFreq:%.3f\r\n",data.WaveFreq);   //频率	
	printf("WaveAmplitude:%.3f\r\n",data.WaveAmplitude); //幅值    

}

// 获取FFT峰值
int fft_getpeak(float *inputx,float *input,float *output,uint16_t inlen,uint8_t x,uint8_t N,float y) //  intlen 输入数组长度,x寻找长度
{                                                                           
	int i,i2;
	uint32_t idex;  //不同于上一个函数中的,因为他们在不同的函数中被定义
	float datas;
	float sum;
	int outlen=0;
	for(i=0;i<inlen-x;i+=x)
	{
		arm_max_f32(input+i,x,&datas,&idex);   
		if( (input[i+idex]>=input[i+idex+1])&&(input[i+idex]>=input[i+idex-1])&&( (2*datas)/FFT_LENGTH )>y)   
		   {
			   sum=0;   
			   for(i2=i+idex-N;i2<i+idex+N;i2++)   
			   {
				   sum+=input[i2];          
			   }        
			   if(1.5*sum/(2*N)<datas)       
			   {                                                                                             
				     output[3*outlen+2] = atan2(inputx[2*(i+idex+1)+1],inputx[2*(i+idex+1)])*180/3.1415926f;	//计算相位		   
				     output[3*outlen+1] = 1.0*(2*datas)/FFT_LENGTH;   //计算幅度
					   output[3*outlen] = 1.0*fft_sample_freq*(i+idex+1)/FFT_LENGTH;//计算频率
					   outlen++;				   
			   }                                                                                               
               else continue;			   
		   }
			
		else continue;
		
	}
	return outlen;
}
  1. 定时器初始化 (Tim3_Init): 配置定时器3,用于控制ADC的采样频率。

  2. ADC初始化 (Adc_Init): 配置两个ADC(ADC1和ADC2),用于采集模拟信号。ADC1和ADC2分别连接到不同的通道,采集不同的模拟信号。

  3. DMA初始化 (Dma_ADC_Init): 配置DMA,用于将ADC采集的数据直接传输到内存中,减少CPU的负担。

  4. 数据初始化 (Data_Init): 调用上述初始化函数,完成系统的基本配置。

  5. FFT峰值获取函数 (fft_getpeak): 该函数用于在FFT结果中寻找峰值,这些峰值代表了信号中的基波(主要频率成分)。下面是核心中的核心。

    		arm_cfft_radix4_f32(&scfft,fft_inputbuf);  //fft运算
    		arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);	//把运算结果复数求模得幅值
    		freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2); //寻找基波和谐波
  6. DMA中断服务函数 (DMA2_Stream0_IRQHandler): 当DMA传输完成时,该函数会被调用。它负责执行FFT算法,计算信号的偏置电压、幅值、频率、均方根、平均值、最大值、最小值、峰峰值与标准差的相关信息并用串口1打印。 

五、现象

按下复位键,串口输出采集的4096个数据点,通过excel图表显示。的确一个周期内将有50个采样点。

1.信号发生器输入信号源,信号1:50Hz的信号的,幅度1V,偏置电压0.5V,相位0°;

2.串口收到测量结果与输入信号源的实际接近。

3.信号2:50Hz的信号的,幅度2V,偏置电压1V,相位0°;

六、总结 

通过上述介绍,我们探讨了如何使用STM32微控制器执行FFT计算,以提取信号的幅值、频率、均方根、平均值、最大值、最小值、峰峰值与标准差,一些示波器测量的参数。


希望这些内容能够为大家提供有价值的参考和指导。在实际应用中,理解和运用FFT的原理和技巧,将有助于我们更有效地处理和分析各种复杂的信号。

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

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

相关文章

关于Github报错Verify your two-factor authentication (2FA) settings的解决方案

如果我们在使用GitHub出现2FA验证问题&#xff1a;Verify your two-factor authentication (2FA) settings&#xff0c;那么可以参考下面的解决方法解决问题。 当然&#xff0c;如果有国外的手机号直接使用验证码接收就可以&#xff0c;问题是不支持中国手机啊。那么怎么办呢&…

【机器学习chp2】贝叶斯最优分类器、概率密度函数的参数估计、朴素贝叶斯分类器、高斯判别分析。万字超详细分析总结与思考

前言&#xff0c;请先看。 本文的《一》《二》属于两个单独的知识点&#xff1a;共轭先验和Laplace平滑&#xff0c;主要因为他们在本文的后续部分经常使用&#xff0c;又因为他们是本人的知识盲点&#xff0c;所以先对这两个知识进行了分析&#xff0c;后续内容按照标题中的顺…

游戏引擎学习第16天

视频参考:https://www.bilibili.com/video/BV1mEUCY8EiC/ 这些字幕讨论了编译器警告的概念以及如何在编译过程中启用和处理警告。以下是字幕的内容摘要&#xff1a; 警告的定义&#xff1a;警告是编译器用来告诉你某些地方可能存在问题&#xff0c;尽管编译器不强制要求你修复…

01.防火墙概述

防火墙概述 防火墙概述1. 防火墙的分类2. Linux 防火墙的基本认识3. netfilter 中五个勾子函数和报文流向 防火墙概述 防火墙&#xff08; FireWall &#xff09;&#xff1a;隔离功能&#xff0c;工作在网络或主机边缘&#xff0c;对进出网络或主机的数据包基于一定的 规则检…

express 从0-1如何创建一个项目 注册接口

内容参考&#xff1a; windos下安装mysql express 使用mysql 一、创建一个空项目 二、创建一个包管理工具 npm init -y三、安装需要的插件及app.js的部分实现 npm i express 安装express 框架 npm i cors 安装cors 用于跨域 npm install mysql2 安装mysql数据库 npm i b…

Shell基础(4)

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…

(长期更新)《零基础入门 ArcGIS(ArcMap) 》实验一(下)----空间数据的编辑与处理(超超超详细!!!)

续上篇博客&#xff08;长期更新&#xff09;《零基础入门 ArcGIS(ArcMap) 》实验一&#xff08;上&#xff09;----空间数据的编辑与处理&#xff08;超超超详细&#xff01;&#xff01;&#xff01;&#xff09;-CSDN博客 继续更新 本篇博客内容为道路拓扑检查与修正&#x…

Python防检测之鼠标移动轨迹算法

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

3D编辑器教程:如何实现3D模型多材质定制效果?

想要实现下图这样的产品DIY定制效果&#xff0c;该如何实现&#xff1f; 可以使用51建模网线上3D编辑器的材质替换功能&#xff0c;为产品3D模型每个部位添加多套材质贴图&#xff0c;从而让3D模型在展示时实现DIY定制效果。 具体操作流程如下&#xff1a; 第1步&#xff1a;上…

Qt按钮类-->day09

按钮基类 QAbstractButton 标题与图标 // 参数text的内容显示到按钮上 void QAbstractButton::setText(const QString &text); // 得到按钮上显示的文本内容, 函数的返回就是 QString QAbstractButton::text() const;// 得到按钮设置的图标 QIcon icon() const; // 给按钮…

Cellebrite VS IOS18Rebooting

Cellebrite VS IOS18Rebooting我们想分享一些有关 iOS 18 重启“功能”的信息。在过去一周左右的时间里&#xff0c;人们对 iOS 18 中一项新的未记录功能产生了极大关注&#xff0c;该功能会导致设备在一段时间不活动后重新启动。 这意味着&#xff0c;如果设备在一定时间不活…

【Linux】:进程信号(详谈信号捕捉 OS 运行)

✨ 来去都是自由风&#xff0c;该相逢的人总会相逢 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞…

视觉SLAM相机——单目相机、双目相机、深度相机

一、单目相机 只使用一个摄像头进行SLAM的做法称为单目SLAM&#xff0c;这种传感器的结构特别简单&#xff0c;成本特别低&#xff0c;单目相机的数据&#xff1a;照片。照片本质上是拍摄某个场景在相机的成像平面上留下的一个投影。它以二维的形式记录了三维的世界。这个过程中…

MongoDB在现代Web开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 引言 MongoDB 概述 定义与原理 发展…

OceanBase 分区表详解

1、分区表的定义 在OceanBase数据库中&#xff0c;普通的表数据可以根据预设的规则被分割并存储到不同的数据区块中&#xff0c;同一区块的数据是在一个物理存储上。这样被分区块的表被称为分区表&#xff0c;而其中的每一个独立的数据区块则被称为一个分区。 如下图所示&…

Linux(CentOS 7) yum一键安装mysql8

1、通过yum安装 &#xff08;1&#xff09;下载mysql 在Linux找个地方输入以下命令 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm &#xff08;2&#xff09;安装mysql yum 仓库配置文件 [rootVM-8-15-centos ~]# sudo rpm -Uvh mysql80-c…

记一次预览USB摄像头并获取实时回调数据的过程(UVCAndroid集成)

背景 主工程是gradle4.8 jdk1.8 启用jetifier要接入的usb摄像头的库是UVCAndroid gradle8.7 jdk17 接入过程 看了下setCallbackActivity非常适合我们的需求&#xff0c;而且回调后的数据是RGB888&#xff0c;看到demo中用到了xml若干于是想到用aar打包&#xff0c;整个过程也…

shell脚本_永久环境变量和字符串操作

一、永久环境变量 1. 常见的环境变量 2. 设置永久环境变量 3.1.将脚本加进PATH变量的目录中 3.2.添加进环境变量里 3.2.修改用户的 shell 配置文件 二、字符串操作 1. 字符串拼接 2. 字符串切片 3. 字符串查找 4. 字符串替换 5. 字符串大小写转换 6. 字符串分割 7…

操作系统进程管理实验

父子进程 用系统调用fork()函数实现子进程的创建&#xff0c;熟悉进程创建的执行过程。 #include <stdio.h> #include <stdlib.h> #include <unistd.h>int main() {// 打印主进程的 PIDprintf("hello, world (pid: %d)\n", (int)getpid());// 创…

DB Type

P位 p 1时段描述符有效&#xff0c;p 0时段描述符无效 Base Base被分成了三个部分&#xff0c;按照实际拼接即可 G位 如果G 0 说明描述符中Limit的单位是字节&#xff0c;如果是G 1 &#xff0c;那么limit的描述的单位是页也就是4kb S位 S 1 表示代码段或者数据段描…