蚣澡 发表于 2026-3-2 20:55:06

使用pyside6编写简单的串口上位机

一、设计UI界面

使用qt设计师
https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=ZjRhOTQ0YzQ1YWM5YWZjMGVhOTQwNThiYzY3ZGE4N2RfamdtV1pUeDdDazdHWTMxV3ZrdnBrYkI3TGt6eVp6b1VfVG9rZW46RmR6QWJJYm1Wb1d5MXF4Uk9KQ2M0SUZHblFZXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
需要注意的是
调整整体大小

最后设置整体自动调整大小时,对于QMainWindow窗口需要右键最基本的框,选择布局,不然在右侧是没有布局选项的
窗口放大时非对话框有最大大小,不跟着变大

设置对应窗口的对齐为 0
https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=NmMzMDliOWNlMzI1Yzk5OTNiMDZlOTQzMTA3MDQ5Zjdfc3plZTBEcE5kNmtac0M4bTQyUDlHdm5RcWJkREJveXRfVG9rZW46TXpNRmJrc0J4b2RMRU54YWF2R2NvNXVibmVlXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
按钮进入对齐后选择minimum,可以变大

https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=YmQ5ZDcyZTYxMTQyMmM4ODgwZTg1MWFhMWI3MTc5NDJfbndraU1KTHZYdE1zVE1vckZFVDVRUzFnZlVZYkUzd1ZfVG9rZW46R2xFSWJOSnN3bzNBZmJ4TEpVWWNtS0k4bnBjXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
默认时
https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=MjE0M2M3NTg5MGUxNzI2MzhkMjM5Mzc4NDI5NzJjZjVfZURjVjdkYklQc2FDN0VyU1J2NlZ4ZUJiU0tXSGFVY1pfVG9rZW46S0V0SGJySmZhb25OdGJ4T21uVGN4SjdPbjRkXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
二、编写逻辑

编写基本框架

选择自己使用的是QMainWindow还是QWidget
导入pyside的库

from PySide6.QtWidgets import QApplication,QMainWindow,QWidget引用对应界面库

#ui为上一层目录
from ui.Ui_serial_up import Ui_MainWindowclass MyWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
      super().__init__()
      self.setupUi(self)

if __name__ == "__main__":
    app = QApplication()
    window = MyWindow()
    window.show()
    sys.exit(app.exec())逻辑部分

导入串口所需库

from PySide6.QtCore import QThread,Signal

import serial
import serial.tools.list_ports配置大体框架

#接收线程
class SerialReadThread(QThread):
    ReadSignal = Signal(str) #接收信号
    def __init__(self,serial_obj):
      super().__init__()
      
    def run():
   
    def stop():子窗口

子窗口现在qt设计师设计好,使用QSialog类型
https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=Y2Y2ZDQ2NWIwODNlYjA5OGJiODhlNDQwOTUxNjljY2RfQ01SYWhiMVRRRGtzRFBGRzFabGhaTGQzVm9DQTU1VzJfVG9rZW46VzlIQWJlRTR5b21hcmh4Y1RhMGNMRE5wbmFmXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
#子窗口界面
class SettingSerial(QDialog,Ui_Dialog):
    def __init__(self,parent = None):#parent 为传入形参接口
      super().__init__(parent)
      self.setupUi(self)#调用子窗口Ui
      #绑定自带的确认和取消键
      #连接固定的确定取消方法
      #self.reject()和self.accept()是取消和确认方法
      self.buttonBox.accepted.connect(self.buttonAccept)
      self.buttonBox.rejected.connect(self.reject)
      
    def buttonAccept(self):
      self.accept()
    def backSetting(self):
    '''子窗口返回方法,同与父窗口调用后关闭子窗口时调用'''父窗口框架

#界面
class MyWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
      super().__init__()
      self.setupUi(self)#调用父窗口Ui
      
    def SerialSend(self):
   
    def SerialReceive(self):
   
    def SerialStart(self):
   
    def SerialStop(self):
   
    def ClearTx(self):
   
    def ClearRx(self):
    编写代码

首先编写主界面的基本操作
#清除
def clearTx(self):
      self.TextEdit_2.clear()
   
    def clearRx(self):
      self.TextEdit_1.clear()#变量定义
