From 842210f53a24666206caaff135cc2dbdc259e1e3 Mon Sep 17 00:00:00 2001 From: Dave Hylands Date: Tue, 21 Oct 2014 22:27:33 -0700 Subject: [PATCH] Add pydfu.py to the micropython tree. Use dfu_util bgy default You can do: make USE_PYDFU=1 deploy to use pydfu.py --- stmhal/Makefile | 6 + tools/pydfu.py | 527 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 533 insertions(+) create mode 100755 tools/pydfu.py diff --git a/stmhal/Makefile b/stmhal/Makefile index 7e825279fa..b3b2d7a9b4 100644 --- a/stmhal/Makefile +++ b/stmhal/Makefile @@ -24,6 +24,8 @@ USBDEV_DIR=usbdev FATFS_DIR=fatfs DFU=../tools/dfu.py # may need to prefix dfu-util with sudo +USE_PYDFU ?= 0 +PYDFU = ../tools/pydfu.py DFU_UTIL ?= dfu-util DEVICE=0483:df11 @@ -249,7 +251,11 @@ all: $(BUILD)/firmware.dfu $(BUILD)/firmware.hex deploy: $(BUILD)/firmware.dfu $(ECHO) "Writing $< to the board" +ifeq ($(USE_PYDFU),1) + $(Q)$(PYTHON) $(PYDFU) -u $< +else $(Q)$(DFU_UTIL) -a 0 -d $(DEVICE) -D $< +endif $(BUILD)/firmware.dfu: $(BUILD)/firmware.elf $(ECHO) "Create $@" diff --git a/tools/pydfu.py b/tools/pydfu.py new file mode 100755 index 0000000000..af805b4ce1 --- /dev/null +++ b/tools/pydfu.py @@ -0,0 +1,527 @@ +#!/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 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 +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 + +__verbose = None + +# USB DFU interface +__DFU_INTERFACE = 0 + + +def init(): + """Initializes the found DFU device so that we can program it.""" + global __dev + 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) + + # Clear status + clr_status() + + +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="") + 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( + "-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()