此为“用C#进行仪器控制系列--数字万用表”的第4篇文章,在此之前已经编写了3篇相关文章了,可参见:
1) 用C#进行仪器控制系列——数字万用表(1) :该篇文章介绍了如何从零开始创建一个控制台应用程序,实现 Keysight 数字万用表的电压测量,并将结果通过控制台展示;
2) 用C#进行仪器控制系列——数字万用表(2) :该篇文章在第1篇文章的基础上,以窗体应用程序的形式重写了万用表电压测量程序,并能够自动识别仪器地址,以及能够通过按钮控制测量的开始和停止,电压的显示等。
3) 用C#进行仪器控制系列——数字万用表(3) :该篇文章在第2篇文章的基础上,通过代码的简单优化,实现了不同测量类型的下拉选择,根据下拉选项执行对应类型的测量功能(电压、电流、电阻、二极管、导通、频率测试等)。
此篇文章是在第3篇文章的基础上,添加波形图表显示功能。
优化需求:添加波形图表显示功能,用以观测数据的变化趋势
通过添加波形图表功能,将测量数据实时传递给波形图表,使其能够在界面中显示测量的历史数据,就可以观测到数据的变化趋势了。最终想要实现的效果如下图所示。

需求实现效果
优化实现过程
1)窗体界面的优化
- 从工具箱中找到 Visual Studio 自带的 Chart 图表控件,并将其拖拽至窗体面板中。

Chart控件
- 选中新添加的 Chart 控件,在其属性窗口中找到图表的“Series”属性,然后点击该属性右侧的3个小圆点组成的按钮进入“Series”属性设置页,如下图所示

图表的“Series”属性
- 修改数据序列的名称(Name)为“Data”,修改图表类型(ChartType)为“Line”,即修改图表类型为折线图显示。

修改“Series”属性
- 设置完图表属性后,调整窗体上相关控件的布局,调整后如下图所示。

