Add DFU over USB

`make dfu` now invokes a python script which puts the ODrive into DFU mode uploads the .hex file to the device via USB.
This commit is contained in:
Samuel Sadok
2018-02-22 19:49:10 -08:00
parent 91f36963df
commit dde6ba5a57
4 changed files with 237 additions and 1 deletions
+3
View File
@@ -194,6 +194,9 @@ clean:
flash: $(BUILD_DIR)/$(TARGET).elf
openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ write_image\ erase\ $(BUILD_DIR)/$(TARGET).elf -c reset\ run -c exit
dfu: $(BUILD_DIR)/$(TARGET).hex
../tools/dfu.py $(BUILD_DIR)/$(TARGET).hex
gdb: $(BUILD_DIR)/$(TARGET).elf
arm-none-eabi-gdb $(BUILD_DIR)/$(TARGET).elf -x openocd.gdbinit
+8 -1
View File
@@ -88,6 +88,11 @@ void motors_1_set_current_setpoint_func(void) {
motors[1].set_current_setpoint_args.current_setpoint);
}
void enter_dfu_mode() {
*((unsigned long *)0x2001C000) = 0xDEADBEEF;
NVIC_SystemReset();
}
// This table specifies which fields and functions are exposed on the USB and UART ports.
// TODO: Autogenerate this table. It will come up again very soon in the Arduino library.
// clang-format off
@@ -210,7 +215,9 @@ const Endpoint endpoints[] = {
Endpoint::make_function("set_current_setpoint", &motors_1_set_current_setpoint_func),
Endpoint::make_property("current_setpoint", &motors[1].set_current_setpoint_args.current_setpoint),
Endpoint::close_tree(),
Endpoint::close_tree() // motor1
Endpoint::close_tree(), // motor1
Endpoint::make_function("enter_dfu_mode", &enter_dfu_mode),
Endpoint::close_tree() // enter_dfu_mode
};
// clang-format on
+16
View File
@@ -80,6 +80,14 @@ void MX_FREERTOS_Init(void);
/* USER CODE BEGIN 0 */
void jump_to_builtin_bootloader(void) {
__set_MSP(0x20001000);
// http://www.st.com/content/ccc/resource/technical/document/application_note/6a/17/92/02/58/98/45/0c/CD00264379.pdf/files/CD00264379.pdf
void (*builtin_bootloader)(void) = (void (*)(void))(*((uint32_t *)0x1FFF0004));
builtin_bootloader();
for (;;);
}
/* USER CODE END 0 */
int main(void)
@@ -87,6 +95,14 @@ int main(void)
/* USER CODE BEGIN 1 */
/* We could jump to the bootloader directly on demand without rebooting
but that requires us to reset several peripherals and interrupts for it
to function correctly. Therefore it's easier to just reset the entire chip. */
if(*((unsigned long *)0x2001C000) == 0xDEADBEEF) {
*((unsigned long *)0x2001C000) = 0xCAFEFEED; //Reset bootloader trigger
jump_to_builtin_bootloader();
}
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
Executable
+210
View File
@@ -0,0 +1,210 @@
#!/usr/bin/env python3
"""
Tool for flashing .hex files to the ODrive via the STM built-in USB DFU mode.
"""
import argparse
import sys
import time
import dfuse
import usb.core
import usb.util
import odrive.core
try:
from intelhex import IntelHex
except:
print("You need intelhex for this (sudo pip install IntelHex)", file=sys.stderr)
sys.exit(1)
SIZE_MULTIPLIERS = {' ': 1, 'K': 1024, 'M' : 1024*1024}
TRANSFER_SIZE = 2048
def load_sectors(dfudev, hexfile):
"""
Checks for which on-device sectors there is data in the hex file and
returns a sector object for each touched sector. Each sector object
is filled with the associated data from the hex file.
"""
for name, alt in dfudev.alternates():
# example for name:
# '@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg'
label, addr, layout = name.split('/')
addr = int(addr, 0) # convert hex to decimal
for sector in layout.split(','):
repeat, size = map(int, sector[:-2].split('*'))
size *= SIZE_MULTIPLIERS[sector[-2].upper()]
mode = sector[-1]
while repeat > 0:
# check if any segment from the hexfile overlaps with this sector
touched = False
for (start, end) in hexfile.segments():
if start < addr and end > addr:
touched = True
break
elif start >= addr and start < addr + size:
touched = True
break
if touched:
# TODO: verify if the section is writable
yield {
'alt': alt,
'addr': addr,
'data': hexfile.tobinarray(addr, addr + size - 1)
}
addr += size
repeat -= 1
def set_alternate_safe(dfudev, alt):
dfudev.set_alternate(alt)
if dfudev.get_state() == dfuse.DfuState.DFU_ERROR:
dfudev.clear_status()
dfudev.wait_while_state(dfuse.DfuState.DFU_ERROR)
def erase(dfudev, sectors):
for i, sector in enumerate(sectors):
print("Erasing... (sector {}/{}) \r".format(i, len(sectors)), end='', flush=True)
set_alternate_safe(dfudev, sector['alt'])
dfudev.erase(sector['addr'])
status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY, timeout=len(sector['data'])/32)
if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
print('Erasing... done ')
def flash(dfudev, sectors):
for i, sector in enumerate(sectors):
print("Flashing... (sector {}/{}) \r".format(i, len(sectors)), end='', flush=True)
set_alternate_safe(dfudev, sector['alt'])
dfudev.set_address(sector['addr'])
status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY)
if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
data = sector['data']
blocks = [data[i:i + TRANSFER_SIZE] for i in range(0, len(data), TRANSFER_SIZE)]
for blocknum, block in enumerate(blocks):
#print('write to {:08X} ({} bytes)'.format(
# sector['addr'] + blocknum * TRANSFER_SIZE, len(block)))
dfudev.write(blocknum, block)
status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY)
if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
print('Flashing... done ')
# Results in usb.core.USBError. Probably the device should go to dfuIDLE first, but how?
#def verify(dfudev, sectors):
# for i, sector in enumerate(sectors):
# print("Verifying... (sector {}/{}) \r".format(i, len(sectors)), end='', flush=True)
# set_alternate_safe(dfudev, sector['alt'])
# dfudev.set_address(sector['addr'])
# status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY)
# if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE:
# raise RuntimeError("An error occured. Device Status: %r" % status)
#
# print("state: {}".format(dfudev.get_state()))
# #dfudev.clear_status()
# print("state: {}".format(dfudev.get_state()))
# data = sector['data']
# blocks = [data[i:i + TRANSFER_SIZE] for i in range(0, len(data), TRANSFER_SIZE)]
# for blocknum, block in enumerate(blocks):
# print('read at {:08X}'.format(sector['addr'] + blocknum * TRANSFER_SIZE))
# deviceBlock = dfudev.read(blocknum, TRANSFER_SIZE)
# print(dfudev.get_state())
# if (deviceBlock != block):
# raise RuntimeError("verification failed at address {:08X}".format(sector['addr'] + blocknum * TRANSFER_SIZE))
# print('Verifying... done ')
def jump_to_application(dfudev, address):
dfudev.set_address(address)
status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY)
if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: {}".format(status[1]))
dfudev.leave()
status = dfudev.wait_while_state(dfuse.DfuState.DFU_MANIFEST_SYNC)
if status[1] != dfuse.DfuState.DFU_MANIFEST:
raise RuntimeError("An error occured. Device Status: {}".format(status[1]))
### BEGINNING OF APPLICATION ###
# parse arguments
parser = argparse.ArgumentParser(description='Program an STM32 in DFU mode.')
parser.add_argument('file', metavar='HEX', help='the .hex file to be flashed')
args = parser.parse_args()
# load hex file
hexfile = IntelHex(args.file)
print("Contiguous segments in hex file:")
for start, end in hexfile.segments():
print(" {:08X} to {:08X}".format(start, end - 1))
# find an STM32 in DFU mode (if there is none, find an ODrive and put it in DFU mode)
usbdev = usb.core.find(idVendor=0x0483, idProduct=0xdf11)
if usbdev is None:
# Find a connected ODrive (this will block until you connect one)
print("Waiting for ODrive...")
my_drive = odrive.core.find_any(consider_usb=True, consider_serial=False)
print("Putting device into DFU mode...")
try:
my_drive.enter_dfu_mode()
except usb.core.USBError as ex:
if ex.errno != 32:
raise ex
time.sleep(1.0)
usbdev = usb.core.find(idVendor=0x0483, idProduct=0xdf11)
if usbdev is None:
raise ValueError('No STM32 DfuSe device found.')
dfudev = dfuse.DfuDevice(usbdev)
# fill sectors with data
sectors = list(load_sectors(dfudev, hexfile))
print("Sectors to be flashed: ")
for sector in sectors:
print(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + len(sector['data']) - 1))
# flash!
erase(dfudev, sectors)
flash(dfudev, sectors)
#verify(dfudev, sectors)
# If the flash operation failed for some reason, your device is bricked now.
# You can unbrick it as long as the device remains powered on.
# (or always with an STLink)
# So for debugging you should comment this last part out.
# Jump to application
jump_to_application(dfudev, 0x08000000)
# Note: the flashed image can be verified using: (0x12000 is the number of bytes to read)
# $ openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c flash\ read_bank\ 0\ image.bin\ 0\ 0x12000 -c exit
# $ hexdump -C image.bin > image.bin.txt
#
# If you compare this with a reference image that was flashed with the STLink, you will see
# minor differences. This is because this script fills undefined sections with 0xff.
# $ diff image_ref.bin.txt image.bin.txt
# 21c21
# < *
# ---
# > 00000180 d9 47 00 08 d9 47 00 08 ff ff ff ff ff ff ff ff |.G...G..........|
# 2553c2553
# < 00009fc0 9e 46 70 47 00 00 00 00 52 20 96 3c 46 76 50 76 |.FpG....R .<FvPv|
# ---
# > 00009fc0 9e 46 70 47 ff ff ff ff 52 20 96 3c 46 76 50 76 |.FpG....R .<FvPv|