PyCharm+PyQt5实现串口数据采样

hwwjian

关注

阅读 36

2022-02-10

1、软件安装

需要用到的软件包括:

Python:本人使用的版本是3.8.2

PyCharm:本人使用的版本是PyCharm 2021.2.3(Community Edition)

PyQt5相关插件:PyQt5、PyQt5Designer、PyQt5-tools;

绘制曲线相关插件:pyqtgraph;

串口工具相关插件:pyqt5-Uart-Tool;

打包工具插件:pyinstaller

安装方法可参考:Python:命令安装pyQt5相关插件

配置PyQt5工具

菜单栏中打开:File——>Settings,在对话框中选择:Tools——>External Tools

如下图添加Qt设计师工具:

 Program项:填写Qt设计师安装路径

$ProjectFileDir$:表示文件所在项目路径

如下图添加.ui文件转.py文件的工具:(下图中命名为PyUIC)

Program项:填写python.exe安装路径

Arguments项:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py

Working directory项:$FileDir$

 $FileName$:表示文件名

$FileNameWithoutExtension$:表示没有扩展名的文件名

$FileDir$:表示文件所在路径

2、制作UI界面

打开Tools——>External Tools——>Qt Designer

其中这里的Qt Designer就是上一节配置的PyQt5工具

 界面制作省略……

3、将UI文件转换为.py

选中需要转换的.ui文件(下图中选中的是mainUI.ui)

打开Tools——>External Tools——>PyUIC

其中这里的PyUIC就是第1节配置的PyQt5工具

如果转换成功将会有如下图提示

 

4、代码实现

基本界面1:串口配置对话框

其中windowModality项选择ApplicationModal,目的是将该对话框设置为模式对话框,即该对话框不关闭就不能操作打开该对话框的主页面;

基本界面2:主页面

通过菜单栏:设置——> 串口设置,打开串口配置对话框;

采样数据界面显示接收到的数据,采样曲线界面显示采样曲线。

 

4.1整体框架

其中只有serial_page.py、main_page.py、myUI.py三个文件需要写代码:

serial_page.py文件是serialUI.py界面的逻辑部分代码,该文件定义了类:

class SerialPage(QtWidgets.QDialog, Ui_DialogSerial)

main_page.py文件是mainPage.py界面的逻辑部分代码,该文件中定义了类:

class MainPage(QtWidgets.QMainWindow, Ui_MainWindow)

myUI.py文件是main函数文件:

import sys
from PyQt5 import QtWidgets
from mainUI.main_page import MainPage

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main_ui = MainPage()
    main_ui.show()

    sys.exit(app.exec_())

4.2串口配置逻辑

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMessageBox

import serial
import serial.tools.list_ports
from .serialUI import Ui_DialogSerial


class SerialPage(QtWidgets.QDialog, Ui_DialogSerial):
    def __init__(self):
        super(SerialPage, self).__init__()
        self.setupUi(self)
        self.this_serial = serial.Serial()

        self.port_check()
        self.open_button.clicked.connect(self.port_open)
        self.close_button.clicked.connect(self.port_close)

    def port_check(self):
        port_list = list(serial.tools.list_ports.comports())
        self.box_serial_port.clear()

        for port in port_list:
            self.box_serial_port.addItem(port[0])

    def port_open(self):
        self.this_serial.port = self.box_serial_port.currentText()
        self.this_serial.baudrate = int(self.box_baudrate.currentText())
        self.this_serial.bytesize = 8
        self.this_serial.stopbits = 1
        self.this_serial.parity = 'N'

        try:
            self.this_serial.open()
        except:
            QMessageBox.critical(self, "Port Error", "此串口不能被打开!")
            return None

        if self.this_serial.isOpen():
            self.open_button.setEnabled(False)
            self.close_button.setEnabled(True)

        self.close()

    def port_close(self):
        try:
            self.this_serial.close()
        except:
            pass
        self.open_button.setEnabled(True)
        self.close_button.setEnabled(False)

4.3主页面逻辑

import array
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QTimer, QDateTime
import pyqtgraph as pg
from .mainUI import Ui_MainWindow
from .serialUI.serial_page import SerialPage


