ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FSW频谱仪SCPI远控手册:连接、同步与测量避坑

FSW频谱仪SCPI远控手册:连接、同步与测量避坑

简介:面向罗德施瓦茨FSW系列频谱仪的远程控制需求,这份SCPI指令手册为实验室研究人员、电子工程师及自动测试系统设计者提供固件5.20及以后版本的编程参考,覆盖FSW8、FSW13、FSW26、FSW43、FSW50、FSW67、FSW85等型号。压缩包仅含1个PDF文件,约39.69MB,以官方用户手册为主体,便于按章节检索常规控制指令、远程控制方法及特殊测量功能。内容还纳入B4 OCXO、B10外部发生器控制、B21外部混频器、B24前置放大器、K19噪声功率比率、K54 EMI测量、K544频率响应校正等选件说明,并附安全与法规信息。已有601人学习,适合自动测试、数据采集和仪器远程配置人员作为案头资料,帮助理解SCPI命令组织与选件操作。

1. FSW 频谱仪远控手册:一份把 SCPI 命令写到能直接抄的底稿

频谱仪控制指令里代价最高的往往并非写不出来的那一条,更贵的是写错了却没人发现的那一条。有次做整机杂散扫描,脚本里的INIT:CONT OFF漏了,读回来的 trace 一直是上一轮的数据,报告照样生成,直到复测才发现整批数据不可用。这份 FSW 系列用户手册覆盖固件 5.20 及以后的 FSW8/13/26/43/50/67/85 七个型号,另加 B4、B10、B13、B21、B24、B25、B90G 与 K19、K54、K544、K553、K980 等选件的命令说明;无论你是写自动化测试脚本、把仪表接进产线,还是只会点面板、想转成编程控制,它都是绕不开的底稿。


2. SCPI 远控链路选型:VISA、HiSLIP、裸 socket 到底用哪个

第一次接 FSW 的人最容易在连接层耗掉半天:资源字符串写错、端口填错、结尾符对不上,脚本报的却是一句含糊的超时。链路本身不难,难在不知道有哪几条路,以及每条路各自会在哪里出错。这一章先把手册的命令分层和三种连接方式讲清楚,再给一段上机就能跑的最小验证代码。

2.1 手册的四个层次:通用命令、应用命令、选件命令、状态命令

FSW 手册的命令参考不是从头到尾平铺的一张表,它按用途分成四层,理解了分层,查命令的速度会差好几倍。

第一层是 IEEE 488.2 通用命令,*IDN?、*RST、*CLS、*OPC?、*OPT?、*ESR?、*STB?这些和仪表型号无关,任何一台支持 SCPI 的设备行为都一致。它们不负责测量,只负责会话管理,但恰恰是排错时最先用到的。

第二层是仪表级命令,管的是频率、电平、带宽、扫描、触发、显示窗口,比如FREQ:CENT、DISP:WIND:TRAC:Y:RLEV、BAND:RES、SWE:POIN、TRIG:SOUR。这一层在手册的远程控制章节里有完整树状结构,节点之间是冒号继承关系,写全称和写短形式效果一样,FREQuency:CENTer和FREQ:CENT等价。

第三层是应用级命令,跟着当前激活的测量通道走。频谱分析(SAN)、通道功率/邻道功率(ACLR)、占用带宽(OBW)、载噪比(C/N)、噪声功率比(NPR)、频谱发射模板(SEM)各自有一套自己的配置和结果查询节点,不在当前通道里的命令发过去就是未定义头。踩过坑的人都懂,CALC:MARK1:FUNC:POW:RES? NPR在 SAN 通道里发,返回的是一句错误而不是数值。

第四层是选件命令,和硬件、软件选件绑定。B10 外部发生器控制负责 NPR 测量时的源联动,B21 外部混频器决定毫米波段怎么配本振,B24 前置放大器、B25 电子衰减器影响电平链路,B4 OCXO 决定频率准确度和预热行为,K19 是 NPR 的软件许可,K54 是 EMI 测量,K544 做频率响应修正,K553 控外部前端,K980 面向健康监测类应用。选件没装,对应命令要么不存在,要么返回设置冲突,这一点在后文的避坑章里会反复出现。

