4. SCPI指令与仪表通信实战

SCPI(Standard Commands for Programmable Instruments)是建立在 IEEE 488.2 通用命令基础上的一套仪器程控命令语言。它把命令组织成一棵分层树,用冒号分隔层级,用问号区分“设置”与“查询”,用分号在同一层级内串联多条命令。理解这棵命令树,是让 PC 与 R&S 仪表稳定对话的第一步。

命令结构:一棵可寻址的命令树

SCPI 命令按子系统分层,例如 SOURce(信号源)、SENSe(测量/接收)、CALCulate(计算/迹线)、TRIGger(触发)、SYSTem(系统)。以 R&S 信号源设置频率为例:

SOURce:FREQuency:CW 1GHz
SOURce:FREQuency:CW?
SOURce:FREQuency:MODE CW
SOURce:POWer:LEVel:IMMediate:AMPLitude -10dBm

命令中的大写字母是“短格式”必需部分,小写字母可省略。因此 SOUR:FREQ:CW 1GHz 与 SOURce:FREQuency:CW 1GHz 等价。参数与命令之间用空格分隔,单位(GHz、dBm、MHz)可被仪表识别并自动换算,但建议统一使用基本单位以避免歧义。

层级之间用冒号分隔,同一层级的多条命令可用分号合并,减少通信往返:

SOUR:FREQ:CW 2.4GHz;POW:LEV:IMM:AMPL -20dBm
SOUR:FREQ:CW?;POW:LEV:IMM:AMPL?

注意:分号后的命令若省略前导冒号,则继承上一条命令的父层级;若以冒号开头,则从根节点重新寻址。这是初学者最容易出错的地方。

查询与设置语法:问号决定方向

SCPI 用问号区分两类操作:不带问号是“设置”(write),带问号是“查询”(query)。查询会返回一个响应,必须由 PC 读取,否则响应会滞留在仪表的输出缓冲区,导致后续命令错位。典型模式是“设置—查询—校验”:

SOUR:FREQ:CW 1GHz
SOUR:FREQ:CW?
> 1000000000

返回值为基本单位数值,不带单位。若查询的是枚举型参数,返回的是短格式字符串,例如 SOUR:FREQ:MODE? 可能返回 CW 或 SWE。对于 R&S 仪表,常见查询还包括 *IDN?(识别)、*OPC?(操作完成)、SYST:ERR?(错误队列)。

设置命令通常不产生响应,但若参数越界或语法错误,仪表不会立即报错,而是把错误压入错误队列。因此“设置成功”不等于“参数被接受”,必须通过错误队列确认。

错误队列处理:把静默失败变成可判断结果

SCPI 错误队列是一个先进先出的缓冲区,记录语法错误、参数越界、执行失败等。标准查询是 SYSTem:ERRor?,它返回一个错误号和一个描述字符串,格式为 <code>,"<message>"。当队列为空时返回 0,"No error"。

推荐在每次关键设置后执行一次错误队列清空式读取,直到读到 0 为止:

def check_errors(inst):
    while True:
        err = inst.query("SYST:ERR?")
        code = int(err.split(",")[0])
        if code == 0:
            break
        print("SCPI error:", err)

常见错误号示例:-113 表示“Undefined header”(命令头拼写错误),-222 表示“Data out of range”(参数越界),-410 表示“Query INTERRUPTED”(查询被中断,通常是未读响应就发了下一条命令)。把错误队列纳入每次操作的收尾检查,是自动化脚本可靠性的关键。

通过 VISA 实现 PC 与 R&S 仪表的双向通信

VISA(Virtual Instrument Software Architecture)是 PC 与仪表之间的统一 I/O 抽象层。R&S 仪表通常支持 LAN(VXI-11 或 HiSLIP)、USB(USBTMC)和 GPIB。以 LAN 为例,资源字符串形如:

TCPIP0::192.168.1.100::inst0::INSTR

使用 PyVISA 建立连接并完成一次“设置—查询—校验”闭环:

