(转)用C#一步步写串口通信

最近在公司让用C#写一个串口调试的工具,要求向串口中输入16进制数据或字符串。因为我刚到公司,并且对C#也不是很熟悉,针对硬件编程更是从来没接触过,确实耗掉了一些时间。好在一切都可以慢慢来,通过网上查资料,几天工作下来,还是小有成就。下面我就将这次遇到的问题和解决方法奉献出来,目的是和同行交流,回馈网友们提供的帮助,也是为了自己对知识加深一下巩固。
      
说了一大通废话之后,我们来看具体的实现步骤。

公司要求实现以下几个功能:
1):实现两台计算机之前的串口通信,以16进制形式和字符串两种形式传送和接收。
2):根据需要设置串口通信的必要参数。
3):定时发送数据。
4):保存串口设置。

看着好像挺复杂,其实都是纸老虎,一戳就破,前提是你敢去戳。我尽量讲的详细一些,争取说到每个知识点。

在编写程序前,需要将你要测试的COM口短接,就是收发信息都在本地计算机,短接的方式是将COM口的2、3号针接起来。COM口各针的具体作用,度娘是这么说的:COM口。记住2、3针连接一定要连接牢固,我就是因为接触不良,导致本身就不通,白白花掉了一大半天时间调试代码。

下面给出主要的操作界面,如下:

顺便,我将所有控件对应的代码名字也附上了,相信对初学者来说,再看下面的代码会轻松很多。控件名字命名的方法是“控件名+作用”的形式,例如“打开串口”的开关按钮,其名字是btnSwitch  (btn就是button的简写了)。我认为这种命名控件的方式比较好,建议大家使用,如果你有好的命名方式,希望你能告诉我!

