mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-19 22:19:10 +08:00
Merge pull request #153 from madcowswe/sam_versions
Add Board & Firmware versions
This commit is contained in:
@@ -2,7 +2,11 @@
|
||||
Please add a note of your changes below this heading if you make a Pull Request.
|
||||
|
||||
### Added
|
||||
* `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you can run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `board_version_[...]` properties.
|
||||
* bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties.
|
||||
|
||||
### Changed
|
||||
* The DFU script now verifies the flash after writing
|
||||
* Set thread priority of USB pump thread above protocol thread
|
||||
### Fixed
|
||||
* Enums now transported with correct underlying type on native protocol
|
||||
|
||||
@@ -28,6 +28,54 @@ bmp: all
|
||||
erase_config:
|
||||
openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ erase_address\ 0x80C0000\ 0x40000 -c reset\ run -c exit
|
||||
|
||||
# The one-time programmable memory stores the board version
|
||||
# has the following format:
|
||||
# - OTP format version (0xFE: version 1)
|
||||
# - vendor ID (01: ODrive Robotics - do not use this on custom incompatible hardware!)
|
||||
# - product ID (01: ODrive)
|
||||
# - hardware major version
|
||||
# - hardware minor version
|
||||
# - hardware variant (equal to the board nominal voltage)
|
||||
# Bits in the OTP can only ever be set to 0 but never back to 1.
|
||||
# Therefore do not try to run this command on the same board
|
||||
# twice with different data.
|
||||
#
|
||||
# This OpenOCD command is intended for a STM32F405 and does the following:
|
||||
# FLASH_KEYR = 0x45670123; // unlock FLASH_CR
|
||||
# FLASH_KEYR = 0xCDEF89AB; // unlock FLASH_CR
|
||||
# FLASH_CR = (1 << FLASH_CR_PG); // unlock flash memory
|
||||
# [write OTP]
|
||||
write_otp:
|
||||
ifeq ($(ODRV_FACTORY),TRUE)
|
||||
# Data:
|
||||
openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg \
|
||||
-c init \
|
||||
-c 'reset halt' \
|
||||
-c 'mww 0x40023C04 0x45670123' \
|
||||
-c 'mww 0x40023C04 0xCDEF89AB' \
|
||||
-c 'mww 0x40023C10 0x00000001' -c 'sleep 10' \
|
||||
-c 'mwb 0x1fff7800 0xFE' -c 'sleep 10' \
|
||||
-c 'mwb 0x1fff7801 0x01' -c 'sleep 10' \
|
||||
-c 'mwb 0x1fff7802 0x01' -c 'sleep 10' \
|
||||
-c 'mwb 0x1fff7803 3' -c 'sleep 10' \
|
||||
-c 'mwb 0x1fff7804 4' -c 'sleep 10' \
|
||||
-c 'mwb 0x1fff7805 48' -c 'sleep 10' \
|
||||
-c 'reset run' \
|
||||
-c exit
|
||||
|
||||
else
|
||||
@echo "The one-time programmable memory can only be"
|
||||
@echo "written ONCE on every board (what a surprise)."
|
||||
@echo "If you're on an ODrive v3.5 or later we already did this for you."
|
||||
@echo "Otherwise, if you're mentally ready for this irreversible action,"
|
||||
@echo "take the following steps:"
|
||||
@echo " 1. open the Makefile and look at the write_otp target"
|
||||
@echo " 2. understand the structure of the OTP"
|
||||
@echo " 3. edit the bytes that are written to match your board version"
|
||||
@echo "Run this command again, this time with ODRV_FACTORY=TRUE appended"
|
||||
@echo "to the command in the terminal"
|
||||
endif
|
||||
|
||||
clean:
|
||||
-rm -fR .dep $(BUILD_DIR)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "freertos_vars.h"
|
||||
#include "utils.h"
|
||||
#include "config.h"
|
||||
#include "../build/version.h" // autogenerated based on Git state
|
||||
|
||||
#ifdef ENABLE_LEGACY_PROTOCOL
|
||||
#include "legacy_commands.h"
|
||||
@@ -113,12 +114,47 @@ void enter_dfu_mode() {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
#if HW_VERSION_MAJOR == 3
|
||||
// Determine start address of the OTP struct:
|
||||
// The OTP is organized into 16-byte blocks.
|
||||
// If the first block starts with "0xfe" we use the first block.
|
||||
// If the first block starts with "0x00" and the second block starts with "0xfe",
|
||||
// we use the second block. This gives the user the chance to screw up once.
|
||||
// If none of the above is the case, we consider the OTP invalid (otp_ptr will be NULL).
|
||||
const uint8_t* otp_ptr =
|
||||
(*(uint8_t*)FLASH_OTP_BASE == 0xfe) ? (uint8_t*)FLASH_OTP_BASE :
|
||||
(*(uint8_t*)FLASH_OTP_BASE != 0x00) ? NULL :
|
||||
(*(uint8_t*)(FLASH_OTP_BASE + 0x10) != 0xfe) ? NULL :
|
||||
(uint8_t*)(FLASH_OTP_BASE + 0x10);
|
||||
|
||||
// Read hardware version from OTP if available, otherwise fall back
|
||||
// to software defined version.
|
||||
const uint8_t board_version_major = otp_ptr ? otp_ptr[3] : HW_VERSION_MAJOR;
|
||||
const uint8_t board_version_minor = otp_ptr ? otp_ptr[4] : HW_VERSION_MINOR;
|
||||
const uint8_t board_version_variant = otp_ptr ? otp_ptr[5] : HW_VERSION_VOLTAGE;
|
||||
#else
|
||||
#error "not implemented"
|
||||
#endif
|
||||
|
||||
// the corresponding macros are defined in the autogenerated version.h
|
||||
const uint8_t fw_version_major = FW_VERSION_MAJOR;
|
||||
const uint8_t fw_version_minor = FW_VERSION_MINOR;
|
||||
const uint8_t fw_version_revision = FW_VERSION_REVISION;
|
||||
const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise
|
||||
|
||||
// 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
|
||||
const Endpoint endpoints[] = {
|
||||
Endpoint::make_property("vbus_voltage", const_cast<const float*>(&vbus_voltage)),
|
||||
Endpoint::make_property("serial_number", const_cast<const uint64_t *>(&serial_number)),
|
||||
Endpoint::make_property("board_version_major", &board_version_major),
|
||||
Endpoint::make_property("board_version_minor", &board_version_minor),
|
||||
Endpoint::make_property("board_version_variant", &board_version_variant),
|
||||
Endpoint::make_property("fw_version_major", &fw_version_major),
|
||||
Endpoint::make_property("fw_version_minor", &fw_version_minor),
|
||||
Endpoint::make_property("fw_version_revision", &fw_version_revision),
|
||||
Endpoint::make_property("fw_version_unreleased", &fw_version_unreleased),
|
||||
Endpoint::make_function("run_anticogging_calibration", &motors_run_anticogging_calibration_func),
|
||||
// No parameters, but still requires a close_tree()
|
||||
Endpoint::close_tree(),
|
||||
|
||||
@@ -120,6 +120,11 @@ build{
|
||||
includes=stm_includes
|
||||
}
|
||||
|
||||
tup.frule{
|
||||
command='bash dump_version.sh %o',
|
||||
outputs={'build/version.h'}
|
||||
}
|
||||
|
||||
build{
|
||||
name='ODriveFirmware',
|
||||
toolchains={toolchain},
|
||||
|
||||
+2
-1
@@ -67,8 +67,9 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags)
|
||||
else
|
||||
extra_outputs = {}
|
||||
end
|
||||
if src == 'MotorControl/commands.cpp' then extra_inputs = 'build/version.h' end -- TODO: fix hack
|
||||
tup.frule{
|
||||
inputs= { src },
|
||||
inputs= { src, extra_inputs=extra_inputs },
|
||||
command=compiler..' -c %f '..
|
||||
tostring(compiler_flags)..' '.. -- CFLAGS for this compiler
|
||||
tostring(inc_flags)..' '.. -- CFLAGS for this translation unit
|
||||
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -eq 1 ]; then
|
||||
OUTPUT="$1"
|
||||
else
|
||||
OUTPUT="/dev/stdout"
|
||||
fi
|
||||
|
||||
# The git root lies outside of the tup root
|
||||
export GIT_DISCOVERY_ACROSS_FILESYSTEM=1
|
||||
|
||||
# Get a description of the current Git state
|
||||
# Examples of what this string may become:
|
||||
# fw-v0.3.6 The current commit is exactly at tag "fw-v0.3.6"
|
||||
# There may or may not be untracked files in the
|
||||
# working directory.
|
||||
# fw-v0.3.6* The current commit is at tag "fw-v0.3.6" and there
|
||||
# are uncommitted changes in the working directory.
|
||||
# fw-v0.3.6-4-g3703ae5 The working directory at a commit with hash 3703ae5,
|
||||
# 4 commits ahead of tag fw-v0.3.6 and clean.
|
||||
FW_VERSION="$(git describe --always --tags --dirty=* || echo "[unknown commit]")"
|
||||
|
||||
# Extract version numbers
|
||||
FW_VERSION_MAJOR="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\1/p' <<< "$FW_VERSION")"
|
||||
FW_VERSION_MINOR="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\2/p' <<< "$FW_VERSION")"
|
||||
FW_VERSION_REVISION="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\3/p' <<< "$FW_VERSION")"
|
||||
FW_VERSION_SUFFIX="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\4/p' <<< "$FW_VERSION")"
|
||||
|
||||
# Fall back to 0 if the verions does not match the expected pattern
|
||||
[ "$FW_VERSION_MAJOR" == "" ] && FW_VERSION_MAJOR=0
|
||||
[ "$FW_VERSION_MINOR" == "" ] && FW_VERSION_MINOR=0
|
||||
[ "$FW_VERSION_REVISION" == "" ] && FW_VERSION_REVISION=0
|
||||
|
||||
if [ "$FW_VERSION_SUFFIX" == "" ]; then
|
||||
FW_VERSION_UNRELEASED=0
|
||||
else
|
||||
FW_VERSION_UNRELEASED=1
|
||||
fi
|
||||
|
||||
cat > "$OUTPUT" <<EOF
|
||||
#define FW_VERSION "$FW_VERSION"
|
||||
#define FW_VERSION_MAJOR $FW_VERSION_MAJOR
|
||||
#define FW_VERSION_MINOR $FW_VERSION_MINOR
|
||||
#define FW_VERSION_REVISION $FW_VERSION_REVISION
|
||||
#define FW_VERSION_UNRELEASED $FW_VERSION_UNRELEASED
|
||||
EOF
|
||||
+185
-85
@@ -9,9 +9,10 @@ import time
|
||||
import threading
|
||||
import platform
|
||||
import struct
|
||||
import array
|
||||
import fractions
|
||||
import dfuse
|
||||
import usb.core
|
||||
import usb.util
|
||||
import odrive.core
|
||||
|
||||
# We are interactively printing status messages, so flush by default
|
||||
@@ -27,21 +28,26 @@ except:
|
||||
|
||||
|
||||
SIZE_MULTIPLIERS = {' ': 1, 'K': 1024, 'M' : 1024*1024}
|
||||
TRANSFER_SIZE = 2048
|
||||
MAX_TRANSFER_SIZE = 2048
|
||||
|
||||
|
||||
def load_sectors(dfudev, hexfile):
|
||||
def get_device_sectors(dfudev):
|
||||
"""
|
||||
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.
|
||||
Returns a list of all sectors on the device.
|
||||
Each sector is represented as a dictionary with the following keys:
|
||||
- name: name of the associated memory region (e.g. "Internal Flash")
|
||||
- alt: USB alternate setting associated with this memory region
|
||||
- addr: Start address of the sector (e.g. 0x08004000 for the second flash sectors)
|
||||
- baseaddr: Start address of the memory region associated with the sector
|
||||
(e.g. 0x08000000 for all flash sectors)
|
||||
- len: Number of bytes in the sector
|
||||
"""
|
||||
|
||||
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
|
||||
label, baseaddr, layout = name.split('/')
|
||||
baseaddr = int(baseaddr, 0) # convert hex to decimal
|
||||
addr = baseaddr
|
||||
|
||||
for sector in layout.split(','):
|
||||
repeat, size = map(int, sector[:-2].split('*'))
|
||||
@@ -49,99 +55,145 @@ def load_sectors(dfudev, hexfile):
|
||||
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)
|
||||
}
|
||||
# TODO: verify if the section is writable
|
||||
yield {
|
||||
'name': label.strip().strip('@'),
|
||||
'alt': alt,
|
||||
'baseaddr': baseaddr,
|
||||
'addr': addr,
|
||||
'len': size,
|
||||
'mode': mode
|
||||
}
|
||||
|
||||
addr += size
|
||||
repeat -= 1
|
||||
|
||||
def populate_sectors(sectors, hexfile):
|
||||
"""
|
||||
Checks for which on-device sectors there is data in the hex file and
|
||||
returns a (sector, data) tuple for each touched sector where data
|
||||
is a byte array of the same size as the sector.
|
||||
"""
|
||||
for sector in sectors:
|
||||
addr = sector['addr']
|
||||
size = sector['len']
|
||||
# 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 (sector, hexfile.tobinarray(addr, addr + size - 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 clear_error(dfudev)
|
||||
def set_address_safe(dfudef, addr):
|
||||
dfudev.set_address(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)
|
||||
# take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE
|
||||
dfudev.abort()
|
||||
status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_SYNC)
|
||||
if status[1] != dfuse.DfuState.DFU_IDLE:
|
||||
raise RuntimeError("An error occured. Device Status: %r" % status)
|
||||
|
||||
|
||||
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'])
|
||||
def erase(dfudev, sector):
|
||||
set_alternate_safe(dfudev, sector['alt'])
|
||||
dfudev.erase(sector['addr'])
|
||||
status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32)
|
||||
if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE:
|
||||
raise RuntimeError("An error occured. Device Status: %r" % status)
|
||||
|
||||
def flash(dfudev, sector, data):
|
||||
set_alternate_safe(dfudev, sector['alt'])
|
||||
set_address_safe(dfudev, sector['addr'])
|
||||
|
||||
transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE)
|
||||
|
||||
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)
|
||||
|
||||
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 ')
|
||||
|
||||
def read(dfudev, sector):
|
||||
"""
|
||||
Reads data from the specified sector
|
||||
Returns: a byte array containing the data
|
||||
"""
|
||||
set_alternate_safe(dfudev, sector['alt'])
|
||||
set_address_safe(dfudev, sector['addr'])
|
||||
|
||||
# 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 ')
|
||||
transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE)
|
||||
#blocknum_offset = int((sector['addr'] - sector['baseaddr']) / transfer_size)
|
||||
|
||||
|
||||
data = array.array(u'B')
|
||||
for blocknum in range(int(sector['len'] / transfer_size)):
|
||||
#print('read at {:08X}'.format(sector['addr'] + blocknum * TRANSFER_SIZE))
|
||||
deviceBlock = dfudev.read(blocknum, transfer_size)
|
||||
data.extend(deviceBlock)
|
||||
dfudev.abort() # take device into DFU_IDLE
|
||||
return data
|
||||
|
||||
def get_first_mismatch_index(array1, array2):
|
||||
"""
|
||||
Compares two arrays and returns the index of the
|
||||
first unequal item or None if both arrays are equal
|
||||
"""
|
||||
if len(array1) != len(array2):
|
||||
raise Exception("arrays must be same size")
|
||||
for pos in range(len(array1)):
|
||||
if (array1[pos] != array2[pos]):
|
||||
return pos
|
||||
return None
|
||||
|
||||
|
||||
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]))
|
||||
set_address_safe(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]))
|
||||
|
||||
|
||||
def dump_otp():
|
||||
"""
|
||||
Dumps the contents of the one-time-programmable
|
||||
memory. The OTP will be used in future versions of
|
||||
this script to determine the board version.
|
||||
"""
|
||||
# 512 Byte OTP
|
||||
otp_sector = [s for s in sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0]
|
||||
data = read(dfudev, otp_sector)
|
||||
print(' '.join('{:02X}'.format(x) for x in data))
|
||||
|
||||
# 16 lock bytes
|
||||
otp_lock_sector = [s for s in sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7A00][0]
|
||||
data = read(dfudev, otp_lock_sector)
|
||||
print(' '.join('{:02X}'.format(x) for x in data))
|
||||
|
||||
def str_to_uuid(uuid):
|
||||
uuid = bytearray.fromhex(uuid.replace('-', ''))
|
||||
return struct.unpack('>I', uuid[0:4]), struct.unpack('>I', uuid[4:8]), struct.unpack('>I', uuid[8:12])
|
||||
@@ -214,6 +266,8 @@ def put_odrive_into_dfu_mode_thread(cancellation_token):
|
||||
parser = argparse.ArgumentParser(description="Program an STM32 in DFU mode. The device can be identified either by it's serial number or UUID."
|
||||
"You can list all connected devices by running"
|
||||
"(lsusb -d 1209:0d32 -v; lsusb -d 0483:df11 -v) | grep iSerial")
|
||||
parser.add_argument("-v", "--verbose", action="store_true",
|
||||
help="print debug information")
|
||||
parser.add_argument('file', metavar='HEX', help='the .hex file to be flashed')
|
||||
parser.add_argument("-u", "--uuid",
|
||||
help="The 12-byte UUID of the device. This is a hexadecimal number of the format"
|
||||
@@ -260,17 +314,63 @@ try:
|
||||
|
||||
dfudev = dfuse.DfuDevice(stm_device)
|
||||
|
||||
sectors = list(get_device_sectors(dfudev))
|
||||
|
||||
if (args.verbose):
|
||||
print("Sectors on device: ")
|
||||
for sector in sectors:
|
||||
print(" {:08X} to {:08X} ({})".format(
|
||||
sector['addr'],
|
||||
sector['addr'] + sector['len'] - 1,
|
||||
sector['name']))
|
||||
|
||||
# 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))
|
||||
touched_sectors = list(populate_sectors(sectors, hexfile))
|
||||
|
||||
if (args.verbose):
|
||||
print("The following sectors will be flashed: ")
|
||||
for sector,_ in touched_sectors:
|
||||
print(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + sector['len'] - 1))
|
||||
|
||||
if (args.verbose):
|
||||
print("OTP:")
|
||||
dump_otp()
|
||||
|
||||
# Erase
|
||||
try:
|
||||
for i, (sector, data) in enumerate(touched_sectors):
|
||||
print("Erasing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True)
|
||||
erase(dfudev, sector)
|
||||
print('Erasing... done \r', end='', flush=True)
|
||||
finally:
|
||||
print('', flush=True)
|
||||
|
||||
# Flash
|
||||
try:
|
||||
for i, (sector, data) in enumerate(touched_sectors):
|
||||
print("Flashing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True)
|
||||
flash(dfudev, sector, data)
|
||||
print('Flashing... done \r', end='', flush=True)
|
||||
finally:
|
||||
print('', flush=True)
|
||||
|
||||
# Verify
|
||||
try:
|
||||
for i, (sector, expected_data) in enumerate(touched_sectors):
|
||||
print("Verifying... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True)
|
||||
observed_data = read(dfudev, sector)
|
||||
mismatch_pos = get_first_mismatch_index(observed_data, expected_data)
|
||||
if not mismatch_pos is None:
|
||||
mismatch_pos -= mismatch_pos % 16
|
||||
observed_snippet = ' '.join('{:02X}'.format(x) for x in observed_data[mismatch_pos:mismatch_pos+16])
|
||||
expected_snippet = ' '.join('{:02X}'.format(x) for x in expected_data[mismatch_pos:mismatch_pos+16])
|
||||
raise RuntimeError("Verification failed around address 0x{:08X}:\n".format(sector['addr'] + mismatch_pos) +
|
||||
" expected: " + expected_snippet + "\n"
|
||||
" observed: " + observed_snippet)
|
||||
print('Verifying... done \r', end='', flush=True)
|
||||
finally:
|
||||
print('', flush=True)
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -59,6 +59,9 @@ class DfuDevice:
|
||||
def get_state(self):
|
||||
return self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1)[0]
|
||||
|
||||
def abort(self):
|
||||
self.control_msg(DFU_REQUEST_RECEIVE, DFU_ABORT, 0, 0)
|
||||
|
||||
def set_address(self, ap):
|
||||
return self.dnload(0x0, [0x21] + address_to_4bytes(ap))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user