import pyvisa
rm = pyvisa.ResourceManager()
inst = rm.open_resource("TCPIP0::192.168.1.100::inst0::INSTR")
inst.timeout = 5000

print(inst.query("*IDN?"))
inst.write("SOUR:FREQ:CW 1GHz")
print(inst.query("SOUR:FREQ:CW?"))
check_errors(inst)
inst.close()

其中 write 用于设置,query 用于查询并读取响应,read 用于读取已发出的查询响应。若混用 write 发送带问号的命令,响应会留在缓冲区,下一次 query 就会读到旧数据,造成“命令错位”。

下面这张图把本节的核心关系串起来:PC 侧通过 VISA 资源字符串建立会话,命令按 SCPI 树寻址后分为设置与查询两条路径,查询响应与错误队列共同构成结果判读依据。

SCPI 命令树与 VISA 双向通信链路 PC 应用 PyVISA / VISA 库 write / query / read timeout 5000 ms VISA 资源字符串 TCPIP0::192.168.1.100::inst0::INSTR LAN / USB / GPIB 统一抽象 会话建立 → 命令收发 R&S 仪表 SCPI 解析器 命令树 / 参数校验 错误队列 SYST:ERR? 命令 响应 SCPI 数据/错误 SCPI 命令树(节选) SOURce FREQuency CW POWer:LEVel SYSTem:ERRor 根节点 设置频率 连续波 设置功率 错误查询 设置(write) SOUR:FREQ:CW 1GHz 无响应,参数越界进错误队列 分号合并:SOUR:FREQ:CW 1GHz;POW:LEV -10dBm 查询(query) SOUR:FREQ:CW? → 1000000000 必须读取响应,否则缓冲区错位 SYST:ERR? → 0,"No error" 表示正常

一次完整的双向通信闭环

把上述要素串起来,一次可靠的仪表操作应包含:打开 VISA 会话、用 *IDN? 确认连接对象、发送设置命令、查询回读校验、读取错误队列、关闭会话。下面给出一个可直接参考的示例流程,参数为教学假设值:

import pyvisa

rm = pyvisa.ResourceManager()
inst = rm.open_resource("TCPIP0::192.168.1.100::inst0::INSTR")
inst.timeout = 5000

print("IDN:", inst.query("*IDN?").strip())

inst.write("SOUR:FREQ:CW 2.4GHz")
inst.write("SOUR:POW:LEV:IMM:AMPL -20dBm")

freq = inst.query("SOUR:FREQ:CW?").strip()
power = inst.query("SOUR:POW:LEV:IMM:AMPL?").strip()
print("回读频率:", freq, "Hz")
print("回读功率:", power, "dBm")

while True:
    err = inst.query("SYST:ERR?").strip()
    if err.startswith("0,"):
        print("错误队列已清空")
        break
    print("SCPI 错误:", err)

inst.close()

判断是否成功的依据有三条:*IDN? 返回的型号与预期一致;回读频率与功率与设定值在容差内一致;错误队列最终读到 0,"No error"。若回读值不符,优先检查命令头拼写与参数单位;若错误队列出现 -410,说明存在未读取的查询响应,需要检查 write 与 query 是否混用。

在 R&S 仪表上,*RST 可复位到已知状态,*CLS 可清除状态寄存器,*OPC? 可用于等待长操作完成。把 *OPC? 与错误队列结合,能显著减少“命令已发但仪表未就绪”导致的偶发失败。
不要用 write 发送带问号的命令。带问号的命令必须用 query 或“先 write 再 read”,否则响应会留在输出缓冲区,造成后续查询读到旧数据,表现为“设置不生效”或“返回值错位”。

掌握命令树寻址、设置与查询的方向区分、错误队列的收尾检查,以及 VISA 会话的正确读写方式,就具备了用 PC 稳定控制 R&S 仪表的基础能力。后续更复杂的序列控制、状态机与并发操作,都建立在这套最小闭环之上。