『7x24小时有问必答』

前言

调试硬件设备的时候,手边没有示波器是常有的事。要么设备在产线上搬不出来,要么实验室的示波器被别人占着。这时候如果能有一个软件示波器,直接读取设备上报的数据在屏幕上画波形,调试效率会高很多。
本文要讲的是一套模拟工业示波器的实现。它基于WPF和DevExpress Charts控件,界面风格参照了真实示波器的操作面板,支持波形生成、参数调节、测量数据显示等常用功能。虽然目前是模拟数据源,但底层架构已经预留了对接真实数据接口的位置。

项目介绍

一款模拟工业示波器的桌面软件,UI配色和交互方式都参考了真实的数字示波器。主界面分左右两区:左侧是参数控制面板,右侧是波形显示区和测量数值面板。
技术实现上用的是WPF + MVVM模式,图表部分用的是DevExpress Charts组件。波形生成逻辑在ViewModel中完成,通过绑定实时更新界面,整体操作响应比较流畅。
这套代码适合拿来学习WPF的复杂界面布局、MVVM数据绑定、以及工业软件界面设计。对做设备调试工具的人来说,也可以作为真实示波器软件的原型参考。

项目功能

功能
说明
波形类型选择
支持正弦波、方波、三角波、锯齿波四种常用波形
频率调节
1-100Hz范围内调节信号频率
振幅调节
1-10V范围内调节信号幅值
占空比设置
方波模式下可选10%-90%占空比
噪声模拟
低、中、高三档噪声强度,模拟真实采样环境
采样率控制
可选1k-100k采样率,影响波形显示精细度
时间基准调节
0.1-10秒/格可调,控制波形水平缩放
垂直灵敏度调节
0.5-5V/格可调,控制波形垂直缩放
触发方式
支持自动、正常、单次三种触发模式
触发边沿
上升沿、下降沿可选
触发电平显示
波形区域显示红色触发电平参考线
波形启停控制
开始/停止按钮控制波形更新
测量数据显示
实时显示Vpp、Vrms、频率、周期四个关键参数
保存设置
保存当前所有参数配置到本地
保存波形数据
将当前波形数据导出为文件

项目特点

MVVM分层清晰:界面层只负责布局和绑定,所有业务逻辑在ViewModel里处理,改动界面不影响核心逻辑
DevExpress图表专业:用DXChart替代原生Chart控件,自带十字光标、轴刻度和网格线,少写不少样式代码
实时数据绑定:波形数据点集合绑定到图表,ViewModel按采样率定时刷新,界面自动响应
控件联动反馈:调节左侧参数时波形立即变化,不需要额外点击确认按钮
暗色工业主题:背景色、网格线、波形颜色都按真实示波器风格设计,适合长时间盯着看的场景
测量参数自动计算:每个波形周期自动算出峰峰值、有效值、频率和周期,省去手动读数

项目技术

技术
用途
C# .NET
开发语言
WPF
桌面UI框架
MVVM
架构模式,界面与逻辑分离
DevExpress Charts
专业图表控件,渲染波形
DevExpress Editors
下拉框、数值输入等编辑控件
System.Windows.Threading
定时器驱动波形刷新
ICommand
按钮命令绑定
INotifyPropertyChanged
属性变更通知
IDataErrorInfo
输入验证

项目代码

MainWindow.xaml 关键结构

