#!/usr/bin/env python# This file is part of the OpenMV project.# Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com># This work is licensed under the MIT license, see the file LICENSE for# details."""This module implements enough functionality to program the STM32F4xx overDFU, without requiring dfu-util.See app note AN3156 for a description of the DFU protocol.See document UM0391 for a dscription of the DFuse file."""from __future__ import print_functionimport argparseimport collectionsimport inspectimport reimport structimport sysimport usb.coreimport usb.utilimport zlib# VID/PID__VID = 0x0483__PID = 0xDF11# USB request __TIMEOUT__TIMEOUT = 4000# DFU commands__DFU_DETACH = 0__DFU_DNLOAD = 1__DFU_UPLOAD = 2__DFU_GETSTATUS = 3__DFU_CLRSTATUS = 4__DFU_GETSTATE = 5__DFU_ABORT = 6# DFU status__DFU_STATE_APP_IDLE = 0x00__DFU_STATE_APP_DETACH = 0x01__DFU_STATE_DFU_IDLE = 0x02__DFU_STATE_DFU_DOWNLOAD_SYNC = 0x03__DFU_STATE_DFU_DOWNLOAD_BUSY = 0x04__DFU_STATE_DFU_DOWNLOAD_IDLE = 0x05__DFU_STATE_DFU_MANIFEST_SYNC = 0x06__DFU_STATE_DFU_MANIFEST = 0x07__DFU_STATE_DFU_MANIFEST_WAIT_RESET = 0x08__DFU_STATE_DFU_UPLOAD_IDLE = 0x09__DFU_STATE_DFU_ERROR = 0x0A_DFU_DESCRIPTOR_TYPE = 0x21__DFU_STATUS_STR = {__DFU_STATE_APP_IDLE: "STATE_APP_IDLE",__DFU_STATE_APP_DETACH: "STATE_APP_DETACH",__DFU_STATE_DFU_IDLE: "STATE_DFU_IDLE",__DFU_STATE_DFU_DOWNLOAD_SYNC: "STATE_DFU_DOWNLOAD_SYNC",__DFU_STATE_DFU_DOWNLOAD_BUSY: "STATE_DFU_DOWNLOAD_BUSY",__DFU_STATE_DFU_DOWNLOAD_IDLE: "STATE_DFU_DOWNLOAD_IDLE",__DFU_STATE_DFU_MANIFEST_SYNC: "STATE_DFU_MANIFEST_SYNC",__DFU_STATE_DFU_MANIFEST: "STATE_DFU_MANIFEST",__DFU_STATE_DFU_MANIFEST_WAIT_RESET: "STATE_DFU_MANIFEST_WAIT_RESET",__DFU_STATE_DFU_UPLOAD_IDLE: "STATE_DFU_UPLOAD_IDLE",__DFU_STATE_DFU_ERROR: "STATE_DFU_ERROR",}# USB device handle__dev = None# Configuration descriptor of the device__cfg_descr = None__verbose = None# USB DFU interface__DFU_INTERFACE = 0# Python 3 deprecated getargspec in favour of getfullargspec, but# Python 2 doesn't have the latter, so detect which one to usegetargspec = getattr(inspect, "getfullargspec", inspect.getargspec)if "length" in getargspec(usb.util.get_string).args:# PyUSB 1.0.0.b1 has the length argumentdef get_string(dev, index):return usb.util.get_string(dev, 255, index)else:# PyUSB 1.0.0.b2 dropped the length argumentdef get_string(dev, index):return usb.util.get_string(dev, index)def find_dfu_cfg_descr(descr):if len(descr) == 9 and descr[0] == 9 and descr[1] == _DFU_DESCRIPTOR_TYPE:nt = collections.namedtuple("CfgDescr",["bLength","bDescriptorType","bmAttributes","wDetachTimeOut","wTransferSize","bcdDFUVersion",],)return nt(*struct.unpack("<BBBHHH", bytearray(descr)))return Nonedef init():"""Initializes the found DFU device so that we can program it."""global __dev, __cfg_descrdevices = get_dfu_devices(idVendor=__VID, idProduct=__PID)if not devices:raise ValueError("No DFU device found")if len(devices) > 1:raise ValueError("Multiple DFU devices found")__dev = devices[0]__dev.set_configuration()# Claim DFU interfaceusb.util.claim_interface(__dev, __DFU_INTERFACE)# Find the DFU configuration descriptor, either in the device or interfaces__cfg_descr = Nonefor cfg in __dev.configurations():__cfg_descr = find_dfu_cfg_descr(cfg.extra_descriptors)if __cfg_descr:breakfor itf in cfg.interfaces():__cfg_descr = find_dfu_cfg_descr(itf.extra_descriptors)if __cfg_descr:break# Get device into idle statefor attempt in range(4):status = get_status()if status == __DFU_STATE_DFU_IDLE:breakelif status == __DFU_STATE_DFU_DOWNLOAD_IDLE or status == __DFU_STATE_DFU_UPLOAD_IDLE:abort_request()else:clr_status()def abort_request():"""Sends an abort request."""__dev.ctrl_transfer(0x21, __DFU_ABORT, 0, __DFU_INTERFACE, None, __TIMEOUT)def clr_status():"""Clears any error status (perhaps left over from a previous session)."""__dev.ctrl_transfer(0x21, __DFU_CLRSTATUS, 0, __DFU_INTERFACE, None, __TIMEOUT)def get_status():"""Get the status of the last operation."""stat = __dev.ctrl_transfer(0xA1, __DFU_GETSTATUS, 0, __DFU_INTERFACE, 6, 20000)# firmware can provide an optional string for any errorif stat[5]:message = get_string(__dev, stat[5])if message:print(message)return stat[4]def check_status(stage, expected):status = get_status()if status != expected:raise SystemExit("DFU: %s failed (%s)" % (stage, __DFU_STATUS_STR.get(status, status)))def mass_erase():"""Performs a MASS erase (i.e. erases the entire device)."""# Send DNLOAD with first byte=0x41__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, "\x41", __TIMEOUT)# Execute last commandcheck_status("erase", __DFU_STATE_DFU_DOWNLOAD_BUSY)# Check command statecheck_status("erase", __DFU_STATE_DFU_DOWNLOAD_IDLE)def page_erase(addr):"""Erases a single page."""if __verbose:print("Erasing page: 0x%x..." % (addr))# Send DNLOAD with first byte=0x41 and page addressbuf = struct.pack("<BI", 0x41, addr)__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, buf, __TIMEOUT)# Execute last commandcheck_status("erase", __DFU_STATE_DFU_DOWNLOAD_BUSY)# Check command statecheck_status("erase", __DFU_STATE_DFU_DOWNLOAD_IDLE)def set_address(addr):"""Sets the address for the next operation."""# Send DNLOAD with first byte=0x21 and page addressbuf = struct.pack("<BI", 0x21, addr)__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, buf, __TIMEOUT)# Execute last commandcheck_status("set address", __DFU_STATE_DFU_DOWNLOAD_BUSY)# Check command statecheck_status("set address", __DFU_STATE_DFU_DOWNLOAD_IDLE)def write_memory(addr, buf, progress=None, progress_addr=0, progress_size=0):"""Writes a buffer into memory. This routine assumes that memory hasalready been erased."""xfer_count = 0xfer_bytes = 0xfer_total = len(buf)xfer_base = addrwhile xfer_bytes < xfer_total:if __verbose and xfer_count % 512 == 0:print("Addr 0x%x %dKBs/%dKBs..."% (xfer_base + xfer_bytes, xfer_bytes // 1024, xfer_total // 1024))if progress and xfer_count % 2 == 0:progress(progress_addr, xfer_base + xfer_bytes - progress_addr, progress_size)# Set mem write addressset_address(xfer_base + xfer_bytes)# Send DNLOAD with fw datachunk = min(__cfg_descr.wTransferSize, xfer_total - xfer_bytes)__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 2, __DFU_INTERFACE, buf[xfer_bytes : xfer_bytes + chunk], __TIMEOUT)# Execute last commandcheck_status("write memory", __DFU_STATE_DFU_DOWNLOAD_BUSY)# Check command statecheck_status("write memory", __DFU_STATE_DFU_DOWNLOAD_IDLE)xfer_count += 1xfer_bytes += chunkdef write_page(buf, xfer_offset):"""Writes a single page. This routine assumes that memory has alreadybeen erased."""xfer_base = 0x08000000# Set mem write addressset_address(xfer_base + xfer_offset)# Send DNLOAD with fw data__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 2, __DFU_INTERFACE, buf, __TIMEOUT)# Execute last commandcheck_status("write memory", __DFU_STATE_DFU_DOWNLOAD_BUSY)# Check command statecheck_status("write memory", __DFU_STATE_DFU_DOWNLOAD_IDLE)if __verbose:print("Write: 0x%x " % (xfer_base + xfer_offset))def exit_dfu():"""Exit DFU mode, and start running the program."""# Set jump addressset_address(0x08000000)# Send DNLOAD with 0 length to exit DFU__dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, None, __TIMEOUT)try:# Execute last commandif get_status() != __DFU_STATE_DFU_MANIFEST:print("Failed to reset device")# Release deviceusb.util.dispose_resources(__dev)except:passdef named(values, names):"""Creates a dict with `names` as fields, and `values` as values."""return dict(zip(names.split(), values))def consume(fmt, data, names):"""Parses the struct defined by `fmt` from `data`, stores the parsed fieldsinto a named tuple using `names`. Returns the named tuple, and the datawith the struct stripped off."""size = struct.calcsize(fmt)return named(struct.unpack(fmt, data[:size]), names), data[size:]def cstring(string):"""Extracts a null-terminated string from a byte array."""return string.decode("utf-8").split("0円", 1)[0]def compute_crc(data):"""Computes the CRC32 value for the data passed in."""return 0xFFFFFFFF & -zlib.crc32(data) - 1def read_dfu_file(filename):"""Reads a DFU file, and parses the individual elements from the file.Returns an array of elements. Each element is a dictionary with thefollowing keys:num - The element index.address - The address that the element data should be written to.size - The size of the element data.data - The element data.If an error occurs while parsing the file, then None is returned."""print("File: {}".format(filename))with open(filename, "rb") as fin:data = fin.read()crc = compute_crc(data[:-4])elements = []# Decode the DFU Prefix## <5sBIB# < little endian Endianness# 5s char[5] signature "DfuSe"# B uint8_t version 1# I uint32_t size Size of the DFU file (without suffix)# B uint8_t targets Number of targetsdfu_prefix, data = consume("<5sBIB", data, "signature version size targets")print(" %(signature)s v%(version)d, image size: %(size)d, ""targets: %(targets)d" % dfu_prefix)for target_idx in range(dfu_prefix["targets"]):# Decode the Image Prefix## <6sBI255s2I# < little endian Endianness# 6s char[6] signature "Target"# B uint8_t altsetting# I uint32_t named Bool indicating if a name was used# 255s char[255] name Name of the target# I uint32_t size Size of image (without prefix)# I uint32_t elements Number of elements in the imageimg_prefix, data = consume("<6sBI255s2I", data, "signature altsetting named name " "size elements")img_prefix["num"] = target_idxif img_prefix["named"]:img_prefix["name"] = cstring(img_prefix["name"])else:img_prefix["name"] = ""print(" %(signature)s %(num)d, alt setting: %(altsetting)s, "'name: "%(name)s", size: %(size)d, elements: %(elements)d' % img_prefix)target_size = img_prefix["size"]target_data = data[:target_size]data = data[target_size:]for elem_idx in range(img_prefix["elements"]):# Decode target prefix## <2I# < little endian Endianness# I uint32_t element Address# I uint32_t element Sizeelem_prefix, target_data = consume("<2I", target_data, "addr size")elem_prefix["num"] = elem_idxprint(" %(num)d, address: 0x%(addr)08x, size: %(size)d" % elem_prefix)elem_size = elem_prefix["size"]elem_data = target_data[:elem_size]target_data = target_data[elem_size:]elem_prefix["data"] = elem_dataelements.append(elem_prefix)if len(target_data):print("target %d PARSE ERROR" % target_idx)# Decode DFU Suffix## <4H3sBI# < little endian Endianness# H uint16_t device Firmware version# H uint16_t product# H uint16_t vendor# H uint16_t dfu 0x11a (DFU file format version)# 3s char[3] ufd "UFD"# B uint8_t len 16# I uint32_t crc32 Checksumdfu_suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")print(" usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, ""dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix)if crc != dfu_suffix["crc"]:print("CRC ERROR: computed crc32 is 0x%08x" % crc)returndata = data[16:]if data:print("PARSE ERROR")returnreturn elementsclass FilterDFU(object):"""Class for filtering USB devices to identify devices which are in DFUmode."""def __call__(self, device):for cfg in device:for intf in cfg:return intf.bInterfaceClass == 0xFE and intf.bInterfaceSubClass == 1def get_dfu_devices(*args, **kwargs):"""Returns a list of USB devices which are currently in DFU mode.Additional filters (like idProduct and idVendor) can be passed into refine the search."""# Convert to list for compatibility with newer PyUSBreturn list(usb.core.find(*args, find_all=True, custom_match=FilterDFU(), **kwargs))def get_memory_layout(device):"""Returns an array which identifies the memory layout. Each entryof the array will contain a dictionary with the following keys:addr - Address of this memory segment.last_addr - Last address contained within the memory segment.size - Size of the segment, in bytes.num_pages - Number of pages in the segment.page_size - Size of each page, in bytes."""cfg = device[0]intf = cfg[(0, 0)]mem_layout_str = get_string(device, intf.iInterface)mem_layout = mem_layout_str.split("/")result = []for mem_layout_index in range(1, len(mem_layout), 2):addr = int(mem_layout[mem_layout_index], 0)segments = mem_layout[mem_layout_index + 1].split(",")seg_re = re.compile(r"(\d+)\*(\d+)(.)(.)")for segment in segments:seg_match = seg_re.match(segment)num_pages = int(seg_match.groups()[0], 10)page_size = int(seg_match.groups()[1], 10)multiplier = seg_match.groups()[2]if multiplier == "K":page_size *= 1024if multiplier == "M":page_size *= 1024 * 1024size = num_pages * page_sizelast_addr = addr + size - 1result.append(named((addr, last_addr, size, num_pages, page_size),"addr last_addr size num_pages page_size",))addr += sizereturn resultdef list_dfu_devices(*args, **kwargs):"""Prints a lits of devices detected in DFU mode."""devices = get_dfu_devices(*args, **kwargs)if not devices:raise SystemExit("No DFU capable devices found")for device in devices:print("Bus {} Device {:03d}: ID {:04x}:{:04x}".format(device.bus, device.address, device.idVendor, device.idProduct))layout = get_memory_layout(device)print("Memory Layout")for entry in layout:print(" 0x{:x} {:2d} pages of {:3d}K bytes".format(entry["addr"], entry["num_pages"], entry["page_size"] // 1024))def write_elements(elements, mass_erase_used, progress=None):"""Writes the indicated elements into the target memory,erasing as needed."""mem_layout = get_memory_layout(__dev)for elem in elements:addr = elem["addr"]size = elem["size"]data = elem["data"]elem_size = sizeelem_addr = addrif progress:progress(elem_addr, 0, elem_size)while size > 0:write_size = sizeif not mass_erase_used:for segment in mem_layout:if addr >= segment["addr"] and addr <= segment["last_addr"]:# We found the page containing the address we want to# write, erase itpage_size = segment["page_size"]page_addr = addr & ~(page_size - 1)if addr + write_size > page_addr + page_size:write_size = page_addr + page_size - addrpage_erase(page_addr)breakwrite_memory(addr, data[:write_size], progress, elem_addr, elem_size)data = data[write_size:]addr += write_sizesize -= write_sizeif progress:progress(elem_addr, addr - elem_addr, elem_size)def cli_progress(addr, offset, size):"""Prints a progress report suitable for use on the command line."""width = 25done = offset * width // sizeprint("\r0x{:08x} {:7d} [{}{}] {:3d}% ".format(addr, size, "=" * done, " " * (width - done), offset * 100 // size),end="",)try:sys.stdout.flush()except OSError:pass # Ignore Windows CLI "WinError 87" on Python 3.6if offset == size:print("")def main():"""Test program for verifying this files functionality."""global __verboseglobal __VIDglobal __PID# Parse CMD argsparser = argparse.ArgumentParser(description="DFU Python Util")parser.add_argument("-l", "--list", help="list available DFU devices", action="store_true", default=False)parser.add_argument("--vid", help="USB Vendor ID", type=lambda x: int(x, 0), default=__VID)parser.add_argument("--pid", help="USB Product ID", type=lambda x: int(x, 0), default=__PID)parser.add_argument("-m", "--mass-erase", help="mass erase device", action="store_true", default=False)parser.add_argument("-u", "--upload", help="read file from DFU device", dest="path", default=False)parser.add_argument("-x", "--exit", help="Exit DFU", action="store_true", default=False)parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true", default=False)args = parser.parse_args()__verbose = args.verbose__VID = args.vid__PID = args.pidif args.list:list_dfu_devices(idVendor=__VID, idProduct=__PID)returninit()command_run = Falseif args.mass_erase:print("Mass erase...")mass_erase()command_run = Trueif args.path:elements = read_dfu_file(args.path)if not elements:print("No data in dfu file")returnprint("Writing memory...")write_elements(elements, args.mass_erase, progress=cli_progress)print("Exiting DFU...")exit_dfu()command_run = Trueif args.exit:print("Exiting DFU...")exit_dfu()command_run = Trueif command_run:print("Finished")else:print("No command specified")if __name__ == "__main__":main()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。