[开源分享]一个用于单片机IAP自动升级的串口助手,上位机,使用Python+tkinter制作

news/2024/9/18 3:46:21/文章来源:https://www.cnblogs.com/lizesen/p/18361530

使用Python + tkinter制作。

功能:

这是个给单片机通过串口进行IAP的上位机,与单片机中的BOOT程序配合使用,完成对单片机APP程序的升级。可以完成bin文件的切片,CRC校验(使用Crc32Mpeg2),打包自动发送。

界面如下图所示:

image

  1. 接收区是显示信息的区域,接收和发送的信息都在这显示
  2. 串口配置区域用来配置和打开串口
  3. 命令设置区域,设置上位机发送到单片机开始升级的命令,可以手动点击按钮发送(点一次发1次),可以勾选自动发送(约60ms发一次直到接收到begin),设置CRC一致,设置每次发送bin文件的字节数。
  4. bin文件路径区域,就是选择你要发送到单片机的bin文件

工作流程

  1. 上位机发送(手动或自动)开始命令(图中1.命令)
  2. 单片机接收到开始命令,准备好升级,回复上位机开始(图中2.命令)
  3. 选择bin文件,开始发送
  4. 程序自动对bin文件分成用户设置的大小,然后CRC校验(使用Crc32Mpeg2),并把结果加到发送数据的最后四位
  5. 单片机接收数据取出前边的数据进行CRC校验,并于接收数据的后四位比较,如果一致,返回CRC一致命令(图中3.命令)
  6. 上位机接收到CRC一致命令(图中3.命令),开始下一轮发送,直到发送完毕

注意事项

  1. 成功开始升级命令(图中2.命令),是字符串格式
  2. CRC一致命令(图中3.命令)是16进制格式,两个数表示8位,程序只判断第一个8位,比如在输入框中填303132,但只判断接收到的第一个8位在不在里面
  3. 发送字节数单位是KB
  4. CRC校验使用的是Crc32Mpeg2,如下图所示,这个和STM32H7系列的CRC默认设置一样的,其他系列不知道
    image

源码,两个文件