提示:手册开头几章讲的是安全信息、开机、面板操作和测量示例,远程控制和 SCPI 命令参考排在测量章节之后,直接翻后半部分比从头读快得多。

2.2 三条连接路径的取舍与端口

FSW 后面板有 LAN 口,仪表本身跑 Windows,这决定了它同时支持多种远程接口。手册在“连接 LAN”一节里描述了网络配置,实际写脚本时有三种接法:

接法资源字符串示例端口优点代价
VXI-11(VISA)TCPIP0::192.168.1.20::INSTR动态兼容性最好,支持事件、服务请求依赖 VISA 实现,握手略慢
HiSLIPTCPIP0::192.168.1.20::hislip0::INSTR4880吞吐高,适合大数据块回读老版本 VISA 不一定支持
裸 socketTCPIP0::192.168.1.20::5025::SOCKET5025不装厂商 VISA 也能通,最简单没有服务请求机制,全靠自己轮询

我的习惯是:单台仪表、短脚本、临时验证,直接走 5025 裸 socket;进产线、要跑几小时、需要 SRQ 中断的场合,用 VXI-11 或 HiSLIP。数据量大到要频繁回读二进制 trace 时,HiSLIP 的收益比较明显。

无论哪条路,都必须把结尾符对齐。FSW 默认以换行结束消息,脚本侧read_termination和write_termination都设成\n,不要留默认值。见过太多“命令明明发对了却超时”的案例,最后发现是终止符里混了\r。

2.3 上机第一段代码:连通性三连问

环境准备很轻:Python 3.9 以上,pip install pyvisa pyvisa-py即可。用@py后端可以完全不装厂商 VISA,socket 和 VXI-11 都能通;如果现场已经装了商业 VISA 实现,去掉@py走默认后端更省心。

import pyvisa # 用 pyvisa-py 后端,避免在每台机器上装厂商 VISA rm = pyvisa.ResourceManager("@py") # 裸 socket:IP + 5025,最省依赖的一条路 inst = rm.open_resource("TCPIP0::192.168.1.20::5025::SOCKET") inst.timeout = 5000 # 毫秒;大块数据回读时临时调到 20000 inst.read_termination = "\n" # FSW 默认以换行结束 inst.write_termination = "\n" print(inst.query("*IDN?").strip()) # 厂商, 型号, 序列号, 固件版本 print(inst.query("*OPT?").strip()) # 已装选件,K19/K54/K544 一眼看到 print(inst.query("SYST:ERR?").strip()) # 会话干净时必须是 0,"No error" inst.close()

这段代码干了三件事,顺序不能乱。*IDN?确认对端到底是不是你要的那台型号,第四段是固件版本,和手册标注的 5.20 对一下,老固件上某些节点确实不存在。*OPT?返回逗号分隔的选件列表,K19、K54、K544、K553、K980 在不在里面,决定了后面哪些测量能跑。最后读一次错误队列,确认会话从干净状态开始。

timeout是第一处容易设错的地方。默认 2 秒对短查询够用,但TRAC? TRACE1在 100001 点、ASCII 格式下能轻松超过这个值,超时后脚本抛异常,仪表那边其实还在往外吐数据,下一次查询就会读到上一次的残留。所以凡是涉及大块回读,先把超时抬到 20 秒再发命令。

结尾符是第二处。query()内部会检查终止符,read_termination设错的表现是每次都等到超时才返回,或者返回内容里带着上一条的尾巴。遇到这种情况,先别怀疑命令,用inst.query("*IDN?")单独测一次,通不了就是链路参数问题。

3. 单次扫频闭环:从复位到取回 trace 的十一条命令

链路通了只说明能说话,不代表能拿到正确的数。一次完整的单次扫频,本质上是“把仪表推到确定状态 → 触发一次 → 等它真跑完 → 按正确格式取数”四步。中间任何一步含糊,结果都会以某种不易察觉的方式错掉。这一章按顺序拆开讲。

