STM32+Python用XMODEM协议升级固件之Python开发升级工具

本章节讲述基于Pycharm环境,使用Python开发XMODEM软件升级工具的过程。阅读本章节需要具备一定的Python基础,故本章节不在讲述Pycharm安装和Python基础语法,而Python的库如何安装,使用百度搜索就可以了。开发的Python版本是V3.8,Windows是Win7。希望阅读完本文章的博友能大概了解如何在Python环境适应PYQT5库设计Windows窗口界面,当然能动手操作效果最佳。

一: 基于PYQT5使用Qt Designer设计界面

PYQT5是一款用于开发图形界面的库,可以方便的开发各种复杂的界面,但XMODEM升级工具的界面比较简单,因此本小节能介绍到的PYQT5的功能不多,希望对初学者起到抛砖引玉的作用。

1.1: 通过Python调出Qt Designer

1.2: 创建新的窗口

Qt Designer出来后,选择Widget,然后点击创建即可。

说明下: Main Window用于创建带有菜单的主窗口,而Widget创建无菜单的主窗口。

创建完成后,把鼠标放在新窗口的边上,会出现箭头,可以通过箭头调整窗口的大小。

把完新窗口保存到文件夹,并命名为Layout.ui。

1.3: 把Layout.ui转换成代码

转成代码后,可以看到,Layout.py和相关的代码

1.4: 添加显示窗口的代码

点击右键运行代码,未能看懂窗口,那是因为还未添加显示代码。

添加显示窗口的代码,代码中绿色部分。

from PyQt5 import QtCore, QtGui, QtWidgets

class UiMainWindow(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(673, 548)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MyMainWindow = QtWidgets.QMainWindow()
    MyUi = UiMainWindow()
    MyUi.setupUi(MyMainWindow)   
    MyMainWindow.show()
    sys.exit(app.exec_())        # app.exec_()是类中的一个循环监听方法

点击运行代码,出现空白的窗口,如下图。

主窗口的左上角的“Form”,可以在QtDesigner修改,修改方法如下:

鼠标随便电解QtDesigner界面中主窗口,在窗口右边,找到windowTitle,修改名称。

1.5: 显示窗口代码与Layout布局代码分离

在设计主窗口界面时,会出现添加新功能,然后同步至Python,再转换成Python代码;如果Layout的代码与显示主窗口的未分离,每次转换成Python代码时,都是把显示主窗口代码弄丢,很时烦人,所以这两部分代码必须要分离。分离方法如下。

1: 新建一个Main.py的文件。

后续所有逻辑功能的代码都在Main.py里实现,当然也可以把各种不同的逻辑分到多个文件。因为XMODEM升级工具比较简单,所以没必要这么做。

1.6: 显示窗口的代码放到Main.py

在Main.py里创建一个新的类,用于实现各种逻辑,在类的初始化里实现窗口显示。

from Layout import Ui_MainWindow
from PyQt5 import QtWidgets,QtGui,QtCore

class MyMainWindow(QtWidgets.QMainWindow, Ui_MainWindow):    # 从Layout文件中,继承Ui_MainWindow类

    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)           # 使用父类初始化
        self.setupUi(self)                                   # 初始化QDialog界面
        #self.myWindowInit()                                 # 所有窗口初始化
        return

# *************************************************************************************


# 逻辑代码区


# *************************************************************************************

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    myUiWindow = MyMainWindow()
    myUiWindow.show()
    sys.exit(app.exec_())                       # app.exec_()是类中的一个循环监听方法

同时,需要把Layout.py里的显示代码注释掉,否则会报错。

刚才修改了主窗口名称为“XMODEM”,保存后,在Pychar重新转换成python代码后,上图注释部分已消失,运行“Main.py”,出现同样的窗口。

1.7: 设计自己喜欢的窗口界面

Qt Designer设计窗口界面非常友好,需要什么样的功能,直接把鼠标按住左边的功能框,拖到右边的窗口即可。在把功能框拖到右边时,Qt Designer直接建立一个类,注意在Python中,所有的信息都是以类的形式存在,小到一个字节,大到主窗口。

XMODEM需要以下功能框:

1: 标签Label,标识各种信息,对应Qt Designer左边窗口的Display Widgets中的Label。如果有多的标签,将按序号先后顺序出现,下图。

