SerialComms.Manager - 一个适用于 .NET 的串口通信插件
使用此组件,轻松为 .NET 程序添加串口通信功能。
引言
SerialComms.Manager 是一个用 C# 编写的 .NET 组件,提供完整的 RS232 串口通信功能。
它是一个 DLL,可以简单地添加到任何项目中,并利用 System.IO.Ports.SerialPort 类。
它提供了一个接口,用于选择和设置串口,以及发送和接收数据。只需几行代码即可使其工作。
它提供了一个接口,用于选择和设置串口,以及发送和接收数据。只需几行代码即可使其工作。
背景
PC 不再配备用于串口通信的板载 UART,通过机箱背面的 DB9 插头进行访问。
尽管如此,RS232 协议并未消亡。许多设备仍然可以通过串口通信进行访问。
例如数据记录和医疗仪器、GPS 设备以及当今无处不在的创客设备,如 Arduino。
访问通常通过作为 USB 设备接口实现的虚拟 COM 端口进行。
访问通常通过作为 USB 设备接口实现的虚拟 COM 端口进行。
为什么已经有很多关于实现串口通信的文章,还要再写一个串口通信类呢?
许多文章提供了可以直接剪切粘贴到开发人员代码中的样板代码,
另一些则根据作者的想法提供了完整的应用程序模型。我多次实现串口通信功能,
发现我需要的功能在其他解决方案中并不总是可用。
这促使我创建了一个完整的组件 SerialComms.Manager。它可以很容易地添加到项目中,
具有易于使用的接口,并且开箱即用。我尝试实现了在实际情况下使用该组件所需的基本功能,
例如数据采集或通过串口配置设备。
例如数据采集或通过串口配置设备。
.NET 提供了 System.IO.Ports.SerialPort 类来实现串口通信。
它本身是 C 和 C++ 程序员熟悉的 Win32 类的包装。SerialPort 实现了 IDisposable 接口,
因为非托管资源必须在使用完毕后释放。SerialPort 类使用起来很简单,
但任何在互联网上的研究都会表明其实现不够完整。
这可能是因为 RS232 串口通信不再像以前那样重要。Open() 和 Close() 方法就是一个很好的例子。
MSDN 关于 Close() 的说明指出:
对于任何应用程序来说,最佳实践是在调用 Close 方法后等待一段时间,然后尝试调用 Open 方法,因为端口可能不会立即关闭。
等待多长时间没有说明,当然也没有人知道,因为正在释放一个 Win32 句柄,所需时间取决于许多因素。
从串口接收数据有两种方法。一种是从主线程连续轮询设备,
检查数据是否已到达,然后读取它——同步操作。
另一种是有一个专门用于读取数据的线程,然后该线程被阻塞,
直到数据到达事件发生——异步操作。然后该线程处理数据,
允许主线程继续做其他事情。如果数据定期到达,这是维持用户界面响应的唯一方法。
允许主线程继续做其他事情。如果数据定期到达,这是维持用户界面响应的唯一方法。
.NET 的早期版本要求用户在需要异步操作时设置一个工作线程,
但现在 SerialPort 类为此提供了 DataReceived 事件。
当从 SerialPort 对象接收到数据时,DataReceived 事件会在一个辅助线程上引发。
使用它所需做的就是提供一个事件处理方法并将其附加到 DataReceived 事件。
这使得主线程可以用于更新用户界面或发送数据。
解决方案
串口通信组件需要哪些功能?它必须能够:
- 枚举可用端口并获取其各自的能力。
- 选择一个端口并设置其操作参数。
- 在端口上接收数据——异步。
- 在端口上发送数据。
- 保存和恢复参数。
SerialComms.Manager 组件通过这些类实现此功能:
_SerialCommsManager
提供了组件的接口。它是一个包含 _Serialport
的容器,而 _Serialport
本身又包含一个 System.IO.Ports.SerialPort。它使用 _SerialPortSettingsXML
将其设置保存为 XML。在构造过程中,XML 文件从默认位置检索,其设置用于实例化一个 _Serialport
(如果文件不存在则使用默认设置)。如果需要,可以调用 _SerialPortSettingsForm
对话框来选择端口并修改其设置。设置以 _SerialPortSettings
对象的形式返回。设置对话框如下图所示:
一旦选择了端口并设置正确,就可以打开连接并接收和发送数据。
以下截图显示了一个 WinForm 客户端显示 GPS NMEA 数据。
串口配置保存在当前目录下的 SerialPortSettings.xml XML 文件中。其格式如下:
<!--?xml version="1.0"?-->
<_SerialPortSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<_useWMI>true
<_BaudRate>19200
<_Handshake>None
<_Parity>None
<_PortName>COM3
<_StopBits>One
<_DataBits>8
工作原理
可用串口名称通过 System.IO.Ports.SerialPort.GetPortNames 获取。它提供了一个诸如 COM1、COM2 的名称数组。如果可能,通过 WMI 查询获取有关端口的附加信息。这是设置对话框中的端口描述。可以使用反射来访问 SerialPort 的 BaseStream 并获取一个 COMMPROP 结构。这提供了一些端口能力的枚举。这些用于完善端口属性选择列表。
System.IO.Ports.SerialPort 实现了 DataReceived 事件。此事件未通过接口公开。相反,定义了两种新的事件类型,并通过委托公开。当调用 _SerialCommsManager 的构造函数时,将相应的事件处理程序作为参数传递。当 DataReceivedEvent 触发时,它由 DataReceivedHandler 处理。这反过来又会触发两个新事件,NewSerialDataBytesReceived
和 NewSerialDataStringReceived
。这些是应用程序可以订阅的事件。事件处理程序在单独的线程中运行,因此此处可以进行的任何处理都将释放其他线程。
使用代码
该组件使用起来非常简单。提供了 Winforms SerialCommsClient 示例以提供一个实际示例。不需要窗体来托管 _SerialCommsManager
对象。
要将组件集成到用户程序中,请按照以下步骤操作。
- 打开 Visual Studio 项目并 添加对 dll 的引用。
- 在代码中添加对
_SerialCommsManager
类的引用。
private _SerialCommsManager serialCommsManager = null;
- 定义一个具有所需签名的事件处理程序。此处理程序将处理数据字节事件。
void DataBytesReceived(object sender, _SerialDataBytesArgs data)
{
if (this.InvokeRequired)
{
// Invoke causes deadlock when closing serial port. BeginInvoke() doesn't block the event handler.
this.BeginInvoke(new EventHandler<_SerialDataBytesArgs>(DataBytesReceived), new object[] { sender, data });
return;
}
const int maxTextLength = 1000; // maximum text length in text box
if (textBoxData.TextLength > maxTextLength)
textBoxData.Text = textBoxData.Text.Remove(0, textBoxData.TextLength - maxTextLength);
// Convert bytes to ASCII text.
string str = Encoding.ASCII.GetString(data.BytesOut, 0, data.NumBytes);
textBoxData.AppendText(str);
}
此处理程序将处理数据字符串事件。
void DataStringReceived(object sender, _SerialDataStringArgs data)
{
if (this.InvokeRequired)
{
// Invoke causes deadlock when closing serial port. BeginInvoke() doesn't block the event handler.
this.BeginInvoke(new EventHandler<_SerialDataStringArgs>(DataStringReceived), new object[] { sender, data });
return;
}
const int maxTextLength = 1000; // maximum text length in text box
if (textBoxData.TextLength > maxTextLength)
textBoxData.Text = textBoxData.Text.Remove(0, textBoxData.TextLength - maxTextLength);
textBoxData.AppendText(data._String);
}
- 创建 _SerialCommsManager 类的一个实例。这将订阅所选的事件类型。
serialCommsManager = new _SerialCommsManager(DataBytesReceived);
这就是实例化 _SerialCommsManager
类所需的全部。如果管理器之前运行过,则保存的配置将从 XML 文件 SerialPortSettings.xml 中恢复。如果硬件配置未更改(例如,USB 串口适配器仍然插入),则可以调用 SCM_Start
打开端口。这将立即启用数据接收。SCM_Stop
将关闭端口,停止数据处理。此截图显示了 Arduino 传感器数据的显示。
也可以调用 SCM_Send
发送数据。在示例应用程序中,数据可以键入到发送窗口。数据一次发送一行。每次按下“Enter”键时,当前行的数据都会发送。光标可以放置在现有数据行上,双击会重新发送数据。
如果这是应用程序第一次运行,或者如果硬件配置已更改,则必须设置一个串口。要选择和设置可用串口,请调用 SCM_GetSettingsGUI
。这将打开设置对话框以允许进行选择。
关注点
有两个可用的下载。
- SerialComms.zip - 此解决方案包含创建 SerialComms.Manager.dll 的项目。它还包含一个客户端项目,演示如何将组件集成到应用程序中。
- SerialCommsApp.zip - 仅可执行文件(发布版),可作为完整应用程序运行。使用此文件可以了解 dll 的功能。
计划在未来的文章中展示如何将此组件用作基于 Arduino 的数据采集应用程序的基础。
历史
V1.0 文章发布 - 2014年6月18日