基于stm32g031平台设计的带频谱分析功能的双通道示波器
通过硬禾学堂提供的基于STM32g031平台设计一款带有频谱显示的双通道示波器,支持电平触发功能,并且可以通过旋转编码器改变示波器的分辨率
标签
嵌入式系统
测试
显示
2022寒假在家练
Believer
更新2022-03-02
西南科技大学
1828

题目 设计一个带频谱分析功能的双通道示波器

1 项目需求

  • 通过STM32G031的ADC采集外部模拟信号,信号幅度范围2mVpp到30Vpp,频率为DC - 50KHz
  • 将采集到的波形显示在128*128的OLED上,并支持电平触发的功能
  • 通过FFT进行频谱分析,并将频谱显示在OLED上
  • 测试信号可以通过芯片的PWM+板上LPF的方式产生,比如1KHz、幅度为3V的正弦波
  • 能够自动测量波形的参数 - 峰峰值、平均值、频率/周期

2 完成的功能及达到的性能

经过实测,作品可以测试50KHZ以内的波形(采样率可达1MHZ,根据奈奎斯特采样定理理论上可以测到500KHZ的波形),但实际当达到50KHZ时波形已经开始出现显示失真了

注(重要):由于家中没有示波器和函数发生器,进行实测的波形都是由其他单片机产生的(只有0-3.3V),所以波形的最高输入范围最多为0-6.6V(这是外部电路决定的),超过这个范围的输入可能会损坏芯片!!!!

2.1   波形显示

设计的双通道示波器,在显示波形时,可以同时将两个波形同时显示在屏幕上。

在波形显示界面,通过旋转编码器可以改变采样率,采样率的范围为10Hz、20Hz、50Hz、100Hz、200Hz、500Hz、1kHz、2kHz、5kHz、10kHz、20kHz、50kHz、100kHz、200kHz、500kHz、1MHz通过改变采样率来实现横轴的缩放。

Y轴(幅度范围)通过旋转编码器的按键按钮来选择调节Y轴,即手动调整Y轴中心电压值和Y轴缩放范围

下方显示波形参数,从上到下依次显示通道一和通道二,参数显示从左到右依次显示平均值度值、信号峰峰值和频率。

按下key2键可以暂停波形刷新,再按可以继续刷新。

单通道显示(通道一显示1KHZ正弦波)

FtcTaKE1ShR22Ob1n-EllOgGaiS2

双通道显示(通道一显示1KHZ正弦波、通道2显示1KHZ方波)

FkohhiC2EUTTD9GpT0taa_y3RGaJ

2.2   触发显示

程序通过输出PWM波+电路的低通滤波器上电使输入信号偏置1.1V

触发类型为电平触发,通过旋转编码器的选择按钮选择触发模式,此时可以旋转编码器调节触发电平,程序上电默认不触发,且只有在触发模式下才能计算出波形的频率。

触发波形(通道一1KHZ正弦波+通道二接地)

FqNQ503mtrIzxjdMxhM_n0poO530

3.3   频谱显示

按下key1键进入频谱显示界面

显示频率范围为直流至采样频率的一半。同样按下旋转编码器按钮选择改变采样率模式,旋转编码器可以改变采样率,Y轴显示功能同上。

屏幕右下角显示频谱的基波频率。

双通道频谱显示(1KHZ正弦波+方波1KHZ)

FkPYmIaItYZ7v6IHHzHp1CjJ72qe

3 实现思路

ADC由定时器触发进行采样,对采样到的数据使用DMA搬运到内存;

将adc采样到的数字量进行坐标映射显示到屏幕上变成一个个的点,再将点与点之间依次相连形成光滑的曲线;

通过按键切换波形显示和频谱显示,旋转编码器改变实现波形在时间轴上的扩展与压缩;

对采样序列进行FFT变换,绘制频谱;

信号参数的显示,如峰峰值、平均值、信号频率等;

4 实现过程

4.1实现流程:

读取key1按键状态—>默认示波器模式进行数据采样—>波形产生—>波形生成

读取key1按键状态—>key1按下—>进入频谱模式—>数据采样—>DSPfft—>频谱产生—>频谱显示

FuqgKgdeD1MujW09rqjnTJKA6CLy

4.2ADC对数据进行采样:

由于g031的空间所限,又为了能进行双通道的波形同时显示,ADC一次性采集128个点。ADC由定时器3触发,触发频率最高为1MHZ,即ADC采样率最高为1Msps。ADC的转换结果直接由DMA搬运至内存。

ADC转换开始函数(定义位置:sample.c):

void Scope_Sample_Try_Start_New_ADC(void) 
{
   if (scope_sample_arr[i]->sample_flag == Scope_Sample_Not) 
   {
               
                HAL_ADC_Start_DMA(&SCOPE_hadc, (uint32_t *) scope_sample_arr[i]->data, SCOPE_SAMPLE_NUM * SCOPE_CHANNEL_NUM);
                HAL_TIM_Base_Start(&SCOPE_htim);
    }
}

4.3ADC采样结束回调函数

ADC采样结束函数(定义位置:sample.c):

void Scope_Sample_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) 
{
    HAL_TIM_Base_Stop(&SCOPE_htim);
    HAL_ADC_Stop_DMA(&SCOPE_hadc);
    
    dma_busy = 0;
    Scope_Sample_Try_Start_New_ADC();
}

4.4数据处理函数

由于外部电路的输入信号是通过一个反向比例的放大电路,所以对数据进行处理时需要对所有数据进行一次反向排列,即数据的最大值处理为数据的最小值。

计算采样数据进行最大值、最小值、平均值、周期、绘图数据计算

