问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

基于STM32实现简易示波器

创作时间:
作者:
@小白创作中心

基于STM32实现简易示波器

引用
CSDN
1.
https://m.blog.csdn.net/2302_81324947/article/details/144805715

示波器是电子设计和调试中不可或缺的工具,用于观察信号的波形及其特性。然而,市场上的商业示波器价格较高,对于学习或简单应用来说,使用微控制器(如STM32)实现一个简易示波器是一个很好的选择。不仅可以节约成本,还能加深对信号采集、处理和显示的理解。本文将详细介绍如何基于STM32实现一个简易的示波器功能,包括系统架构、关键技术等。

一、系统架构

一个简易示波器的基本功能包括信号采集、数据处理和波形显示。基于STM32的设计可大致分为以下模块:

  1. 信号采集模块:利用ADC(模数转换器)采集模拟信号,将其转换为数字信号。
  2. 数据处理模块:对采集到的信号进行FFT算法,来计算频率等参数。
  3. 显示模块:通过LCD、TFT屏幕或串行通信(如USART)将波形呈现出来。这里使用的是陶晶驰串口屏(TJC4827X243_011R)。其UI界面如下图所示

可以显示频率,峰峰值,有效值以及实时波形更新。

硬件准备

  1. 核心板:STM32微控制器(本样例采用STM32F103C8T6最小系统板)。
  2. 屏幕:陶晶驰串口屏(支持串口通信)。
  3. 信号输入端:信号发生器或自制的模拟信号电路。
  4. 电源模块:为STM32和外围设备提供稳定的电源。
  5. 必要的连接线:用于屏幕、信号输入与STM32的连接。

二、核心功能实现

(1) 信号采集

1. 采样脉冲初始化

采样脉冲的生成基于 TIM2 定时器的 PWM 输出。该功能为 ADC 提供触发信号,确保采样的稳定性。

void TIM2_PWM_init(void) {
    arr1 = 45;
    psc1 = 150;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    TIM_TimeBaseStructure.TIM_Period = arr1;
    TIM_TimeBaseStructure.TIM_Prescaler = psc1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    TIM_CtrlPWMOutputs(TIM2, ENABLE);
    TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_OC2Ref);
}

2. ADC 数据采集与 DMA 配置

通过 ADC 将模拟信号转换为数字信号,并使用 DMA 实现高速传输。

在传统数据传输方式中,CPU 需要逐次读取 ADC 的采样结果并将其存储到内存,这种方式不仅占用了大量的 CPU 时间,还可能导致在高速采样场景下的数据丢失。而 DMA(直接存储器访问)模块可以在无需 CPU 参与的情况下,将 ADC 的采样结果直接传输到内存,大大减轻了 CPU 的负担,使其能够专注于其他任务处理。同时,DMA 的循环模式支持连续数据传输,保证了采样过程的稳定性。

以下是 ADC 和 DMA 配置的代码实现:

void AD_FFT_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = nn;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    ADC_DMACmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);
}

(2) 数据处理

1. FFT 数据处理

通过 STM32 的 DSP 库对采集到的数据进行 FFT 计算,从而提取信号的频率和幅值。

void fre_calcu(void) {
    signed short lX, lY;
    float X, Y, Mag;
    unsigned short i;
    fus = 72000000 / (arr1 * psc1);
    adxmax = AD_Value[0];
    adxmin = AD_Value[0];
    for (i = 0; i < nn; i++) {
        fftin[i] = ((u32)(AD_Value[i])) << 16;
        if ((float)AD_Value[i] >= adxmax) adxmax = (float)AD_Value[i];
        if ((float)AD_Value[i] <= adxmin) adxmin = (float)AD_Value[i];
    }
    adxmin = adxmin / 4095 * 3.3;
    adxmax = adxmax / 4095 * 3.3;
    Bias = (adxmin + adxmax) / 2;
    Af = adxmax - adxmin;
    cr4_fft_1024_stm32(fftout, fftin, nn);
    for (i = 0; i < nn / 2; i++) {
        lX = (fftout[i] << 16) >> 16;
        lY = (fftout[i] >> 16);
        X = nn * ((float)lX) / 32768;
        Y = nn * ((float)lY) / 32768;
        Mag = sqrt(X * X + Y * Y) / nn;
        fftbuffer[i] = (unsigned long)(Mag * 32768);
    }
    Mag = fm = 0;
    for (i = 1; i < nn / 2; i++) {
        if (Mag < fftbuffer[i]) {
            Mag = fftbuffer[i];
            fm = i;
        }
    }
    F = (u32)(fm * fus / nn);
}

(3) 数据显示

3. 波形更新与显示

通过定时器中断定期更新波形数据,并通过串口发送到串口屏中。

void TIM4_IRQHandler(void) {
    if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET) {
        fre_calcu();
        Serial_SendHZ(F);
        Serial_SendVPP(Af);
        Serial_SendRMS(Af / 2 / 1.414);
        if(F-F1 > 10 | fabs(Af-Af1) > 0.08)
        {
            for(i=0;i<255;i++)
            {
                Vol[i] = (float)((float)AD_Value[i] / 4095 * 3.3);
                Data[i] = (int)((0.188*Af+0.248)*(Vol[i]-Bias)/Af*128+127);
            }	
            Serial_SendData(Data);
            F1=F;Af1=Af;
        }
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    }
}

到这里,简易示波器的大致流程就到这里。

三、注意事项

以下是润色和修改后的内容,保持了专业性和易读性:

  1. 关于波形实时变化

由于ADC读取数据、单片机处理数据并通过串口发送到串口屏之间存在一定延迟,我在此基础上设计了一个小算法,使波形能够实现实时变化。但需要注意的是,这种变化是实时的,并非完全动态变化。

2.自定义串口屏的函数

为了实现串口屏的发送和接收功能,需要自定义相关的函数。例如,在文章中定义了类似于
Serial_SendHZ()

Serial_SendVPP()
的函数,用于具体的功能实现。

3.ADC读取范围的限制

本项目的ADC设计为仅支持读取0~3.3V范围内的输入信号。如果输入信号的峰峰值超过3.3V,由于STM32内置ADC的限制,将会导致测量不准确,并出现严重失真的情况。

4.数据映射关系

串口屏的波形控件仅支持显示范围为0255的整数值,而ADC读取的是03.3V的模拟信号。因此,需要将ADC的03.3V线性映射到0255之间,以便波形控件能够正确显示数据。

以下是之前项目中拍摄的示例图:输入为频率100Hz、峰峰值0.65V的正弦波信号,经过处理后在串口屏上显示的效果。

四、总结

本文介绍了如何使用 STM32 实现一个简易示波器功能,从采样脉冲生成到信号频谱分析,再到波形显示,涵盖了完整的实现流程。通过此项目,您不仅能够深入理解 STM32 的定时器、ADC、DMA 和 DSP库功能,还可以掌握基础的信号处理技术。这为后续开发更复杂的电子测量工具打下了坚实的基础。如果发现有任何错误,请随时指出。如果反馈的人数较多,我将考虑将项目开源。接下来,我会更新关于电路特性测试仪的相关内容。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号