STM32程序烧录软件设计

      本次STM32程序烧录软件是基于本人的上一篇博客所设计的BootLoader实现的,因为实际使用过程中,我们不能说每次下载程序都打开一个Python工程来进行下载,到别的电脑上也不一定有Python的环境,最好的方式是能够做个下载助手,这样更加的使用和友好。 因为上一篇博客中使用的TCP客户端是用Python写的,Python也能用来开发界面软件,所以程序烧录软件用了PyQt5来做,

      PyQt5可以简单的理解为Python和QT的融合,QT是非常流行的功能强大的界面开发软件,PyQt几乎拥有QT中所有的功能,而且函数形式也是大同小异,使用PyQt开发的感觉总体上来说比用QT开发爽很多,因为PyQt中可以使用Python的各种API,有时候同一种功能的实现既可以用Python API来实现,也可以用QT的API来实现,哪种实现起来更爽就用哪种,因为以前学了一段时间的深度学习,用的是Python语言,如果使用PyQt的话,就可以把深度学习等等看起来比较牛逼的应用结合QT一起开发。

      一、烧录软件界面设计

      QT Designer的具体使用方法就不多说了,首先打开Qt designer创建新的QT窗体工程,目前只实现最基本的程序下载功能,界面简简单单的不用太花里胡哨。程序烧录助手的界面如下(随便拖一拖控件就完成了):

 

    二、将ui文件转化为py文件

    打开cmd命令行,进入到ui文件所在的目录,然后输入命令pyuic5 -o mainwindow.py mainwindow.ui

命令执行正确的话就可以在ui文件同级目录看到生成的mainwindow.py文件了,接下来我把py文件的名字改成了ISPwindow.py,当然不改也行,生成的py文件就可以供python调用生成界面了。

 

三、如何显示界面

刚刚将ui界面文件转换成了py文件,现在我们要将这个界面显示出来,简单写几行代码就可以了,我使用的Python编译器是PyCharm,先看看ISPwindow.py里面的代码,这是根据ui文件自动生成的。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'ISPwindow.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(525, 306)
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        self.centralWidget.setObjectName("centralWidget")
        self.btn_download = QtWidgets.QPushButton(self.centralWidget)
        self.btn_download.setGeometry(QtCore.QRect(10, 100, 131, 51))
        self.btn_download.setObjectName("btn_download")
        self.edt_ipAddress = QtWidgets.QLineEdit(self.centralWidget)
        self.edt_ipAddress.setGeometry(QtCore.QRect(30, 10, 151, 21))
        self.edt_ipAddress.setObjectName("edt_ipAddress")
        self.label = QtWidgets.QLabel(self.centralWidget)
        self.label.setGeometry(QtCore.QRect(10, 10, 31, 16))
        self.label.setObjectName("label")
        self.edt_port = QtWidgets.QLineEdit(self.centralWidget)
        self.edt_port.setGeometry(QtCore.QRect(220, 10, 61, 21))
        self.edt_port.setObjectName("edt_port")
        self.label_2 = QtWidgets.QLabel(self.centralWidget)
        self.label_2.setGeometry(QtCore.QRect(190, 10, 31, 16))
        self.label_2.setObjectName("label_2")
        self.btn_selectFile = QtWidgets.QPushButton(self.centralWidget)
        self.btn_selectFile.setGeometry(QtCore.QRect(10, 40, 131, 51))
        self.btn_selectFile.setObjectName("btn_selectFile")
        self.btn_tcpConnect = QtWidgets.QPushButton(self.centralWidget)
        self.btn_tcpConnect.setGeometry(QtCore.QRect(290, 10, 71, 21))
        self.btn_tcpConnect.setObjectName("btn_tcpConnect")
        self.pgb_downloadProgress = QtWidgets.QProgressBar(self.centralWidget)
        self.pgb_downloadProgress.setGeometry(QtCore.QRect(150, 110, 371, 23))
        self.pgb_downloadProgress.setProperty("value", 24)
        self.pgb_downloadProgress.setObjectName("pgb_downloadProgress")
        self.edt_filePath = QtWidgets.QLineEdit(self.centralWidget)
        self.edt_filePath.setGeometry(QtCore.QRect(150, 50, 361, 21))
        self.edt_filePath.setObjectName("edt_filePath")
        self.edt_downloadMsg = QtWidgets.QTextEdit(self.centralWidget)
        self.edt_downloadMsg.setEnabled(False)
        self.edt_downloadMsg.setGeometry(QtCore.QRect(10, 180, 511, 121))
        self.edt_downloadMsg.setObjectName("edt_downloadMsg")
        self.label_3 = QtWidgets.QLabel(self.centralWidget)
        self.label_3.setGeometry(QtCore.QRect(10, 160, 91, 16))
        self.label_3.setObjectName("label_3")
        # MainWindow.setCentralWidget(self.centralWidget)

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "程序烧录助手V1.0"))
        self.btn_download.setText(_translate("MainWindow", "下载"))
        self.edt_ipAddress.setText(_translate("MainWindow", "192.168.1.41"))
        self.label.setText(_translate("MainWindow", "IP"))
        self.edt_port.setText(_translate("MainWindow", "5198"))
        self.label_2.setText(_translate("MainWindow", "端口"))
        self.btn_selectFile.setText(_translate("MainWindow", "选择文件"))
        self.btn_tcpConnect.setText(_translate("MainWindow", "连接"))
        self.edt_downloadMsg.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
        self.label_3.setText(_translate("MainWindow", "下载信息"))

