数据采集库
一个简单、灵活且可扩展的基于 C# 和 .Net 的数据采集库,可以处理多个硬件设备,数据速率超过 10 Mbps。
引言
在数据采集软件中,需要处理许多挑战,例如处理数据速率、设备处理、内存管理和其他问题。该库解决了许多与数据采集相关的问题。在本文中,我将介绍我在许多数据采集和实时数据显示项目中所开发的库。库代码已附在此文章中。
该库完全由我使用 .Net 和 C# 编程开发。它已在多个项目中进行了测试,我已将其测试为处理速率超过 10 Mbps 的数据。但它也可以支持更高的数据速率,具体取决于您使用的采集设备。我在许多数据采集项目中使用了此库或其修改形式。
该库高度可扩展且灵活,采用标准设计模式和原则构建。一个基本原则是“面向接口编程,而非实现”,该原则在该库中得到了广泛应用。通过使用此原则,库的用户可以编写自己的对库中提供的许多接口的实现。这样,用户就可以为自己的应用程序扩展此库。
库的特性
数据采集
该库可以采集多种数据速率的数据。大多数情况下,该库不与数据速率绑定,但您可以根据需要更改参数来处理数据。该库会立即将队列中的数据提供给用户;如果没有数据可用,则返回 Null。只要收到数据请求,数据就会立即传递给用户;如果在请求时没有数据可用,则返回 Null。
数据保存
该库还支持同时进行数据采集和数据保存功能。数据一到,就会存储到硬盘上的指定位置。用户可以通过指定相关参数来控制此行为。
内存管理
如果您使用的是 32 位操作系统,它最多只能允许程序使用 2 GB 的内存。现在,如果您的数据速率约为 2 到 6 Mbps,并且您将数据存储在内部内存(RAM)中,例如在数组中,那么不到一个小时就会完全填满您的内存。为了防止这种情况,您需要在到达一定量的数据后清空内存缓冲区。该库具有此功能,并为用户自动处理内存。该库在到达一定数据量后会清空并调整缓冲区大小。
多线程和线程安全代码
数据采集和数据保存都与调用线程在单独的线程中执行。这使我们能够提高性能,并且不会阻塞调用线程。
对设备的调用是线程安全的,这意味着如果设备调用例程未完成,另一个线程将不会进入该例程。这使我们能够确保数据完整性,并且我们的数据在采集和保存过程中不会损坏。
多个设备
该库的一个主要特性是它可以处理或与多个设备协同工作。用户只需为该设备实现最简单的接口,并将该实现提供给该库,该库就会开始从该特定设备采集数据。
多种帧格式
该库可以搜索并从传入的数据中提取特定帧。这些数据帧可以是任何格式。它们可以包含头部来指示帧的开始或结束。该库可以检测并提取这些帧,并将这些帧而不是原始数据传输给用户。用户也可以提供用于在数据中查找帧的实现。
如何使用此库
库类图

