Python应用-PyQt5串口助手

PyQt5环境安装

PC环境介绍

1
2
3
Window 10
Python 3.7
PyCharm

PyQt5环境安装

  1. 安装Sip

    1
    pip3 install Sip
  2. 安装PyQt5

    1
    pip3 install PyQt5
  3. 安装PyQt5 Tool

    1
    pip install PyQt5-tools -i http://pypi.douban.com/simple --trusted-host=pypi.douban.com
  4. 安装pyserial

    1
    pip install pyserial
  5. 添加环境变量

    1
    2
    QT_QPA_PLATFORM_PLUGIN_PATH
    C:\Python\Python37\Lib\site-packages\PyQt5\Qt\plugins

PyQt5设计流程

http://03.xiaolinjun.top/0c6-1.png

  1. Requirement

    1
    功能需求。在设计之前先想好要做怎样的一个东西,它包含哪些功能,把功能做一个列表
  2. UI Design

    1
    UI设计分为两部分:Component(组件)和Layout(布局)
    1. Component

      1
      组件。根据功能合理使用相应的组件
    2. Layout

      1
      布局。合理布局可以使得UI更加美观,操作更直观
  3. UI Test

    1
    UI测试。在使用UI组件和布局时,应该用实时预览功能测试组件使用是否符合预期,以便修改
  4. Logical Code

    1
    逻辑代码。编程过程使用逻辑与UI代码分离,逻辑代码实现预期的功能
  5. Optimize

    1
    优化。在测试代码过程中会发现很多小细节被遗漏,需要一步一步进行优化

PyQt5实战-Serial Tool V1.0

MCU应用工程师/嵌入式开发工程师在实际的工作中需要经常对设备进行调试,一般情况是通过上位机对下位机进行调试。本文使用PyQt5快速开发一个特定功能的串口上位机Serial Tool V1.0,环境搭建可以参考第一部分的介绍

功能规划

Serial Tool V1.0上位机根据需求实现一下功能:

  1. 实现与下位机串口通信(参数不可变)
  2. 可配置特定的输入通道、输出通道和和三组数据(0~1023)输出给下位机
  3. 能接收下位机发送的信息,并格式化显示

UI 设计

功能明确后,开始进行UI设计。UI设计使用designer.exe,位于C:\Python37\Lib\site-packages\pyqt5_tools目录下,打开时建议以管理员身份运行

注意:这里使用的是作者的Python的安装目录

组件

UI designer中的组件非常丰富,对于Serial Tool V1.0只是用了其中很少的组件。组件提供了很多的事件接口,如按钮的按下和松开都可以出发一个事件,用户可以灵活地设计功能。UI designer中还支持信号槽,即通过某个组件的事件来触发一个行为,如按下按钮改变标签的内容。下面介绍Serial Tool V1.0中使用的相关组件

Widget。小窗口,一般简单的GUI可以只使用Widget就够了。点击Nwe–>Widget创建,然后在右边的Property Editor窗口修改相关属性(窗口大小,背景颜色…等等)

http://03.xiaolinjun.top/0c6-2.gif

QLabel。标签,使用率非常高的组件,通常配合其他组件用作说明、标识等等用途。在左边的组件栏选择QLabel组件拖放置到窗口中,然后双击修改内容,或者在在右边的Property Editor窗口修改内容及其相关属性(字体,字体大小,颜色,居中…等等),然后Ctrl+R可以预览窗口

http://03.xiaolinjun.top/0c6-3.gif

QComboBox。下拉列表框,通常用于有限选择的选项列表。在左边组件栏选择QComboBox组件拖放到窗口中,然后双击添加下拉列表内容,同理也可以在右边的Property Editor窗口修改内容及其相关属性(字体,字体大小,颜色,居中…等等),然后Ctrl+R预览窗口

http://03.xiaolinjun.top/0c6-4.gif

QPushButton。按钮,用作与开关量,在UI设计中最常见的组件之一。在左边组件栏选择QPushButton组件拖放到窗口中,然后双击修改按键标题,同理也可以在右边的Property Editor窗口修改内容及其相关属性(字体,字体大小,颜色,居中…等等),然后Ctrl+R预览窗口

http://03.xiaolinjun.top/0c6-5.gif

