STM32实现FFT,求取幅度频谱

news2024/12/23 13:23:52

STM32实现FFT,求取幅度频谱

FFT不太对劲的理解

FFT的原理比较复杂,因为32使用FFT不用去管算法是如何运作的,我在这里就进行简单的介绍了。

因为是简单介绍,就只介绍下幅度频谱图,不考虑相位频谱图。

​ FFT可以将一个信号从时域变换到频域,比如一个1VPP的1k的正弦信号,它的时域和频域的示意图如下:

image-20220228172259796

​ 频域为我们观察信号提供了一个新的视角。比如下面是1k和2k信号的叠加。

image-20220228172741687

​ 从时域上看,1k+2k的波形不容易进行处理,也不好猜出来这个波形到底有什么特性(当然这个例子其实还是比较好猜测的,复杂情况就不好看了)。可是变换到频域后,特性非常的明显,处理起来就方便了。

STM32实现FFT

添加DSP库

STM32 DSP库的快速添加 基于cubemx 调用,使用DSP库_四臂西瓜的博客-CSDN博客_cubemx dsp

采用第二种方法添加DSP,第一种方式添加的DSP库比较老,支持的FFT函数用起来不方便,这篇文章介绍的FFT函数老版本不支持。

设置ADC采集交流+串口重定向

STM32HAL ADC+TIM+DMA采集交流信号 基于cubemx_四臂西瓜的博客-CSDN博客

代码编写

DSP库里面的FFT支持32-4096,同时是 2 n 2^n 2n个整数的傅里叶变换。首先定义下面这些变量。其中只有fft_inputbuf,fft_outputbuf是用来进行FFT的。

#define FFT_LENGTH 1024

__IO uint8_t AdcConvEnd = 0;
uint16_t adcBuff[FFT_LENGTH];

float fft_inputbuf[FFT_LENGTH * 2];  
float fft_outputbuf[FFT_LENGTH];  

image-20220228190839152

​ 定义完成变量后进行服务内容的书写。首先是ADC进行数据的采集,这一部分再赘述:

HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adcBuff, FFT_LENGTH);
HAL_TIM_Base_Start(&htim3);

while (!AdcConvEnd)       //等待转换完毕
	;

​ 傅里叶变换分实数和虚数两种,使用最为频繁的是虚数。我们需要把ADC采集到的数据以虚数的形式存放到傅里叶变换的输入数组fft_inputbuf。虚数的存放方式如下

数组下标0123
数值实部虚部实部虚部

​ 比如ADC采集到的第二个数据adcBuff[1],它的数据存入到fft_inputbuf[2],它的虚部是fft_inputbuf[3],补零。

​ 我们调用arm_cfft_f32(&arm_cfft_sR_f32_len1024, fft_inputbuf, 0, 1);来对输入数据进行傅里叶变换。它的输入参数含义如下

  • arm_cfft_sR_f32_len1024为傅里叶变换结构体,1024是要运算的点数。我们在进行其他点数的计算时,比如说32个点的FFT,就可以用arm_cfft_sR_f32_len32。
  • fft_inputbuf为傅里叶变换需要处理的数据的首地址
  • 第三个参数0是正变换,1是反变换
  • 第四个参数一般是1

​ 经过傅里叶变换后的结果仍然为复数,虚部和实部的比可以计算出频率点的相位,这个在这里不进行考虑。我们直接对复数取模。

​ 取模是实部和虚部的平方和取平均来算,不过我们没必要自己去这样写,因为DSP库为我们提供了取模函数:arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH);参数含义如下:

  • fft_inputbuf源数据,形式为复数
  • fft_outputbuf取完模后的数据,形式为实数
  • FFT_LENGTH是取模的点数

​ 他们背后的运算是: f f t o u t p u t b u f [ i ] = f f t i n p u t b u f [ i ∗ 2 ] 2 + f f t i n p u t b u f [ i ∗ 2 + 1 ] 2 fftoutputbuf[i]=\sqrt{fftinputbuf[i*2]^2+fftinputbuf[i*2+1]^2} fftoutputbuf[i]=fftinputbuf[i2]2+fftinputbuf[i2+1]2

