笔者经常会遇到使用上位机进行相关测试的场景,但现成的上位机并不能完全满足自己的需求,或是上位机缺乏使用说明。所以,自己写?
pycharm 2023.2.25
python 3.10
anaconda
- 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安装,为了上位机实现串口相关操作
-
- # -*- 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. 界面更新太平凡容易卡死,所以起一个信号量来更新
- 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_())