3.1 复位顺序:*RST之后为什么要补*CLS和错误队列清空

*RST把仪表恢复到出厂默认,包括测量通道、频率、电平、扫描模式。它在脚本开头很常见,但有两个前提要先想清楚。

第一个前提是共享仪表。实验室里那台 FSW 可能刚被别人调过一堆限线和校正因子,你一个*RST全给抹了,人情账比技术账难还。我的做法是:调试阶段用*RST保证可复现;进产线后改成先MMEM:LOAD:STAT 1,'C:\R_S\Instr\user\base.dfl'载入一份基线设置,再往下改参数,需要保留现场时用MMEM:STOR:STAT 1,'...'存一份。

第二个前提是*RST不清错误队列。队列里可能还躺着上一位用户留下的-113或-221,你读第一次SYST:ERR?会误以为是自己的命令出错。标准动作是*RST之后紧跟*CLS,再把错误队列读空。注意SYST:ERR?一次只弹一条,要循环读到返回0,"No error"为止,写个循环比读一次靠谱。

def drain_errors(inst, limit=20): """读空错误队列;返回非零错误列表,用于日志留证""" errs = [] for _ in range(limit): code, msg = inst.query("SYST:ERR?").strip().split(",", 1) if int(code) == 0: break errs.append((int(code), msg.strip('"'))) return errs inst.write("*RST") inst.write("*CLS") leftover = drain_errors(inst) # 复位前残留的错误,单独记一笔

drain_errors的limit是保险丝,防止某个固件版本在队列异常时把脚本卡死。返回的列表不要直接丢掉,写进日志,后面排查“为什么这条命令没生效”时,它比任何猜测都有用。

3.2 四组核心参数:频率、电平、RBW、扫描时间

扫频测量的参数看着多,真正决定结果形态的就四组,其余都是它们的派生。

频率组:FREQ:CENT设中心,FREQ:SPAN设跨度,也可以改用FREQ:STAR和FREQ:STOP。单位直接写在数值后面,1.5E9和1.5GHZ都认,但混着写容易出错,我统一用科学计数法加单位后缀。

电平组:DISP:WIND:TRAC:Y:RLEV是参考电平,决定纵轴顶端;INP:ATT是衰减器,INP:ATT:AUTO ON让它自动选;装了 B24 前置放大器时INP:GAIN:STAT ON打开,注意开了前置放大后最大输入电平会下降,大信号下必须先关。

带宽组:BAND:RES是分辨率带宽 RBW,BAND:VID是视频带宽 VBW。RBW 决定噪底和扫描时间,窄 RBW 噪声低但扫得慢;BAND:RES:AUTO ON让仪表自己按跨度选,做杂散细扫时再手动压到 1 kHz 以下。

扫描组:SWE:POIN是点数,SWE:TIME是扫描时间,SWE:TIME:AUTO ON自动算。点数直接决定回读的数据量和后续处理开销,1001 点够看形态,找窄带杂散至少 10001 点。

这四组之间存在耦合:RBW 改小,自动扫描时间会变长;参考电平压低,前端可能过载。脚本里的顺序建议是先频率、再电平、再带宽、最后点数,每改一组跑一次drain_errors,出问题能立刻定位到是哪一组。

3.3 同步点:INIT:CONT OFF+INIT:IMM+*OPC?的三段式

这是整篇最容易翻车的地方。INIT:CONT ON是自由运行,仪表不停地扫,你随时读 trace 都能读到数,但读到的是哪一次扫的、有没有扫完,完全不确定。做功率积分、ACLR 这类对单次扫描完整性敏感的计算,必须切成单次。

三段式写起来就三行,但每一步都有讲究:

inst.write("INIT:CONT OFF") # 停止自由运行,此后不会自动开扫 inst.write("INIT:IMM") # 触发一次,立即返回,扫描在后台跑 inst.query("*OPC?") # 阻塞到本次操作完成,返回 "1"