然后创建一个新的py文件 ISPdownloader.py,内容如下:

from ISPwindow import Ui_MainWindow
from PyQt5.QtWidgets import *

class Demo(QWidget, Ui_MainWindow):
    def __init__(self):
        super(Demo, self).__init__()
        self.setupUi(self)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

 这样就可以把界面显示出来了,效果如下,其实和Qt designer中的效果是一样的。

 

 四、编写功能实现

 在我的上一篇博客里面已经实现了使用TCP通信烧写程序的基本功能了,现在只需要做做界面上控件的功能映射就可以了,还是原来的套路,实现代码如下:

import sys
from socket import *
from ISPwindow import Ui_MainWindow
from PyQt5.QtWidgets import *
import os
import ctypes
import traceback

class Demo(QWidget, Ui_MainWindow):
    def __init__(self):
        super(Demo, self).__init__()
        self.setupUi(self)

        self.btn_selectFile.clicked.connect(self.slot_openFile)
        self.btn_tcpConnect.clicked.connect(self.slot_tcpConnect)
        self.btn_download.clicked.connect(self.slot_downloadFile)

        self.pgb_downloadProgress.setValue(0)

        self.edt_downloadMsg.setEnabled(True)
        self.edt_downloadMsg.setReadOnly(True)
        self.edt_filePath.setEnabled(True)
        self.edt_filePath.setReadOnly(True)
        self.edt_downloadMsg.setText("")

        self.filePath = ""
        self.tcpHost = 'localhost'
        self.tcpRecBufSize = 2048
        self.tcpAddr = ""

        self.isTcpConnected = False

        self.maxFIleSize = 384*1024
        self.fileSize = 0
        self.fileCRC = 0

    def slot_openFile(self):
        filePath, _ = QFileDialog.getOpenFileName(self, "选择下载文件", os.getcwd(), "Bin File (*.bin)")

        if filePath == "":
            if self.filePath == "":
                QMessageBox.warning(self, "文件选择错误", "没有选择下载文件,请重新选择!", QMessageBox.Ok, QMessageBox.Ok)
                return
            else:
                filePath = self.filePath

        size = os.path.getsize(filePath)

        if size > self.maxFIleSize:
            QMessageBox.warning(self, "文件选择错误", "下载文件太大了,最大为384KB,请重新选择!", QMessageBox.Ok, QMessageBox.Ok)
            return

        self.fileSize = size
        self.filePath = filePath
        self.edt_filePath.setText(self.filePath)

    def slot_downloadFile(self):
        try:
            self.DownloadFile()
        except Exception as e:
            self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + '\r\n下载出错')
            traceback.print_exc()

    def slot_tcpConnect(self):
        if self.isTcpConnected == False:
            try:
                self.tcpSock = socket(AF_INET, SOCK_STREAM)
                self.tcpAddr = (self.edt_ipAddress.text(), int(self.edt_port.text()))
                self.tcpSock.connect(self.tcpAddr)

                self.btn_tcpConnect.setText("断开")
                self.isTcpConnected = True
            except Exception as e:
                self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + '\r\n连接失败!')
                traceback.print_exc()
                return

            self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + '\r\n连接成功!')
        else:
            self.tcpSock.close()
            self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + '\r\n断开成功!')
            self.btn_tcpConnect.setText("连接")
            self.isTcpConnected = False


    def CalculateFileCRC(self, filePath):
        file = open(filePath, "rb")

        buf = file.read(4)
        crc = 0

        while len(buf) > 0:
            crc = crc ^ (int.from_bytes(buf, byteorder='little', signed=False))
            buf = file.read(4)

        file.close()

        return crc

    def DownloadFile(self):

        if self.filePath == "":
            QMessageBox.warning(self, "提示", "请选择下载文件!", QMessageBox.Ok, QMessageBox.Ok)
            return

        if self.isTcpConnected == False:
            QMessageBox.warning(self, "提示", "请先连接到开发板!", QMessageBox.Ok, QMessageBox.Ok)
            return

        # 传输下载头
        self.fileCRC = self.CalculateFileCRC(self.filePath)

        headInfo = []
        headInfo.append(ctypes.c_uint32(0x55591012))
        headInfo.append(ctypes.c_uint32(~0x55591012))
        headInfo.append(ctypes.c_uint32(0x00000000))
        headInfo.append(ctypes.c_uint32(self.fileSize))
        headInfo.append(ctypes.c_uint32(self.fileCRC))
        headInfo.append(ctypes.c_uint32(headInfo[0].value ^ headInfo[1].value ^ headInfo[2].value ^ headInfo[3].value ^ headInfo[4].value))

        DownloadHead = bytes()

        for item in headInfo:
            DownloadHead = DownloadHead + bytes(item)

        self.tcpSock.send(DownloadHead)

        file = open(self.filePath, "rb")

        downloadLen = 0

        self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + "\r\n下载中......")
        while True:
            buf = file.read(120)

            if len(buf) <= 0:
                if downloadLen == self.fileSize:
                    self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + "\r\n下载成功!")
                else:
                    self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + "\r\n下载出错!")
                    self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + "\r\n文件大小为" + str(self.fileSize) + "Byte,实际下载大小为" + str(downloadLen) + "Byte")
                break;

            self.tcpSock.send(buf)
            rec = self.tcpSock.recv(self.tcpRecBufSize)

            if buf != rec:
                self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + "\r\n下载出错!")
                self.edt_downloadMsg.setText(self.edt_downloadMsg.toPlainText() + "\r\n恢复数据包不正确")
                break

            downloadLen = downloadLen + len(buf)

            self.pgb_downloadProgress.setValue(downloadLen * 100 / self.fileSize)

        #程序下载完,开发板程序跳转到APP之后TCP连接就断了
        self.tcpSock.close()
        self.btn_tcpConnect.setText("连接")
        self.isTcpConnected = False

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

    五、效果演示

    STM32端的BootLoader代码还是上一篇博客的代码,一点没改过,接下来看看效果。

 

    六、将工程打包成exe文件

    开发的工作都做好了,调试也没问题了,然后将python工程打包成exe文件就可以拿到其他人的电脑上去用了。要打包成exe文件,首先打开cmd命令窗口,进入到python工程的ISPdownloader.py文件所在的同级目录,然后运行命令pyinstaller.exe -F -w ISPdownloader.py,运行没问题的话就生成了两个文件夹,build和dist。exe文件就在dist目录里面。

 

