MATLAB通过SCPI控制普源示波器实现波形自动采集

📅 发布时间:2026/9/19 18:33:01
MATLAB通过SCPI控制普源示波器实现波形自动采集
结论先行用MATLAB通过SCPI命令控制普源示波器这件事本质上就是把示波器当作一个带命令行接口的远端设备来用。你不需要再去按面板上的“存储”键、插U盘、拷贝CSV直接在MATLAB里写几行命令就能把波形数据、测量结果、屏幕截图全部拉回来还能循环做批量采集。这篇我把自己实测过的完整流程、代码、踩坑记录都整理出来了从驱动安装到数据解析全都有适合正在做硬件调试、自动化测试或者实验室里需要批量记录波形的人参考。先说一个我自己的真实场景。以前测电源纹波要在不同负载电流下各抓一组波形手动操作示波器的流程是暂停采集、按存储、选CSV格式、插U盘、拷贝、再导入MATLAB画图。一组数据折腾几分钟几十组下来人已经麻了。换成SCPI之后整个流程就是MATLAB脚本自动跑设置通道、调整时基、触发采集、拉回数据、直接绘图分析一条线跑完一组耗时不到一秒。这也是我写这篇文章的初衷别再当人肉U盘了让示波器自己把数据交出来。1. 整体方案设计与技术选型1.1 为什么选择SCPI而不是官方上位机市面上的示波器上位机软件包括普源官方的PC端工具做得其实都不错能看波形、能导出数据、能截图。但对做自动化的工程师来说这些工具的短板非常明显不支持脚本批量操作。你要测100组工况上位机软件就得手动点100次导出跟直接在示波器面板上操作没什么本质区别。SCPIStandard Commands for Programmable Instruments就不一样了它是仪器行业的标准命令集本质上是给仪器写命令行指令。你想让示波器做什么就用文本命令告诉它比如“把时基设为200微秒每格”“把CH1的数据发给我”。这些命令可以一条条写在脚本里循环执行完全不需要人工介入。而且SCPI是一个开放的行业标准不是普源私有协议。你今天用这套方法控制普源明天换成是德、泰克命令大同小异只是前缀和参数格式有差异。学一次长期复用这个性价比非常高。1.2 技术链路MATLAB VISA SCPI三层架构实际跑的链路是三层最上层是MATLAB负责发命令、收数据、做处理和可视化。中间层是VISAVirtual Instrument Software Architecture它是一个统一的I/O接口库帮你屏蔽USB、网口、GPIB等物理接口的差异。你的电脑通过USB线连示波器VISA负责在MATLAB和示波器之间建立通信管道。最底层是示波器固件里的SCPI解析器收到命令后执行操作把结果返回。这个三层架构有什么好处答案是解耦。MATLAB里不用关心USB底层协议怎么封装示波器也不用关心你用的是不是MATLAB。标准化的代价是学习成本但收益是可移植性和稳定性。我实测下来MATLAB连接普源示波器连接方式选USB最方便即插即用不用配IP地址。如果示波器和电脑在同一局域网走网口TCP/IP也可以速度更快适合大数据量传输这个后面讲性能优化时再细说。2. 环境准备与设备握手2.1 安装VISA驱动与设备识别在MATLAB里调visa函数之前电脑上必须先装一套VISA运行时。一般装NI-VISA比较多也可以装普源官方提供的驱动包。装完之后用USB线连接示波器和电脑打开设备管理器能看到识别出一个USB Test and Measurement Device或者类似名字的硬件说明驱动层已经通了。接下来打开NI MAXNI VISA自带的管理工具在“设备和接口”里能看到示波器选中后会显示VISA资源名形如USB0::0x1AB1::0x04CE::DS1ZA0000001::INSTR这个字符串很重要等下MATLAB连接时就靠它。其中0x1AB1是普源的USB厂商ID0x04CE是产品ID后面那串是设备序列号。注意如果插上USB线设备管理器里是未知设备通常是因为没装对驱动先装好驱动再连接。还有一个细节示波器面板上的IO设置里要把USB Device模式打开有些型号默认关着。2.2 MATLAB中创建VISA对象装好驱动、确认设备能被识别后开始写MATLAB代码。注意你的MATLAB需要安装Instrument Control Toolbox没有的话在附加功能里装上这步不用我多说了。创建连接对象的代码很简单% 创建VISA对象 scope visa(NI, USB0::0x1AB1::0x04CE::DS1ZA0000001::INSTR); % 关键属性配置 scope.InputBufferSize 50e6; % 接收缓冲区按需调大50MB辅助处理大批量数据 scope.Timeout 20; % 超时时间单位秒 % 打开连接 fopen(scope); % 握手测试读取设备标识 idn query(scope, *IDN?); disp(idn);这里有个需要注意的地方visa(NI, ...)的第一个参数NI不是随便写的它对应你装的VISA驱动厂商。装了NI-VISA就填NI如果装的是Keysight IO Libraries就填keysight这个要和实际安装的驱动保持一致否则会报找不到VISA库。运行到query(scope, *IDN?)这句时示波器会返回一行设备信息一般包含厂商、型号、序列号和固件版本比如RIGOL TECHNOLOGIES,DS1104Z,DS1ZA0000001,00.04.01看到这行字说明链路已经打通了。我第一台设备返回这行字时还挺激动的因为这意味着后面的所有自动化都有戏了。2.3 连接失败的常见原因这一步最容易卡住的地方有三个第一个是资源名不对。很多朋友从网上复制了一段代码资源名还是别人那台的序列号直接跑当然连不上。要先去NI MAX里查自己设备的资源名复制粘贴进来。第二个是缓冲区开太小读取数据时溢出报错。如果用默认的InputBufferSize读几条命令没问题一读波形数据就报错这时候把缓冲区开到10MB以上基本就稳了。第三个是连接被占用。有时候之前跑过的脚本没有执行fclose资源还被占着再跑新脚本就会报权限错误。养成写完就fclose的习惯或者在脚本开头先清理一下对象。3. 核心实操用SCPI命令读取波形数据设备连上只是第一步真正有用的是把示波器屏幕上的波形数据变成一组可以计算的数字。这一节我直接把完整流程拆开讲。3.1 示波器端的几个关键设置在读取数据之前得先跟示波器确认几件事读哪个通道、波形是什么格式、从哪里开始读、读多少个点。对应的SCPI命令如下% 选择读取CH1通道的数据 fprintf(scope, :WAV:SOUR CHAN1); % 设置为NORM模式读取示波器屏幕显示区的波形点 fprintf(scope, :WAV:MODE NORM); % 数据格式设为BYTE二进制格式节省传输量 fprintf(scope, :WAV:FORM BYTE); % 查询当前波形点数 fprintf(scope, :WAV:POIN?); points str2double(query(scope, :WAV:POIN?)); disp([当前波形点数: , num2str(points)]); % 设置读取范围从第1个点到全部点 fprintf(scope, :WAV:STAR 1); fprintf(scope, :WAV:STOP %d, points);很多人不理解NORM模式和RAW模式的区别。简单说NORM模式读的是示波器屏幕上显示的那组数据点数一般是一千多响应快适合做常规测量。RAW模式读的是示波器采集存储里的完整原始采样点点数可以到几十万甚至上百万数据量大但能看到更多细节。对于一个通用场景先用NORM模式跑通全流程再根据需求切RAW模式这是比较稳妥的思路。3.2 读取二进制块BINBLOCK并解析设置完成后发送读取命令示波器会返回一个二进制块。% 发送读取命令 fprintf(scope, :WAV:DATA?); % 读取二进制块数据按uint8格式存储 rawData binblockread(scope, uint8); % 清理缓冲区中残留的换行符 fread(scope, 1, uint8);这里binblockread是Instrument Control Toolbox里专门读二进制块的函数它会自动解析示波器返回的数据块头不用自己手动去剥。我之前不知道有这函数用fread硬读经常因为字节错位解析出乱七八糟的数字后来翻文档才发现有专门函数。数据读回来之后rawData就是一个uint8类型的数组里面每个元素对应波形上的一个采样点的原始值。这个值不是电压值需要根据示波器的垂直刻度换算。3.3 波形数据转换成实际电压值普源示波器BYTE格式下原始值是一个范围在0到255的整数。要换算成真实电压最稳妥的方式是读取示波器的波形预设参数直接在示波器上把换算系数问出来% 读取波形预设参数返回值里包含YIN、YOR、YREF等信息 fprintf(scope, :WAV:PRE?); preStr query(scope, :WAV:PRE?); preValues strsplit(strtrim(preStr), ,);不同型号的:WAV:PRE?返回字段个数不一样但一般前几个字段就包含了关键信息。我自己用的时候会针对手头型号确定字段索引然后这样计算电压值% 以普源DS1000Z系列为例结合文档解析关键字段 % 假设preValues中对应YIN每字节对应的电压增量和YOR垂直偏移 % 不同型号字段位置不同这里仅示意 yincrement str2double(preValues{9}); yreference str2double(preValues{10}); yoffset str2double(preValues{11}); % 换算为电压值 voltage (double(rawData) - yreference) * yincrement yoffset;如果没有拿到这几个参数也可以用通道的垂直刻度V/div和位置手动估算但精度会差一些。实测下来用:WAV:PRE?返回的参数做换算和示波器屏幕上显示的电压值是一致的可以放心用。至于时间轴同理也建议直接读取X轴增量参数% 查询X轴增量单位秒 fprintf(scope, :WAV:XIN?); xin str2double(query(scope, :WAV:XIN?)); % 生成时间轴NORM模式下从0开始 time (0:points-1) * xin;拿到时间和电压两组数组之后就可以直接plot了画出来的波形和示波器屏幕上显示的基本一致。3.4 读取RAW模式的注意事项NORM模式能覆盖大部分应用但如果要做更精细的分析比如看毛刺、测高速信号的边沿需要用RAW模式读取完整采样点。RAW模式的命令就是把MODE改成RAW其他步骤类似。不过RAW模式有几个坑要先说清一是内存。RAW模式下示波器存储深度可能很大如果一次读几百万个点InputBufferSize要调大建议至少50MB起步电脑内存也最好预留足够空间。二是速度。一次读大数据量在USB连接下可能需要几秒钟如果做批量测试会明显变慢。这种场景建议改用网口连接速度能快一个量级。三是触发。RAW模式读到的数据是示波器内存里的全部采样点如果没触发好可能抓到一屏幕没用的数据。读取前要确保波形已经稳定触发必要时先用单次触发模式等捕获完成再读。4. 自动批量采集与数据落地读单次波形只是热身SCPI的真正价值在于批量自动化。我自己的应用是测负载调整率需要让示波器在不同的负载条件下各抓一组输入波形然后统计纹波峰峰值。手动操作大概要一小时脚本跑完只要几分钟。4.1 封装读取函数先把上面读取流程封装成一个可复用的函数function [time, voltage] readWaveform(scope, channel) % 选择通道 fprintf(scope, [:WAV:SOUR , channel]); % NORM模式 fprintf(scope, :WAV:MODE NORM); % BYTE格式 fprintf(scope, :WAV:FORM BYTE); % 查询点数 fprintf(scope, :WAV:POIN?); points str2double(query(scope, :WAV:POIN?)); % 设置范围 fprintf(scope, :WAV:STAR 1); fprintf(scope, :WAV:STOP %d, points); % 读取数据 fprintf(scope, :WAV:DATA?); rawData binblockread(scope, uint8); fread(scope, 1, uint8); % 读取参数并换算 fprintf(scope, :WAV:XIN?); xin str2double(query(scope, :WAV:XIN?)); time (0:points-1) * xin; % 电压换算这里简化处理生产中建议读WAV:PRE fprintf(scope, :CHAN1:SCAL?); % 以CH1为例 vScale str2double(query(scope, :CHAN1:SCAL?)); voltage (double(rawData) - 128) * vScale / 128; end这个函数接收已连接的VISA对象和通道名返回时间和电压数组可以直接用来计算峰峰值、有效值或者直接画图。4.2 批量场景的代码模板假设我要在不同负载条件下各读100次波形统计纹波% 预设负载电流列表 loadCurrents [0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]; results zeros(length(loadCurrents), 1); for i 1:length(loadCurrents) % 这里通过另一个SCPI通道控制电子负载不同设备命令不同 setELoadCurrent(loadCurrents(i)); pause(0.5); % 等待电路稳定 % 设置示波器单次触发 fprintf(scope, :TRIG:MODE SINGLE); fprintf(scope, :TRIG:SWE SINGLE); % 等待触发完成状态从TRIG变为STOP for j 1:50 fprintf(scope, :TRIG:STAT?); trigStat query(scope, :TRIG:STAT?); if contains(trigStat, STOP) || contains(trigStat, TDON) break; end pause(0.1); end % 读取CH1波形 [time, voltage] readWaveform(scope, CHAN1); % 计算纹波峰峰值 results(i) max(voltage) - min(voltage); fprintf(负载电流 %.2fA纹波峰峰值 %.1fmV\n, ... loadCurrents(i), results(i)*1000); end这段代码里有几个工程细节值得展开说。首先是pause(0.5)等待电路稳定。真实硬件不是理想模型切换负载后电源会有瞬态响应不等待就采集很容易抓到过渡过程。等待时间根据被测对象动态响应特性调整保守一点没坏处。其次是单次触发模式。批量测试时用RUN模式一直采集可能抓到乱七八糟的波形单次触发可以保证每次抓到的都是触发后的稳定波形。等待触发完成的循环也很关键直接读容易读到旧数据。最后是contains(trigStat, STOP)判断。不同型号触发状态返回的字符串有差异有的返回STOP有的返回TDON用contains做模糊匹配比直接比较字符串更稳。读回来的数据可以顺便存成CSV或者MAT文件方便事后复盘也方便写测试报告时直接贴图% 保存当前数据 save(sprintf(waveform_%dA.mat, loadCurrents(i)), time, voltage);4.3 性能优化思路如果做大数据量批量采集有几个优化经验可以分享。如果走USB一次读100万点大概是几百毫秒到一秒级别问题不大。但如果每秒要抓好几次或者一次要读多个通道建议改成网口TCP/IP连接。同一台设备网口传输速率通常比USB高不少实测抓一次100万点能从接近一秒降到两三百毫秒。MATLAB侧也有优化空间。读回来的数据如果直接存成MAT文件后续加载很快如果转成CSV文件体积大而且读写慢适合需要外部工具查看的场景。大批量处理时可以考虑把数据拼成大数组一次性写入逐条写入会比较慢。另外关闭屏幕刷新也能提速。示波器在采集时还要画屏幕本身就有开销。通过SCPI关掉显示波形层只保留数据采集能进一步降低整体延迟。5. 常见问题与排查技巧实录这部分是重头戏。我用这套方案断断续续踩了不少坑整理了最有代表性的几个做个速查表现象可能原因解决办法fopen报错找不到设备资源名错误 / 驱动未装好NI MAX里重新确认资源名重装VISA驱动fopen报权限错误上一个会话未关闭确认之前脚本执行过fclose*IDN?无返回或超时仪器处于本地锁定 / USB线松动检查IO设置拔插USB线读取数据全是0通道未开启 / 没有信号输入确认:CHAN1:DISP是ON状态读取数据明显不对通道选择错误 / 换算系数错误确认:WAV:SOUR参数检查YIN/YORbinblockread报错缓冲区太小 / 命令序列错误调大InputBufferSize重发WAV:DATA?读取的点数和预期不符示波器显示区点数设置不一样用:WAV:POIN?查询实际点数后再设置5.1 设备连接类问题连接问题最常见占了我当初踩坑的一半以上。新手最容易遇到的问题就是fopen时报“VISA: The specified resource could not be found”不用怀疑百分之九十是资源名填错了。注册表里可能有多个资源名要把NI MAX里显示的复制全一个字符都不能多不能少。0x1AB1这种十六进制标识大小写也不要改。还有一个比较隐蔽的问题设备装了两套VISA库。电脑上同时装了NI-VISA和Keysight的IO LibrariesMATLAB的visa函数可能加载了其中一套但设备驱动注册在另一套下面结果就是找不到设备。这种情况建议只保留一套VISA库或者统一用NI-VISA。USB线也有讲究尽量用示波器原装的线有些线只支持充电没有数据线芯插上去什么反应都没有这个我真的遇到过。5.2 数据内容类问题连上了命令也有响应但读回来的数据看着不对这是另一大类问题。举个典型情况读回来的voltage数组全是0或者全是同一个常数。排查思路是检查通道有没有开。如果:WAV:SOUR选的是CHAN1但CHAN1根本没接信号或者被关闭CHAN1:DISP OFF读回来的就是底盘数据全是0。检查探头衰减比。探头打在10X档但示波器通道的探头比例设成了1X读回来的电压值会比实际小10倍。这个属于基础操作问题但新手很容易忽略尤其是连续换探头之后。检查换算系数。有些换算公式里用了固定的128作为中点有些示波器的BYTE模式配置是从0到255中点确实在128附近但带符号时高位会不同。建议从:WAV:PRE?里读取准确的YREF参数去做换算别用固定值硬套。还有一次我遇到波形数据读回来形状对但时间轴不对横坐标长度和点数对不上。后来发现是:WAV:STOP设的比实际点数大示波器返回了空数据填补导致数组末尾多了一截。所以设置STAR和STOP之前一定要先查询实际的POIN用查询值去设置STOP。5.3 二进制块读取的坑binblockread这个函数省了不少事但它不是万能的。有一次我读到的数据前面几百个点是对的后面全乱套排查半天发现是上一次命令的返回数据没清干净。SCPI交互是流式的上一次查询如果没读完遗留的数据会混到下一次读取里。解决方法很粗暴每次读取前先清一下缓冲区再发命令或者用fread把残留数据读掉。另一个常见问题是:WAV:DATA?返回的二进制块里可能有多个数据段binblockread默认读一个块就返回了后面的数据没人管。如果发现点数和预期差很多可以多调用几次binblockread看看是不是有后续数据。还有一点是关于换行符的。示波器返回二进制块之后一般会带一个换行符这个换行符不会被binblockread消费掉需要用fread(scope, 1, uint8)显式读掉否则会影响下一次查询。这个小细节不处理的话经常会出现数据错位好几个字节的问题。5.4 性能与稳定性问题实战批量采集对稳定性要求高跑几百次中间掉链子一次前面的数据可能就要重来。我的经验是在大循环里加个超时保护。比如等待触发的循环里设置最大尝试次数超时就重新初始化连接而不是无限等下去。很多设备的SCPI服务一旦卡住fopen不会超时自动断开需要手动删掉对象重建。大数据量读取时如果电脑内存不够读几百万个点直接爆内存。这种情况建议分段读取先设置:WAV:STAR和:WAV:STOP读前半段再改范围读后半段最后拼接。普源示波器的SCPI是支持指定读取区间的这个特性正好用来做分块传输。仪器端还有一个容易忽略的就是屏幕亮度。示波器屏幕常亮会让面板发热连续跑几小时高温可能影响采样精度。做长时间批量测试时可以用SCPI关闭屏幕背光或者设置自动关屏虽然听起来不是大事但实测对稳定性的提升是实实在在的。5.5 一点提前避坑的经验最后分享一个很实用的习惯写批次采集脚本时把每步操作的返回状态都打印出来尤其是触发状态、点数字段。虽然看起来是废话但真正出问题时看日志定位比瞎猜快得多。还有如果只在本地做实验建议先跑通一个最小闭环——连接、握手、读一组数据、关闭——然后再往上加逻辑。我见过不少同学一上来就写几百行的批量采集结果连最基本的设备都连不上排查起来非常痛苦。先小后大逐步迭代是自动化测试里最靠谱的推进方式。我自己的习惯是先确认读写单条命令没问题再封函数最后写循环每一步都有验证出问题也知道该往哪里看。