INIT:IMM是异步的,发完立刻返回,此时去读 trace 拿到的可能是旧数据或者半截数据。*OPC?才是同步点,它在所有挂起操作完成后才返回1。不要用*OPC加*WAI的组合去替代,*OPC是置位标志、*WAI是等总线空闲,两者叠在一起在某些时序下会互相等待,表现是脚本挂死,只能重启会话。

还有一个容易忽略的点:*OPC?只等“当前已排队的操作”,如果前面有条命令因为参数非法被拒绝,它照样立刻返回 1,你以为测完了,其实什么都没跑。所以三段式之后紧跟一次错误队列检查,这一步不能省。

3.4 trace 回读:ASCII 与REAL,32的取舍

TRAC? TRACE1默认返回逗号分隔的 ASCII 浮点,单位是 dBm,个数等于SWE:POIN。1001 点大约 12 KB 文本,解析用split(",")加float()就行,调试阶段我全用这个,肉眼可读,出错一眼能看出来。

点数上去以后 ASCII 就不划算了。100001 点接近 1.2 MB,传输加解析要几秒,这时切二进制:

import struct def read_trace_binary(inst): inst.write("FORM:DATA REAL,32") # 每个点 4 字节单精度 inst.write("FORM:BORD NORM") # 大端字节序,和 struct 的 ">" 对应 inst.write("TRAC? TRACE1") raw, _ = inst.read_raw() # 二进制块必须用 read_raw assert raw[0:1] == b"#", "返回不是 IEEE 488.2 块数据" ndigits = int(raw[1:2]) # "#4" 里的 4 length = int(raw[2:2 + ndigits]) # 后续 4 位是字节数 payload = raw[2 + ndigits:2 + ndigits + length] return list(struct.unpack(f">{len(payload) // 4}f", payload))

块数据的头是#加一位位数、再加该位数表示的长度,比如#42004表示后面 2004 字节,正好 501 个 float。解析错位最常见的两个原因:忘了设FORM:BORD NORM,字节序一反,数值全是垃圾;或者用query()去接二进制,pyvisa 会按文本解码,直接把块头搞坏。

FORM:DATA是粘性设置,改完不会自己恢复。脚本里如果有别的函数还在期待 ASCII,记得用完切回FORM:DATA ASC,0。

4. 测量应用命令:ACLR、OBW、C/N、NPR、SEM 的调用姿势

基础扫频只能回答“频谱长什么样”,工程上真正要出数的是那几类带标准定义的测量:邻道功率比、占用带宽、载噪比、噪声功率比、发射模板。它们在 FSW 里各自是一个测量应用,命令写法共享一套骨架,但前置条件和结果节点差别不小。

4.1 先切应用通道再发命令,顺序错了报-113

FSW 的多视图机制允许同时开多个测量通道,每个通道有自己的应用类型。INST:SEL切换当前通道,INST:LIST?查现有通道。所有应用级命令都作用于当前通道,通道不对,命令就是未定义头。

inst.write("INST:SEL SAN") # 回到频谱分析通道 inst.write("CALC:MARK1:FUNC:POW:SEL OBW") # 在 SAN 里启用 OBW 测量 print(inst.query("CALC:MARK1:FUNC:POW:RES? OBW").strip())

顺序反过来的写法很常见:先发CALC:MARK1:FUNC:POW:SEL ACLR再切通道,结果命令落在了旧通道上,或者因为旧通道不支持这个测量而报错。我一般把“切通道”写成每个测量函数的第一行,哪怕看起来多余。

通道名不要靠猜。INST:LIST?会列出当前所有通道和它们的应用标识,先用它确认名字,再写进脚本。

4.2 四个基础测量应用的最小命令集

下面这张表是常用四类测量的最小调用集合,命令都基于同一套CALC:MARK:FUNC:POW骨架,记住一个就记住四个。