数据处理函数函数(定义位置:sample.c):

 uint8_t Scope_Sample_Service(Scope_Sample *sample) 
{
   for (uint8_t k = 0; k < SCOPE_CHANNEL_NUM; k++)   // 计算最小值、最大值、平均值
  {
     min = UINT16_MAX, 
     max = 0;
     for (uint16_t i = 0; i < SCOPE_SAMPLE_NUM; i++) 
        {
            if (sample->data[i][k] > max) max = sample->data[i][k];
            if (sample->data[i][k] < min) min = sample->data[i][k];
            sum += sample->data[i][k];
        }
     avg = (float) sum / SCOPE_SAMPLE_NUM;
  }
}

4.5编码器的处理

编码器采用外部中断的形式,对A、B相的状态进行捕获和判断,可实现顺时针和逆时针识别

编码器处理函数(定义位置:key.c):

static void Encoder_Scan(void) {
    static uint8_t status = 1; 

    uint8_t now_status = HAL_GPIO_ReadPin(ENCOA_GPIO_Port, ENCOA_Pin);
    if (now_status != status) { 
        if (!now_status) {
            if (HAL_GPIO_ReadPin(ENCOB_GPIO_Port, ENCOB_Pin))
                Key_Push(encoder_R);
            else
                Key_Push(encoder_L);
        }
        status = now_status;
    }
}


4.6 波形显示代码

取起始点后128个采样值(屏幕分辨率为128*128,采样128个点可以尽可能展现波形更多的细节)使其显示在OLED屏幕上(一次性刷新),为此需要将ADC转换值与OLED屏幕上的坐标进行线性对应。

(定义位置:display_scope.c):

void Show_Wave(Scope_Sample *sample) 
{
    float ratio = (float) (sample->len - 1) / (float) (SCOPE_X_NUM - 1);
    float j = 0; 
    uint16_t j_int = UINT16_MAX; // 第一个点一定有数据,这样便于绘制第一个点
    uint16_t last_i[SCOPE_CHANNEL_NUM] = {0}, last_val[SCOPE_CHANNEL_NUM];
    for (uint16_t i = 0; i < SCOPE_X_NUM; i++) { // 以示波器的横轴每个像素点为循环
        if ((uint16_t) j != j_int) { 
            j_int++;

            for (uint8_t k = 0; k < SCOPE_CHANNEL_NUM; k++) {
                float voltage = toVoltage(sample->data[sample->sp + j_int][k]);
                uint16_t val = Voltage_To_Coordinate(voltage);

                if (j_int != 0)
                    OLED_DrawLine(SCOPE_X_MIN + last_i[k], last_val[k], SCOPE_X_MIN + i, val, 1);
                last_i[k] = i;
                last_val[k] = val;
            }
        }
        j += ratio;
    }
}

4.7 fft处理

使用stm32的官方提供的DSP库进行FFT运算

(定义位置:sample_spectrum.c):

FFT采样处理处理点数:SPECTRUM_SAMPLE_NUM = 128

 arm_cfft_f32(&arm_cfft_sR_f32_len, fft, 0, 1);
 arm_cmplx_mag_f32(fft, fft, SPECTRUM_SAMPLE_NUM);
 arm_max_f32(fft + 1, SPECTRUM_SAMPLE_NUM / 2 - 1, &FFT_max, &FFT_max_index); // 只考虑正模长

4.8信号发生功能(未完成的功能)

由于在设计过程中优先考虑完成示波器和频谱的功能,所以在完成后才发现板载的双RC低通滤波器的引脚所能使用的定时器已经被其他用途所占用了,所以未完成信号发生的功能,在进行演示时,采用其他stm32开发板产生波形用于作品的演示

5.遇到的问题

以前对stm32嵌入式开发习惯性都是使用的标准库进行构建,由于是g031是比较新的芯片,不支持标准库开发,无奈只能使用提供的stm32cubeide软件舒勇hal库进行开发,在初期是不小的难度,只能不断在网上学习怎么在软件上进行初始化的配置,但是也渐渐发现,cubeide的软件自动生成初始化代码才是未来进行开发的趋势,可以省去很多时间,也能确保初始化不是出错。

在项目进行期间,参考了很多往期一起练的同学的代码(https://www.eetree.cn/project/detail/167),他们的项目都是很值得借鉴的,当然最感谢还是微信群一位网名叫WonderBoy的同学,多亏在他的帮助和指导下,我才能比较顺利完成此次项目。

本次难度最大的地方就是对进行FFT频谱的功能,在学习过程中使用过zerofft也用过其他的fft,但是由于芯片的现在,需要对其进行更改,不然内存不够,但更改后仍然达不到要求,最后在微信群里一位同学的帮助下使用DSP进行FFT完成了频谱显示的功能。

6.未来和建议

本次项目说实话其实对我来说难度挺大的,需要学习很多的知识,但是最后收获也很大,硬禾学堂举办的这次活动为我们提供了一个很好的平台,去发现问题,学习新的知识来解决问题,将理论知识运用到实践项目中,在这个过程中,虽有困难,但更多的是学到新的知识时的那份喜悦。虽然看着其他同学们陆续做出了成果,本以为难度应该不大,但是真正动手去做才发现有很多的难题,而且有些时候程序要不断的进行改进。

针对建议,主要就是双通道同时显示有个问题是如果波形比较相近,可能无法识别是哪一个通道的,针对这个问题也想过其他办法,但是都不太理想,我觉得比较简单的方法就是如果显示屏可以进行色彩显示就可以很简单解决这个问题了。

还有由于屏幕和芯片的限制,示波器的分辨率有限,而且很多细节也无法观察的到。

附件下载
blink_g031.zip
可供编译的工程文件
blink_g031.bin
可烧录进板子的bin文件
团队介绍
Beilever
团队成员
周杭
学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号