调整窗体布局
2)代码优化
主要涉及如下3块代码段的修改:
- 在窗体的构造函数中增加曲线图样式的设置语句,包括曲线样式、曲线颜色的设置。
public Form1()
{
InitializeComponent();
// 设置曲线图样式
WaveChart.Series[0].ChartType = SeriesChartType.Line;
WaveChart.Series[0].Color = System.Drawing.Color.Blue;
}
- 在开始测试的事件响应代码段添加清除曲线代码,实现开始测试时重新绘图。
if (btnStart.Text == "开始测试")
{
btnStart.Text = "停止测试";
// 清空曲线图数据
WaveChart.Series[0].Points.Clear();
- 在测量线程代码段,增加将实时测量数据传递给波形图表的代码。
// 循环读取万用表数据
while (true)
{
// 发送采集命令
messageBasedSession.WriteString(":READ?", true);
// 读取返回值
string response = messageBasedSession.ReadString();
// 在控件和图表中输出结果
this.Invoke((MethodInvoker)delegate
{
resultTextBox.Text = ChangeDataToD(response.Trim());
WaveChart.Series[0].Points.AddY(double.Parse(resultTextBox.Text));
});
}
优化效果验证
程序优化后的运行效果如下图所示,可通过下拉选择测量类型,实现不同的测试需求,然后通过Text Box显示控件和Chart波形图表显示数据。

运行效果
通过验证,可以完美的实现测量需求。
附:本文中涉及到的完整源代码
using System;
using System.Resources;
using System.Windows.Forms;
using Ivi.Visa.Interop;
using System.Threading;
using System.Linq.Expressions;
using System.Windows.Forms.DataVisualization.Charting;
namespace WinForm_ComPorts
{
public partial class Form1 : Form
{
private Ivi.Visa.Interop.ResourceManager resourceManager;
private FormattedIO488 messageBasedSession;
private Thread measurementThread;
public Form1()
{
InitializeComponent();
// 设置曲线图样式
WaveChart.Series[0].ChartType = SeriesChartType.Line;
WaveChart.Series[0].Color = System.Drawing.Color.Blue;
}
#region 端口号下拉选择框下拉事件响应代码
/// <summary>
/// 端口号下拉选择框下拉事件响应代码
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cboComPorts_DropDown(object sender, EventArgs e)
{
try
{
// 清除下拉列表
this.cboComPorts.Items.Clear();
// 获取设备的所有串口资源
string[] comNames = System.IO.Ports.SerialPort.GetPortNames();
// 添加可用串口号到下拉框中
foreach (string s in comNames)
{
this.cboComPorts.Items.Add(s);
}
// 创建VISA资源对象实例
Ivi.Visa.Interop.ResourceManager rm = new Ivi.Visa.Interop.ResourceManager();
// 获取设备的所有仪器资源
string[] usbNames = rm.FindRsrc("?*INSTR");
// 添加可用USB资源到下拉框中
foreach (string s in usbNames)
{
if (s.Contains("0x"))
this.cboComPorts.Items.Add(s);
}
}
catch (Exception)
{
MessageBox.Show("系统内不存在此类仪器~", "ERROR");
}
}
#endregion
#region 开始测试/停止测试按钮事件响应代码
/// <summary>
/// 开始测试/停止测试按钮事件响应代码
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnStart_Click(object sender, EventArgs e)
{
try
{
if (btnStart.Text == "开始测试")
{
btnStart.Text = "停止测试";
// 清空曲线图数据
WaveChart.Series[0].Points.Clear();
// 创建ResourceManager和MessageBasedSession对象
resourceManager = new Ivi.Visa.Interop.ResourceManager();
messageBasedSession = new FormattedIO488();
// 打开万用表连接,万用表的USB通讯地址通过端口号下拉列表的值传递过来
messageBasedSession.IO = (IMessage)resourceManager.Open(cboComPorts.Text.ToString(), AccessMode.NO_LOCK, 5000, "");
//获取测量类型,根据测量类型选择相应的采集指令
string ConfigCommand = "";
switch (cboMeasType.Text)
{
case "DC Voltage":
ConfigCommand = ":CONF:VOLT:DC AUTO,DEF";
break;
case "AC Voltage":
ConfigCommand = ":CONF:VOLT:AC AUTO,DEF";
break;
case "DC Current":
ConfigCommand = ":CONF:CURR:DC AUTO,DEF";
break;
case "AC Current":
ConfigCommand = ":CONF:CURR:AC AUTO,DEF";
break;
case "Resistance 2-wire":
ConfigCommand = ":CONF:RES AUTO";
break;
case "Resistance 4-wire":
ConfigCommand = ":CONF:FRES AUTO";
break;
case "Diode":
ConfigCommand = ":CONF:DIOD";
break;
case "Continuity":
ConfigCommand = ":CONF:CONT";
break;
case "Frequency":
ConfigCommand = ":CONF:FREQ";
break;
default:
ConfigCommand = ":CONF:VOLT:DC AUTO,DEF";
break;
}
// 设置采集模式和测量范围
messageBasedSession.WriteString(ConfigCommand, true);
// 创建线程执行连续测量
measurementThread = new Thread(new ThreadStart(MeasureThread));
measurementThread.Start();
}
else
{
btnStart.Text = "开始测试";
// 停止测量线程
if (measurementThread != null && measurementThread.IsAlive)
{
measurementThread.Abort();
measurementThread.Join();
}
// 关闭连接
if (messageBasedSession != null)
{
messageBasedSession.IO.Close();
// messageBasedSession.Dispose();
}
if (resourceManager != null)
{
// resourceManager.Dispose();
// MessageBox.Show("资源释放");
}
}
}
catch (Exception ex)
{
MessageBox.Show("发生错误:" + ex.Message);
}
}
#endregion
#region 测量线程代码
/// <summary>
/// 测量线程,读取万用表的数据,并将结果反馈至resultTextBox控件
/// </summary>
private void MeasureThread()
{
try
{
// 循环读取万用表数据
while (true)
{
// 发送采集命令
messageBasedSession.WriteString(":READ?", true);
// 读取返回值
string response = messageBasedSession.ReadString();
// 在控件和图表中输出结果
this.Invoke((MethodInvoker)delegate
{
resultTextBox.Text = ChangeDataToD(response.Trim());
WaveChart.Series[0].Points.AddY(double.Parse(resultTextBox.Text));
});
}
}
catch (ThreadAbortException)
{
// 线程被中断
// MessageBox.Show("线程被中断");
// 查询错误
messageBasedSession.WriteString(":SYST:ERR?");
// 读取返回的错误信息
string response = messageBasedSession.ReadString();
// 若仪器返回了“-410”错误(读取万用表读数时被中断导致)或无错误,则忽略
if (response.Contains("-410") || response.Contains("No error")) { }
else
{
MessageBox.Show(response);
}
}
catch (Exception ex)
{
MessageBox.Show("发生错误:" + ex.Message);
}
}
#endregion
#region 数据显示格式转换代码
/// <summary>
/// 将科学计数法表示的字符串数据转换为double型数据显示格式
/// </summary>
/// <param name="strData"></param>
/// <returns>返回double型数据格式的字符串</returns>
private string ChangeDataToD(string strData)
{
Double dData = 0.0;
if (strData.Contains("E"))
{
dData = Double.Parse(strData);
}
return dData.ToString();
}
#endregion
}
}