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