基于 Python 的虚拟示波器设计
1.1 研究背景
第一章 绪论
在嵌入式开发领域中,示波器是必不可少的电子测量仪器。科学技术日新月
异,示波器的结构也在不断改变。最早的模拟示波器是利用阴极射线管打出的电
子束,通过水平偏置和垂直偏置系统,打在荧光物质上显示出波形。这种示波器
实时性很好但存储能力不够,而且随着对频率的要求逐渐提高,制作成本也大幅
提高。随后出现的数字示波器解决了这些问题,数字示波器一般由信号通道、AD
转换器、数字信号处理芯片、FPGA 以及显示设备组成。FPGA 负责过滤、筛选、
存储数据,数字信号处理设备负责绘制波形以及分析工作。相对于模拟示波器,
数字示波器具有更强大的波形显示处理能力以及更丰富的测量功能,因此在上世
纪八十年代起受到广大开发者的青睐,逐渐占据了主要的市场。
目前,随着微控制器性能的逐渐升级,系统日益集成化和复杂化对测量监控
平台提出了更高的要求,传统示波器已经难以满足嵌入式开发者愈发多元和高效
的检测需求。
在这种情况下,一种以计算机软件技术为核心的全新仪器概念——虚拟示波
器应运而生。虚拟示波器是利用 PC 机或其他智能设备对采集的波形数据进行处
理,进而实现数据存储、波形分析、波形绘制、波形触发等各种功能。
相对于传统示波器,虚拟示波器有以下优点:数据采集和数据处理两部分分
离,采集设备体积小易携带;开发时间短,而且功能扩展性优秀;软件工作在 PC
或其他智能设备平台,操作方便显示清晰,用户体验好。
1.2 国内外研究现状
1986 年美国 NI 公司率先提出虚拟仪器的概念,在过去的几十年间,世界各
大虚拟仪器公司针对不同的平台和框架设计了多种虚拟仪器控件以及开发软件,
其中比较知名的有 LabView、HP-VEE 和 HPTIG 等。国外示波器主要制造公司
如美国的泰克、是德,日本的日立等也在逐渐将自身的电子测量技术与虚拟仪器
1
技术相融合。到如今,虚拟示波器软件已经不仅仅局限于 C/S 架构,也可以采用
B/S 的架构设计。
随着计算机技术的发展,软件平台操作系统的选择越来越多。Windows 操作
系统为软件开发者提供了平台操作接口和代码编写环境,同时在该平台上开发虚
拟示波器软件可以有多种开发框架以供选择。力科的 HDO6000 系列示波器以及
是德科技生产的 USB 模块化示波器 U2701A 都是在 Windows 系统上搭建的虚拟
示波器操作平台。
国内示波器行业近些年也取得了一些可喜的成果,具有代表性的厂商如鼎阳
科技、优利德、普源精电等都在示波器的发展上起到了关键作用。以电子科技大
学为代表的示波器研发机构在 Android 智能终端上开发出基于 Wi-Fi 的虚拟示波
器软件,通过示波器的网络适配器实现无线网络的数据交互。东方振动和噪声技
术研究所等针对虚拟仪器产品和 NI 产品进行了研究工作并取得了不错的成就,
重庆大学、清华大学等高等院校也逐渐引入虚拟仪器,并研发出一系列虚拟仪器
系统用于科研和教学工作。
2
第二章 系统方案设计
2.1 开发需求分析
根据一般示波器的功能,本次开发要实现的功能包括,波形的实时动态显示,
可调整窗口幅度显示比例,可调整窗口时间宽度,可设置采样深度和采样频率,
周期、频率、幅值、占空比等基本波形信息,可自由暂停与重新开始采集波形,
可保存当前窗口波形为图片文件等基本功能。本文所研究的虚拟示波器系统架构
如图 2-1 所示。
虚拟示波器
暂停
停止
位置调整
缩放调整
采样速率
频率
周期
幅值
占空比
波形显示
波形调整
波形分析
波形保存
图 2‐1 虚拟示波器系统整体架构
2.2 开发框架选择
开发框架的选择决定了软件开发的效率和质量。在 Windows 平台有以下三
种主流的图形界面开发框架选择。
1.PyQt5
Qt 框架是一个功能强大的、可跨平台的 C++图形界面开发库。Qt 在对象间
的通信上使用的是信号槽机制,相对于回调函数而言这种机制可以大幅降低组件
之间的耦合度。Qt 发展多年至今且依然保持更新,目前已支持 CSS 设计,因此
做出的 GUI 相对 MFC 更美观;同时 Qt 内部的各种架构设计更为合理,封装性
良好,模块化程度较高,上手比较简单。
3
PyQt5 是一套 Python 绑定 DigiaQT5 应用的框架,作为 Python 的一个模块,
它有 620 多个类和 6000 个函数和方法,上手速度快,开发方便。
2.MFC
MFC 以 C++类的形式封装了 Windows 操作系统的 API,提供了面向对象的
编程接口、Document/View 的设计模式和诸多开发模板,一定程度上减少开发人
员的工作量。不过 MFC 图形界面的开发基于回调函数机制,这种方式耦合性相
对较强,每个窗口类都要维护自己的 MessageMap;同时 MFC 对 API 的封装较
浅,虽然运行效率高但开发库比较难用,出现问题也比较难定位,因此总体开发
时间会比较长。
3.Labview
Labview 是美国 NI 公司所开发的 G 语言程序开发平台,开发人员通过图标
和连线就可以设计出逻辑复杂的虚拟仪表。它既能够集成大量的硬件设备,也可
以通过内置的库函数实现波形分析等高级功能。Labview 生成的程序可以运行在
Windows 和 Linux 平台上,也可以运行在多种嵌入式平台。
在这些框架中,Labview 需要使用相对应的硬件设备或者硬件驱动,同时当
项目达到一定规模后,如果模块化设计不合理,非常容易导致代码可读性变差,
后期维护困难;MFC 框架的库函数比较底层,开发效率较低;结合本人的学习
情况,选择 PyQt5 作为开发库,选择 jupyterlab 云端编译系统作为软件的开发环
境。
4
第三章 系统应用开发
3.1 窗口设计
整个窗口分为中心波形显示区域和右侧波形调整区域两部分。中心波形显示
区域位置实时更新显示当前波形数据,波形调整区域自上而下主要包括幅度调节
旋钮、时间宽度调节旋钮,实时模式和触发模式两种波形触发模式选择,幅度阈
值选择,波形数量阈值选择,启动暂停按钮。整个窗口设计如图 3-1 所示。
图 3‐1 窗口设计
3.2 通讯模块设计
串口通信是指外设和计算机间,通过数据信号线、地线、控制线等,按位进
行传输数据的一种通讯方式。这种通信方式使用的数据线少,在远距离通信中可
以节约通信成本,但其传输速度比并行传输低。串口是计算机上一种非常通用的
设备通信协议。Pyserial 模块封装了 python 对串口的访问,为多平台的使用提供
了统一的接口,通过 python 属性即可访问串口设置。
Pyserial 模块支持二进制传输,支持字节调整、停止位设置、校验位设置和
流控设置等。将下位机的传输数据按其格式分开,读取所需要的数据,程序如表
3-1 所示。
表 3-1:串口通信协议
5
class PrintLines(FramedPacket):
def __init__(self):
super(FramedPacket, self).__init__()
self.packet = bytearray()
def connection_lost(self, exc):
self.transport = None
self.in_packet = False
del self.packet[:]
super(PrintLines, self).connection_lost(exc)
sys.stdout.write('port closed')
def data_received(self, data):
handel_Data = []
for byte in serial.iterbytes(data):
if byte == self.START:
self.in_packet = True
elif byte == self.STOP:
self.in_packet = False
handel_Data.append(self.handle_packet(bytes(self.packet)))
del self.packet[:]
elif self.in_packet:
self.packet.extend(byte)
else:
self.handle_out_of_packet_data(byte)
return handel_Data
为了方便测试,设计了虚拟串口,直接在软件层级进行串口收发数据的模拟,
程序如表 3-2 所示。
表 3-2:虚拟串口设计程序
if __name__ == '__main__':
from multiprocessing import Process
def write1(ki,k2):
global tempdata
k0 = None
for i in range(100):
k0 = '(Da' + str(i+i*0.001) +'b)'
z0 = bytes(k0,encoding= 'utf-8')
ki.write(z0)
time.sleep(0.05)
myqueue = Queue()
kuia = []
ser0 = serial.serial_for_url('COM2', baudrate=115200, timeout=1)
ser1 = serial.serial_for_url('COM4', baudrate=115200, timeout=1)
6
t0 = ReadThread(ser0,PrintLines,myqueue)
t1 = ReadThread(ser1,PrintLines,myqueue)
kai = DataConduit(queue0= myqueue,mythread= t1)
t0.start()
t1.start()
kai.start()
write1(t0,t1)
kai.pause()
t0.close()
t1.close()
t1.join(5)
t0.join(5)
kai.stop()
print(kai.is_alive())
del kai.list[:]
print(kai.list)
3.3 波形模块设计
3.3.1 波形绘制
虚拟示波器软件利用 PyQt5 模块通过自绘的方式设计了一套窗口波形显示
原型,其中包含波形显示类 WaveScreen。WaveScreen 继承自 PyQt5 的 UI 设计中
的面板控件 Panel 类,然后其上放置按钮、图片、文字、输入框等控件。
WaveScreen 类负责构建示波器屏幕显示框架原型,其中定义了窗口构建模
式为布局管理模式,整体窗口的左右上下与屏幕边框的像素距离分别为 70 像素,
70 像素,40 像素和 40 像素,示波器屏幕响应跟随窗口大小变化。程序如表 3-3
所示。
表 3-3:示波器屏幕原型伪程序
class WaveScreen(qt.Panel):
'''示波器显示屏幕'''
def __init__(self, parent): # 构造函数
wx.Panel.__init__(self, parent, -1, style=wx.EXPAND)
self.SetBackgroundColour(wx.Colour(0, 0, 0))
self.parent = parent
self.ML,self.MR,self.MT,self.MB = 70,70,40,40 # 绘图边框距距离
self.Bind(wx.EVT_SIZE, self.onSize)
self.Bind(wx.EVT_PAINT, self.onPaint)
7
def onSize(self, evt): # 响应窗口大小变化
w, h = self.parent.GetSize()
self.w_scr, self.h_scr = w-176, h-118 # 示波器屏幕宽度、高度
self.rePaint()
def onPaint(self, evt): # 响应重绘事件
dc = wx.PaintDC(self)
self.plot(dc)
def plot(self, dc): # 绘制屏幕
dc.Clear() # 绘制外边框
dc.SetPen(wx.Pen(wx.Colour(224,0,0), 1))
dc.DrawLine(self.ML, self.MT, self.w_scr-self.MR, self.MT)
dc.DrawLine(self.ML, self.h_scr-self.MB, self.w_scr-self.MR, self.h_scr-self.MB)
dc.DrawLine(self.ML, self.MT, self.ML, self.h_scr-self.MB)
dc.DrawLine(self.w_scr-self.MR, self.MT, self.w_scr-self.MR, self.h_scr-self.MB)
在屏幕原型框架上,应用布局管理控件,设计虚拟示波器的界面。分别声明
继承自 qt.Frame 父类的 mainFrame 子类,继承自 qt.App 父类的 main.App 子类。
mainFrame 负责构建虚拟示波器屏幕显示界面。分别创建了界面菜单栏和状
态栏,布局方式为水平布局。创建了幅度调节和时间宽度调节两个可调节旋钮,
设计了触发模式,触发幅度和触发数量三类复选框选择项,创建了开始暂停单击
按钮,布局方式为由上至下垂直布局。程序如表 3-4 所示。
表 3-4:示波器屏幕界面设计程序
class mainFrame(qt.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, APP_NAME,
style=wx.DEFAULT_FRAME_STYLE)
self.Maximize()
self.SetBackgroundColour(wx.Colour(240, 240, 240))
self.__create_menu_bar() # 创建菜单栏
self.__create_status_bar() # 创建状态栏
self.mode_ch = [u'实时模式', u'触发模式'] # 触发模式选择项
self.level_ch = ['128', '256', '512', '1024'] # 触发幅度选择项
self.over_ch = ['1', '8', '32', '128'] # 触发数量选择项
sizer_max = wx.BoxSizer() # 最顶层的布局控件,水平布局
sizer_left = wx.BoxSizer(wx.VERTICAL) # 左侧区域布局控件,垂直布局
main.App 负责构建程序进程,调用框架类启动示波器显示进程,程序如表 3-
5 所示。
表 3-5:进程程序设计
8