#!/usr/bin/python import sys, os, serial, time, re DEV = '/dev' PORTS = [os.path.join(DEV, port) for port in os.listdir(DEV) if port.startswith('ttyUSB')] TIMEOUT = 0.2 # for serial communications PAUSE = 0.1 # for hardware signaling EOL = '\r\n' TOGGLE_LINES = True RAM_BUFFER = 0x40000200 # see write_to_ram() for parts that cannot be used CHUNKSIZE = 256 # how much data to buffer for write CLOCK_RATE = '20000' # clock rate of SBC in KHz (CCLK) (as string) CMD_SUCCESS = '0' # expected from PCB after successful command entry PARAMETERS = { 'abort_on_checksum_error': True, 'synchronized': False, 'unlocked': False, 'port': '', } def burn(*args): '''\ Write data to LPC2103 "The host should send the data only after receiving the CMD_SUCCESS return code. The host should send the check-sum after transmitting 20 UU-encoded lines. The checksum is generated by adding raw data (before UU-encoding) bytes and is reset after transmitting 20 UU-encoded lines. The length of any UU-encoded line should not exceed 61 characters(bytes) i.e. it can hold 45 data bytes. When the data fits in less then 20 UU-encoded lines then the check-sum should be of the actual number of bytes sent. The ISP command handler compares it with the check-sum of the received bytes. If the check-sum matches, the ISP command handler responds with "OK" to continue further transmission. If the check-sum does not match, the ISP command handler responds with "RESEND". In response the host should retransmit the bytes." From UM10161, p. 249 ''' chunksize = CHUNKSIZE if len(args) < 2: print >>sys.stderr, 'Must specify start address and source of data' sys.exit(1) start, infile = safe_int(args[0]), args[1] if len(args) > 2: chunksize = safe_int(args[2]) if infile == '-': input = sys.stdin else: input = open(infile) data = pad(input.read()) input.close() port = init() sync(port) unlock(port) erase(start, len(data)) for chunk in range(0, len(data), chunksize): write_to_ram(port, pad(data[chunk:chunk + chunksize], chunksize), RAM_BUFFER) copy_ram_to_flash(port, start + chunk, RAM_BUFFER, chunksize) compare(port, start + chunk, RAM_BUFFER, chunksize) def compare(port, destination, source, length): '''\ Compare two regions of memory on word boundaries, length multiple of 4 "Compare result may not be correct when source or destination address contains any of the first 64 bytes starting from address zero. First 64 bytes are re-mapped to flash boot sector." p. 253, ibid. ''' if length % 4: skip = 4 - (length % 4) source, destination, length = source - skip, destination - skip, length + skip if min(source, destination) < 64: skip = 64 - min(source, destination) source, destination, length = source + skip, destination + skip, length - skip send(port, 'M %d %d %d' % (destination, source, length)) expect(port, CMD_SUCCESS) def copy_ram_to_flash(port, flash_address, ram_address, length): prepare(port, flash_address, length) send(port, 'C %d %d %d' % (flash_address, ram_address, length)) expect(port, CMD_SUCCESS) def write_to_ram(port, data, location, chunksize = CHUNKSIZE): '''\ To write the flash, it is first necessary to load data into RAM From page 242 of UM10161, the LPC2101/02/03 manual, we find that there are certain areas of RAM that should not be overwritten: RAM used by ISP command handler ISP commands use on-chip RAM from 0x4000 0120 to 0x4000 01FF. The user could use this area, but the contents may be lost upon reset. Flash programming commands use the top 32 bytes of on-chip RAM. The stack is located at RAM top - 32. The maximum stack usage is 256 bytes and it grows downwards. RAM used by IAP command handler Flash programming commands use the top 32 bytes of on-chip RAM. The maximum stack usage in the user allocated stack space is 128 bytes and it grows downwards. RAM used by RealMonitor The RealMonitor uses on-chip RAM from 0x4000 0040 to 0x4000 011F. The user could use this area if RealMonitor based debug is not required. The Flash boot loader does not initialize the stack for RealMonitor. ''' send(port, 'W %s %s' % (location, len(data))) expect(port, CMD_SUCCESS) chunked = [data[n:n + chunksize] for n in range(0, len(data), chunksize)] for chunk in chunked: checksum = sum(map(ord, chunk)) send(port, EOL.join(uuencode(chunk) + ['%d' % checksum])) expect(port, 'OK') def pad(data, chunksize = 4): '''\ Length of data written must be multiple of at least 4 Padding should be 0xff because that leaves the extra bytes erased; any 0 bits written would force the sector to be erased before the data could be extended. ''' pad_length = (chunksize - (len(data) % chunksize)) % chunksize return data + ('\xff' * pad_length) def erase(*args): if len(args) != 2: print >>sys.stderr, 'Must specify start address and length of erasure' sys.exit(1) start, length = map(safe_int, args) port = init() sync(port) prepare(port, start, length) send(port, 'E %d %d' % (sector(start), sector(start + length))) expect(port, CMD_SUCCESS) def prepare(port, start, length): send(port, 'P %d %d' % (sector(start), sector(start + length))) expect(port, CMD_SUCCESS) def sector(address): 'LPC2103 flash sectors are 4096 (4k) bytes long' if address >= 0x40000000: raise ValueError, 'Sectors do not apply to RAM' else: return address / 4096 def unlock(port): if not PARAMETERS['unlocked']: send(port, 'U 23130') expect(port, CMD_SUCCESS) PARAMETERS['unlocked'] = True def dump(*args): if len(args) != 2: print >>sys.stderr, 'Must specify start address and number of bytes' sys.exit(1) start, bytecount = map(safe_int, args) if start + bytecount > 0x40000000: PARAMETERS['abort_on_checksum_error'] = False # RAM checksum is unreliable print >>sys.stderr, 'Dumping %d bytes starting at 0x%x' % (bytecount, start) port = init() sync(port) dumped = '' send(port, 'R %d %d' % (start, bytecount)) response = filter(None, expect(port, True).group().split(EOL)) debug('response: %s' % response) check_equal('0', response.pop(0), 'Error code from R command') dumped += uudecode(response) while True: send(port, 'OK') response = filter(None, expect(port, True).group().split(EOL)) debug('response: %s' % response) if not response: return xxd(dumped, address = start) else: dumped += uudecode(response) def xxd(data, address = 0, chunksize = 16, wordsize = 2): 'dump data in xxd format' address_format = '%07x:' if address + len(data) < 0x10000000 else '%08x:' padding = [' ' * wordsize * 2] * (chunksize / wordsize) chunked = [data[n:n + chunksize] for n in range(0, len(data), chunksize)] for chunk in chunked: print address_format % address, words = [chunk[n:n + wordsize].encode('hex') for n in range(0, chunksize, wordsize)] for word in (words + padding)[:chunksize / wordsize]: print (word + padding[0])[:len(padding[0])], print ' ' + re.compile('[^ -~]').sub('.', chunk) address += chunksize def init(): '''\ Select a serial port and initialize it for ISP communication "The boot loader code is executed every time the part is powered on or reset. The loader can execute the ISP command handler or the user application code. A a LOW level after reset at the P0.14 pin is considered as an external hardware request to start the ISP command handler. Assuming that proper signal is present on XTAL1 pin when the rising edge on RESET pin is generated, it may take up to 3 ms before P0.14 is sampled and the decision on whether to continue with user code or ISP handler is made. If P0.14 is sampled LOW and the watchdog overflow flag is set, the external hardware request to start the ISP command handler is ignored. If there is no request for the ISP command handler execution (P0.14 is sampled HIGH after reset), a search is made for a valid user program. If a valid user program is found then the execution control is transferred to it. If a valid user program is not found, the auto-baud routine is invoked." From UM10161, p. 239 ''' if PARAMETERS['port']: return PARAMETERS['port'] port = serial.Serial(PORTS[0], baudrate = 19200, xonxoff = True, timeout = TIMEOUT) if TOGGLE_LINES: port.setRTS(0) # RTS low on DTR rising edge forces ISP mode (pin 14) time.sleep(PAUSE) port.setDTR(1) # DTR high forces reset port.setRTS(1) port.setDTR(0) time.sleep(PAUSE) # give SBC a chance to initialize PARAMETERS['port'] = port return port def safe_int(number): if type(number) == int: return number if number.startswith(('0X', '0x')): return int(number, 16) else: return int(number) def comm(): port = init() sync(port) print >>sys.stderr, 'Communications established. Enter commands:' while True: try: command = raw_input('> ').rstrip() if command == 'QUIT': raise EOFError, 'User exited' send(port, command) print expect(port, True).group() except EOFError: print >>sys.stderr, EOL break port.close() def sync(port): if PARAMETERS['synchronized']: return expect(port, '', eol = None) send(port, '?', eol = None) expect(port, 'Synchronized') send(port, 'Synchronized') expect(port, ['Synchronized', 'OK']) # because echo is still on send(port, CLOCK_RATE) expect(port, [CLOCK_RATE, 'OK']) send(port, 'A 0') # turn off echo expect(port, ['A 0', CMD_SUCCESS]) # last echo we should get PARAMETERS['synchronized'] = True def uuencode(data): 'Python uuencodes with a "begin" and "end" line' message = re.compile(r'[\r\n]+').split(data.encode('uu').rstrip()) assert message.pop(0).startswith('begin ') assert message.pop(-1) == 'end' assert len(message.pop(-1)) == 1 # final line either " " or "`" return message def uudecode(lines): 'uudecode requires begin and end; treats \r as illegal character' checksum = lines.pop(-1) message = 'begin\n' + '\n'.join(lines) + '\n \nend\n' debug('decoding %s' % repr(message)) decoded = message.decode('uu') check_equal(int(checksum), sum(map(ord, decoded)), 'Checksum for uudecode') return decoded def check_equal(expected, got, message): if got != expected: print >>sys.stderr, '%s: got %s, expected %s' % (message, got, expected) if message == 'Checksum for uudecode': if PARAMETERS['abort_on_checksum_error']: sys.exit(1) else: print >>sys.stderr, 'Ignoring checksum error for RAM dump' else: sys.exit(1) def send(port, message, eol = EOL): debug('Sending: %s' % message) port.write(message + (eol or '')) def expect(port, what, eol = EOL, message = '', retry = 5, match = None): if type(what) == str: debug('Expecting: %s' % repr(what)) elif hasattr(what, 'pattern'): debug('Expecting match for pattern %s' % repr(what.pattern)) elif what is True: debug('Expecting a response') what = re.compile('(?s).*') else: # assume iterable what = eol.join(what) debug('Expecting: %s' % repr(what)) while not match and retry: message += read(port) match, pattern, found = check_match(message, what, eol) retry -= 1 if not match: print >>sys.stderr, 'Expected %s, got %s' % (repr(pattern), repr(found)) sys.exit(1) else: return match def check_match(text, pattern, eol): if type(pattern) == str and pattern + (eol or '') == text: return True, pattern, text elif hasattr(pattern, 'pattern'): match = pattern.match(text) return match, pattern.pattern, text else: return False, pattern, text def read(port, chunksize = 64): text = '' while True: partial = port.read(chunksize) # must have timeout or will block forever if len(partial): text += partial else: return text def debug(message): if os.getenv('DEBUGGING'): print >>sys.stderr, message if __name__ == '__main__': command = os.path.splitext(os.path.split(sys.argv[0])[1])[0] print >>sys.stderr, eval(command)(*sys.argv[1:]) or ''