class MainPage(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainPage, self).__init__()
        self.setupUi(self)
        self.serial_page = SerialPage()

        self.timer_tx = QTimer()
        self.timer_rx = QTimer()
        self.timer_sys = QTimer()
        self.data_num_rx = 0
        self.data_num_tx = 0
        self.start_flag = 0
        self.pre_str = ""

        self.init()
        self.init_curve()
        self.tmp_time = 0
        self.data = array.array("f")
        self.time = array.array("L")

    def init_curve(self):
        win = pg.PlotWidget()
        self.gridLayoutCurve.addWidget(win)
        win.showGrid(x=True, y=True)
        win.setLabel(axis="left", text="重量", units="g")
        win.setLabel(axis="bottom", text="时间", units="s")

        self.curve = win.plot()
        self.curve.setPen((255, 0, 0))

    def init(self):
        self.actionSerialSet.triggered.connect(self.serial_config)
        self.clear_button.clicked.connect(self.clear_rx)
        self.start_button.clicked.connect(self.start_sample)
        self.timer_tx.timeout.connect(self.data_send)
        self.timer_rx.timeout.connect(self.data_receive)
        self.timer_sys.timeout.connect(self.show_time)
        self.timer_sys.start(200)

    def serial_config(self):
        self.serial_page.show()

    def clear_rx(self):
        self.receive_text.setText("")

    def show_time(self):
        datetime = QtCore.QDateTime.currentDateTime()
        text = datetime.toString("yyyy-MM-dd hh:mm:ss")
        self.statusbar.showMessage(text)

    def start_sample(self):
        if self.serial_page.this_serial.isOpen():
            if self.start_flag == 0:
                self.start_flag = 1
                self.timer_rx.start(30)
                self.timer_tx.start(int(self.lineEdit_period.text()))
                self.lineEdit_period.setEnabled(False)
                self.radioButtonDaoJin.setEnabled(False)
                self.radioButtonMeiTeLe.setEnabled(False)
                self.start_button.setText("停止采样")
            else:
                self.start_flag = 0
                self.timer_rx.stop()
                self.timer_tx.stop()
                self.lineEdit_period.setEnabled(True)
                self.radioButtonDaoJin.setEnabled(True)
                self.radioButtonMeiTeLe.setEnabled(True)
                self.start_button.setText("启动采样")
        else:
            QMessageBox.critical(self, "Error!!!", "请先打开串口:设置——>串口设置")
            return None

    def data_send(self):
        if self.serial_page.this_serial.isOpen():
            # input_s = 'S S    1.04664 g\r\n'
            input_s = 'SI\r\n'
            if self.radioButtonDaoJin.isChecked():
                input_s = 'D05\n'
            input_s = input_s.encode('utf-8')

            num = self.serial_page.this_serial.write(input_s)
            self.data_num_tx += num
            self.lineEdit_tx_num.setText(str(self.data_num_tx))
        else:
            pass

    def data_receive(self):
        try:
            num = self.serial_page.this_serial.inWaiting()
        except:
            self.serial_page.port_close()
            return None
        if num > 0:
            data = self.serial_page.this_serial.read(num)
            self.show_balance_text(data)
            self.show_balance_curve(data)
        else:
            pass

    def show_balance_text(self, str_data):
            num = len(str_data)

            # 串口接收到的字符串为b'123',要转化成unicode字符串才能输出到窗口中去
            self.receive_text.insertPlainText(str_data.decode('iso-8859-1'))

            # 统计接收字符的数量
            self.data_num_rx += num
            self.lineEdit_rx_num.setText(str(self.data_num_rx))

            # 获取到text光标
            text_cursor = self.receive_text.textCursor()
            # 滚动到底部
            text_cursor.movePosition(text_cursor.End)
            # 设置光标到text中去
            self.receive_text.setTextCursor(text_cursor)

    def show_balance_curve(self, str_data):
        self.pre_str = self.pre_str+str_data.decode()
        print(self.pre_str)

        while len(self.pre_str) >= 16:
            if self.pre_str.startswith('S ') and self.pre_str.find('.') == 8 and self.pre_str.find('g') == 15:
                break
            else:
                self.pre_str = self.pre_str.replace(self.pre_str[0], '', 1)
        print(self.pre_str)
        if self.pre_str.startswith('S ') and self.pre_str.find('.') == 8 and self.pre_str.find('g') == 15:
            tmp_str = self.pre_str.split()
            print(tmp_str)
            tmp_data = float(tmp_str[2])
            print(tmp_data)

            self.tmp_time += 1
            self.pre_str = self.pre_str.replace(self.pre_str[0:14], '', 1)
            self.data.append(tmp_data)
            self.time.append(self.tmp_time)
            self.curve.setData(self.data)
        else:
            pass

5、打包成.exe文件

在工程路径中打开cmd窗口(就是main函数所在文件的文件夹下打开cmd窗口):

输入指令:pyinstaller -F -w myUI.py

指令执行完毕后生成的.exe文件在该路径下的dist文件夹里面:

6、遇到的问题

问题1、串口接收数据不完整

现象:天平上传字符串格式为:'S S 1.04664 g\r\n'

但实际串口接收时发现很多时候一次接收不完整,需要分两次或三次接收才能接收完整;

解决办法:使用缓冲区,将每次接收到数据先放到缓冲区,在解析数据是否完整;

问题2、接收到的数据是char类型不是string类型

解决办法:将每次接收到的串口字符串先做解码,再放到缓冲区中:

self.pre_str = self.pre_str+str_data.decode()

精彩评论(0)

0 0 举报