用python写一个简单串口助手

1 配置开发环境:

注意-需要现配置python的环境变量;
然后CMD 窗口运行2条指令:
pip3 install pyserial
python -m pip install ttkbootstrap

2 完整代码:

main.py

点击查看代码
import ctypes
import sys
import time
import tkinter

from tkinter.ttk import *
from tkinter import *

import serial  # 导入模块
import serial.tools.list_ports
import threading
from tkinter import messagebox
from ttkbootstrap import Style

from logo import img
import  base64,os


global UART          #全局型式保存串口句柄
global RX_THREAD     #全局型式保存串口接收函数
global gui           #全局型式保存GUI句柄

tx_cnt=0 #发送字符数统计
rx_cnt=0 #接收字符数统计


def ISHEX(data):        #判断输入字符串是否为十六进制
    if len(data)%2:
        return False
    for item in data:
        if item not in '0123456789ABCDEFabcdef': #循环判断数字和字符
            return False
    return True


def uart_open_close(fun,com,bund):  #串口打开关闭控制
    global UART
    global RX_THREAD

    if fun==1:#打开串口
        try:
           UART = serial.Serial(com, bund, timeout=0.2)  # 提取串口号和波特率并打开串口
           if UART.isOpen(): #判断是否打开成功
               lock = threading.Lock()
               RX_THREAD = UART_RX_TREAD('URX1',lock)  #开启数据接收进程
               RX_THREAD.setDaemon(True)               #开启守护进程 主进程结束后接收进程也关闭 会报警告 不知道咋回事
               RX_THREAD.start()
               RX_THREAD.resume()
               return True
        except:
            return False
        return False
    else:                   #关闭串口
        print("Closed commport")
        RX_THREAD.pause()
        UART.close()

def uart_tx(data,isHex=False):          #串口发送数据
    global UART

    try:
        if  UART.isOpen():  #发送前判断串口状态 避免错误
            print("uart_send=" + data)
            gui.tx_rx_cnt(tx=len(data)) #发送计数
            if isHex:   #十六进制发送
                data_bytes = bytes.fromhex(data)
                return UART.write(bytes(data_bytes))
            else:      #字符发送
                return UART.write(data.encode('gb2312'))
    except:#错误返回
        messagebox.showinfo('Error', 'Send failed')


class UART_RX_TREAD(threading.Thread):          #数据接收进程 部分重构
    global gui

    def __init__(self, name, lock):
        threading.Thread.__init__(self)
        self.mName = name
        self.mLock = lock
        self.mEvent = threading.Event()

    def run(self): #主函数
        print('Start Receiving\r')
        while True:
            self.mEvent.wait()
            self.mLock.acquire()
            if UART.isOpen():
                rx_buf =  UART.read()
                if len(rx_buf) >0:
                    rx_buf += UART.readall()  #有延迟但不易出错
                    gui.tx_rx_cnt(rx=len(rx_buf))
                    if gui.ascii_hex_get() == False:
                        print('Hex Received', rx_buf.hex().upper())
                        gui.txt_rx.insert(END,  rx_buf.hex().upper())
                    else:
                        str_data = str(rx_buf, encoding='gb2312')
                        print("Comport Received:", len(rx_buf), str_data)
                        gui.txt_rx.insert(END,str_data)
                        # self.txt_rx.insert(END,str_data)
            self.mLock.release()
           #time.sleep(3)
    def pause(self): #暂停
        self.mEvent.clear()

    def resume(self):#恢复
        self.mEvent.set()

# 自动隐藏 CMD 窗口(仅 Windows 有效)
def hide_console():
    if sys.platform.startswith('win'):
        ctypes.windll.user32.ShowWindow(
            ctypes.windll.kernel32.GetConsoleWindow(), 0)
'''GUI'''''''''''''''''''''''''''''''''''''''''''''''''''''''''


