diff --git a/.gitignore b/.gitignore index 5efc835..68504e4 100644 --- a/.gitignore +++ b/.gitignore @@ -202,4 +202,7 @@ local.properties /Package/Firmware Installer Setup.exe /Package/Firmware Installer-cache/ -.idea \ No newline at end of file +.Package/ +.DS_Store +.idea/ +firmwareInstaller.bak.py diff --git a/.idea/FirmwareInstaller.iml b/.idea/FirmwareInstaller.iml deleted file mode 100644 index d0876a7..0000000 --- a/.idea/FirmwareInstaller.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 314edf0..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Installer.spec b/Installer.spec index d94b441..c028924 100644 --- a/Installer.spec +++ b/Installer.spec @@ -3,12 +3,14 @@ block_cipher = None - +binaries = [ + ('C:\\Windows\\System32\\libusb0.dll', '.'), +] a = Analysis(['src\\firmwareInstaller.py'], pathex=['D:\\Work\\Project\\FirmwareInstaller'], - binaries=[], + binaries=binaries, datas=[('src\\ico.ico', '.')], - hiddenimports=[], + hiddenimports=['usb'], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/Package/Firmware Installer.aip b/Package/Firmware Installer.aip index 4585b9c..6dd08fd 100644 --- a/Package/Firmware Installer.aip +++ b/Package/Firmware Installer.aip @@ -1,9 +1,13 @@ - + + + + + @@ -14,10 +18,10 @@ - + - + @@ -34,42 +38,32 @@ - - - - - - - - - - + - - - - - + + + + @@ -82,80 +76,36 @@ - + - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + + + - - - - + - - - - - - - - - - - @@ -163,416 +113,264 @@ - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -592,117 +390,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -771,46 +458,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + @@ -830,7 +483,7 @@ - + @@ -847,9 +500,9 @@ - + - + @@ -858,28 +511,28 @@ - - + + - - - + + + - - + + - - + + - - - - - - + + + + + + - - + + @@ -901,7 +554,7 @@ - + @@ -936,7 +589,7 @@ - + @@ -971,7 +624,7 @@ - + @@ -1120,7 +773,7 @@ - + @@ -1149,10 +802,9 @@ - - + - + @@ -1348,10 +1000,10 @@ - + - + @@ -1364,7 +1016,6 @@ - @@ -1400,10 +1051,10 @@ - + - + @@ -1413,7 +1064,6 @@ - @@ -1457,10 +1107,10 @@ - + - + @@ -1473,7 +1123,6 @@ - @@ -1497,10 +1146,10 @@ - + - + @@ -1511,7 +1160,6 @@ - @@ -1550,10 +1198,10 @@ - + - + @@ -1564,7 +1212,6 @@ - @@ -1601,10 +1248,10 @@ - + - - + + @@ -1614,8 +1261,8 @@ - - + + @@ -1669,7 +1316,7 @@ - + @@ -1680,10 +1327,10 @@ - + - + @@ -1693,1279 +1340,54 @@ - - + + - - + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - + + + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -3032,24 +1454,25 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -3081,7 +1504,111 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3102,6 +1629,7 @@ + @@ -3153,7 +1681,7 @@ - + @@ -3185,23 +1713,14 @@ - - - - - - - - - @@ -3248,97 +1767,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3365,6 +1793,8 @@ + + @@ -3379,17 +1809,20 @@ + + - + + - + @@ -3425,143 +1858,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -3610,6 +2006,7 @@ + @@ -3619,13 +2016,22 @@ + + + + + + + + + - - - + + + @@ -3634,7 +2040,4 @@ - - - diff --git a/file_version_info.txt b/file_version_info.txt index 676acf0..dd149e8 100644 --- a/file_version_info.txt +++ b/file_version_info.txt @@ -6,8 +6,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. - filevers=(3, 0, 0, 1), - prodvers=(3, 0, 0, 1), + filevers=(3, 0, 0, 2), + prodvers=(3, 0, 0, 2), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -31,13 +31,13 @@ VSVersionInfo( u'040904B0', [StringStruct(u'CompanyName', u'CreatBot'), StringStruct(u'FileDescription', u'Hex Installer'), - StringStruct(u'FileVersion', u'3.0.0.1'), + StringStruct(u'FileVersion', u'3.0.0.2'), StringStruct(u'InternalName', u'Firmware Installer'), - StringStruct(u'LegalCopyright', u'Copyright(C) CreatBot 2021. All rights reserved'), + StringStruct(u'LegalCopyright', u'Copyright(C) CreatBot 2022. All rights reserved'), StringStruct(u'LegalTrademarks', u'CreatBot'), StringStruct(u'OriginalFilename', u'Installer.exe'), StringStruct(u'ProductName', u'Firmware Installer'), - StringStruct(u'ProductVersion', u'3.0.0.1')]) + StringStruct(u'ProductVersion', u'3.0.0.2')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ] diff --git a/requirements.txt b/requirements.txt index e538cad..b667233 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/avr_isp/__init__.py b/src/avr_isp/__init__.py index fb9bd4d..4fd08d4 100644 --- a/src/avr_isp/__init__.py +++ b/src/avr_isp/__init__.py @@ -2,4 +2,4 @@ from .chipDB import * from .errorBase import portError from .intelHex import * from .ispBase import * -from .stk500v2 import * \ No newline at end of file +from .stk500v2 import * diff --git a/src/avr_isp/chipDB.py b/src/avr_isp/chipDB.py index d796fb7..83b0e2d 100644 --- a/src/avr_isp/chipDB.py +++ b/src/avr_isp/chipDB.py @@ -4,27 +4,27 @@ Created on 2017年8月15日 @author: CreatBot-SW ''' -#=============================================================================== +# =============================================================================== # Database of AVR chips for avr_isp programming. Contains signatures and flash sizes from the AVR datasheets. # To support more chips add the relevant data to the avrChipDB list. -#=============================================================================== +# =============================================================================== avrChipDB = { - 'ATMega1280': { - 'signature': [0x1E, 0x97, 0x03], - 'pageSize': 128, - 'pageCount': 512, - }, - 'ATMega2560': { - 'signature': [0x1E, 0x98, 0x01], - 'pageSize': 128, - 'pageCount': 1024, - }, + 'ATMega1280': { + 'signature': [0x1E, 0x97, 0x03], + 'pageSize': 128, + 'pageCount': 512, + }, + 'ATMega2560': { + 'signature': [0x1E, 0x98, 0x01], + 'pageSize': 128, + 'pageCount': 1024, + }, } def getChipFromDB(sig): - for chip in avrChipDB.values(): - if chip['signature'] == sig: - return chip - return False + for chip in avrChipDB.values(): + if chip['signature'] == sig: + return chip + return False diff --git a/src/avr_isp/intelHex.py b/src/avr_isp/intelHex.py index 68d57dc..d674168 100644 --- a/src/avr_isp/intelHex.py +++ b/src/avr_isp/intelHex.py @@ -5,62 +5,62 @@ Created on 2017年8月15日 ''' import io -#=============================================================================== + +# =============================================================================== # Module to read intel hex files into binary data blobs. # IntelHex files are commonly used to distribute firmware # See: http://en.wikipedia.org/wiki/Intel_HEX -#=============================================================================== +# =============================================================================== def readHex(filename): - """ - Read an verify an intel hex file. Return the data as an list of bytes. - """ - data = [] - extraAddr = 0 - f = io.open(filename, "r") - for line in f: - line = line.strip() - if line[0] != ':': - raise formatError("Hex file must start with ':' @ " + line) - recLen = int(line[1:3], 16) - addr = int(line[3:7], 16) + extraAddr - recType = int(line[7:9], 16) - if len(line) != recLen * 2 + 11: - raise formatError("Length error in hex file @ " + line) - checkSum = 0 - for i in range(0, recLen + 5): - checkSum += int(line[i * 2 + 1:i * 2 + 3], 16) - checkSum &= 0xFF - if checkSum != 0: - raise formatError("Checksum error in hex file @ " + line) + """ + Read an verify an intel hex file. Return the data as an list of bytes. + """ + data = [] + extraAddr = 0 + f = io.open(filename, "r") + for line in f: + line = line.strip() + if line[0] != ':': + raise formatError("Hex file must start with ':' @ " + line) + recLen = int(line[1:3], 16) + addr = int(line[3:7], 16) + extraAddr + recType = int(line[7:9], 16) + if len(line) != recLen * 2 + 11: + raise formatError("Length error in hex file @ " + line) + checkSum = 0 + for i in range(0, recLen + 5): + checkSum += int(line[i * 2 + 1:i * 2 + 3], 16) + checkSum &= 0xFF + if checkSum != 0: + raise formatError("Checksum error in hex file @ " + line) - if recType == 0: # Data record - while len(data) < addr + recLen: - data.append(0) - for i in range(0, recLen): - data[addr + i] = int(line[i * 2 + 9:i * 2 + 11], 16) - elif recType == 1: # End Of File record - pass - elif recType == 2: # Extended Segment Address Record - extraAddr = int(line[9:13], 16) * 16 - elif recType == 3: # Start Segment Address Record - raise formatError("Dont support record type 03") - elif recType == 4: # Extended Linear Address Record - extraAddr = int(line[9:13], 16) << 16 - elif recType == 5: # Start Linear Address Record - raise formatError("Dont support record type 05") - else: - print(recType, recLen, addr, checkSum, line) - f.close() - return data + if recType == 0: # Data record + while len(data) < addr + recLen: + data.append(0) + for i in range(0, recLen): + data[addr + i] = int(line[i * 2 + 9:i * 2 + 11], 16) + elif recType == 1: # End Of File record + pass + elif recType == 2: # Extended Segment Address Record + extraAddr = int(line[9:13], 16) * 16 + elif recType == 3: # Start Segment Address Record + raise formatError("Dont support record type 03") + elif recType == 4: # Extended Linear Address Record + extraAddr = int(line[9:13], 16) << 16 + elif recType == 5: # Start Linear Address Record + raise formatError("Dont support record type 05") + else: + print(recType, recLen, addr, checkSum, line) + f.close() + return data class formatError(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) diff --git a/src/avr_isp/ispBase.py b/src/avr_isp/ispBase.py index 8d94e7f..e5d0f07 100644 --- a/src/avr_isp/ispBase.py +++ b/src/avr_isp/ispBase.py @@ -5,74 +5,75 @@ Created on 2017年8月15日 ''' from avr_isp import chipDB -#=============================================================================== + +# =============================================================================== # General interface for Isp based AVR programmers. # The ISP AVR programmer can load firmware into AVR chips. Which are commonly used on 3D printers. # # Needs to be subclassed to support different programmers. # Currently only the stk500v2 subclass exists. -#=============================================================================== +# =============================================================================== class IspBase(): - """ - Base class for ISP based AVR programmers. - Functions in this class raise an IspError when something goes wrong. - """ + """ + Base class for ISP based AVR programmers. + Functions in this class raise an IspError when something goes wrong. + """ - def programChip(self, flashData): - """ Program a chip with the given flash data. """ - self.curExtAddr = -1 - self.chip = chipDB.getChipFromDB(self.getSignature()) - if not self.chip: - raise IspError("Chip with signature: " + str(self.getSignature()) + "not found") - self.chipErase() + def programChip(self, flashData): + """ Program a chip with the given flash data. """ + self.curExtAddr = -1 + self.chip = chipDB.getChipFromDB(self.getSignature()) + if not self.chip: + raise IspError("Chip with signature: " + str(self.getSignature()) + "not found") + self.chipErase() - print("Flashing %i bytes" % len(flashData)) - self.writeFlash(flashData) - print("Verifying %i bytes" % len(flashData)) - self.verifyFlash(flashData) + print("Flashing %i bytes" % len(flashData)) + self.writeFlash(flashData) + print("Verifying %i bytes" % len(flashData)) + self.verifyFlash(flashData) - def getSignature(self): - """ - Get the AVR signature from the chip. This is a 3 byte array which describes which chip we are connected to. - This is important to verify that we are programming the correct type of chip and that we use proper flash block sizes. - """ - sig = [] - sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3]) - sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3]) - sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3]) - return sig + def getSignature(self): + """ + Get the AVR signature from the chip. This is a 3 byte array which describes which chip we are connected to. + This is important to verify that we are programming the correct type of chip and that we use proper flash block sizes. + """ + sig = [] + sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3]) + sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3]) + sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3]) + return sig - def sendISP(self, data): - """ - Send data to chip, needs to be implemented in a subclass. - """ - raise IspError("Called undefined sendISP") + def sendISP(self, data): + """ + Send data to chip, needs to be implemented in a subclass. + """ + raise IspError("Called undefined sendISP") - def chipErase(self): - """ - Do a full chip erase, clears all data, and lockbits. - """ - self.sendISP([0xAC, 0x80, 0x00, 0x00]) + def chipErase(self): + """ + Do a full chip erase, clears all data, and lockbits. + """ + self.sendISP([0xAC, 0x80, 0x00, 0x00]) - def writeFlash(self, flashData): - """ - Write the flash data, needs to be implemented in a subclass. - """ - raise IspError("Called undefined writeFlash") + def writeFlash(self, flashData): + """ + Write the flash data, needs to be implemented in a subclass. + """ + raise IspError("Called undefined writeFlash") - def verifyFlash(self, flashData): - """ - Verify the flash data, needs to be implemented in a subclass. - """ - raise IspError("Called undefined verifyFlash") + def verifyFlash(self, flashData): + """ + Verify the flash data, needs to be implemented in a subclass. + """ + raise IspError("Called undefined verifyFlash") class IspError(Exception): - def __init__(self, value): - self.value = value + def __init__(self, value): + self.value = value - def __str__(self): - return repr(self.value) + def __str__(self): + return repr(self.value) diff --git a/src/avr_isp/stk500v2.py b/src/avr_isp/stk500v2.py index 3435c2d..0281d2e 100644 --- a/src/avr_isp/stk500v2.py +++ b/src/avr_isp/stk500v2.py @@ -8,14 +8,14 @@ Created on 2017年8月15日 # The STK500v2 protocol is used by the ArduinoMega2560 and a few other Arduino platforms to load firmware. # =============================================================================== -import struct, sys -import time +import struct +import sys from PyQt5.QtCore import QIODevice, QThread, pyqtSignal from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo from PyQt5.QtWidgets import QApplication - from avr_isp import intelHex, ispBase + from .errorBase import portError @@ -88,10 +88,8 @@ class Stk500v2(ispBase.IspBase, QSerialPort): loadCount = (len(flashData) + pageSize - 1) // pageSize for i in range(0, loadCount): - self.sendMessage( - [0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[ - (i * pageSize):( - i * pageSize + pageSize)]) + self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):( + i * pageSize + pageSize)]) self.progressCallback.emit(i + 1, loadCount * 2) def verifyFlash(self, flashData): @@ -191,9 +189,8 @@ class stk500v2Thread(QThread): self.finished.connect(self.done) def run(self): - + self.isWork = True try: - self.isWork = True self.programmer = Stk500v2() if self.callback is not None: self.programmer.progressCallback.connect(self.callback) @@ -201,26 +198,24 @@ class stk500v2Thread(QThread): if self.parent is None: runProgrammer(self.port, self.speed, self.filename, self.programmer) else: - self.stateCallback[str].emit(self.tr("Connecting..")) + self.stateCallback[str].emit(self.tr("Connecting...")) self.msleep(200) self.programmer.connect(self.port, self.speed) self.stateCallback[str].emit(self.tr("Programming...")) self.programmer.programChip(intelHex.readHex(self.filename)) - except Exception as e: if self.isInterruptionRequested(): print("Int") else: if self.parent is not None: self.stateCallback[Exception].emit(e) - # while self.isWork: - # pass # 等待父进程处理异常 + while self.isWork: + pass # 等待父进程处理异常 else: raise e self.isWork = False finally: - self.isWork = False if self.programmer.isConnected(): self.programmer.fastReset() self.programmer.close() @@ -266,8 +261,7 @@ def main(): programmer.programChip(intelHex.readHex(sys.argv[2])) else: programmer.connect("COM4") - programmer.programChip( - intelHex.readHex("D:/OneDrive/Desktop/CreatBot F160 01 EN KTC ( AUTO_LEVELING ).hex")) + programmer.programChip(intelHex.readHex("D:/OneDrive/Desktop/CreatBot F160 01 EN KTC ( AUTO_LEVELING ).hex")) except portError as e: print(e.value) print("PortError: " + str(e)) diff --git a/src/firmwareInstaller.py b/src/firmwareInstaller.py index c4a794a..6f558f2 100644 --- a/src/firmwareInstaller.py +++ b/src/firmwareInstaller.py @@ -6,13 +6,12 @@ Created on 2017年8月15日 import os import sys -import time -from PyQt5 import QtSerialPort, QtCore, QtWidgets +from PyQt5 import QtCore from PyQt5.Qt import pyqtSignal from PyQt5.QtCore import QSize, QDir, QTimer from PyQt5.QtGui import QIcon -from PyQt5.QtSerialPort import QSerialPortInfo +from PyQt5.QtSerialPort import QSerialPortInfo, QSerialPort from PyQt5.QtWidgets import QApplication, QVBoxLayout, QGroupBox, \ QRadioButton, QGridLayout, QWidget, QProgressBar, QStatusBar, QComboBox, QLabel, \ QHBoxLayout, QLineEdit, QPushButton, QFileDialog, QCheckBox @@ -20,8 +19,6 @@ from PyQt5.QtWidgets import QApplication, QVBoxLayout, QGroupBox, \ from avr_isp.intelHex import formatError from avr_isp.ispBase import IspError from avr_isp.stk500v2 import stk500v2Thread, portError - -# from DfuseTool import DfuseTool as DFUse from pydfu import DFUTool as DFUse if getattr(sys, 'frozen', False): @@ -64,20 +61,17 @@ class mainWindow(QWidget): def __init__(self): super(mainWindow, self).__init__() - self.setWindowTitle(self.tr("Firmware Installer")) + self.setWindowTitle(self.tr("Firmware Installer" + " V3.1.0")) self.setWindowIcon(QIcon(os.path.join(bundle_dir, "ico.ico"))) self.setFixedSize(QSize(480, 240)) self.setAcceptDrops(True) self.portUpdateTimer = QTimer() self.portUpdateTimer.timeout.connect(self.portUpdate) - self.portUpdateTimer.start(200) - + self.portUpdateTimer.start(100) self.autoTimer = QTimer() # self.autoTimer.setSingleShot(True) - self.autoTimer.timeout.connect(self.portUpdate) - # self.autoTimer.start(1000) - + self.autoTimer.timeout.connect(self.installFile) self.task = None self.initUI() @@ -135,7 +129,7 @@ class mainWindow(QWidget): self.autoCheck = QCheckBox(self.tr("Auto Install")) self.autoTimeLabel = QLabel(self.tr("Idle Time:")) self.autoTimeLabel2 = QLabel(self.tr("s")) - self.autoTime = QLineEdit("3") + self.autoTime = QLineEdit("2") self.autoTime.setInputMask("00") self.autoTime.setMaximumWidth(20) @@ -174,8 +168,7 @@ class mainWindow(QWidget): # 计数栏 self.countBar = QStatusBar() self.countBar.setSizeGripEnabled(False) - self.countBar.setStyleSheet( - "QStatusBar::item { border: 0 } QLabel {border:0; font-size: 14px; font-weight: bold}") + self.countBar.setStyleSheet("QStatusBar::item { border: 0 } QLabel {border:0; font-size: 14px; font-weight: bold}") countSuccessLabel = QLabel("Success: ") countFailureLabel = QLabel("Failure: ") self.countSuccess = countLabel("0") @@ -231,9 +224,9 @@ class mainWindow(QWidget): self.statusBar.messageChanged.connect(self.stateClearAction) # 默认选中 + self.autoCheck.click() + self.autoCheck.click() self.autoRadio.click() - # self.manualRadio.click() - # self.autoCheck.click() self.fileBtn.clicked.connect(self.selectFile) self.installBtn.clicked.connect(self.installFile) @@ -244,36 +237,24 @@ class mainWindow(QWidget): def portUpdate(self, forceUpdate=False): """ Auto 监听端口 """ - print("search port") if self.autoRadio.isChecked(): - self.baudCombo.setCurrentText("115200") # self.portCombo.addItems(portList()) - # self.portCombo.clear() - port_list = QSerialPortInfo.availablePorts() - for port in port_list: - print(':--:', port.portName(), port.description()) - if port.description() not in ["Arduino Mega 2560", "USB-SERIAL CH340"]: # 过滤2560和CH340 + self.portCombo.clear() + for port in QSerialPortInfo.availablePorts(): + if port.description() not in ["Arduino Mega 2560", "USB-SERIAL CH340", "USB 串行设备"]: # 过滤2560和CH340 continue - self.portCombo.addItem(port.portName() + " (" + port.description() + ")", port.portName()) - - if len(port_list) > 0: - port = port_list[0] - info = QSerialPortInfo(port) - print(info.productIdentifier()) - if info.productIdentifier() == 0: - return - if not self.installBtn.isEnabled(): - self.installFile() - + portInfo = QSerialPortInfo(port) + self.portCombo.addItem(port.portName() + " (" + port.description() + ")", (port.portName(), portInfo.vendorIdentifier())) else: - currentPortData = self.portCombo.currentData() - if forceUpdate or (currentPortData and currentPortData not in [port.portName() for port in - QSerialPortInfo.availablePorts()]): + currentPortData = self.portCombo.currentText() + if forceUpdate or (currentPortData and currentPortData not in [port.portName() for port in QSerialPortInfo.availablePorts()]): self.portCombo.clear() for port in QSerialPortInfo.availablePorts(): - self.portCombo.addItem(port.portName() + " (" + port.description() + ")", port.portName()) - self.portCombo.setCurrentIndex(self.portCombo.findData(currentPortData)) + portInfo = QSerialPortInfo(port) + self.portCombo.addItem(port.portName() + " (" + port.description() + ")", + (port.portName(), portInfo.vendorIdentifier())) + self.portCombo.setCurrentIndex(self.portCombo.findText(currentPortData)) self.portCombo.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.baudCombo.setSizeAdjustPolicy(QComboBox.AdjustToContents) @@ -285,14 +266,6 @@ class mainWindow(QWidget): if not check and self.autoTimer.remainingTime() > 0: self.stopInstall() - if check: - # 开启自动安装的端口扫描 - self.portUpdateTimer.stop() - self.autoTimer.start(int(self.autoTime.text())*1000) - else: - self.autoTimer.stop() - self.portUpdateTimer.start(200) - def autoTimeChangeAction(self): """ 修改时间间隔 """ self.autoTime.clearFocus() @@ -329,65 +302,35 @@ class mainWindow(QWidget): self.file.setText(hexFileDialog.selectedFiles()[0]) def installFile(self, notFromButton=True): - """ 开始安装 """ - print("-------------------开始安装-------------------") - # for port in QSerialPortInfo.availablePorts(): - # print('port.description===', port.portName()) - # print('port.description===', port.description()) - # info = QSerialPortInfo(port) - # print(info.productIdentifier()) - # print(info.vendorIdentifier()) + if self.portCombo.currentData() is None: + return if self.file.text() == "": if not notFromButton: self.selectFile() self.installFile(True) else: - if self.autoTimer.remainingTime() > 0: + if self.autoTimer.remainingTime() != -1: self.autoTimer.stop() - port_list = QSerialPortInfo.availablePorts() - if len(port_list) <= 0: - QtWidgets.QMessageBox.about(self, "提示", "没有可用的串口!") - return + self.portBox.setDisabled(True) + self.fileBox.setDisabled(True) + self.installBtn.setDisabled(True) - port = port_list[0] - info = QSerialPortInfo(port) + self.progress.show() + self.resize() - port_Name = None - baud_rate = None - if self.manualRadio.isChecked(): - if self.portCombo.currentData() is None: - QtWidgets.QMessageBox.about(self, "提示", "请选择串口设备") - return - else: - port_Name = self.portCombo.currentData() - baud_rate = int(self.baudCombo.currentText()) - - else: - port_Name = info.portName() - baud_rate = QtSerialPort.QSerialPort.Baud115200 - - print("port:", port_Name) - print("baudCombo:", baud_rate) - print(info.portName()) - print(info.description()) - print(info.productIdentifier()) - print(info.vendorIdentifier()) - if info.productIdentifier() == 0: - self.autoTimer.start(1000) - pass - - if info.vendorIdentifier() == 1155: - print("------命令开启DFU模式 start------") + info = self.portCombo.currentData() + if info[1] == 1155: + self.stopBtn.setEnabled(False) self.statusBar.showMessage("Serial to DFU...") - serial = QtSerialPort.QSerialPort(self) - serial.setPortName(port_Name) - serial.setBaudRate(baud_rate) - serial.setDataBits(QtSerialPort.QSerialPort.Data8) - serial.setParity(QtSerialPort.QSerialPort.NoParity) - serial.setStopBits(QtSerialPort.QSerialPort.OneStop) - serial.setFlowControl(QtSerialPort.QSerialPort.NoFlowControl) + serial = QSerialPort(self) + serial.setPortName(info[0]) + serial.setBaudRate(QSerialPort.Baud115200) + serial.setDataBits(QSerialPort.Data8) + serial.setParity(QSerialPort.NoParity) + serial.setStopBits(QSerialPort.OneStop) + serial.setFlowControl(QSerialPort.NoFlowControl) if not serial.open(QtCore.QIODevice.ReadWrite): # QtWidgets.QMessageBox.about(self, "提示", "无法打开串口!") @@ -396,25 +339,35 @@ class mainWindow(QWidget): data = bytes("M9999\r", encoding='utf-8') data = QtCore.QByteArray(data) serial.write(data) - print("------命令开启DFU模式 end------") + self.statusBar.showMessage("Serial to dfu...") - self.task = DFUse(self, self.file.text(), self.progressUpdate) + self.task = DFUse(self, self.portCombo.currentData()[0], int(self.baudCombo.currentText()), self.file.text(), + self.progressUpdate) self.task.stateCallback[str].connect(self.stateUpdate) self.task.stateCallback[Exception].connect(self.stateUpdate) self.task.finished.connect(self.autoAction) # 检查是否自动烧写,并启动。 # self.task.start() - elif info.vendorIdentifier() != 0: - self.task = stk500v2Thread(self, self.portCombo.currentData(), int(self.baudCombo.currentText()), + elif info[1] != 0: + self.stopBtn.setEnabled(True) + self.task = stk500v2Thread(self, info[0], int(self.baudCombo.currentText()), self.file.text(), self.progressUpdate) self.task.stateCallback[str].connect(self.stateUpdate) self.task.stateCallback[Exception].connect(self.stateUpdate) self.task.finished.connect(self.autoAction) self.task.start() - # 开始烧录,刷新UI - # self.statusBar.showMessage(" ") + return + + if self.file.text() == "": + if not notFromButton: + self.selectFile() + self.installFile(True) + else: + if self.autoTimer.remainingTime() != -1: + self.autoTimer.stop() + self.portBox.setDisabled(True) self.fileBox.setDisabled(True) self.installBtn.setDisabled(True) @@ -422,6 +375,14 @@ class mainWindow(QWidget): self.progress.show() self.resize() + self.task = DFUse(self, self.portCombo.currentData()[0], int(self.baudCombo.currentText()), self.file.text(), + self.progressUpdate) + self.task.stateCallback[str].connect(self.stateUpdate) + self.task.stateCallback[Exception].connect(self.stateUpdate) + self.task.finished.connect(self.autoAction) + self.task.start() + self.statusBar.showMessage(" ") + def stopInstall(self, succeed=False, autoInstall=False): """ 停止自动安装 """ if autoInstall: @@ -440,8 +401,8 @@ class mainWindow(QWidget): self.countSuccess.setText(str(int(self.countSuccess.text()) + 1)) self.task = None else: - # if self.autoTimer.remainingTime() != -1: - # self.autoTimer.stop() + if self.autoTimer.remainingTime() != -1: + self.autoTimer.stop() if self.task is not None and self.task.isRunning(): self.task.finished.disconnect() if self.task.isReady(): @@ -459,14 +420,10 @@ class mainWindow(QWidget): def autoAction(self): """ 上一个任务结束后,开启新的烧录任务 """ - self.task = None - self.statusBar.showMessage("Done!") + self.stopBtn.setEnabled(True) self.stopInstall(True, self.autoCheck.isChecked()) - # 开启自动安装 if self.autoCheck.isChecked(): - if self.autoTimer.remainingTime() > 0: - self.autoTimer.stop() self.autoTimer.start(int(self.autoTime.text()) * 1000) def resize(self): @@ -481,31 +438,24 @@ class mainWindow(QWidget): """ 安装状态 """ self.tryAgainLabel.setHidden(True) self.tryAgain.setHidden(True) - if self.task.isReady(): - self.tryAgain.setText("0") + # if self.task.isReady(): + # self.tryAgain.setText("0") - print(stateOrError, str) if type(stateOrError) == str: - print("222---") # self.statusBar.setStyleSheet("QStatusBar::item { border: 0 } QLabel {color: red; font-weight: bold}") self.statusBar.setStyleSheet( "QStatusBar {font-weight: bold; color: black} QStatusBar::item { border: 0 } QLabel {font-weight: bold}") if self.task is not None and not self.task.isWork and not self.autoCheck.isChecked(): - print("statusBar---") self.statusBar.showMessage(stateOrError, 3000) else: - print("else---") self.statusBar.showMessage(stateOrError) - else: - print("333---") self.task.requestInterruption() self.statusBar.setStyleSheet( "QStatusBar {font-weight: bold; color: red} QStatusBar::item { border: 0 } QLabel {font-weight: bold; color: red}") if type(stateOrError) == portError: - if (stateOrError.value in [portError.errorInvalid, portError.errorBusy]) and ( - int(self.tryAgain.text()) < 20): + if (stateOrError.value in [portError.errorInvalid, portError.errorBusy]) and (int(self.tryAgain.text()) < 20): self.statusBar.showMessage("PortError: " + str(stateOrError)) self.tryAgain.setText(str(int(self.tryAgain.text()) + 1)) self.tryAgainLabel.setVisible(True) @@ -531,7 +481,6 @@ class mainWindow(QWidget): else: self.statusBar.showMessage("Error: " + str(stateOrError), 5000) self.stopInstall() - print("1111---") self.task.isWork = False self.task.wait(100) @@ -539,7 +488,6 @@ class mainWindow(QWidget): if __name__ == '__main__': - app = QApplication(sys.argv) win = mainWindow() diff --git a/src/pydfu.py b/src/pydfu.py index 98f0b81..c305e0b 100644 --- a/src/pydfu.py +++ b/src/pydfu.py @@ -18,10 +18,12 @@ import collections import inspect import re import struct -import sys, time +import sys +import time +import zlib + import usb.core import usb.util -import zlib # USB request __TIMEOUT __TIMEOUT = 4000 @@ -658,24 +660,213 @@ def main(): # if __name__ == "__main__": # main() -from avr_isp.errorBase import portError - -from PyQt5.QtCore import QIODevice, QThread, pyqtSignal +from PyQt5.QtCore import QIODevice, QThread, pyqtSignal, QByteArray from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo from PyQt5.QtWidgets import QApplication +from avr_isp import ispBase +from avr_isp.errorBase import portError + + +class STM32Dev(ispBase.IspBase, QSerialPort): + progressCallback = pyqtSignal(int, int) + + def __init__(self): + super(STM32Dev, self).__init__() + self.seq = 1 + self.lastAddr = -1 + self.portInfo = None + + def connect(self, port='COM4', speed=115200): + print("connect", port) + self.portInfo = QSerialPortInfo(port) + print("portInfo", self.portInfo) + self.setPortName(port) + # self.setBaudRate(speed) + # self.setPortName("COM3") + self.setBaudRate(QSerialPort.Baud115200) + self.setDataBits(QSerialPort.Data8) + self.setParity(QSerialPort.NoParity) + self.setStopBits(QSerialPort.OneStop) + self.setFlowControl(QSerialPort.NoFlowControl) + + if self.portInfo.isNull(): + raise portError(portError.errorInvalid, port) + else: + if self.portInfo.isBusy(): + raise portError(portError.errorBusy, port) + else: + if self.open(QIODevice.ReadWrite): + # self.setBreakEnabled() + print("open") + # self.entryISP() + print("open--end") + else: + raise portError(portError.errorOpen, port) + + def close(self): + super(STM32Dev, self).close() + self.portInfo = None + + def serial_DFU(self): + print("serial_DFU") + # if not self.open(QIODevice.ReadWrite): + # # QtWidgets.QMessageBox.about(self, "提示", "无法打开串口!") + # return + print("1111") + data = bytes("M9999\r", encoding='utf-8') + data = QByteArray(data) + print("2222") + # self.write(data) + print(self.write(data)) + print("serial_DFU---end") + self.close() + + def entryISP(self): + self.seq = 1 + # Reset the controller + self.setDataTerminalReady(True) + QThread.msleep(100) + self.setDataTerminalReady(False) + QThread.msleep(200) + self.clear() + print("=====") + + recv = self.sendMessage([1])[3:] + if "".join([chr(c) for c in recv]) != "AVRISP_2": + raise ispBase.IspError("Unkonwn bootloaders!") + + if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]: + raise ispBase.IspError("Failed to enter programming mode!") + + def leaveISP(self): + if self.portInfo is not None: + if self.sendMessage([0x11]) != [0x11, 0x00]: + raise ispBase.IspError("Failed to leave programming mode!") + + def isConnected(self): + return self.isOpen() + + def sendISP(self, data): + recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]]) + return recv[2:6] + + def writeFlash(self, flashData): + # Set load addr to 0, in case we have more then 64k flash we need to enable the address extension + pageSize = self.chip['pageSize'] * 2 + flashSize = pageSize * self.chip['pageCount'] + if flashSize > 0xFFFF: + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + else: + self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) + + loadCount = (len(flashData) + pageSize - 1) // pageSize + for i in range(0, loadCount): + self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):( + i * pageSize + pageSize)]) + self.progressCallback.emit(i + 1, loadCount * 2) + + def verifyFlash(self, flashData): + # Set load addr to 0, in case we have more then 64k flash we need to enable the address extension + flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount'] + if flashSize > 0xFFFF: + self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00]) + else: + self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00]) + + loadCount = (len(flashData) + 0xFF) // 0x100 + for i in range(0, loadCount): + recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102] + self.progressCallback.emit(loadCount + i + 1, loadCount * 2) + for j in range(0, 0x100): + if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]: + raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j)) + + def fastReset(self): + QThread.msleep(50) + self.setDataTerminalReady(True) + self.setDataTerminalReady(False) + + def sendMessage(self, data): + message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E) + for c in data: + message += struct.pack(">B", c) + checksum = 0 + for c in message: + checksum ^= c + message += struct.pack(">B", checksum) + try: + print("----00") + self.write(message) + self.flush() + print("----11") + except: + raise ispBase.IspError("Serial send timeout") + self.seq = (self.seq + 1) & 0xFF + print("----222") + # time.sleep(1) + if self.waitForReadyRead(1000): + print("----33") + return self.recvMessage() + else: + print("----44") + raise ispBase.IspError("Serial recv timeout") + + def recvMessage(self): + state = 'Start' + checksum = 0 + while True: + s = self.read(1) + if len(s) < 1: + if self.waitForReadyRead(20): + continue + else: + raise ispBase.IspError("Serial read timeout") + b = struct.unpack(">B", s)[0] + checksum ^= b + if state == 'Start': + if b == 0x1B: + state = 'GetSeq' + checksum = 0x1B + elif state == 'GetSeq': + state = 'MsgSize1' + elif state == 'MsgSize1': + msgSize = b << 8 + state = 'MsgSize2' + elif state == 'MsgSize2': + msgSize |= b + state = 'Token' + elif state == 'Token': + if b != 0x0E: + state = 'Start' + else: + state = 'Data' + data = [] + elif state == 'Data': + data.append(b) + if len(data) == msgSize: + state = 'Checksum' + elif state == 'Checksum': + if checksum != 0: + state = 'Start' + else: + return data + class DFUTool(QThread): print("DFUTool(QThread)") stateCallback = pyqtSignal([str], [Exception]) + progressCallback = pyqtSignal(int, int) - def __init__(self, parent, filename, callback=None): + def __init__(self, parent, port, speed, filename, callback=None): super(DFUTool, self).__init__() - print("----------------------") self.parent = parent + self.port = port + self.speed = speed self.filename = filename - self.progress = callback + self.callback = callback + self.programmer = None self.isWork = False self.finished.connect(self.done) @@ -721,6 +912,7 @@ class DFUTool(QThread): def w(state): if state is False: + self.stateCallback[Exception].emit(portError(portError.errorOpen, port)) self.stateCallback[str].emit("Done!") self.quit() return @@ -737,30 +929,40 @@ class DFUTool(QThread): self.thread = DFUSearch(self, kwargs=kwargs) self.thread.searchResults.connect(w) # 异步完成后执行函数w self.thread.start() + self.isWork = True def cl_progress(self, addr, offset, size): """Prints a progress report suitable for use on the command line.""" + print("offset", offset, "size", size) self.progressCallback.emit(offset, size) + def disconnect(self, QMetaObject_Connection=None): + print("QMetaObject_Connection") + def run(self): - if self.progress is not None: - self.progressCallback.connect(self.progress) - print(self.args) - self.stateCallback[str].emit("read fils") - with open(self.args.path, "rb") as fin: - dfu_file = fin.read() - - if dfu_file is None: - print("file is None") - return - self.isWork = True - - elem = {"addr": 134217728, "size": len(dfu_file), "data": dfu_file} - try: - self.stateCallback[str].emit("Write file...") - write_elements([elem], self.args.mass_erase, progress=self.cl_progress) + with open(self.args.path, "rb") as fin: + dfu_file = fin.read() + + if dfu_file is None: + print("file is None") + return + elem = {"addr": 134217728, "size": len(dfu_file), "data": dfu_file} + + self.programmer = STM32Dev() + + if self.callback is not None: + self.progressCallback.connect(self.callback) + + if self.parent is None: + pass + + else: + self.stateCallback[str].emit(self.tr("Programming...")) + # self.programmer. + write_elements([elem], self.args.mass_erase, progress=self.cl_progress) + exit_dfu() # 退出DFU模式 except Exception as err: if self.isInterruptionRequested(): print("int") @@ -773,16 +975,13 @@ class DFUTool(QThread): raise err self.isWork = False finally: - self.isWork = False - self.stateCallback[str].emit("Exiting DFU...") - print("Exiting DFU...") - exit_dfu() - # 退出DFU模式 - print("Done...") - self.ude = None + self.stateCallback[str].emit("Done!") + self.programmer = None def isReady(self): return True + return self.programmer is not None and self.programmer.isConnected() + try: status = get_status() print(status) @@ -807,6 +1006,9 @@ class DFUTool(QThread): print("Failure!") def terminate(self): + if self.thread.isRunning(): + self.thread.exit() + self.requestInterruption() return super(DFUTool, self).terminate() @@ -830,11 +1032,11 @@ class DFUSearch(QThread): devices = get_dfu_devices(**self.kwargs) attempts += 1 print("搜索DFU设备", attempts) - if attempts > 10: + if attempts > 20: self.searchResults.emit(False) self.quit() return - time.sleep(2) + time.sleep(1) self.searchResults.emit(True)