diff --git a/usr/pydfu.py b/usr/pydfu.py index 4a14bf574..af805b4ce 100755 --- a/usr/pydfu.py +++ b/usr/pydfu.py @@ -1,20 +1,29 @@ #!/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. -# -# DFU util. -# See app note AN3156. +# 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 requiringdfu-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 re import struct -import sys,time +import sys import usb.core import usb.util -import argparse +import zlib # VID/PID -__VID=0x0483 -__PID=0xdf11 +__VID = 0x0483 +__PID = 0xdf11 # USB request __TIMEOUT __TIMEOUT = 4000 @@ -41,6 +50,8 @@ __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 @@ -50,11 +61,16 @@ __verbose = None # USB DFU interface __DFU_INTERFACE = 0 + def init(): + """Initializes the found DFU device so that we can program it.""" global __dev - __dev = usb.core.find(idVendor=__VID, idProduct=__PID) - if __dev is None: + devices = 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] # Claim DFU interface usb.util.claim_interface(__dev, __DFU_INTERFACE) @@ -62,84 +78,114 @@ def init(): # Clear status clr_status() + def clr_status(): - __dev.ctrl_transfer(0x21, __DFU_CLRSTATUS, 0, __DFU_INTERFACE, None, __TIMEOUT) + """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(): - stat =__dev.ctrl_transfer(0xA1, __DFU_GETSTATUS, 0, __DFU_INTERFACE, 6, 20000) + """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) + __dev.ctrl_transfer(0x21, __DFU_DNLOAD, 0, __DFU_INTERFACE, + "\x41", __TIMEOUT) # Execute last command - if (get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY): + if get_status() != __DFU_STATE_DFU_DOWNLOAD_BUSY: raise Exception("DFU: erase failed") # Check command state - if (get_status() != __DFU_STATE_DFU_DOWNLOAD_IDLE): + 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)) + 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="") + sys.stdout.flush() + 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("-u", "--upload", help="read file from DFU device", action="store_true", default=False) - parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true", default=False) + #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 - with open(args.path, 'r') as fin: - buf= fin.read() + if args.list: + list_dfu_devices(idVendor=__VID, idProduct=__PID) + return - - print("Init DFU...") init() - #to erase pages: - # erase_page(0x08004000) - # erase_page(0x08020000) - # erase_page(...) - print ("Mass erase...") - mass_erase() + if args.mass_erase: + print ("Mass erase...") + mass_erase() - print("Writing memory...") - write_memory(buf) + 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() + print("Exiting DFU...") + exit_dfu() + return + + print("No command specified") + +if __name__ == '__main__': + main()