一、串口调试助手

1、环境搭建

工具列表:

工具 功能 下载地址
Python 3.7.4 Python官方包(解释器) https://www.python.org/
pySerial 3.4 Serial Port访问的Python封装库 https://pypi.org/project/pyserial/ https://github.com/pyserial/pyserial https://pythonhosted.org/pyserial/
wxPython 4.0.4 跨平台开源GUI库 wxWidgets 的Python封装库 https://www.wxpython.org/ https://pypi.org/project/wxPython/
wxFormBuilder 3.8.0 wxPython GUI界面构建工具 https://github.com/wxFormBuilder/wxFormBuilder
PyCharm Community 2019.3.1 一款流行的Python集成开发环境 http://www.jetbrains.com/pycharm/
PyInstaller 3.5 Python应用程序打包工具 http://www.pyinstaller.org/ https://github.com/pyinstaller/pyinstaller
vspd 9 虚拟串口驱动,可以在PC上虚拟出Serial Port https://www.eltima.com/products/vspdxp/
sscom 5.13.1 大虾和丁丁联合推出的一款很流行的串口调试工具 http://www.daxia.com/sscom/

pip:

1
2
3
4
5
6
#升级pip
python -m pip install --upgrade pip
#查看需要升级的包
pip list --outdated
#pip升级包 建议通过Anaconda Navigator升级
pip install --upgrade 要升级的包名

安装包:

1
2
3
4
5
6
#串口
pip install pyserial
#跨平台GUI库wxWidgets的python版
pip install wxPython
#打包发布工具
pip install pyinstaller

单纯使用wxPython设计GUI界面时仅能是手工写代码布局,我们需要一款可视化的界面设计工具,本文选择的是wxFormBuilder,从其github官网下载安装包并安装。安装完成打开软件便可在Designer里尽情创作界面,创作完成后点击"Python"便可看到Python GUI源代码,这个GUI源代码后续直接复制到工程里使用。

2、界面设计

在真正进入代码设计界面前,首先应该在纸上画一个界面草图,确定应该有哪些元素构成,这些元素分别位于界面上什么位置。下面是界面简图,界面主要包括三大部分:接收区、配置区、发送区,接收区用于显示从串口接收到的数据;配置区用于配置串口参数;发送区用于编辑要从串口发送出去的数据。

界面简图

下一步需要将设计简图解析成如下的wxPython组件图,将简图里的元素转换成wxPython里的真实组件。这一步需要配合查阅wxPython相关手册,了解wxPython有哪些组件。

有一个地方需要特别提醒的是,wxWrapSizer里的控件是从左到右自上而下排列的,有的时候为了排版,会故意插入一些无效的wxStaticText来占位,下图中便用了4个占位的wxStaticText(浅色框表示)。

wxPython组件图

下面便可以在wxFormBuilder里照样子创作出真正的界面了。

2.1wxFormBuilder入门

wxWidgets的各种UI控件功能均是通过class来实现的,这个链接 http://docs.wxwidgets.org/3.0/page_class_cat.html 列出了wxWidgets里的所有class,wxPython并没有实现wxWidgets里全部class,但基本实现了大部分常用class,这个链接 https://docs.wxpython.org/wx.1moduleindex.html 列出了wxPython里所有的class。

2.1.1基础布局

一个GUI的基础框架包括:Frame(外围轮廓)、Sizer(内部控件区)、menubar(顶部菜单栏)、statusBar(底部状态栏)。

第一步是添加一个Frame,这是GUI的轮廓基础,其size(default为500; 300)决定了GUI整体界面的大小。

第二步是在Frame下添加一个Sizer,后续所有控件均是放在Sizer里的。关于Sizer部分需要特别说明一下,wxPython提供的Sizer类型有如下七种:wxBoxSizerwxWrapSizerwxStaticBoxSizerwxGridSizerwxFlexGridSizerwxGridBagSizerwxStdDialogButtonSizer,Sizer的样式决定了后续控件的整体相对位置关系,选定了Sizer即选定了GUI界面样式。关于这七种Sizer的具体样式请见 https://docs.wxpython.org/sizers_overview.html#sizers-overview。如果你觉得单个Sizer里的控件布局太单调,你可以嵌套使用Sizer,这是实现GUI界面控件布局多样化的关键。

  • wxBoxSizer:自上而下布局,因此里边的控件在Sizer是自上而下排列的,各个控件的位置后续在属性里还可以微调,但改变不了自上而下的格局。(可以在属性栏中调整是垂直或水平布置其子级)
  • wxWrapSizer:暂时猜测是自左向右,自上向下的布局,会随着界面的拉伸而改变
  • wxStaticBoxSizer:与wxBoxSizer相似,但由一个静态框包围。
  • wxGridSizer:一个二维大小调整器。所有子项都具有相同的大小,为最大子项的大小(可以通过属性调整行列大小)。
  • wxFlexGridSizer:从wxGridSizer派生的另一个二维sizer。每列的宽度和每行的高度是根据相应行列最大子元素的最低要求分别计算的。此外,如果为sizer分配了与其请求的大小不同的大小,则可以将列和行属性设为可拉伸的。
  • wxGridBagSizer:与其他类不同,它可以直接将元素放在sizer中的给定位置。
  • wxStdDialogButtonSizer:

第三步是在Frame顶部添加一个menubar

第四步是在Frame底部添加一个statusBar

2.1.2控件

