#!/usr/bin/env python # This file is part of the OpenMV project. # Copyright (c) 2013/2014 Ibrahim Abdelkader # This work is licensed under the MIT license, see the file LICENSE for # details. """This module implements enough functionality to program the STM32F4xx over DFU, 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_function import argparse import collections import re import struct import sys import usb.core import usb.util import 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 # 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 use import inspect getargspec = getattr(inspect, 'getfullargspec', inspect.getargspec) if 'length' in getargspec(usb.util.get_string).args: # PyUSB 1.0.0.b1 has the length argument def get_string(dev, index): return usb.util.get_string(dev, 255, index) else: # PyUSB 1.0.0.b2 dropped the length argument def 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(' 1: raise ValueError("Multiple DFU devices found") __dev = devices[0] __dev.set_configuration() # Claim DFU interface usb.util.claim_interface(__dev, __DFU_INTERFACE) # Find the DFU configuration descriptor, either in the device or interfaces __cfg_descr = None for cfg in __dev.configurations(): __cfg_descr = find_dfu_cfg_descr(cfg.extra_descriptors) if __cfg_descr: break for itf in cfg.interfaces(): __cfg_descr = find_dfu_cfg_descr(itf.extra_descriptors) if __cfg_descr: break # Get device into idle state for attempt in range(4): status = get_status() if status == __DFU_STATE_DFU_IDLE: break elif (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) # print (__DFU_STAT[stat[4]], stat) return stat[4] 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 command if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: raise Exception("DFU: erase failed") # Check command state if get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE: raise Exception("DFU: erase failed") def page_erase(addr): """Erases a single page.""" if __verbose: print("Erasing page: 0x%x..." % (addr)) # Send DNLOAD with first byte=0x41 and page address buf = struct.pack(" 0: write_size = size if 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 it page_size = segment['page_size'] page_addr = addr & ~(page_size - 1) if addr + write_size > page_addr + page_size: write_size = page_addr + page_size - addr page_erase(page_addr) break write_memory(addr, data[:write_size], progress, elem_addr, elem_size) data = data[write_size:] addr += write_size size -= write_size if 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 = 25 done = offset * width // size print("\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.6 if offset == size: print("") def main(): """Test program for verifying this files functionality.""" global __verbose # Parse CMD args parser = argparse.ArgumentParser(description='DFU Python Util') #parser.add_argument("path", help="file path") parser.add_argument( "-l", "--list", help="list available DFU devices", action="store_true", default=False ) 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( "-v", "--verbose", help="increase output verbosity", action="store_true", default=False ) args = parser.parse_args() __verbose = args.verbose if args.list: list_dfu_devices(idVendor=__VID, idProduct=__PID) return init() if args.mass_erase: print ("Mass erase...") mass_erase() if args.path: elements = read_dfu_file(args.path) if not elements: return print("Writing memory...") write_elements(elements, args.mass_erase, progress=cli_progress) print("Exiting DFU...") exit_dfu() return print("No command specified") if __name__ == '__main__': main()