简介:VISA控制仪器的例程是一份针对测试测量与自动化领域的VISA编程实例包,面向需要控制USB、LAN、GPIB、COM接口仪器的开发者。包内共15个文件,压缩后195KB,包含C++源代码文件、Visual C++工程文件(dsp/dsw)、编译生成的exe可执行程序,以及pdb、ilk、obj、sbr等调试与构建中间文件,覆盖从源码编辑、编译链接到运行调试的完整工程结构,便于直接查看或二次开发。例程围绕“测量通道功率”场景展开,详细演示了通过VISA打开资源、使用visaWrite下发命令、visaRead读取数据的完整交互流程,同时展现了错误处理与接口适配思路。配套的工程文件让初学者可以快速理解仪器控制程序的框架,有经验的开发者也能将其中命令交互和资源管理方法复用到实验室自动化、远程数据采集等项目中。目前已有2853人学习,对希望掌握跨接口仪器编程的读者具有直接参考价值。 做自动化测试这些年,我越来越觉得在实验室里最该掌握的第一课,不是某台示波器的花式玩法,而是一套能把所有仪器都“指挥”起来的通用方法。这个方法的名字叫VISA(Virtual Instrument Software Architecture,虚拟仪器软件架构)。它把GPIB、USB、LAN、串口这些完全不搭界的物理接口,统一成同一套API:open、write、read、close,就这四板斧。只要你拿到一台仪器的SCPI指令手册,基本能在一个小时内写出第一版控制例程。这篇文章我就把自己常用的VISA仪器控制例程完整拆开讲一遍,覆盖环境搭建、资源字符串识别、最小示例、不同接口的变体和常见坑位,适合测试工程师、实验室负责人,以及正在搭自动化采集平台的嵌入式开发者参考。
1. VISA在仪器控制链路里到底扮演什么角色
1.1 没有VISA之前:接口与驱动的乱象
先往回看一眼。早年控制仪器,写GPIB程序要调IEEE 488接口卡自带的驱动函数,换成USB接口又要装厂商的USBTMC驱动,仪器走网口的话还得处理VXI-11或者LXI协议。每换一种连接方式,代码就得重写一遍;每换一个仪器品牌,指令交互逻辑又是一套。项目一大,驱动文件、动态库、回调函数散落各处,维护成本高得吓人。
VISA就是在这种背景下出现的。它把“传输层”的差异全部吃掉——不管底层走的是GPIB总线、USB总线、以太网还是串口,上层看到的都是同一个viOpen、viWrite、viRead、viClose会话模型。打个比方:VISA相当于一套全国通用的公路网,SCPI指令就是公路上的交通规则,而IVI驱动则是更高层的导航系统。你真正在例程里写的,其实是“用VISA把SCPI指令发给仪器”这件事。
1.2 VISA、SCPI、IVI三者的分工
很多刚开始接触自动化的朋友容易把这三个概念混在一起,我简单区分一下:
- VISA:通信会话层,解决“字节怎么传过去”的问题。它不关心指令内容,只负责把数据送到仪器并收回来。
- SCPI(Standard Commands for Programmable Instruments):指令语法层,解决“传什么内容”的问题。比如
*IDN?是查询仪器身份,MEAS:VOLT:DC?是测直流电压。它是一套可读性很强的ASCII文本命令。 - IVI:可互换虚拟仪器驱动,解决“不同仪器怎么统一操作”的问题。它把同类仪器抽象成标准类驱动,换一台仪器只需换驱动而不改业务代码。
理解这三层,后面写例程心里就有谱了。底层用VISA把链路打通,上层用SCPI跟仪器对话,如果需要跨品牌互换,再考虑套一层IVI。
2. 环境准备与最小例程:先把链路打通
2.1 驱动后端怎么选
写VISA例程前,计算机上必须有一套可用的VISA驱动。目前主流方案有三类,我整理过一张对照表,平时选型基本够用:
| 方案 | 适用场景 | 安装要点 |
|---|---|---|
| NI-VISA | 工业/实验室最常用,兼容NI生态 | 安装NI MAX,自带资源扫描器 |
| Keysight IO Libraries Suite | 是德(原安捷伦)仪器用户 | 含Connection Expert,可替代NI-VISA |
| pyvisa-py(纯Python后端) | 轻量开发、跨平台、不想装大驱动 | pip install pyvisa pyvisa-py,适合简单串口/TCP连接 |
我个人最推荐的做法是:先装NI-VISA,再在Python里用pyvisa库。这样既稳定又能快速实验。如果是跑在Linux服务器上,不想装闭源驱动,pyvisa-py也能顶一顶,但遇到复杂USB-TMC或者GPIB设备时,还是原生驱动更靠谱。
注意:NI-VISA和Keysight VISA不要同时安装,两个驱动会抢占资源,导致
list_resources()扫不到设备,这种问题极其隐蔽。
2.2 一个能跑起来的最小例程
装好环境后,先用Python把链路打通。这段代码是我每次接手新仪器时都会先跑一遍的“握手测试”:
import pyvisa rm = pyvisa.ResourceManager() print(rm.list_resources()) # 以网口仪器为例,后面接实际扫描到的资源 inst = rm.open_resource('TCPIP0::192.168.1.20::inst0::INSTR') # 设置超时时间,单位毫秒 inst.timeout = 3000 # 发送 *IDN? 并读取返回 print(inst.query('*IDN?')) inst.close() rm.close()如果你看到类似Keysight Technologies,DSO-X 3024A,...这种回显,链路就算是通了。此时立刻做两件事:
- 把这行资源字符串存到配置里,后续所有例程复用;
- 把
*IDN?的结果打印出来存档,它是你这台仪器在系统里的“身份指纹”。
2.3 VISA资源字符串的构成规则
资源字符串是VISA例程里最重要的一个参数,拼错一个字符就open失败。常见格式我列一下:
| 接口类型 | 资源字符串示例 | 关键字段 |
|---|---|---|
| TCPIP(VXI-11) | TCPIP0::192.168.1.20::inst0::INSTR | IP地址、逻辑名 |
| TCPIP(Raw Socket) | TCPIP0::192.168.1.20::5025::SOCKET | IP地址、端口号 |
| USB-TMC | USB0::0x2A8D::0x0001::MY59001352::INSTR | VID、PID、序列号 |
| GPIB | GPIB0::1::INSTR | 卡号、设备地址 |
| 串口 | ASRL4::INSTR(Windows)或/dev/ttyUSB0 | COM口或设备节点 |
同一台仪器可能同时支持多种连接方式,比如示波器既有USB又有LAN口,你可以每种接口各写一个资源标识,在例程里通过配置文件切换。这样换环境时不用改代码,只要改配置项。
3. 通用控制例程的完整拆解
3.1 建立会话:清晰不是可选项
很多新手拿到例程,直接open_resource就开写。我建议至少补这三步:
inst = rm.open_resource(resource_str) inst.clear() # 清除仪器输入输出缓冲和状态寄存器 inst.timeout = 5000 # 初始超时给足,避免命令执行慢导致误报 inst.encoding = 'utf-8'clear()这一步最容易被忽略。如果上一段程序异常退出,仪器端可能还残留半截命令,不清除直接开测,第一个*IDN?就会收到异常返回。另外,超时时间不要抄别人的固定值,不同SCPI命令耗时差距很大,查询测量结果可能需要几秒,而单纯的写配置命令几十毫秒就完成。我习惯在例程里把超时做成参数。
3.2 写入与查询:query不是万能的
query()是write()加read()的简写,适合一问一答类命令。但有两种情况不能用:
- 写入命令(无返回):比如
*RST(复位)、OUTP ON(打开输出),用write(); - 返回数据量很大的命令:比如下载波形数据,
query()内部可能因为一次性读不完而出错,得自己控制读写节奏。
正确姿势是:
inst.write(':SOUR:VOLT 1.5') # 设置信号源输出电压 inst.query('*OPC?') # 等待上一条命令执行完成 # 主动读取 inst.write('MEAS:VOLT:DC?') voltage = inst.read() print(float(voltage))关于同步,这里多说一句。仪器执行命令是有“顺序”的,但SCPI命令本身没有自动等待机制。如果写完命令立刻就去读结果,可能读到的是上一次的旧值。最稳妥的做法是下发完需要执行时间的命令后,查询*OPC?(Operation Complete),等它返回1再继续。
3.3 二进制大块数据的读取
做波形采集或者频谱数据下载时,返回的往往不是ASCII文本,而是遵守IEEE 488.2规则的二进制块(block data)。格式长这样:
#5<字节数><数据>#5表示跟随5位数字描述数据长度,然后才是原始二进制内容。在pyvisa里读取这类数据,我建议用底层方式拿原始字节:
inst.write(':WAV:DATA?') raw = inst.read_raw() # 拿到原始字节流,避免编码干扰然后自己解析头部的#N...字段,再从后续字节中切出波形数据。这里特别提醒:不要在最外层用query()转字符串再去解析,二进制数据里可能包含非ASCII字节,一转换就丢了精度。
3.4 资源回收:异常时也要关会话
VISA会话是有限的系统资源,Windows上异常退出后资源不释放,再开新程序时可能open不了设备。我的做法是封装一个上下文管理器,保证无论正常还是异常都能关闭:
from contextlib import contextmanager @contextmanager def visa_session(rm, resource_str): inst = rm.open_resource(resource_str) inst.clear() try: yield inst finally: inst.close() rm = pyvisa.ResourceManager() with visa_session(rm, 'TCPIP0::192.168.1.20::inst0::INSTR') as inst: print(inst.query('*IDN?'))用了这个模式之后,程序崩溃导致的“端口被占用”问题基本绝迹。
4. 不同物理接口的例程变体
4.1 TCPIP:现代仪器的首选
网口仪器是目前最主流的选择。优势很明显:不用插拔USB,只要在同一个局域网内,随便换一台电脑都能控制。VXI-11模式下的资源字符串是inst0::INSTR,配置比较傻瓜;如果你想绕过VXI-11直接用Raw Socket,则要用::端口号::SOCKET,常见端口是5025。两种模式通信方式略有差异,Raw Socket更简单直接,但需要自己处理命令终止符(通常是\n)。
我实际项目中的习惯是:优先用Raw Socket,因为它不依赖额外的RPC服务,性能和稳定性更好。唯一的坑就是要记得在每条命令末尾手动追加换行符。
4.2 USB-TMC:即插即用的新方案
USB仪器在Windows下装好驱动后,会显示为一个USBTMC设备。好处是即插即用,坏处是“挑驱动”。如果你在list_resources()里看到了USB资源但open时报错,八成是驱动装成了打印机模式而不是TMC模式。处理办法是在设备管理器里手动把驱动改为“USBTMC”,然后重新插拔。
USB资源里的VID(厂商ID)和PID(产品ID)是固定的,但序列号每台设备唯一,因此换一台同型号仪器后资源字符串也要跟着变。例程里最好把这个字段做在配置里,避免换机后到处找哪里写死了序列号。
4.3 GPIB与串口:老仪器与嵌入式场景
GPIB虽然年头长,但工业现场依然大量存在。连接方式是GPIB0::1::INSTR,其中1是仪器的GPIB地址,需要先在仪器面板上设置好,PC端和仪器端地址不一致就找不到设备。
串口仪器更常见于老旧仪表和一些嵌入式板卡联调场景。资源字符串在Windows上是ASRL4::INSTR,在Linux上可能是/dev/ttyUSB0::INSTR。用串口时,波特率、数据位、停止位这些参数要跟仪器面板一致,例程里用inst.set_visa_attribute(pyvisa.constants.ResourceAttribute.baud_rate, 115200)设置。
补充一句:如果你是在单片机平台(比如STM32或ESP32)做仪器控制,这时通常没有VISA层可用,得更底层的串口/TCP状态机自己实现。VISA的会话模型对这类场景同样有借鉴意义——先建立连接、再收发数据、最后释放资源,思路完全一致。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
以下是我这几年使用过程中遇到频率最高的问题,整理成了一张速查表:
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
list_resources()返回空 | VISA驱动未装或被冲突 | 重装NI-VISA,卸载其他VISA实现 |
| open时Resource not found | 资源字符串不匹配 | 用NI MAX扫描复制完整字符串 |
| 读取超时 | 命令执行时间过长/终止符不配 | 加大timeout,检查\n终止符 |
| 返回数据乱码 | 编码设置不对/二进制按文本读 | 用read_raw()读原始字节 |
| 仪器面板报错 | SCPI语法或状态冲突 | 重置仪器,读取SYST:ERR?定位 |
| 程序崩溃后端口被占用 | 会话未正常关闭 | 用上下文管理器确保close |
5.2 排查思路:不要盲目试错
我调仪器的经验是:排查问题要按“链路分层”来走。先确认物理连接(网线灯亮不亮、USB识别没有),再确认资源字符串(NI MAX扫描可见),然后确认会话参数(timeout、终止符),最后才去怀疑SCPI指令写法。顺序反了,会在命令字写错上浪费大量时间。
一个很典型的案例:我遇到过一台电源,*IDN?能正常返回,但下发OUTP ON后读不到状态。折腾半天发现是仪器面板的Output按钮处于锁定模式,SCPI层根本没权限执行。这种和通信无关的“仪器状态问题”,光看代码是看不出来的,及时用仪器的原生面板或仿真软件排查。
5.3 两个我一直保留的底仓技巧
最后分享两个每次写例程都会用上的细节:
- 开始测试前先
*CLS清错误队列,并在测试结束时查询一次SYST:ERR?,把累积的错误信息拉出来。这样就算某条命令执行失败,也能在下一次运行前定位。 - 不要在一个例程里硬编码资源字符串和仪器型号,把它抽到配置项或环境变量里。你永远不知道下一次去现场用的是哪台仪器、哪个接口,配置外置是最低成本的最优解。
我在实际项目中,通常是先做一个极简的*IDN?握手例程验证链路,然后把它封装成visa_session工具函数,再往上叠加仪器特有的SCPI指令集。等仪器多起来之后,你会发现最值得投入的不是某台仪器的具体指令,而是这一层“统一会话管理”的地基。把地基打稳了,后面加仪器只用新增指令集,不动通信框架,整套测试系统就能一直稳稳地跑下去。
本文还有配套的精品资源,点击获取