点击查看代码,GUI.py
from tkinter import *
import tkinter.filedialog
import tkinter.messagebox
import tkinter.ttk as ttk
import serial
import serial.tools.list_ports
import time
import IAP_Send
import threadingglobal ser
lock = threading.Lock()
rx_data = ""#创建窗口
root = Tk()
root.title("IAP发送助手")
root.geometry("750x430")#创建窗格管理
pw = PanedWindow(root, orient="horizontal",showhandle=True)
pw.pack(fill="both", expand=True)#创建框架
fr_receive = LabelFrame(master=root,text="接收区",width=450,height=390)
fr_receive.pack(side="left",anchor="nw",fill="both",padx=5,pady=5)fr_right = Frame(master=root,width=230,height=390)
fr_right.pack(side="right",anchor="ne",fill="both",padx=5,pady=5)fr_port_set = LabelFrame(master=fr_right,text="串口配置",width=250, height=230)
fr_port_set.pack(side="top",anchor="nw",padx=5,pady=5,fill="x")fr_cmd_set = LabelFrame(master=fr_right,text="命令设置",width=220, height=50)
fr_cmd_set.pack(side="top",anchor="se",padx=5,pady=5,fill="x")fr_send = LabelFrame(master=fr_right,text="bin文件路径",width=220, height=100)
fr_send.pack(side="top",anchor="se",padx=5,pady=5,fill="both")#拖动柄
sg = ttk.Sizegrip(master=fr_right)
sg.pack(side="bottom", anchor="se", padx=2, pady=2)#添加框架到窗格管理
pw.add(fr_receive)
pw.add(fr_right)#创建文本区和滚动条
text1 = Text(master=fr_receive,width=45,height=30,font=("宋体",10))
sb = Scrollbar(master=fr_receive,width=20,command=text1.yview)
# 注意顺序,先放scroll,再放text
sb.pack(side="right",fill="y")
text1.pack(side="top",padx=5,pady=5)
text1.config(yscrollcommand=sb.set)#-----------------------------------------右侧 上边 串口配置
lb1=Label(master=fr_port_set,text="串口号")
lb2=Label(master=fr_port_set,text="波特率")
lb3=Label(master=fr_port_set,text="数据位:")
lb4=Label(master=fr_port_set,text="停止位:")
lb5=Label(master=fr_port_set,text="校验位:")
lb1.grid(row=0,column=0,padx=5,pady=5,sticky="w")
lb2.grid(row=1,column=0,padx=5,pady=5,sticky="w")
lb3.grid(row=2,column=0,padx=5,pady=5,sticky="w")
lb4.grid(row=3,column=0,padx=5,pady=5,sticky="w")
lb5.grid(row=4,column=0,padx=5,pady=5,sticky="w")#全局变量
var_btn   = StringVar(value="打开串口")
data_len  = IntVar(value=8)
stop_len  = DoubleVar(value=1)datalen = Entry(master=fr_port_set,textvariable=data_len,width=5)
datalen.grid(row=2,column=1,padx=5,pady=5,sticky="w")
stoplen = Entry(master=fr_port_set,textvariable=stop_len,width=5)
stoplen.grid(row=3,column=1,padx=5,pady=5,sticky="w")
#创建下拉菜单
var_cb1 = StringVar()
cb1 = ttk.Combobox(fr_port_set,textvariable=var_cb1,state="readonly", width=35)
cb1['values'] = serial.tools.list_ports.comports() #列出可用串口
cb1.current(1)  # 设置默认选项
cb1.grid(row=0,column=1,padx=5,pady=5,sticky="w",columnspan=2)var_cb2 = IntVar()
cb2 = ttk.Combobox(fr_port_set,textvariable=var_cb2,state="readonly",width=18)
cb2['values'] = [9600,115200]
cb2.current(1)  # 设置默认选项
cb2.grid(row=1,column=1,padx=5,pady=5,sticky="w")parity_bit = StringVar()
parity_cb = ttk.Combobox(fr_port_set,textvariable=parity_bit,state="readonly",width=9)
parity_cb['values'] = ["无","奇校验","偶校验"]
parity_cb.current(0)  # 设置默认选项
parity_cb.grid(row=4,column=1,padx=5,pady=5,sticky="w")
#-定义函数
def open_port():global serglobal rx_dataif(var_btn.get()=="打开串口"):try:ser=serial.Serial(port=str(cb1.get())[0:5],baudrate=cb2.get(),bytesize=data_len.get(),stopbits=stop_len.get(),timeout=0.1)#传递下拉框选择的参数 COM号+波特率  [0:5]表示只提取COM号字符except:tkinter.messagebox.showinfo('错误','串口打开失败')return#ser.parity   #校验位N-无校验,E-偶校验,O-奇校验if(parity_cb.get()=="无"):ser.parity=serial.PARITY_NONE#无校验elif parity_cb.get()=="奇校验":ser.parity=serial.PARITY_ODD#奇校验elif parity_cb.get()=="偶校验":ser.parity=serial.PARITY_EVEN#偶校验if(ser.is_open):var_btn.set('关闭串口')            #改变按键内容btn1.config(background='red')cb1.config(state="disabled")cb2.config(state="disabled")parity_cb.config(state="disabled")datalen.config(state="disabled")stoplen.config(state="disabled")rx_th=threading.Thread(target=usart_receive,name="serial_receive",daemon=True)rx_th.start()else:tkinter.messagebox.showinfo('错误','串口打开失败')elif(var_btn.get()=="关闭串口"):if(ser.is_open):ser.close()var_btn.set("打开串口")cb1.config(state="normal")cb2.config(state="normal")parity_cb.config(state="normal")datalen.config(state="normal")stoplen.config(state="normal")btn1.config(background=default_color)text1.delete(1.0,END)rx_data=""def usart_receive():global rx_datarx_data=""while True:lock.acquire()if(ser.is_open):rx_buf = ser.read()if len(rx_buf) >0:time.sleep(0.01)rx_buf += ser.readall()  #有延迟但不易出错hex_data=rx_buf.hex().upper()if(len(hex_data)==8):text1.insert(END, hex_data+'\n')rx_data = "no CRC"elif(len(hex_data)>8):str_data = str(rx_buf, encoding='utf-8')text1.insert(END, str_data)text1.insert(END,"\n")if("egin" in str_data):rx_data = "begin ok"else:rx_data = "no begin"elif(len(hex_data)<8):if(hex_data[0:2] in entry_CRC.get().upper()):text1.insert(END, hex_data+'\n')rx_data = "CRC ok"else:rx_data = "no CRC"text1.yview_moveto(1)text1.update()else:rx_data = "no ser"breaklock.release()time.sleep(0.01)lock.release()#创建按钮
btn1 = Button(fr_port_set, textvariable=var_btn,width=10,state="normal",command=open_port)
btn1.grid(row=4,column=2,padx=5,pady=5)
default_color = btn1.cget('background')  # 获取默认背景颜色#----------------------------------------右侧 中间 命令设置
CRC_lb=Label(master=fr_cmd_set,text="CRC一致接收到(HEX)")
CRC_lb.grid(row=1,column=0,padx=5,pady=5,sticky="w")
lb8=Label(master=fr_cmd_set,text="发送字节数(KB)")
lb8.grid(row=1,column=2,padx=5,pady=5,sticky="w")#全局变量
begin_cmd = StringVar(value=":UD")
cmd_CRC_right = StringVar(value="30")
send_size = IntVar(value=1)
auto_send_begincmd = BooleanVar()#创建输入框
entry_begin = Entry(master=fr_cmd_set,textvariable=begin_cmd,width=8)
entry_begin.grid(row=0,column=1,padx=5,pady=5,sticky="w")
entry_CRC = Entry(master=fr_cmd_set,textvariable=cmd_CRC_right,width=8)
entry_CRC.grid(row=1,column=1,padx=5,pady=5,sticky="w")
entry_size = Entry(master=fr_cmd_set,textvariable=send_size,width=5)
entry_size.grid(row=1,column=3,padx=5,pady=5,sticky="w")def send_begin_command():#发送:UD 开始升级命令send_data = entry_begin.get().strip()try:#字符发送if(ser.is_open):  #发送前判断串口状态 避免错误ser.write(send_data.encode('utf-8'))text1.insert(index=END,chars=send_data+"      ")except:#错误返回tkinter.messagebox.showinfo('错误', '发送开始失败,串口没开')def create_auto_send_cmd():global rx_dataif(auto_send_begincmd.get()):try:if(not (ser.is_open)):tkinter.messagebox.showinfo('错误', '串口没打开')returnelse:entry_begin.config(state="readonly")if(rx_data!="begin ok"):text1.delete(1.0,END)th_auto_send = threading.Thread(target=auto_send_cmd,daemon=True)th_auto_send.start()except:tkinter.messagebox.showinfo('错误', '串口没打开')cbtn1.deselect()else:entry_begin.config(state="normal")def auto_send_cmd():global rx_datawhile(rx_data!="begin ok"):if(auto_send_begincmd.get()==False):breakelse:lock.acquire()if(rx_data=="begin ok"):lock.release()breakelse:send_begin_command()rx_data=""lock.release()time.sleep(0.05)# 创建按钮
btn6 = Button(fr_cmd_set,text="开始命令",width=10,command=send_begin_command)
btn6.grid(row=0,column=0,padx=5,pady=5,sticky="w")#创建选择框
cbtn1 = Checkbutton(master=fr_cmd_set,text="自动发送开始命令",variable=auto_send_begincmd,command=create_auto_send_cmd)
cbtn1.grid(row=0,column=2,padx=5,pady=5,columnspan=2,sticky="w")
#---------------------------------------右侧 下边 bin文件路径
#全局变量
path = StringVar(value="")
#创建输入框,选择文件
entry_path = Entry(master=fr_send,textvariable=path,width=40)
entry_path.pack(side="top",padx=5,pady=5,fill='both')def send_data(): #发送数据global rx_datalock.acquire()if(entry_path.get()==""):tkinter.messagebox.showinfo('错误', '文件错误')lock.release()returnlock.release()while(rx_data==""):passlock.acquire()if(rx_data == "begin ok"):#已经开始,发送bin packbin_list = IAP_Send.IAP_CRC(entry_path.get(),send_size.get()*1024)text1.insert(index=END,chars=f"分包、CRC校验完成,发送次数:{len(bin_list)}\n")else:tkinter.messagebox.showinfo('错误', '接收到的begin不对')lock.release()return#发送bin packbin_i = 0retry_num = 0while(True):if(send_bin_pack(bin_list[bin_i])):text1.insert(index=END, chars=f"{bin_i}   ,   ")rx_data=""lock.release()while(rx_data==""):passlock.acquire()if(rx_data=="CRC ok"):bin_i+=1retry_num=0elif(rx_data=="no CRC"):retry_num+=1if(retry_num>5):tkinter.messagebox.showinfo('错误', '发送失败,no CRC * 5')breakelif(rx_data=="no ser"):tkinter.messagebox.showinfo('错误', '接收失败,串口没打开,no ser')breakelse:if(not ser.is_open):tkinter.messagebox.showinfo('错误', '发送失败,串口没打开,no ser')breakretry_num+=1if(retry_num>5):tkinter.messagebox.showinfo('错误', '发送失败*5,重试')breakif(bin_i==len(bin_list)):text1.insert(index=END, chars="发送完成")tkinter.messagebox.showinfo('成功', '发送完成')breaktime.sleep(0.01)lock.release()    def create_thread():global rx_datatry:if(not (ser.is_open)):tkinter.messagebox.showinfo('错误', '串口没打开')returnelse:if(rx_data!="begin ok"):text1.delete(1.0,END)th_send = threading.Thread(target=send_data,name="send_bin_file",daemon=True)th_send.start()except:tkinter.messagebox.showinfo('错误', '串口没打开')def send_bin_pack(bin_pack):try:ser.write(bin_pack)return Trueexcept:#错误返回tkinter.messagebox.showinfo('错误', '发送bin pack失败')return Falsedef selectPath():path1 = tkinter.filedialog.askopenfilename(filetypes=[("bin文件", "*.bin")])if path1:path1 = path1.replace("/", "\\")  # 实际在代码中执行的路径为“\“ 所以替换一下path.set(path1)#创建按钮
btn2 = Button(fr_send, text="选择文件",width=20,command=selectPath)
btn2.pack(side='left',anchor="center",padx=5,pady=5)
btn3 = Button(fr_send, text="开始发送",width=20,command=create_thread)
btn3.pack(side="right",anchor="center",padx=5,pady=5)mainloop()
点击查看代码,IAP_Send.py
import crccheck
import osdef IAP_CRC(filepath, send_size=1024):# send_size : 每次发送的字节数# filepath : bin文件路径 # 'D:\\STM32 Projects\\Power_Control\\Debug Internal\\Power_Control.bin'# 打开bin文件binfile = open(filepath, 'rb') #打开二进制文件file_size = os.path.getsize(filepath) #获得文件大小file_data = binfile.read()binfile.close()# 发送数据的列表,一次一个send_list = []# 发送次数send_num = int(file_size/send_size)+1for i in range(send_num-1):data = file_data[i*send_size:(i+1)*send_size]crc_value = crccheck.crc.Crc32Mpeg2.calcbytes(data)send_list.append(data+crc_value)data = file_data[(send_num-1)*send_size:]crc_value = crccheck.crc.Crc32Mpeg2.calcbytes(data)send_list.append(data+crc_value)return send_list

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/783902.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