2: 选择框Combo Box,对应Qt Designer左边窗口的Input Widgets中的Combo Box。如果有多的选择框,将按序号先后顺序出现,下图。

3: 按钮Button,对应Qt Designer左边窗口的Buttons中的Push Button。如果有多的按钮,将按序号先后顺序出现,下图。

4: 进度显示条Progress Bar,对应Qt Designer左边窗口的Display Widgets中的Progress Bar。如果有多的进度显示条,将按序号先后顺序出现,下图。

5: 单行输入框Line Edit,对应Qt Designer左边窗口的Input Widgets中的Line Edit。如果有多的单行输入框,将按序号先后顺序出现,下图。

6: 多行文本显示框Text Browser,对应Qt Designer左边窗口的Input Widgets中的Text Browser。如果有多的单行输入框,将按序号先后顺序出现,下图

各个功能框可以行对齐或者列对齐等方式对齐,还可以把各个功能框合并,合并后的功能框再对齐。

所有的功能框都可以设定默认值,Python代码运行后,功能框的数值都是以设定的默认值出现。

至此,XMODEM所有的功能框添加和设计完成。

7: 保存窗口,设计好的界面转换成python代码,Layout.py出现新的代码,运行Main.py,结果如下。

1.8: 显示窗口的代码放到Main.py

Layout.py源代码:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(896, 579)
        self.layoutWidget_2 = QtWidgets.QWidget(MainWindow)
        self.layoutWidget_2.setGeometry(QtCore.QRect(430, 10, 411, 25))
        self.layoutWidget_2.setObjectName("layoutWidget_2")
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.layoutWidget_2)
        self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.pushButton_3 = QtWidgets.QPushButton(self.layoutWidget_2)
        font = QtGui.QFont()
        font.setPointSize(11)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setFocusPolicy(QtCore.Qt.NoFocus)
        self.pushButton_3.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.pushButton_3.setObjectName("pushButton_3")
        self.horizontalLayout_4.addWidget(self.pushButton_3)
        self.pushButton_4 = QtWidgets.QPushButton(self.layoutWidget_2)
        font = QtGui.QFont()
        font.setPointSize(11)
        self.pushButton_4.setFont(font)
        self.pushButton_4.setFocusPolicy(QtCore.Qt.NoFocus)
        self.pushButton_4.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.pushButton_4.setObjectName("pushButton_4")
        self.horizontalLayout_4.addWidget(self.pushButton_4)
        self.progressBar = QtWidgets.QProgressBar(self.layoutWidget_2)
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.horizontalLayout_4.addWidget(self.progressBar)
        self.layoutWidget_3 = QtWidgets.QWidget(MainWindow)
        self.layoutWidget_3.setGeometry(QtCore.QRect(10, 50, 831, 25))
        self.layoutWidget_3.setObjectName("layoutWidget_3")
        self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.layoutWidget_3)
        self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.lineEdit = QtWidgets.QLineEdit(self.layoutWidget_3)
        self.lineEdit.setObjectName("lineEdit")
        self.horizontalLayout_6.addWidget(self.lineEdit)
        self.pushButton_2 = QtWidgets.QPushButton(self.layoutWidget_3)
        font = QtGui.QFont()
        font.setPointSize(11)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout_6.addWidget(self.pushButton_2)
        self.pushButton_5 = QtWidgets.QPushButton(self.layoutWidget_3)
        font = QtGui.QFont()
        font.setPointSize(11)
        self.pushButton_5.setFont(font)
        self.pushButton_5.setFocusPolicy(QtCore.Qt.NoFocus)
        self.pushButton_5.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.pushButton_5.setObjectName("pushButton_5")
        self.horizontalLayout_6.addWidget(self.pushButton_5)
        self.textBrowser = QtWidgets.QTextBrowser(MainWindow)
        self.textBrowser.setGeometry(QtCore.QRect(10, 80, 831, 461))
        self.textBrowser.setObjectName("textBrowser")
        self.layoutWidget = QtWidgets.QWidget(MainWindow)
        self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 301, 27))
        self.layoutWidget.setObjectName("layoutWidget")
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.layoutWidget)
        self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.label = QtWidgets.QLabel(self.layoutWidget)
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.horizontalLayout_2.addWidget(self.label)
        self.comboBox = QtWidgets.QComboBox(self.layoutWidget)
        self.comboBox.setObjectName("comboBox")
        self.horizontalLayout_2.addWidget(self.comboBox)
        self.horizontalLayout_3.addLayout(self.horizontalLayout_2)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton = QtWidgets.QPushButton(self.layoutWidget)
        font = QtGui.QFont()
        font.setPointSize(11)
        self.pushButton.setFont(font)
        self.pushButton.setFocusPolicy(QtCore.Qt.NoFocus)
        self.pushButton.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        self.comboBox_2 = QtWidgets.QComboBox(self.layoutWidget)
        self.comboBox_2.setObjectName("comboBox_2")
        self.horizontalLayout.addWidget(self.comboBox_2)
        self.horizontalLayout_3.addLayout(self.horizontalLayout)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "XMODEM"))
        self.pushButton_3.setText(_translate("MainWindow", "打开文件"))
        self.pushButton_4.setText(_translate("MainWindow", "升级固件"))
        self.pushButton_2.setText(_translate("MainWindow", "发送数据(Hex)"))
        self.pushButton_5.setText(_translate("MainWindow", "清除窗口"))
        self.label.setText(_translate("MainWindow", "波特率:"))
        self.pushButton.setText(_translate("MainWindow", "打开串口"))