QTextEdit。文本编辑框,用于输入文本或者接收文本显示使用。在左边组件栏选择QTextEdit组件拖放到窗口中,在右边的Property Editor窗口修改内容及其相关属性(字体,字体大小,颜色,居中…等等),然后Ctrl+R预览窗口

http://03.xiaolinjun.top/0c6-6.gif

Layout

组件选好后就开始布局,布局有以下四种

Vertical Layout。垂直布局,让组件垂直排列。选择要垂直排列的组件,然后点击菜单栏的垂直布局的图标进行垂直布局

http://03.xiaolinjun.top/0c6-7.gif

Horizontal Layout。水平布局,让组件水平排列。选择要水平排列的组件,然后点击菜单栏的水平布局的图标进行水平布局

http://03.xiaolinjun.top/0c6-8.gif

Grid Layout。网格布局,让组件像网格一样有序排列。择要网格排列的组件,然后点击菜单栏的网格布局的图标进行网格布局

http://03.xiaolinjun.top/0c6-9.gif

Form Layout。表格布局,让组件像表格一样排列。择要表格排列的组件,然后点击菜单栏的表格布局的图标进行表格布局

http://03.xiaolinjun.top/0c6-10.gif

布局决定了应用的美观,在一个窗口中通常由很多的组件组成,组件之间可以组成一个一个功能模块,因此布局在其中的应用也是多种方式组合的,下图为Serial Tool V1.0应用的组件和布局窗口

http://03.xiaolinjun.top/0c6-11.png

PyQt5代码实现

UI设计好后,需要把.ui文件转为.py文件然后再应用。首先打开CMD命令行,然后切换到.ui文件所在的目录下,输入命令

1
pyuic5 -x -o my_ui.py my_ui.ui

注:上面的命令是把my_ui.ui文件转换为my_ui.py文件,用户根据需要修改文件名

转换完成后使用PyCharm打开文件(或者使用python的IDLE打开),运行文件查看效果

http://03.xiaolinjun.top/0c6-12.gif

注:pycharm打开单独的文件可能需要配置python解释器才能运行,具体操作可以百度一下

到目前位置,根据预想的功能实现了Serial Tool V1.0的UI设计,并转换为py文件运行参看效果,从UI中可以知道后面需要用代码实现如下功能:

  1. 扫描介入PC端的串口设备并显示在COM下拉列表框中
  2. 当有设备接入时,按UI中的参数点击Open按钮打开串口,如果没有发现设备点击了Open需要提示错误信息
  3. 打开串口后,修改左下相关参数点击Send按钮能把参数按一定的格式发送出去
  4. 打开串口后,如果接收到信息,需要在文本编辑框中按一定的格式显示出来

使用PyCharm新建Serial Tool V1.0工程,然后把ui文件复制到工程目录下,再新建一个用于存放逻辑代码的py文件(my_main.py),下面内容开始讲述相关的逻辑代码实现

导入相关库

程序中会使用很多python库,如果对python比较熟悉都是使用相关函数再导入与之相关的库,下面列出工程中需要导入的python库

1
2
3
4
5
6
7
8
9
10
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMessageBox

from my_ui import Ui_Form
from serial.tools.list_ports import comports

import os
import sys
import serial

Serial Tool V1.0运行流程

程序运行流程大致为:启动,初始化界面,打开串口,发送数据。其中还涉及几部分,如打开串口后接收数据、按下Clear按钮清除接收内容、刷新串口、没有打开串口前按Send按钮需要提示错误信息、数据的格式化发送和接收等等

Serial Tool V1.0初始化

根据程序实现的功能和运行流程,首先定义一个SerialTool类,然后在其中进行初始化。初始化部分首先继承ui文件的Ui_Form父类,然后实例化一个串口ser,实例化两个定时器:timer_refresh用于在打开串口前刷新串口列表;timer_receive用于在打开串口后接收数据并显示在Receive Edit框中。最后初始化Open按钮按下触发事件和Send按钮按下出发事件。代码如下:

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
class SerialTool(QtWidgets.QWidget, Ui_Form):
def __init__(self, parent=None):
super(SerialTool, self).__init__(parent)

"""Init Window"""
self.setupUi(self)

