做频谱分析这行,很多时候最气人的不是算法不会写,而是明明MCU里的FFT库能跑,出来的频谱图却怎么看都不对。我拿STM32F4做过一个嵌入式FFT音频频谱分析系统,ADC采集+FFT处理这条链路踩过的坑,比写代码的时间加起来还多。这篇文章把最典型的5个错误整理出来,涉及ADC采样参数配置、采样率与FFT点数匹配、窗口函数、DMA缓存衔接以及幅值换算,适合正在做音频频谱显示、电机振动监测、电源纹波分析这类项目的朋友,也适合刚把STM32点完流水灯、准备往信号处理方向走的初学者。
1. 先搭好整体框架:一条ADC+FFT链路到底有哪些关键环节
1.1 从信号到频谱,中间发生了什么
如果只看最终结果,STM32做FFT频谱分析似乎就是“ADC采样数组→调用一次FFT函数→画出柱状图”。但实际工程中,结果准不准,取决于ADC之前和FFT之间的三个环节:模拟输入调理、ADC采样参数、FFT预处理。
模拟输入调理:很多传感器输出阻抗高或者直流偏置大,直接怼到ADC引脚会让采样精度下降甚至超出量程。FFT分析又要求信号在量程内尽量占满动态范围,所以通常要加一级运放跟随或放大/偏置电路。我见过不少项目,MCU代码写得没问题,但信号源是高阻输出,接上ADC以后波形直接“压扁”,这时候怎么调FFT都没用。
ADC采样参数:采样率、采样时间、分辨率、触发方式这四个参数决定原始数据的质量。尤其采样率不是随便填的,它等于ADC时钟除以(采样周期数+转换周期数),跟定时器触发配置直接相关。
FFT预处理:常用的是直流去除、加窗函数和数据格式转换。不做这一步,FFT出来的频谱会有很大的0Hz分量和严重的频谱泄漏。
实际上,从信号进入MCU到显示频谱,任何一环出错,最后都会表现为“FFT结果很奇怪”。但很多人的第一反应是怀疑FFT算法库坏了,或者MCU硬件有问题,这恰恰是最大的误区。
1.2 一个口诀记好三个参数:采样率、采样点数、ADC位宽
做频谱分析前先把三个基础参数想清楚,这是整条链路的物理基础。
采样率fs:决定能分析的频率范围。按奈奎斯特定理,能分析的最高频率是fs/2,超过这个频率的信号会混叠到低频,产生“假频谱”。比如你用40kHz采样,以为能测20kHz,但如果前端没有抗混叠滤波,30kHz的信号会折叠到10kHz的位置,结果里出现一个你根本没输入的频率成分。
采样点数N:决定频率分辨率,也就是相邻两条谱线能区分的最小间隔Δf = fs/N。想分辨两个很近的频点,就得提高N或者降低fs。注意换算关系:FFT本身的运算量是Nlog2(N),STM32F4做1024点浮点FFT大概几十微秒到一两百微秒级别,但实时系统里还要考虑ADC采样时间,不是N越大越好。
ADC位宽:决定动态范围下限。12位ADC理论上动态范围约74dB,对于多数嵌入式频谱分析场景够用。但如果把12位结果存到16位变量却忘记做对齐处理,或者在转浮点时直接除以4096而不是正确的缩放,误差会被FFT进一步放大。
这三个参数不是孤立的,后面每一个坑,几乎都是它们之间的“配合”出了问题。
2. 核心细节解析:采样、窗函数和ADC时序那些容易出错的关键点
2.1 采样率到底怎么定?先分清“需要测多少频段”和“能用多少时钟”
很多新手对采样率的理解是“反正能设多高就设多高”,听起来安全,实际上有很多副作用。采样率太高,你关注的低频范围内的频率分辨率就会变差;采样率太低,高频信号会混叠。所以第一步应该是问自己:我要分析的信号最高频率是多少?
如果是工频电源纹波,关注0~10kHz就够了;如果是音频信号,至少要覆盖0~20kHz;如果是电机振动,可能要看转速基频的几十倍频,用200kHz以上的采样率也不奇怪。
确定最高分析频率Fmax后,理论采样率只要大于2Fmax即可。但工程上建议留20%以上的余量,因为实际滤波器不是理想低通,ADC输入的信号总有高频噪声,前端最好加RC低通或运放滤波,截止频率设在Fmax附近,然后再把采样率设为略高于2Fmax。比如音频场景,前端低通设20kHz,采样率用48kHz,这是很经典的组合。
接下来是时钟树怎么配置的问题。以STM32F407为例,APB2外设时钟通常是84MHz,但ADC内部时钟最高不能超过36MHz,所以分频系数至少要选4(84/4=21MHz)或6(84/6=14MHz)。然后每个ADC通道的转换时间等于采样时间加上固定转换时间,12位模式下固定大概是12个ADC时钟周期。如果你把采样时间设为3 cycles,那单个通道总转换时间就是15个ADC时钟周期,在21MHz时钟下约0.714us,理论上最高采样率约1.4MHz。但这是连续转换模式下的理论上限,实际建议用定时器触发,采样率能精确到整数,也更容易控制。
这里有个很多人会忽略的配套问题:ADC连续转换加DMA循环模式虽然代码简单,采样时间间隔却容易受DMA传输占用总线的影响,产生抖动。做FFT这类对采样间隔一致性敏感的场景,我更推荐定时器触发ADC,让触发脉冲严格等间隔,采样数据才符合FFT的等间隔采样前提。
2.2 频率分辨率、FFT点数和实时性的三角交易
频率分辨率是频谱分析里最容易被误解的概念。假设采样率fs=40kHz,FFT点数N=1024,那么每条谱线的间隔就是40k/1024≈39Hz。这意味着如果你有两个频率35Hz和45Hz的分量,它们会落在同一条或相邻的谱线上,肉眼很难分开。
想提高分辨率,有两种办法:提高N,或者降低fs。但提高N会带来两方面代价:一是FFT运算量按Nlog2(N)增长,二是采样时长N/fs变长,对实时显示来说意味着刷新率下降。降低fs则直接缩小可分析频率范围。所以“频率分辨率”和“频率范围”本质上是一对矛盾,你必须根据应用场景做取舍。
在STM32F4上做浮点FFT,1024点通常几十微秒量级;4096点会增加到几百微秒,如果主循环还在做LCD刷新、按键扫描和Can通信,要注意调度。如果你的采样率是40kHz,一次4096点FFT需要102.4ms的采样窗长,作为实时频谱显示会显得很“卡”。我一般先从256或512点起步验证算法,调通后再根据实际情况加大点数。
另外补一句:FFT输出结果是对称的,前N/2条对应0~fs/2正频率,后N/2条是镜像。N通常是2的整数次幂,CMSIS-DSP库也要求这一点。常见坑是N取1000之类,直接导致FFT库报错或结果异常。
2.3 采样时间的坑:采样电容没充满,数据全是“假的”
ADC采样过程,本质是在采样时间内用一个内部采样电容对输入电压充电。充电是否充分,决定了采样结果准不准。STM32内部采样电容大概几pF,如果输入源阻抗很高,比如传感器直接输出并通过10kΩ电阻分压接入,RC时间常数就会明显变大。采样时间设置太短,电容还没有充电到位就被“冻结”,同一电平下采出来的值可能偏低或不稳定,尤其在信号变化快的边沿,转换结果会带上明显的非线性失真。
这个问题的具体表现是:直流输入时看不出问题,一旦输入正弦波,FFT结果里就会出现不该有的高次谐波,而且谐波幅度会随采样时间缩短急剧上升。很多人的第一反应是“ADC坏了”或“外部干扰”,实际上就是采样时间不够。
我给一个根据经验估算的口诀:采样时间设置的cycles数,尽量不小于信号高频分量的充电需求。对于低频音频信号,用15 cycles到28 cycles通常比较稳;对于高速动态信号,在保证源阻抗低的前提下才可以考虑3 cycles。STM32官方应用笔记AN2834里给出了“ADC误差和对精度的影响”的系统性说明,做高精度测量时值得翻一翻。
如果电路上不得不串大电阻,比如为了阻抗匹配串了10k,那你不仅要在软件上把采样时间调大,还要检查RC低通截止频率会不会太低,把想分析的信号滤掉或者造成幅度衰减。这里我建议在调试阶段用示波器测量ADC引脚的波形,确认信号确实是你预期的幅度,再回头审视采样时间,能省去大量猜测时间。
3. 实操过程:STM32F4上从定时器触发ADC到幅值换算的完整实现
3.1 硬件准备与参数选择
我以STM32F407ZGT6开发板为例,开发环境用STM32CubeIDE+HAL库+CMSIS-DSP。信号源接到PA1,也就是ADC1的通道1,信号幅度确保在0~3.3V范围内。如果信号本身有负压或偏置不在这个区间,需要先做电平搬移,比如用运放加法器加1.65V偏置,保证所有采样点都不削顶。
参数选择:
- ADC分辨率:12位
- ADC时钟:APB2/4 = 21MHz
- 采样时间:15 cycles
- 触发方式:TIM2更新事件触发
- 采样率:40kHz
- FFT点数:1024
- 频率分辨率:约39Hz
3.2 定时器触发配置:先写定时器,再写ADC
采用TIM2作为触发源,原因很简单:定时器触发能精确控制采样间隔,同时不占用CPU。按照84MHz的APB1定时器时钟,分频83后变成1MHz计数频率,自动重载值设为24,每次计数到25个脉冲触发一次,得到40kHz触发频率。这里的预分频和重载值是这样计算的:定时器时钟 = 84MHz,预分频PSC=83后得到1MHz,ARR=24时更新周期为(24+1)/1MHz = 25us,对应40kHz。
需要特别注意的是,定时器更新事件本身可能不止用于触发ADC,如果还在同一个TIM2上做PWM输出或周期中断,要确认这些功能是否互相影响。更稳妥的做法是用专门一个定时器仅负责“节拍”,逻辑清晰,后续改采样率时也不用翻代码找依赖关系。
void MX_TIM2_Init(void) { TIM_MasterConfigTypeDef sMasterConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 83; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 24; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); }这里TIM_TRGO_UPDATE是关键,它把更新事件作为触发输出送到ADC的触发输入。
ADC1的初始化中,重点是把外部触发源设为T2_TRGO,触发边沿选上升沿,连续转换模式关闭,转换成单次,但DMA循环模式打开。注意顺序上,两次HAL_TIM_Base_Start不要写反。启动时先开ADC的DMA,再启动定时器,确保第一次触发脉冲不会在ADC尚未就绪时丢失。
void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO; hadc1.Init.DataAlign = ADC_RIGHTALIGN; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); }3.3 DMA和双缓冲思路:边采边算,别让CPU白等
FFT需要每次处理N个连续采样点,如果先采完一轮再做FFT,采样期间FFT闲着;做FFT期间,ADC又闲着。为了提高利用率,常用双缓冲:一块缓冲在ADC填数据,另一块拿去做FFT,两块交替。
在STM32F4的循环DMA模式下,最简单的双缓冲实现是开一个2N长度的数组,通过DMA半传输中断和全传输中断区分上半块和下半块。半传输完成时上半块填满,全传输完成时下半块填满。每次都只处理“刚填完”的那一半,这样上一半的数据能覆盖到上一轮FFT处理周期,数据没有空隙。
#define FFT_SIZE 1024 uint16_t adc_buffer[2 * FFT_SIZE]; float32_t fft_input[2 * FFT_SIZE]; float32_t fft_mag[FFT_SIZE]; void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { process_fft_block(&adc_buffer[0]); } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { process_fft_block(&adc_buffer[FFT_SIZE]); } }process_fft_block里做的第一件事是把ADC原始数据从uint16_t转成float,同时去掉直流偏置。用12位ADC测量一个在0~3.3V之间摆动的信号时,信号的中心大约在2048附近,转浮点后直接减去2048,再除以2048,得到±1范围的数据。这里的2048不是随便写的,它对应12位ADC中点的原始码值。
3.4 FFT计算与幅值换算:不换算,频谱值永远对不上
CMSIS-DSP里的FFT函数要求输入在时间域是实部+虚部交替排列的结构。这意味着处理前要把实部数据放到偶数位置,虚部置0。不过如果用的是arm_cfft_f32,输入数组本身就是2N大小,可以直接把N个实数放到fft_input[0], fft_input[2], ...,把奇数位置清零,然后调用FFT。
void process_fft_block(uint16_t *src) { for (uint32_t i = 0; i < FFT_SIZE; i++) { fft_input[2 * i] = ((float32_t)src[i] - 2048.0f) / 2048.0f; fft_input[2 * i + 1] = 0.0f; } arm_cfft_f32(&arm_cfft_sR_f32_len1024, fft_input, 0, 1); arm_cmplx_mag_f32(fft_input, fft_mag, FFT_SIZE); }这里arm_cfft_f32的参数里ifftFlag=0表示正变换,bitReverseFlag=1建议打开,否则输出顺序不符合后续处理习惯。而arm_cmplx_mag_f32会算出包括正负频率在内的N条谱线的幅度,实际使用只取前N/2条。
到这里,fft_mag里的值还不能直接当作信号的物理幅度使用。因为FFT输出的复数模长是累加后的值,显示单边频谱时要把正负两半叠加,再做幅度归一化。对不加窗的情况,公式是:真实幅度 ≈ mag[k] * 2 / N。
如果你的输入信号是幅值1V的正弦波,ADC满量程3.3V,12位ADC,那么换算到信号实际电压时还要乘上3.3V/4096。这个系数很多人会漏,结果测出来的幅度永远是“码值”,而不是“伏特”。
如果加了窗函数,还要额外除以窗函数的相干增益。比如汉宁窗的相干增益是0.5,那幅度就差了一倍。我建议直接把窗函数补偿和归一化合并写成一个函数,不要在显示代码里二次计算,否则改一处漏一处。
3.5 如果想要更高性能:定点Q15和查表法
浮点FFT在F4上已经很快,但对于需要同时做几路信号分析或高刷新率的场景,可以把数据转成Q15定点格式,使用arm_cfft_q15。CMSIS-DSP的q15 FFT速度大约是浮点版本的2到3倍,代价是动态范围不如浮点,输入信号的幅度不能太接近满量程,否则容易溢出。
一个常用的做法是:把ADC原始值减2048后除以16,压到[-128, 127]范围,再作为q15输入。这个缩放比例要根据实际信号幅度来调,原则是保证FFT过程中间量不溢出,同时尽量利用Q15的动态范围。跑通之后,再用定点版本替换浮点版本,性能提升非常明显。
3.6 关于窗口函数:什么时候加,加了怎么补偿
我一直把窗口函数放在实操部分讲,因为大多数人加窗是用错场景的典型。
如果你的输入信号是稳定的周期信号,并且采样长度恰好是整周期,比如40kHz采样,信号是1kHz,1024点正好是25.6个周期,不是整数,所以不加窗100%会出现频谱泄漏。这时加汉宁窗能明显减小旁瓣,代价是主瓣变宽,幅度变小。
如果信号本身就是宽带噪声或瞬态冲击,加窗的效果没有正弦信号那么关键,有时候甚至不如不用窗。
常用的经验:不知道信号特性时,先加汉宁窗,再用已知幅值的正弦波做标定,测出来的幅度会比不加窗更准。汉宁窗实现起来很简单,预先算好窗口系数存表,每次处理数据前逐点相乘即可。注意,加窗要在去直流之后,顺序不能反。
4. 五大常见错误的排查实录
4.1 错误一:频率分辨率以为够用,实际根本分不开
有一次项目是分析两个间隔只有20Hz的音频信号,我一开始设置采样率48kHz,FFT点数512,频率分辨率是48000/512=93.75Hz,两个频点完全落在同一条谱线里,频谱图上只能看到一个“鼓包”。当时我还以为是ADC灵敏度不够,后来算了频率分辨率才发现问题完全出在参数选择上。
这类错误最坑人的地方在于“代码一点毛病没有,结果就是不符合预期”。检查思路很简单:先列出来目标频率间隔Δf,然后确认fs/N是否小于Δf/2左右。如果不满足,要么增大N,要么降低fs,要么应用高分辨算法,但嵌入式场景最实用的还是前两种。
建议在代码里把fs和N定义成宏,并加一个注释算出理论分辨率,例如// fs=48000,N=512,df=93.75Hz,方便下次看代码时一眼就能发现问题。
4.2 错误二:采样时间太短,ADC“没吃饱”就开始干活
我一个做电机电流检测的朋友遇到过这样的现象:采集到的波形明显失真,FFT频谱里除了基波,还冒出一堆高次谐波,而且谐波次数越高幅度反而越大。他的采样时间设的是3 cycles,输入信号是霍尔电流传感器的输出电压,输出阻抗比较高。
把采样时间改成28 cycles后,谐波分量明显下降。原因很直接:采样电容没有足够的时间充电,高阻源带来的充放电时间常数大于采样窗口,每次采到的电压都滞后于真实信号,反映到频域就是非线性失真。
定位这类问题的办法:把ADC输入接到一个干净的直流电压,看转换结果的稳定度;再输入一个已知低失真正弦波,比较FFT频谱的THD。如果THD随着采样时间缩短而明显恶化,基本就是采样时间不够。如果连直流都不稳,那就是参考电压或布线问题,别急着改软件。
4.3 错误三:不做去直流和加窗,0Hz分量把整个频谱都压扁了
这是最典型的现象:第一次跑出FFT频谱,只看见最左侧一条冲得老高的谱线,右边全部小得看不见。原因通常不是“信号太大”,而是信号里的直流分量没有去掉。
传感器信号经过运放之后,不是以0为中心,而是叠加了一个直流偏置。FFT会把直流分量转化为0Hz处的巨大能量。尤其是STM32这种12位ADC,信号中心在2048左右,这个相当于满量程一半的直流分量,会严重压缩显示端的动态范围,让其他频率分量看起来都很小。
解决办法是先减直流,再做FFT。具体做法是把输入信号中心对应的码值算出来。如果信号经过电平搬移后中心在1.65V,对应码值约2048;如果中心不是整数码值或存在缓慢漂移,可以动态求平均,把前N个点求平均后减去,这样连低频漂移也能一并处理掉一部分。
加窗顺序一定要放在去直流之后。如果先加窗再算均值,窗函数造成的非对称性会让去直流变得不准确。
4.4 错误四:DMA缓冲区长度与FFT长度不一致,数据错位
这个坑我踩过一次。ADC的DMA配置为循环模式,缓冲区长度为4096,但FFT只用了1024点。本以为逻辑上是每个FFT周期处理1024点,但DMA一直在以一个4096长度的窗口循环搬运,你从缓冲区起始地址取1024个点的时候,取到的其实是圆环上任意位置的片段,数据块不是连续的采样序列,FFT结果自然是一堆看不懂的杂乱谱线。
正确做法是让DMA缓冲长度等于FFT点数,或者使用2倍长度双缓冲,并在处理时严格区分“哪个半块已就绪”。如果只用单缓冲加手动等待,也要确保在处理前数据已完整填充,不能边采边算。
另外DMA的数据宽度也要检查。ADC输出是半字(16位),DMA的外设和数据宽度都应该设成HALFWORD。有人习惯清一色选WORD,结果缓冲区里有效数据隔三岔五出现空洞,频谱看起来就像噪声。这个配置在CubeMX里很直观,但如果是手写DMA初始化代码,容易忽略。
4.5 错误五:FFT后没有正确换算幅值,实测永远对不上
最后这个错误最隐蔽,因为它不会让频谱“难看”,只会让幅度“偏大”或“偏小”。比如示波器读到的正弦波幅值是1V,FFT峰值却显示0.75V,有人就以为是ADC参考电压偏差或前端衰减,折腾半天发现只是幅值换算少了窗函数补偿系数。
我做音频频谱显示时,习惯把信号幅度、频率和谱线幅度一起打印出来,直接用确认的参数验算一遍。假设输入1kHz正弦波1V,ADC参考3.3V,满量程码值4096,经过去直流和2/N归一化之后,FFT峰值应接近1V。如果测出来是0.5V,基本可以确认漏了“正负频率叠加”这一步,或者窗增益没补偿。
幅值换算公式整理:
- 无窗/矩形窗:幅度 = mag[k] * 2 / N
- 汉宁窗:幅度 = mag[k] * 2 / N / 0.5
如果你做的是RMS幅值频谱而不是峰值频谱,需要再乘0.707,这一点也要在软件注释里写清楚,避免过一个月再看代码时一脸问号。
5. 问题排查速查表与几条调试心得
5.1 常见异常频谱快速判定表
调试时经常要快速判断问题方向,我整理了一张自己常用的对照表:
| 频谱现象 | 常见原因 | 检查方向 |
|---|---|---|
| 0Hz处巨大尖峰,其他全看不见 | 直流未去除 | FFT前是否做了减均值处理 |
| 频率峰值明显偏低/偏高 | 幅值换算漏系数 | 单边归一化、窗补偿、电压比例 |
| 主峰周围有规律旁瓣拖尾 | 频谱泄漏 | 是否加窗、采样长度是否整周期 |
| 全频段均匀噪声底抬升 | DMA宽度错、缓存悬空、电源噪声 | 查DMA配置、ADC输入是否空闲 |
| 出现不存在的频率分量 | 混叠或ADC非线性 | 检查抗混叠滤波、采样时间、源阻抗 |
| 同一输入信号结果忽大忽小 | 缓冲区未完整就绪就处理 | 检查DMA半/全传输标志同步 |
5.2 用已知正弦波做系统标定,这步不能省
很多人写完FFT代码,直接上真实信号,一旦结果不对就四处找问题。我自己的习惯是先用信号发生器给一个已知频率和幅度的正弦波做整个链路的标定。比如给1kHz、1V信号,先看ADC原始波形峰值,确认没有削顶和明显失真,再看FFT峰值频率是否在1000Hz附近,再看峰值幅度是否接近1V。
这三个“是否”,刚好分别验证了模拟输入、采样参数和FFT后处理三个环节。如果某个环节不对,定位范围会小很多。
如果手头没有信号发生器,也可以用MCU自带的DAC输出一个正弦波,接到ADC输入。虽然DAC自身的谐波失真和采样率限制会带来一些误差,但用于验证链路完整性绰绰有余。
5.3 几条我觉得很实用的调试技巧
最后分享几个容易忽略但很实用的小技巧。
第一,打印原始ADC数据。不要只盯着FFT结果看。把ADC原始序列用串口转发到上位机,用Python或者串口绘图工具画出来,一眼就能看出信号是否正常。很多FFT结果异常,其实在原始波形阶段就已经能看出问题了。
第二,FFT输入数据先做加窗还是先去直流,顺序不要反。先减直流再加窗是最稳的做法。
第三,采样率不是一拍脑门定的。用定时器触发时,把PSC和ARR写成宏,并注释计算过程,例如40kHz = 84MHz / (83+1) / (24+1)。这样以后改采样率时不会算错。
第四,STM32CubeMX生成的ADC初始化只负责基本配置,实际工程的模拟输入阻抗匹配、参考电压稳定性、PCB布局对FFT结果的影响,往往比算法本身更大。我第一次做FFT项目时,在面包板上搭的采样电路噪声奇大,后来换到干净的PCB上,一个问题都没了。这不是玄学,电源和地平面的噪声会直接被ADC采进去,FFT会把它们全部呈现出来。
做STM32的ADC采集和FFT处理,本质是一个“信号完整性+参数匹配+后处理”的综合工程。代码本身不难,难的是把采样链路里每一个环节都想明白。这篇文章里的五个错误,每一个我都亲自踩过或帮人排查过。“先查参数,再查硬件,最后才怀疑算法”,这个排查顺序能帮你省下大量时间。