def initVariable(self):
    self.baudrate = "115200"
    self.bytesize = '8'
    self.stopbits_set = '1'
    self.parity_set = 'None'
    # 串口对象
    self.serial_port = None
    self.reader_thread = None
    self.send_byte_count = 0#发送字节总数
    self.receive_byte_count = 0#接收字节总数#按键关联方法
def ConnectButton(self):
    #关联按键
    self.pushButton_5.clicked.connect(self.clearTx)
    self.pushButton.clicked.connect(self.clearRx)
    #打开串口
    self.pushButton_2.clicked.connect(self.SerialSwitch)
    #刷新端口
    self.action10.setShortcut('F5')
    self.action10.setStatusTip('刷新端口')
    self.action10.triggered.connect(self.RefreshSerial)
    #串口设置
    self.pushButton_4.clicked.connect(self.open_dialog)
    self.action20.setStatusTip('配置串口')
    self.action20.triggered.connect(self.open_dialog)
    #更改baud下拉栏
    self.comboBox_2.currentIndexChanged.
      connect(self.backBaudrate)
    #发送数据
    self.pushButton_3.clicked.connect(self.SendData)打开串口关闭串口

def SerialSwitch(self):
    '''设置标志位,区分打开关闭串口'''
    if self.serial_port and self.serial_port.is_open:
      self.closeSerial()
    else:
      self.StartSerial()
def StartSerial(self):
    #读取当前com口
    is_com = self.comboBox.currentText()
    if not is_com :
      QMessageBox.warning(self,"warning","没有可用的串口")
      return
    #读取的端口从 - 分割开
    com = is_com.split(" - ")
    #把确认的端口信息赋值给局部变量,后面赋值给串口库形参
    baudrate = self.baudrate
    bytesize = int(self.bytesize)
    stopbits = serial.STOPBITS_ONE
    if self.stopbits_set == '1.5':
      stopbits = serial.STOPBITS_ONE_POINT_FIVE
    elif self.stopbits_set == '2':
      stopbits = serial.STOPBITS_TWO
    parity = serial.PARITY_NONE
    if self.parity_set == 'Odd':
      parity = serial.PARITY_ODD
    elif self.parity_set == 'Even':
      parity = serial.PARITY_EVEN
    elif self.parity_set == 'Mark':
      parity = serial.PARITY_MARK
    elif self.parity_set == 'Space':
      parity = serial.PARITY_SPACE

    try:
      self.serial_port = serial.Serial(
            port=com,
            baudrate=baudrate,
            bytesize=bytesize,
            stopbits=stopbits,
            parity=parity,
            timeout=0.1   # 读超时,让线程可以循环检查停止标志
      )
    except Exception as e:
      QMessageBox.critical(self, "错误", f"无法打开串口:{e}")
      return
    #开启多线程
    self.reader_thread = SerialReadThread(self.serial_port)
    #判断ReadSignal信号是否接收到数据,接收到进入数据处理
    self.reader_thread.ReadSignal.connect(self.on_data_received)
    self.reader_thread.start()
    #打开串口后将按钮改为关闭
    self.pushButton_2.setText('关闭串口')
    #失能部分按键,防止误触,出错
    self.set_serial_settings_enabled(False)

def closeSerial(self):
    if self.reader_thread:
      self.reader_thread.stop()
      self.reader_thread = None
    if self.serial_port and self.serial_port.is_open:
      self.serial_port.close()
    self.serial_port = None
    self.pushButton_2.setText("打开串口")
    self.set_serial_settings_enabled(True)失能使能模块方法包装
def set_serial_settings_enabled(self, enabled):
    self.comboBox.setEnabled(enabled)
    self.comboBox_2.setEnabled(enabled)
    self.pushButton_4.setEnabled(enabled)
    self.action20.setEnabled(enabled)https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=YmQxM2RlYmU4NTRhMWUxMjlmMWMzNjg5N2Q2NjM0YmNfUElRSEZKV2JPRGI1dEgzNzhGekUwOVZOVXByWHVlSG1fVG9rZW46VUtDSmJMSlRHbzJHd1F4OUhUUmNmelJYbmpjXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
打开串口的时候,关闭其他使能
设置串口 读 多线程