"""Instantiation serial"""
self.ser = serial.Serial()

"""Refresh timer configuration"""
self.timer_refresh = QTimer(self)
self.timer_refresh.setInterval(100)
self.timer_refresh.start()
self.timer_refresh.timeout.connect(self.refresh_com)

"""Receive timer configuration"""
self.timer_receive = QTimer(self)
self.timer_receive.setInterval(20)
self.timer_receive.stop()
self.timer_receive.timeout.connect(self.receive_data)

"""Handle when the Open button is clicked"""
self.OpenPushButton.clicked.connect(self.open_and_close)

"""Handle when the Send button is clicked"""
self.SendPushButton.clicked.connect(self.send_data)

Serial Tool V1.0相关方法实现

用于刷新串口的方法refresh_com。该方法用于在打开窗口前,实现每隔100ms获取COM信息并把端口号显示在COM下拉列表框中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"""Refresh the serial port when it is not open"""
def refresh_com(self):
# Access to a COM port
port_list = serial.tools.list_ports.comports()

# No port found, Clear SerialComboBox
if len(port_list) == 0:
self.SerialComComboBox.clear()
else:
# find COM, display COM
for port, desc, hwid in comports():
if self.SerialComComboBox.findText(port) != -1:
pass
else:
self.SerialComComboBox.addItem(port)

用于打开或关闭串口的Open按键方法open_and_close。该方法用于打开或关闭串口,当串口处于关闭的状态下点击按钮打开串口(按指定参数打开,关闭串口刷新定时器,打开接收数据定时器),串口处于打开的状态下点击按钮关闭串口(打开串口刷新定时器,关闭接收数据定时器),如果没有找到可用的串口就会提示错误信息

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
"""Open or close serial dispose"""
def open_and_close(self):
# In the closed case, open the initialization serial port and open it
if not self.ser.is_open:
self.ser.port = self.SerialComComboBox.currentText()
if not self.ser.port:
QMessageBox.critical(self, 'Message', 'No COM are available!')
else:
self.ser.baudrate = 115200
self.ser.stopbits = 1
self.ser.bytesize = 8
self.ser.parity = serial.PARITY_NONE

# Close refresh timer ,open serial, open receive timer
self.timer_refresh.stop()
self.timer_receive.start()
self.ser.open()
self.SerialComComboBox.setEnabled(False)
self.OpenPushButton.setText('Close')
# In the Opened case, close serial port, receive timer stop, refresh timer enable
else:
self.ser.close()
self.timer_receive.stop()
self.timer_refresh.start()
self.SerialComComboBox.setEnabled(True)
self.OpenPushButton.setText('Open')

