ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32 FFT实战指南:从原理到CMSIS-DSP库移植与频谱分析

STM32 FFT实战指南:从原理到CMSIS-DSP库移植与频谱分析 简介面向需要在STM32上实现快速傅里叶变换的嵌入式开发者这套源码基于ARM Cortex-M内核提供从CMSIS-DSP库调用、64点FFT计算到结果整理的完整工程实现适用于信号采集、频谱分析等实时处理场景。资源总计40个文件、约275KB主要包含C源码、头文件、汇编启动文件以及HEX、AXF等烧录调试文件uvproj等工程配置附件也一并打包解压后可直接用开发环境打开工程对照学习或二次开发同时可从压缩包中清晰看到工程目录结构与编译中间文件。无论是单片机入门者还是需要快速集成FFT模块的工程师都能从中获得直接的代码参考、工程配置思路与排错提示避免从零搭建工程。目前已有370人浏览学习。压缩包内还附带试验波形Excel、工程构建报告与备份文件便于检查FFT输出效果和排错也能帮助开发者理解STM32F10x上FFT底层库的调用细节并结合中断、DMA等外设设置快速迁移到自身项目中。1. 先搞清楚一件事STM32上做FFT到底图什么很多做STM32的工程师第一次碰FFT都是从网上搜STM32 FFT源码开始的。我也不例外——当时是为了做一个音频频谱显示的项目需要在MCU上把ADC采进来的波形数据变成频谱上板调试的时候才发现FFT源码好找但真正能让它在STM32上跑起来、结果还对得上里面全是细节。这篇就按我踩坑的顺序把STM32上做FFT从选方案、配环境、写代码到调试排查的完整过程捋一遍适合刚接触数字信号处理的单片机开发者参考。1.1 用调音师类比一次搞懂FFT原理FFT全称是快速傅里叶变换公式层面的东西随便一本数字信号处理教材都有但绝大多数单片机工程师不需要从公式推导开始。我更愿意把它类比成一个调音师你给他一段混合了各种乐器的录音时域波形他能在几秒钟内告诉你里面有哪些音高、每个音高响度多大频域信息。FFT干的就是这件事只不过它的输入是ADC连续采样得到的一串离散数值输出是一组代表不同频率分量强度的数组。对单片机而言理解FFT不需要啃完整个推导过程抓住几个能直接指导代码编写的结论就够用。第一输入是时域采样序列点数必须是2的整数次幂至少对经典的基2 FFT是这样库函数一般要求点数满足这个条件。第二输出是对称的实信号做FFT后后半段是前半段的镜像实际只取前N/2个频率点。第三频率分辨率等于采样率除以点数这个公式决定你能否区分两个靠得很近的频率成分。这三个结论贯穿整个开发过程后面每一步都会用到。1.2 哪些项目真正需要STM32跑FFTSTM32做FFT的常见场景我接触过的就有这么几类。音频频谱分析最常见比如频谱灯、音乐节奏灯、简易声控设备用ADC以8kHz或16kHz采样做256到1024点FFT再把幅值映射到LED柱状条。振动监测是工业场景的大头用加速度传感器采集振动信号FFT后找特征频率判断轴承磨损、转子不平衡。电力参数分析也经常出现测量交流电压电流的谐波含量计算总谐波失真THD这类项目通常用16位ADC加高精度采样。另外还有家电里的洗衣机不平衡检测、风扇动平衡校准以及大量的基于STM32的XXX检测系统毕业设计本质都是采集信号然后FFT找峰值。记住一点FFT本身只是工具真正值钱的是你对自己的信号有多了解——采样率该设多少、点数该取多大、要不要加窗这些决定了结果能不能用。下面我按实际开发顺序把这几个决策环节逐个讲透。2. FFT源码从哪来三条路线让新手少走弯路搜STM32 FFT源码你大概会看到三类东西。第一类是直接复制粘贴的裸代码网上流传的各种FFT源码.rar大多属于这类代码很短几百行自己实现了基2蝶形运算和位倒序。优点是直观、没有依赖打开就能看缺点是你得自己管理旋转因子表而且浮点运算在Cortex-M0/M3上慢得让人怀疑人生。这类代码适合学习原理不适合直接上产品。第二类是ARM官方的CMSIS-DSP库这是我最推荐的一条路。它不是某个网友写的零散代码而是ARM针对自家Cortex-M内核深度优化过的DSP函数库FFT只是其中一部分。它里面既包含标准实现也包含针对M4/M7内核做了优化的版本比如利用SIMD指令、硬件除法器。用这套库你不需要懂蝶形运算怎么实现只需要知道怎么配置实例、调用函数、解析结果即可而且它经过大量验证结果可信度远高于自己写的。第三类是第三方DSP库比如KissFFT、FFTW的嵌入式裁剪版。这类库跨平台能力强但针对Cortex-M的优化普遍不如CMSIS-DSP到位还引入额外代码维护成本。除非你有特殊需求比如要做超过4K点的FFT否则在STM32上没有理由放着官方的不用。三条路线的取舍我整理成了下表方案上手难度性能代码可读性适用场景自写基2 FFT中低M3/M0上浮点慢高学习原理、教学演示CMSIS-DSP低高中绝大多数实际项目第三方通用库中中中需要跨平台复用2.1 CMSIS-DSP源码结构和关键文件CMSIS-DSP的源码可以从GitHub上的ARM-software/CMSIS-DSP仓库获取也可以在STM32CubeMX的Pack管理器里直接勾选安装。源码按函数类别分目录组织Include/arm_math.h是总头文件所有数据结构和函数原型都在这必须包含Source/TransformFunctions是FFT、DCT等变换函数这是我们用的主体Source/CommonTables是旋转因子表FFT的旋转因子是预先生成好的常数表编译时直接烧进Flash这也是它运算速度快的原因之一Source/StatisticsFunctions提供均值、方差、RMS等统计函数做频谱分析时经常顺手用上。关键的数据结构是arm_rfft_fast_instance_f32它保存了FFT长度和旋转因子表。你只需要调用arm_rfft_fast_init_f32进行初始化之后每次处理一帧数据调用arm_rfft_fast_f32。不要自己去动实例内部的字段官方API设计已经帮你屏蔽了实现细节动内部结构反而容易踩坑。2.2 为什么ARM_MATH_CM4这个宏能决定性能移植CMSIS-DSP时有个最容易漏的步骤定义内核宏。ARM_MATH_CM4这个宏告诉库你运行在Cortex-M4内核上库会启用针对M4的优化路径如果不定义库默认走通用路径性能差一大截。如果你用的芯片没有FPU比如F1系列是M3内核就不需要FPU宏但浮点FFT在M3上会很慢建议改用q15或q31定点版本后面会细说。很多人编译通过、运行也正常就是没意识到性能不对直到发现1024点FFT耗时跟理论值差好几倍才想起查宏定义。我建议在Keil或CubeIDE里打开预处理器输出编译后确认ARM_MATH_CM4确实被定义了避免这种隐性坑。3. STM32开发环境搭建与CMSIS-DSP库移植指南3.1 Keil、CubeIDE和VSCode怎么选STM32开发环境的选择非常多Keil MDK、STM32CubeIDE、VSCode加CMake工具链甚至IAR都能跑。选哪个不重要重要的是你要能把CMSIS-DSP库正确加进工程。Keil MDK是国内用得最多的但要注意Keil5的Pack机制把芯片支持拆开了装好Keil之后还必须通过Pack Installer安装对应芯片系列的设备支持包比如STM32F1系列要装Keil.STM32F1xx_DFPSTM32F4系列要装Keil.STM32F4xx_DFP。习惯用Keil4的老手第一次转Keil5时经常在这里卡住编译报target not created或找不到芯片头文件。如果你之前装的是兼容C51和STM32的Keil5版本想同时开发两种芯片不需要重装只要在Pack Installer里把ARM相关包装上再到License管理里确认ARM Compiler可用就行。STM32CubeIDE是ST官方的免费IDE基于Eclipse集成了CubeMXHAL库和CMSIS-DSP都能直接管理。VSCode配EIDE插件或PlatformIO也是不错的选择适合喜欢命令行编译的老手。我个人的习惯是快速验证电路用CubeIDE长期维护的工程用KeilVSCode用来写代码和看版本差异。3.2 把DSP库加进工程的五个步骤不管用什么IDE把CMSIS-DSP加进工程的核心步骤都一样。第一步把CMSIS-DSP源码复制到工程目录建议放在ThirdParty/CMSIS-DSP这种独立文件夹下别跟自己的代码混在一起。第二步把Include目录加入头文件搜索路径也就是Include Paths配置。第三步把Source/TransformFunctions和Source/CommonTables两个目录下的.c文件加入编译如果只需要FFT这两个目录就够其他目录不加能省一大截编译时间。第四步在编译器选项里开启快速浮点模式Keil里是--fast_mathGCC里是-ffast-math能显著提升浮点计算速度代价是浮点精度略降对FFT应用完全能接受。第五步确认宏定义使用ARM_MATH_CM4或ARM_MATH_CM7对应你的内核在Keil里勾选Use FPU如果芯片有FPU。我见过很多新手把整个CMSIS-DSP的所有源文件一股脑加入工程编译一次等五分钟还没开始跑就已经失去耐心了。按需添加文件才是正确的做法。3.3 最小可运行的FFT代码模板下面这段是我在STM32F4上用的最小模板功能是把ADC采集的1024个点做FFT输出各个频率点的幅值。#include arm_math.h #include arm_const_structs.h #define FFT_SIZE 1024 #define SAMPLING_RATE 8000.0f static arm_rfft_fast_instance_f32 s_fftInst; static float32_t s_input[FFT_SIZE]; static float32_t s_fftBuf[FFT_SIZE * 2]; static float32_t s_mag[FFT_SIZE / 2]; void FFT_Init(void) { arm_rfft_fast_init_f32(s_fftInst, FFT_SIZE); } void FFT_Process(const float32_t *adcData) { uint32_t i; for (i 0; i FFT_SIZE; i) { s_input[i] adcData[i]; } /* 正变换时域转频域输出复数交错排列 */ arm_rfft_fast_f32(s_fftInst, s_input, s_fftBuf, 0); /* 计算幅值对复数取模 */ arm_cmplx_mag_f32(s_fftBuf, s_mag, FFT_SIZE / 2); /* s_mag[0]是直流分量s_mag[k]对应第k个频率点 */ }几个容易被坑的细节必须说清楚。arm_rfft_fast_f32的输出缓冲区长度必须是输入的两倍因为输出按实部、虚部、实部、虚部交错排列s_fftBuf[0]是直流分量的实部s_fftBuf[1]是直流分量的虚部理论上为0。arm_cmplx_mag_f32的第三个参数是复数个数也就是FFT_SIZE/2不是FFT_SIZE。实际有用的频率点只有前N/2个第k个点对应的频率是k乘以采样率除以N。比如8kHz采样、1024点频率分辨率约7.81Hzs_mag[128]对应的频率就是128乘8000除以1024等于1000Hz。想要真实的物理幅值还需要对s_mag做归一化非直流分量除以N/2直流分量除以N。这点很多教程不讲导致很多人拿到的幅值跟MATLAB对不上。归一化这一步建议单独封装一个函数后面排查问题时会省很多事。4. FFT参数计算与MATLAB验证采样率、点数和频率分辨率4.1 三个参数的计算方法FFT最常见的错误不是代码写错而是参数选错。采样率、点数、频率分辨率这三个参数必须一起考虑。采样率决定你能分析的最高频率根据奈奎斯特采样定理可分析的最高频率是采样率的一半。点数决定频率分辨率频率分辨率等于采样率除以点数也决定计算量和内存占用。频率分辨率决定你能区分多近的两个频率。举个例子你想检测市电的谐波最高分析到2kHz也就是40次谐波采样率至少取4kHz为了留裕量工程上常取8kHz或更高。如果频率分辨率要做到1Hz点数就需要8000向上取2的幂就是8192。8192点的浮点FFT在F4上大概需要几十毫秒实时性要求高的系统就得权衡取舍要么降分辨率要么用硬件加速要么改定点数。我的经验是先算带宽再定分辨率最后反推点数不要一上来就拍脑袋取1024点。很多振动监测项目其实只需要分析0到200Hz取512点、采样率400Hz就足够了根本不用跑到几千点。另外别忘了定时器触发ADC采样时用内部RC振荡器做主时钟容易有偏差如果发现频谱整体偏移先查实际采样率是不是跟设定值一致。4.2 加窗的作用和代码实现另一个新手经常忽略的是加窗。FFT处理的是截断后的有限长信号截断相当于给信号乘了一个矩形窗矩形窗的频谱旁瓣很高会让一个强频率成分泄漏到邻近频率点表现为频谱拖尾或出现假峰。解决的办法是在FFT之前给数据乘一个旁瓣更低的窗函数最常用的是汉宁窗代价是主瓣变宽、频率分辨率略微下降但换来的是旁瓣大幅衰减对想找多个频率成分的应用来说非常划算。static float32_t s_window[FFT_SIZE]; void Window_Init(void) { for (uint32_t i 0; i FFT_SIZE; i) { s_window[i] 0.5f - 0.5f * arm_cos_f32(2.0f * PI * i / (FFT_SIZE - 1)); } } void FFT_ProcessWithWindow(const float32_t *adcData) { for (uint32_t i 0; i FFT_SIZE; i) { s_input[i] adcData[i] * s_window[i]; } arm_rfft_fast_f32(s_fftInst, s_input, s_fftBuf, 0); arm_cmplx_mag_f32(s_fftBuf, s_mag, FFT_SIZE / 2); }判断要不要加窗就看你的信号是不是整周期截断。如果是锁相环同步采样能做到整周期截断不加窗直接FFT也是对的如果只是定时器触发ADC自由采样那必须加窗。加窗之后信号能量会损失一部分需要做能量校正不同窗函数的校正系数不同这在测量类项目里很重要纯看频谱形状就不用太纠结。4.3 用MATLAB验证STM32的计算结果移植完代码第一件事不是上板跑而是先在MATLAB里把算法验证一遍。这里就涉及很多人搜的如何将csv导入到matlab中进行fft仿真。做法很简单在STM32上开一个调试模式把ADC原始采样值通过串口或J-Link RTT导出来存成CSV文件然后在MATLAB里用csvread或importdata读进来用同样的采样率和点数做FFT跟板子上跑的结果对比。data csvread(adc_capture.csv); % 读CSV文件中的ADC采样值 Fs 8000; % 采样率必须与STM32一致 N length(data); % 点数一般为FFT_SIZE data data - mean(data); % 去直流分量 Y fft(data, N); P2 abs(Y / N); % 双侧频谱归一化 P1 P2(1:N/21); % 取单侧 P1(2:end-1) 2 * P1(2:end-1); % 单侧频谱幅值补偿 f Fs * (0:(N/2)) / N; % 构造频率轴 plot(f, P1); xlabel(频率 (Hz)); ylabel(幅度);对不上的时候优先查三个地方采样率是否跟实际一致、数据是否加了窗、幅值是否做了同样的归一化。我遇到最多的不是代码问题而是ADC的实际采样率跟预期不一致比如用了内部RC振荡器没校准8kHz实际是7.6kHz频谱整体就偏了。用外部有源晶振或者校准过的HSE能彻底避免这个问题。5. STM32 FFT实战问题排查从ST-Link报错到结果校准5.1 ST-Link连接失败与no stm32 target found怎么办这个报错我估计接触过的人不少。用ST-Link或OpenOCD调试时控制台弹出一句error: no stm32 target found!第一反应通常是线接错了但其实原因有好几类排查顺序很重要。先检查接线。SWD接口只需要四条线SWDIO、SWCLK、GND外加3.3V供电。SWDIO和SWCLK接反是最高频的错误各种转接板的引脚顺序五花八门最好对照原理图逐个量。再检查供电和复位目标板必须独立供电或由调试器供电共地必须可靠。按住复位键再点连接如果这样能连上说明程序把SWD引脚复用掉了或者进入了低功耗模式。接着检查调试器速率在IDE里把SWD时钟从4MHz降到1MHz甚至500kHz排线过长或接触不良时高速SWD很容易失败。还有一种情况是芯片的读保护级别被设置成Level 1或更高调试器无法正常访问报错信息里会提示关于debug authentication的内容。解决办法是用STM32CubeProgrammer做一次选项字节复位或全片擦除但要注意全片擦除会抹掉程序。如果以上都试过还不行把ST-Link接到另一块板上测一下排除调试器本身损坏。5.2 FFT结果不对的排查顺序代码编译通过、能下到板子上不代表FFT结果正确。我见过太多人对着频谱图发呆这里给一个排查清单。先不接真实信号用DAC或外部信号源灌一个已知频率的正弦波比如1kHz、1Vpp这样你能预知频谱上应该在哪个位置出现峰值排查效率最高。然后确认峰值位置对不对1kHz信号在8kHz采样、1024点FFT下峰值应在第128个频率点附近如果偏了基本是采样率算错。再确认幅值对不对1Vpp的正弦波有效值约0.354VFFT归一化后峰值对应的幅值应该和理论值接近差一个数量级通常是归一化系数搞错。最后确认有没有失真的杂散峰如果除了主峰还有很多不该存在的峰先查电源噪声、ADC参考电压是否稳定再看是不是窗函数没加上。用MATLAB对同一数据处理一遍对比两边一致说明算法正确不一致说明理解有误。一个很隐蔽的坑是ARM_MATH_CM4这个宏没定义库走了通用路径结果精度会下降但不会高到你一眼看出来。5.3 性能和存储优化建议最后聊性能。Cortex-M4和M7带FPU跑f32浮点FFT没问题但F0、F1这种M0/M3内核没有FPU浮点运算极慢1024点f32 FFT可能要几十到上百毫秒。这种场合要改用q15或q31定点版本。CMSIS-DSP提供对应的定点接口比如arm_rfft_init_q15、arm_rfft_q15。定点版本要求输入是Q格式的定点数ADC的12位数值需要先左移到Q15格式再送入定点FFT速度能比浮点快好几倍代价是动态范围受限对归一化后的信号完全够用。存储上1024点f32的FFT需要输入4KB、复数输出8KB、幅值数组2KB再加上窗函数4KB总共约18KB的RAM。对F4的192KB RAM来说没压力但对只有16KB RAM的小芯片就要注意了。可以考虑用in-place计算让输出覆盖输入或者把窗函数从float32换成q15再或者把点数降到512或256。另外一个很实用的优化是如果你只需要看某几个频段的幅值不必每次做全频谱FFT可以先用滤波器把信号分频段降低采样率后再FFT计算量能砍掉一个数量级。最后说个我一直建议周围同事的做法FFT源码拿到了别急着往工程里塞。先花半天时间用MATLAB或Python把你要分析的信号从头到尾仿真一遍确定好采样率和点数再把定下来的参数套到STM32的代码里。这个顺序能省下的调试时间远比你想的多。我自己后来的几个项目都是这么干的几乎没有再为FFT结果对不上发过愁。本文还有配套的精品资源点击获取
返回列表