Main.py源代码(未添加逻辑代码):

from Layout import Ui_MainWindow
from PyQt5 import QtWidgets,QtGui,QtCore

class MyMainWindow(QtWidgets.QMainWindow, Ui_MainWindow):    # 从Layout文件中,继承Ui_MainWindow类

    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)           # 使用父类初始化
        self.setupUi(self)                                   # 初始化QDialog界面
        #self.myWindowInit()                                 # 所有窗口初始化
        return


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    myUiWindow = MyMainWindow()
    myUiWindow.show()
    sys.exit(app.exec_())                       # app.exec_()是类中的一个循环监听方法

二: 添加XMODEM逻辑代码

2.1: 定义各种参数和变量

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMessageBox,QFileDialog
from Layout import Ui_MainWindow


import  re
import  os
import  time
import  struct
import  binascii
import  threading
import  serial  as MySerial
# ****************************************** 定义常量 ******************************************************************
global BIN_FILE_PACK_SIZE                       # XMODEM数据包真实数据长度,128字节
BIN_FILE_PACK_SIZE = 128

global  UPDATE_FIRMWARE_STATUS_NULL             # 固件更新的状态,空状态
UPDATE_FIRMWARE_STATUS_NULL = 0

global  UPDATE_FIRMWARE_STATUS_WAIT_C           # 等待字符“C”状态
UPDATE_FIRMWARE_STATUS_WAIT_C = 1

global  UPDATE_FIRMWARE_STATUS_RECEIVED_C       # 接收字符“C”状态
UPDATE_FIRMWARE_STATUS_RECEIVED_C = 2

global  UPDATE_FIRMWARE_STATUS_SEND_DATA        # 发送数据状态
UPDATE_FIRMWARE_STATUS_SEND_DATA = 3

global  UPDATE_FIRMWARE_STATUS_WAIT_ACK         # 等待ACK状态
UPDATE_FIRMWARE_STATUS_WAIT_ACK = 4

global  UPDATE_FIRMWARE_STATUS_RECEIVED_ACK     # 接收到ACK状态
UPDATE_FIRMWARE_STATUS_RECEIVED_ACK = 5

global  UPDATE_FIRMWARE_STATUS_RECEIVED_NACK    # 接收到NACK状态
UPDATE_FIRMWARE_STATUS_RECEIVED_NACK = 6

global  UPDATE_FIRMWARE_STATUS_SEND_EOT         # 发送EOT状态
UPDATE_FIRMWARE_STATUS_SEND_EOT = 7

global  UPDATE_FIRMWARE_TIME_OUT                # 固件更新超时
UPDATE_FIRMWARE_TIME_OUT = 3

global  XMODEM_NULL
XMODEM_NULL = '0x00'

global  XMODEM_SOH
XMODEM_SOH = '0x01'
global  XMODEM_EOT
XMODEM_EOT = '0x04'
global  XMODEM_ACK
XMODEM_ACK = '0x06'
global  XMODEM_NACK
XMODEM_NACK = '0x15'
global  XMODEM_ETB
XMODEM_ETB = '0x17'
global  XMODEM_CAN
XMODEM_CAN = '0x18'
global  XMODEM_C
XMODEM_C = '0x43'
global  XMODEM_DATA_1A
XMODEM_END_DATA = '0x1A'