发送数据方法send_data。该方法用于在打开串口后发送相关的数据,数据部分来源于输入列表框和微调框部分,并把数据整合一定的格式输出

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""Send data dispose"""
def send_data(self):
global send_nums
if self.ser.is_open:
# Get input channel data
if self.InputChannelComboBox.currentText() == 'CH_1':
input_channel = '1'
elif self.InputChannelComboBox.currentText() == 'CH_2':
input_channel = '2'
elif self.InputChannelComboBox.currentText() == 'CH_3':
input_channel = '3'
else:
pass

# Get output channel data
if self.OutputChannelComboBox.currentText() == 'CH_1':
output_channel = '1'
elif self.OutputChannelComboBox.currentText() == 'CH_2':
output_channel = '2'
elif self.OutputChannelComboBox.currentText() == 'CH_3':
output_channel = '3'
else:
pass

# Formatted data,start by !, finish by #, data is separated by -
channel_data = '!' + input_channel + '-' + output_channel + '-'
channel_data = channel_data.encode('gbk')

control_data = str(self.ConstrastSpinBox.value()) + '-'
control_data = control_data.encode('gbk')

brightness_data = str(self.BrightnessSpinBox.value()) + '-'
brightness_data = brightness_data.encode('gbk')

vctrl_data = str(self.VCtrlSpinBox.value()) + '#'
vctrl_data = vctrl_data.encode('gbk')

# Send data
try:
send_data = channel_data + control_data + brightness_data + vctrl_data
except Exception as e:
print(e)
try:
send_nums = self.ser.write(send_data)
print(send_nums)
except Exception as e:
print(e)
self.ser.close()
self.timer_receive.stop()
self.timer_refresh.start()
self.SerialComComboBox.setEnabled(True)
self.OpenPushButton.setText('Open')
else:
QMessageBox.critical(self, 'Message', 'Please open the serial port')

接收数据方法receive_data。该方法用于接收串口数据,在串口打开的情况下每20ms读取一下串口的缓存,如果里面有数据就把它按格式(GBK格式,输出数据后空行)打印出来

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
28
29
30
31
"""Receive data dispose"""
def receive_data(self):
global nums_byte
# When the serial port is open
if self.ser.is_open:
try:
# Gets thr number of bytes in the Output Buffer
nums_byte = self.ser.inWaiting()
# exception handling
except Exception as e:
print(e)
self.timer_refresh.start()
self.timer_receive.stop()
self.ser.close()
self.ser = None

# Receive the data,display in ReceiveEdit
if nums_byte > 0:
data = self.ser.read(nums_byte)
try:
# GBK display
data = data.decode('gbk', 'ignore')
except Exception as e:
print(e)
text_cursor = self.ReceiveEdit.textCursor()
self.ReceiveEdit.moveCursor(text_cursor.End)
self.ReceiveEdit.insertPlainText(data + '\n\r')
else:
pass
else:
pass

Clear按钮按下方法on_ClearPushButton_clicked。该方法是UI设计的按钮自带的方法,这里重写这个方法,所以在它上面要加上`@QtCore.pyqtSlot()`,用于清除接收文本编辑框

1
2
3
4
"""Clear PushButton Event dispose"""
@QtCore.pyqtSlot()
def on_ClearPushButton_clicked(self):
self.ReceiveEdit.clear()

最后主函数直接实例化类然后调用显示

1
2
3
4
5
if __name__ == "__main__":
app = QApplication(sys.argv)
win = SerialTool()
win.show()
sys.exit(app.exec_())

运行程序,实现效果(接一个USB转TTL模块到电脑,然后短接TXD和RXD):

http://03.xiaolinjun.top/0c6-13.gif

PyQt5学习总结

通过使用PyQt5来实现Serial Tool V1.0后,得出如下几点总结

  1. 使用UI布局要跟自身所需功能结合选用组件,多查看组件的属性
  2. 编写代码前要有清晰的框架思维,最好先总结功能画出程序执行流程图
  3. 编写代码时多使用调试、try、print等方法查找并解决错误

UI Designer使用技巧

UI Designer是一个很强大的UI工具,可以快速实现UI设计,但是众多的组件也会让用户眼花缭乱,因此对于怎样用好UI Designer给出以下几点建议

  1. 每个组件都试着使用一下,查看它的相关属性
  2. 使用Ctrl+R查看组件在实际运行中的效果
  3. 查看组件在使用过程会有哪些事件产生

注:有些不理解的部分可以百度一下

代码的调试

PyCharm是一个很强大的代码编辑器,对于写python程序时很方便的一个IDE工具。PyCharm提供Debug功能,在每实现一个功能时最好Debug运行,如果出现错误的话在下面的信息窗口会给出错误信息

其次在编写代码时,对于不确定会不会出错的代码使用try语句,然后在except打印错误的信息,这样有助于开始发现错误并解决

最后,很多时候print函数时很有用的,它方便我们查看数据类型或者数据长度、或数据本身。比如接收到数据后,先print查看数据是怎样的,然后再对其进行格式化,在print重复到满意的效果,就可以删除print输出到其他设备上

其他

  1. 使用PyQt5开发的好处就是快,坏处就是工程文件会比较大(使用的库较多)
  2. 可以使用PyInstaller打包程序为exe
  3. PyQt官方网站:https://riverbankcomputing.com
  4. PySerial官方网站:https://pypi.org/project/pyserial/
  5. 多百度、多谷歌、多参考他人代码

本文标题:Python应用-PyQt5串口助手

文章作者:LGG001

发布时间:2019年07月01日 - 09:07

最后更新:2019年07月01日 - 19:07

原始链接:http://yoursite.com/2019/07/01/Python应用-PyQt5串口助手/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------
Thank You For Your Approval !