用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 最后效果:


浙公网安备 33010602011771号