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_())

界面示例

相关推荐
aliceDingYM4 小时前
Linux python3.6安装mayavi报错
linux·python·ui
.生产的驴7 小时前
SpringBoot AOP切入点表达式
spring boot·后端·python
逆水寻舟7 小时前
算法学习记录2
python·学习·算法
B站计算机毕业设计超人8 小时前
计算机毕业设计Python深度学习美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js
大数据·python·深度学习·机器学习·数据分析·课程设计·推荐算法
码农超哥同学8 小时前
Python面试题:请解释 `lambda` 函数是什么,并举一个例子
开发语言·python·面试·编程
sssjjww8 小时前
python输出日志out.log相关问题(缓存机制)
java·python·缓存
Uluoyu8 小时前
python爬虫爬取中国国际招标有限公司
开发语言·爬虫·python
Python私教8 小时前
zdppy+onlyoffice+vue3解决文档加载和文档强制保存时弹出警告的问题
vue.js·python
菜鸟赵大宝9 小时前
【Python】Python中TODO的用法解析
python·pycharm
Narutolxy9 小时前
如何高效管理和迁移Python开发环境:从Conda到纯Python的转换指南
开发语言·python·conda