1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# -*- coding: utf-8 -*- 5# vim: sw=4:ts=4:si:et:enc=utf-8 6 7# Author: Ivan A-R <ivan@tuxotronic.org> 8# Project page: http://tuxotronic.org/wiki/projects/stm32loader 9# 10# This file is part of stm32loader. 11# 12# stm32loader is free software; you can redistribute it and/or modify it under 13# the terms of the GNU General Public License as published by the Free 14# Software Foundation; either version 3, or (at your option) any later 15# version. 16# 17# stm32loader is distributed in the hope that it will be useful, but WITHOUT ANY 18# WARRANTY; without even the implied warranty of MERCHANTABILITY or 19# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 20# for more details. 21# 22# You should have received a copy of the GNU General Public License 23# along with stm32loader; see the file COPYING3. If not see 24# <http://www.gnu.org/licenses/>. 25 26import sys, getopt 27import serial 28import time 29 30try: 31 from progressbar import * 32 usepbar = 1 33except: 34 usepbar = 0 35 36# Verbose level 37QUIET = 20 38 39def mdebug(level, message): 40 if(QUIET >= level): 41 print >> sys.stderr , message 42 43 44class CmdException(Exception): 45 pass 46 47class CommandInterface: 48 def open(self, aport='/dev/ttyUSB0', abaudrate=115200): 49 self.sp = serial.Serial( 50 port=aport, 51 baudrate=abaudrate, # baudrate 52 bytesize=8, # number of databits 53 parity=serial.PARITY_EVEN, 54 stopbits=1, 55 xonxoff=0, # enable software flow control 56 rtscts=0, # disable RTS/CTS flow control 57 timeout=0.5 # set a timeout value, None for waiting forever 58 ) 59 60 def _wait_for_ack(self, info="", timeout=0): 61 stop = time.time() + timeout 62 got = None 63 while not got: 64 got = self.sp.read(1) 65 if time.time() > stop: 66 break 67 68 if not got: 69 raise CmdException("No response to %s" % info) 70 # wait for ask 71 ask = ord(got) 72 if ask == 0x79: 73 # ACK 74 return 1 75 elif ask == 0x1F: 76 # NACK 77 raise CmdException("Chip replied with a NACK during %s" % info) 78 79 # Unknown response 80 raise CmdException("Unrecognised response 0x%x to %s" % (ask, info)) 81 82 def reset(self): 83 self.sp.setDTR(1) 84 time.sleep(0.5) 85 self.sp.setDTR(0) 86 time.sleep(0.5) 87 88 def initChip(self): 89 # Set boot 90 self.sp.setRTS(0) 91 self.reset() 92 93 self.sp.write("\x7F") # Syncro 94 return self._wait_for_ack("Syncro") 95 96 def releaseChip(self): 97 self.sp.setRTS(1) 98 self.reset() 99 100 def cmdGeneric(self, cmd): 101 self.sp.write(chr(cmd)) 102 self.sp.write(chr(cmd ^ 0xFF)) # Control byte 103 return self._wait_for_ack(hex(cmd)) 104 105 def cmdGet(self): 106 if self.cmdGeneric(0x00): 107 mdebug(10, "*** Get command"); 108 len = ord(self.sp.read()) 109 version = ord(self.sp.read()) 110 mdebug(10, " Bootloader version: "+hex(version)) 111 dat = map(lambda c: hex(ord(c)), self.sp.read(len)) 112 mdebug(10, " Available commands: "+str(dat)) 113 self._wait_for_ack("0x00 end") 114 return version 115 else: 116 raise CmdException("Get (0x00) failed") 117 118 def cmdGetVersion(self): 119 if self.cmdGeneric(0x01): 120 mdebug(10, "*** GetVersion command") 121 version = ord(self.sp.read()) 122 self.sp.read(2) 123 self._wait_for_ack("0x01 end") 124 mdebug(10, " Bootloader version: "+hex(version)) 125 return version 126 else: 127 raise CmdException("GetVersion (0x01) failed") 128 129 def cmdGetID(self): 130 if self.cmdGeneric(0x02): 131 mdebug(10, "*** GetID command") 132 len = ord(self.sp.read()) 133 id = self.sp.read(len+1) 134 self._wait_for_ack("0x02 end") 135 return id 136 else: 137 raise CmdException("GetID (0x02) failed") 138 139 140 def _encode_addr(self, addr): 141 byte3 = (addr >> 0) & 0xFF 142 byte2 = (addr >> 8) & 0xFF 143 byte1 = (addr >> 16) & 0xFF 144 byte0 = (addr >> 24) & 0xFF 145 crc = byte0 ^ byte1 ^ byte2 ^ byte3 146 return (chr(byte0) + chr(byte1) + chr(byte2) + chr(byte3) + chr(crc)) 147 148 149 def cmdReadMemory(self, addr, lng): 150 assert(lng <= 256) 151 if self.cmdGeneric(0x11): 152 mdebug(10, "*** ReadMemory command") 153 self.sp.write(self._encode_addr(addr)) 154 self._wait_for_ack("0x11 address failed") 155 N = (lng - 1) & 0xFF 156 crc = N ^ 0xFF 157 self.sp.write(chr(N) + chr(crc)) 158 self._wait_for_ack("0x11 length failed") 159 return map(lambda c: ord(c), self.sp.read(lng)) 160 else: 161 raise CmdException("ReadMemory (0x11) failed") 162 163 164 def cmdGo(self, addr): 165 if self.cmdGeneric(0x21): 166 mdebug(10, "*** Go command") 167 self.sp.write(self._encode_addr(addr)) 168 self._wait_for_ack("0x21 go failed") 169 else: 170 raise CmdException("Go (0x21) failed") 171 172 173 def cmdWriteMemory(self, addr, data): 174 assert(len(data) <= 256) 175 if self.cmdGeneric(0x31): 176 mdebug(10, "*** Write memory command") 177 self.sp.write(self._encode_addr(addr)) 178 self._wait_for_ack("0x31 address failed") 179 #map(lambda c: hex(ord(c)), data) 180 lng = (len(data)-1) & 0xFF 181 mdebug(10, " %s bytes to write" % [lng+1]); 182 self.sp.write(chr(lng)) # len really 183 crc = 0xFF 184 for c in data: 185 crc = crc ^ c 186 self.sp.write(chr(c)) 187 self.sp.write(chr(crc)) 188 self._wait_for_ack("0x31 programming failed") 189 mdebug(10, " Write memory done") 190 else: 191 raise CmdException("Write memory (0x31) failed") 192 193 194 def cmdEraseMemory(self, sectors = None): 195 if self.cmdGeneric(0x43): 196 mdebug(10, "*** Erase memory command") 197 if sectors is None: 198 # Global erase 199 self.sp.write(chr(0xFF)) 200 self.sp.write(chr(0x00)) 201 else: 202 # Sectors erase 203 self.sp.write(chr((len(sectors)-1) & 0xFF)) 204 crc = 0xFF 205 for c in sectors: 206 crc = crc ^ c 207 self.sp.write(chr(c)) 208 self.sp.write(chr(crc)) 209 self._wait_for_ack("0x43 erasing failed") 210 mdebug(10, " Erase memory done") 211 else: 212 raise CmdException("Erase memory (0x43) failed") 213 214 GLOBAL_ERASE_TIMEOUT_SECONDS = 20 # This takes a while 215 def cmdExtendedEraseMemory(self): 216 if self.cmdGeneric(0x44): 217 mdebug(10, "*** Extended erase memory command") 218 # Global mass erase 219 mdebug(5, "Global mass erase; this may take a while") 220 self.sp.write(chr(0xFF)) 221 self.sp.write(chr(0xFF)) 222 # Checksum 223 self.sp.write(chr(0x00)) 224 self._wait_for_ack("0x44 extended erase failed", 225 timeout=self.GLOBAL_ERASE_TIMEOUT_SECONDS) 226 mdebug(10, " Extended erase memory done") 227 else: 228 raise CmdException("Extended erase memory (0x44) failed") 229 230 def cmdWriteProtect(self, sectors): 231 if self.cmdGeneric(0x63): 232 mdebug(10, "*** Write protect command") 233 self.sp.write(chr((len(sectors)-1) & 0xFF)) 234 crc = 0xFF 235 for c in sectors: 236 crc = crc ^ c 237 self.sp.write(chr(c)) 238 self.sp.write(chr(crc)) 239 self._wait_for_ack("0x63 write protect failed") 240 mdebug(10, " Write protect done") 241 else: 242 raise CmdException("Write Protect memory (0x63) failed") 243 244 def cmdWriteUnprotect(self): 245 if self.cmdGeneric(0x73): 246 mdebug(10, "*** Write Unprotect command") 247 self._wait_for_ack("0x73 write unprotect failed") 248 self._wait_for_ack("0x73 write unprotect 2 failed") 249 mdebug(10, " Write Unprotect done") 250 else: 251 raise CmdException("Write Unprotect (0x73) failed") 252 253 def cmdReadoutProtect(self): 254 if self.cmdGeneric(0x82): 255 mdebug(10, "*** Readout protect command") 256 self._wait_for_ack("0x82 readout protect failed") 257 self._wait_for_ack("0x82 readout protect 2 failed") 258 mdebug(10, " Read protect done") 259 else: 260 raise CmdException("Readout protect (0x82) failed") 261 262 def cmdReadoutUnprotect(self): 263 if self.cmdGeneric(0x92): 264 mdebug(10, "*** Readout Unprotect command") 265 self._wait_for_ack("0x92 readout unprotect failed") 266 self._wait_for_ack("0x92 readout unprotect 2 failed") 267 mdebug(10, " Read Unprotect done") 268 else: 269 raise CmdException("Readout unprotect (0x92) failed") 270 271# Complex commands section 272 273 def readMemory(self, addr, lng): 274 data = [] 275 if usepbar: 276 widgets = ['Reading: ', Percentage(),', ', ETA(), ' ', Bar()] 277 pbar = ProgressBar(widgets=widgets,maxval=lng, term_width=79).start() 278 279 while lng > 256: 280 if usepbar: 281 pbar.update(pbar.maxval-lng) 282 else: 283 mdebug(5, "Read %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256}) 284 data = data + self.cmdReadMemory(addr, 256) 285 addr = addr + 256 286 lng = lng - 256 287 if usepbar: 288 pbar.update(pbar.maxval-lng) 289 pbar.finish() 290 else: 291 mdebug(5, "Read %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256}) 292 data = data + self.cmdReadMemory(addr, lng) 293 return data 294 295 def writeMemory(self, addr, data): 296 lng = len(data) 297 if usepbar: 298 widgets = ['Writing: ', Percentage(),' ', ETA(), ' ', Bar()] 299 pbar = ProgressBar(widgets=widgets, maxval=lng, term_width=79).start() 300 301 offs = 0 302 while lng > 256: 303 if usepbar: 304 pbar.update(pbar.maxval-lng) 305 else: 306 mdebug(5, "Write %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256}) 307 self.cmdWriteMemory(addr, data[offs:offs+256]) 308 offs = offs + 256 309 addr = addr + 256 310 lng = lng - 256 311 if usepbar: 312 pbar.update(pbar.maxval-lng) 313 pbar.finish() 314 else: 315 mdebug(5, "Write %(len)d bytes at 0x%(addr)X" % {'addr': addr, 'len': 256}) 316 self.cmdWriteMemory(addr, data[offs:offs+lng] + ([0xFF] * (256-lng)) ) 317 318 def __init__(self): 319 pass 320 321 322def usage(): 323 print """Usage: %s [-hqVewvr] [-l length] [-p port] [-b baud] [-a addr] [file.bin] 324 -h This help 325 -q Quiet 326 -V Verbose 327 -e Erase 328 -w Write 329 -v Verify 330 -r Read 331 -l length Length of read 332 -p port Serial port (default: /dev/tty.usbserial-ftCYPMYJ) 333 -b baud Baud speed (default: 115200) 334 -a addr Target address 335 336 ./stm32loader.py -e -w -v example/main.bin 337 338 """ % sys.argv[0] 339 340 341if __name__ == "__main__": 342 343 # Import Psyco if available 344 try: 345 import psyco 346 psyco.full() 347 print "Using Psyco..." 348 except ImportError: 349 pass 350 351 conf = { 352 'port': '/dev/ttyUSB0', 353 'baud': 115200, 354 'address': 0x08000000, 355 'erase': 0, 356 'write': 0, 357 'verify': 0, 358 'read': 0, 359 'len': 1000, 360 'fname':'', 361 } 362 363# http://www.python.org/doc/2.5.2/lib/module-getopt.html 364 365 try: 366 opts, args = getopt.getopt(sys.argv[1:], "hqVewvrp:b:a:l:") 367 except getopt.GetoptError, err: 368 # print help information and exit: 369 print str(err) # will print something like "option -a not recognized" 370 usage() 371 sys.exit(2) 372 373 QUIET = 5 374 375 for o, a in opts: 376 if o == '-V': 377 QUIET = 10 378 elif o == '-q': 379 QUIET = 0 380 elif o == '-h': 381 usage() 382 sys.exit(0) 383 elif o == '-e': 384 conf['erase'] = 1 385 elif o == '-w': 386 conf['write'] = 1 387 elif o == '-v': 388 conf['verify'] = 1 389 elif o == '-r': 390 conf['read'] = 1 391 elif o == '-p': 392 conf['port'] = a 393 elif o == '-b': 394 conf['baud'] = eval(a) 395 elif o == '-a': 396 conf['address'] = eval(a) 397 elif o == '-l': 398 conf['len'] = eval(a) 399# elif o == '-f': 400# conf['fname'] = a 401 else: 402 assert False, "unhandled option" 403 404 cmd = CommandInterface() 405 cmd.open(conf['port'], conf['baud']) 406 mdebug(10, "Open port %(port)s, baud %(baud)d" % {'port':conf['port'], 'baud':conf['baud']}) 407 try: 408 try: 409 cmd.initChip() 410 except: 411 print "Can't init. Ensure that BOOT0 is enabled and reset device" 412 413 bootversion = cmd.cmdGet() 414 mdebug(0, "Bootloader version %X" % bootversion) 415 mdebug(0, "Chip id `%s'" % str(map(lambda c: hex(ord(c)), cmd.cmdGetID()))) 416# cmd.cmdWriteProtect([0, 1]) 417 418 if (conf['write'] or conf['verify']): 419 data = map(lambda c: ord(c), file(args[0], 'rb').read()) 420 421 if conf['erase']: 422 if bootversion < 0x30: 423 cmd.cmdEraseMemory() 424 else: 425 cmd.cmdExtendedEraseMemory() 426 427 if conf['write']: 428 cmd.writeMemory(conf['address'], data) 429 if conf['verify']: 430 verify = cmd.readMemory(conf['address'], len(data)) 431 if(data == verify): 432 print "Verification OK" 433 else: 434 print "Verification FAILED" 435 print str(len(data)) + ' vs ' + str(len(verify)) 436 for i in xrange(0, len(data)): 437 if data[i] != verify[i]: 438 print hex(i) + ': ' + hex(data[i]) + ' vs ' + hex(verify[i]) 439 440 if not conf['write'] and conf['read']: 441 rdata = cmd.readMemory(conf['address'], conf['len']) 442# file(conf['fname'], 'wb').write(rdata) 443 file(args[0], 'wb').write(''.join(map(chr,rdata))) 444 445 cmd.cmdGo(conf['address']) 446 finally: 447 cmd.releaseChip() 448 449