笔者能力有限,如果文中出现错误的地方,还希望各位朋友能够给我指出来,我将不胜感激,谢谢~

引言

数字信号在我们生活中随处可见,自然而然地就会涉及到对于数字信号的处理,最为典型的一个应用就是示波器,在使用示波器的过程当中,我们会通过示波器测量到信号的频率以及幅值,同时我们也可以通过示波器对测量到的信号进行 FFT ,从而能够观察到待测信号的频谱,方便直观的看出信号的高频分量和低频分量,从而帮助我们去除信号中携带的噪声。而在嵌入式方面的应用,我们可以直接使用 DSP 芯片对信号进行处理,同时, ARM 公司推出的 Cortex-M4F 内核是带有 FPU ,DSP 和 SIMD 单元的,针对于这些单元也增加了专用的指令,指令如下图所示:

不同架构的指令集合

ARM 官方也对此专门做了一个 DSP 方面的库,方便用户调用。关于 Cortex M4 的信号处理本文暂不进行阐述,相反本文的对象是 Cortex M3 ,基于 STM32F103 的 FFT,而在上述图中,我们看到针对于 Cortex M3 来说,是不带 FPU 以及 DSP 的,那有如何来进行 FFT 呢?

FFT 的提出

在数字信号处理中常常需要使用到离散傅里叶变换(DFT),从而能够获取到信号的频域特征。尽管传统的 DFT 算法能够获取到信号的频域特征,但是算法计算量大,耗时长,不利于进行计算机实时对信号进行处理。因此才有了 FFT 的出现。需要强调的是,FFT 并不是一种新的频域特征获取方式,而是 DFT 的一种快速实现算法。FFT 之所以能够改善运算量,是因为其充分运用了 DFT 运算中的对称性和周期性,从而能够将运算量从 N^2 减少到 N*log2(N),其中 N 为待计算的序列的长度。当 N 非常大的时候,这种优化在时间维度上的提升是非常显著的。下面是关于 DFT 和 FFT 所需乘法次数的比较曲线。

FFT 算法与 DFT 算法的比较

FFT 变换之后和原始信号的对应关系

假设我们对一个波形进行了采样,采样了 N 个点,经过 FFT 之后,就可以得到 N 个点的 FFT 结果,每一个点就对应着一个频率点。这个点的模值,就是该频率下的幅度特性。具体的关系就是如果原始信号的峰值为 A ,那么 FFT 的结果的每个点的模值就是 A 的 N / 2 倍。而第一个点就是直流分量,它的模值是直流分量的 N 倍。而每个点的相位就是在该频率下的信号的相位,第一个点表示的是直流分量,也就是 0 HZ的点,而最后一个点 N 的再下一个点(实际这个点是不存在的),也就是 N+1 个点则表示的是采样频率 Fs,这中间被 N - 1 个点平均分成 N 等份,每一个点的频率依次增加。也就是如果要计算某个点的频率,那么就只需要这样计算即可:Fn = (n - 1) * Fs / N。
从上述所展示的公式,我们可以知道 Fn 所能够分辨的频率为 Fs / N,如果采样频率 Fs 为 1024Hz,采样点数为 1024 点,则可以分辨到 1 HZ。也就是说采样 1s 时间的信号并做 FFT ,则结果可以分析精确到 1 Hz,如果采样 2 s 时间的信号并做 FFT,则结果可以分析精确到 0.5 Hz,所以也就说明了一个道理,如果要提高频率分辨率,则必须增加采样点数,也就是采样时间,下面这张图更能够清晰地表示这种关系:

频率分辨率

将原信号变换之后的频谱的宽度与原始信号也存在一定的关系。根据 Nyquist采样定律,FFT 之后的频谱宽度最大只能是原始信号采样率的 1/2,如果原始信号的采样频率为 4GS/s,那么 FFT 之后的频宽最多只能是 2GHz,这还只是理想情况。所以也能够得出一个结论:时域信号的采样率乘上一个固定系数即是变换之后频谱的宽度,可以用如下所示的一张图清晰说明:

采样率与频谱宽度的关系

经过上述的分析,我们有了如下的结论:更高的频谱分辨率需要有更长的采样时间,更宽的频谱分布需要提高对于原始信号的采样率,那我们在实际的使用过程中,当然是希望频谱更宽,分辨率更加精确,那么示波器的长存储就是必要的。

F103 如何进行 FFT

FFT 汇编库介绍

在本文的开头叙述了 ARM Cortex M4 具有 FPU 以及 DSP 指令,同时 ARM 官方也出了 DSP 方面的库来进行数字信号处理方面的工作,那么针对于 ARM Cortex M3 的 STM32F103 又是如何进行 FFT 的呢,显然,如果我们用 C 语言直接编写 FFT 算法,那样子的效率是极其低下的,因此,本文采用的方法是 ST 官方汇编 FFT 库的应用,由于官网现在找不到这个软件包,可以在公众号后台回复 FFT 获取软件包。
简单介绍一下,这个库是由汇编实现的,而且是基 4 算法,所以实现 FFT 在速度上较快。如果 X[N]是采样信号的话,使用 FFT 时必须满足如下两条:

  • N 得满足 4^n (n = 1,2,3…),也就是以 4 为基数。

  • 采样信号必须是 32 位数据,高 16 位存实部,低 16 位存虚部(这个是针对大端模式),小端模式是高位存虚部,低位存实部,一般常用的是小端模式。

汇编 FFT 的实现主要包括以下三个函数:

  1. cr4_fft_64_stm32 : 实现 64 点 FFT

  2. cr4_fft_256_stm32: 实现 256 点 FFT

  3. cr4_fft_1024_stm32: 实现 1024 点 FFT

FFT 汇编库移植

将我们下载到的文件进行解压得到如下所示的文件:

解压得到的文件

进一步的我们需要将文件加入到我们的 keil 工程,加入工程之后的图如下所示:

汇编库添加

因为本文是针对于 256 点的 FFT ,因此只需要将cr4_fft_256_stm32 添加进来即可,加进来之后,再使用到 FFT 的文件里添加相关路径就可以。下面讲述具体的代码实例。

代码实例

FFT 计算幅值

首先我们定义采样的点数,以及 FFT 的输入数组,输出数组,以及各个谐波的幅值:

#define  NPT  256             /* 采样点数 */uint32_t lBufInArray[NPT];    /* FFT 运算的输入数组 */uint32_t lBufOutArray[NPT/2]; /* FFT 运算的输出数组 */uint32_t lBufMagArray[NPT/2]; /* 各次谐波的幅值 */

在上述中,FFT 的输出数组和各次谐波的幅值的数组中只有采样点数的一半,是因为 FFT 计算出来的数据是对称的,因此通常而言只取一半的数据。
下面是波形采样并进行 FFT 的代码:

