MATLAB、FPGA、STM32中调用FFT计算频率、幅值及相位差
系列文章目录
文章目录
- 系列文章目录
- 前言
- MATLAB
- STM32
- 调用DSP
- STM32中实现FFT
- 关于初相位
- FPGA
前言
最近在学习如何在STM32中调用FFT
MATLAB
首先对FFT进行一下说明,我们输入N个点的数据到FFT中,FFT会返回N个点的数据,这些数据都是复数,其模值就是我们用来计算频率和幅值,模值越大代表该频率占比越多,模值/N*2就是幅值。每个复数的角度代表了该频率的相位差(这里的初相位不一定准确,只有特定条件下是初相位)。FFT的结果一般是中心对称的,只需要看左半部分就行。
例如:
t = 0:1:255;
x = 30*cos(2*pi/10*t)
figure
plot(x);
y = fft(x)
figure
plot(abs(y))
tol = 2e+3; //将不想要的频率的相位筛掉
y(abs(y) < tol) = 0;
theta = angle(y);
figure
plot(theta/pi*180);
我们看到第27个点的幅值是最大的,那么该点该表的频率就是我们所求的,假设采样率为X,那么该点的频率为X27/采样点数,该点的幅值=2930/2562,该点的相位这里显示为-71度,但很明显我们的初相位是0
这里我们不变采样点数,移相30度
x = 30*cos(2*pi/10*t-30/180*pi)
频率没变
可以看到相位确实变了30度,这就是为什么说计算不了初相位,但是对于同一频率,同样采样点数下的两个波,可以计算相位差的原因
那么什么情况下计算的结果是初相位呢,只有FFT的输入数据刚好是整数个周期的时候,得到的才是初相位25
x = 30*cos(2*pi/25.6*t-30/180*pi)
这里输入256个数据,那么我们设计为刚好输入10个周期的情况,很明显,得到的-30度就是我们的初相位
STM32
调用DSP
首先添加DSP的环境
之后,添加对应的.lib文件
添加需要的头文件
添加CMSIS\DSP\include的路径
之后就可以顺利编译了
STM32中实现FFT
#include "arm_math.h"
#include "arm_const_structs.h"// FFT 相关参数的定义
#define FFT_SIZE 256
#define SAMPLING_FREQUENCY 10000000
float32_t inputSignal[FFT_SIZE*2];// FFT 输入信号数组
float32_t fftOutput[FFT_SIZE];// FFT 输出数组
uint32_t index_;// 存放 FFT 输出中最大值的索引
float32_t maxValue;
float32_t Vpp;
float32_t frequency ;// 用于存放计算结果的频率变量
float phase;//角度
#define M_PI (3.1415926f)void fftCalculate(void)// FFT 计算函数
{uint8_t i;arm_cfft_f32(&arm_cfft_sR_f32_len256, inputSignal, 0, 1);// 执行 FFT 计算//这个就是快速傅里叶变换的主要接口,第一个参数可以理解为你输入到FFT里的采样点的个数;第二个参数为输入数组;第三个参数为正反变换,一般使用填0;第四个参数为位反转使能,一般使用填1。输出会覆盖掉输入arm_cmplx_mag_f32(inputSignal, fftOutput, FFT_SIZE );// 计算 FFT 输出的幅度,输入inputSignal,输出fftOutputindex_ = 0;// 查找 FFT 输出中的最大值maxValue = fftOutput[1]; //跳过直流分量 for (uint32_t i = 1; i < FFT_SIZE /2; i++) //这里的数组第0个数据放的是直流分量,跳过就行{if (fftOutput[i] > maxValue){ maxValue = fftOutput[i];index_ = i;}}phase = atan2(inputSignal[2*index_+1],inputSignal[2*index_]) * 180 / 3.1415926f; //相位frequency = (float32_t)index_ * (float32_t)SAMPLING_FREQUENCY / (float32_t)FFT_SIZE;// 根据最大值的索引计算信号的频率Vpp = maxValue * 2 / 256; //幅值
}
第一个参数可以理解为你输入到FFT里的采样点个数,除了256还有512等等;第二个参数为输入数组;第三个参数为正反变换,一般使用填0;第四个参数为位反转使能,一般使用填1。输出会覆盖掉输入,所以最后计算出的复数会写入到inputSignal中。后面计算相位就需要用到这个数据
arm_cfft_f32(&arm_cfft_sR_f32_len256, inputSignal, 0, 1);
首先要注意,输入到arm_cfft_f32中的数据inputSignal是由一个实部一个虚部组成的,所以调用的时候要手动将虚部设置为0
for(i=0;i<FFT_SIZE;i++)
{inputSignal[i*2] = (float)SPI_AD_DATA.RX_DATA_BUF[i+503] - 128; //2+500+2inputSignal[i*2+1] = 0;
}
最后对计算出的相位要进行处理,因为FFT如果选择的数据是非整数个周期,计算出的相位是有一定偏移,但是在同一频率下,同一FFT计算点数下,这个偏移量是固定的,所以可以计算两个之间的相位差,但无法得到相位。
相位差就把得到的两个相位做差就行,但需要把负相位加360度转到正,最后计算出的相位差还要取绝对值
关于初相位
前面MATLAB介绍了初相位的问题,那么STM32中不能计算大部分频率的相位吗,其实有一种方法,如果能改变ADC的采样率,因为频率计算不会出现初相位这样问题,那么得到频率后,更改ADC的采样率,使得在我们设计的采样点数下,采集整数个周期,就能得到初相。
但感觉实现起来有难度
FPGA
通过调用Quaruts的官方IP核就能实现FFT名单时需要写一个外部控制模块,这里我利用ROM来进行测试,可以取intel官网找他们的IP手册
Length:FFT变换长度64、128、256、512、1024、2048、4096、8192、16384、32768或65536。可变流还允许8、16、32、131072和262144。
Direction:傅里叶变换的方向,选择Forward:FFT(快速傅里叶变换),Reverse:FFT(快速傅里叶反变换),Bi-directional(用户可通过输入控制是FFT还是IFFT)
Calculation:计算的延迟
Throughput Latency:处理延迟
Burst:突发架构,需要的内存资源最小,平均吞吐量最低。
Buffered Brust:缓冲突发架构需要的内存资源比流式低,平均吞吐量较低。
Streaming:流式架构可以连续变换处理。
Variable Streaming:可变流式架构可以连续处理,并且可以运行时控制变换长度。在线改变FFT的大小,速度和流式差不多。前三种模式运算速度依次增大,占用资源也一次增大。
Input Otder:输入数据的顺序(顺序模式或逆序模式)
Output Order:输出数据的顺序(顺序模式或逆序模式)
Representation:数据结构和旋转因子
Block Floating(块浮点):应用Burst、Buffered Brust、Streaming
Fixed Point(定点)和Single Floating Point(单浮点):用于Variable Streaming
块浮点就是在数据的一帧数据中有一个共同的缩放因子,当一帧数据中有大有小的时候,共用一个缩放因子会造成误差增大。
Data Input Width:输入数据的数据宽度,8, 10, 12, 14, 16, 18, 20,24, 28, 32。
Twiddle Width:旋转因子的数据宽度,旋转因子的数据宽度不能大于输入数据的数据宽度。
Data Output Width::输出数据的数据宽度。FFT的计算结果是输出的实部和虚部与缩放因子(EXP)的结合,缩放因子为负,输出数据需要左移(增大),为正则右移,输出的实部和虚部,缩放因子都是有符号数。
clk : (in) 时钟信号
reset_n : (in)复位信号,低电平有效,至少一个时钟周期
inverser : (in)低电平为FFT,高电平为IFFT
sink_valid : (in)输入数据有效信号
sink_sop : (in)输入数据起始信号,与第一个数据对齐,只需保持一个时钟周期即可
sink_eop : (in)输入数据结束信号,与最后一个数据对齐,只需保持一个时钟周期
sink_real : (in)输入数据的实部
sink_imag : (in)输入数据的虚部,一般直接置0
sink_ready : (out) IP核准备好接收数据了,实测一直高电平
sink_error : (in)输入错误信号,置0即可,不会影响。00 = no error,01 = missing start of packet (SOP),10 = missing end of packet (EOP),11 = unexpected EOP
source_error : (out) 输出错误信号,若输入的数据格式有误,则不进行FFT变换,并给出错误值。
source_ready : (in)输入数据准备好,置1即可,随时可以输出数据
source_sop : (out)输出数据起始信号,与输出的第一个数据对齐
source_eop : (out)输出数据的终止信号,与输出的最后一个数据对齐
source_real : (out)输出数据的实部
source_imag : (out)输出数据的虚部
source_exp : (out)数据数据的缩放因子,只有流式、突发和缓冲突发模式有。
source_vaild : (out) IFFT控制线,FFT完成时,信号置高,输出数据
输入数据时序:
输出数据时序:
多次调用时序:
module FFT_control
(input clk,input rst_n,input [15:0] source_real,input [15:0] source_imag,input [5:0] source_exp, input source_valid, input [1:0] source_error, input source_sop, input source_eop, input sink_ready,output reg sink_valid,output reg sink_sop,output reg sink_eop,output wire [1:0] sink_error,output wire [15:0] sink_imag, //实部数据采集模块输入,虚部直接置0output wire source_ready,output wire inverse, output reg [ 10:0] ROM_address,output wire [ 11:0] FFT_out,output wire [ 15:0] sqrt_data_out);assign FFT_out=sqrt_data_out;
localparam frames_FFT=13'd1024-1'd1;
/*输入数据帧数:1024或2048看IP核内部使用*/
reg [12:0] frames_in;reg FFT_output_start;reg [ 15:0] fft_real_out;
reg [ 15:0] fft_image_out;
reg [ 31:0] fft_data_out;
reg [ 5:0] fft_exp_out;reg FFT_count_output;
reg FFT_count_output_MAX;
reg FFT_count_output_start;/* FFT输入数据虚部 */
assign sink_imag=8'd0;
/* FFT控制信号,1:FFT,0:FFT */
assign inverse = 1'b0;/* 输入错误信号 */
assign sink_error = 2'b0;
/*置1即可,FFT随时可以输出数据*/
assign source_ready=1'd1;reg [2:0]state;
localparam start_state = 3'b00;
localparam start_next_state= 3'b01;
localparam end_input_state= 3'b10;
localparam wait_output_state= 3'b11; /*数据读取部分,FFT数据输入*/
always @(posedge clk or negedge rst_n) begin if(rst_n== 1'd0)begin sink_valid<=1'd0;sink_sop<=1'd0;sink_eop <= 1'd0;state<=start_state;endelse begincase (state)start_state:begin sink_valid<=1'd1; sink_sop<=1'd1;state<=start_next_state;endstart_next_state:begin sink_sop<=1'd0;if(frames_in == frames_FFT-1'd1) beginstate <=end_input_state; endendend_input_state:beginsink_valid<=1'd0; sink_eop <= 1'd1; state <=wait_output_state;endwait_output_state:begin sink_eop <= 1'd0; FFT_output_start=1'd1;endendcaseend
end/*计算输入数据帧数*/
always @ (posedge clk or negedge rst_n) beginif(rst_n== 1'd0) beginframes_in <= 'd0;endelse beginif(sink_valid==1'd1) begin //这里sink_ready会自己拉低frames_in <= frames_in+1'd1;endelse beginframes_in <= frames_in;endend
end/*FFT输出数据读取*//* FFT输出的实部信号,数据是有符号数据,需要转换 */
always @ (*)
beginif(source_valid==1'd1)fft_real_out = source_real[15] ? (~source_real[15:0]+1) : source_real;elsefft_real_out = 0;
end/* 相当组合逻辑,FFT输出的虚部信号 */
always @ (*)
beginif(source_valid==1'd1)fft_image_out = source_imag[15] ? (~source_imag[15:0]+1) : source_imag;elsefft_image_out = 0;
end/* 相当组合逻辑,FFT输出的数据转换 ,这个数据不能直接用来就平方根,老不稳定,很奇怪,必须加时序,加了就会延后1拍*/
always @ (posedge clk )
beginif(source_valid==1'd1)fft_data_out <= fft_real_out*fft_real_out + fft_image_out*fft_image_out;elsefft_data_out <= 0;
end/*相当组合逻辑,FFT输出的指数信号,旋转因子 */
always @ (*)
beginif(source_valid==1'd1)fft_exp_out = source_exp;elsefft_exp_out = fft_exp_out;
end/* 平方根模块IP核,fft_data_out时序电路延迟1拍 */
ALtearsqrt SQRT_Module
(.radical (fft_data_out ),.q (sqrt_data_out),
);always @(posedge clk or negedge rst_n) begin if(rst_n== 1'd0) beginROM_address <= 'd0;endelse beginif(ROM_address<11'd1022)ROM_address<=ROM_address+1'd1;elseROM_address<='d0;end
end
endmodule
之后用signaltap抓取数据进行测试:
以Burst模式、1024数据长度、16Bits数据宽度、ROM为输入的模式进行测试(其他数据宽度经过测试效果不佳)
此频率代指一段FFT数据(1024)中有几个周期正弦波,因为存在时序打拍,所以这里峰值点减1为实际点位
相关文章:

MATLAB、FPGA、STM32中调用FFT计算频率、幅值及相位差
系列文章目录 文章目录 系列文章目录前言MATLABSTM32调用DSPSTM32中实现FFT关于初相位 FPGA 前言 最近在学习如何在STM32中调用FFT MATLAB 首先对FFT进行一下说明,我们输入N个点的数据到FFT中,FFT会返回N个点的数据,这些数据都是复数&#…...

基于SSM的医院药品库存系统的设计与实现---附源码76620
摘要 医院药品库存管理是医院管理的重要组成部分,对于保障医疗服务的质量和效率具有重要意义。传统的手工管理方式已经无法满足药品库存管理的需求,因此建立一个医院药品库存系统具有重要的实践价值。 使用Java语言开发医院药品库存系统可以兼容不同操作…...

Jupyter管理内核命令
1.显示有哪些内核 jupyter kernelspec list2.删除某个内核 jupyter kernelspec remove xxx3.添加某个内核 先激活环境 conda activate test_env然后安装ipykernel包 pip install ipykernel在虚拟环境中安装ipykernel包 python -m ipykernel install --name test_env安装过…...

简单分享-获取.txt文件内数据 文件内数据逗号分隔 分隔符 C语言
简单分享-获取.txt文件内数据 文件内数据逗号分隔 分隔符 C语言 数据存储到文件中,把文件数据读取到数组,方便数据处理。 # include <stdio.h> # include <stdlib.h> # include <string.h>#define DATANUM 307200 //数组个数 int ma…...

从0开始手把手带你入门Vue3
前言 本文并非标题党,而是实实在在的硬核文章,如果有想要学习Vue3的网友,可以大致的浏览一下本文,总体来说本篇博客涵盖了Vue3中绝大部分内容,包含常用的CompositionAPI(组合式API)、其它CompositionAPI以及一些新的特…...

C# USB通信技术(通过LibUsbDotNet库)
文章目录 1.下载LibusbDotNet库2.引入命名空间3. 实例化USB设备4.发送数据5.关闭连接 1.下载LibusbDotNet库 右击项目选择管理NuGet程序包在弹出的界面中搜索LibusbDotNet,然后下载安装。 2.引入命名空间 using LibUsbDotNet; using LibUsbDotNet.Main;3. 实例化…...

常用Java API
1 字符串处理 1.1 String 类 String 类是 Java 中不可变的字符序列。它提供了以下常用方法: length():返回字符串的长度。 charAt(index):返回指定索引处的字符。 substring(startIndex, endIndex):返回从 startIndex 到 endI…...

使用opencv优化图片(画面变清晰)
文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强…...

Java 回顾方法的定义
一、方法的定义 1.修饰符(public static…)详见博客【Java 方法的定义】 2.返回值(int, double, char[],…., void)详见博客【Java 方法的定义】 3. break:跳出switch 结束循环,详…...

网络安全产品认证证书大全(持续更新...)
文章目录 一、引言二、《计算机信息系统安全专用产品销售许可证》2.1 背景2.2 法律法规依据2.3 检测机构2.4 检测依据2.5 认证流程2.6 证书样本 三、《网络关键设备和网络安全专用产品安全认证证书》3.1 背景3.2 法律法规依据3.3 检测机构3.4安全认证和安全检测依据标准3.5 认证…...

win10 安装多个版本的python
1,安装python3.9 和python3.10 2, 安装完之后分别打开两个版本的Python的安装目录(第一层目录),把pythonw.exe分别重命名为pythonw_39.exe和pythonw_310.exe,把python.exe复制一份,并分别重命名为python_…...

【ORACLE】数据备份
Oracle数据库备份是确保数据安全和可靠性的重要环节。Oracle提供了多种备份方法,包括冷备份、热备份、逻辑备份(如使用expdp和impdp)以及使用RMAN(Recovery Manager)进行物理备份。 冷备份:在数据库关闭的状…...

[Golang] goroutine
[Golang] goroutine 文章目录 [Golang] goroutine并发进程和线程协程 goroutine概述如何使用goroutine 并发 进程和线程 谈到并发,大多都离不开进程和线程,什么是进程、什么是线程? 进程可以这样理解:进程就是运行着的程序&…...

【前端】JavaScript高级教程:函数高级——执行上下文与执行上下文栈
文章目录 遍历提升与函数提升执行上下文执行上下文栈(1)执行上下文栈(2)面试题 遍历提升与函数提升 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>01_变量提升与函数提升</title> </head&…...

【阻抗管传递函数法】频域声压,即复声压是指什么
在阻抗管传递函数法中提到的“频域声压数据”,是通过对传声器测得的“时域声压信号”进行快速傅里叶变换(FFT)后得到的结果。 具体来说,这些频域声压数据指的是传声器测量的声压随时间变化的数据,经过傅里叶变换后&am…...

Python青少年简明教程:类和对象入门
Python青少年简明教程:类和对象入门 Python支持多种编程范式(programming paradigms),即支持多种不同的编程风格和方法。初学者开始重点学习关注的编程范式,一般而言是面向过程编程和面向对象编程。面向过程编程&#…...

【vue+el-table】表格操作列宽度跟随按钮个数自适应, 方法封装全局使用
效果图 以上图片分别代表不同用户权限下所能看到的按钮个数, 操作列宽度也会自适应宽度, 就不会一直处于最大宽度, 导致其他权限用户看到的页面出现大量留白问题. 目录 解决方法解决过程中可能出现的问题width赋值时为什么不放update()中btnDom为什么不能直接调用forEach为…...

OpenAI发布全新o1 AI模型具备推理能力
🦉 AI新闻 🚀 OpenAI发布全新o1 AI模型具备推理能力 摘要:OpenAI推出新AI模型o1,具备推理能力,旨在比人类更快地解决复杂问题。o1与o1-mini版本同时发布,前者训练成本较高,但在编程和多步骤问…...

如何在本地部署大语言模型
近年来,随着大语言模型(如GPT、BERT等)的迅速发展,越来越多的开发者和研究人员希望在本地环境中部署这些强大的模型,以便用于特定的应用场景或进行个性化的研究。本文将详细介绍如何在本地部署大语言模型,涵…...

秒懂:环境变量
前言 1.Linux当中70%以上的命令程序都是用C语言写的 2.执行命令程序和运行自己写的程序没有任何区别 3.自己程序运行必须要带路径(绝对/相对都可) 4. 系统指令可带可不带(带不要瞎带) 变量具有全局特性是…...

使用 @Param 注解标注映射关系
目录 1. 场景描述 2. SQL语句 3. 方法定义 4. Param注解的使用 5. 总结 在开发过程中,我们经常需要在Java应用程序中执行数据库操作,尤其是更新操作。在Spring Data JPA框架中,我们可以使用原生SQL语句来执行这些操作,并通过…...

Java学习中在打印对象时忘记调用 .toString() 方法或者没有重写 toString() 方法怎么办?
在 Java 编程中,toString() 方法对于调试、日志记录以及打印对象信息至关重要。然而,许多初学者在打印对象时可能会忘记调用 .toString() 方法,或者在自定义类中没有重写 toString() 方法,这可能导致输出结果不符合预期。 一、Ja…...

如何评估一个RAG(检索增强生成)系统-上篇
最近项目中需要评估业务部门搭建的RAG助手的效果好坏,看了一下目前业界一些评测的方法。目前分为两大类,基于传统的规则、机器学习的评测方法,基于大模型的评测方法。在这里做一些记录,上篇主要做评测方法的记录,下篇会…...

rust解说
Rust 是一种开源的系统编程语言,由 Mozilla 研究院开发,旨在提供高性能、内存安全且并发性良好的编程体验。 Rust 于 2010 年由 Graydon Hoare 开始设计,并在 2015 年发布了第一个稳定版本。 Rust 的设计目标是解决 C 等传统系统编程语言在…...

Elasticsearch 开放 inference API 为 Hugging Face 添加了原生分块支持
作者:来自 Elastic Max Hniebergall 借助 Elasticsearch 开放推理 API,你可以使用 Hugging Face 的推理端点(Inference Endpoints)在 Elasticsearch 之外执行推理。这样你就可以使用 Hugging Face 的可扩展基础架构,包…...

Jenkins部署若依项目
一、配置环境 机器 jenkins机器 用途:自动化部署前端后端,前后端自动化构建需要配置发送SSH的秘钥和公钥,同时jenkins要有nodejs工具来进行前端打包,maven工具进行后端的打包。 gitlab机器 用途:远程代码仓库拉取和…...

ELK笔记
要搞成这样就需要钱来买服务器 开发人员一般不会给服务器权限,不能到服务器上直接看日志,所以通过ELK看日志。不让开发登录服务器。即使你查出来是开发的问题,费时间,而且影响了业务了,就是运维的问题 开发也不能登录…...

计算机网络 --- 计算机网络的分类
一、计算机网络分类 1.1 按分布范围分类 举例:广域网(WAN)、局域网(LAN) 举例:个域网(PAN) 1.2 按传输技术分类 广播式网络――当一台计算机发送数据分组时,广播范围…...

三维动画|创意无限,让品牌传播更精彩!
随着三维动画技术的不断成熟,三维动画宣传片能够很好地宣传品牌、推广产品,因而慢慢地受到不少企业的青睐,成为品牌最常用的一种宣传方式。 三维动画宣传片作为艺术感极高的宣传视频有强烈的节奏感,而且具有风趣、易懂等特点&…...

欧零导航系统正式版,功能强大,可直接运营
欧零导航系统正式版,带广告位/导航分类/可直接运营 本系统采用PHPMySQL技术开发 拥有独立的安装和后台系统 后台采用BootstripMDUI框架 前台使用响应式界面,自适应各种屏幕 代码免费下载:百度网盘...