打印
[应用相关]

基于STM32单片机的FFT算法

[复制链接]
11441|117
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
1. 为什么需要FFT?
任何连续测量的时域信号都可以表示为不同频率的正弦波信号的无限叠加。以累加的方式来计算该信号中不同信号的频率、振幅和相位。有些信号在时域很难看出什么特征,但是如果变换到频率之后,就很容易看出特征了,这就是很多信号要采用FFT的原因。

使用特权

评论回复
沙发
hanzhen654|  楼主 | 2019-11-19 18:35 | 只看该作者
1. 变换如何进行的?
按照变换输入信号的类型不同,傅里叶变换分为四种类型:
01. 非周期连续信号傅里叶变换(FT
02. 周期连续信号傅里叶变换(FS
03. 非周期离散信号离散时域傅里叶变换(DTFT
  04.周期离散信号离散傅里叶变换(DFT


使用特权

评论回复
板凳
hanzhen654|  楼主 | 2019-11-19 18:36 | 只看该作者
我们只研究离散信号,因为单片机只能处理离散信号,最终的目的是让单片机来处理的。所以对于离散信号变换只有离散傅里叶变换才能被适用。单片机只能处理一些离散的有限长的数据,我们要研究的FFT也就是DFT的一种快速算法。
DFT的运算过程:     

X(K) — 频域值
x(n) — 时域采样点
n — 时域采样点的序列索引
k — 频域值的索引
N — 进行转换的采样数量

使用特权

评论回复
地板
hanzhen654|  楼主 | 2019-11-19 18:37 | 只看该作者
以一个4个点的DFT变换来简单说明FFT是怎样实现快速算法的:
计算得出:

其中红色部分是FFT必须计算的分量,蓝色部分不需要计算,有红色部分直接推倒出来,比如:
  …….因此,FFT与DFT相比大大减少了计算量。

使用特权

评论回复
5
hanzhen654|  楼主 | 2019-11-19 18:38 | 只看该作者
3.变换前后信号有何种对应关系?
假设采样频率为Fs,信号频率为F,采样点为N。那么经过FFT之后结果就是一个N点的复数。这个点的模值就是频率值下的幅度特性。假设原始信号的峰值为A,那么FFT的结果的每一个点(除第一个直流分量外)的模值就是A的倍,而第一个点就是直流分量,它的模值就是直流分量的N倍。而每一个点的相位就是该频率下的信号相位。第一个点表示直流分量(即0Hz),而最后一个点N的再下一个点(实际上这个点是不存在的,这里是假设的第N+1个点,也可以看做是将第一个点分做两半分,另一半移到最后)则表示采样频率Fs,这中间被N-1个点平均分成N等份,每个点的频率依次增加。例如某点n所表示的频率为:Fn=(n-1)*Fs/N。由上面的公式可以看出,Fn所能分辨到频率为为Fs/N,如果采样频率Fs为1024Hz,采样点数为1024点,则可以分辨到1Hz。1024Hz的采样率采样1024点,刚好是1秒,也就是说,采样1秒时间的信号并做FFT,则结果可以分析精确到1Hz,如果采样2秒时间的信号并做FFT,则结果可以分析精确到0.5Hz。如果要提高频率分辨率,则必须增加采样点数,也即采样时间。频率分辨率和采样时间是倒数关系。

使用特权

评论回复
6
hanzhen654|  楼主 | 2019-11-19 18:39 | 只看该作者
matlab 测试代码:
close all; %先关闭所有图片
Adc=2; %直流分量幅度
A1=3;   %频率F1信号的幅度
A2=1.5; %频率F2信号的幅度
F1=50; %信号1频率(Hz)
F2=75; %信号2频率(Hz)
Fs=256; %采样频率(Hz)
P1=-30; %信号1相位(度)
P2=90; %信号相位(度)
N=256; %采样点数
t=[0:1/Fs:N/Fs]; %采样时刻
%信号
S=Adc+A1*cos(2*pi*F1*t+pi*P1/180)+A2*cos(2*pi*F2*t+pi*P2/180);
%显示原始信号
plot(S);
title('原始信号');
figure;
Y = fft(S,N); %做FFT变换
Ayy = (abs(Y)); %取模
plot(Ayy(1:N)); %显示原始的FFT模值结果
title('FFT 模值');
figure;
Ayy=Ayy/(N/2);   %换算成实际的幅度
Ayy(1)=Ayy(1)/2;
F=([1:N]-1)*Fs/N; %换算成实际的频率值
plot(F(1:N/2),Ayy(1:N/2));   %显示换算后的FFT模值结果
title('幅度-频率曲线图');
figure;
Pyy=[1:N/2];
for i=1:N/2
Pyy(i)=phase(Y(i)); %计算相位
Pyy(i)=Pyy(i)*180/pi; %换算为角度
end;
plot(F(1:N/2),Pyy(1:N/2));   %显示相位图
title('相位-频率曲线图');

使用特权

评论回复
7
hanzhen654|  楼主 | 2019-11-19 18:44 | 只看该作者
原始信号

使用特权

评论回复
8
hanzhen654|  楼主 | 2019-11-19 18:44 | 只看该作者
FFT模值

使用特权

评论回复
9
hanzhen654|  楼主 | 2019-11-19 18:45 | 只看该作者
幅度-频率曲线

使用特权

评论回复
10
hanzhen654|  楼主 | 2019-11-19 18:45 | 只看该作者
相位-频率曲线

使用特权

评论回复
11
hanzhen654|  楼主 | 2019-11-19 20:26 | 只看该作者
STM32F1移植ST 的DSP官方库
先在STMF1上移植ST 的FFT官方库运行一下看一下效果,然而STM32F103毕竟不是STM32F4系列的处理器,对于一般的FFT运算程序还是比较缓慢的。官方提供了针对FFT的官方库,下载移植体验一下,下载完毕后,主要有四个文件:

使用特权

评论回复
12
hanzhen654|  楼主 | 2019-11-19 20:26 | 只看该作者
cr4_fft_256_stm32.s: Cortex-M3的256点基4复FFT优化。
cr4_fft_1024_stm32.s:Cortex-M3的1024点基4复FFT优化。
stm32_dsp.h:         头文件包含DSP函数的原型。
table_fft.h:           包含FFT计算所需的系数

使用特权

评论回复
13
hanzhen654|  楼主 | 2019-11-19 20:27 | 只看该作者
在正点原子的基础工程上进行移植DSP库,一共就4个文件,不难办吧。移植后的效果如下:

使用特权

评论回复
14
hanzhen654|  楼主 | 2019-11-19 20:27 | 只看该作者
添加头文件路径如下:

使用特权

评论回复
15
hanzhen654|  楼主 | 2019-11-19 20:27 | 只看该作者
在主函数main.c中直接调用stm32_dsp.h 会报错,提示不能打开stm32f1xx_hal.h,看来dsp库使用的是HAL库,这里我们把它改为标准库就不会报错啦!

使用特权

评论回复
16
hanzhen654|  楼主 | 2019-11-19 20:28 | 只看该作者
注释 #include "stm32f1xx_hal.h" 引用#include "stm32f10x.h",再次编译就没有错误啦!

使用特权

评论回复
17
hanzhen654|  楼主 | 2019-11-27 10:06 | 只看该作者
ST 的DSP官方库函数介绍和简单测试
1.提供STM32 单片机调用的FFT变换的64点、256点、2048点的函数如下:
/* Radix-4 complex FFT for STM32, in assembly  */
/* 64 points*/
void cr4_fft_64_stm32(void *pssOUT, void *pssIN, u16 Nbin);
/* 256 points */
void cr4_fft_256_stm32(void *pssOUT, void *pssIN, u16 Nbin);
/* 1024 points */
void cr4_fft_1024_stm32(void *pssOUT, void *pssIN, u16 Nbin);
pssIN:是输入采样的样本。
pssOUT:调用官方函数后,输出的傅里叶序列。
Nbin:样本的数量(采了多少个点)。
为什么是64、256、1024这个3个值?因为官方使用的是基4蝶形算法,简单理解要使用官方fft库函数,必须遵循采样点数Nbin,是4的n次方。

使用特权

评论回复
18
hanzhen654|  楼主 | 2019-11-27 10:07 | 只看该作者
2.提供了16位的IIR滤波器和FIR滤波器函数,具体如下:
/* FIR 16-bit filter in assembly */
void fir_16by16_stm32(void *y, void *x, COEFS *c, u32 N);
/* IIR filter in assembly */
void iirarma_stm32(void *y, void *x, u16 *h2, u16 *h1, u32 ny );
/* IIR filter in C */
void iir_biquad_stm32(u16 *y, u16 *x, s16 *IIRCoeff, u16 ny);

使用特权

评论回复
19
hanzhen654|  楼主 | 2019-11-27 10:07 | 只看该作者
3.此外还提供了一些PID相关的,这里暂且不深究
/* PID controller in C, error computed outside the function */
u16 DoPID(u16 Error, u16 *Coeff);
/* Full PID in C, error computed inside the function */
u16 DoFullPID(u16 In, u16 Ref, u16 *Coeff);
/* PID controller in assembly, error computed outside the function */
u16 PID_stm32(u16 Error, u16 *Coeff);

使用特权

评论回复
20
hanzhen654|  楼主 | 2019-11-27 10:08 | 只看该作者
4.傅里叶变换后,输出了个啥,咋用?我们需要有个基础知识,傅里叶变换的目的:求取幅频特性/相频特性,fft变换后,输出的是一个傅里叶序列(怎么用?)。傅里叶序列本身,不是我们能够肉眼分析的东西,我们还需要对傅里叶序列进行计算,求取幅频特性/相频特性序列。通过串口打印输出的方式先测试一下64点、256点、2048点的FFT函数。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

73

主题

1766

帖子

2

粉丝