#!/usr/bin/python # # spreadspace pic utils # # # Copyright (C) 2011-2013 Christian Pointner # # This file is part of spreadspace pic utils. # # spreadspace pic utils 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 of the License, or # any later version. # # spreadspace pic utils 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 spreadspace pic utils. If not, see . # '''spreadspace simple pic downloader.''' VERSION_MAJ = 1 VERSION_MIN = 0 ### HEX File Magic def load_hex(file): from ihexpic import IHexPic import os fin = file if fin == '-': fin = sys.stdin elif not os.path.isfile(fin): print >> sys.stderr, "ERROR: File not found: %s" % fin sys.exit(1) hexdata = IHexPic() hexdata.load_from_file(fin) return hexdata def write_hex(file, codedata): from ihexpic import IHexPic fout = file if fout == '-': fout = sys.stdout hexdata = IHexPic() hexdata.load_from_dict(codedata) hexdata.write_to_file(fout) def get_lowest_flash_addr(hexdata, fss): lowest_code_addr = hexdata.get_lowest_addr() return lowest_code_addr - (lowest_code_addr%fss) def get_highest_flash_addr(hexdata, fss): highest_code_addr = hexdata.get_highest_addr() return highest_code_addr + (fss - highest_code_addr%fss) def create_flash_image(hexdata, fss, sa, ea): img = ( sa, [0xFFFF]*(ea-sa) ) for a,d in hexdata.items(): if a < ea: img[1][a-sa] = d return img def create_flash_segments(hexdata, fs, fss): sa = get_lowest_flash_addr(hexdata, fss) ea = get_highest_flash_addr(hexdata, fss) if ea >= fs: print >> sys.stderr, "WARNING: the hex file contains data after end of flash, these words will be ignored" ea = fs img = create_flash_image(hexdata, fss, sa, ea) for i in xrange(0, ea, fss): slice = tuple(img[1][i:i+fss]) if not all( (elem == 0xFFFF) for elem in slice): yield (i + img[0], slice) ### Interface to Bootloader def open_serial(device, baud): import serial import time print >> sys.stderr, "opening %s (%s Baud)" % (device, baud) try: dev = serial.Serial(port=device, baudrate=baud, timeout=3) dev.flushInput() dev.flushOutput() dev.setDTR(True) # send a reset pulse dev.setBreak(True) # boot into bootloader time.sleep(0.1) dev.setDTR(False) time.sleep(0.01) dev.setBreak(False) return dev except (ValueError, serial.SerialException), msg: print >> sys.stderr, "ERROR: opening serial device: %s" % msg sys.exit(3) def calc_csum(str): cs = 0 for c in str: cs ^= c return cs def exec_command(dev, cmd, param, answer): return_codes = { 0: "OK", 1: "invalid command", 2: "bad checksum", 3: "not implemented", 4: "flash write error", 5: "address invalid", 6: "address prohibited", 7: "value out of bounds" } dev.flushInput() dev.flushOutput() cstr = bytearray(struct.pack('> sys.stderr, "ERROR: timeout while reading response header (expected %d bytes, got %d)" % (4, len(astr)) sys.exit(4) if astr[0] != cstr[0]: print >> sys.stderr, "ERROR: bootloader returned wrong command code" sys.exit(4) ret = astr[2] if ret != 0: rstr = "invalid return code" try: rstr = return_codes[ret] except KeyError: pass print >> sys.stderr, "ERROR: bootloader returned %d: %s" % (ret, rstr) sys.exit(4) answer_len = astr[1] - 4 if answer_len < struct.calcsize(answer): print >> sys.stderr, "ERROR: short answer %d bytes received: expected %s bytes" % (answer_len, struct.calcsize(answer)) sys.exit(4) if answer_len > 0: tmp = bytearray() tmp += dev.read(answer_len) if len(tmp) < answer_len: print >> sys.stderr, "ERROR: timeout while reading response (expected %d bytes, got %d)" % (answer_len, len(tmp)) sys.exit(4) astr += tmp if 0 != calc_csum(astr): print >> sys.stderr, "ERROR: checksum error" sys.exit(4) return struct.unpack_from(answer, astr, 3) ### low level commands def cmd_identify(dev, name): data = exec_command(dev, 1, '', '> sys.stderr, "incompatible protocol version, expected: %d, got: %d" % (VERSION_MAJ, id['ver_maj']) sys.exit(4) if name and id['name'] != name: print >> sys.stderr, "ERROR: the bootloaders name '%s' differs from the one supplied via" % id['name'] print >> sys.stderr, " command line option '%s'. Are sure you are connected to the" % name print >> sys.stderr, " right device?" sys.exit(4) print >> sys.stderr, "connected with Bootloader '%s' Version %d.%d,\n (ID=%04X, %d words Flash, FSS=%d, %d bytes EEPROM, MESS=%d, %d words Config)\n" % \ (id['name'], id['ver_maj'], id['ver_min'], id['devid'], id['fs'], id['fss'], id['es'], id['mess'], id['cfg']) return id def cmd_boot(dev): exec_command(dev, 2, '', '<') def cmd_reset(dev, id): exec_command(dev, 3, '', '<') def cmd_read_flash_segment(dev, id, addr): param = struct.pack('> sys.stderr, "booting to user code" cmd_boot(dev) def reset(dev, id, args): print >> sys.stderr, "reseting MCU" cmd_reset(dev,id) def write_flash(dev, id, args): hexdata = load_hex(args[0]) flashsegments = list(create_flash_segments(hexdata, id['fs'], id['fss'])) print >> sys.stderr, "writing to flash from '%s'" % args[0] bar = progressbar(len(flashsegments), "write flash") for segment in flashsegments: cmd_write_flash_segment(dev, id, segment[0], segment[1]) bar.increment() bar.end() print >> sys.stderr, "" def read_flash(dev, id, args): codedata = {} print >> sys.stderr, "reading flash to '%s'" % args[0] bar = progressbar(float(id['fs'])/float(id['fss']), "read flash") for addr in xrange(0, id['fs'], id['fss']): data = cmd_read_flash_segment(dev, id, addr) bar.increment() a = addr for d in data: if d != 0x3FFF: codedata[a] = d a += 1 bar.end() write_hex(args[0], codedata) print >> sys.stderr, "" def verify_flash(dev, id, args): hexdata = load_hex(args[0]) err = 0 flashsegments = list(create_flash_segments(hexdata, id['fs'], id['fss'])) print >> sys.stderr, "comparing flash with '%s'" % args[0] bar = progressbar(len(flashsegments), "verify flash") for segment in flashsegments: flashdata = cmd_read_flash_segment(dev, id, segment[0]) bar.increment() for file,flash in zip(segment[1] , flashdata): if flash == 0x3FFF: flash = 0xFFFF if flash != file: err = 1 break if err !=0: break bar.end() print >> sys.stderr, "" if err != 0: print >> sys.stderr, " ********* verify failed! ******** .. exiting" sys.exit(-1) else: print >> sys.stderr, " *********** verify ok! **********\n" def read_config(dev, id, args): nr = int(args[0]) print >> sys.stderr, "reading configuration word nr %d" % nr print "0x%04X" % cmd_read_config(dev, id, nr) commands = { 'boot': boot, 'reset': reset, 'write': write_flash, 'read': read_flash, 'verify': verify_flash, 'read-config': read_config } ### Main if __name__ == '__main__': import getopt import sys import struct usage = '''spreadspace simple pic downloader. Usage: python downloader.py [options] command [ command2 [ .. ] ] You can supply as many commands as you wish. Any command except 'boot' may be supplied more than once. The commands will be executed in the order of appearence at command line. Mind that all commands after 'boot' will be ignored because the bootloader is no longer reachable. The same may be true after a reset in which case, depending on the state of BOOTPIN, the user code may get started. If verify detects an error the downloader will exit with '-1' immediatly and the remaining commands will get ignored. If you don't specify any command the downloader will connect to the bootloader print some information and exit. Options: -h, --help this help message. -v, --version version info. --device=N the serial port to use (default: /dev/ttyUSB0). --baud=N baudrate to use (default: 57600). --name=N the expected name of the bootloader. Commands: --write= write to flash (use '-' for stdin). --verify= compare flash with (use '-' for stdin). --read= read flash and store in (use '-' for stdout). --read-config= read the configuration word and print it on stdout. --reset reset the MCU (this may start the user code area: BOOTPIN) --boot boot to user code ''' device = "/dev/ttyUSB0" baudrate = 57600 name = None cmds = [] try: opts, args = getopt.getopt(sys.argv[1:], "hv", ["help", "version", "device=", "baud=", "name=", \ "write=", "read=", "verify=", "read-config=", "reset", "boot" ]) for o, a in opts: if o in ("-h", "--help"): print >> sys.stderr, usage sys.exit(0) elif o in ("-v", "--version"): print >> sys.stderr, "Version %d.%d" % (VERSION_MAJ, VERSION_MIN) sys.exit(0) elif o == "--device": device = a elif o == "--baud": baudrate = a elif o == "--name": name = a else: cmds.append({ 'name': o[2:], 'args': a.split(':') }); if len(args) > 1: raise getopt.GetoptError('Too many arguments') if len(cmds) == 0: print "WARNING: no commands specified" sys.exit except getopt.GetoptError, msg: print >> sys.stderr, "ERROR: %s" % msg print >> sys.stderr, usage sys.exit(2) dev = open_serial(device, baudrate) id = cmd_identify(dev, name) try: for cmd in cmds: commands[cmd['name']](dev, id, cmd['args']) except KeyError: print >> sys.stderr, "ERROR: unknown command '%s'" % cmd['name']