基于STM32G031系列单片机实现波形显示以及上位机交互

视频讲解及演示

B站视频链接:【【硬核DIY】手搓虚拟仪器!STM32G031零基础搭建示波器+信号发生器 | 串口通信 | 上位机交互 | 开源分享】 https://www.bilibili.com/video/BV1wUP8e4E1n/?share_source=copy_web&vd_source=da84cbebb0e49a0bb1c2d641c53da05d

在硬禾学堂的项目介绍(内容更完整):https://www.eetree.cn/project/3928#heading-2-%EF%BC%88%E4%B8%8A%E4%BD%8D%E6%9C%BA%E9%83%A8%E5%88%86%EF%BC%89-13

项目介绍

1.硬件介绍

项目基于STM32G031的口袋仪器训练平台,采用Arm Cortex M0+内核,主频最高可达64MHz。使用一个光电旋转编码器用于控制输入操作,实现采用时间,电平触发,单元格幅值切换等操作。SPI接口的128*128分辨率的OLED显示屏可以实现简单的波形显示和参数显示,用12bits ADC的双通道采集两路信号。设有PWM输出口实现单通道的信号输出。

2.方案框图和项目设计思路介绍

(1)方案框图

(2)项目设计思路

  • 任务要求在完成简易示波器的基础上,实现与上位机的交互,在PC端观察波形参数。所以任务要求我们不仅要在cubeide等嵌入式开放平台编程,还需要学习掌握部分上位机开发的编程。所以总的来看,项目需要分别完成从机和主机的代码编写,在调试过程中,利用串口通信实现不断修改调整。
  • 在单片机方面,项目需要利用到多个定时器(内部时钟,PWM输出,触发信号),ADC(信号采集),SPI(OLED),UART(串口通信),DMA(保证ADC数据的快速传输),Key(输入控制),Buzzer(功能响应提示)等等。
  • 在上位机方面,项目需要将单片机同PC连接起来,利用PC的数据处理能力和大屏幕显示,实现更好的测试测量效果。具体需要完成:a.串口数据接收.b.多线程执行及调用.c.滤波处理及双通道波形显示.d.FFT快速傅里叶变换及频谱显示.e.信号发生器界面显示.f.上位机窗口参数设置等等。

3.软件流程图和关键代码介绍

(1)软件流程图

如图所示,本项目的软件流程分为从机(单片机)和上位机(PC)两个部分,两者之间利用串口进行通信和交互。

(2)关键代码

A.单片机(CubeIDE-C语言)

1.串口重定向:通过串口重定向,之后发送数据可以使用printf()函数,像c语言打印数据一样的格式像串口发送数据,即printf(“对应的打印符",变量);

  • ptr指向要发送的数据的指针。
  • len要发送的数据的长度。
  • 0xffff发送超时时间,单位为毫秒。这里设置为 0xffff 表示无限等待,直到数据发送完成。

下面为串口重定向的声明和使用处的部分代码。

extern UART_HandleTypeDef hlpuart1;

__attribute__((weak)) int _write(int file, char *ptr, int len)
{
	 if(HAL_UART_Transmit(&hlpuart1,ptr,len,0xffff) != HAL_OK)
	 {
		 Error_Handler();
	 }
}

串口重定向(位置...\core\Inc\uart.h)

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(k == 0){printf("C1%d\r\n",val);}
                if(k == 1){printf("C2%d\r\n",val);}
                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;
            }

利用printf()函数像上位机发送两个通道采集的数据(位置...\core\App\Scope\UI.c)

2.触发位置调整:为了确保波形显示的美观,同时保证波形显示效果稳定,此处利用下降沿触发计算与方差中心最小的点,以该点作为触发点,显示采集到的波形。

  • edges_cnt[!scope_tri_edge] 表示非当前触发边沿的边沿数量。由于输入反相,输入上升沿对应数据下降沿,所以使用 !scope_tri_edge 来访问边沿数组。
  • 对于每个边沿位置 edges[!scope_tri_edge][i],计算其与样本中心位置的差值 diff
  • 如果 diff 小于当前的 min_diff,则更新 min_diff 和 min_diff_p
