ARTICLE DETAIL

资讯详情

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

STM32F407基于CMSIS-DSP实现FFT频谱分析与IFFT信号还原详解

STM32F407基于CMSIS-DSP实现FFT频谱分析与IFFT信号还原详解 搞嵌入式信号处理的朋友应该都有体会数据采集上来是一串时域波形光看曲线根本看不出门道。FFT能把信号按频率拆开IFFT又能把处理完的频域数据还原回时域。STM32F407这颗Cortex-M4F内核的芯片主频168MHz还带FPU配合ARM官方的CMSIS-DSP库用arm_rfft_fast_f32做实数信号的FFT/IFFT非常顺手。这篇文章把我在F407上完整跑通FFT频谱分析、再通过IFFT还原信号的过程整理一遍附上可以直接抄进工程的全套代码给准备做振动监测、音频分析、电力谐波检测这些方向的朋友一个参考。1. 项目概述与方案选型1.1 这个项目到底要解决什么问题很多传感器信号都是多个频率分量的叠加。比如电机振动信号里有转频、轴承故障特征频率、齿轮啮合频率还掺着各种随机噪声直接看时域波形只能看到一个混沌的振荡很难判断设备状态。FFT能把叠加在一起的频率成分拆开让你清楚地看到信号里有哪些频率、各频率的幅度有多大。反过来做滤波或者频域特征提取之后处理完的频域数据还得还原成时域信号这就是IFFT的工作。在STM32F407上做这套流程价值在于能把原来要送到上位机或者云端处理的信号分析任务直接在嵌入式端完成。这样既节省了数据传输带宽也提升了系统的实时响应能力。我最早是在一个电机轴承故障监测项目里用到这套方案的传感器采回振动信号MCU做FFT提取特征频率判断轴承有没有磨损。后来做音频频谱显示、电力谐波分析底层都是同一套东西——先FFT拆频率后IFFT还原信号。1.2 为什么选CMSIS-DSP而不是自己写FFT有些朋友觉得FFT不就是蝶形运算嘛自己写一个也就几百行。确实标准的基2 FFT算法并不复杂但自己写有两个硬伤。第一是效率CMSIS-DSP内部针对Cortex-M4F做了汇编级优化充分使用了FPU单精度浮点指令和SIMD并行能力你用C语言手写的版本很难跑过它。第二是可靠性和维护成本CMSIS-DSP是ARM官方出品边界条件处理完善全局测试覆盖充分自写代码在工程里出了问题排查起来非常烧时间。CMSIS-DSP库里的arm_rfft_fast_f32是专门针对实数信号设计的FFT函数。我们ADC采回来的数据都是实数如果直接用复数FFT去算有一半的计算量是浪费的。rfft通过把实数序列重排成复数序列、用一次复数蝶形完成变换计算效率接近同样点数复数FFT的一半。再加上名字里的fast说明这个变体用的是分解后的高效算法所以在F407上做实数信号频谱分析它几乎是唯一值得考虑的选择。1.3 FFT/IFFT整体流程怎么拆整个信号还原的流程可以拆成四步第一步准备或采集时域信号第二步初始化FFT实例并执行正向FFT第三步直接或者经处理后把频域数据送给IFFT第四步把IFFT输出缩放回去得到还原的时域信号。这个流程看似简单实际工程里藏着很多细节比如FFT输出数据的排列方式、IFFT结果为什么要除以点数N、数组长度怎么分配、内存对齐怎么处理这些任何一个搞错出来的数据就是乱的。本项目的核心验证逻辑很简单构造一个已知频率和幅值的合成信号先做FFT看频谱峰值是否落在正确频率上、幅值是否正确再把FFT输出直接丢给IFFT还原后的时域信号应该和原始信号高度一致。这样既能验证FFT功能正常也能验证IFFT还原功能正常。后面扩展去噪、滤波、特征提取等高级应用都是在这个基础上加一层频域处理而已。2. 环境准备与库配置2.1 需要准备哪些硬件和软件硬件上我用的是一块STM32F407VET6核心板板上25MHz晶振通过PLL倍频到168MHz外接CH340串口芯片用于打印调试数据。用其他F407开发板也完全没问题核心资源都一样Cortex-M4F内核、192KB RAM、1MB Flash、FPU单元。这里要提醒一句有朋友想用Proteus做仿真但Proteus对F407的器件模型支持很差很多版本压根找不到STM32F407。我的建议是直接上真板子F407最小系统板几十块就能买到而且FFT浮点运算在仿真器里的速度惨不忍睹光等结果就够你崩溃的。软件上我用STM32CubeMX 6.x生成工程框架配Keil MDK 5编译调试。CubeMX用来配置系统时钟、串口、GPIO这些基础外设生成HAL库工程然后手动把CMSIS-DSP库加进来。为什么不全部手写寄存器F407这种寄存器数量庞大的芯片手动配时钟树非常容易出错用CubeMX可以图形化确认每个外设的时钟源和引脚分配生成的启动文件和链接脚本也都是验证过的能省下大量排查低级错误的时间。2.2 CMSIS-DSP库的三种添加方式第一种是CubeMX图形化添加。在Pinout Configuration页面找到Middleware and Software Packs把CMSIS-DSP勾选上生成代码后头文件路径会自动加到工程里。但这里有个坑CubeMX并不会自动把预编译库文件链接进去你需要去Drivers/CMSIS/Lib/GCC/目录下找到对应编译器版本的库文件手动添加进链接列表。第二种是手动拷贝。从STM32CubeF4固件包里找到Drivers/CMSIS/DSP目录整个拷进工程源码目录然后在编译器里添加include路径同时把库文件加进链接选项。Keil MDK对应的是Drivers/CMSIS/Lib/ARM/目录下的arm_cortexM4lf_math.libGCC环境用Lib/GCC/下的libarm_cortexM4lf_math.a。注意文件名里有lf表示Little Endian FloatF407单精度浮点运算就选这个。第三种是直接用Keil的Manage Run-Time Environment勾选CMSIS-DSP相关组件工具链会自动添加文件。但生成的工程关联的是Keil安装目录下的文件代码移植到别的机器或者CI环境时经常丢依赖我不太推荐团队项目用这种方式。2.3 编译选项和宏定义这部分非常关键做不好后面全是坑。首先是FPU要让F407的硬件浮点单元真正生效必须在编译器里开启硬浮点选项。Keil MDK在Options for Target - Target页面把Floating Point Hardware选成Single Precision也就是硬件单精度。GCC环境下要加-mfpufpv4-sp-d16 -mfloat-abihard。其次需要在C/C Compiler的Define里加上两个重要的宏ARM_MATH_CM4和__FPU_PRESENT1。少了这两个宏CMSIS-DSP的数学函数会退回到纯软件计算模式效果等同没开FPU性能直接腰斩再腰斩。ARM_MATH_CM4告诉库当前运行在Cortex-M4内核上__FPU_PRESENT告诉库目标芯片带FPU库内部会根据这些宏选择对应的优化分支。第三是优化等级。调试阶段可以用-O0但跑FFT这种计算密集任务建议至少-O2。我实测-O2比-O0性能提升接近一倍算法使用说明里也明确要求在高优化等级下运行才有最佳表现。另外建议开启MicroLIB可以显著减少printf等库函数对RAM和Flash的占用。2.4 核心APIarm_rfft_fast_f32用法剖析arm_rfft_fast_f32的函数原型是这样的void arm_rfft_fast_f32( const arm_rfft_fast_instance_f32 *S, float32_t *p, float32_t *pOut, uint8_t ifftFlag );参数S是一个实例结构体指针需要通过arm_rfft_fast_init_f32初始化arm_status arm_rfft_fast_init_f32( arm_rfft_fast_instance_f32 *S, uint16_t fftLen );fftLen支持32、64、128、256、512、1024、2048、4096这八个点数。初始化函数的返回值是ARM_MATH_SUCCESS表示成功ARM_MATH_ARGUMENT_ERROR表示传入的点数不支持。p是输入缓冲pOut是输出缓冲ifftFlag控制方向0是正变换时域到频域1是逆变换频域到时域。最关键的是弄懂数据排列格式。正变换时输入是N个实数的时域序列输出是N2个float按复数交错格式排列。具体来说输出数组的第0和第1个元素是直流分量DC的实部和虚部第2和第3个元素是第一个频率点的实部和虚部依次类推直到第N和N1个元素是奈奎斯特频率分量的实部和虚部。由于实数FFT的对称性只需要保留从DC到奈奎斯特频率这N/21个频点。做IFFT时输入必须是这种同样格式的复数交错数组输出是N个实数的时域序列。正因为格式对齐所以可以直接把FFT的输出数组传给IFFT作为输入不需要额外转换。但注意一个大坑IFFT的结果默认是放大了N倍的需要每个元素除以N才是真实的时域幅值。这一点后面代码里会详细演示。3. 完整代码实现构造信号到FFT再到IFFT还原3.1 构造一个可验证的测试信号为了验证FFT和IFFT功能是否正常需要构造一个频谱特征明确的测试信号。我选了50Hz和120Hz两个正弦波叠加幅度分别是3.0和1.5采样率设为1000HzFFT点数1024。为什么这么选50Hz和120Hz在1000Hz采样率下对应频率分辨率为1000/1024约等于0.9766Hz两个频率间隔70Hz远大于频率分辨率FFT出来的频谱两个峰值分得很开肉眼就能确认结果对不对。构造信号的代码不复杂关键是使用arm_sin_f32这个CMSIS-DSP自带的正弦函数。它内部用查表加插值实现比标准C库的sinf快很多在循环里调用性能优势明显。当然也可以用math.h里的sinf只是这个测试节约一点是一点。实际工程里这一步一般是ADC采样获得真实数据。3.2 FFT执行与幅值计算调用arm_rfft_fast_f32执行正变换的代码很简洁一句话arm_rfft_fast_f32(fft_instance, input_signal, fft_output, 0);执行完fft_output里就是复数交错的频谱数据。但直接看这些复数值没有物理意义需要计算幅值。幅值谱的计算方法是对每个频点取实部和虚部的平方和开根号float32_t magnitude[FFT_SIZE / 2]; void Compute_Magnitude(float32_t *fft_output, float32_t *magnitude) { // DC分量幅值 绝对值(fft_output[0]) / N magnitude[0] fabsf(fft_output[0]) / FFT_SIZE; // 中间频点幅值 sqrt(real^2 imag^2) * 2 / N for (int i 1; i FFT_SIZE / 2; i) { float32_t real fft_output[2 * i]; float32_t imag fft_output[2 * i 1]; magnitude[i] sqrtf(real * real imag * imag) * 2.0f / FFT_SIZE; } // 奈奎斯特频率点的幅值 绝对值(fft_output[N]) / N这里略去不处理 }这里有个幅度缩放问题容易搞糊涂。FFT输出直接反映的是各个频率成分的能量但实际幅值大小和点数N有关。对于双边的频谱中间频点的真实幅度大约是模值乘以2除以N而DC分量和奈奎斯特频率分量由于没有对称的另一半幅度就是模值除以N。这个关系在做定量分析时一定要搞清楚否则你会看到50Hz正弦波幅度算出来是1500而不是3。3.3 IFFT还原与1/N缩放IFFT的调用同样简洁arm_rfft_fast_f32(fft_instance, fft_output, ifft_output, 1);执行完ifft_output里就是还原后的时域序列。但如前所述CMSIS-DSP的IFFT实现没有内置归一化结果整体放大了N倍。如果不做缩放你会看到还原后的信号是一条幅度3000多的正弦波而不是原始信号的3。所以在IFFT之后必须手动除以点数值for (int i 0; i FFT_SIZE; i) { ifft_output[i] / FFT_SIZE; }为什么要这么设计因为有些数字信号处理场景里需要连续做IFFT再FFT的循环把1/N的缩放延迟到最后一次性处理可以避免中间过程引入截断误差。CMSIS-DSP选择不做自动归一化把选择权留给使用者这个设计在工程上是合理的只是初学者容易在这里栽跟头。缩放完之后做误差检验计算原始信号和还原信号每个点的绝对误差取最大值。因为FFT和IFFT都是基于浮点运算的理论上存在微小的数值舍入误差但应该在一个非常小的量级。实测1024点FFT往返之后最大误差在1e-6级别说明整个链路精度完全达标。3.4 可以直接用的完整代码下面给出一个围绕本项目设计的比较完整的main函数概括了整个过程。为了方便演示我精简了硬件初始化部分把信号处理的核心流程完整呈现出来实际工程中你可以直接复用这些函数。#include arm_math.h #include stdio.h #include math.h #define FFT_SIZE 1024 #define SAMPLE_RATE 1000.0f #define PI 3.14159265358979f // 全局缓存数组 float32_t input_signal[FFT_SIZE]; // 原始时域信号 float32_t fft_output[FFT_SIZE 2]; // FFT输出复数交错格式 float32_t ifft_output[FFT_SIZE]; // IFFT还原后的时域信号 float32_t magnitude[FFT_SIZE / 2]; // 幅值谱 arm_rfft_fast_instance_f32 fft_instance; // 构造50Hz 120Hz复合信号 void Generate_Test_Signal(void) { for (int i 0; i FFT_SIZE; i) { float32_t t (float32_t)i / SAMPLE_RATE; input_signal[i] 3.0f * arm_sin_f32(2.0f * PI * 50.0f * t) 1.5f * arm_sin_f32(2.0f * PI * 120.0f * t); } } // 执行FFT正变换 void FFT_Transform(void) { arm_rfft_fast_f32(fft_instance, input_signal, fft_output, 0); } // 计算幅值谱 void Compute_Magnitude(void) { magnitude[0] fabsf(fft_output[0]) / FFT_SIZE; for (int i 1; i FFT_SIZE / 2; i) { float32_t real fft_output[2 * i]; float32_t imag fft_output[2 * i 1]; magnitude[i] sqrtf(real * real imag * imag) * 2.0f / FFT_SIZE; } } // 执行IFFT逆变换并做归一化缩放 void IFFT_Restore(void) { arm_rfft_fast_f32(fft_instance, fft_output, ifft_output, 1); // 关键步骤除以FFT点数还原真实幅值 for (int i 0; i FFT_SIZE; i) { ifft_output[i] / FFT_SIZE; } } // 验证还原误差 float32_t Check_Restore_Error(void) { float32_t max_error 0.0f; for (int i 0; i FFT_SIZE; i) { float32_t err fabsf(input_signal[i] - ifft_output[i]); if (err max_error) max_error err; } return max_error; } int main(void) { // 硬件初始化部分时钟、串口、GPIO等此处省略 // 初始化FFT实例 if (arm_rfft_fast_init_f32(fft_instance, FFT_SIZE) ! ARM_MATH_SUCCESS) { // 初始化失败通常是不支持的点数导致的 while (1); } // 生成测试信号 Generate_Test_Signal(); // FFT频谱分析 FFT_Transform(); Compute_Magnitude(); // 此时可遍历magnitude数组找到幅值最大的两个频点 // 理论上应分别在28Hz附近实际为50Hz对应索引51和 // 87Hz附近实际为120Hz对应索引122出现尖峰 // IFFT还原信号 IFFT_Restore(); // 检查还原误差 float32_t max_error Check_Restore_Error(); printf(Max restore error: %.8f\r\n, max_error); while (1) { // 实际工程中可以在这里周期性采集数据并处理 } }代码里有个细节要注意fft_output数组的长度必须是FFT_SIZE 2不是FFT_SIZE。我遇到过很多朋友在这里定义少了两个float结果是FFT执行完后面的变量被悄悄改写程序行为变得非常诡异。另外所有缓存数组都建议用全局变量或者静态变量因为FFT运算会频繁访问这些内存栈上分配的数组可能因为对齐和空间问题引入不可预估的行为。4. 常见问题与排查技巧4.1 IFFT还原结果幅值对不上这是问得最多的问题。表现是FFT的频谱峰值频率是正确的但IFFT还原后的信号幅值比原始信号大了很多倍。原因就是我前面强调的归一化问题。CMSIS-DSP的IFFT结果需要除以N。不论你用的是arm_rfft_fast_f32的IFFT方向还是arm_cfft_f32的IFFT方向都需要你自己做这个1/N操作。官方API文档里其实写得很清楚函数返回的是未归一化的原始计算结果但估计不少朋友看文档时没注意这一点看到幅值不对就开始怀疑是不是数据格式弄错了。另外一个幅值相关的坑是在做幅值谱时忘了对中间频点乘2。很多教程里的FFT幅值谱代码乘了2新手容易误以为FFT输出本身就是幅值。实际上FFT输出是复数模值的一半配比如果不乘2频谱上看到的峰值幅度会比真实信号小一半。DC分量和奈奎斯特频率分量又不需要乘2这三个分量的处理规则不一样写代码时建议分开处理。4.2 FFT结果出现严重的频谱泄漏信号频率如果不是频率分辨率的整数倍频谱上会在真实频率周围铺开一片能量峰值幅度还会偏低这就是频谱泄漏。最典型的表现是你构造一个50Hz信号采样率1000HzFFT点数1024频率分辨率大约0.9766Hz50除以0.9766约等于51.19不是整数所以频谱上你会在第51和52个频点都看到不小的幅度就像能量被抹开了。解决频谱泄漏最常用的办法是加窗函数。对原始信号逐点乘以一个窗函数再送进FFT常见的窗有汉宁窗、海明窗、布莱克曼窗。加窗能显著压低旁瓣让主瓣更尖锐但代价是主瓣稍微变宽频率分辨能力略微下降。如果你更关心幅值精度用布莱克曼窗如果关心频率分辨率和幅值平衡汉宁窗最常用。需要注意的是加窗会改变信号的总能量幅值计算时通常要做对应的幅值恢复修正。在频率分析场景里我常用汉宁窗实现非常简单// 汉宁窗 加到 input_signal 上 for (int i 0; i FFT_SIZE; i) { float32_t win 0.5f * (1.0f - arm_cos_f32(2.0f * PI * i / (FFT_SIZE - 1))); input_signal[i] * win; }由于加窗处理不属于本项目的基础验证范围在代码演示里我没有加。实际用的时候你会发现加了窗后IFFT还原的时域信号就不再和原始信号完全一致了因为窗函数本身会调制幅度。所以如果你的目标是要做IFFT无损还原就不要在频域链路里加窗如果目标是频谱分析加窗是必需品。这两个目标要区分清楚。4.3 编译报错和内存对齐问题CMSIS-DSP库代码对编译器和环境有依赖最容易遇到的问题就是缺宏定义报错类似error: unknown type name float32_t。这类问题基本都是没加ARM_MATH_CM4和__FPU_PRESENT1造成的在前面章节已经说明这里不重复。另一个常见问题是链接时找不到数学库函数例如arm_sin_f32报undefined reference原因是Keil工程里没有勾选Use MicroLIB或者库文件的路径没有配对。CMSIS-DSP的数学函数依赖标准数学库的某些基础函数开启MicroLIB可以解决一部分链接问题。还有内存对齐问题。CMSIS-DSP要求输入输出缓冲区至少是4字节对齐的因为内部用到了32位加载存储指令。在Keil中全局数组默认是8字节对齐一般没问题。但如果你用了动态分配或者自定义缓冲区就要小心。可以用__attribute__((aligned(4)))或者Keil的__align(4)来显式指定。__attribute__((aligned(4))) float32_t input_signal[FFT_SIZE];如果对齐不正确FFT的结果可能会出现偶发性的错误而且这种错误很难复现排查起来非常耗时间。所以尽量使用全局或静态数组不要用栈上的大数组。4.4 性能优化和实时性提升如果发现FFT的计算时间太长实时性不够优先检查三件事。第一确认FPU和宏定义真的生效了这个可以简单从编译时间或者看汇编来确认。第二把编译优化等级提到-O2/-O3性能差距非常明显。第三检查你的FFT点数是否过大。1024点FFT在168MHz的F407上实测大约是150微秒级别如果这个时间还不能接受可以考虑把点数降到512或者256。除了这三板斧还有一些工程手段。比如用ADC的DMA模式把采样数据源源不断搬进内存采样结束通过中断通知CPU来做FFT这样采样和计算可以流水线并行。再比如把频繁使用的数组放到CCM RAM里CCM RAM挂在D总线上访问速度快一些。但注意CCM RAM不能做DMA访问所以DMA缓冲区一般放在普通SRAM里CCM RAM留给FFT计算用。还有一个容易忽略的点有些朋友喜欢在中断服务函数里直接调用FFT函数这种做法要尽量避免。FFT计算耗时在微秒到毫秒级别长时间占用中断会破坏系统的实时性。建议把FFT放在主循环或者RTOS任务里中断只负责置一个标志位通知主循环数据准备好了。5. 实测数据、避坑总结与应用扩展5.1 在F407上的性能实测数据我在168MHz主频的F407VET6上用 -O2 优化实测了几组常用FFT点数的耗时包含数据拷贝和FFT计算的时间FFT点数arm_rfft_fast_f32耗时说明256约30微秒适合对实时性要求极高的场合512约70微秒频率分辨率和性能比较均衡1024约150微秒本项目使用的点数精度足够2048约330微秒适合需要更高频率分辨率的场景4096约700微秒内存占用较大注意RAM预算这些数据在不同编译器和编译选项下会有差异但量级是参考得上的。IFFT的耗时和FFT基本一致因为底层蝶形运算量相同。如果你看到自己工程里时间比这个表差好几倍那基本是没优化好按4.4的小节去排查。内存占用方面1024点FFT所需的缓冲大约是输入信号4KBFFT输出约4KB10242个float幅值数组2KBIFFT输出4KB总计约14KB。加上实例结构体和其他开销F407的192KB RAM绰绰有余。但如果要做多通道并行处理内存和CPU的占用会成倍增长这时就要考虑分时复用缓冲或者选择更小点数。5.2 信号还原的功能扩展方向IFFT只是把频域数据还原回时域这个还原本身并不神奇神奇的是在还原之前你可以在频域里做很多文章。最常见的扩展是频域滤波。比如你采集到的信号里50Hz工频干扰很大而有效信号在500Hz以上可以在FFT之后把50Hz附近频点的数据置零再做IFFT就能得到去除工频干扰后的时域信号。这个操作的滤波效果非常干净幅度和相位响应都是理想的矩形特性这是FIR/IIR数字滤波器不容易实现的。还有一个很实用的方向是数字压缩。有些场景需要把信号数据无线传输出去直接发时域原始数据流量很大。可以FFT后只保留幅值较大的前几十个频点然后IFFT重建能大幅减少数据量虽然会有一定失真但在很多工程场景里重建精度够用。音频里的MP3、JPEG图像压缩本质都是类似思路。在嵌入式端做这类轻量级压缩FFT/IFFT是底层基础。另一个方向是特征提取和模式识别。振动信号的频谱特征可以用来判断轴承故障是外圈、内圈还是滚动体故障音频信号的频谱特征可以用来识别说话人这些都可以在FFT之后提取相应的特征参数。IFFT在这个场景里不是必须的但如果需要在时域里看某个特定频段的重构信号IFFT就派上用场了。5.3 用串口和Python把结果可视化开发阶段光靠printf打印一堆数字很难直观确认效果。我习惯的做法是STM32通过串口把FFT幅值数组或者IFFT还原后的时域数据发到PC端PC上用Python的matplotlib把波形画出来。这样调试效率提升非常大能一眼看出频谱峰值在哪个位置、还原信号和原始信号的波形是否重合。串口发送部分很简单准备好定长数据包加上帧头帧尾和校验通过UART DMA发送。Python端用pyserial读取串口数据解析成功后用matplotlib绘图。下面是一个简单示例脚本的骨架import serial import matplotlib.pyplot as plt import struct ser serial.Serial(COM3, 921600, timeout1) # 读1024个float32数据 data ser.read(1024 * 4) values struct.unpack(1024f, data) plt.plot(values) plt.show()这种联调方式特别适合验证我们前面提到的IFFT归一化问题——把原始信号和还原信号画在同一张图上一眼就能看出幅值是不是对不上比盯着printf打印的数字判断要高效得多。5.4 个人踩坑实录与建议做这套FFT/IFFT链路的过程中我印象最深的一个坑是C库数学函数的性能问题。早期版本我在信号预处理里用了标准库的sin函数没有用CMSIS-DSP的arm_sin_f32结果整个信号构造阶段就慢得离谱。换用arm_sin_f32之后速度提升非常明显。有朋友会想反正是初始化阶段慢点无所谓。但在实际在线处理场景里如果要对采集到的数据做实时窗函数或者信号预加重标准数学库的调用开销会影响整条链路的实时性所以建议对这个项目里涉及的所有浮点运算函数做一次性能审查。另一个经验是工程里一定要设计一个自检模式。我习惯在设备上电后先生成一段内部已知的测试信号跑一遍完整的FFT和IFFT流程核对还原误差是否在阈值内。如果自检失败说明DSP链路有问题及时报警事后再去处理业务数据就不会出大错。这个自检逻辑不复杂但能帮你把算法问题从系统集成问题中快速剥离出来强烈推荐加上。最后想说一点关于CMSIS-DSP资料的问题。官方文档对每个函数的数据格式说明得比较规范但很多细节藏在源码注释里。遇到结果不对时建议直接打开源码文件arm_rfft_fast_f32.c把数据索引关系理一遍比在网上搜零散教程要可靠得多。这套库用好了在STM32上做信号处理会变成一件非常顺手的事。
返回列表