视频讲解及演示
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
喜欢的话麻烦点赞收藏加关注,之后我会发布更多有关单片机和嵌入式相关的帖子与各位一起交流。