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

界面示例

相关推荐
笨鸟先飞,勤能补拙5 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能6 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂6 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study7 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境8 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境8 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1238 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102168 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb009 小时前
一个项目带你入门AI应用开发01
python
0566469 小时前
Python康复训练——常用标准库
开发语言·python·学习