测量启用命令结果查询关键配置选件依赖
邻道功率 ACLRCALC:MARK1:FUNC:POW:SEL ACPCALC:MARK1:FUNC:POW:RES? ACP信道带宽、邻道间距、预定义标准无
占用带宽 OBWCALC:MARK1:FUNC:POW:SEL OBWCALC:MARK1:FUNC:POW:RES? OBW功率百分比(99% 常见)无
载噪比 C/NCALC:MARK1:FUNC:POW:SEL CNCALC:MARK1:FUNC:POW:RES? CN载波带宽、噪声带宽偏移无
噪声功率比 NPRCALC:MARK1:FUNC:POW:SEL NPRCALC:MARK1:FUNC:POW:RES? NPR陷波器配置、源功率K19,源联动还需 B10

ACLR 有个省事的入口:CALC:MARK1:FUNC:POW:PRES?查询内置的预定义标准列表,选中某个标准后信道带宽、邻道偏移、滤波器形状一次配好,比手工一个个填靠谱。做多制式共存场景时,MSR ACLR 的配置项更多,手册里单独成节,值得单独读一遍。

OBW 和 C/N 的参数少,但要注意它们都依赖当前扫频的跨度:跨度太窄,积分功率不全,OBW 会偏小;跨度太宽,噪底占比上升,C/N 会偏差。我的习惯是先按信号带宽的 3 到 5 倍设跨度,再让 RBW 自动。

def measure_aclr(inst): inst.write("INST:SEL SAN") inst.write("FREQ:CENT 2.14E9") inst.write("FREQ:SPAN 60E6") inst.write("INIT:CONT OFF") inst.write("CALC:MARK1:FUNC:POW:SEL ACP") inst.write("INIT:IMM") inst.query("*OPC?") res = inst.query("CALC:MARK1:FUNC:POW:RES? ACP").strip() errs = drain_errors(inst) return res, errs

这里的res是一串逗号分隔的结果值,顺序按手册 ACLR 结果节点定义,通常是主信道功率、下邻道功率、上邻道功率、下邻道比、上邻道比。不要凭感觉按位置取值,先打印一次全串,对着手册的字段顺序核对,再写解析。

4.3 NPR、EMI、频率响应修正:选件命令的依赖链

带选件的测量,失败原因往往不在命令本身,而在依赖没满足。

NPR 需要 K19 许可,*OPT?里没有 K19 时,CALC:MARK1:FUNC:POW:SEL NPR直接报错。它还有个容易被忽略的联动:手册里 NPR 章节专门讲了发生器设置和发生器频率耦合,如果希望仪表自动去驱动外部源扫陷波器,就要走 B10 外部发生器控制那条链路,源的地址、接口、功率都要先配好。只做分析不做源控时,B10 可以不装。

K54 EMI 是另一套命令组,带宽选择走的是 EMI 专用的分辨率带宽滤波器(比如 9 kHz、120 kHz 那几档),和普通 RBW 不是一回事。用普通BAND:RES 9E3去凑 EMI 测量,形态像但不符合标准,出报告会被挑。

K544 频率响应修正、K553 外部前端控制、K980 健康监测类应用,属于“装了就多一层校准或控制逻辑”的选件,命令散在各自的选件章节里。判断能不能用的方法很朴素:*OPT?看有没有,SYST:HELP:HEAD?看命令树里有没有对应节点,两个都在,基本就能调通。

SEM 有个更省事的做法:别在脚本里手工拼模板。SEM 的限线结构复杂,分段、偏移、参考功率、失败判定一堆参数,手写 XML 极容易错。正确姿势是在仪表面板上把 SEM 配好、跑通一次,导出成文件,脚本里用MMEM:LOAD载入,再触发测量。手册的 SEM 参考章节里有文件格式说明,需要批量生成多套模板时才值得去读。

4.4 多通道批量测量与 sequencer

一台 FSW 同时开 SAN、ACLR、SEM 三个通道,靠 sequencer 串起来跑,是产线节拍的常见做法。手册把 sequencer 的概念、设置和搭建步骤单独成节,核心是“一次触发,按序执行多个通道的测量”。