class SerialReadThread(QThread):
    #创建信号 , bytes类型
    ReadSignal = Signal(bytes)

    def __init__(self,serial_port):
      super().__init__()
      self.serial_port = serial_port
      self.is_running = False

    def run(self):
      self.is_running = True
      while self.is_running and self.serial_port and self.serial_port.is_open:
            try:
            #接收数据
                data = self.serial_port.read(self.serial_port.in_waiting or 1)
                if data:
                  #将接收到的数据连接上信号
                  self.ReadSignal.emit(data)
            except Exception as e:
                break
      

    def stop(self):
      self.is_running = False
      #等待主线程完成,使子线程完全退出
      self.wait()接收数据处理

https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=MmYyYzk5NGJhOWFkODhmMWM4NGRjYWZjYmExNTVkYjBfd2hiYzBvVTNVVGR4aHY3eG9DMTFxbWJEZnBaa2NCS2pfVG9rZW46T0dIVmJiOHdBbzJsU3d4QU1DUmNvWWJxbkViXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
def on_data_received(self,data):
    #状态栏的变量,接收数据+1
    self.receive_byte_count += len(data)
    self.update_status_bar()
    #判断hex是否勾选
    if self.checkBox_3.isChecked():
      hex_str = data.hex().upper()

      hex_str = ' '.join(hex_str for i in range(0, len(hex_str), 2))
      self.TextEdit_1.insertPlainText(hex_str + " ")
    else:
      try:
            text = data.decode('utf-8', errors='replace')
      except:
            text = str(data)
      self.TextEdit_1.insertPlainText(text)

    #自动滚动
    #返回光标
    cursor = self.TextEdit_1.textCursor()
    #光标移动到最后
    cursor.movePosition(cursor.MoveOperation.End)
    #页面到光标处
    self.TextEdit_1.setTextCursor(cursor)子窗口

https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=NzZlZjg0ODUyZDE3Y2NmMTA0MTlkZjVlNjZkZTRkNGVfdEhrc291M1hCUzR6blU0eGlRaWh5ejRqUFV0MmhlanhfVG9rZW46TXdBYWJpS2oybzRmTkl4STBmY2NTRTBPbjJnXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
子窗口

class SettingSerial(QDialog,Ui_Dialog):
    def __init__(self,parent = None):
      super().__init__(parent)
      self.setupUi(self)

      self.buttonBox.accepted.connect(self.buttonAccept)
      self.buttonBox.rejected.connect(self.reject)
    #刷新子窗口com,父子一致
    def refreashCom(self,postList,currentPort):
      self.comboBox.clear()
      self.comboBox.addItems(postList)
      if currentPort :
            Index = self.comboBox.findText(currentPort)
            if Index>=0:
                self.comboBox.setCurrentIndex(Index)
    #刷新子窗口,父子一致
    def refreashBaud(self,baudrate):
      if baudrate :
            Index = self.comboBox_2.findText(baudrate)
            if Index>=0:
                self.comboBox_2.setCurrentIndex(Index)
    #刷新子窗口,父子一致
    def refreashother(self,bytesize,stopbits_set,parity_set):
      if bytesize :
            Index = self.comboBox_3.findText(bytesize)
            if Index>=0:
                self.comboBox_3.setCurrentIndex(Index)
      if stopbits_set :
            Index = self.comboBox_4.findText(stopbits_set)
            if Index>=0:
                self.comboBox_4.setCurrentIndex(Index)
      if parity_set :
            Index = self.comboBox_6.findText(parity_set)
            if Index>=0:
                self.comboBox_6.setCurrentIndex(Index)
   
    def buttonAccept(self):
      self.accept()

    def backSetting(self):
      com = self.comboBox.currentText()#.split(" - ")
      return (com
                ,self.comboBox_2.currentText()
                ,self.comboBox_3.currentText()
                ,self.comboBox_4.currentText()
                ,self.comboBox_6.currentText()
                ,self.comboBox_5.currentText()
                )   父窗口调用子窗口