基础布局搞定之后,接下来便是在Sizer里添加控件,wxPython支持的控件非常丰富,其中比较常用的是如下几个:button(按钮)、staticText(静态显示文本框)、textCtrl(输入输出文本框)、Choice(复选框)、checkBox(选中框)、slider(滑动条)。前面痞子衡选择的Sizer是wxBoxSizer,即自上而下布局,因此这些控件在Sizer是自上而下排列的,各个控件的位置后续在属性里还可以微调,但改变不了自上而下的格局。

2.1.3属性
  • name:在后续python代码的对象名,一般需要按其功能修改,修改后使得代码阅读/修改起来更直观
  • label:在GUI里显示的标签名,也需要按其功能修改,方便用户使用软件
  • size:控件尺寸,这个尺寸最大不应超过Sizer尺寸
  • flag:调整对齐方式从而调整控件在Sizer里的位置

另外有一个属性不得不说,即控件位置pos,在wxFormBuilder里设置这个属性并不生效,猜想可能跟Sizer样式有关,因为Sizer决定了控件间相对位置关系,因此控件的pos不能随意设置。

2.1.4生成代码

当GUI界面布局全部完成之后,需选择File->Generate Code或F8生成python代码,需要复制所有的python代码并保存在单独的文件里(其中Frame class名为com_win),并存放于\pzh-py-com\src目录下,此时需要另外新建一个名为main的主函数文件,并放在\pzh-py-com\src目录下。其中main文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import wx
import sys, os
import win

class mainWin(win.com_win):

def clearRecvDisplay( self, event ):
event.Skip()

def openClosePort( self, event ):
event.Skip()

def clearSendDisplay( self, event ):
event.Skip()

def sendData( self, event ):
self.m_textCtrl_recv.Clear()
self.m_textCtrl_recv.SetValue('hello world')

if __name__ == '__main__':
app = wx.App()

main_win = mainWin(None)
main_win.SetTitle(u"PyCOM v0.1.0")
main_win.Show()

app.MainLoop()

main.py里并没有实现具体功能,只有一个hello world打印的效果,此处只是演示界面已经创建成功,界面运行效果如下:

3、串口功能实现

pySerial是一套基于python实现serial port访问的库,使用非常简单,可在其官网浏览一遍其提供的API: https://pythonhosted.org/pyserial/pyserial_api.html,下面整理了比较常用的API如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Serial(SerialBase):
# 初始化串口参数
def __init__(self, *args, **kwargs):
# 打开串口
def open(self):
# 关闭串口
def close(self):
# 获取串口打开状态
def isOpen(self):
# 设置input_buffer/output_buffer大小
def set_buffer_size(self, rx_size=4096, tx_size=None):

# 获取input_buffer(接收缓冲区)里的byte数据个数
def inWaiting(self):
# 从串口读取size个byte数据
def read(self, size=1):
# 清空input_buffer
def reset_input_buffer(self):

# 向串口写入data里所有数据
def write(self, data):
# 等待直到output_buffer里的数据全部发送出去
def flush(self):
# 清空output_buffer
def reset_output_buffer(self):

pySerial常用参数整理如下,需要特别强调的是任何运行时刻对如下参数进行修改,均是直接应用生效的,不需要重新调用open()和close()去激活。

参数名 功能解释 备注/可设值
port 设备名 /dev/ttyUSB0 on GNU/Linux or COM3 on Windows
baudrate (int) 波特率 /
bytesize 数据位bit个数 FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
stopbits 停止位 STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO
parity 奇偶校验位 PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, PARITY_SPACE
timeout (float) 接收超时 None:blocking mode 0: non-blocking mode x: 超时时间x秒
write_timeout (float) 发送超时 同timeout

4、打包

在使用PyInstaller进行打包工作之前,首先需要确定你的Python应用程序所调用的所有第三方库是不是在PyInstaller支持列表里,这个主页显示了PyInstaller支持的所有第三方库: https://github.com/pyinstaller/pyinstaller/wiki/Supported-Packages

在使用PyInstaller打包前必须明白一点的是,PyInstaller仅能将.py格式的源文件以及其所调用的相关Python第三方源文件库打包进最终的.exe文件,如果你的应用程序会用到图片等多媒体文件,这些多媒体文件并不能被打包,后续exe在使用时,这些多媒体文件必须一同在场,并且还要保证与打包/开发时的相对路径是一致的。

使用如下命令格式打包:

1
pystaller -F -w [src1.py] [src2.py]... -i  [pic.ico]
  • -F的意思是将应用程序打包成单个可执行文件(与其对立的命令是-D,打包成多文件放在一个文件夹)
  • -w表明要打包成窗口型(与其对立的命令是-c,控制台型)
  • [src1.py] [src2.py] [...]为你自己创建的应用程序源文件(src1.py必须是含__main__的主函数文件)
  • -i指定图标文件。

打包命令成功执行之后,会生成如下文件:

  • build\:存放的是PyInstaller在打包过程中生成的调试信息文件
  • dist\main.exe:最终的可执行文件
  • main.spec:PyInstaller自动生成的命令解释文件(在命令行里输入的命令首先被翻译放到.spec文件里,然后PyInstaller主要是根据.spec文件打包)

关于不会一起打包图片的情况,可以考虑通过这篇博客 pyinstaller打包——图片资源无法显示问题 实现,思路大概原理是事先将图片编码存到.py源文件里,这样在打包时便可将这个图片数据.py源文件直接打包进exe文件,后续在运行时首先将图片数据解码出来并在本地保存为临时图片,等图片加载完成之后可以删除临时图片文件。