长安.
Articles7
Tags12
Categories3
基于Serial和PYQT5实现的Python串口上位机

基于Serial和PYQT5实现的Python串口上位机

本文介绍了关于制作Python上位机的相关内容…

设计初衷

本设计的初衷在于制作一款自己够用的基于串口协议的上位机调试工具,而Python的Seerial模块恰好满足了所有需求
本设计经实际测试后发现,串口调试的基本功能可以实现,可以投入实际应用
但是在测试过程中我也发现了一些问题,比较直观的两个就是:
1、在通过线程接收串口下位机发送的数据时,接收的频率太低或者说从接收到显示的间隔时间太长,给用户制造了很不好的使用体验;
2、当用户点击关闭程序按钮后,在调试程序时,程序并不能很好的退出,用于轮询接收下位机数据的线程没有得到较好的关闭;
针对以上问题,后续我会做出改进,因为本设计本身就是为后续的程序提供一个较为良好的模板,后续我会添加更多的功能进入此设计,例如数据的可视化绘制、数据的存储和分析等
也有可能联合ESP32无线调试器制作一个无线调试功能,如果有什么更好的功能或者有意思的功能也欢迎反馈~

设计思路

本设计一共用到了Python的Serial库和PYQT5库,PYQT的部分曾经提到过多次,此处便不再赘述
而更为直观的思路则是通过Python的Serial库,来实现Ser对象的创建和配置等操作。

Serial模块

本设计使用的Serial模块是Python对于串口协议的标准匹配模块,具有使用简单,功能强大的特点,以下是对Serial模块的简介:

引用自ChatGPT
Python的serial模块是一个用于串行通信的库,它提供了在Python中进行串行通信的功能。它允许Python程序通过串行端口与外部设备(如传感器、微控制器、Arduino等)进行通信。

serial模块提供了一组用于配置和控制串行端口的函数和类。通过该模块,你可以打开串行端口、设置波特率、数据位、停止位和校验位等通信参数,并通过串行端口进行数据的读取和写入。

以下是serial模块的一些常用功能和类:

Serial类:这是serial模块的核心类,用于表示打开的串行端口。通过该类的实例,你可以进行读取、写入和控制串行通信。

serial.Serial()函数:用于创建Serial类的实例,可以指定串行端口名称、波特率和其他通信参数。

open()函数:用于打开串行端口,返回一个Serial类的实例。

write()函数:用于向串行端口写入数据。你可以传递字符串或字节序列作为要写入的数据。

read()函数:用于从串行端口读取数据。它可以指定要读取的字节数,并返回一个字符串或字节序列。

close()函数:用于关闭串行端口。

除了基本的读写操作外,serial模块还提供了其他功能,如超时控制、流控制、事件通知和错误处理等。你可以根据自己的需求使用这些功能来实现更复杂的串行通信操作。

下图为上位机的界面:
串口上位机

程序首先等待用户的串口插入电脑,等待串口插入好后,点击“刷新串口”后,程序会自动选择一个串口号,等待用户点击连接,当用户点击连接按钮后,
程序会执行创建串口对象(Ser)的操作,并且配置Ser的一系列参数例如波特率、数据位等。
等待连接成功后,轮询接收线程开始轮询接收串口数据,并通过槽函数将接收到的数据显示在接收框内,程序界面内还有“清除接收按钮”,当按下清除接收按钮后,程序的
接收框内接收到的数据将会清空并且继续显示新数据。
而程序的发送功能则是使用到了Serial模块的write()方法,此方法具有自定义超时时间的功能,可以根据用户定义的超时时间来发送数据,若发送过程超时
程序则会终止发送。
以下是部分代码,仅供参考和学习使用:

class MyWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.setupUi(self)
        self.setWindowIcon(QIcon('serialscope.ico'))
        self.sendbutton.setToolTip('点击打开串口')
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.time_out)
        #self.check_bit_changed(self)
        #self.radioButton.setChecked(True)
        self.sendbot.setCurrentIndex(0)

    def time_out(self):
        global ser, timer_value
        self.timer.start(timer_value)

    #######################
    def open_com(self):
        global ser, serialPort, baudRate, com_list
        com_list = get_com_list_1()
        if com_list != []:
            if serialPort != None:
                ser.port = serialPort
                ser.baudrate = baudRate
                if self.sendbutton.text() == '连接':
                    try:
                        ser.open()
                        self.timer.start(timer_value)
                        self.sendbutton.setText("关闭")
                        self.sendbutton.setToolTip('点击关闭串口')
                        QMessageBox.about(myWin, "提示", f"串口{serialPort}连接成功!")
                        print('串口已连接')
                    except:
                        QMessageBox.about(myWin, "警告", f"串口{serialPort}出错")
                        pass
                else:
                    self.timer.stop()
                    ser.close()
                    self.sendbutton.setText("连接")
                    self.sendbutton.setToolTip('点击打开串口')
                    print('串口已断开')
            else:
                myWin.port_combo.clear()
                for i in range(len(com_list)):
                    myWin.sendcom.addItem(com_list[i])
                serialPort = com_list[0]
        else:
            serialPort = None
            com_list = self.get_com_list()
            myWin.sendcom.clear()
            QMessageBox.about(myWin, "警告", "请先打开串口")

    def port_changed(self, text):
        global serialPort
        serialPort = text
        print('串口:' + serialPort)
        ser.port = serialPort

    def baud_changed(self, text):
        global baudRate
        baudRate = int(text)
        print("波特率为"+text)
        ser.baudrate = baudRate

    def check_bit_changed(self,text):
        if text == "none":
            ser.parity = serial.PARITY_NONE
        elif text == "odd":
            ser.parity = serial.PARITY_ODD
        elif text == "even":
            ser.parity = serial.PARITY_EVEN
        print("校验位为"+text)

    def data_bit_changed(self,text):
        ser.bytesize = int(text)
        print("数据位为"+text)

    def stop_bit_changed(self,text):
        if int(text)==1:
            ser.stopbits = serial.STOPBITS_ONE
        elif int(text)==1.5:
            ser.stopbits = serial.STOPBITS_ONE_POINT_FIVE
        elif int(text)==2:
            ser.stopbits = serial.STOPBITS_TWO
        print("停止位为"+text)

    def clear_recall(self):
        self.recall_data.clear()

    def flush_data_buf(self,text):
        #global send_buff
        print("缓冲区数据为" + text)
        #send_buff = text

    def send_data_buff(self):
        global send_buff
        send_buff = self.send_buff.toPlainText()
        if self.sendbutton.text() == "关闭":
            if send_buff == "":
                QMessageBox.about(myWin, "警告", "缓冲区无数据")
            else:
                #ser.write_timeout = 0.5
                if self.send_newline.isChecked():
                    ser.write(("\r\n"+send_buff+"\r\n").encode('utf-8'))
                else:
                    ser.write(send_buff.encode('utf-8'))
                print(send_buff)
        else:
            QMessageBox.about(myWin, "警告", "未连接到任何串口!")

    def get_com_list(self):
        global serialPort
        myWin.sendcom.clear()
        com_list = get_com_list_1()
        if com_list != []:
            for i in range(len(com_list)):
                myWin.sendcom.addItem(com_list[i])
            serialPort = com_list[0]
        else:
            serialPort = None
            QMessageBox.about(myWin, "警告", "无新串口加入!")

def Read_buf():
    while True:
        if myWin.sendbutton.text()=="关闭":
            try:
                if ser.in_waiting:
                    textSetial = ser.readline()
                    serialData = textSetial
                    print("接收到数据")
                    myWin.recall_data.append(textSetial.decode('utf-8'))
                    # print(textSetial)
                    #self.txt.config(state=serial.NORMAL)
                    #self.txt.insert(serial.END, textSetial)
                    #self.txt.config(state=serial.DISABLED)
            except:
                print("无数据")

该程序实现了串口调试助手的基本功能,但是距离较高级别的调试助手还是有较大的差距,后续我可能会添加像matplotlib库之类的绘图库
使得数据更为可视化和直观化,也可能添加一些模块化的功能例如TCP协议调试器等来提升开发者的调试效率。
.
.
.
.
.
.
.

写在后面的话

整体来说我对这个界面还是比较满意的,只是对于线程的处理部分还没能够比较熟练的处理,这也印证了我对于Python的多线程还不够熟悉,在以后的学习中我会持续熟练多线程模块
实现对此项目的优化和升级~

Author:长安.
Link:http://example.com/2023/05/22/%E5%9F%BA%E4%BA%8ESerial%E5%92%8CPYQT5%E5%AE%9E%E7%8E%B0%E7%9A%84Python%E4%B8%B2%E5%8F%A3%E4%B8%8A%E4%BD%8D%E6%9C%BA/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可