资产负债率、净资产收益率如何解读?教你弄懂财务报表的关键

财务报表中包含大量的信息,如果我们在解读财务报表时没有思路,不分重点,就很容易被繁杂的数据弄得头晕眼花。本文就财务报表中的关键指标、资产负债率解读、净资产收益率分析、计算销售复合增长率等几个方面进行介绍,大家可以根据自己的需要进行选择性的学习。 一、这些指标…

程序运行异常: Modulo by zero

用户在使用PbootCMS系统时遇到一个问题,即在网站描述或栏目描述中添加百分号(%)会导致错误。其实, 解决并不复杂。 将模板中标题、描述、关键词用下面的标签替换就可以解决<title>{pboot:pagetitle}</title><meta name="keywords" content="…

java基础private/封装篇

private的使用 private 设置后 想要更改变量只能在此类中更改 若想在其他类中更改和使用需要用get/set方法 get获取变量值 set更改变量值 需自定义 方法可加判断 构造方法的概述构造方法是一种特殊的方法作用:创建对象格式:public class 类名{修饰符 类名(参数){}}修饰符一般…

金蝶云星空解锁时同时解锁序列号

业务背景 公司业务要求,如果检查发现序列号有问题,先锁库不允许出库。 如果已经锁库,此时序列号允许出库,则可以解锁。前置任务:金蝶云星空锁库时同时锁定序列号 - lanrenka - 博客园 (cnblogs.com) 系统现状 即时库存锁库,锁定的是数量,库存-锁库数=可用数,当可用量小…

