PyQt5串口测试工具

笔者经常会遇到使用上位机进行相关测试的场景,但现成的上位机并不能完全满足自己的需求,或是上位机缺乏使用说明。所以,自己写?

环境说明

pycharm 2023.2.25

python 3.10

anaconda

环境配置

bash 复制代码
conda create -n envsram     ##新建虚拟环境,不用anaconda也行自己使用python新建都行
conda env list    ##查看虚拟环境及路径,方便修改python解释器路径
conda activate envsram    
conda install pyqt     ##安装pyqt5及依赖

designer    ##键入,以打开pyqt图像设计界面,设计完成后为.ui文件

pyuic5 -o ccm_Test.py .\Uartsendframe.ui    ##转换.ui文件为测试文件

pip install pyserial    ##我conda install失败了,直接用pip安装,为了上位机实现串口相关操作

代码测试

ui界面代码

python 复制代码
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file '.\Uartsendframe.ui'
#
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets



class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(432, 476)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox.setObjectName("groupBox")
        self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
        self.gridLayout.setObjectName("gridLayout")
        self.pushButton = QtWidgets.QPushButton(self.groupBox)
        self.pushButton.setObjectName("pushButton")
        self.gridLayout.addWidget(self.pushButton, 0, 3, 1, 1)
        self.pushButton2 = QtWidgets.QPushButton(self.groupBox)
        self.pushButton2.setObjectName("pushButton2")
        self.gridLayout.addWidget(self.pushButton2, 0, 4, 1, 1)
        self.pushButton3 = QtWidgets.QPushButton(self.groupBox)
        self.pushButton3.setObjectName("pushButton3")
        self.gridLayout.addWidget(self.pushButton3, 0, 5, 1, 1)
        self.pushButton4 = QtWidgets.QPushButton(self.groupBox)
        self.pushButton4.setObjectName("pushButton4")
        self.gridLayout.addWidget(self.pushButton4, 1, 5, 1, 1)
        self.lineEdit = QtWidgets.QLineEdit(self.groupBox)
        self.lineEdit.setObjectName("lineEdit")
        self.gridLayout.addWidget(self.lineEdit, 1, 1, 1, 4)
        self.label = QtWidgets.QLabel(self.groupBox)
        font = QtGui.QFont()
        font.setFamily("宋体")
        font.setPointSize(9)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
        self.label_2 = QtWidgets.QLabel(self.groupBox)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
        self.comboBox = QtWidgets.QComboBox(self.groupBox)
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.gridLayout.addWidget(self.comboBox, 0, 1, 1, 2)
        self.verticalLayout.addWidget(self.groupBox)
        self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox_2.setObjectName("groupBox_2")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.textEdit = QtWidgets.QTextEdit(self.groupBox_2)
        self.textEdit.setObjectName("textEdit")
        self.gridLayout_2.addWidget(self.textEdit, 0, 0, 1, 1)
        self.verticalLayout.addWidget(self.groupBox_2)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 322, 26))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        self.menu_2 = QtWidgets.QMenu(self.menubar)
        self.menu_2.setObjectName("menu_2")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.menubar.addAction(self.menu.menuAction())
        self.menubar.addAction(self.menu_2.menuAction())

        self.retranslateUi(MainWindow)
        self.pushButton.clicked.connect(self.comboBox.update) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "串口sram测试工具v0.1"))
        self.groupBox.setTitle(_translate("MainWindow", "配置选项"))
        self.pushButton.setText(_translate("MainWindow", "刷新串口"))
        self.label.setText(_translate("MainWindow", "串口选择:"))
        self.label_2.setText(_translate("MainWindow", "文件路径:"))
        self.pushButton2.setText(_translate("MainWindow", "串口打开"))
        self.pushButton3.setText(_translate("MainWindow", "串口发送"))
        self.pushButton4.setText(_translate("MainWindow", "选择文件"))
        self.comboBox.setItemText(0, _translate("MainWindow", "com"))
        self.groupBox_2.setTitle(_translate("MainWindow", "数据显示"))
        self.menu.setTitle(_translate("MainWindow", "帮助"))
        self.menu_2.setTitle(_translate("MainWindow", "关于"))

逻辑代码

这边在处理的时候有几个遇到的bug,

  1. 上位机需要一直接收,所以需要开一个线程用来持续接收。

  2. 界面更新太平凡容易卡死,所以起一个信号量来更新

