• PyQt5串口测试工具


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

    环境说明

    pycharm 2023.2.25

    python 3.10

    anaconda

    环境配置

    1. conda create -n envsram ##新建虚拟环境,不用anaconda也行自己使用python新建都行
    2. conda env list ##查看虚拟环境及路径,方便修改python解释器路径
    3. conda activate envsram
    4. conda install pyqt ##安装pyqt5及依赖
    5. designer ##键入,以打开pyqt图像设计界面,设计完成后为.ui文件
    6. pyuic5 -o ccm_Test.py .\Uartsendframe.ui ##转换.ui文件为测试文件
    7. pip install pyserial ##我conda install失败了,直接用pip安装,为了上位机实现串口相关操作

    代码测试

    ui界面代码

    1. # -*- coding: utf-8 -*-
    2. # Form implementation generated from reading ui file '.\Uartsendframe.ui'
    3. #
    4. # Created by: PyQt5 UI code generator 5.15.10
    5. #
    6. # WARNING: Any manual changes made to this file will be lost when pyuic5 is
    7. # run again. Do not edit this file unless you know what you are doing.
    8. from PyQt5 import QtCore, QtGui, QtWidgets
    9. class Ui_MainWindow(object):
    10. def setupUi(self, MainWindow):
    11. MainWindow.setObjectName("MainWindow")
    12. MainWindow.resize(432, 476)
    13. self.centralwidget = QtWidgets.QWidget(MainWindow)
    14. self.centralwidget.setObjectName("centralwidget")
    15. self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
    16. self.verticalLayout.setObjectName("verticalLayout")
    17. self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
    18. self.groupBox.setObjectName("groupBox")
    19. self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
    20. self.gridLayout.setObjectName("gridLayout")
    21. self.pushButton = QtWidgets.QPushButton(self.groupBox)
    22. self.pushButton.setObjectName("pushButton")
    23. self.gridLayout.addWidget(self.pushButton, 0, 3, 1, 1)
    24. self.pushButton2 = QtWidgets.QPushButton(self.groupBox)
    25. self.pushButton2.setObjectName("pushButton2")
    26. self.gridLayout.addWidget(self.pushButton2, 0, 4, 1, 1)
    27. self.pushButton3 = QtWidgets.QPushButton(self.groupBox)
    28. self.pushButton3.setObjectName("pushButton3")
    29. self.gridLayout.addWidget(self.pushButton3, 0, 5, 1, 1)
    30. self.pushButton4 = QtWidgets.QPushButton(self.groupBox)
    31. self.pushButton4.setObjectName("pushButton4")
    32. self.gridLayout.addWidget(self.pushButton4, 1, 5, 1, 1)
    33. self.lineEdit = QtWidgets.QLineEdit(self.groupBox)
    34. self.lineEdit.setObjectName("lineEdit")
    35. self.gridLayout.addWidget(self.lineEdit, 1, 1, 1, 4)
    36. self.label = QtWidgets.QLabel(self.groupBox)
    37. font = QtGui.QFont()
    38. font.setFamily("宋体")
    39. font.setPointSize(9)
    40. self.label.setFont(font)
    41. self.label.setObjectName("label")
    42. self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
    43. self.label_2 = QtWidgets.QLabel(self.groupBox)
    44. self.label_2.setObjectName("label_2")
    45. self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
    46. self.comboBox = QtWidgets.QComboBox(self.groupBox)
    47. self.comboBox.setObjectName("comboBox")
    48. self.comboBox.addItem("")
    49. self.gridLayout.addWidget(self.comboBox, 0, 1, 1, 2)
    50. self.verticalLayout.addWidget(self.groupBox)
    51. self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
    52. self.groupBox_2.setObjectName("groupBox_2")
    53. self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
    54. self.gridLayout_2.setObjectName("gridLayout_2")
    55. self.textEdit = QtWidgets.QTextEdit(self.groupBox_2)
    56. self.textEdit.setObjectName("textEdit")
    57. self.gridLayout_2.addWidget(self.textEdit, 0, 0, 1, 1)
    58. self.verticalLayout.addWidget(self.groupBox_2)
    59. MainWindow.setCentralWidget(self.centralwidget)
    60. self.menubar = QtWidgets.QMenuBar(MainWindow)
    61. self.menubar.setGeometry(QtCore.QRect(0, 0, 322, 26))
    62. self.menubar.setObjectName("menubar")
    63. self.menu = QtWidgets.QMenu(self.menubar)
    64. self.menu.setObjectName("menu")
    65. self.menu_2 = QtWidgets.QMenu(self.menubar)
    66. self.menu_2.setObjectName("menu_2")
    67. MainWindow.setMenuBar(self.menubar)
    68. self.statusbar = QtWidgets.QStatusBar(MainWindow)
    69. self.statusbar.setObjectName("statusbar")
    70. MainWindow.setStatusBar(self.statusbar)
    71. self.menubar.addAction(self.menu.menuAction())
    72. self.menubar.addAction(self.menu_2.menuAction())
    73. self.retranslateUi(MainWindow)
    74. self.pushButton.clicked.connect(self.comboBox.update) # type: ignore
    75. QtCore.QMetaObject.connectSlotsByName(MainWindow)
    76. def retranslateUi(self, MainWindow):
    77. _translate = QtCore.QCoreApplication.translate
    78. MainWindow.setWindowTitle(_translate("MainWindow", "串口sram测试工具v0.1"))
    79. self.groupBox.setTitle(_translate("MainWindow", "配置选项"))
    80. self.pushButton.setText(_translate("MainWindow", "刷新串口"))
    81. self.label.setText(_translate("MainWindow", "串口选择:"))
    82. self.label_2.setText(_translate("MainWindow", "文件路径:"))
    83. self.pushButton2.setText(_translate("MainWindow", "串口打开"))
    84. self.pushButton3.setText(_translate("MainWindow", "串口发送"))
    85. self.pushButton4.setText(_translate("MainWindow", "选择文件"))
    86. self.comboBox.setItemText(0, _translate("MainWindow", "com"))
    87. self.groupBox_2.setTitle(_translate("MainWindow", "数据显示"))
    88. self.menu.setTitle(_translate("MainWindow", "帮助"))
    89. self.menu_2.setTitle(_translate("MainWindow", "关于"))

    逻辑代码

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

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

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

    1. import sys
    2. from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
    3. from PyQt5.QtCore import pyqtSignal
    4. from ccm_Test import Ui_MainWindow
    5. import time
    6. import serial
    7. import serial.tools.list_ports
    8. import threading
    9. class MyApp(QMainWindow, Ui_MainWindow):
    10. data_received_signal = pyqtSignal(str) # 定义信号,传递字符串数据
    11. def __init__(self):
    12. super().__init__()
    13. self.setupUi(self)
    14. self.data_received_signal.connect(self.update_text_edit) # 连接信号到槽
    15. self.pushButton_onclik()
    16. self.pushButton2_onclik()
    17. self.pushButton3_onclik()
    18. self.pushButton4_onclik()
    19. def update_text_edit(self, data):
    20. self.textEdit.append(data) # 更新文本编辑器
    21. def pushButton_onclik(self):
    22. # self.mainop()
    23. def cao():
    24. print("已刷新串口。")
    25. self.textEdit.append('刷新')
    26. ports = serial.tools.list_ports.comports()
    27. self.comboBox.clear() # 清空所有项
    28. for port, desc, hwid in sorted(ports):
    29. print(port, type(port),type(ports))
    30. self.comboBox.addItem(port) # 添加新的项列表
    31. # print(ports[0], type(ports[0]))
    32. # try:
    33. # self.comboBox.clear() # 清空所有项
    34. # self.comboBox.addItems() # 添加新的项列表
    35. #
    36. # print("Items added successfully.")
    37. # except Exception as e:
    38. # print(f"An error occurred: {e}")
    39. self.pushButton.clicked.connect(cao)
    40. def pushButton2_onclik(self):
    41. # self.mainop()
    42. def cao():
    43. global port_name
    44. global serial_port
    45. port_name = self.comboBox.currentText()
    46. baud_rate = 115200
    47. print(port_name)
    48. serial_port = self.open_serial(port_name, baud_rate)
    49. self.textEdit.append('打开'+port_name)
    50. self.pushButton2.clicked.connect(cao)
    51. def pushButton3_onclik(self):
    52. # self.mainop()
    53. ''' bootloader 使用了0x20000400开始的地址,0xC00大小的区间。共开了0x10000的堆栈。 '''
    54. hex_string = "1234"
    55. hex_string1 = "1235"
    56. hex_string2 = "123455" #"52312000" 2000是实际动态代码 map文件中main的起始地址
    57. hex_string3 = "123456"
    58. hex_data_to_send = bytes.fromhex(hex_string)
    59. hex_data_to_send1 = bytes.fromhex(hex_string1)
    60. hex_data_to_send2 = bytes.fromhex(hex_string2)
    61. hex_data_to_send3 = bytes.fromhex(hex_string3)
    62. def cao():
    63. try:
    64. thread = threading.Thread(target=self.read_serial, args=(serial_port,))
    65. thread.start()
    66. except serial.SerialException as e:
    67. print(f"open_serial : {e}")
    68. ## 第一条命令
    69. self.write_serial(serial_port, hex_data_to_send)
    70. self.textEdit.append('>> ' + hex_string)
    71. time.sleep(0.1)
    72. # rxbuff = self.read_serial(serial_port)
    73. # print(rxbuff)
    74. # self.textEdit.append('<< '+ rxbuff)
    75. ## 第二条命令
    76. self.write_serial(serial_port, hex_data_to_send1)
    77. self.textEdit.append('>> ' + hex_string1)
    78. time.sleep(0.1)
    79. # rxbuff = self.read_serial(serial_port)
    80. # print(rxbuff)
    81. # self.textEdit.append('<< '+ rxbuff)
    82. ## 第三条命令
    83. print(fileName)
    84. binfilecontent = self.read_bin_file(fileName)
    85. print(type(binfilecontent), binfilecontent)
    86. binfilecontent_len = len(binfilecontent)
    87. binfilecontent_len_hex = bytes.fromhex(self.int_to_hex16(binfilecontent_len))
    88. # print(binfilecontent_len_hex,type(binfilecontent_len_hex))
    89. # print(self.read_bin_file(fileName))
    90. self.write_serial(serial_port, hex_data_to_send2 + binfilecontent_len_hex + binfilecontent)
    91. self.textEdit.append('>> ' + hex_string2)
    92. time.sleep(0.1)
    93. # rxbuff = self.read_serial(serial_port)
    94. # self.textEdit.append('<< '+ rxbuff)
    95. ## 第四条命令
    96. self.write_serial(serial_port, hex_data_to_send3)
    97. self.textEdit.append('>> ' + hex_string3)
    98. time.sleep(0.1)
    99. # rxbuff = self.read_serial(serial_port)
    100. # if(rxbuff == None):
    101. # self.textEdit.append('<< ' + 'None.')
    102. # else:
    103. # self.textEdit.append('<< '+ rxbuff)
    104. # time.sleep(3)
    105. # rxbuff = 0
    106. # rxbuff = self.read_serial(serial_port)
    107. # self.textEdit.append('等待后续sram测试数据返回...')
    108. # self.textEdit.append('<< '+ rxbuff)
    109. # self.close_serial(serial_port)
    110. pass
    111. self.pushButton3.clicked.connect(cao)
    112. def pushButton4_onclik(self):
    113. # global fileName
    114. def cao():
    115. # 打开文件选择对话框
    116. options = QFileDialog.Options()
    117. options |= QFileDialog.DontUseNativeDialog
    118. global fileName
    119. fileName, _ = QFileDialog.getOpenFileName(self, "QFileDialog.getOpenFileName()", "",
    120. "All Files (*);;Text Files (*.txt)", options=options)
    121. if fileName:
    122. self.lineEdit.setText(fileName) # 将选择的文件路径设置到 QLineEdit
    123. pass
    124. self.pushButton4.clicked.connect(cao)
    125. # 打开串口
    126. def open_serial(self, port, baudrate):
    127. try:
    128. ser = serial.Serial(
    129. port, baudrate,
    130. parity=serial.PARITY_EVEN, # 设置校验
    131. stopbits=serial.STOPBITS_ONE, # 设置停止位
    132. bytesize=8, # 数据位为 8 位
    133. timeout=0 # 超时设置 非阻塞
    134. ) # 打开串口
    135. print(f"Serial port {port} opened at {baudrate} baud.")
    136. # try:
    137. # thread = threading.Thread(target=self.read_serial(ser), args=(ser,))
    138. # thread.start()
    139. # except serial.SerialException as e:
    140. # print(f"open_serial : {e}")
    141. return ser
    142. except serial.SerialException as e:
    143. print(f"Error opening serial port {port}: {e}")
    144. return None
    145. # 从串口读取数据
    146. def read_serial(self, ser):
    147. while True:
    148. if ser:
    149. try:
    150. if ser.in_waiting > 0:
    151. received_data = ser.read(ser.in_waiting)
    152. received_hex = received_data.hex()
    153. if (received_hex == None):
    154. self.data_received_signal.emit('<< ' + received_hex) # 发射信号('<< ' + 'None.')
    155. else:
    156. self.data_received_signal.emit('<< ' + received_hex) # 发射信号
    157. print("Received data (hex):", received_hex)
    158. # return received_hex
    159. else:
    160. # print("No data received.")
    161. pass
    162. except serial.SerialTimeoutException:
    163. print("Read timeout occurred")
    164. except serial.SerialException as e:
    165. print(f"Error during read: {e}")
    166. time.sleep(0.1) # 短暂休眠以减少CPU负担
    167. # 向串口写入数据
    168. def write_serial(self, ser, data):
    169. if ser:
    170. try:
    171. successlen = ser.write(data) # 发送数据
    172. print("successlen: ",successlen)
    173. print(f"Sent data: {data}")
    174. # self.textEdit.append('>> ' + data.encode('utf-8').hex())
    175. except serial.SerialException as e:
    176. print(f"Error sending data: {e}")
    177. # 关闭串口
    178. def close_serial(self, ser):
    179. if ser:
    180. ser.close()
    181. print("Serial port closed.")
    182. # 读取bin文件
    183. def read_bin_file(self, file_path):
    184. try:
    185. with open(file_path, 'rb') as file:
    186. return file.read()
    187. except Exception as e:
    188. print(f"Failed to read file: {str(e)}")
    189. def int_to_hex16(self, value):
    190. if not (0 <= value <= 65535):
    191. raise ValueError("Integer value out of range for 16-bit representation")
    192. # 将整数转换为16位十六进制字符串
    193. hex_string = f"{value:04x}"
    194. return hex_string
    195. if __name__ == "__main__":
    196. app = QApplication(sys.argv)
    197. window = MyApp()
    198. window.show()
    199. sys.exit(app.exec_())

    界面示例

  • 相关阅读:
    【Linux】环境基础开发工具使用——vim使用
    JS/TS项目里的Module都是什么?
    拼多多店铺搜索相关问题,为什么新品上架搜索不到
    神经网络NLP基础 循环神经网络 LSTM
    string类接口介绍及应用
    LeetCode分支-搜索插入位置
    Cookie技术
    操作系统之微内核架构
    FastAPI 学习之路(七)字符串的校验
    Chapter 12 End-User Task As Shell Scripts
  • 原文地址:https://blog.csdn.net/Yoolell/article/details/139418945