父窗口按键关联
https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=ZWRkNzYwZWE3NmZiZjEwYjQ3NWRjZGViNzQ1OTNjNGRfUFRRcXh2NVM4OVBwUWRQdGt6TlFjaDNORThRSG9XT3FfVG9rZW46RUtOdmJDUUYyb0Y0TFV4a0FWQ2NhcXF3blRlXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
def open_dialog(self):
    dialog = SettingSerial(self)
    '''刷新数据'''
    self.RefreshSerial()
    dialog.refreashCom(self.postList,self.currentPort)
    dialog.refreashBaud(self.baudrate)
    dialog.refreashother(self.bytesize,self.stopbits_set,self.parity_set)
    #使用.exec()方法,等待子窗口点击确认,此时可以返回一些数据
    if dialog.exec() == QDialog.Accepted:
      self.settings = dialog.backSetting()
      self.settingAccept(self.settings)返回数据
def settingAccept(self,settings):
    self.currentPort,self.baudrate,self.bytesize,self.stopbits_set,self.parity_set,flow_set = settings
    if self.currentPort :
      Index = self.comboBox.findText(self.currentPort)
      if Index>=0:
            self.comboBox.setCurrentIndex(Index)
    if self.baudrate :
      baud = self.comboBox_2.findText(self.baudrate)
      if baud>=0:
            self.comboBox_2.setCurrentIndex(baud)发送数据

def SendData(self):
    if not self.serial_port or not self.serial_port.is_open:
      QMessageBox.warning(self, "警告", "请先打开串口!")
      return
    #读取当前发送框有什么
    text = self.TextEdit_2.toPlainText()
    if not text:
      return
    #判断是不是 使用hex方式
    if self.checkBox_3.isChecked():
      hex_str = ''.join(text.split())
      try:
            #.fromhex 转16进制
            data = bytes.fromhex(hex_str)
      except ValueError as e:
            QMessageBox.warning(self, "错误", f"无效的十六进制字符串:{e}")
            return
    else:
      #ASCII发送
      data = text.encode('utf-8', errors='ignore')
    try:
      #返回发送的长度
      sent_bytes = self.serial_port.write(data)
      #状态栏
      self.send_byte_count += sent_bytes
      self.update_status_bar()
    except Exception as e:
      QMessageBox.critical(self, "错误", f"发送失败:{e}")运行

if __name__ == "__main__":
    app = QApplication()
    window = MyWindow()
    window.setWindowIcon(QIcon("icon.png"))
    window.show()
    sys.exit(app.exec())状态栏

https://acnivk8iy91z.feishu.cn/space/api/box/stream/download/asynccode/?code=OTMyODUxNzllNTc5MmM2OWU3NWUwMTRiNGI1MmFlMGVfUDNWYlVjaFVhQXRJTDBTTmVsSHZYVUFEOVVmcHRWampfVG9rZW46TkM1b2JxdnVLb09yd2J4d3ZPcGN0M3pVbkhjXzE3NzI0NTE2NzM6MTc3MjQ1NTI3M19WNA
可以创建QLabel窗口,然后使用.addPermanentWidget(self.label)一直添加到状态栏右侧
def status_bars(self):
    '''多状态栏'''
    status_text = f"接收:{self.receive_byte_count} 字节 | 发送:{self.send_byte_count} 字节"
    self.serial_status_label = QLabel(status_text)
    self.statusBar.addPermanentWidget(self.serial_status_label)

    self.textlabel = QLabel(' 天天好心情')
    self.statusBar.addPermanentWidget(self.textlabel)

def update_status_bar(self):
    """更新状态栏显示:发送/接收字节数"""
    # status_text = f"接收:{self.receive_byte_count} 字节 | 发送:{self.send_byte_count} 字节"
    # self.statusBar.setStyleSheet("QStatusBar { padding-left: 1000px; }")
    # 设置状态栏文本(showMessage默认显示5秒,传0表示永久显示)
    # self.statusBar.showMessage(status_text, 0)
    self.serial_status_label.setText(f"接收:{self.receive_byte_count} 字节 | 发送:{self.send_byte_count} 字节")https://gitee.com/liliww/serial-upper-computer.git

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

邹弘丽 发表于 2026-3-8 07:45:23

谢谢分享,试用一下

馑妣窟 发表于 2026-3-9 07:54:53

这个好,看起来很实用
页: [1]
查看完整版本: 使用pyside6编写简单的串口上位机