实验室里最折磨人的一件事,不是仪器坏了,而是“得有人守着它”。一台上位机跑一个老化测试,动不动就是十几个小时,中途还不能走开,因为某台仪器一报错,整批数据就废了。这种情况在产线、检测机构和高校实验室里非常常见。但你回头看,手里的数字万用表、信号源、示波器、可编程电源,每一台都带程控接口,本身就支持“听指挥”——用计算机下发指令、自动读数、自动判断,只是一直没把这套能力真正用起来。
这篇分享围绕一个非常具体的方向:怎么让测试仪器从“人盯着”变成“听指挥”。我会讲清楚背后的控制机制、方案选型、多台仪器协同调度的落地做法,以及实际跑测试时最容易踩的一批坑。适合正在做产线测试、实验室自动化改造,或者自己搭自动测试系统的工程师参考。只要你有几台带GPIB、USB、串口或网口的仪器,这套思路基本都能迁移过去。
1. 先想清楚:让仪器“听指挥”到底是要做一件什么事
1.1 仪器本来就是“能听话”的:SCPI和控制接口的底层逻辑
很多人一提到自动化测试,第一反应是“得写代码”,第二反应是“写代码很难”。实际上,仪器遥控这件事的底层逻辑比你想象得简单:每台仪器前面板上的按键,背后本质上都是一条内部指令。你用手按“Voltage”键,仪器内部就执行一次电压测量流程;你用计算机发一条文本命令,比如MEAS:VOLT:DC?,仪器执行的也是同一套流程。只是这个流程的入口从“手指”换成了“文本指令”。
为了让这个过程标准化,行业里搞了一套通用的仪器控制语言,叫SCPI(Standard Commands for Programmable Instruments)。它规定了命令怎么写、数据怎么返回、状态怎么查询。所以不管你是泰克、是德、吉时利还是国产仪器的用户,只要能发SCPI命令,基本都能控制仪器——这就是“听指挥”的前提。
连接方式上,老一点的仪器靠GPIB和串口,新一代仪器基本都有USB和LAN口。GPIB是经典总线,稳定可靠,但需要专用卡和手工设地址;串口便宜,但速度慢;USB即插即用,适合单机操作;LAN口则是最适合多台仪器组网的方案,一台电脑通过网络可以同时管理几十台仪器。我实测下来,长时间跑批量的场景里,LAN连接比USB稳定得多,掉线概率明显低。
1.2 什么测试适合自动化,什么不适合硬上
先说结论:长时间、大批量、重复性测试最适合“听指挥”。比如电池充放电循环测试,一跑就是十几个小时,靠人盯着既浪费精力又容易出错;又比如产线上的板卡筛选,同一块板子要测十几个点位、几十项指标,手动按按钮按到后面都会麻木,判错一个就是批量事故。这类测试,脚本一挂,仪器自己执行、自己判断、自己落盘,比人靠谱得多。
但有些场景不适合硬上自动化。一个是调试阶段的临时测量,你今天还说不清要测什么,明天参数就要变,写脚本的时间比手动测还长;另一个是需要手感或目视判断的场景,比如检查焊接质量、判断波形是否有毛刺,这种主观判断光靠SCPI返回的数值很难做准确。我的原则是:先判断测试的“稳定周期”有多长。如果一个测试流程能稳定跑一个星期不被推翻,那才值得为它写自动化。
这里有个投入产出比的经验:写一套单机脚本,熟练之后半天到一天;写一套多台仪器协同的系统,两到三天。如果这套测试每天能帮你省两三个小时的人工盯守,那基本一个月回本。如果只是偶尔测一次,那就别折腾了,手动按几下反而更快。
1.3 方案选型:裸脚本、轻量框架、现成ATE软件怎么权衡
很多人以为做测试自动化必须上NI TestStand这类重型ATE平台,其实不是。方案选型要看你的场景复杂度,我建议按下面这条线从轻到重。
裸脚本是门槛最低的方式,直接装一个VISA库,再用Python或LabVIEW写一小段脚本,控制一台仪器完成一个固定流程。适合单台仪器、流程简单的场景。优点是快,改起来也快;缺点是流程一复杂就变成意大利面条,不好维护。
轻量框架适合多台仪器协同的场景。不需要引入重量级平台,自己在脚本外面加一层封装:设备接口层、任务调度层、数据管理层。后面我会用Python写一个实际例子,这套结构大概两三百行代码就能撑起一套能用的小型测试系统,而且换仪器、加测试项都方便。
现成ATE平台功能最全,报表、数据库、联锁机制都有,但贵,而且学习成本不低。适合公司统一规范、多人协作、测试项极多的情况。我的经验是:别一上来就上平台,先用裸脚本把测试流程跑通,确认整体方案可行,再决定要不要抽公共层、要不要换平台。先跑通,再优化,永远比一开始就设计过度强。
2. 仪器连接和SCPI指令控制,把基本功扎扎实实练好
2.1 接口选择:GPIB、串口、USB、LAN各有什么坑
仪器连接方式选得对不对,直接决定后面踩不踩坑。我这里把这几种接口放在一起对比一下,方便你按自己的设备情况选。
| 接口 | 传输速率 | 常见距离 | 典型场景 | 注意细节 |
|---|---|---|---|---|
| GPIB | 最高约1MB/s | 20米以内 | 老仪器、多台级联 | 每台仪器地址要唯一,线缆不要热插拔 |
| RS-232 | 一般115200bps级 | 几米到十几米 | 老电源、老DMM | 注意电平标准,别直接当USB用 |
| USB | 几十MB/s | 几米 | 新仪器、单机即插即用 | 需要装对应VISA/USB驱动,占用系统USB资源 |
| LAN(LXI) | 百兆起步 | 跨机柜 | 产线集中管理、多台组网 | 分配静态IP,注意VXI-11端口占用 |
实际选型时,我优先推荐LAN。原因很简单:新仪器普遍带网口,能跨机柜管理,多台仪器只需要一台交换机就能连到电脑,不用纠结驱动和线缆。USB适合临时单机调试,插上就能用,但多台设备同时插的时候Windows下偶尔会掉驱动,批量跑数据时稳定性不如LAN。GPIB我建议只在老仪器场景用,毕竟很多老电源、老DMM只有GPIB口,没法选。串口能用,但速度和稳定性都比较勉强,除非是十几年前的设备,否则不建议新搭系统时优先考虑。
还有一个关键点是VISA层。无论你选什么接口,电脑上都要装一套VISA运行库,NI-VISA或者Keysight IO Libraries Suite都可以。装好之后,它会把GPIB、USB、LAN、串口统一成一套API,你的代码就不用关心底层走的是什么协议了。
2.2 三板斧:识别仪器、下发配置、读取数据
用SCPI控制仪器,核心就三件事:识别、配置、读数。先把这三件事练熟,后面搭系统就有底了。
第一件事,连接仪器后第一件事永远是发*IDN?。这条命令是IEEE 488.2标准规定的识别命令,仪器会返回厂商、型号、序列号和固件版本。如果你连这条命令都读不到返回值,那后面所有操作都白搭。把读到的信息和仪器背后的铭牌对一下,基本能确认连接没问题。
第二件事,下发配置。以最常见的数字万用表测直流电压为例,命令是CONF:VOLT:DC 10,0.001。这里的含义是:把量程设为10V,分辨率设为0.001V。为什么要把两个参数写清楚?因为如果你用自动量程,仪器每次切换量程都要时间,批量测量时速度会慢很多。固定量程之后,仪器一次配置、多次测量,速度能差出一个数量级。
第三件事,读取数据。最简单的方式是用READ?,这条命令会触发一次测量并返回结果。返回的是一串字符串,比如+1.000000E+01\n,需要在代码里做类型转换才能当成数值用。这里有个小坑:不同仪器返回格式不一定一样,有的带单位,有的带换行符,解析之前先把字符串strip一下,再按实际情况处理。
2.3 超时、清错、同步:无人值守脚本的保命三件套
我见过太多人写自动化脚本,功能都对了,但一挂过夜就卡死。问题往往出在最容易被忽略的三个地方:超时没设、错误队列没清、同步没做。
超时是最基本的。pyvisa默认超时时间很短,而测量仪器的响应时间有时远比你想象得慢。实测下来,做无人值守时timeout至少设到10秒,也就是10000毫秒,否则仪器一忙,脚本立刻报错退出。如果仪器做长时间扫描,比如扫描卡一轮100个通道,timeout要设到30秒以上才稳。
错误队列一定要处理。仪器运行中如果出现命令语法错误、量程溢出等问题,会把错误记到内部队列里。你如果继续往下发命令,仪器可能表现很正常,但错误会堆积。写代码时定期用SYSTem:ERRor?读一下错误队列,有错就记录并处理。同时,每次测试开始前先发一条*CLS,把状态寄存器清干净,避免上一次残留的状态影响这次判断。
同步机制是这个环节的关键。SCPI里最常用的同步命令是*OPC?,意思是“操作完成后再返回”。比如你让信号源切换频率,仪器执行这个操作需要时间,如果你紧接着就去读输出,可能读到的是切换前的值。发完配置命令后,先发*OPC?,等它返回1,再发下一步命令,就能保证时序正确。无人值守脚本的核心就是“一步一步来”,不要抢跑。
3. 多台仪器协同调度的落地实操:从单台到一群
3.1 先分层再写码:设备接口、任务调度、数据管理的边界
单台仪器自动化跑通了,紧接着就会遇到多台仪器的调度问题。一台信号源负责给激励,一台可编程电源负责供电,一台DMM负责测输出,中间还有待测件切换。如果把这些逻辑全堆在一个脚本里,前几次运行可能没问题,等你加测试项、换设备的时候就会很痛苦。
我的习惯是把整个系统拆成三层。设备接口层负责和硬件打交道,每台仪器封装成一个类,提供open、close、write、query、idn这些基础方法;任务调度层负责把测试流程拆成一个个任务,放到任务队列里逐个执行,需要并发的场景再加锁控制;数据管理层负责把测试结果记录下来,写CSV、写SQLite、判断PASS/FAIL都在这一层。分层的好处是换仪器不需要改业务代码,改测试流程也不会影响设备连接,出问题的时候还容易定位。
有人可能会觉得这么小的系统搞三层太隆重。但我的经验是:当你有超过三台仪器、测试流程超过二十个步骤,不分层后面维护成本会指数级上升。不用搞得很复杂,代码里分清楚“谁负责通信、谁负责调度、谁负责存数据”这三条线就够了。
3.2 Python + pyvisa 跑通一套完整的多仪器测试流程
下面我用Python加pyvisa写一个完整示例。这个例子的场景是:信号源输出1kHz、0.5V正弦波,电源给待测件供12V电,DMM测量待测件输出,连续测10次取平均。这是很典型的实验室小型自动化流程,代码可以直接参考改造。
首先安装依赖:
pip install pyvisa电脑上需要先装好NI-VISA或Keysight IO Libraries,否则pyvisa找不到底层驱动。
import pyvisa import time # 初始化资源管理器 rm = pyvisa.ResourceManager() print(rm.list_resources()) # 打开三台仪器,地址以实际设备为准 dmm = rm.open_resource('USB0::0x0957::0x2A19::MY59012345::INSTR') psu = rm.open_resource('TCPIP0::192.168.1.50::inst0::INSTR') sig = rm.open_resource('TCPIP0::192.168.1.51::inst0::INSTR') dmm.timeout = 10000 psu.timeout = 10000 sig.timeout = 10000 # 复位所有仪器,确保从干净状态开始 for inst in (dmm, psu, sig): inst.write('*RST') inst.write('*CLS') # 信号源:输出 1kHz、0.5V 正弦波 sig.write('FREQ 1000') sig.write('VOLT 0.5') sig.write('OUTP ON') # 电源:设置 12V / 1A,先设限流再开输出 psu.write('VOLT 12') psu.write('CURR 1') psu.write('OUTP ON') # DMM:固定量程,连续测量 10 次 dmm.write('CONF:VOLT:DC 20,0.001') results = [] for i in range(10): raw = dmm.query('READ?') value = float(raw.strip()) results.append(value) time.sleep(0.2) # 收尾:先关负载相关输出,再关闭仪器 psu.write('OUTP OFF') sig.write('OUTP OFF') dmm.close() psu.close() sig.close() average = sum(results) / len(results) print(f'平均电压: {average:.6f} V')这里有两个细节值得强调。电源的CURR 1是限流设置,一定要在OUTP ON之前发,否则如果待测件短路,电源可能直接进入限流保护甚至过放。另外DMM固定到20V量程而不是用自动量程,是为了避免每次测量时仪器花时间去切换量程,这个细节在长时间批量测量时非常关键。
如果测试任务更多,可以用queue加threading把它变成小型调度系统。下面这段代码演示的是多通道切换加两个工作线程消费任务的模式,适用于带扫描卡的数字万用表,或者任何需要并发处理的场景。
import threading import queue import time task_queue = queue.Queue() inst_lock = threading.Lock() # 任务列表:通道号、下限、上限、稳定等待时间 tasks = [ (101, 4.90, 5.10, 1.0), (102, 3.70, 3.90, 0.5), ] for task in tasks: task_queue.put(task) def worker(worker_id): while not task_queue.empty(): ch, low, high, settle = task_queue.get() try: # 同一台DMM同一时刻只允许一个线程操作 with inst_lock: dmm.write(f'ROUT:CLOS (@{ch})') time.sleep(settle) raw = dmm.query('READ?') value = float(raw.strip()) status = 'PASS' if low <= value <= high else 'FAIL' print(f'[{worker_id}] ch{ch} value={value:.4f} {status}') finally: task_queue.task_done() threads = [threading.Thread(target=worker, args=(i,)) for i in range(2)] for t in threads: t.start() for t in threads: t.join() task_queue.join()注意,这里dmm.write(f'ROUT:CLOS (@{ch})')是针对带扫描卡或者开关矩阵的DMM。如果你的DMM没有多通道能力,直接把ROUT:CLOS换成对应的通道切换手段,或者干脆顺序执行测试项即可。并发控制的核心是inst_lock——同一台仪器同一时刻只能被一个线程操作,否则两个线程同时往仪器发命令,返回值就乱套了。
3.3 数据落盘、结果判定和断线重连:让脚本能自己过夜
自动化测试跑到后面,最大的价值不是“省人工”,而是“能积累数据”。所以数据怎么落盘,从第一天就要想清楚,不要等测试跑完了才去翻日志。
我的建议是最简单的方式起步:CSV文件加SQLite双写。CSV方便用Excel直接看,字段设计成时间戳、测试项、测量值、下限、上限、判定结果、备注。写CSV的时候有个小坑:用Python的csv模块,打开文件时指定encoding='utf-8-sig',否则用Excel打开中文会乱码。另外多线程写文件一定要加锁,否则数据行会交错。
如果后续需要做趋势分析,建议同时写入SQLite。代码就几行:
import sqlite3 conn = sqlite3.connect('test_results.db') conn.execute('''CREATE TABLE IF NOT EXISTS results ( ts TEXT, item TEXT, value REAL, low REAL, high REAL, result TEXT )''') conn.execute( 'INSERT INTO results VALUES (?, ?, ?, ?, ?, ?)', ('2025-01-01 12:00:00.123', 'ch101', 4.982, 4.90, 5.10, 'PASS') ) conn.commit()断线重连和过夜运行是无人值守的最大考验。我的处理原则是:不是简单地加一个大循环重试,而是在每次操作失败时,先关闭资源、释放句柄,等一段时间再重新打开资源。重试等待时间可以按1秒、3秒、5秒递增,避免仪器还没恢复就反复冲击接口。连续重试超过5次就直接报错并写日志,绝不死循环。
还有一台容易忽略的事:仪器的自动休眠。不少仪器默认在长时间无操作后会进入待机或屏幕保护状态,某些老型号甚至会自动断开连接。如果测试流程中间要停几分钟等待温升或稳定,最好在流程里定期发一条查询命令“活动”一下,或者查手册把自动休眠关掉。这个问题不解决,你再怎么调试代码都压不住仪器半夜掉线。
4. 常见问题与排查技巧实录:这些坑我替你踩过了
4.1 连不上、找不到仪器,问题到底出在哪
做自动化测试的人,第一道坎永远是“仪器连不上”。根据我这几年的经验,先用资源列表确认系统能看到设备,再往下排查。
| 现象 | 排查顺序 | 常见处理方法 |
|---|---|---|
list_resources()里没有设备 | 驱动→接口→供电 | 装VISA库、检查USB驱动、确认仪器上电 |
| 能看到设备但连接报错 | 地址→权限→占用 | 核对GPIB地址、LAN IP、确认别的程序没占用 |
| LAN口能找到但发命令没响应 | 端口→远程模式 | 确认VXI-11端口、SCPI端口,检查仪器面板是否处于本地锁定 |
| GPIB设备读写超时 | 地址→终端器 | 确认GPIB地址唯一,首尾设备接终端电阻/终端器 |
我印象最深的一次定位经历:同事把GPIB地址拨码设成了6,代码里打开的是地址3,仪器当然一直没反应。排查了两个小时,最后拿厂商自带的连接工具手动连了一下地址6,才能发现是地址对不上。所以现在我的习惯是:先用厂商自带的通信工具(就是Keysight Connection Expert或者NI-MAX)手动敲一条*IDN?,如果能通,再回到代码里找问题。
还有网络仪器的IP配置。新仪器默认可能是DHCP,但有些仪器出厂默认固定IP,和电脑不在同一网段就很难发现。插上网线先ping一下仪器IP,能ping通再谈VISA连接。这一套排查顺序下来,九成连不上的问题都能解决。
4.2 读数乱码、脚本卡死、多线程抢占,怎么定位和修复
连上了,接下来就是各种奇奇怪怪的读取问题。最典型的是读数偶尔对、偶尔乱,或者脚本跑到一半就卡住不动。这一类问题的根因,绝大多数是时序和并发,而不是命令写错。
先讲乱码。仪器返回的字符串经常带换行、回车,有的还带提示符或者单位,直接float(raw)肯定报错。我的标准做法是raw.strip()之后再解析,如果有单位,用正则把数字部分提出来。编码上,老仪器通常用ASCII,新仪器多数是UTF-8,如果遇到乱码,先试encode('ascii', 'ignore'),再试UTF-8,逐个排除。
再讲卡死。卡死最常见的原因是前一条命令还没执行完,下一条命令就发出去了。SCPI本身没有强制时序,仪器忙的时候不理你是很正常的。解决方式就是我在2.3里说的*OPC?,每次关键命令后做一次同步,宁可慢一点,也要稳。另一个原因就是timeout设了但不够,仪器扫描时间长的时候尤其容易触发。
多线程抢占的问题,则一律用锁来解决。每台仪器配一个threading.Lock,任何操作这台仪器的地方都先with lock:再执行。这种方式简单粗暴,但效果很稳。不要指望用延时来规避并发问题——延时只会降低冲突概率,不会消除冲突,该上锁就上锁。
4.3 电源顺序、量程切换、固件升级:几个容易忽略的细节
最后说几个容易被人忽略的细节,但每一项都可能导致严重后果。
电源顺序是最重要的一条。先把电压和限流设置好,再打开输出;关闭的时候先关输出,再动线路。我见过一次反面教材:同事在电源还没设置限流的情况下直接打开输出,待测件瞬间短路,电源立刻过流保护,板子上一颗电容直接鼓包。为了保护设备,除了代码上严格控制顺序,接线端子和被测件连接处也要加保险丝或者电流保护模块。
量程切换的问题,前面提了一次,这里再展开说。很多测量类仪器默认自动量程,它的优点是方便,缺点是慢。在批量测试时,如果每次测量都是自动切换量程,一次测量可能要多花几十到几百毫秒。固定量程之后,仪器不再反复试探档位,速度提升非常明显。但要注意,固定量程的前提是你对被测值范围有把握,否则超出量程读数会不准甚至报错。
还有一个很多老工程师都中过招的事:仪器固件升级之后,某些命令的行为可能会变。比如某型号电源升级固件后,原来直接写VOLT 12就能设置电压,新固件要求写VOLT 12.0,不然可能不被识别。固件升级之后,第一件事是把自动化测试的冒烟用例完整跑一遍,确认所有命令都正常,再放到生产流程里。
最后再分享一个实操技巧:日志的时间戳一定要精确到毫秒。排查时序问题的时候,时间戳不够细,根本看不出谁先谁后,也定位不了是哪个环节卡了多久。统一用time.time()记录,配合测试步骤编号,出问题的时候翻日志能省大量时间。
我个人在实际操作中的体会是:把测试交给脚本这件事,真正的门槛不是写命令,而是把“失败路径”想清楚。超时、重连、清错、日志,这些看起来琐碎的东西,才是决定一套自动化测试系统能不能扛住过夜运行的关键。只要把这些基础打牢,哪怕是几台不同的仪器混在一起,也可以组织得井井有条。希望这篇分享能帮你少踩几个坑,早点把守仪器的时间省下来,去做更有价值的事。