如果使用过 PCI 采集卡的盆友应该对 “研华” 这个品牌不陌生,可以说研华还是很强大的。最近因为工作需要,使用一块研华的 PCI1714UL 型号的采集卡,去高速采集电压信号,学习了几天后分享给各位。
采集卡
首先介绍一下这块采集卡。品牌:研华 Advantech,型号:PCI1714UL,基本参数:4 通道,12 位精度,采样频率:10MS/s,采样:8192 / 通道,可采集电压范围有:±5、±2.5、±1、±0.5,每个范围对应的绝对精度不同,所以视情况来决定需要什么样的采样范围。支持 PCI 总线控制 DMA 数据传输。
工作方式
这块采集卡有两种工作模式:BufferedAI、InstantAI。
BufferedAI:使用 PC 的一个存储块,可以实现总线控制数据采集,不需要占用 CPU;
InstantAI:瞬时值,采样率相对没那么高,适合一些对数据要求不算太高的场合。
资料下载
官网 资料
驱动安装
研华提供了一款 DAQNavi 软件(见 “资料下载” 去官网下载),有一个 Navigator,可以直接打开可以看到所有支持的卡,选中你需要的卡右键就可以安装驱动了。并且这款软件也提供数据采集展示。
软件二次开发:接口函数 ---Automation.BDaq4(见 “资料下载” 去官网下载)
一、使用研华提供的控件进行二次开发
研华提供了相关控件:
WaveformAiCtrl 用于 BufferedAI 读取,InstantAiCtr 用于 InstantAI 读取。
二、自己从底层开发
瞬时电压读取比较简单,直接创建 InstantAiCtrl,传递一个设备名称:instantAiCtrl.SelectedDevice = new DeviceInformation (deviceCode); 初始化:instantAiCtrl.Initialized,就可以开始读取数据了:instantAiCtrl.Read (channelIndex,out data [channelIndex])。
BufferedAI 代码量多一点:首先需要创建一个线程,用于将采集卡数据读取到内存中,再开一个线程专门见内存中的数据往本地写入(因为数据量太大不能一下处理),最后开一个线程用于将本地的数据读取出来,进行数据处理、分析、展示。
代码展示
1. 创建一个 AnalogCard 父类,利于再添加采集卡型号的时候进行扩展
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
namespace AdvantechPCIDemo
{
/// <summary>
/// 数据读取策略:
/// 采集卡读取速度极快(10MS/s、30MS/s),因此现将采集卡数据保存至指定文件,多线程同步将数据读取
/// 再对数据进行处理、显示
/// </summary>
public class AnalogCard
{
protected int channelCount;//通道数
protected uint readLength;//数据一次读取长度
protected uint readCount;
protected ManualResetEventSlim readEvent = new ManualResetEventSlim(false);
protected ManualResetEventSlim writeEvent = new ManualResetEventSlim(false);
protected ManualResetEventSlim readADEvent = new ManualResetEventSlim(false);
protected Task writeTask;//任务:将内存Queue数据写入指定文件
protected Task readTask;//任务:将指定文件数据读入内存Queue
protected Task readADTask;//任务:将采集卡数据读取进内存Queue
protected Queue<ushort[]> dataQueue = new Queue<ushort[]>(1000);//先进先出队列,定义初始长度,不足自动增长
protected CancellationTokenSource source = new CancellationTokenSource();//线程是否取消
protected string fileName = string.Format("Data\\Data-{0}.txt", DateTime.Now.ToString("yy-MM-dd-HH-mm-ss"));//定义数据保存与读取文件名
protected Action<ushort[][]> ChannelDataAction { get; set; }
public AnalogCard()
{
//将内存Queue数据写入指定文件
writeTask = Task.Factory.StartNew(() =>
{
using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
{
while (true)
{
writeEvent.Wait(source.Token);//等待开启写入任务
if (source.IsCancellationRequested) return;
if (dataQueue.Count == 0) continue; ;
ushort[] us = dataQueue.Dequeue();//将第一个数据去除并移除
if (dataQueue.Count > 1000) { dataQueue.Clear(); }
byte[] bt = new byte[readLength * 2];
for (int i = 0, j = 0; i < us.Length; i++)
{
ushort temp = us[i];
bt[j++] = (byte)(temp >> 8);//取高八位
bt[j++] = (byte)temp;//取低八位
}
fs.Write(bt, 0, bt.Length);
}
}
}, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Thread.Sleep(500);
//将指定文件的数据流写入内存队列Queue
readTask = Task.Factory.StartNew(() =>
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Write))
{
int currentLength = 0;
byte[] bt=null;
while (true)
{
readEvent.Wait(source.Token);//等待开启读取
if (source.IsCancellationRequested) break;//任务被取消
if (bt == null)
{
bt = new byte[readLength * 2];//byte数组为需要读取数据的两倍:两个字节转换为一个字符
}
if (currentLength < bt.Length)
{
currentLength = fs.Read(bt, currentLength, bt.Length - currentLength);
}
else
{
currentLength = 0;
if (ChannelDataAction == null)
throw new Exception("无通道数据接收方法!");
ushort[][] us;
if (!GetChannelData(bt, out us))
throw new Exception("");
ChannelDataAction(us);//将读取到的数据向上传递
}
}
}
});
}
protected virtual void StartWriteReadTxt()
{
writeEvent.Set();
readEvent.Set();
readADEvent.Set();
dataQueue.Clear();
}
protected virtual void StopWriteReadTxt()
{
writeEvent.Reset();
readEvent.Reset();
readADEvent.Reset();
}
public virtual void CancelWriteReadTxt()
{
source.Cancel();
}
/// <summary>
/// 将读取的数据分到各通道
/// 数据顺序:temp[0]+temp[1]=ch1va1;temp[2]+temp[3]=ch2va1...
/// </summary>
/// <param name="temp"></param>
/// <returns></returns>
private bool GetChannelData(byte[] temp, out ushort[][] us)
{
us = new ushort[channelCount][];
int index = 0;
for (int i = 0; i < temp.Length; i+=2*channelCount)
{
for (int j = 0; j < channelCount; j++)
{
if (us[j] == null)
{
us[j] = new ushort[readLength / channelCount];
}
us[j][index] = (ushort)((temp[i + j * 2] << 8) + temp[i + j * 2 + 1]);
}
index++;
}
return true;
}
}
}
View Code
在父类设置卡的一些基本字段,并创建了两个线程:writeTask、readTask,分别是将内存 Queue 中数据写入本地 txt,将本地 txt 文件读取进内存 Queue。用 ManualResetEventSlim 对象现将该线程堵塞,等待开启。
2. 创建一个 PCI1714UL 子类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Automation.BDaq;
using System.Threading;
using System.Threading.Tasks;
namespace AdvantechPCIDemo
{
public class PCI1714UL:AnalogCard
{
private string deviceCode = "";
private const int bufferLength = 2048;
private int channelMax=0;
private BufferedAiCtrl bufferedCtrl = null;
private InstantAiCtrl instantAiCtrl = null;
private ManualResetEventSlim readADInternal = new ManualResetEventSlim(false);//有数据再开启
private ushort[] data;
private object locker = new object();
public PCI1714UL(string deviceCode):base()
{
this.deviceCode = deviceCode;
}
public void StartBufferedAI(int channelCount,Action<ushort[][]> channelDataAction)
{
bufferedCtrl = new BufferedAiCtrl();
bufferedCtrl.SelectedDevice = new DeviceInformation(deviceCode);
this.channelCount = channelCount;
this.readLength = (uint)(bufferLength * channelCount);
this.readCount = bufferLength;
this.data = new ushort[readLength];
this.ChannelDataAction = channelDataAction;
ScanChannel channel = bufferedCtrl.ScanChannel;
channel.ChannelCount = channelCount;
channel.ChannelStart = 0;
channel.IntervalCount = bufferLength; // each channel 触发DataReady事件的采样个数,即到了指定个数之后便触发DataReady事件,可以跟windows的定时器控件类似
channel.Samples = bufferLength;//采样的个数。例如,我设定采样个数为1024个,Rate是1024/s,那么也就是说采样经过了1秒
bufferedCtrl.DataReady += new EventHandler<BfdAiEventArgs>(BufferedReady);
this.channelMax = bufferedCtrl.Features.ChannelCountMax;
bufferedCtrl.Streaming = false;
ErrorCode ret = bufferedCtrl.Prepare();//初始化所选设备,准备开始
if (ret != ErrorCode.Success) throw new InvalidOperationException("Failed to prepare AD!");
//将采集卡数据读取到Queue
readADTask = Task.Factory.StartNew(() =>
{
while (true)
{
readADEvent.Wait(source.Token);//等待开启
if (source.IsCancellationRequested) return;
if (!StartDevice()) return;//查看设备是否已运行
readADInternal.Wait(source.Token);//先等待,有数据传递触发BufferedReady后再开启
if (dataQueue.Count > 1000) dataQueue.Clear();
dataQueue.Enqueue(data);
}
}, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
StartWriteReadTxt();
}
public void StopBufferedAI()
{
bufferedCtrl.Stop();
//bufferedCtrl.Dispose();
StopWriteReadTxt();
}
public double[] StartInstantAI()
{
instantAiCtrl = new InstantAiCtrl();
instantAiCtrl.SelectedDevice = new DeviceInformation(deviceCode);
if (!instantAiCtrl.Initialized)
{
throw new Exception("No device be selected or device open failed!");
}
double[] data=new double[4] ;
ErrorCode er0 = instantAiCtrl.Read(0,out data[0]);
ErrorCode er1 = instantAiCtrl.Read(1, out data[1]);
ErrorCode er2 = instantAiCtrl.Read(2, out data[2]);
ErrorCode er3 = instantAiCtrl.Read(3, out data[3]);
return data;
}
public void StopInstantAI()
{
if (instantAiCtrl.State == ControlState.Running)
{
instantAiCtrl.Dispose(); ;
}
}
private bool StartDevice()
{
ErrorCode ret;
if (bufferedCtrl.State != ControlState.Running)
{
readADInternal.Reset();
ret = bufferedCtrl.Start();
//Thread.Sleep(500);
if (ret != ErrorCode.Success)
{
//log.ErrorFormat("Failed to start PCI1714 first time! ErrorCode is {0}.", ret);
ret = bufferedCtrl.Stop();
if (ret != ErrorCode.Success)
{
//log.ErrorFormat("Failed to stop PCI1714! ErrorCode is {0}.", ret);
}
Thread.Sleep(500);
ret = bufferedCtrl.Start();
if (ret != ErrorCode.Success)
{
//log.ErrorFormat("Failed to start PCI1714 second time! ErrorCode is {0}.", ret);
return false;
}
}
}
return true;
}
public void BufferedReady(object e,BfdAiEventArgs b)
{
short[] data = new short[b.Count];
ErrorCode ret = bufferedCtrl.GetData(b.Count, data);//data存放的是从采集卡得到的数据
if (ret != ErrorCode.Success)
{
lock (locker)
{
this.data = null;
readADInternal.Set();
}
}
lock (locker)
{
this.data = data.Select(o => (ushort)o).ToArray();
readADInternal.Set();
}
}
}
}
View Code
在这个类里,先继承上面的 AnalogCard,再设置一些板卡信息。并提供两种方式的数据读取:StartBufferedAI,StartInstantAI。
主要说明 StartBufferedAI:
在这里需要再创建一个线程 readADTask,用于将采集卡数据读取进内存 Queue,同样现将该线程堵塞等待开启。定义一个 BufferedReady 事件,并进行绑定:bufferedCtrl.DataReady += new EventHandler<BfdAiEventArgs>(BufferedReady); 这样采集卡有数据后会自动触发该事件,在该事件中将采集卡数据读取出来。
3. 桌面数据显示
参考研华的数据采集样式,根据数据实时画出数据曲线图。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using Automation.BDaq;
namespace AdvantechPCIDemo
{
public partial class FrmMain : Form
{
private ushort[][] us;//接受PCI发生来的数据
private string deviceName = "PCI-1714UL,BID#13";
public FrmMain()
{
InitializeComponent();
OperateCustomBtn(false);
DrawGrid(this.pbBuffCh1);
DrawGrid(this.pbBuffCh2);
DrawGrid(this.pbBuffCh3);
DrawGrid(this.pbBuffCh4);
DrawGrid(this.pbIstantCh1);
DrawGrid(this.pbIstantCh2);
DrawGrid(this.pbIstantCh3);
DrawGrid(this.pbIstantCh4);
}
#region 数据列表定义
private List<Point[]> pointFromBuffAI = new List<Point[]>();
private List<double[]> dataFromBuffAI = new List<double[]>();
private List<Point[]> pointFromInstantAI = new List<Point[]>();
private List<double[]> dataFromInstantAI = new List<double[]>();
#endregion
#region 画网格和曲线
/// <summary>
/// 画网格
/// </summary>
/// <param name="pb"></param>
private void DrawGrid(PictureBox pb)
{
int width = pb.Width, height = pb.Height;
int cellWidth = height / 4;
Color color = Color.Green;//绿色线条
Bitmap bp = new Bitmap(width, height);
Graphics objG = Graphics.FromImage(bp);
objG.FillRectangle(new SolidBrush(Color.Black), 0, 0, width, height);//黑色背景
//网格列
for (int i = 0; i < width; i += cellWidth)
{
objG.DrawLine(new Pen(new SolidBrush(color)), i + cellWidth, 0, i + cellWidth, height);
}
//网格行
for (int i = 0; i < width; i += cellWidth)
{
objG.DrawLine(new Pen(new SolidBrush(color)), 0, i + cellWidth, width, i + cellWidth);
}
pb.Image = bp;
}
/// <summary>
/// 画动态曲线
/// </summary>
/// <param name="channelIndex"></param>
/// <param name="pb"></param>
private void DrawBufferedAICurve()
{
for (int channel = 0; channel < 4; channel++)
{
PictureBox pbTemp = null;
switch (channel)
{
case 0:
pbTemp = this.pbBuffCh1; break;
case 1:
pbTemp = this.pbBuffCh2; break;
case 2:
pbTemp = this.pbBuffCh3; break;
case 3:
pbTemp = this.pbBuffCh4; break;
}
int width = pbTemp.Width, height = pbTemp.Height;
if (pointFromBuffAI.Count == 0)
{
for (int i = 0; i < 4; i++)
{
pointFromBuffAI.Add(new Point[width]);
}
}
lock (locker)
{
while (dataFromBuffAI.Count >= width + 2)
{
dataFromBuffAI.RemoveAt(0);
}
}
int count = dataFromBuffAI.Count;
if (count != 0)
{
pointFromBuffAI[channel] = new Point[count];
}
for (int i = 0; i < width + 1; i++)
{
if (i >= count) break;
if (dataFromBuffAI[i] == null) continue;
pointFromBuffAI[channel][i] = new Point(i, (int)(height - ((dataFromBuffAI[i][channel] + 5) * height) / 10));//从负到正 //prData[channelIndex][i] = new Point(i, (int)(((Math.Abs(showData[i][channelIndex])) * height) / 10));//全负数
}
pbTemp.Refresh();
}
}
private void ClearBufferedAICurve()
{
Graphics g = Graphics.FromImage(this.pbBuffCh1.Image);
g.Clear(this.pbBuffCh1.BackColor);
//DrawGrid(this.pbBuffCh1);
//DrawGrid(this.pbBuffCh2);
//DrawGrid(this.pbBuffCh3);
//DrawGrid(this.pbBuffCh4);
}
private void DrawInstantAICurve()
{
for (int channel = 0; channel < 4; channel++)
{
PictureBox pbTemp = null;
switch (channel)
{
case 0:
pbTemp = this.pbIstantCh1; break;
case 1:
pbTemp = this.pbIstantCh2; break;
case 2:
pbTemp = this.pbIstantCh3; break;
case 3:
pbTemp = this.pbIstantCh4; break;
}
int width = pbTemp.Width, height = pbTemp.Height;
if (pointFromInstantAI.Count == 0)
{
for (int i = 0; i < 4; i++)
{
pointFromInstantAI.Add(new Point[width]);
}
}
lock (locker)
{
while (dataFromInstantAI.Count >= width + 2)
{
dataFromInstantAI.RemoveAt(0);
}
}
int count = dataFromInstantAI.Count;
if (count != 0)
{
pointFromInstantAI[channel] = new Point[count];
}
for (int i = 0; i < width + 1; i++)
{
if (i >= count) break;
if (dataFromInstantAI[i] == null) continue;
pointFromInstantAI[channel][i] = new Point(i, (int)(height - ((dataFromInstantAI[i][channel] + 5) * height) / 10));//从负到正 //prData[channelIndex][i] = new Point(i, (int)(((Math.Abs(showData[i][channelIndex])) * height) / 10));//全负数
}
pbTemp.Refresh();
}
}
private List<int> data = new List<int>();
private Pen redPen = new Pen(Color.Red, 1);
private void pBCh_Paint(object sender, PaintEventArgs e)
{
PictureBox pb = (PictureBox)sender;
string pbName = pb.Name;
if (pbName.Contains("pbBuffCh"))
{
if (pointFromBuffAI.Count == 4)
{
switch (pbName)
{
case "pbBuffCh1":
if (pointFromBuffAI[0].Count() < 3) break;//画曲线至少需要三个点
e.Graphics.DrawCurve(redPen, pointFromBuffAI[0]); break;
case "pbBuffCh2":
if (pointFromBuffAI[1].Count() < 3) break;
e.Graphics.DrawCurve(redPen, pointFromBuffAI[1]); break;
case "pbBuffCh3":
if (pointFromBuffAI[2].Count() < 3) break;
e.Graphics.DrawCurve(redPen, pointFromBuffAI[2]); break;
case "pbBuffCh4":
if (pointFromBuffAI[3].Count() < 3) break;
e.Graphics.DrawCurve(redPen, pointFromBuffAI[3]); break;
}
}
}
else if (pbName.Contains("pbIstantCh"))
{
if (pointFromInstantAI.Count == 4)
{
switch (pbName)
{
case "pbIstantCh1":
if (pointFromInstantAI[0].Count() < 3) break;//画曲线至少需要三个点
e.Graphics.DrawCurve(redPen, pointFromInstantAI[0]); break;
case "pbIstantCh2":
if (pointFromInstantAI[1].Count() < 3) break;
e.Graphics.DrawCurve(redPen, pointFromInstantAI[1]); break;
case "pbIstantCh3":
if (pointFromInstantAI[2].Count() < 3) break;
e.Graphics.DrawCurve(redPen, pointFromInstantAI[2]); break;
case "pbIstantCh4":
if (pointFromInstantAI[3].Count() < 3) break;
e.Graphics.DrawCurve(redPen, pointFromInstantAI[3]); break;
}
}
}
}
#endregion
#region 界面按钮控制
private void OperateCustomBtn(bool b)
{
this.btnCustomBufferedAI.Enabled = b;
this.btnCustomInstantAI.Enabled = b;
}
private void OperateCtrBtn(bool b)
{
this.btnControlInstantAI.Enabled = b;
this.btnControlBufferedAI.Enabled = b;
}
#endregion
#region 自定义BufferedAI
PCI1714UL objPCI1714 = null;
private void btnConfirm_Click(object sender, EventArgs e)
{
try
{
objPCI1714 = new PCI1714UL(deviceName);
OperateCustomBtn(true);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStartRead_Click(object sender, EventArgs e)
{
try
{
//ClearBufferedAICurve();
objPCI1714.StartBufferedAI(4, ChannelDataAction);
OperateCtrBtn(false);
OperateCustomBtn(false);
bufferedAITimer.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStopCusBuffAI_Click(object sender, EventArgs e)
{
try
{
//if (objPCI1714 != null)
//{
// objPCI1714.StopBufferedAI();
// //objPCI1714 = null;
// bufferedAITimer.Stop();
// OperateCustomBtn(true);
// OperateCtrBtn(true);
//}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void ChannelDataAction(ushort[][] us)//动作方法:接受与处理数据,并交给数据展示列表
{
this.us = us;
if (us == null || us[0] == null) return;
dataFromBuffAI.Add(this.us.Select(u1 =>
{
return GetVoltage(u1.Select(u => (double)u).ToList().Average());
}).ToArray());
}
private double GetVoltage(double data)
{
return data * 10 / 4095 - 5.0190516943459;
}
private void customBufferedAI_Tick(object sender, EventArgs e)
{
DrawBufferedAICurve();
}
#endregion
#region 自定义InstantAI
private void btnCustomInstantAI_Click(object sender, EventArgs e)
{
try
{
customInstantAI.Start();
OperateCustomBtn(false);
OperateCtrBtn(false);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStopCusInstantAI_Click(object sender, EventArgs e)
{
try
{
//if (objPCI1714 != null)
//{
// objPCI1714.StopInstantAI();
// objPCI1714 = null;
// customInstantAI.Stop();
//}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void customInstantAI_Tick(object sender, EventArgs e)
{
dataFromInstantAI.Add(objPCI1714.StartInstantAI());
DrawInstantAICurve();
}
#endregion
#region 控件BufferedAI
//private double[] d;
private void btnControlBufferedAI_Click(object sender, EventArgs e)
{
try
{
waveformAiCtrl1.SelectedDevice = new DeviceInformation(deviceName);
if (!waveformAiCtrl1.Initialized)
{
MessageBox.Show("No device be selected or device open failed!");
return;
}
int channelCount = waveformAiCtrl1.Conversion.ChannelCount;
int sectionLength = waveformAiCtrl1.Record.SectionLength;
//d = new double[channelCount*sectionLength];
waveformAiCtrl1.Prepare();
ErrorCode er=ErrorCode.Success;
if (waveformAiCtrl1.State != ControlState.Running)
{
er = waveformAiCtrl1.Start();
}
if (er != ErrorCode.Success)
{
MessageBox.Show("设备打开失败!");
return;
}
OperateCtrBtn(false);
OperateCustomBtn(false);
bufferedAITimer.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStopCtrBuffAI_Click(object sender, EventArgs e)
{
//if (waveformAiCtrl1.State == ControlState.Idle)
//{
// return;//has been disposed
//}
//waveformAiCtrl1.Stop();
//waveformAiCtrl1.Dispose();
}
private object locker = new object();
private void waveformAiCtrl1_DataReady(object sender, BfdAiEventArgs e)
{
double[] data = new double[e.Count];
ErrorCode er = waveformAiCtrl1.GetData(e.Count, data);
if (er != ErrorCode.Success)
{
return;
}
lock (locker)
{
for (int i = 0; i < data.Length; i += 4)
{
double[] temp = new double[4];
for (int j = 0; j < 4; j++)
{
temp[j] = data[i + j];
}
dataFromBuffAI.Add(temp);
}
}
}
#endregion
#region 控件InstantAI
private void btnControlInstantAI_Click(object sender, EventArgs e)
{
try
{
instantAiCtrl1.SelectedDevice = new DeviceInformation(deviceName);
if (!instantAiCtrl1.Initialized)
{
MessageBox.Show("No device be selected or device open failed!");
return;
}
OperateCtrBtn(false);
OperateCustomBtn(false);
ctrInstant.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStopCtrInstantAI_Click(object sender, EventArgs e)
{
try
{
//ctrInstant.Stop();
}
catch (Exception ex)
{
MessageBox.Show( ex.Message);
}
}
private void ctrInstant_Tick(object sender, EventArgs e)
{
double[] data = new double[4]; ;
ErrorCode er0 = instantAiCtrl1.Read(0, out data[0]);
ErrorCode er1 = instantAiCtrl1.Read(1, out data[1]);
ErrorCode er2 = instantAiCtrl1.Read(2, out data[2]);
ErrorCode er3 = instantAiCtrl1.Read(3, out data[3]);
dataFromInstantAI.Add(data);
DrawInstantAICurve();
}
#endregion
}
}
View Code
样式如下图所示:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)