别再手动算频率了!用STM32F1的ADC+DMA+FFT做个简易频谱分析仪(附完整代码)

张开发
2026/4/18 10:12:30 15 分钟阅读

分享文章

别再手动算频率了!用STM32F1的ADC+DMA+FFT做个简易频谱分析仪(附完整代码)
基于STM32F1的实时频谱分析仪开发实战在嵌入式系统开发中信号处理一直是个既基础又关键的领域。想象一下当你需要快速了解某个未知信号的频率成分时传统示波器只能显示时域波形而专业频谱分析仪又价格昂贵。这时候用STM32F1系列单片机配合ADC、DMA和FFT库搭建的简易频谱分析仪就显得格外实用。这种方案特别适合电子爱好者、嵌入式开发者以及需要快速验证信号特性的工程师。它不仅成本低廉整套硬件成本可控制在50元以内还能根据需求灵活调整采样率和分析带宽。下面我们就从硬件设计到软件实现完整剖析这个项目的技术细节。1. 硬件设计与信号调理1.1 输入信号调理电路任何频谱分析仪的第一步都是确保输入信号符合ADC的采样要求。STM32F1的ADC输入范围是0-3.3V而实际信号可能超出这个范围甚至包含负电压。典型的信号调理电路应包含电压偏置电路使用运算放大器搭建加法器将交流信号抬升到1.65V直流偏置抗混叠滤波器二阶有源低通滤波器截止频率设为采样频率的1/3保护电路TVS二极管和限流电阻防止过压损坏ADC引脚// 典型偏置电路计算公式 Vout (Vin * R2/(R1R2)) (Vref * R1/(R1R2))提示偏置电压稳定性直接影响FFT结果精度建议使用REF3033等精密基准源1.2 关键器件选型器件类型推荐型号关键参数备注运放LMV358增益带宽积1MHz双通道低成本基准源REF30333.3V±0.2%低噪声滤波器电容C0G/NP0容值1nF-100nF温度稳定性好2. 软件架构设计2.1 系统工作流程整个频谱分析仪的软件流程可以分为三个主要阶段采样阶段定时器触发ADCDMA自动搬运采样数据处理阶段对采样数据加窗后执行FFT运算显示阶段提取幅频特性并通过串口或OLED输出[信号输入] → [ADC采样] → [DMA传输] → [FFT计算] → [结果可视化]2.2 关键参数配置在STM32F1上实现频谱分析需要精心配置几个核心参数采样率由定时器频率决定遵循Nyquist定理采样点数64/256/1024点对应不同频率分辨率ADC时钟需平衡转换速度和精度// 定时器配置示例产生256kHz采样时钟 TIM_TimeBaseInitTypeDef TIM_InitStruct; TIM_InitStruct.TIM_Period 280; // 72MHz/(2801)256kHz TIM_InitStruct.TIM_Prescaler 0; // 无预分频 TIM_TimeBaseInit(TIM3, TIM_InitStruct);3. FFT实现与优化3.1 ST官方FFT库使用STM32F1的DSP库提供了优化过的FFT函数使用时需注意输入数据需要转换为Q15格式输出结果为复数形式需计算模值不同点数FFT函数不可混用// FFT处理流程示例 int16_t fftInput[256]; // 实部为采样值虚部为0 int16_t fftOutput[256]; // 复数输出 // 准备输入数据 for(int i0; i256; i) { fftInput[i] ((int16_t)adcData[i] - 2048) 4; // 12位ADC转Q15 } // 执行256点FFT cr4_fft_256_stm32(fftOutput, fftInput, 256); // 计算幅值 for(int i0; i128; i) { // 只取前一半频谱 int16_t real fftOutput[i*2]; int16_t imag fftOutput[i*21]; magnitude[i] sqrtf(real*real imag*imag); }3.2 频谱泄露与加窗处理直接进行FFT会产生频谱泄露常见解决方案包括汉宁窗减少旁瓣泄露适合大多数情况平顶窗提高幅值测量精度凯撒窗可调节主瓣宽度和旁瓣衰减// 汉宁窗应用示例 for(int i0; i256; i) { float window 0.5f * (1 - cosf(2*M_PI*i/255)); fftInput[i] (int16_t)(window * adcData[i]); }4. 结果可视化方案4.1 串口输出频谱数据最简单的可视化方式是通过串口将频谱数据发送到PC# Python端接收处理示例 import serial import matplotlib.pyplot as plt ser serial.Serial(COM3, 115200) data [] while len(data) 128: line ser.readline().decode().strip() if line: data.append(float(line)) plt.plot(data) plt.xlabel(Frequency bin) plt.ylabel(Magnitude) plt.show()4.2 OLED实时频谱显示对于需要独立工作的场合可以使用0.96寸OLED显示频谱图初始化SSD1306驱动设计频谱柱状图绘制函数添加频率标尺和峰值标记// OLED频谱绘制核心代码 void DrawSpectrum(uint8_t *magnitude) { SSD1306_Clear(); for(int i0; i64; i) { // 显示前64个频点 uint8_t height magnitude[i] / 16; // 归一化 SSD1306_DrawColumn(i*2, 63-height, height); } SSD1306_UpdateScreen(); }5. 性能优化技巧经过多个实际项目的验证以下几个优化措施能显著提升系统性能DMA双缓冲避免FFT计算阻塞新数据采集定点数优化用Q格式数代替浮点运算动态调整采样率根据信号特征自动切换背景校准定期测量并补偿ADC偏移在最近的一个电机振动分析项目中通过采用这些优化我们将频谱刷新率从5Hz提升到了20Hz完全满足了实时监控的需求。特别是在处理突发信号时双缓冲机制确保了不会丢失任何关键数据。

更多文章