4. SCPI命令与仪器控制
在高速测试自动化中,上位机与示波器、信号源之间的“对话语言”就是 SCPI(Standard Commands for Programmable Instruments)。它建立在 IEEE 488.2 通用命令规范之上,用一套可读的 ASCII 字符串描述“设置什么、查询什么”。理解 SCPI 的语法结构,是写出稳定、可维护自动化脚本的前提;而 PyVISA 则是 Python 侧最常用的“翻译与传输层”,负责把 SCPI 字符串通过 USB、LAN 或 GPIB 送到仪器并取回响应。
SCPI 语法结构:树状命令与三种书写形式
SCPI 命令按“子系统—功能—参数”组织成树状层级,层级之间用冒号分隔。例如 :TIMebase:SCALe 表示“时基子系统下的刻度设置”。命令关键字有长、短两种写法:长写法完整拼写(:TIMebase),短写法只保留大写字母部分(:TIM),二者等价,仪器不区分大小写。方括号表示可省略部分,如 [:SENSe]:VOLTage 中的 [:SENSe] 可省略,写成 :VOLTage 也合法。
命令分为两类:设置命令(无问号)用于写入参数,如 :CHANnel1:SCALe 0.5;查询命令(以问号结尾)用于读取,如 :CHANnel1:SCALe?。参数之间用逗号分隔,多个通道或多项设置可用分号在同一层级下串联,例如 :CHAN1:SCAL 0.5;OFFS 0。需要特别注意:查询命令必须“有问必答”,若只发查询不读取,返回数据会滞留在输出缓冲区,导致后续命令错位——这是自动化脚本最常见的故障之一。
常用命令集:示波器与信号源
示波器侧最常用的命令集中在通道、时基、触发和采集四类。通道垂直设置如 :CHANnel1:SCALe 0.5(0.5 V/div)、:CHANnel1:OFFSet 0、:CHANnel1:COUPling DC;时基如 :TIMebase:SCALe 1e-6(1 µs/div);触发如 :TRIGger:A:EDGE:SOUrce CH1、:TRIGger:A:LEVel 0.5、:TRIGger:A:MODe NORMal;采集如 :ACQuire:MODe SAMple、:ACQuire:STOPAfter RUNSTop。读取波形时常用 :DATa:SOUrce CH1、:DATa:WIDth 2、:DATa:ENCdg RIBinary,再配合 :WFMOutpre? 获取水平刻度、垂直刻度与偏移,才能把原始码值换算成电压与时间。
信号源侧则围绕波形、频率、幅度和输出开关。例如 :SOURce1:FUNCtion SIN、:SOURce1:FREQuency 10e6(10 MHz)、:SOURce1:VOLTage:AMPLitude 1.0(1 Vpp)、:SOURce1:VOLTage:OFFSet 0、:OUTPut1:STATe ON。若使用任意波形,还需 :SOURce1:FUNCtion ARB 并加载波形数据。注意幅度参数在不同仪器上可能是 Vpp 或高电平/低电平组合,务必以仪器手册为准,避免把 1 Vpp 误当成 1 V 幅度。
状态查询与错误处理
SCPI 提供一组通用状态命令,是自动化脚本的“安全网”。*IDN? 返回厂商、型号、序列号和固件版本,用于确认连接对象;*OPC? 在操作完成后返回 1,可用于同步等待;*RST 复位仪器到已知状态;*CLS 清除状态;SYSTem:ERRor? 读取错误队列,返回形如 -113,"Undefined header" 的条目,0 表示无错误。建议每次关键设置后都查询一次错误队列,而不是等到波形异常才排查。
SYST:ERR? 只弹出最早的一条。若怀疑有多条错误,应循环读取直到返回 0,"No error"。用 Python / PyVISA 进行基础控制
PyVISA 通过后端(如 NI-VISA、pyvisa-py)访问仪器。典型流程是:创建 ResourceManager、打开资源、设置超时、写入命令、读取响应、关闭资源。下面是一段可直接参考的示例,参数为教学假设值,实际请按仪器型号调整。
import pyvisa
rm = pyvisa.ResourceManager()
print(rm.list_resources()) # 例如 ('USB0::0x0699::0x0401::C012345::INSTR',)
scope = rm.open_resource('USB0::0x0699::0x0401::C012345::INSTR')
scope.timeout = 5000 # 单位 ms
scope.write_termination = '\n'
scope.read_termination = '\n'
print(scope.query('*IDN?')) # 确认连接对象
scope.write(':CHANnel1:SCALe 0.5')
scope.write(':TIMebase:SCALe 1e-6')
scope.write(':TRIGger:A:EDGE:SOUrce CH1')
scope.write(':TRIGger:A:LEVel 0.5')
scope.write(':ACQuire:STOPAfter RUNSTop')
# 等待采集完成
scope.query('*OPC?')
# 读取错误队列
while True:
err = scope.query('SYSTem:ERRor?').strip()
print(err)
if err.startswith('0'):
break
scope.close()
rm.close()
几个实践要点:第一,query() 等价于“写 + 读”,但只适用于会返回数据的命令;对设置命令使用 query() 会因等待超时而报错。第二,超时时间要覆盖仪器最慢操作,例如长采集或自校准,建议先设 5 s,再按需放大。第三,读取二进制波形时不要用文本方式,应使用 query_binary_values() 并指定数据类型与字节序,否则会得到乱码或长度错误。第四,资源字符串中的 USB 序列号、LAN 地址必须与 list_resources() 输出一致,否则会抛出 VisaIOError。
VI_ERROR_TMO,优先检查三件事:命令拼写是否正确、是否漏读查询响应、超时是否过短。多数“仪器没反应”其实是命令语法错误或响应未读取。