#!/usr/bin/python import sys, os, serial, time, re, struct DEV = '/dev' PORTS = [] TIMEOUT = 0.2 # for serial communications PAUSE = 0.1 # for hardware signaling EOL = '\r\n' DO_NOT_PATCH_BINARY = os.getenv('DO_NOT_PATCH_BINARY') or False RAM_BUFFER = 0x40000200 # see write_to_ram() for parts that cannot be used CHUNKSIZE = int(os.getenv('CHUNKSIZE', '256')) # write buffer size 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(start = 0, infile = None, chunksize = CHUNKSIZE): '''\ 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 ''' if not infile: print >>sys.stderr, 'Must specify start address and source of data' sys.exit(1) start, chunksize = map(safe_int, [start, chunksize]) if infile == '-': input = sys.stdin else: input = open(infile) data = pad(input.read()) input.close() if DO_NOT_PATCH_BINARY == False and start == 0 and len(data) >= 64: data = patch_in_checksum(data) 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) go(port, 0, 'A') #reset(port) 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 send(port, 'M %d %d %d' % (destination, source, length)) if expect(port, CMD_SUCCESS, abort = False): return elif 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) else: sys.exit(1) def patch_checksum(filename = None, checksum_offset = 0x14): if not filename: print >>sys.stderr, 'Must specify filename of binary to patch' sys.exit(1) input = open(filename) data = input.read() input.close() patched = patch_in_checksum(data) assert data[:checksum_offset] == patched[:checksum_offset] assert data[checksum_offset + 4:] == patched[checksum_offset + 4:] output = open(filename, 'w') output.write(patched) output.close() def patch_in_checksum(data, checksum_index = 0x14 / 4): vectors, program = data[:32], data[32:] vector_list = list(struct.unpack('<8L', vectors)) old_checksum = vector_list.pop(checksum_index) old_sum = sum(vector_list) checksum = 0x100000000 - (old_sum & 0xffffffff) vector_list.insert(checksum_index, checksum) debug('replacing checksum 0x%x with 0x%x' % (old_checksum, checksum)) debug('old sum: 0x%x, new: 0x%x' % (old_sum, sum(vector_list))) assert sum(vector_list) & 0xffffffff == 0 vectors = struct.pack('<8L', *vector_list) return vectors + program 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(start = 0, length = 0): if not length: print >>sys.stderr, 'Must specify start address and length of erasure' sys.exit(1) start, length = map(safe_int, [start, length]) port = init() sync(port) prepare(port, start, length) send(port, 'E %d %d' % (sector(start), sector(start + length))) expect(port, CMD_SUCCESS) def go(port, start = 0, mode = 'A'): send(port, 'G %d %s' % (start, mode)) 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(start = 0, bytecount = 0): if not bytecount: print >>sys.stderr, 'Must specify start address and number of bytes' sys.exit(1) start, bytecount = map(safe_int, [start, bytecount]) 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(portstring = None): '''\ 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 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 On the ARMmite2 schematic, the FT232R's RTS is tied to DSR, and the signal is named DCD1, which feeds into the LPC2103's P14/BOOT. The FT232's DTR is the RESETn signal, which feeds through an inverter into the LPC2103's RESET. The reset pushbutton switch is also connected to the same signal, and activating the switch grounds it. So there is little doubt that DTR low activates reset. But it is possible that the FT232R is inverting the sense of the signals. It should also be noted that lpc21isp, which works well with the -control option, first sets RTS and DTR, drops DTR, then drops RTS. Then on exiting it raises DTR and drops it, leaving RTS low. ''' if not PORTS: PORTS.extend([os.path.join(DEV, port) for port in os.listdir(DEV) if port.startswith('ttyUSB')]) if portstring: PORTS.insert(0, portstring) if PARAMETERS['port']: return PARAMETERS['port'] else: port = PARAMETERS['port'] = serial.Serial(PORTS[0], baudrate = 19200, xonxoff = True, timeout = TIMEOUT) if not portstring: debug('RTS is tied to DSR, which is %d' % port.getDSR()) port.setRTS(1) # RTS low on DTR rising edge forces ISP mode (pin 14) port.setDTR(1) # DTR low forces reset time.sleep(PAUSE) port.setDTR(0) time.sleep(PAUSE) port.setRTS(0) time.sleep(PAUSE) # give SBC a chance to initialize 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(portstring = None): port = init(portstring) 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' % repr(message)) port.write(message + (eol or '')) def expect(port, what, eol = EOL, message = '', retry = 5, match = None, abort = True): 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)) return sys.exit(1) if abort else None 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 reset(port): 'see notes under init()' port.setDTR(1) time.sleep(PAUSE) port.setDTR(0) 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 ''