资讯动态

基于PyQt5与泰克示波器的自动化测试方案:实时波形刷新与日志打印

发布时间:2026/10/22 5:03:10 来源:尧图企业网站定制
简介这是一套面向自动化测试初学者与仪器控制开发者的Python实战项目围绕泰克MDO3014示波器实现上位机软件解决波形实时显示、图像保存与日志读取等测试场景需求。资源包共16个文件约139KB包含2个py主程序与1个ui界面文件另有5个xml配置、3张png界面截图、2个txt日志样例及pyc缓存等结构紧凑便于直接运行与二次开发。项目支持示波器界面实时刷新图片、实时打印并保存log窗口信息可单次循环执行自编测试用例同时详细演示常用QT控件在Python中的调用方式。目前已有3659人学习下载适合想掌握PyQt5界面开发与仪器通信的读者参考能从中获得可复用的界面框架、日志处理思路与示波器联调经验快速搭建自己的自动化测试工具。1. 从手动按示波器到自动化测试这套方案要解决什么如果你在实验室里做过硬件测试大概率经历过这样的场景一块板子上电后你蹲在泰克示波器前面手动调时基、调触发电平、截图、记数据测一轮下来半小时没了测十轮就是五个小时中间还容易漏记某个通道的波形。更麻烦的是测试报告要手动整理截图要一张张命名归档一旦发现异常波形想回溯当时的参数配置只能靠记忆。这套基于 Python PyQt5 泰克示波器的自动化测试方案要解决的就是这个问题把「配置仪器 → 采集波形 → 实时显示 → 记录数据 → 生成报告」整条链路串起来用一个桌面端界面统一控制。它适合正在做硬件测试毕设的学生、需要搭建产线自动化测试工位的工程师以及任何想把泰克 3000 系列示波器从「手动工具」变成「可编程测试节点」的人。核心关键词就三个PyQt5 做界面、Tektronix3000 做数据源、实时刷新图片和打印信息做交互反馈。2. 需求拆解与技术选型为什么是 PyQt5 加泰克 3000 系列2.1 测试场景的真实需求先把这个项目的需求说清楚不然后面选型没有依据。一个典型的自动化测试场景通常包含这几个动作第一通过程控接口连接示波器设置通道、时基、触发模式等参数第二启动采集获取波形数据第三把波形画成图实时显示在界面上第四把关键测量值频率、峰峰值、上升时间等打印到日志区域第五保存数据和截图供后续生成报告。这五个动作里前两个是仪器控制第三个是 GUI 绘图第四个是日志输出第五个是文件 IO。手动操作时这些动作是串行且依赖人的自动化方案要做的是把它们变成可配置、可循环、可无人值守的流程。这里有一个容易被忽略的点实时性。很多同学一开始觉得「采集完再画图」就行但实际测试中你需要一边采集一边看波形变化否则等一轮跑完才发现触发没设对时间全浪费了。所以「实时刷新图片」和「实时刷新打印信息」不是锦上添花而是这个方案能不能真正替代手动操作的关键。2.2 为什么选 PyQt5 而不是 Web 或命令行技术选型上仪器控制层用 Python 是共识因为 PyVISA 这个库把 VISA 通信封装得很干净几行代码就能跟示波器对话。争议点在界面层有人用 WebFlask ECharts有人用命令行有人用 PyQt5。我选 PyQt5 的理由很实际。第一部署简单。Web 方案要起服务、开浏览器实验室的测试电脑往往不联网多一层就多一个故障点。PyQt5 打包成 exe 后双击就能跑对不写代码的测试人员友好。第二绘图性能。PyQt5 的 QGraphicsView 或者 pyqtgraph 在刷新率上比 Web 的 Canvas 重绘更可控尤其是波形数据点上千的时候Web 方案容易卡。第三线程模型清晰。PyQt5 的 QThread 和信号槽机制天然适合「采集线程 UI 线程」分离避免界面假死。命令行方案虽然轻但没法直观展示波形测试人员不买账。至于示波器选泰克 3000 系列是因为这个系列在高校和中小型实验室里保有量高支持 LAN、USB、GPIB 多种接口程控指令集基于 SCPI文档齐全PyVISA 能直接识别。你换成其他支持 SCPI 的示波器代码结构基本不用大改改一下资源地址和部分指令即可。2.3 整体架构三层分离这套方案的架构我一般会分成三层。最底层是仪器通信层负责跟示波器建立连接、发送 SCPI 指令、读取波形数据用 PyVISA 实现。中间层是业务逻辑层负责测试流程编排、数据解析、异常判断比如「设置触发 → 等待触发 → 读取波形 → 计算参数 → 判断是否合格」。最上层是界面层用 PyQt5 实现包含波形显示区、日志打印区、参数配置区和控制按钮区。三层之间通过信号槽和队列通信采集线程不直接操作 UI 控件而是发信号让 UI 线程更新。这样做的好处是即使采集过程中示波器响应慢界面也不会卡死。下面这张表是三层各自的职责和关键技术点。层级职责关键技术仪器通信层连接示波器、发送 SCPI、读取波形PyVISA、SCPI 指令业务逻辑层流程编排、数据解析、合格判断Python 多线程、NumPy界面层波形显示、日志打印、参数配置PyQt5、pyqtgraph3. 仪器通信与数据采集把示波器变成可编程节点3.1 连接示波器与基础 SCPI 指令连接示波器是第一步也是最容易翻车的一步。泰克 3000 系列支持 LAN 和 USB 两种常用接口。LAN 方式下你需要知道示波器的 IP 地址然后在代码里用 TCPIP 资源地址连接。USB 方式下资源地址是一串包含厂商 ID 和序列号的字符串通常用 PyVISA 的列表功能自动发现。import pyvisa # 创建资源管理器 rm pyvisa.ResourceManager() # 列出所有可用仪器确认示波器被识别 resources rm.list_resources() print(可用仪器, resources) # 连接示波器LAN 方式示例 # 实际使用时把 IP 换成你示波器的地址 scope rm.open_resource(TCPIP0::192.168.1.100::inst0::INSTR) scope.timeout 10000 # 超时设 10 秒波形数据大时默认 2 秒不够 # 查询仪器标识确认通信正常 print(scope.query(*IDN?))这段代码里list_resources()是排查连接问题的第一手段如果列表里没有你的示波器后面都不用谈。timeout参数建议设大一点因为读取长波形数据时默认超时经常不够会报 VisaIOError。*IDN?是 SCPI 的通用查询指令返回厂商、型号、序列号、固件版本能返回就说明通信链路通了。3.2 配置采集参数与读取波形通信通了之后要设置采集参数。泰克 3000 系列的 SCPI 指令集里常用的有通道开关、垂直档位、时基、触发模式等。下面这段代码演示了配置通道 1、设置时基、单次触发采集、读取波形数据的完整流程。# 配置通道 1打开、直流耦合、垂直档位 1V/格 scope.write(SELECT:CH1 ON) scope.write(CH1:COUPLING DC) scope.write(CH1:SCALE 1) # 设置时基为 1ms/格共 10 格 scope.write(HORIZONTAL:SCALE 0.001) # 设置触发边沿触发通道 1上升沿电平 0.5V scope.write(TRIGGER:A:TYPE EDGE) scope.write(TRIGGER:A:EDGE:SOURCE CH1) scope.write(TRIGGER:A:EDGE:SLOPE RISE) scope.write(TRIGGER:A:LEVEL 0.5) # 单次采集 scope.write(ACQUIRE:STATE ON) scope.write(ACQUIRE:STOPAFTER SEQUENCE) # 等待采集完成查询状态 import time while True: state scope.query(ACQUIRE:STATE?) if state.strip() 0: break time.sleep(0.1) # 读取波形数据 scope.write(DATA:SOURCE CH1) scope.write(DATA:ENCDG ASCII) # 用 ASCII 编码方便解析 scope.write(DATA:WIDTH 2) raw_data scope.query(CURVE?)这里有几个关键点。DATA:ENCDG ASCII让示波器返回文本格式的波形数据解析简单但数据量大时传输慢如果追求速度可以用二进制编码DATA:ENCDG RIBINARY但解析要处理字节序。ACQUIRE:STOPAFTER SEQUENCE表示采集一次就停适合单次测试如果要连续采集改成RUNSTOP或者循环触发。等待采集完成的轮询里ACQUIRE:STATE?返回 0 表示停止返回 1 表示运行中这个判断逻辑要写对否则会读到不完整的波形。3.3 数据解析与参数计算读回来的CURVE?是一串用逗号分隔的数值需要转成 NumPy 数组再结合示波器的垂直和水平刻度换算成实际电压和时间。泰克示波器的波形数据通常需要这样换算电压 (原始值 - 垂直偏移) × 垂直档位 / 垂直分辨率时间 水平偏移 索引 × 水平档位 / 水平分辨率。具体公式要看型号手册但思路是一样的。import numpy as np # 解析 ASCII 波形数据 values np.array([float(v) for v in raw_data.split(,)]) # 查询换算参数 y_mult float(scope.query(WFMPRE:YMULT?)) y_off float(scope.query(WFMPRE:YOFF?)) y_zero float(scope.query(WFMPRE:YZERO?)) x_incr float(scope.query(WFMPRE:XINCR?)) # 换算成实际电压和时间 voltage (values - y_zero) * y_mult - y_off * y_mult time_axis np.arange(len(voltage)) * x_incr # 计算峰峰值和频率 vpp np.max(voltage) - np.min(voltage) print(f峰峰值{vpp:.3f} V)这段代码里YMULT、YOFF、YZERO、XINCR是泰克示波器波形前导码里的标准参数分别代表垂直缩放因子、垂直偏移、垂直零点、水平采样间隔。不同型号的指令名可能略有差异但逻辑一致。算出来的voltage和time_axis就是后面画图要用的数据。4. PyQt5 界面与实时刷新让波形和日志动起来4.1 界面布局与控件划分界面部分我一般会分成四个区域。左上角是波形显示区用 pyqtgraph 的 PlotWidget因为它比 Matplotlib 嵌入 PyQt5 更流畅刷新率更高。右上角是参数配置区包含 IP 地址输入框、通道选择下拉框、时基和触发设置输入框。下方是日志打印区用 QTextEdit 只读模式实时追加测试信息。最底部是控制按钮区包含「连接」「开始采集」「停止」「保存数据」几个按钮。布局用 QGridLayout 或者 QVBoxLayout 嵌套保证窗口缩放时各区域比例合理。波形显示区要设置最小高度否则窗口拉小时波形会被压扁。日志区要设置自动滚动到底部方便看最新信息。4.2 多线程采集与信号槽通信PyQt5 的界面刷新必须在主线程而采集是耗时操作如果直接在按钮点击事件里写采集循环界面会卡死。标准做法是把采集逻辑放到 QThread 里通过信号把数据和日志发回主线程。from PyQt5.QtCore import QThread, pyqtSignal import numpy as np class AcquireThread(QThread): # 定义信号波形数据、日志信息 wave_ready pyqtSignal(np.ndarray, np.ndarray) log_ready pyqtSignal(str) def __init__(self, scope): super().__init__() self.scope scope self.running True def run(self): while self.running: try: # 触发采集 self.scope.write(ACQUIRE:STATE ON) self.log_ready.emit(开始采集...) # 等待采集完成 while self.scope.query(ACQUIRE:STATE?).strip() ! 0: self.msleep(50) # 读取并解析波形 raw self.scope.query(CURVE?) values np.array([float(v) for v in raw.split(,)]) # 换算逻辑省略同上一章 voltage values # 占位实际要换算 time_axis np.arange(len(voltage)) * 0.001 # 发信号给 UI 线程 self.wave_ready.emit(time_axis, voltage) self.log_ready.emit(f采集完成点数{len(voltage)}) except Exception as e: self.log_ready.emit(f采集异常{str(e)}) self.msleep(500) # 采集间隔 def stop(self): self.running False self.wait()这段代码的核心是pyqtSignal和emit。wave_ready信号携带两个 NumPy 数组log_ready携带字符串。在 UI 线程里把这两个信号连接到对应的槽函数槽函数里更新波形图和日志框。注意msleep是 QThread 的方法比time.sleep更适合 Qt 环境因为它不会阻塞事件循环。4.3 波形实时刷新与日志打印UI 线程里的槽函数负责把数据画出来、把日志打出来。波形刷新用 pyqtgraph 的setData方法它比清空重画快得多。日志打印用append方法配合ensureCursorVisible自动滚动。from PyQt5.QtWidgets import QMainWindow, QTextEdit, QPushButton, QVBoxLayout, QWidget import pyqtgraph as pg class MainWindow(QMainWindow): def __init__(self): super().__init__() # 波形显示控件 self.plot_widget pg.PlotWidget() self.plot_widget.setLabel(left, 电压, unitsV) self.plot_widget.setLabel(bottom, 时间, unitss) self.curve self.plot_widget.plot(peny) # 日志控件 self.log_text QTextEdit() self.log_text.setReadOnly(True) # 按钮 self.btn_start QPushButton(开始采集) self.btn_start.clicked.connect(self.start_acquire) # 布局 layout QVBoxLayout() layout.addWidget(self.plot_widget) layout.addWidget(self.btn_start) layout.addWidget(self.log_text) container QWidget() container.setLayout(layout) self.setCentralWidget(container) def start_acquire(self): self.thread AcquireThread(scope) self.thread.wave_ready.connect(self.update_wave) self.thread.log_ready.connect(self.append_log) self.thread.start() def update_wave(self, time_axis, voltage): # 实时刷新波形setData 比重画快 self.curve.setData(time_axis, voltage) def append_log(self, msg): self.log_text.append(msg) # 自动滚动到底部 self.log_text.ensureCursorVisible()这里的关键是setData而不是clear后重画。pyqtgraph 的setData只更新数据点不重建图形对象刷新率能到几十帧每秒完全满足实时显示需求。日志的ensureCursorVisible保证每来一条新日志视图自动滚到最新一行测试人员不用手动拖滚动条。注意采集线程里不要直接操作 UI 控件否则会报「QObject::setParent: Cannot set parent, new parent is in a different thread」之类的错误。所有 UI 更新必须通过信号槽回到主线程。5. 避坑与常见问题那些让我熬夜的翻车点5.1 连接超时与资源被占用现象程序运行几次后突然连不上示波器报「VI_ERROR_RSRC_BUSY」或者超时。原因通常是上一次连接没有正确关闭VISA 资源被占用。解决方法是每次用完调用scope.close()并且在程序退出时用try...finally保证关闭。如果已经占用了重启示波器或者换一个资源地址比如从 LAN 换成 USB能临时解决。5.2 波形数据读取不完整现象画出来的波形只有前半段或者数值明显不对。原因可能是DATA:WIDTH和DATA:ENCDG设置不匹配或者读取时超时导致数据截断。解决方法是把timeout设大并且确认DATA:START和DATA:STOP覆盖了整个记录长度。另外ASCII 编码下数据量大时传输慢可以改用二进制编码但解析要处理字节序。5.3 界面卡死与信号槽连接错误现象点击「开始采集」后界面无响应或者日志不更新。原因通常是采集逻辑写在了主线程或者信号槽连接时参数类型不匹配。解决方法是把采集放到 QThread并且检查pyqtSignal定义的类型和emit传入的数据类型是否一致。NumPy 数组要用np.ndarray声明不能写list。5.4 触发设置不当导致采集不到波形现象程序一直在等待采集完成但示波器没有触发。原因可能是触发电平设得过高或过低或者触发源选错了通道。解决方法是先用示波器面板手动调好触发确认能稳定触发后再用query读回当前设置照着写到代码里。不要凭感觉设触发参数。5.5 打包成 exe 后找不到 VISA 库现象在开发环境跑得好好的打包成 exe 后报「找不到 visa32.dll」或者「No module named pyvisa」。原因是 PyInstaller 没有自动打包 VISA 的动态库。解决方法是在打包命令里加--add-binary把 VISA 库带上或者让目标机器先安装泰克官方的 VISA 驱动。6. 验收与答辩怎么证明这套方案真的能跑验收这套方案我一般会从四个维度来检查。第一连接稳定性连续运行 30 分钟每隔 2 秒采集一次看是否出现掉线或超时。第二数据准确性用信号发生器输出一个已知频率和幅度的正弦波看程序算出来的频率和峰峰值跟信号发生器显示的是否一致误差控制在 5% 以内算合格。第三界面响应采集过程中拖动窗口、点击按钮界面不卡死波形刷新流畅。第四异常处理拔掉网线或者关掉示波器程序应该给出明确错误提示而不是直接崩溃。答辩的时候评委通常会问三个问题为什么用 PyQt5 不用 Web多线程怎么保证数据安全如果换一台示波器代码要改多少第一个问题按第 2 章的理由答。第二个问题答「采集线程只发信号不碰 UI数据通过信号槽传递天然线程安全」。第三个问题答「只要支持 SCPI改资源地址和少量指令即可架构不变」。下面这张表是我常用的验收检查清单你可以直接拿去用。检查项合格标准测试方法连接稳定性30 分钟无掉线连续采集 900 次数据准确性频率误差 5%信号发生器对比界面响应无卡死刷新流畅采集时操作界面异常处理有明确提示不崩溃拔网线/关仪器报告完整性数据截图齐全检查输出目录最后说一个我自己的习惯。每次交付这类测试方案前我都会强制走一遍「冷启动」流程关掉所有设备重启电脑从零开始连接、采集、保存、生成报告全程不参考任何笔记。这一步能暴露 90% 的环境依赖问题比如某个库没装、某个路径写死了、某个驱动没打包。从那以后我每次验收都强制走一遍冷启动省了很多返工。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