让我们来谈谈这个类图。DataAcquisisiton 是主类。它引用其他类的接口。它对类的具体实现一无所知。每个具体类都实现相应的接口。DataAcquisition 类负责在内部写入数据,而 FrameSearcher 和 FrameFormatter 则从外部传递给它。为了演示,我包含了两个设备实现和两个帧搜索算法实现。
可以使用 Idisplay 将一些信息显示给用户屏幕,例如 WinForms 或控制台。
数据采集的一般步骤
这个库是如何实现的?该库实现了一些通用步骤。
- 从设备采集数据
- 如果需要,搜索数据帧
- 格式化数据(如果需要)
- 将数据传递给用户
库中的一个线程持续调用设备进行数据采集,然后搜索数据中的帧,如果找到数据,则格式化该数据,然后将该帧数据传递给用户。该线程持续调用设备并执行上述步骤。以下代码展示了如何实现对方法的持续调用:
AutoResetEvent autoEvent = new AutoResetEvent(false);
TimerCallback continuousTimerCallback = new TimerCallback(acquireData);
device.Refresh();
threadTimer = new System.Threading.Timer(continuousTimerCallback, autoEvent, 0, device.GetSubseque ntReadTimeDelay() );
autoEvent.WaitOne(-1);
此代码展示了上述步骤的实现方式:
byte[] tempDataBuffer = device.Read();
if (tempDataBuffer != null)
{
// copy the data to the main buffer
internalMemoryBuffer.AddRange(tempDataBuffer);
// this will search and extract frames in the internal memory
SearchFormatHandoff();
EmptyInternalBuffer();// Empty the internal buffer and /or write the data chunk to hard disk }
用户可以根据以下代码启用或禁用数据采集的某些步骤:
public void EnableDataWriting(bool aval)
{
//check the user if data acquisition open do not set the control variables.
if (!stopDataAcquisition)
{
isDataWritingRequired = aval;
if (isDataWritingRequired)
this.dataWriter = new DataWriter();
}
}
public void EnableFrameSearching(bool aval)
{
isFrameSearchingRequired = aval;
}
public void EnableDataFormatting(bool aval)
{
isDataFormattingRequired = aval;
}
如您在上面的代码中所见,用户可以启用或禁用数据写入、帧搜索和帧格式化。通过这种方式,用户可以灵活地控制数据采集。
如何初始化、启动和停止数据采集?
NetworkAcquisitionDevice device = new NetworkAcquisitionDevice("100.0.0.1",51212,30);
FrameSearcherSecond frameSearcher = new FrameSearcherSecond();
FormatterXorByte formatterCust = new FormatterXorByte();
DataAcquisition daq = new DataAcquisition(device, frameSearcher, formatterCust);
daq.EnableDataFormatting(true);
daq.EnableFrameSearching(true);
daq.EnableDataWriting(true); // data wil be in your harddisk.
daq.StartAcquisition();
bool stop =false;
while (!stop)
{
byte[] dataFromDevice= daq.GetData();
//process data here
}
daq.StopDataAcquisition();
daq.Reset(); // it will reset all parameters.
在上面的代码中,我使用了/设置了网络数据采集。您也可以使用/设置 USB 数据采集。您只需将 NetworkAcquisitionDevice 更改为 USBAcquisitionDevice 即可。
如何从库中消费数据?
用户可以通过两种方式利用该库。一种是库本身将数据推送到调用用户(也称为推送方法),另一种是用户调用库来获取数据(也称为拉取方法)。我已在这两种方式下测试了该应用程序。
第一种方法,当您从库中拉取数据时,请确保尽快获取可用数据。这对于更新用户显示至关重要。在这种情况下,用户将需要维护用户屏幕的帧速率。我还使用了阻塞集合,并将限制设置为 30 个数据帧。如果您获取数据的速度不够快,阻塞集合将不会在 30 个项目处阻塞。因此,在实现库时请牢记这一点。
第二种方法是实现 IDisplay 接口,因为库拥有 IDisplay 接口的引用,它可以调用显示屏幕代码。这里需要注意的一点是,在 WinForms 的情况下,您必须使用 Control.BegiInvoke 来更新 Win Forms 控件,或者在 WPF 的情况下使用 Dispatcher。这是因为库是在与您创建该库的线程不同的线程中执行的。
以下代码将展示如何消费数据。
public byte[] GetData()
{
byte[] dataResultWithTry = null;
bool isDataAvailable = this.transferBuffer.TryTake(out dataResultWithTry);
return dataResultWithTry;
}
这是您可以在其中推送库的代码。它位于 DataAcquisition 类和 acquireData() 方法中。
// now Hand off data..
transferBuffer.TryAdd(locData);
//publisher.UpdateUserInterface(locData);
如何扩展此库?
如何扩展以支持多个设备?
可以使用该库来扩展 PCI 接口、PCIe 接口、并行端口接口和其他接口。为此,用户需要实现一个非常简单的设备采集接口,例如打开设备、关闭设备、刷新设备、从设备获取数据字节。用户还可以通过实现 IAcquisitionDevice 接口的方法 GetSubsequentReadTimeDelay() 来配置库在多长时间(毫秒)后可以调用设备。通过这种方式,可以使用多个设备通过该库进行数据采集。
我已在许多设备上测试了此库,例如串行端口、FTDI USB 设备、National Instrument 数据采集设备以及网络(LAN 和 TCP/IP)。为了演示,我包含了 USB 端口接口和网络接口。
如何扩展其他功能?
其他可以轻松扩展的处理功能包括帧搜索器、帧数据格式化和显示接口。
可以通过实现一个非常简单的接口 IFrameSearcher 来扩展帧搜索。同样,可以通过实现一个非常简单的接口 IFormatFrameData 来扩展帧数据格式化。类似地,用户可以通过实现 IDisplay 接口来扩展许多用户界面。用户可以为 WinForms、控制台或 WPF 提供实现。
参考文献
在 Github 上查看此代码:https://github.com/engrumair/DataAcquisitionLibraryDemo