uint16_t min_diff = UINT16_MAX, min_diff_p = 0;
            for (uint16_t i = 0; i < edges_cnt[!scope_tri_edge]; i++) { // 因输入反相,输入上升沿是数据下降沿
                int diff = abs((int) edges[!scope_tri_edge][i] * 2 - SCOPE_SAMPLE_NUM);
                if (diff < min_diff) {
                    min_diff = diff;
                    min_diff_p = edges[!scope_tri_edge][i];
                }
            }
            uint16_t tri_p = min_diff_p;

调整显示波形触发点(位置...\Core\App\Scope\sample.c)

B.上位机(Visual Studio窗体应用编程-C#语言)

1.FFT频谱图

此处使用了MathNet.Numerics.IntegralTransforms库中的Fourier.Forward()函数,将complexData复数数组内的数据自动进行FFT变换。

  • data.Average()计算了一段数据点内元素的平均值。这段平均值可以看做是直流分量的大小。
  • ToList() 方法将经过转换后的元素收集到一个新的 List<double> 中,这个新列表 dataWithoutDC 就是去除了直流分量的数据。
  • Complex 是 .NET 中用于表示复数的类型,它的构造函数 new Complex(x, 0) 表示创建一个实部为 x,虚部为 0 的复数。使用 Select 方法对 dataWithoutDC 中的每个元素进行转换,将其转换为对应的复数。
  • ToArray() 方法将转换后的复数元素收集到一个 Complex 类型的数组 complexData 中,因为 FFT 算法通常需要处理复数形式的数据。
 // 计算直流分量
            double dcComponent = data.Average();
            // 去除直流分量
            List<double> dataWithoutDC = data.Select(x => (double)x - dcComponent).ToList();
            // 将输入数据转换为复数数组
            Complex[] complexData = dataWithoutDC.Select(x => new Complex(x, 0)).ToArray();
            // 执行 FFT 变换
            Fourier.Forward(complexData, FourierOptions.NoScaling);
            // 计算采样率和频率分辨率
            double frequencyResolution = currentSampleRate * 8 / complexData.Length;
            // 清空之前的 FFT 数据点
            fftSeries.Points.Clear();

部分FFT变换代码

2.组合滤波器

由于传输过程中存在不可避免的噪声干扰,波形时常会有尖波和混叠的情况产生,于是我引入了组合滤波器对波形数据进行滤波。

  • 初始化输出列表:创建一个空的列表 filteredData 用于存储滤波后的结果。
  • 遍历输入数据:当 i 大于等于 MedianWindowSize - 1 时,从输入数据中选取长度为 MedianWindowSize 的窗口数据,依次应用中值滤波器和自适应阈值滤波器,并将滤波后的结果添加到 filteredData 中。当 i 小于 MedianWindowSize - 1 时,直接将输入数据的元素添加到 filteredData 中。
  • 返回滤波结果:返回 filteredData
private List<int> ApplyCombinedFilter(List<int> data)
        {
            List<int> filteredData = new List<int>();
            for (int i = 0; i < data.Count; i++)
            {
                if (i >= MedianWindowSize - 1)
                {
                    var window = data.Skip(i - MedianWindowSize + 1).Take(MedianWindowSize).ToList();
                    int medianFiltered = MedianFilter(window);
                    int adaptiveFiltered = AdaptiveThresholdFilter(window, medianFiltered);
                    filteredData.Add(adaptiveFiltered);
                }
                else
                {
                    filteredData.Add(data[i]);
                }
            }
            return filteredData;
        }

组合滤波器遍历数据

4.功能展示图及说明

(单片机部分)

(此处可以参考B站视频或者我在硬禾学堂的项目
)

(上位机部分)

通道一的波形和频谱图显示(输入波形为方波,频率为200Hz)

通道二的波形和频谱图显示(输入波形为三角波,频率为200Hz)

项目内容下载

通过网盘分享的文件:Windows_chart_serial.zip等2个文件
链接: https://pan.baidu.com/s/14aZb1q5IBSEN5jf27jVAMQ 提取码: 1732

喜欢的话麻烦点赞收藏加关注,之后我会发布更多有关单片机和嵌入式相关的帖子与各位一起交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值