脚本侧要配合的只有两件事。一是通道切换用INST:SEL,并且每次切换后确认一次错误队列,避免命令落到上一个通道。二是读结果前必须等整个序列完成,*OPC?在 sequencer 模式下等的是整个序列,不是单个通道,这一点比单通道场景更省心。

反过来的坑是:sequencer 开着的时候,脚本又自己发INIT:IMM,两条触发来源打架,表现为结果时有时无、通道数据串台。做脚本化采集时我一般把 sequencer 关掉,用脚本顺序控制通道,逻辑全在自己手里,出问题好定位。

5. 避坑清单:五类最容易踩的 SCPI 远控问题

下面五条都是我在真实联调中反复遇到的,按“现象 → 原因 → 解决”写,照着对照基本能覆盖八成的现场故障。

5.1 症状、根因、处置

第一条:脚本每条命令都卡满超时,最后抛异常。

现象是query()一直等,等到timeout抛VisaIOError,但仪表屏幕上命令确实执行了。

原因几乎都是终止符不匹配,或者上一条查询发出去没读,输出缓冲区里堆着旧数据,新的查询排在后面读串了。

解决:先单独跑inst.query("*IDN?")验证链路;确认read_termination和write_termination都是\n;检查代码里有没有write("...?")后面忘了read()的写法,所有带问号的命令统一用query()。

第二条:报-113 Undefined header,命令看着没问题。

现象是某条命令在别人机器上能跑,在你这里报未定义头。

原因有两种:一是缩写不合法,SCPI 的大写字母部分是必需短形式,FREQ:CENT合法,FREQ:CEN不合法;二是固件或选件不支持,手册标注适用于 5.20 及以后,老固件上某些节点确实没有。

解决:用SYST:HELP:HEAD?把本机支持的命令头全导出来,grep 一下就知道有没有;再用*OPT?确认选件。

第三条:数读回来了,但和面板显示对不上,像是上一轮的结果。

现象是改了中心频率,trace 形态没变,或者功率值差一截。

原因是没有同步点。INIT:CONT ON下仪表自由运行,你读到的可能是上一次扫描的缓冲;或者INIT:IMM之后没等*OPC?就去读。

解决:切成INIT:CONT OFF,INIT:IMM后必须query("*OPC?")拿到1再读数据,读完再查一次错误队列。

第四条:二进制回读解析出来全是乱码,点数对不上。

现象是struct.unpack报长度不是 4 的倍数,或者数值全是 1e38 之类的怪值。

原因是没设FORM:BORD NORM,字节序反了;或者用query()接了二进制块,文本解码把块头破坏了;也可能是把#42004这六个字节当成了数据的一部分。

解决:固定用FORM:DATA REAL,32加FORM:BORD NORM,用read_raw()收数据,严格按#+ 位数 + 长度解析,解析完拿点数跟SWE:POIN对一遍。

第五条:结果查询报-221 Settings conflict或干脆返回空。

现象是CALC:MARK1:FUNC:POW:RES? NPR这类命令在某个通道里失败。

原因是在错误的测量通道下发命令,或者选件没装。NPR 要在支持它的应用通道里、且 K19 在位;SEM 的限线文件路径不对也会连带报错。

解决:INST:LIST?看当前通道,INST:SEL切到目标应用;*OPT?核选件;文件类参数统一放到C:\R_S\Instr\user\下,路径里不要有中文和空格。

5.2 错误队列与日志:把每次失败留成证据

现场排错最怕的是“刚才还报错,现在复现不了”。SCPI 的错误队列是先进先出,读一条少一条,脚本如果只在最后读一次,中间的报错早就被后面的覆盖了。

我的做法是把drain_errors挂到每个关键步骤后面,连同当时的命令、时间戳、返回值一起写进日志文件。跑一整晚的自动化测试,第二天出问题直接翻日志,能定位到具体是哪条命令、在哪个状态下失败,不用重跑。