void adc_sample(void){    for (i = 0; i     {        lBufInArray[i] = ADC_ConvertedValue[i];    }    cr4_fft_256_stm32(lBufOutArray,lBufInArray, NPT);    GetPowerMag();}

for循环里将 ADC 采集的数据存储到 FFT 运算的输入数组中去,在这里需要注意的是 STM32 是小端模式,因此采样数据是高位存虚部,低位存实部。紧接着就是调用汇编函数进行 FFT,FFT 运算之后,就进行幅值的计算,幅值的计算函数如下所示:

void GetPowerMag(void){    signed short lX,lY;    float X,Y,Mag;    unsigned short i;    for(i=0; i2; i++)    {        lX  = (miniscope.freqency.lBufOutArray[i] <16) >> 16;        lY  = (miniscope.freqency.lBufOutArray[i] >> 16);//除以32768再乘65536是为了符合浮点数计算规律        X = NPT * ((float)lX) / 32768;        Y = NPT * ((float)lY) / 32768;        Mag = sqrt(X * X + Y * Y)*1.0/ NPT;if(i == 0)                miniscope.freqency.lBufMagArray[i] = (unsigned long)(Mag * 32768);else            miniscope.freqency.lBufMagArray[i] = (unsigned long)(Mag * 65536);    }}

上述代码中,lx 和 ly 的计算中,分别取的是FFT 的输出数组的高位和低位。进一步的,在计算 x 和 y 的时,除以 32768 是为了符合浮点数计算规律,至于为什么要进行浮点化,是因为浮点化就好像 10 进制里面的科学计数法。32768 = 2 的 15 次。除以 32768 也就是去除了浮点数后面的那个基数,只剩下前面的。比如 1991 改写成 1.991 * 10 的三次幂,除以 10 的三次方,只剩下 1.991,方便于下面的运算。而在后面又乘以 32768 和 65536 是因为要恢复到原先数据的大小,为什么下标为 0 的乘以 32768,而大于 0 的乘以 65535,是因为下标为 0 的代表的是直流分量,而剩余的是求出的乘以 2 才是实际模值。

FFT 计算频率

在本文的前面,笔者给出了这样一个公式用来计算 FFT 变换之后每个点对应的频率:Fn = (n - 1) * Fs / N
N 是采样的点数,Fs 是采样频率,采样点数已经知道,还剩下采样频率未知,采样频率说白了也就是采样一个点的时间,也是 1 s 钟采样的点数,而这个该怎么确定呢。现有两种方法,第一种方法是在单片机进行 ADC 采集时,通过延时的方法每隔一段时间进行读取转换得到的数据,而这个延时的时间就是采样频率,这样听起来略显粗糙。另一种比较精确的方法,是通过 DMA + TIM 的方法,也就是通过 TIM 产生 PWM ,通过 PWM 触发 ADC 进行采集,这个时候,PWM 的频率也就是 ADC 的采样率,只需要控制 PWM 的频率就可以控制 ADC 的采样率,采集的数据通过 DMA 搬运至内存,当采样的点数达到规定的采样点数时,触发 DMA 中断,在中断里给出数据处理的信号,进一步进行 FFT,具体的原理及代码参考笔者的这篇文章:STM32 定时器触发 ADC 多通道采集,DMA搬运至内存。下面是一个简单的代码示例:

void wave_frequency_calculate(void){    sample_frequency = 72000000.0 / (float)((sample_arr + 1) * (sample_psc + 1));    wave_frequency = frequency_max_position * sample_frequency / NPT;}

上述第一行代码是根据公式计算 pwm 的频率的公式,而 pwm 的频率也就是我们所需要的的采样频率。第二条代码中的 frequency_max_position 是除了直流分量幅值最大的点在数组中的位置,而这个点所对应的频率也就是我们采样波形的频率,至此,我们就计算出了采样波形的频率。

结论

上述就是关于 STM32F103 中实现 FFT 的一个基本方法,通过 FFT 计算出了波形的频谱,能够在不借助 DSP 芯片的前提下比较快的实现了 FFT ,对我们在 F103 平台上进行信号处理提供了很大的帮助,这就是本次分享的内容啦。

如果觉得我的文章对你所有帮助,欢迎转发,点赞,再看三连呀~

wave文件 fft_STM32F103 如何实现 FFT?相关推荐

  1. 使用python写Wave文件

    1.Wave文件   WAV是Microsoft开发的一种声音文件格式,虽然它支持多种压缩格式,不过它通常被用来保存未压缩的声音数据(PCM脉冲编码调制).WAV有三个重要的参数:声道数.取样频率和量 ...

  2. Wave 文件(5): 获取 Wave 文件的格式信息

    装载格式信息的结构有: TWaveFormat = wFormatTag: Word;nChannels: Word;nSamplesPerSec: DWORD;nAvgBytesPerSec: DW ...

  3. Delphi多媒体设计之播放WAVE文件(API)

    多媒体程序设计是一个名不符实的词组,其道理就是多媒体程序设计包含着广泛的可能性,它尤其包括了Wave音频.MIDI音频.AVI视频和动画等.不要将多媒体程序设计与游戏程序设计混淆了. 游戏设计自然包含 ...

  4. wave文件(*.wav)格式、PCM数据格式介绍

    音频简介 经常见到这样的描述: 44100HZ 16bit stereo 或者 22050HZ 8bit mono 等等. 44100HZ 16bit stereo: 每秒钟有 44100 次采样, ...

  5. 嵌入式 wave文件(*.wav)格式、PCM数据格式收藏

    1.音频简介 经常见到这样的描述: 44100HZ16bit stereo 或者 22050HZ 8bit mono 等等. 44100HZ16bit stereo: 每秒钟有 44100 次采样, ...

  6. C语言里的out函数,c语言 vc 用waveout函数写wave文件播放器

    用WaveOut函数写wave文件播放器 要炒菜的话,就得先准备工具,如锅.铲子.炉灶等.对程序来说,就是各种函数的应用.WaveOut函数在windowsAPI中属于低阶接口,用来播放的话需要用到下 ...

  7. 【C语言】Wave文件处理

    WAVE 文件作为多媒体中使用的声音波形文件格式之一,它是以RIFF(Resource Interchange File Format)格式为标准的. 1. 大小端对齐问题 大小端是不同的对于数据在内 ...

  8. ADPCM WAVE文件的压缩与解压缩

    一.WAVE文件   WAVE文件是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav& ...

  9. C# 获取wave文件信息【转】

    public class WaveHelper{/// <summary>/// 数据流/// </summary>private Stream m_WaveData;priv ...

最新文章

  1. 【iOS】NSDate分类,获得中国农历
  2. mysql 5.7 marriadb_CentOS7下安装MySQL
  3. fifo java_java linux fifo文件通信
  4. Android 设置Activity透明
  5. 【Java报错】MultipartFile 类型文件上传 Current request is not a multipart request 问题处理(postman添加MultipartFile)
  6. 目标检测--Rich feature hierarchies for accurate object detection and semantic segmentation(CVPR 2014)
  7. Python字典和集合
  8. Echarts多个坐标轴多组/一组数据 - 温度降水量示例
  9. vue保存图片到手机相册_手机照片误删了怎么找回?这三个方法轻松搞定,亲测有效...
  10. 全局系统性地把握客户感知-建立VOC
  11. VC 2012 visualstudio的项目属性表 .props文件
  12. 《路由协议与交换技术》重点知识总结(路由交换知识点)
  13. c++中getline()函数用法与坑
  14. 【旋转摆正验证码】知苗易约小程序旋转摆正验证码识别——识别解绝方法
  15. 深入理解计算机系统_3e 第四章家庭作业(部分) CS:APP3e chapter 4 homework
  16. 浏览器无法上网解决方案
  17. 《MySQL是怎么样运行的》读书笔记一 数据页+索引
  18. centos7常用命令详解
  19. java刷机教程,小米Mix2s刷机教程
  20. How to caching Global data in on-chip (level 1) cache in Morden GPU

热门文章

  1. python打印字节流_Python 调用系统命令的模块 Subprocess
  2. Java菜鸟教程math类_Java Number Math 类
  3. 二分搜索及其扩展(循环递增数组的搜索)
  4. ajax请求, 前后端, 代码示例
  5. 大话目标检测经典模型(RCNN、Fast RCNN、Faster RCNN)
  6. js事件冒泡与捕捉解析
  7. Codeforces Round #379 (Div. 2) E. Anton and Tree
  8. APScheduler —— Python化的Cron
  9. Java用JSONObject-lib来解析json串
  10. 利用dns解析来实现网站的负载均衡