mirror of https://github.com/ArduPilot/ardupilot
512 lines
16 KiB
Python
512 lines
16 KiB
Python
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
# vim: sw=4:ts=4:si:et:enc=utf-8
|
|
|
|
# Author: Ivan A-R <ivan@tuxotronic.org>
|
|
# Project page: http://tuxotronic.org/wiki/projects/stm32loader
|
|
#
|
|
# This file is part of stm32loader.
|
|
#
|
|
# stm32loader is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free
|
|
# Software Foundation; either version 3, or (at your option) any later
|
|
# version.
|
|
#
|
|
# stm32loader is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
# for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with stm32loader; see the file COPYING3. If not see
|
|
# <http://www.gnu.org/licenses/>.
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
import getopt
|
|
import serial
|
|
import time
|
|
import glob
|
|
import tempfile
|
|
import os
|
|
import subprocess
|
|
|
|
try:
|
|
from progressbar import *
|
|
usepbar = 1
|
|
except:
|
|
usepbar = 0
|
|
|
|
try:
|
|
xrange # Python 2
|
|
except NameError:
|
|
xrange = range # Python 3
|
|
|
|
# Verbose level
|
|
QUIET = 20
|
|
|
|
def mdebug(level, message):
|
|
if QUIET >= level:
|
|
print(message, file=sys.stderr)
|
|
|
|
|
|
class CmdException(Exception):
|
|
pass
|
|
|
|
class CommandInterface:
|
|
def open(self, aport='/dev/tty.usbserial-FTD3TMCH', abaudrate=115200) :
|
|
self.sp = serial.Serial(
|
|
port=aport,
|
|
baudrate=abaudrate, # baudrate
|
|
bytesize=8, # number of databits
|
|
parity=serial.PARITY_EVEN,
|
|
stopbits=1,
|
|
xonxoff=0, # enable software flow control
|
|
rtscts=0, # disable RTS/CTS flow control
|
|
timeout=0.5 # set a timeout value, None for waiting forever
|
|
)
|
|
|
|
|
|
def _wait_for_ask(self, info = ""):
|
|
got = self.sp.read(1)
|
|
|
|
if not got:
|
|
raise CmdException("No response to %s" % info)
|
|
|
|
# wait for ask
|
|
ask = ord(got)
|
|
|
|
if ask == 0x79:
|
|
# ACK
|
|
return 1
|
|
elif ask == 0x1F:
|
|
# NACK
|
|
raise CmdException("Chip replied with a NACK during %s" % info)
|
|
|
|
# Unknown response
|
|
raise CmdException("Unrecognised response 0x%x to %s" % (ask, info))
|
|
|
|
def reset(self):
|
|
self.sp.setDTR(0)
|
|
time.sleep(0.1)
|
|
self.sp.setDTR(1)
|
|
time.sleep(0.5)
|
|
|
|
def initChip(self):
|
|
# Set boot
|
|
self.sp.setRTS(0)
|
|
self.reset()
|
|
|
|
# Be a bit more persistent when trying to initialise the chip
|
|
stop = time.time() + 5.0
|
|
|
|
while time.time() <= stop:
|
|
self.sp.write('\x7f')
|
|
|
|
got = self.sp.read()
|
|
|
|
# The chip will ACK a sync the very first time and
|
|
# NACK it every time afterwards
|
|
if got and got in '\x79\x1f':
|
|
# Synced up
|
|
return
|
|
|
|
raise CmdException('No response while trying to sync')
|
|
|
|
def releaseChip(self):
|
|
self.sp.setRTS(1)
|
|
self.reset()
|
|
|
|
def cmdGeneric(self, cmd):
|
|
self.sp.write(chr(cmd))
|
|
self.sp.write(chr(cmd ^ 0xFF)) # Control byte
|
|
return self._wait_for_ask(hex(cmd))
|
|
|
|
def cmdGet(self):
|
|
if self.cmdGeneric(0x00):
|
|
mdebug(10, "*** Get command");
|
|
len = ord(self.sp.read())
|
|
version = ord(self.sp.read())
|
|
mdebug(10, " Bootloader version: "+hex(version))
|
|
dat = map(lambda c: hex(ord(c)), self.sp.read(len))
|
|
mdebug(10, " Available commands: "+str(dat))
|
|
self._wait_for_ask("0x00 end")
|
|
return version
|
|
else:
|
|
raise CmdException("Get (0x00) failed")
|
|
|
|
def cmdGetVersion(self):
|
|
if self.cmdGeneric(0x01):
|
|
mdebug(10, "*** GetVersion command")
|
|
version = ord(self.sp.read())
|
|
self.sp.read(2)
|
|
self._wait_for_ask("0x01 end")
|
|
mdebug(10, " Bootloader version: "+hex(version))
|
|
return version
|
|
else:
|
|
raise CmdException("GetVersion (0x01) failed")
|
|
|
|
def cmdGetID(self):
|
|
if self.cmdGeneric(0x02):
|
|
mdebug(10, "*** GetID command")
|
|
len = ord(self.sp.read())
|
|
id = self.sp.read(len+1)
|
|
self._wait_for_ask("0x02 end")
|
|
return id
|
|
else:
|
|
raise CmdException("GetID (0x02) failed")
|
|
|
|
|
|
def _encode_addr(self, addr):
|
|
byte3 = (addr >> 0) & 0xFF
|
|
byte2 = (addr >> 8) & 0xFF
|
|
byte1 = (addr >> 16) & 0xFF
|
|
byte0 = (addr >> 24) & 0xFF
|
|
crc = byte0 ^ byte1 ^ byte2 ^ byte3
|
|
return (chr(byte0) + chr(byte1) + chr(byte2) + chr(byte3) + chr(crc))
|
|
|
|
|
|
def cmdReadMemory(self, addr, lng):
|
|
assert(lng <= 256)
|
|
if self.cmdGeneric(0x11):
|
|
mdebug(10, "*** ReadMemory command")
|
|
self.sp.write(self._encode_addr(addr))
|
|
self._wait_for_ask("0x11 address failed")
|
|
N = (lng - 1) & 0xFF
|
|
crc = N ^ 0xFF
|
|
self.sp.write(chr(N) + chr(crc))
|
|
self._wait_for_ask("0x11 length failed")
|
|
return map(lambda c: ord(c), self.sp.read(lng))
|
|
else:
|
|
raise CmdException("ReadMemory (0x11) failed")
|
|
|
|
|
|
def cmdGo(self, addr):
|
|
if self.cmdGeneric(0x21):
|
|
mdebug(10, "*** Go command")
|
|
self.sp.write(self._encode_addr(addr))
|
|
self._wait_for_ask("0x21 go failed")
|
|
else:
|
|
raise CmdException("Go (0x21) failed")
|
|
|
|
|
|
def cmdWriteMemory(self, addr, data):
|
|
assert(len(data) <= 256)
|
|
if self.cmdGeneric(0x31):
|
|
mdebug(10, "*** Write memory command")
|
|
self.sp.write(self._encode_addr(addr))
|
|
self._wait_for_ask("0x31 address failed")
|
|
#map(lambda c: hex(ord(c)), data)
|
|
lng = (len(data)-1) & 0xFF
|
|
mdebug(10, " %s bytes to write" % [lng+1]);
|
|
self.sp.write(chr(lng)) # len really
|
|
crc = 0xFF
|
|
for c in data:
|
|
crc = crc ^ c
|
|
self.sp.write(chr(c))
|
|
self.sp.write(chr(crc))
|
|
self._wait_for_ask("0x31 programming failed")
|
|
mdebug(10, " Write memory done")
|
|
else:
|
|
raise CmdException("Write memory (0x31) failed")
|
|
|
|
|
|
def cmdEraseMemory(self, sectors = None):
|
|
if self.cmdGeneric(0x43):
|
|
mdebug(10, "*** Erase memory command")
|
|
if sectors is None:
|
|
# Global erase
|
|
self.sp.write(chr(0xFF))
|
|
self.sp.write(chr(0x00))
|
|
else:
|
|
# Sectors erase
|
|
self.sp.write(chr((len(sectors)-1) & 0xFF))
|
|
crc = 0xFF
|
|
for c in sectors:
|
|
crc = crc ^ c
|
|
self.sp.write(chr(c))
|
|
self.sp.write(chr(crc))
|
|
self._wait_for_ask("0x43 erasing failed")
|
|
mdebug(10, " Erase memory done")
|
|
else:
|
|
raise CmdException("Erase memory (0x43) failed")
|
|
|
|
def cmdWriteProtect(self, sectors):
|
|
if self.cmdGeneric(0x63):
|
|
mdebug(10, "*** Write protect command")
|
|
self.sp.write(chr((len(sectors)-1) & 0xFF))
|
|
crc = 0xFF
|
|
for c in sectors:
|
|
crc = crc ^ c
|
|
self.sp.write(chr(c))
|
|
self.sp.write(chr(crc))
|
|
self._wait_for_ask("0x63 write protect failed")
|
|
mdebug(10, " Write protect done")
|
|
else:
|
|
raise CmdException("Write Protect memory (0x63) failed")
|
|
|
|
def cmdWriteUnprotect(self):
|
|
if self.cmdGeneric(0x73):
|
|
mdebug(10, "*** Write Unprotect command")
|
|
self._wait_for_ask("0x73 write unprotect failed")
|
|
self._wait_for_ask("0x73 write unprotect 2 failed")
|
|
mdebug(10, " Write Unprotect done")
|
|
else:
|
|
raise CmdException("Write Unprotect (0x73) failed")
|
|
|
|
def cmdReadoutProtect(self):
|
|
if self.cmdGeneric(0x82):
|
|
mdebug(10, "*** Readout protect command")
|
|
self._wait_for_ask("0x82 readout protect failed")
|
|
self._wait_for_ask("0x82 readout protect 2 failed")
|
|
mdebug(10, " Read protect done")
|
|
else:
|
|
raise CmdException("Readout protect (0x82) failed")
|
|
|
|
def cmdReadoutUnprotect(self):
|
|
if self.cmdGeneric(0x92):
|
|
mdebug(10, "*** Readout Unprotect command")
|
|
self._wait_for_ask("0x92 readout unprotect failed")
|
|
self._wait_for_ask("0x92 readout unprotect 2 failed")
|
|
mdebug(10, " Read Unprotect done")
|
|
else:
|
|
raise CmdException("Readout unprotect (0x92) failed")
|
|
|
|
|
|
# Complex commands section
|
|
|
|
def readMemory(self, addr, lng):
|
|
data = []
|
|
if usepbar:
|
|
widgets = ['Reading: ', Percentage(),', ', ETA(), ' ', Bar()]
|
|
pbar = ProgressBar(widgets=widgets,maxval=lng, term_width=79).start()
|
|
|
|
while lng > 256:
|
|
if usepbar:
|
|
pbar.update(pbar.maxval-lng)
|
|
else:
|
|
mdebug(5, "Read %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256})
|
|
data = data + self.cmdReadMemory(addr, 256)
|
|
addr = addr + 256
|
|
lng = lng - 256
|
|
if usepbar:
|
|
pbar.update(pbar.maxval-lng)
|
|
pbar.finish()
|
|
else:
|
|
mdebug(5, "Read %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256})
|
|
data = data + self.cmdReadMemory(addr, lng)
|
|
return data
|
|
|
|
def writeMemory(self, addr, data):
|
|
lng = len(data)
|
|
|
|
mdebug(5, "Writing %(lng)d bytes to start address 0x%(addr)X" %
|
|
{ 'lng': lng, 'addr': addr})
|
|
|
|
if usepbar:
|
|
widgets = ['Writing: ', Percentage(),' ', ETA(), ' ', Bar()]
|
|
pbar = ProgressBar(widgets=widgets, maxval=lng, term_width=79).start()
|
|
|
|
offs = 0
|
|
while lng > 256:
|
|
if usepbar:
|
|
pbar.update(pbar.maxval-lng)
|
|
else:
|
|
mdebug(5, "Write %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256})
|
|
self.cmdWriteMemory(addr, data[offs:offs+256])
|
|
offs = offs + 256
|
|
addr = addr + 256
|
|
lng = lng - 256
|
|
if usepbar:
|
|
pbar.update(pbar.maxval-lng)
|
|
pbar.finish()
|
|
else:
|
|
mdebug(5, "Write %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256})
|
|
self.cmdWriteMemory(addr, data[offs:offs+lng] + ([0xFF] * (256-lng)) )
|
|
|
|
|
|
def usage():
|
|
print("""Usage: %s [-hqVewvr] [-l length] [-p port] [-b baud] [-a addr] [file.bin]
|
|
-h This help
|
|
-q Quiet
|
|
-V Verbose
|
|
-e Erase
|
|
-w Write
|
|
-v Verify
|
|
-r Read
|
|
-l length Length of read
|
|
-p port Serial port (default: first USB-like port in /dev)
|
|
-b baud Baud speed (default: 115200)
|
|
-a addr Target address
|
|
|
|
./stm32loader.py -e -w -v example/main.bin
|
|
|
|
""" % sys.argv[0])
|
|
|
|
def read(filename):
|
|
"""Read the file to be programmed and turn it into a binary"""
|
|
with open(filename, 'rb') as f:
|
|
bytes = f.read()
|
|
|
|
if bytes.startswith('\x7FELF'):
|
|
# Actually an ELF file. Convert to binary
|
|
handle, path = tempfile.mkstemp(suffix='.bin', prefix='stm32loader')
|
|
|
|
try:
|
|
os.close(handle)
|
|
|
|
# Try a couple of options for objcopy
|
|
for name in ['arm-none-eabi-objcopy', 'arm-linux-gnueabi-objcopy']:
|
|
try:
|
|
code = subprocess.call([name, '-Obinary', filename, path])
|
|
|
|
if code == 0:
|
|
return read(path)
|
|
except OSError:
|
|
pass
|
|
else:
|
|
raise Exception('Error %d while converting to a binary file' % code)
|
|
finally:
|
|
# Remove the temporary file
|
|
os.unlink(path)
|
|
else:
|
|
return [ord(x) for x in bytes]
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# Import Psyco if available
|
|
try:
|
|
import psyco
|
|
psyco.full()
|
|
print("Using Psyco...")
|
|
except ImportError:
|
|
pass
|
|
|
|
conf = {
|
|
'port': 'auto',
|
|
'baud': 115200,
|
|
'address': 0x08000000,
|
|
'erase': 0,
|
|
'write': 0,
|
|
'verify': 0,
|
|
'read': 0,
|
|
'len': 1000,
|
|
'fname':'',
|
|
}
|
|
|
|
# http://www.python.org/doc/2.5.2/lib/module-getopt.html
|
|
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], "hqVewvrp:b:a:l:")
|
|
except getopt.GetoptError as err:
|
|
# print help information and exit:
|
|
print(str(err)) # will print something like "option -a not recognized"
|
|
usage()
|
|
sys.exit(2)
|
|
|
|
QUIET = 5
|
|
|
|
for o, a in opts:
|
|
if o == '-V':
|
|
QUIET = 10
|
|
elif o == '-q':
|
|
QUIET = 0
|
|
elif o == '-h':
|
|
usage()
|
|
sys.exit(0)
|
|
elif o == '-e':
|
|
conf['erase'] = 1
|
|
elif o == '-w':
|
|
conf['write'] = 1
|
|
elif o == '-v':
|
|
conf['verify'] = 1
|
|
elif o == '-r':
|
|
conf['read'] = 1
|
|
elif o == '-p':
|
|
conf['port'] = a
|
|
elif o == '-b':
|
|
conf['baud'] = eval(a)
|
|
elif o == '-a':
|
|
conf['address'] = eval(a)
|
|
elif o == '-l':
|
|
conf['len'] = eval(a)
|
|
# elif o == '-f':
|
|
# conf['fname'] = a
|
|
else:
|
|
assert False, "unhandled option"
|
|
|
|
# Try and find the port automatically
|
|
if conf['port'] == 'auto':
|
|
ports = []
|
|
|
|
# Get a list of all USB-like names in /dev
|
|
for name in ['tty.usbserial', 'ttyUSB']:
|
|
ports.extend(glob.glob('/dev/%s*' % name))
|
|
|
|
ports = sorted(ports)
|
|
|
|
if ports:
|
|
# Found something - take it
|
|
conf['port'] = ports[0]
|
|
|
|
cmd = CommandInterface()
|
|
cmd.open(conf['port'], conf['baud'])
|
|
mdebug(10, "Open port %(port)s, baud %(baud)d" % {'port':conf['port'],
|
|
'baud':conf['baud']})
|
|
try:
|
|
if (conf['write'] or conf['verify']):
|
|
mdebug(5, "Reading data from %s" % args[0])
|
|
data = read(args[0])
|
|
|
|
try:
|
|
cmd.initChip()
|
|
except CmdException:
|
|
print("Can't init. Ensure that BOOT0 is enabled and reset device")
|
|
|
|
bootversion = cmd.cmdGet()
|
|
|
|
mdebug(0, "Bootloader version 0x%X" % bootversion)
|
|
|
|
if bootversion < 20 or bootversion >= 100:
|
|
raise Exception('Unreasonable bootloader version %d' % bootversion)
|
|
|
|
id = [ord(x) for x in cmd.cmdGetID()]
|
|
mdebug(0, "Chip id '%s'" % ' '.join('0x%x' % x for x in id))
|
|
|
|
if len(id) < 2 or id[0] != 0x04:
|
|
raise Exception('Unrecognised chip ID')
|
|
|
|
# cmd.cmdGetVersion()
|
|
# cmd.cmdGetID()
|
|
# cmd.cmdReadoutUnprotect()
|
|
# cmd.cmdWriteUnprotect()
|
|
# cmd.cmdWriteProtect([0, 1])
|
|
|
|
if conf['erase']:
|
|
cmd.cmdEraseMemory()
|
|
|
|
if conf['write']:
|
|
cmd.writeMemory(conf['address'], data)
|
|
|
|
if conf['verify']:
|
|
verify = cmd.readMemory(conf['address'], len(data))
|
|
if(data == verify):
|
|
print("Verification OK")
|
|
else:
|
|
print("Verification FAILED")
|
|
print('{} vs {}'.format(len(data), len(verify)))
|
|
for i in xrange(0, len(data)):
|
|
if data[i] != verify[i]:
|
|
print(hex(i) + ': ' + hex(data[i]) + ' vs ' + hex(verify[i]))
|
|
|
|
if not conf['write'] and conf['read']:
|
|
rdata = cmd.readMemory(conf['address'], conf['len'])
|
|
# file(conf['fname'], 'wb').write(rdata)
|
|
open(args[0], 'wb').write(''.join(map(chr, rdata)))
|
|
|
|
# cmd.cmdGo(addr + 0x04)
|
|
finally:
|
|
cmd.releaseChip()
|