主界面采用左右两栏布局,左栏是参数设置面板,右栏是波形显示区。
<grid>

      <grid.columndefinitions>

            <columndefinition width=""300"/">

            <columndefinition width=""*"/">

      </columndefinition></columndefinition></grid.columndefinitions>

      <!-- 左栏:控制面板 -->

      <border grid.column=""0"" background=""2D2D2D"" cornerradius=""5"" padding=""10"">

            <scrollviewer>

                  <stackpanel>

                        <textblock text=""信号源设置"" foreground=""White"" fontsize=""16""></textblock>

                        <!-- 波形类型、频率、振幅、占空比、噪声 -->

                        <dxe:comboboxedit editvalue=""{Binding" settings.wavetype}"[="" span][="" span]=""></dxe:comboboxedit>

                        <dxe:spinedit editvalue=""{Binding" settings.frequency}"[="" span][="" span]=""></dxe:spinedit>

                        <!-- 省略其他控件 -->

                  </stackpanel>

            </scrollviewer>

      </border>

      <!-- 右栏:波形显示 -->

      <grid grid.column=""1"">

            <grid.rowdefinitions>

                  <rowdefinition height=""*""></rowdefinition>

                  <rowdefinition height=""120""></rowdefinition>

            </grid.rowdefinitions>

            <border background=""Black"" cornerradius=""5"">

                  <dxc:chartcontrol datasource=""{Binding" wavepoints}"[="" span][="" span]="">

                        <dxc:xydiagram2d>

                              <!-- X轴:时间 -->

                              <!-- Y轴:电压 -->

                              <dxc:lineseries2d argumentdatamember=""Time"

" valuedatamember=""Value"

" brush=""Yellow""></dxc:lineseries2d>

                        </dxc:xydiagram2d>

                  </dxc:chartcontrol>

            </border>

            <!-- 底部:测量数值 -->

            <border grid.row=""1"">

                  <uniformgrid columns=""4"">

                        <textblock text=""{Binding" vpp,="" stringformat="{}{0:F2}" v}"[="" span][="" span]=""></textblock>

                        <textblock text=""{Binding" vrms,="" stringformat="{}{0:F2}" v}"[="" span][="" span]=""></textblock>

                        <textblock text=""{Binding" avgfreq,="" stringformat="{}{0:F1}" hz}"[="" span][="" span]=""></textblock>

                        <textblock text=""{Binding" period,="" stringformat="{}{0:F6}" s}"[="" span][="" span]=""></textblock>

                  </uniformgrid>

            </border>

      </grid>

</grid>

ViewModel 核心逻辑

波形生成方法根据当前选择的波形类型计算对应的电压值。
private void  GenerateWaveform()

{

      var points = new List<wavepoint>();

      double amplitude = Settings.Amplitude;

      double frequency = Settings.Frequency;

      double timeBase = Settings.TimeBase;

      double samplingRate = Settings.SamplingRate;

      int pointCount = (int)(timeBase * samplingRate * 2);

       for  (int i = 0; i < pointCount; i++)

      {

            double t = (double)i / samplingRate;

            double value = 0;

            switch (Settings.WaveType)

            {

                   case  WaveType.Sine:

                        value = amplitude * Math.Sin(2 * Math.PI * frequency * t);

                         break;

                   case  WaveType.Square:

                        double duty = (double)Settings.DutyCycle / 100;

                        double period = 1 / frequency;

                        value = (t % period) / period < duty ? amplitude : -amplitude;

                         break;

                   case  WaveType.Triangle:

                        double triPeriod = 1 / frequency;

                        double triPhase = (t % triPeriod) / triPeriod;

                        value = amplitude * (triPhase < 0.5 ? 4 * triPhase - 1 : 3 - 4 * triPhase);

                         break;

                   case  WaveType.Sawtooth:

                        double sawPeriod = 1 / frequency;

                        double sawPhase = (t % sawPeriod) / sawPeriod;

                        value = amplitude * (2 * sawPhase - 1);

                         break;

            }

            // 叠加噪声

            value += GenerateNoise(Settings.NoiseLevel);

            points.Add(new WavePoint { Time = t, Value = value });

      }

      WavePoints = points;

      CalculateMeasurements(points);

}

触发逻辑处理

触发模式控制波形是否在满足条件时才开始绘制。
private bool CheckTrigger(double currentValue)

{

      double level = Settings.TriggerLevel;

      bool edgeMatched =  false;

       if  (Settings.TriggerEdge == TriggerEdge.Rising)

            edgeMatched = currentValue >= level;

       elseif  (Settings.TriggerEdge == TriggerEdge.Falling)

            edgeMatched = currentValue <= level;

       if  (!edgeMatched)  returnfalse;

      switch (Settings.TriggerMode)

      {

             case  TriggerMode.Auto:

                   returntrue;

             case  TriggerMode.Normal:

                   return  _triggerArmed;

             case  TriggerMode.Single:

                   if  (_triggerArmed)

                  {

                        _triggerArmed =  false;

                         returntrue;

                  }

                   returnfalse;

            default:

                   returnfalse;

      }

}

项目效果

软件启动后主界面分为左右两区。左侧控制面板按功能分组:信号源设置、采样与时间轴、垂直系统与触发、控制按钮。右侧是波形显示区,背景为黑色,黄色波形线显示在网格中,右上角显示当前时间基准和垂直灵敏度。
调节左侧的频率旋钮时,右侧波形立即变密或变疏;调节振幅时波形上下幅度同步变化。切换波形类型时,波形形状实时切换,方波模式下占空比选项会自动显示。
波形区域支持鼠标悬停查看十字光标读数,可以精确定位某个时间点的电压值。触发电平线用红色虚线显示在波形区域中,触发状态在左上角用"已触发"或"等待触发"字样指示。
底部四个数值面板分别显示Vpp、Vrms、频率和周期。波形运行时这些数值每秒更新一次,和真实示波器的读数体验基本一致。

项目源码

项目结构如下:
├── Models/

│    ├── WavePoint.cs                     # 波形数据点

│    ├── WaveType.cs                     # 波形类型枚举

│    └── OscilloscopeSettings.cs     # 所有参数配置

├── ViewModels/

│    └── MainViewModel.cs               # 主视图模型,包含波形生成逻辑

├── Views/

│    └── MainWindow.xaml                 # 主界面布局

├── Converters/

│    ├── WaveTypeToVisibilityConverter.cs   # 波形类型控制控件显隐

│    ├── MultiplyConverter.cs                     # 电压范围计算

│    └── NegativeConverter.cs                     # 数值取反

└── Commands/

      └── RelayCommand.cs                 # ICommand实现

关键的依赖项是DevExpress.Wpf.Charts和DevExpress.Wpf.Editors。如果本地没有安装DevExpress,需要替换成其他图表库或者用原生控件重写图表部分。

总结

这套模拟示波器展示了一个工业风格桌面软件从界面到逻辑的完整实现。MVVM模式让界面和逻辑解耦,DevExpress Charts提供了专业的图表渲染能力,左侧参数面板的实时联动反馈是工业软件的标配体验。
如果只是用来学习WPF工业界面开发,这套代码的结构和风格可以直接参考。如有真实的波形数据源(比如从串口或TCP读取设备数据),只需要替换GenerateWaveform方法中的数据来源,就能把模拟示波器变成真实的设备调试工具。

关键词

最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠]  社区,与其他热爱技术的同行一起交流心得,共同成长!
作者:小码编匠
出处:gitee.com/smallcore/DotNetCore
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!

END

推荐阅读

觉得有收获?不妨分享让更多人受益
关注「DotNet技术匠」,共同提升技术实力

收藏
点赞
分享
在看

</wavepoint>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码