for (int i = 0; i < FFT_LENGTH; i++)
{
    fft_inputbuf[i * 2] = adcBuff[i] * 3.3 / 4096;//实部赋值,* 3.3 / 4096是为了将ADC采集到的值转换成实际电压
    fft_inputbuf[i * 2 + 1] = 0;//虚部赋值,固定为0.
}

arm_cfft_f32(&arm_cfft_sR_f32_len1024, fft_inputbuf, 0, 1);
arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH); 

​ 运算完成的结果还需要进行下面的处理,背后的原理就跟FFT算法有关了,这里不做解释。

​ 我们进行的是1024个点的FFT变换,那么fft_outputbuf下标0需要除以1024,其余的数除以512。

fft_outputbuf[0] /= 1024;

for (int i = 1; i < FFT_LENGTH; i++)//输出各次谐波幅值
{
    fft_outputbuf[i] /= 512;
}

最后再把运算结果打印出来即可。

printf("FFT Result:\r\n");

for (int i = 0; i < FFT_LENGTH; i++)//输出各次谐波幅值
{
	printf("%d:\t%.2f\r\n", i, fft_outputbuf[i]);
}

总的代码如下图

image-20220228193642686

运行结果

​ ADC以100k的频率去采集信号发生器产生的976hz的正弦信号,信号VPP=2v,直流偏置为2V。(注意别超过内部ADC的测量范围0-3.3V)

