1. 项目背景与核心功能

第一次接触STM32的ADC和DMA功能时,我就被这种硬件级的高效数据采集方式吸引了。这个项目最吸引人的地方在于,它用不到100元的硬件成本(一块正点原子精英板),实现了商业示波器上才有的FFT频谱分析功能。你可能要问:为什么要自己做示波器?市面上不是有现成的吗?其实通过这个项目,你能真正掌握信号从物理世界到数字世界的完整转换过程。

这个示波器的核心功能有三部分:首先是实时波形显示,能捕获0-100kHz范围内的信号;其次是自动测量功能,可以计算信号的峰峰值和频率;最亮眼的是FFT频谱分析,能分解出信号中的各频率分量。我实测下来,对于1kHz的正弦波,频率测量误差可以控制在±2%以内,完全能满足日常电子调试需求。

2. 硬件架构设计

2.1 关键硬件选型

整个项目的硬件核心是STM32F103ZET6这颗Cortex-M3芯片,它内置了3个12位ADC,最高采样速率1MHz。我选择PA6作为信号输入口,对应ADC1的通道6。这里有个细节要注意:STM32的ADC输入阻抗只有几十kΩ,对于高阻抗信号源,建议增加一个电压跟随器电路。我在面包板上用LM358搭了个简易缓冲电路,实测输入阻抗提升到了1MΩ以上。

2.2 信号链路设计

信号采集链路是这样的:输入信号→10kΩ电位器(幅值调节)→100nF电容(隔直)→LM358电压跟随器→STM32的PA6引脚。特别提醒:一定要在PA6对地接一个100pF的小电容,这个技巧帮我解决了高频信号采样时的振铃问题。LCD显示用的是板载的2.8寸TFT屏,分辨率320×240,足够显示波形和参数了。

3. 软件核心实现

3.1 ADC与DMA的黄金组合

ADC配置的关键在于触发方式选择。我采用定时器2的PWM模式触发,这样能确保采样间隔绝对均匀。代码中这个参数很关键:

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;

DMA配置为循环模式,这样ADC数据会自动存入指定数组,完全不占用CPU资源。这里有个坑我踩过:DMA缓冲区必须定义成__attribute__((at(0x20001000)))形式,确保地址对齐,否则会出现数据错位。

3.2 FFT算法的实战应用

STM32的DSP库提供了现成的FFT函数,但使用时要注意三点:

  1. 输入数据需要先进行窗函数处理(我用的是汉宁窗)
  2. 输出结果要取模计算幅值
  3. 频率换算公式为:实际频率=频点序号×采样频率/FFT点数

具体实现时,这个函数是核心:

void FFT_Process(void)
{
    // 加窗处理
    for(int i=0; i<1024; i++){
        fft_in[i] = adc_buf[i] * hanning_window[i];
    }
    
    // 执行FFT
    cr4_fft_1024_stm32(fft_out, fft_in, 1024);
    
    // 计算幅值谱
    for(int i=1; i<512; i++){
        float real = (fft_out[i] << 16) >> 16;
        float imag = fft_out[i] >> 16;
        spectrum[i] = sqrtf(real*real + imag*imag);
    }
}

4. 关键参数调试技巧

4.1 采样率优化

通过修改定时器2的ARR和PSC寄存器可以调整采样率。我的经验公式是:

采样频率 = 72MHz / (PSC+1) / (ARR+1)

但要注意两个限制:一是ADC本身最高1MHz采样率,二是FFT要求采样率至少是信号频率的2.56倍。对于音频信号分析,我通常设置为50kHz采样率。

4.2 频谱分辨率提升

频谱分辨率Δf=采样率/N。想要区分1Hz和2Hz的信号,要么增大FFT点数N(内存允许的话),要么降低采样率。我在代码中实现了动态调整:

void adjust_sample_rate(uint8_t dir)
{
    static uint16_t prescaler = 71;
    if(dir && prescaler>0) prescaler--;
    else if(!dir && prescaler<255) prescaler++;
    TIM_PrescalerConfig(TIM2, prescaler, TIM_PSCReloadMode_Immediate);
}

5. 性能优化实战

5.1 实时性保障

为了保证波形刷新流畅,我做了这些优化:

  1. 使用DMA双缓冲技术,一组缓冲用于显示时,另一组接收新数据
  2. 只绘制每4个采样点中的1个到屏幕上(1024点绘256个)
  3. FFT计算放在定时中断外,通过标志位触发

5.2 测量精度提升

要提高频率测量精度,我总结出三个要点:

  1. 信号幅值要占满ADC量程的70%以上
  2. 对FFT结果进行插值处理(我用的是三点插值法)
  3. 适当增加采样时间,我的配置是:
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_41Cycles5);

6. 功能扩展实践

6.1 多波形支持

通过修改DAC输出,可以产生多种测试信号。比如这个三角波生成函数:

void gen_triangle_wave(void)
{
    static uint16_t val = 0;
    static int8_t dir = 1;
    
    DAC_SetChannel1Data(DAC_Align_12b_R, val);
    val += dir * 16;
    if(val >= 4095) dir = -1;
    else if(val <= 0) dir = 1;
}

6.2 自动量程功能

我通过检测信号峰值自动调整垂直灵敏度:

void auto_scale(void)
{
    uint16_t max_val = 0;
    for(int i=0; i<1024; i++){
        if(adc_buf[i] > max_val) max_val = adc_buf[i];
    }
    
    if(max_val > 3800) set_vertical_scale(5.0);  // 5V/div
    else if(max_val > 1900) set_vertical_scale(2.5); // 2.5V/div
    else set_vertical_scale(1.0); // 1V/div
}

7. 常见问题解决方案

在开发过程中,我遇到过几个典型问题:

  1. 波形毛刺多:在ADC输入端增加RC低通滤波(我用的是1kΩ+100nF组合)
  2. FFT频谱泄露:必须加窗函数,汉宁窗效果比矩形窗好很多
  3. 测量值跳动大:采用滑动平均滤波,我通常用8点平均
  4. 屏幕刷新卡顿:将LCD驱动改为DMA方式,CPU占用率从70%降到15%

8. 项目进阶方向

这个基础框架还可以扩展很多实用功能:

  1. 增加SD卡存储功能,实现波形记录
  2. 添加USB虚拟串口,上传数据到PC分析
  3. 实现触摸屏控制,替代物理按键
  4. 加入PID控制算法,做成简易的频谱分析仪

我在实际项目中发现,当信号频率超过20kHz时,测量精度开始下降。这时候可以考虑使用STM32H7系列的高性能芯片,或者外置高速ADC芯片。不过对于大多数音频和低频信号分析,这个基于STM32F1的方案已经完全够用了。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