class GUI:
    def __init__(self):
        self.root = Tk()
        self.root.title('YK_COM_tester-V1.0.1--Defined icon by png')             #窗口名称
        self.root.geometry("800x360+300+250")         #尺寸位置


        icon =open("port0.ico","wb+")
        icon.write(base64.b64decode(img))#写入临时文件中
        icon.close()

        self.root.iconbitmap("port0.ico")
        os.remove("port0.ico")


        self.interface()
       # Style(theme='pulse') #主题修改 可选['cyborg', 'journal', 'darkly', 'flatly' 'solar', 'minty', 'litera', 'united', 'pulse', 'cosmo', 'lumen', 'yeti', 'superhero','sandstone']
        Style(theme='sandstone') #主题修改 可选['cyborg', 'journal', 'darkly', 'flatly' 'solar', 'minty', 'litera', 'united', 'pulse', 'cosmo', 'lumen', 'yeti', 'superhero','sandstone']

    def interface(self):
        """"界面编写位置"""
        #--------------------------------操作区域-----------------------------#
        self.fr1=Frame(self.root)
        self.fr1.place(x=0,y=0,width=510,height=360)     #区域1位置尺寸

        self.lb1 =Label(self.fr1, text='Port:',font="Arial",fg='red')  #点击可刷新
        self.lb1.place(x=1,y=1,width=35,height=35)

        self.var_cb1 = StringVar()
        self.comb1 = Combobox(self.fr1,textvariable=self.var_cb1)
        self.comb1['values'] = list(serial.tools.list_ports.comports()) #列出可用串口
        self.comb1.current(0)  # 设置默认选项 0开始
        self.comb1.place(x=1,y=40,width=225,height=30)
        com=list(serial.tools.list_ports.comports())

        print('**********可用串口***********')
        for i in range(0, len(com)):
            print(com[i])
        print('***************************')

        self.lb2 = Label(self.fr1, text='Baudrate:')
        self.comb2 = Combobox(self.fr1,values=['2400','9600','57600','115200','128000','230400','256000','460800','500000','512000','600000','750000','921600','1000000','1500000','2000000'])
        self.comb2.current(3)                               #设置默认选项 115200
        self.lb2.place(x=1,y=75,width=60,height=20)
        self.comb2.place(x=1,y=100,width=100,height=25)

        self.var_bt1 = StringVar()
        self.var_bt1.set("Open comport")
        self.btn1 = Button(self.fr1,textvariable=self.var_bt1,command=self.uart_opn_close) #绑定 uart_opn_close 方法
        self.btn1.place(x=1,y=140,width=150,height=30)



        self.var_cs = IntVar()  #定义返回类型
        self.rd1 = Radiobutton(self.fr1,text="Ascii",variable=self.var_cs,value=0,command = self.txt_clr) #选择后清除显示内容
        self.rd2 = Radiobutton(self.fr1,text="Hex",variable=self.var_cs,value=1,command = self.txt_clr)
        self.rd1.place(x=1,y=180,width=60,height=30)
        self.rd2.place(x=1,y=210,width=60,height=30)


        self.btn3 = Button(self.fr1, text='Clear',command = self.txt_clr) #绑定清空方法
        self.btn4 = Button(self.fr1, text='Save',command=self.savefiles) #绑定保存方法
        self.btn3.place(x=1,y=260,width=60,height=30)
        self.btn4.place(x=100,y=260,width=60,height=30)

        self.btn5 = Button(self.fr1, text='Function',command=self.ascii_hex_get) #测试用
        self.btn6 = Button(self.fr1, text='Send',command=self.uart_send)  #绑定发送方法
        self.btn5.place(x=1,y=315,width=60,height=30)
        self.btn6.place(x=100,y=315,width=60,height=30)

        #-------------------------------文本区域-----------------------------#
        self.fr2=Frame(self.root)          #区域1 容器  relief   groove=凹  ridge=凸
        self.fr2.place(x=235,y=0,width=590,height=360)     #区域1位置尺寸

        self.txt_rx = Text(self.fr2)
        self.txt_rx.place(relheight=0.6,relwidth=0.9,relx=0.05,rely=0.01) #比例计算控件尺寸和位置

        self.txt_tx = Text(self.fr2)
        self.txt_tx.place(relheight=0.25,relwidth=0.9,relx=0.05,rely=0.66) #比例计算控件尺寸位置

        self.lb3 =Label(self.fr2, text='R:0    S:0',bg="yellow",anchor='w') #字节统计
        self.lb3.place(relheight=0.05,relwidth=0.3,relx=0.045,rely=0.925)

        self.lb4 = Label(self.fr2, text=' ', anchor='w',relief=GROOVE)  #时钟
        self.lb4.place(relheight=0.05, relwidth=0.1, relx=0.85, rely=0.935)