FFT Result:
0:	1.97
1:	0.00
2:	0.00
3:	0.00
4:	0.00
5:	0.00
6:	0.00
7:	0.00
8:	0.00
9:	0.01
10:	1.05
11:	0.01
12:	0.00
13:	0.00
14:	0.00
...(后面的数据都为0

结果分析

​ FFT计算出来的数据是对称的,我们只取前一半的数据,此时的前一半数据是512个。

​ FFT输出数组的下标表示的频率,计算关系为:
频率 = 数组下标 ∗ 采样率 f f t 计算的点数 频率=数组下标*\frac{采样率}{fft计算的点数} 频率=数组下标fft计算的点数采样率
​ 利用这个公式分析下上方的运行结果。数组下标0对应的是0hz,也就是直流偏置,幅度为1.97,和输入信号的2V符合。数组下标10对应的频率为 100 k 1024 ∗ 10 ≈ 976.5 h z \frac{100k}{1024}*10\approx976.5hz 1024100k10976.5hz,对应幅度为1.05V,和输入信号的2VPP相符。

精度问题

不知道读者有没有注意到待测频率为976hz,而不是我们平时常见的1k,2k?这是为了这个频率正好落在数组下标10点上。10对应的是 100 k 1024 ∗ 10 ≈ 976.5 h z \frac{100k}{1024}*10\approx976.5hz 1024100k10976.5hz,11对应的 100 k 1024 ∗ 11 ≈ 1075 h z \frac{100k}{1024}*11\approx1075hz 1024100k111075hz,1k落在了两点中间,这样就引起了栅栏效应,给人的直观感受就是能量分散了。下面是把待测信号改成1k后的运行结果。

FFT Result:
0:	1.98
1:	0.02
2:	0.03
3:	0.03
4:	0.03
5:	0.04
6:	0.05
7:	0.07
8:	0.10
9:	0.18
10:	0.95
11:	0.30
12:	0.13
13:	0.09
...

可以看到10,11,9等下标都分到了电压(能量)。实际应用中应尽可能避免栅栏效应。

栅栏效应下的补偿

如果不可避免得碰到了栅栏效应,是可以通过数据处理尽可能的还原求取待测信号的幅度值。方法是把频率点附近的能量聚集起来,将附近频率点的幅度平方求和,再取平均。

比如上面1k的情况,就可以把8,9,10,11,12的能量聚集起来。
0. 1 2 + 0.1 8 2 + 0.9 5 2 + 0. 3 2 + 0.1 3 2 = 1.026 \sqrt{0.1^2+0.18^2+0.95^2+0.3^2+0.13^2}=1.026 0.12+0.182+0.952+0.32+0.132 =1.026
经过补偿后的幅度值就跟VPP2V真实值更加接近了。

我这里只取了5个点,如果不同主要频率点下标相差比较大,可以取更多;反之更少。

后记

本文章收录于:

唐承乾的电赛小站

本文为系列文章中的冰山一角,欢迎进入小站查看。

配套程序:

STM32进行FFT傅里叶变换——0积分下载

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

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

相关文章

管理类联考——逻辑——形式逻辑——汇总篇——知识点突破——综合推理

角度——重难点 综合推理条件优先级口诀 事实问题优先看&#xff0c;数量不定先计算。 半事实条件可分类&#xff0c;重复元素是关键。 题干如果多假言&#xff0c;就要优先做串联。 题干只有一假言&#xff0c;否后或者找矛盾。 特殊条件优先看&#xff0c;其他条件放后边。

Mybatis 插入、修改、删除

前面几篇我们介绍了使用Mybatis查询数据&#xff0c;并且也了解了如何在Mybatis中使用JDK的日志系统打印日志&#xff1b;本篇我们继续介绍如何使用Mybatis完成数据的插入、修改和删除。 如果您对查询数据和Mybatis集成JDK日志系统不太了解&#xff0c;建议您先进行了解后再阅…

DP读书:鲲鹏处理器 架构与编程(十四)ACPI与软件架构具体调优

一分钟速通ACPI和鲲鹏软件移植 操作系统内核鲲鹏软件移植鲲鹏软件移植流程 编译工具选择编译参数移植案例源码修改案例鲲鹏分析扫描工具 Dependency Advisor鲲鹏代码迁移工具 Porting Advisor 鲲鹏软件性能调优鲲鹏软件性能调优流程CPU与内存子系统性能调优网络子系统性能调优磁…

es6·await/async案例笔记

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>await/async案例笔记</title> </head> …

CXL 内存交织(Memory Interleaving)

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

php在mysql创建数组字段的高效解决方案

案例说明 1.项目展示 将血糖的数值&#xff0c;按照下面表格的分类进行展示。 2.前端录入 将分类名称设置为单选项&#xff0c;血糖数值按照单选项的属性归属到对应的位置。 案例分析及操作步骤 1.在上面场景下&#xff0c;如何建立mysql数字字段&#xff1f; 如果每个…

10个免费PPT下载资源网站分享

PPT超级市场https://pptsupermarket.com/ PPT超级市场是一个完全免费的PPT模板下载网站&#xff0c;不需要注册登录&#xff0c;点击下载就能直接使用。 叮当设计https://www.dingdangsheji.com/ 叮当设计是一个完全免费的PPT模板下载网站&#xff0c;每一套PPT的质量都很高。除…

FileZilla使用密钥文件连接FSTP

文件-》站点管理-》新站点 右侧协议选择SFTP 填写主机和端口号 登录类型选择&#xff1a;密钥文件 填写用户名 选择密钥文件即可 注&#xff1a; 这里密钥文件默认只能选择.ppk&#xff1a; 如果你的密钥文件不是这个格式&#xff08;默认ssh生成的就不是&#xff09;&…

ZooKeeper数据模型/znode节点深入

1、Znode的数据模型 1.1 Znode是什么&#xff1f; Znode维护了一个stat结构&#xff0c;这个stat包含数据变化的版本号、访问控制列表变化、还有时间戳。版本号和时间戳一起&#xff0c;可让Zookeeper验证缓存和协调更新。每次znode的数据发生了变化&#xff0c;版本号就增加。…

CSS中如何实现元素的渐变背景(Gradient Background)效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ CSS 渐变背景效果⭐ 线性渐变背景⭐ 径向渐变背景⭐ 添加到元素的样式⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&…

说说广播流与普通流

分析&回答 user actions 可以看作是事件流&#xff08;普通流&#xff09;patterns 为广播流,把全量数据加载到不同的计算节点。 广播流 Broadcast是一份存储在TaskManager内存中的只读的缓存数据在执行job的过程中需要反复使用的数据&#xff0c;为了达到数据共享&am…

智能安全科技,Vatee万腾为您服务

在智能科技的引领下&#xff0c;Vatee万腾将为您点亮投资之路&#xff0c;助您在金融市场中抓住机遇&#xff0c;实现财务目标。作为一家融合科技与投资的先锋平台&#xff0c;Vatee万腾致力于为投资者提供智能化的投资方案和支持。 Vatee万腾以其先进的智能科技为基础&#xf…

「网络」1.不知的浏览器缓存精品答案

前言&#xff1a;你真的懂浏览器缓存吗 文章目录 强缓存协商缓存协商缓存可以基于两种头部来实现。小结 &#x1f680; 作者简介&#xff1a;作为某云服务提供商的后端开发人员&#xff0c;我将在这里与大家简要分享一些实用的开发小技巧。在我的职业生涯中积累了丰富的经验&am…

企业级智能PDF及文档处理SDK GdPicture.NET 14.2 Crack

企业级智能PDF及文档处理SDK GdPicture.NET 提供了一组非常先进的 API&#xff0c;这些 API 利用了人工智能、机器学习和模糊逻辑算法等尖端技术。经过超过 15 年的持续研究和对创新的专注&#xff0c;我们的 SDK 已成为市场上针对PDF、OCR、条形码、文档成像和各种格式最全面的…

黑客可利用 Windows 容器隔离框架绕过端点安全系统

新的研究结果表明&#xff0c;攻击者可以利用一种隐匿的恶意软件检测规避技术&#xff0c;并通过操纵 Windows 容器隔离框架来绕过端点安全的解决方案。 Deep Instinct安全研究员丹尼尔-阿维诺姆&#xff08;Daniel Avinoam&#xff09;在本月初举行的DEF CON安全大会上公布了…

整理mongodb文档:分页

个人博客 整理mongodb文档:分页 个人博客&#xff0c;求关注&#xff0c;如果文章不够清晰&#xff0c;麻烦指出。 文章概叙 本文主要讲下在聚合以及crud的find方法中如何使用limit还有skip进行排序。 分页的情况很经常出现&#xff0c;这也是这篇博客诞生的理由。 数据准备…

如何使用SQL系列 之 理解什么是关系数据库

简介 数据库管理系统 (DBMS)是允许用户与数据库交互的计算机程序。DBMS允许用户控制对数据库的访问、写入数据、执行查询以及执行与数据库管理相关的任何其他任务。 为了执行这些任务&#xff0c;DBMS必须有某种底层模型来定义数据的组织方式。关系模型是一种组织数据的方法&…

vue自定义键盘

<template><div class"mark" click"isOver"></div><div class"mycar"><div class"mycar_list"><div class"mycar_list_con"><p class"mycar_list_p">车牌号</p>…

OpenCV(一):Android studio jni配置OpenCV(亲测有效,保姆级)

目录 1.下载OpenCV的SDK 2.创建Android Native C项目 3.Android项目中导入OpenCV工程 4.导入OpenCV的库文件 5.实现opencv高斯模糊图像处理的demo 要在Android Studio中配置使用OpenCV库的C方法&#xff0c;需要完成以下步骤&#xff1a; 1.下载OpenCV的SDK 首先&#x…

SpringBoot项目配置文件数据库用户名密码加密

1、需求 在使用SpringBoot开发过程中&#xff0c;会将一些敏感信息配置到SpringBoot项目的配置文件中(不考虑使用配置中心的情况 )&#xff0c;例如数据库的用户名和密码、Redis的密码等。为了保证敏感信息的安全&#xff0c;我们需要将此类数据进行加密配置。 2、操作步骤 …