下面我们将各个功能按照从主到次的顺序逐个实现。(我分块给出代码实现,详细代码见链接:《C#串口通信工具》

一、获取计算机的COM口总个数,将它们列为控件cbSerial的候选项,并将第一个设为cbSerial的默认选项。

这部分是在窗体加载时完成的。请看代码:
(很多信息代码的注释里讲的很清楚,我就不赘述了。)

[csharp] view plaincopy

  1. //检查是否含有串口
  2. string[] str = SerialPort.GetPortNames();
  3. if (str == null)
  4. {
  5. MessageBox.Show("本机没有串口!", "Error");
  6. return;
  7. }
  8. //添加串口项目
  9. foreach (string s in System.IO.Ports.SerialPort.GetPortNames())
  10. {//获取有多少个COM口
  11. cbSerial.Items.Add(s);
  12. }
  13. //串口设置默认选择项
  14. cbSerial.SelectedIndex = 0;         //设置<span style="font-size:18px; "><strong>cbSerial的默认选项</strong></span>

二、“串口设置”
这面我没代码编程,直接从窗体上按照串口信息设置就行。我们仅设置它们的默认选项,但这里我用到了ini文件,暂时不讲,我们先以下面形式设置默认。

[csharp] view plaincopy

  1. cbBaudRate.SelectedIndex = 5;
  2. cbDataBits.SelectedIndex = 3;
  3. cbStop.SelectedIndex = 0;
  4. cbParity.SelectedIndex = 0;
  5. radio1.Checked = true;  //发送数据的“16进制”单选按钮(这里我忘了改名,现在看着很不舒服!)
  6. rbRcvStr.Checked = true;

三、打开串口
在发送信息之前,我们需要根据选中的选项设置串口信息,并设置一些控件的属性,最后将串口打开。

[csharp] view plaincopy

  1. private void btnSwitch_Click(object sender, EventArgs e)
  2. {
  3. <span style="white-space:pre">  </span>//sp1是全局变量。 SerialPort sp1 = new SerialPort();
  4. if (!sp1.IsOpen)
  5. {
  6. try
  7. {
  8. //设置串口号
  9. string serialName = cbSerial.SelectedItem.ToString();
  10. sp1.PortName = serialName;
  11. //设置各“串口设置”
  12. string strBaudRate = cbBaudRate.Text;
  13. string strDateBits = cbDataBits.Text;
  14. string strStopBits = cbStop.Text;
  15. Int32 iBaudRate = Convert.ToInt32(strBaudRate);
  16. Int32 iDateBits = Convert.ToInt32(strDateBits);
  17. sp1.BaudRate = iBaudRate;       //波特率
  18. sp1.DataBits = iDateBits;       //数据位
  19. switch (cbStop.Text)            //停止位
  20. {
  21. case "1":
  22. sp1.StopBits = StopBits.One;
  23. break;
  24. case "1.5":
  25. sp1.StopBits = StopBits.OnePointFive;
  26. break;
  27. case "2":
  28. sp1.StopBits = StopBits.Two;
  29. break;
  30. default:
  31. MessageBox.Show("Error:参数不正确!", "Error");
  32. break;
  33. }
  34. switch (cbParity.Text)             //校验位
  35. {
  36. case "无":
  37. sp1.Parity = Parity.None;
  38. break;
  39. case "奇校验":
  40. sp1.Parity = Parity.Odd;
  41. break;
  42. case "偶校验":
  43. sp1.Parity = Parity.Even;
  44. break;
  45. default:
  46. MessageBox.Show("Error:参数不正确!", "Error");
  47. break;
  48. }
  49. if (sp1.IsOpen == true)//如果打开状态,则先关闭一下
  50. {
  51. sp1.Close();
  52. }
  53. //状态栏设置
  54. tsSpNum.Text = "串口号:" + sp1.PortName + "|";
  55. tsBaudRate.Text = "波特率:" + sp1.BaudRate + "|";
  56. tsDataBits.Text = "数据位:" + sp1.DataBits + "|";
  57. tsStopBits.Text = "停止位:" + sp1.StopBits + "|";
  58. tsParity.Text = "校验位:" + sp1.Parity + "|";
  59. //设置必要控件不可用
  60. cbSerial.Enabled = false;
  61. cbBaudRate.Enabled = false;
  62. cbDataBits.Enabled = false;
  63. cbStop.Enabled = false;
  64. cbParity.Enabled = false;
  65. sp1.Open();     //打开串口
  66. btnSwitch.Text = "关闭串口";
  67. }
  68. catch (System.Exception ex)
  69. {
  70. MessageBox.Show("Error:" + ex.Message, "Error");
  71. return;
  72. }
  73. }
  74. else
  75. {
  76. //状态栏设置
  77. tsSpNum.Text = "串口号:未指定|";
  78. tsBaudRate.Text = "波特率:未指定|";
  79. tsDataBits.Text = "数据位:未指定|";
  80. tsStopBits.Text = "停止位:未指定|";
  81. tsParity.Text = "校验位:未指定|";
  82. //恢复控件功能
  83. //设置必要控件不可用
  84. cbSerial.Enabled = true;
  85. cbBaudRate.Enabled = true;
  86. cbDataBits.Enabled = true;
  87. cbStop.Enabled = true;
  88. cbParity.Enabled = true;
  89. sp1.Close();    //关闭串口
  90. btnSwitch.Text = "打开串口";
  91. }
  92. }

四、发送信息
因为这里涉及到字符的转换,难点在于,在发送16进制数据时,如何将文本框中的字符数据在内存中以同样的形式表现出来,例如我们输入16进制的“eb 90”显示到内存中,也就是如下形式:

        
或输入我们想要的任何字节,如上面的“12 34 56 78 90”.
内存中的数据时16进制显示的,而我们输入的数据时字符串,我们需要将字符串转换为对应的16进制数据,然后将这个16进制数据转换为字节数据,用到的主要方法是:

Convert.ToInt32  (String, Int32);
Convert.ToByte  (Int32);

这是我想到的,如果你有好的方法,希望你能告诉我。

下面看代码:

[csharp] view plaincopy

  1. private void btnSend_Click(object sender, EventArgs e)
  2. {
  3. if (!sp1.IsOpen) //如果没打开
  4. {
  5. MessageBox.Show("请先打开串口!", "Error");
  6. return;
  7. }
  8. String strSend = txtSend.Text;
  9. if (radio1.Checked == true) //“16进制发送” 按钮
  10. {
  11. //处理数字转换,目的是将输入的字符按空格、“,”等分组,以便发送数据时的方便(此处转的比较麻烦,有高见者,请指点!)
  12. string sendBuf = strSend;
  13. string sendnoNull = sendBuf.Trim();
  14. string sendNOComma = sendnoNull.Replace(‘,‘, ‘ ‘);    //去掉英文逗号
  15. string sendNOComma1 = sendNOComma.Replace(‘,‘, ‘ ‘); //去掉中文逗号
  16. string strSendNoComma2 = sendNOComma1.Replace("0x", "");   //去掉0x
  17. strSendNoComma2.Replace("0X", "");   //去掉0X
  18. string[] strArray = strSendNoComma2.Split(‘ ‘);
  19. <span style="white-space:pre">      </span>//strArray数组中会出现“”空字符的情况,影响下面的赋值操作,故将<span style="rgb(255, 255, 255); ">byteBufferLength相应减小</span>
  20. int byteBufferLength = strArray.Length;
  21. for (int i = 0; i <<span style="background-color: rgb(255, 255, 255); ">strArray.Length</span><span style="background-color: rgb(255, 255, 255); ">; i++ )</span>
  22. {
  23. if (strArray[i]=="")
  24. {
  25. byteBufferLength--;
  26. }
  27. }
  28. byte[] byteBuffer = new byte[byteBufferLength];
  29. int ii = 0;<span style="white-space:pre">   </span>//用于给<span style="rgb(255, 255, 255); ">byteBuffer赋值</span>
  30. for (int i = 0; i < strArray.Length; i++)        //对获取的字符做相加运算
  31. {
  32. Byte[] bytesOfStr = Encoding.Default.GetBytes(strArray[i]);
  33. int decNum = 0;
  34. if (strArray[i] == "")
  35. {
  36. continue;
  37. }
  38. else
  39. {
  40. decNum = Convert.ToInt32(strArray[i], 16); //atrArray[i] == 12时,temp == 18
  41. }
  42. try    //防止输错,使其只能输入一个字节的字符,即只能在txtSend里输入 “eb 90”等字符串,不能输入“123 2345”等超出字节范围的数字
  43. {
  44. byteBuffer[ii] = Convert.ToByte(decNum);
  45. }
  46. catch (System.Exception ex)
  47. {
  48. MessageBox.Show("字节越界,请逐个字节输入!", "Error");
  49. return;
  50. }
  51. ii++;
  52. }
  53. sp1.Write(byteBuffer, 0, byteBuffer.Length);
  54. }
  55. else        //以字符串形式发送时
  56. {
  57. sp1.WriteLine(txtSend.Text);    //写入数据
  58. }
  59. }

五、数据的接收

亮点来了,看到这里,如果你还没吐(可能是我的代码比较拙劣!),那么下面的知识点对你也不成问题。
这里需要用到 委托 的知识,我是搞C/C++出身,刚碰到这个知识点还真有点不适应。为了不偏离主题,关于委托,我仅给出两条比较好的链接,需要的网友可以去加深学习:C#委托订阅委托事件
       在窗体加载时就订阅上委托是比较好的,所以在Form1_Load中添加以下代码:

[csharp] view plaincopy

  1. Control.CheckForIllegalCrossThreadCalls = false;    //意图见解释
  2. sp1.DataReceived += new SerialDataReceivedEventHandler(sp1_DataReceived); //订阅委托

注意,因为自.net 2.0以后加强了安全机制,,不允许在winform中直接跨线程(事件触发需要产生一个线程处理)访问控件的属性,第一条代码的意图是说在这个类中我们强制不检查跨线程的调用是否合法。处理这种问题的解决方案有很多,具体可参阅以下内容:解决方案
      好了,订阅委托之后,我们就可以处理接收数据的事件了。

[csharp] view plaincopy

  1. void sp1_DataReceived(object sender, SerialDataReceivedEventArgs e)
  2. {
  3. if (sp1.IsOpen)     //此处可能没有必要判断是否打开串口,但为了严谨性,我还是加上了
  4. {
  5. byte[] byteRead = new byte[sp1.BytesToRead];    //BytesToRead:sp1接收的字符个数
  6. if (rdSendStr.Checked)                          //‘发送字符串‘单选按钮
  7. {
  8. txtReceive.Text += sp1.ReadLine() + "\r\n"; //注意:回车换行必须这样写,单独使用"\r"和"\n"都不会有效果
  9. sp1.DiscardInBuffer();                      //清空SerialPort控件的Buffer
  10. }
  11. else                                            //‘发送16进制按钮‘
  12. {
  13. try
  14. {
  15. Byte[] receivedData = new Byte[sp1.BytesToRead];        //创建接收字节数组
  16. sp1.Read(receivedData, 0, receivedData.Length);         //读取数据
  17. sp1.DiscardInBuffer();                                  //清空SerialPort控件的Buffer
  18. string strRcv = null;
  19. for (int i = 0; i < receivedData.Length; i++) //窗体显示
  20. {
  21. strRcv += receivedData[i].ToString("X2");  //16进制显示
  22. }
  23. txtReceive.Text += strRcv + "\r\n";
  24. }
  25. catch (System.Exception ex)
  26. {
  27. MessageBox.Show(ex.Message, "出错提示");
  28. txtSend.Text = "";
  29. }
  30. }
  31. }
  32. else
  33. {
  34. MessageBox.Show("请打开某个串口", "错误提示");
  35. }
  36. }

为了友好和美观,我将当前时间也显示出来,又将显示字体的颜色做了修改:

[csharp] view plaincopy

  1. <span style="white-space:pre">      </span>//输出当前时间
  2. DateTime dt = DateTime.Now;
  3. txtReceive.Text += dt.GetDateTimeFormats(‘f‘)[0].ToString() + "\r\n";
  4. txtReceive.SelectAll();
  5. txtReceive.SelectionColor = Color.Blue;         //改变字体的颜色

做到这里,大部分功能就已实现了,剩下的工作就是些简单的操作设置了,有保存设置、定时发送信息、控制文本框输入内容等。

六、保存设置
这部分相对简单,但当时我没接触过,也花了点时间,现在想想,也不过如此。
保存用户设置用ini文件是个不错的选择,虽然大部分都用注册表实现,但ini文件保存还是有比较广泛的使用。

.ini 文件是Initialization File的缩写,也就是初始化文件。
为了不偏离正题,也不过多说明,可参考相关内容(网上资源都不错,因人而异,就不加链接了)。
使用Inifile读写ini文件,这里我用到了两个主要方法:

[csharp] view plaincopy

  1. //读出ini文件
  2. a:=inifile.Readstring(‘节点‘,‘关键字‘,缺省值);// string类型
  3. b:=inifile.Readinteger(‘节点‘,‘关键字‘,缺省值);// integer类型
  4. c:=inifile.Readbool(‘节点‘,‘关键字‘,缺省值);// boolean类型
  5. 其中[缺省值]为该INI文件不存在该关键字时返回的缺省值。
  6. //写入INI文件:
  7. inifile.writestring(‘节点‘,‘关键字‘,变量或字符串值);
  8. inifile.writeinteger(‘节点‘,‘关键字‘,变量或整型值);
  9. inifile.writebool(‘节点‘,‘关键字‘,变量或True或False);

请看代码:

[csharp] view plaincopy

  1. //using 省写了
  2. namespace INIFILE
  3. {
  4. class Profile
  5. {
  6. public static void LoadProfile()
  7. {
  8. string strPath = AppDomain.CurrentDomain.BaseDirectory;
  9. _file = new IniFile(strPath + "Cfg.ini");
  10. G_BAUDRATE = _file.ReadString("CONFIG", "BaudRate", "4800");    //读数据,下同
  11. G_DATABITS = _file.ReadString("CONFIG", "DataBits", "8");
  12. G_STOP = _file.ReadString("CONFIG", "StopBits", "1");
  13. G_PARITY = _file.ReadString("CONFIG", "Parity", "NONE");
  14. }
  15. public static void SaveProfile()
  16. {
  17. string strPath = AppDomain.CurrentDomain.BaseDirectory;
  18. _file = new IniFile(strPath + "Cfg.ini");
  19. _file.WriteString("CONFIG", "BaudRate", G_BAUDRATE);            //写数据,下同
  20. _file.WriteString("CONFIG", "DataBits", G_DATABITS);
  21. _file.WriteString("CONFIG", "StopBits", G_STOP);
  22. _file.WriteString("CONFIG", "G_PARITY", G_PARITY);
  23. }
  24. private static IniFile _file;//内置了一个对象
  25. public static string G_BAUDRATE = "1200";//给ini文件赋新值,并且影响界面下拉框的显示
  26. public static string G_DATABITS = "8";
  27. public static string G_STOP = "1";
  28. public static string G_PARITY = "NONE";
  29. }
  30. }

_file声明成了内置对象,可以方便各函数的调用。
下面是“保存设置”的部分代码:

[csharp] view plaincopy

  1. private void btnSave_Click(object sender, EventArgs e)
  2. {
  3. //设置各“串口设置”
  4. string strBaudRate = cbBaudRate.Text;
  5. string strDateBits = cbDataBits.Text;
  6. string strStopBits = cbStop.Text;
  7. Int32 iBaudRate = Convert.ToInt32(strBaudRate);
  8. Int32 iDateBits = Convert.ToInt32(strDateBits);
  9. Profile.G_BAUDRATE = iBaudRate+"";       //波特率
  10. Profile.G_DATABITS = iDateBits+"";       //数据位
  11. switch (cbStop.Text)            //停止位
  12. {
  13. case "1":
  14. Profile.G_STOP = "1";
  15. break;
  16. case "1.5":
  17. Profile.G_STOP = "1.5";
  18. break;
  19. //防止过多刷屏,下面省写了
  20. ……
  21. }
  22. switch (cbParity.Text)             //校验位
  23. {
  24. case "无":
  25. Profile.G_PARITY = "NONE";
  26. break;
  27. …………
  28. }
  29. Profile.SaveProfile();  //保存设置
  30. }

读取ini文件主要在加载窗体时执行:
INIFILE.Profile.LoadProfile();//加载所有

七、控制文本输入这里倒挺简单,只是注意一点。当我们控制输入非法字符时,可通过控制e.Handed的属性值实现,注意这里的Handed属性是“操作过”的含义,而非“执行此处操作”之意,Handled是过去式,看字面意思,"操作过的=是;",将这个操作的状态设为已处理过,自然就不会再处理了。具体参见MSDN:Handed

[csharp] view plaincopy

  1. private void txtSend_KeyPress(object sender, KeyPressEventArgs e)
  2. {
  3. if (radio1.Checked== true)
  4. {
  5. //正则匹配
  6. string patten = "[0-9a-fA-F]|\b|0x|0X| "; //“\b”:退格键
  7. Regex r = new Regex(patten);
  8. Match m = r.Match(e.KeyChar.ToString());
  9. if (m.Success )//&&(txtSend.Text.LastIndexOf(" ") != txtSend.Text.Length-1))
  10. {
  11. e.Handled = false;
  12. }
  13. else
  14. {
  15. e.Handled = true;
  16. }
  17. }//end of radio1

八、定时发送信息
     这边看似很简单,但也有一点需要注意,当定时器生效时,我们要间隔访问“发送”按键的内容,怎么实现?还好MS给我们提供了必要的支持,使用Button的 PerformClick可以轻松做到,  PerformClick参见MSDN:PerformClick

[csharp] view plaincopy

  1. private void tmSend_Tick(object sender, EventArgs e)
  2. {
  3. //转换时间间隔
  4. string strSecond = txtSecond.Text;
  5. try
  6. {
  7. int isecond = int.Parse(strSecond) * 1000;//Interval以微秒为单位
  8. tmSend.Interval = isecond;
  9. if (tmSend.Enabled == true)
  10. {
  11. btnSend.PerformClick(); //产生“发送”的click事件
  12. }
  13. }
  14. catch (System.Exception ex)
  15. {
  16. MessageBox.Show("错误的定时输入!", "Error");
  17. }
  18. }

注意在一些情况下不要忘了让定时器失效,如在取消“定时发送数据"和“关闭串口”时等。

好了,主要内容就是这些,希望以上内容对大家有所帮助,如你有好的想法,还请不吝赐教!

时间: 2024-10-25 15:25:49

(转)用C#一步步写串口通信的相关文章

串口通信(基础)

参考文章:http://www.cnblogs.com/aierong/archive/2009/08/21/1551589.html http://www.cnblogs.com/procoder/archive/2009/04/07/1430871.html http://blog.csdn.net/cy757/article/details/4474930 SerialPort Class Windows 7 虚拟串口 VSPD 6 最近总结了串口(COM)读写操作的三种方式:第1种方式是

嵌入式Linux裸机开发(七)——UART串口通信

嵌入式Linux裸机开发(七)--UART串口通信 一.UART串口通信简介 通用异步收发器简称UART,即UNIVERSAL ASYNCHRONOUS RECEIVER AND TRANSMITTER, 它用来传输串行数据.发送数据时, CPU 将并行数据写入UART,UAR按照一定的格式在一根电线上串 行发出:接收数据时, UART检测另一根电线的信号,将串行收集在缓冲区中, CPU 即可读取 UART 获得这些数据. 在 S5PV210中, UART提供了 4 对独立的异步串口I/O端口,

转:Qt编写串口通信程序全程图文讲解

转载:http://blog.csdn.net/yafeilinux/article/details/4717706  作者:yafeilinux (说明:我们的编程环境是windows xp下,在Qt Creator中进行,如果在Linux下或直接用源码编写,程序稍有不同,请自己改动.) 在Qt中并没有特定的串口控制类,现在大部分人使用的是第三方写的qextserialport类,我们这里也是使用的该类.我们可以去 http://sourceforge.net/projects/qextser

C#串口通信—向串口发送数据,同步接收返回数据

最近写C#串口通信程序,系统是B/S架构.SerialPort类有一个DataReceived事件,用来接收串口返回的数据,但这种方式在C/S架构下很好用,但B/S就不好处理了.所以写了一个同步模式接收返回数据的方法,不使用DataReceived事件.经过测试,可以正常使用(不支持多线程调用). 一.Machine类 1.Machine类有一个静态变量,定义如下: private static SerialPort serialPort = null; 2.向串口发送数据,同步接收返回数据的方

LabVIEW上位机与串口通信

渊源 大一的时候,学校开了门公共选修课,叫LabVIEW编程,当时的我当然还不知道LabVIEW是啥东东,但还是选了.上课的老师是机械学院的一个副教授,他给我们展示了好几个用LabVIEW做的项目,譬如油箱监控上位机等,已经不太记得了.后来随着学习单片机.ARM等的串口操作,有时候一个漂亮的上位机(尤其是能显示波形的上位机)在项目中给用户展示非常的有用.过了这么多年,虽然曾经也用LabVIEW写过简单的温度监控上位机,但这次再拿起LabVIEW又好像从头开始一样,语法几已忘记殆尽! 定义通信格式

SPCOMM控件在Delphi串口通信中的应用

SPCOMM控件在Delphi串口通信中的应用 2010-07-08 22:20:31|  分类: 个人日记 |举报 |字号 订阅 2009-03-01 05:35 摘要:利用Delphi开发工业控制系统软件成为越来越多的开发人员的选择,而串口通信是这个过程中必须解决的问题之一.本文在对几种常用串口通信方法分析比较的基础上,着重讨论了Delphi开发环境下利用Spcomm控件实现PC机与单片机之间串口通信的方法,研究了Spcomm串口通信的关键技术问题,并通过一个实例给出了Spcomm控件在De

linux下串口通信与管理

linux下的串口与windows有一些区别,下面将介绍一下linux下串口通信管理 查看是否支持USB串口: #lsmod | grep usbserial 如果没有信息:sudo apt-get install setserial 插上USB转串口,在终端输入命令 #dmesg | grep ttyUSB0 如果出现连接成功信息,则说明系统已经识别该设备 一.找到自己的串口设备 查找自己的开发板与电脑的连接的COM口方法 Windows:设备管理器 linux: (1)dmesg #查看带有

BluetoothChat用于蓝牙串口通信的修改方法

本人最近在研究嵌入式的串口通信,任务是要写一个手机端的遥控器用来遥控双轮平衡小车.界面只用了一个小时就写好了,重要的问题是如何与板子所带的SPP-CA蓝牙模块进行通信. SPP-CA模块自带代码,在这里我使用的全部都是SPP-CA的默认模式.其中波特率是9600.读者若要修改其匹配密码,波特率等请使用串口调试工具对SPP-CA使用AT命令进行修改.详情参考其技术手册. 首先介绍Android端,官方的SDK中给了一个BluetoothChat的版本,这个版本稍加修改就可以进行串口通信.由于源代码

labview与单片机串口通信

VISA是虚拟仪器软件体系结构的缩写(即Virtual Instruments Software Architecture),实质上是一个I/O口软件库及其规范的总称. VISA是应用于仪器编程的标准I/0应用程序接口,是工业界通用的仪器驱动器标准API(应用程序接口),采用面向对象编程,具有很好的兼容性.扩展性和独立性.用户可用一个API控制包括VXI.GPIB及串口仪器在内的不同种类的仪器.它还支持多平台工作.多接口控制,是一个多类型的函数库. 在LabVIEW中编写的VISA接口程序,当外