其实软件也没实现太多的功能,打包一下有15M左右,感觉有点大,上网查了很多资料,貌似PyQt开发界面软件就是有这样的一个让人不爽的地方,exe文件比较大,但也能理解啊,里面要包含一些Python和Qt的库,肯定会大一点的。

 

    七、结束语

    到这里,就实现了通过上位机软件更新STM32端程序的功能了,还是比较好实现的。我们只需要把设备连接到局域网里面来,就可以实现远程对设备程序的更新了,用网络下载比用烧录器或者是串口下载程序方便很多。

STM32三种启动模式对应的存储介质均是芯片内置的,它们是: 1)用户闪存 = 芯片内置的Flash。 2)系统存储器 = 芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区 域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROM区。 3)SRAM = 芯片内置的RAM区,就是内存啦。            在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序,见下表: BOOT1=x   BOOT0=0   从用户闪存启动,这是正常的工作模式。 BOOT1=0   BOOT0=1   从系统存储器启动,这种模式启动的程序功能由厂家设置。 BOOT1=1   BOOT0=1   从内置SRAM启动,这种模式可以用于调试。         在系统复位后, SYSCLK的第4个上升沿, BOOT引脚的值将被锁存。用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。         在从待机模式退出时, BOOT引脚的值将被被重新锁存;因此,在待机模式下BOOT引脚应保持为需要的启动配置。在启动延迟之后, CPU从地址0x0000 0000获取堆栈顶的地址,并从启动存储器的0x0000 0004指示的地址开始执行代码。        因为固定的存储器映像,代码区始终从地址0x0000 0000开始(通过ICode和DCode总线访问),而数据区(SRAM)始终从地址0x2000 0000开始(通过系统总线访问)。 Cortex-M3的CPU始终从ICode总线获取复位向量,即启动仅适合于从代码区开始(典型地从Flash启动)。 STM32F10xxx微控制器实现了一个特殊的机制,系统可以不仅仅从Flash存储器或系统存储器启动,还可以从内置SRAM启动。         根据选定的启动模式,主闪存存储器、系统存储器或SRAM可以按照以下方式访问: ● 从主闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x0800 0000)访问它,即闪存存储器的内容可以在两个地址区域访问, 0x0000 0000 或 0x0800 0000。 ● 从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(互联型产品原有地址为0x1FFF B000,其它产品原有地址为0x1FFF F000)访问它。(可用于串口下载) ● 从内置SRAM启动:只能在0x2000 0000开始的地址区访问SRAM。 注意: 当从内置SRAM启动,在应用程序的初始化代码中,必须使用NVIC的异常表和偏移寄存器,从新映射向量表之SRAM中。 ————————————————
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值