import logging, time logging.basicConfig(filename="fsw_scpi.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def step(inst, cmd, tag=""): """发一条命令并立刻收错误,失败就带上下文写日志""" inst.write(cmd) errs = drain_errors(inst) logging.info("CMD=%s TAG=%s ERR=%s", cmd, tag, errs) if errs: raise RuntimeError(f"{tag} 失败: {errs}")

step把“发命令”和“收错误”绑成一个原子动作,日志里命令和错误永远成对出现。代价是每步多一次往返,扫频这种几十条命令的流程完全感觉不到,长时间批量采集时反而省下大量排查时间。

6. 把测量固化成脚本骨架:配置、存证、自检三件套

单次跑通和能反复跑是两件事。前者靠命令拼对,后者靠骨架。我现在的习惯是每个仪表脚本都套同一个模板:连接 → 自检 → 载入基线设置 → 执行测量 → 存证(trace、截图、设置文件)→ 断开。中间任何一步失败,日志里必须能看出来卡在哪。

6.1 一个可复用的 run_measure 骨架

import csv, pyvisa def run_measure(ip, out_prefix): rm = pyvisa.ResourceManager("@py") inst = rm.open_resource(f"TCPIP0::{ip}::5025::SOCKET") inst.timeout = 20000 inst.read_termination = inst.write_termination = "\n" try: idn = inst.query("*IDN?").strip() opts = inst.query("*OPT?").strip() logging.info("IDN=%s OPT=%s", idn, opts) step(inst, "*CLS", "clear") step(inst, "MMEM:LOAD:STAT 1,'C:\\R_S\\Instr\\user\\base.dfl'", "baseline") step(inst, "INST:SEL SAN", "channel") step(inst, "FREQ:CENT 2.14E9", "freq") step(inst, "FREQ:SPAN 40E6", "span") step(inst, "SWE:POIN 10001", "points") step(inst, "INIT:CONT OFF", "single") step(inst, "INIT:IMM", "trigger") inst.query("*OPC?") data = read_trace_binary(inst) with open(f"{out_prefix}.csv", "w", newline="") as f: csv.writer(f).writerows(enumerate(data)) # 截图存证:路径必须在仪表可写目录下 step(inst, "HCOP:DEV:LANG PNG", "hcopy") step(inst, f"MMEM:NAME '{out_prefix}_shot.png'", "hcopy_name") step(inst, "HCOP:IMM", "hcopy_run") finally: inst.close()

MMEM:LOAD:STAT载入的基线文件把通道、单位、限线这些一次性定死,脚本只改真正要变的参数,这样不同人跑出来的结果才有可比性。截图这一步在自动化测试里特别值钱,数据对不上时,一张当时的面板截图能省掉半小时的争论。

存证路径统一放C:\R_S\Instr\user\下面,别用桌面或者带中文的目录。命令里的路径用单引号包住,反斜杠转义在 Python 字符串里要写双份,这一点每次手写都会错一次。

6.2 上机前的一张自检表

检查项命令期望结果不通过时看什么
型号与固件*IDN?第四段 ≥ 5.20手册命令是否在该版本存在
选件到位*OPT?含所需 K/B 编号NPR 要 K19,源联动要 B10
命令支持SYST:HELP:HEAD?能 grep 到目标节点缩写写错或固件不支持
会话干净SYST:ERR?0,"No error"逐条读空,看最早那条
同步完成*OPC?返回1前一条命令是否被拒绝
点数一致TRAC? TRACE1个数等于SWE:POIN块头解析或FORM:DATA残留

这张表我一般贴在工位旁边,换一台仪表、换一个固件版本,先过一遍再动手写业务逻辑。

血泪经验是:不要信“上次这么写能跑”。仪表被换过、固件被升过、选件被拆过,任何一种都可能让原本正常的脚本在某天早上突然报错,而错误信息往往指向一个和真实原因毫不相干的节点。从那以后我每次上机都强制走一遍自检三连:*IDN?看固件、*OPT?看选件、SYST:ERR?看会话,三样都过了才开始改参数。这个习惯帮我避掉的返工,比任何调试技巧都多。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进