在Linux下配置java环境

//解压 tar -zxvf jdk-8u401-linux-x64.tar.gz //打开环境变量文件 vim /etc/profile //追加 export JAVA_HOME=/app/toolFile/java/jdk1.8.0_401 ##记得改成自己的jdk安装路径 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export…

【渗透测试】Vulnhub Hackable II

渗透环境 攻击机: IP: 192.168.216.129(Kali) 靶机: IP:192.168.216.131 靶机下载地址:https://www.vulnhub.com/entry/hackable-ii,711/进行渗透 一、 获取端口信息 该虚拟机导入VMware需要在拯救模式中重新配置一下网卡名称,附上教程,不再赘述:https://blog.…

Elasticsearch怎么导出索引数据至CSV

保存Search 打开kibana 选择需要保存的index 定义好时间区间,需要导出的字段等分享CSV下载CSV导出成功在右下角会出现下载链接

PostgreSQL数据库的安装与部署(Linux)

CentOS安装PostgreSQL版本信息:CentOS版本:CentOS-7-x86_64-Minimal-1810PostgreSQL版本: PostgreSQL 10.10, 64-bit第一部分:PostgresSQL的安装 1、安装rpm文件yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.…

2024首届中国Scrum大会成功落幕

这次大会由Scrum.org和Scrum中文网联合主办,以“AI时代下的敏捷”为主题,吸引了来自全国各地的敏捷实践者、企业领导、技术专家和学者,共同探讨敏捷方法在新时代的应用与未来发展。​ ​ 2024年8月17日,首届中国Scrum大会在上海圆满落幕。这次大会由Scrum.org和Scrum中文网…

OV-DINO开放词检测环境安装与推理

​ 引子 开放词检测,之前分享过一篇YOLO-World的文章,感兴趣同学请移步(YOLO-World环境搭建&推理测试_yoloworld 检测-CSDN博客),最近,由中山大学和美团联合提出新的开放域检测方法OV-DINO:基于语言感知选择性融合、统一的开放域检测方法,取得了开放域检测新SOTA!…

jQuery-Mobile-高级教程-全-

jQuery Mobile 高级教程(全)原文:Pro jQuery Mobile 协议:CC BY-NC-SA 4.0零、简介 我们目前正在见证企业和个人构建和分发移动应用的方式的转变。最初的策略是为每个主要平台构建单独的原生应用。然而,团队很快意识到维护多个平台是不可持续的,因为移动团队失去了灵活性…

社区胜于代码,我们在阿帕奇软件基金会亚洲大会聊了聊开源中间件的未来

今年我们在大会第一天的主论坛做了《阿里云中间件持续进化:从分布式应用架构向云原生 AI 应用架构全面升级》的演讲,从云厂商的视角分享了贡献开源、推动社区发展的过程。开发者们在开源社区建立起信任,信任是万事的第一步。 为期 3 天的阿帕奇软件基金会亚洲大会(CoC Asia…