#------------------------------------------方法-----------------------------------------------
    def gettim(self):#获取时间 未用
            timestr = time.strftime("%H:%M:%S")  # 获取当前的时间并转化为字符串
            self.lb4.configure(text=timestr)  # 重新设置标签文本
            # tim_str = str(datetime.datetime.now()) + '\n'
            # self.lb4['text'] = tim_str
            self.txt_rx.after(1000, self.gettim)     # 每隔1s调用函数 gettime 自身获取时间 GUI自带的定时函数

    def txt_clr(self):#清空显示
        self.txt_rx.delete(0.0, 'end')  # 清空文本框
        self.txt_tx.delete(0.0, 'end')  # 清空文本框

    def ascii_hex_get(self):#获取单选框状态
        if(self.var_cs.get()):
            return False
        else:
            return True

    def uart_opn_close(self):#打开关闭串口
        if(self.var_bt1.get() == 'Open comport'):
          if(uart_open_close(1,str(self.comb1.get())[0:5],self.comb2.get())==True): #传递下拉框选择的参数 COM号+波特率  【0:5】表示只提取COM号字符
             self.var_bt1.set('Close comport')                             #改变按键内容
             self.txt_rx.insert(0.0, self.comb1.get() + ' Open successfully\r\n')  # 开头插入
          else:
             print("Open failed.")
             messagebox.showinfo('Error','Comport open failed.')
        else:
            uart_open_close(0, 'COM1', 115200) #关闭时参数无效
            self.var_bt1.set('Open Comport')

    def uart_send(self): #发送数据
        send_data = self.txt_tx.get(0.0, 'end').strip()
        if len(send_data)>0:
         if self.ascii_hex_get():    #字符发送
            uart_tx(send_data)
         else:
            send_data = send_data.replace(" ", "").replace("\n", "0A").replace("\r", "0D") #替换空格和回车换行
            if(ISHEX(send_data)==False):
                messagebox.showinfo('Error', 'Hex required')
                return
            uart_tx(send_data,True)
        else:
            messagebox.showwarning("Warning", "send data is empty!")
    def tx_rx_cnt(self,rx=0,tx=0):  #发送接收统计
        global tx_cnt
        global rx_cnt

        rx_cnt += rx
        tx_cnt += tx
        self.lb3['text'] = 'R:'+str(rx_cnt),'S:'+str(tx_cnt)

    def savefiles(self):   #保存日志TXT文本
        try:
            with open('log.txt','a') as file:       #a方式打开 文本追加模式
                file.write(self.txt_rx.get(0.0,'end'))
                messagebox.showinfo('Message', 'Save successfylly')
        except:
            messagebox.showinfo('Error', 'Save failed')


if __name__ == '__main__':
    hide_console()
    print('Start...')
    gui = GUI()
    gui.gettim()  #开启时钟
    gui.root.mainloop()
    UART.close()   #结束关闭 避免下次打开错误
    print('End...')


convert2base64.py

运行它自动生成logo.py

import base64

open_icon = open("port.ico", "rb")  # Image.icon为你要放入的图标
b64str = base64.b64encode(open_icon.read())  # 以base64的格式读出
open_icon.close()
write_data = "img=%s" % b64str
f = open("logo.py", "w+")  # 将上面读出的数据写入到logo.py的img数组中
f.write(write_data)
f.close()

3 Pycharm 工程:

4 打包指令:

pyinstaller -F -i port.ico --name yksscom.exe main.py^C

5 最后效果:

posted @ 2025-06-05 13:34  Stephen_Young  阅读(184)  评论(0)    收藏  举报