python 复制代码
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5.QtCore import pyqtSignal
from ccm_Test import Ui_MainWindow  
import time
import serial
import serial.tools.list_ports
import threading


class MyApp(QMainWindow, Ui_MainWindow):
    data_received_signal = pyqtSignal(str)  # 定义信号,传递字符串数据
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.data_received_signal.connect(self.update_text_edit)  # 连接信号到槽
        self.pushButton_onclik()
        self.pushButton2_onclik()
        self.pushButton3_onclik()
        self.pushButton4_onclik()

    def update_text_edit(self, data):
        self.textEdit.append(data)  # 更新文本编辑器

    def pushButton_onclik(self):
        # self.mainop()
        def cao():
            print("已刷新串口。")
            self.textEdit.append('刷新')
            ports = serial.tools.list_ports.comports()
            self.comboBox.clear()  # 清空所有项
            for port, desc, hwid in sorted(ports):
                print(port, type(port),type(ports))
                self.comboBox.addItem(port)  # 添加新的项列表

            # print(ports[0], type(ports[0]))
            # try:
            #     self.comboBox.clear()  # 清空所有项
            #     self.comboBox.addItems()  # 添加新的项列表
            #
            #     print("Items added successfully.")
            # except Exception as e:
            #     print(f"An error occurred: {e}")

        self.pushButton.clicked.connect(cao)

    def pushButton2_onclik(self):
        # self.mainop()
        def cao():
            global port_name
            global serial_port
            port_name = self.comboBox.currentText()
            baud_rate = 115200
            print(port_name)
            serial_port = self.open_serial(port_name, baud_rate)
            self.textEdit.append('打开'+port_name)

        self.pushButton2.clicked.connect(cao)

    def pushButton3_onclik(self):
        # self.mainop()
        ''' bootloader 使用了0x20000400开始的地址,0xC00大小的区间。共开了0x10000的堆栈。 '''
        hex_string = "1234"
        hex_string1 = "1235"
        hex_string2 = "123455" #"52312000" 2000是实际动态代码 map文件中main的起始地址
        hex_string3 = "123456"
        hex_data_to_send = bytes.fromhex(hex_string)
        hex_data_to_send1 = bytes.fromhex(hex_string1)
        hex_data_to_send2 = bytes.fromhex(hex_string2)
        hex_data_to_send3 = bytes.fromhex(hex_string3)

        def cao():
            try:
                thread = threading.Thread(target=self.read_serial, args=(serial_port,))
                thread.start()
            except serial.SerialException as e:
                print(f"open_serial : {e}")
            ## 第一条命令
            self.write_serial(serial_port, hex_data_to_send)
            self.textEdit.append('>> ' + hex_string)

            time.sleep(0.1)
            # rxbuff = self.read_serial(serial_port)
            # print(rxbuff)
            # self.textEdit.append('<< '+ rxbuff)

            ## 第二条命令
            self.write_serial(serial_port, hex_data_to_send1)
            self.textEdit.append('>> ' + hex_string1)

            time.sleep(0.1)
            # rxbuff = self.read_serial(serial_port)
            # print(rxbuff)
            # self.textEdit.append('<< '+ rxbuff)

            ## 第三条命令
            print(fileName)
            binfilecontent = self.read_bin_file(fileName)
            print(type(binfilecontent), binfilecontent)
            binfilecontent_len = len(binfilecontent)
            binfilecontent_len_hex = bytes.fromhex(self.int_to_hex16(binfilecontent_len))
            # print(binfilecontent_len_hex,type(binfilecontent_len_hex))
            # print(self.read_bin_file(fileName))
            self.write_serial(serial_port, hex_data_to_send2 + binfilecontent_len_hex + binfilecontent)
            self.textEdit.append('>> ' + hex_string2)

            time.sleep(0.1)
            # rxbuff = self.read_serial(serial_port)
            # self.textEdit.append('<< '+ rxbuff)

            ## 第四条命令
            self.write_serial(serial_port, hex_data_to_send3)
            self.textEdit.append('>> ' + hex_string3)

            time.sleep(0.1)
            # rxbuff = self.read_serial(serial_port)
            # if(rxbuff == None):
            #     self.textEdit.append('<< ' + 'None.')
            # else:
            #     self.textEdit.append('<< '+ rxbuff)

            # time.sleep(3)
            # rxbuff = 0
            # rxbuff = self.read_serial(serial_port)
            # self.textEdit.append('等待后续sram测试数据返回...')
            # self.textEdit.append('<< '+ rxbuff)
            # self.close_serial(serial_port)
            pass
        self.pushButton3.clicked.connect(cao)

    def pushButton4_onclik(self):
        # global fileName
        def cao():
            # 打开文件选择对话框
            options = QFileDialog.Options()
            options |= QFileDialog.DontUseNativeDialog
            global fileName
            fileName, _ = QFileDialog.getOpenFileName(self, "QFileDialog.getOpenFileName()", "",
                                                      "All Files (*);;Text Files (*.txt)", options=options)
            if fileName:
                self.lineEdit.setText(fileName)  # 将选择的文件路径设置到 QLineEdit
            pass

        self.pushButton4.clicked.connect(cao)

    # 打开串口
    def open_serial(self, port, baudrate):
        try:
            ser = serial.Serial(
                port, baudrate,
                parity=serial.PARITY_EVEN,  # 设置校验
                stopbits=serial.STOPBITS_ONE,  # 设置停止位
                bytesize=8,  # 数据位为 8 位
                timeout=0  # 超时设置 非阻塞
            )  # 打开串口
            print(f"Serial port {port} opened at {baudrate} baud.")
            # try:
            #     thread = threading.Thread(target=self.read_serial(ser), args=(ser,))
            #     thread.start()
            # except serial.SerialException as e:
            #     print(f"open_serial : {e}")

            return ser
        except serial.SerialException as e:
            print(f"Error opening serial port {port}: {e}")
            return None

    # 从串口读取数据
    def read_serial(self, ser):
        while True:
            if ser:
                try:
                    if ser.in_waiting > 0:
                        received_data = ser.read(ser.in_waiting)
                        received_hex = received_data.hex()
                        if (received_hex == None):
                            self.data_received_signal.emit('<< ' + received_hex)  # 发射信号('<< ' + 'None.')
                        else:
                            self.data_received_signal.emit('<< ' + received_hex)  # 发射信号
                        print("Received data (hex):", received_hex)
                        # return received_hex
                    else:
                        # print("No data received.")
                        pass
                except serial.SerialTimeoutException:
                    print("Read timeout occurred")
                except serial.SerialException as e:
                    print(f"Error during read: {e}")
                time.sleep(0.1)  # 短暂休眠以减少CPU负担

    # 向串口写入数据
    def write_serial(self, ser, data):
        if ser:
            try:
                successlen = ser.write(data)  # 发送数据
                print("successlen: ",successlen)
                print(f"Sent data: {data}")
                # self.textEdit.append('>> ' + data.encode('utf-8').hex())
            except serial.SerialException as e:
                print(f"Error sending data: {e}")

    # 关闭串口
    def close_serial(self, ser):
        if ser:
            ser.close()
            print("Serial port closed.")

    # 读取bin文件
    def read_bin_file(self, file_path):
        try:
            with open(file_path, 'rb') as file:
                return file.read()
        except Exception as e:
            print(f"Failed to read file: {str(e)}")

    def int_to_hex16(self, value):
        if not (0 <= value <= 65535):
            raise ValueError("Integer value out of range for 16-bit representation")

        # 将整数转换为16位十六进制字符串
        hex_string = f"{value:04x}"
        return hex_string

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

界面示例

相关推荐
Null箘6 分钟前
从零创建一个 Django 项目
后端·python·django
云空9 分钟前
《解锁 Python 数据挖掘的奥秘》
开发语言·python·数据挖掘
玖年41 分钟前
Python re模块 用法详解 学习py正则表达式看这一篇就够了 超详细
python
岑梓铭1 小时前
(CentOs系统虚拟机)Standalone模式下安装部署“基于Python编写”的Spark框架
linux·python·spark·centos
游客5201 小时前
opencv中的各种滤波器简介
图像处理·人工智能·python·opencv·计算机视觉
Eric.Lee20211 小时前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
Dontla1 小时前
vscode怎么设置anaconda python解释器(anaconda解释器、vscode解释器)
ide·vscode·python
qq_529025292 小时前
Torch.gather
python·深度学习·机器学习
数据小爬虫@2 小时前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
Cachel wood2 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架