# ********************************************* 定义变量 ***************************************************************
global  UpdateFirmwareStatus
UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_NULL

global ComPortNumber
ComPortNumber = 'COM0'                                         # 串口端口号

global ComBaudRate
ComBaudRate = 9600                                             # 波特率全局变量

global IsBinFileReady
IsBinFileReady = False

global IsComOpenSuccess
IsComOpenSuccess = False

global  ReceiveComCCount
ReceiveComCCount = 0

global  ReceiveComACKCount
ReceiveComACKCount = 0

global  ReceiveComNACKCount
ReceiveComNACKCount = 0

global SystemTime
SystemTime = 0
# ******************************************************************************************************************

2.2: 功能框初始化

def myWindowInit(self):
    Baudrate = ['9600','19200','38400','57600','15200']
    self.comboBox.addItems(Baudrate)
    self.comboBox.currentIndexChanged.connect(self.GetComBaudRate)

    ComPortNumber = ['COM0', 'COM1', 'COM2', 'COM3', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9']
    self.comboBox_2.addItems(ComPortNumber)
    self.comboBox_2.currentIndexChanged.connect(self.GetComNumber)  # 关联到GetComNumber()函数

    self.pushButton.clicked.connect(self.OpenComPortButton)  # 点击按钮关联到OpenComPortButton()函数

    self.pushButton_2.clicked.connect(self.SendComData)  # 点击按钮关联到SendComData()函数
    self.pushButton_3.clicked.connect(self.OpenFirmwareFile)  # 点击按钮关联到OpenFirmwareFile()函数
    self.pushButton_4.clicked.connect(self.UpdateFirmware)  # 点击按钮关联到UpdateFirmware()函数
    self.pushButton_5.clicked.connect(self.ClearTextBrowser)  # 点击按钮关联到ClearTextBrowser()函数
    self.progressBar.setProperty("value", 10)  # 设置状态进度条初始值为10

    self.lineEdit.setText('AA FF 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 BB 92 98') # 单行编辑框默认值
    return

2.3: 逻辑核心代码

# *********************************************获取串口波特率函数******************************************************

2.3.1: 获取串口波特率函数

def GetComBaudRate(self):        //获取串口波特率
    global ComBaudRate
    ComBaudrateText= self.comboBox.currentText()
    if  ComBaudrateText == '9600':
        ComBaudRate = 9600
    elif ComBaudrateText == '19200':
        ComBaudRate = 19200
    elif ComBaudrateText == '38400':
        ComBaudRate = 38400
    elif ComBaudrateText == '57600':
        ComBaudRate = 57600
    elif ComBaudrateText == '115200':
        ComBaudRate = 115200
    return

# *****************************************获取串口号函数*************************************************

2.3.2: 获取串口号函数

def GetComNumber(self):
    global ComPortNumber
    ComPortNumber = self.comboBox_2.currentText()
    return

2.3.3: 打开串口函数

def OpenComPortButton(self):        //打开串口
    global ComBaudRate
    global ComPortNumber
    global IsComOpenSuccess
    global XmodemSerial

    if (IsComOpenSuccess == True):              # 处于打开状,则点击时关闭串口
        IsComOpenSuccess = False                # 置打开串口标记为False
        XmodemSerial.close()                    # 关闭串口
        self.pushButton.setText("打开串口")      # 按钮字符改变
        print("Close COM success!")
        return

    try:
        XmodemSerial = MySerial.Serial()                         # winsows系统使用com口连接串行口
        XmodemSerial.port = ComPortNumber                        # COM口号
        XmodemSerial.baudrate = ComBaudRate                      # 波特率
        XmodemSerial.bytesize = MySerial.EIGHTBITS               # 数据位8位
        XmodemSerial.stopbits = MySerial.STOPBITS_ONE            # 1位停止位
        XmodemSerial.parity = MySerial.PARITY_NONE               # 无奇偶校验
        XmodemSerial.timeout = 0.001                             # 实时监控串口
        XmodemSerial.open()                                      # 打开串口

        if (XmodemSerial.is_open == True):
            print("Open COM success!")
            IsComOpenSuccess = True

            self.pushButton.setText("关闭串口")
            thread = threading.Thread(target=ReadComPortData, args=(self,))
            thread.daemon = True  # 设置为守护线程,主线程结束时此线程也会结束
            thread.start()
    except:
        QMessageBox.information(self, '提示', '请正确配置端口参数', QMessageBox.Ok)
        IsComOpenSuccess = False
    return


2.3.4: 串口发送数据函数

def SendComData(self):                  # 发送数据按钮
    global XmodemSerial
    global IsComOpenSuccess

    if( IsComOpenSuccess == False ):
        QMessageBox.information(self, '提示', '请打开串口', QMessageBox.Ok)
        return

    LineEditData = self.lineEdit.text()
    HexData = bytes.fromhex( LineEditData )               # 转换成十六进制数据
    XmodemSerial.write( HexData )
    XmodemSerial.flushInput()

    self.textBrowser.insertPlainText("发→□")
    self.textBrowser.insertPlainText( LineEditData )
    self.textBrowser.insertPlainText("\n")
    return

2.3.5:打开文件函数

def OpenFirmwareFile(self):                             # 打开文件按钮
    from PyQt5.QtWidgets import QFileDialog
    FilePath =  QFileDialog.getOpenFileName(None)       # 读取文件路径
    if ( FilePath == False ):
        return
    FilePath = FilePath[0:1]
    FilePathTemp = str(FilePath)                        # tuple类型转成String类型
    FilePathTemp = FilePathTemp[2:-3]                   # 去除头和尾的多余字符

    global BinFileSize
    BinFileSize = os.path.getsize(FilePathTemp)         # 获取文件大小
    print('File size:', end=' ')
    # print(BinFileSize)

    BinFile = open(FilePathTemp, "rb")                  # 通过路径打开文件
    global  BinFileBuffer                               # 声明BinFileBuffer为全局变量
    BinFileBuffer = ()                                  # 创建空元组Tupple类型,数据是bytes类型,不可修改
    BinFileBuffer = BinFile.read()                      # 读取文件内容到缓存
    global  IsBinFileReady
    IsBinFileReady = True

    i=0
    while i < BinFileSize:
        t = FormatHex(BinFileBuffer[i])                 # 转换成十六进制
        self.textBrowser.insertPlainText( t +' ')       # 在MyText多行文本框尾端插入字符
        i = i+1                                         # 准备输出下个值
        if i % 43 == 0:
            self.textBrowser.insertPlainText('\n')      # 在 TextBrowser 多行文本框显示内容

    self.textBrowser.insertPlainText("\n")
    BinFile.close()                                     # 关闭文件

    return

2.3.6: 升级固件函数

def UpdateFirmware(self):                           # 固件升级按钮
    global IsBinFileReady                           # 文件是否打开
    global IsComOpenSuccess                         # 串口是否打开

    global XmodemSerial
    global XMODEM_SOH                               # XMODEM的包起始字符
    global XMODEM_C
    global XMODEM_ACK
    global XMODEM_NACK
    global XMODEM_EOT                               # XMODEM的接收字符
    global XMODEM_END_DATA

    global BinFileSize                              # 文件大小
    global BinFileBuffer                            # 文件内容缓存区
    global BinFilePackLength                        # 包数量
    global BIN_FILE_PACK_SIZE                       # 包的数据大小,XMODEM 是128字节

    global ReceiveComACKCount                       # 接收到ACK的数量
    global ReceiveComNACKCount                      # 接收到NACK的数量

    global UpdateFirmwareStatus
    global UPDATE_FIRMWARE_STATUS_NULL              # XMODEM空状态
    global UPDATE_FIRMWARE_STATUS_WAIT_C            # XMODEM等待接收连接字符“C”状态
    global UPDATE_FIRMWARE_STATUS_RECEIVED_C        # XMODEM接收到字符“C”状态
    global UPDATE_FIRMWARE_STATUS_SEND_DATA         # XMODEM发送数据状态
    global UPDATE_FIRMWARE_STATUS_WAIT_ACK          # XMODEM等待下位机ACK状态
    global UPDATE_FIRMWARE_STATUS_RECEIVED_ACK      # XMODEM接收到ACK状态
    global UPDATE_FIRMWARE_STATUS_RECEIVED_NACK     # XMODM接收到NACK状态
    global UPDATE_FIRMWARE_STATUS_SEND_EOT          # XMODEM发送结束符状态
    global UPDATE_FIRMWARE_TIME_OUT

    if( IsBinFileReady == False ):
        QMessageBox.information(self, '提示', '请打开文件', QMessageBox.Ok)
        return

    if (IsComOpenSuccess == False):         # 串口未打开,不能升级
        QMessageBox.information(self, '提示', '请打开串口', QMessageBox.Ok)
        return

    if (BinFileSize % BIN_FILE_PACK_SIZE == 0):                     # 文件刚好是整数包
        BinFilePackLength = BinFileSize // BIN_FILE_PACK_SIZE       # 整数包大小
    else:
        BinFilePackLength = BinFileSize // BIN_FILE_PACK_SIZE + 1   # 整数包大小+1

    self.progressBar.setProperty("value", 0)                        # 设置升级进度条

    ReceiveComACKCount = 0
    ReceiveComNACKCount = 0
    UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_WAIT_C

    # print( BinFilePackLength )

    SystemTime = time.time()

    while( True ):
        try:
            ComData = XmodemSerial.readall()                                    # 读取串口数据
            if ( len(ComData) ):
                ComDataHex = binascii.hexlify(ComData).decode('utf-8')          # 解码成Hex格式数据
                XmodemSerial.flushInput()                                       # 清空串口输入缓存

                if ( (ComDataHex == XMODEM_C[2:] ) and ( UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_WAIT_C) ):
                    UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_RECEIVED_C    # 收到字符‘C’,进入收到C状态

                elif ( ComDataHex == XMODEM_ACK[2:] ):
                    UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_RECEIVED_ACK  # 收到'ACK'响应,进入收到ACK状态

                elif ( ComDataHex == XMODEM_NACK[2:] ):
                    UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_RECEIVED_NACK  # 收到'NACK'响应,进入收到NACK状态

                else:                                                            # 收到其它无效字符,进入NULL状态
                    pass
        except:
            pass

        if (UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_NULL):           # XMODEM空状态
            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_NULL              # 继续空状态

        elif( UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_WAIT_C ):      # 等待接收'C'字符状态
            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_WAIT_C            # 继续等待
            if( (time.time() - SystemTime) >  UPDATE_FIRMWARE_TIME_OUT):    # 升级固件超时
                QMessageBox.information(self, '提示', '固件升级超时', QMessageBox.Ok)
                break

        elif( UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_RECEIVED_C):   # 接收到'C'字符状态
            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_SEND_DATA         # 转入发送数据状态

        elif (UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_SEND_DATA):    # 发送XMODEM数据
            Position = ReceiveComACKCount * BIN_FILE_PACK_SIZE              # 数据包数据的起始位置
            if( ReceiveComACKCount < (BinFilePackLength-1) ):
                DataLength = BIN_FILE_PACK_SIZE                             # 完整一个数据包的数据长度128字节
            else:
                if( BinFileSize % BIN_FILE_PACK_SIZE == 0 ):                # 文件刚好是128字节的整数倍
                    DataLength = BIN_FILE_PACK_SIZE                         # 完整一个数据包的数据长度128字节
                else:
                    DataLength = BinFileSize%BIN_FILE_PACK_SIZE             # 求余得出尾数据包的数据长度

            PackSOH = struct.pack('>i', 0x01)                               # 包起始符
            PackOrder = struct.pack('>i', (ReceiveComACKCount + 1))         # 包序号,真正地数据包从1开始
            PackOrderInverse = struct.pack('>i', ~(ReceiveComACKCount+1))   # 包序号反码

            if( DataLength < BIN_FILE_PACK_SIZE ):
                DataTupple1 = BinFileBuffer[Position:(Position+DataLength)]
                DataTupple2 = bytes.fromhex('1A')*(BIN_FILE_PACK_SIZE-DataLength)   # 不足128字节,补0x1A数据
                DataTemp = DataTupple1 + DataTupple2                                # 两个元组合并
            else:
                DataTemp = BinFileBuffer[Position:(Position + DataLength)]          # 取出数据

            DataCRCResult = DataCRC(DataTemp, BIN_FILE_PACK_SIZE)                   # 数据作CRC校验
            DataCRCBytes = struct.pack('>i', DataCRCResult)                         # 转换Python数据格式为C语言数据格式
            NewData = PackSOH[3:] + PackOrder[3:] + PackOrderInverse[3:] + DataTemp + DataCRCBytes[2:]
            XmodemSerial.write(NewData)                                         # 发送数据

            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_WAIT_ACK              # 转入等待ACK状态

        elif (UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_WAIT_ACK):         # 等待接收ACK状态
            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_WAIT_ACK              # 转入等待ACK状态


        elif (UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_RECEIVED_ACK):     # 接收到ACK响应
            ReceiveComACKCount = ReceiveComACKCount + 1                         # 准备发送下一个数据包
            UpdateStatusTemp = (int)((ReceiveComACKCount/BinFilePackLength)*100)     # 进度转换为百分比
            self.progressBar.setProperty("value", UpdateStatusTemp)             # 设置升级进度条

            if (ReceiveComACKCount == BinFilePackLength):                       # 数据包发送完成
                UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_SEND_EOT          # 数据发送完成,转入结束状态
            else:
                UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_SEND_DATA         # 转入发送数据状态

        elif (UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_RECEIVED_NACK):    # 接收到NACK响应
            ReceiveComNACKCount = ReceiveComNACKCount + 1                       # 统计接收到NACK的数量
            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_SEND_DATA             # 准备重发数据

        elif (UpdateFirmwareStatus == UPDATE_FIRMWARE_STATUS_SEND_EOT):         # 发送EOT结束符
            XmodemSerial.write(bytes.fromhex(XMODEM_EOT[2:]))
            XmodemSerial.flushOutput()                                          # 清空串口输出缓存
            UpdateFirmwareStatus = UPDATE_FIRMWARE_STATUS_NULL

            self.textBrowser.insertPlainText("Update Firmware Success!")
            self.textBrowser.insertPlainText("\n")
            QMessageBox.information(self, '提示', '固件升级成功', QMessageBox.Ok)
            break
        else:
            QMessageBox.information(self, '提示', '固件升级失败,请重新升级', QMessageBox.Ok)
            pass
    return

2.3.7: 清除文本显示框内容函数

def ClearTextBrowser(self):                           # 清除多行文本显示按钮
    self.textBrowser.clear()
    return

2.3.8: 读取串口数据函数

def ReadComPortData( self ):
    global XmodemSerial
    if (XmodemSerial.is_open == False):
        return

    while(True):
        if( XmodemSerial.in_waiting > 0):
            ComData = XmodemSerial.readall()
            XmodemSerial.flushInput()
            str_data = binascii.hexlify(ComData).decode('utf-8')
            str_data = str_data.upper()
            DataTemp = re.findall(".{2}",str_data)
            NewData = " ".join(DataTemp)
            self.textBrowser.insertPlainText( "收←■" )
            self.textBrowser.insertPlainText( NewData )
            self.textBrowser.insertPlainText("\n")
    return

2.3.9: 常量转换成十六进制函数

def FormatHex(HexNumber):
    hex_str = hex(HexNumber).upper()[2:]                  # 输出十六进制大写字母
    if len(hex_str) < 2:
        return '0{}'.format(hex_str)
    else:
        return hex_str
    return

2.3.10: 数据作CRC运算函数

def DataCRC( NewData,Length ):                          # CRC校验
    i = 0
    CRCData = 0x0000
    while(True):
        CRCData = (CRCData^((NewData[i]<<8 )&0xFFFF)&0xFFFF)    # 此处的0xFFFF是必要的的,因为python无2字节数据
        j = 8
        while(True):
            if( CRCData & 0x8000 ):
                CRCData = ((CRCData<<1)^0x1021)&0xFFFF
            else:
                CRCData = ((CRCData)<<1)&0xFFFF
            j -= 1
            if( j==0 ):
                break
        i += 1
        if( i==Length ):
            break
    return CRCData

2.3.11: 窗口显示代码

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    myUiWindow = MyMainWindow()
    myUiWindow.show()
    sys.exit(app.exec_())                       # app.exec_()是类中的一个循环监听方法

至此,XMODEM的Python代码完成,以上代码可以直接运行,运行的结果如下:

关于XMODEM升级工具的测试,请查看这篇文章第三篇:STM32+Python用XMODEM协议升级固件之STM32固件开发-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hhccxw

你的肯定是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值