# 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 . # #********************************************************************* # This is based on IntelHex 1.4 # which can be found at: http://www.bialix.com/intelhex/ # # # Copyright (c) 2005-2012, Alexander Belchenko # All rights reserved. # # Redistribution and use in source and binary forms, # with or without modification, are permitted provided # that the following conditions are met: # # * Redistributions of source code must retain # the above copyright notice, this list of conditions # and the following disclaimer. # * Redistributions in binary form must reproduce # the above copyright notice, this list of conditions # and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the author nor the names # of its contributors may be used to endorse # or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #********************************************************************* '''Intel HEX file format reader and writer for PIC Microcontroller.''' from array import array from binascii import hexlify, unhexlify from bisect import bisect_right import sys class IHexPic(object): ''' Intel HEX file reader for PIC Microcontroller. ''' def __init__(self): # private members self._buf = {} self._offset = 0 def _decode_record(self, s, line=0): '''Decode one record of HEX file. @param s line with HEX record. @param line line number (for error messages). @raise EndOfFile if EOF record encountered. ''' s = s.rstrip('\r\n') if not s: return # empty line if s[0] == ':': try: bin = array('B', unhexlify(str(s[1:]))) except (TypeError, ValueError): # this might be raised by unhexlify when odd hexascii digits raise HexRecordError(line=line) length = len(bin) if length < 5: raise HexRecordError(line=line) else: raise HexRecordError(line=line) record_length = bin[0] if length != (5 + record_length): raise RecordLengthError(line=line) if record_length%2 != 0: raise RecordLengthError(line=line) addr = bin[1]*256 + bin[2] if addr%2 != 0: raise RecordAlignmentError(line=line) record_type = bin[3] if not (0 <= record_type <= 5): raise RecordTypeError(line=line) crc = sum(bin) crc &= 0x0FF if crc != 0: raise RecordChecksumError(line=line) if record_type == 0: # data record addr += self._offset for i in xrange(4, 4+record_length, 2): addr16 = addr/2 if not self._buf.get(addr16, None) is None: raise AddressOverlapError(address=addr16, line=line) self._buf[addr16] = bin[i] + (bin[i+1]<<8) addr += 2 # FIXME: addr should be wrapped # BUT after 02 record (at 64K boundary) # and after 04 record (at 4G boundary) elif record_type == 1: # end of file record if record_length != 0: raise EOFRecordError(line=line) raise _EndOfFile elif record_type == 2: # Extended 8086 Segment Record if record_length != 2 or addr != 0: raise ExtendedSegmentAddressRecordError(line=line) self._offset = (bin[4]*256 + bin[5]) * 16 elif record_type == 4: # Extended Linear Address Record if record_length != 2 or addr != 0: raise ExtendedLinearAddressRecordError(line=line) self._offset = (bin[4]*256 + bin[5]) * 65536 else: raise UnsupportedRecordTypeError(type=record_type, line=line) def load_from_file(self, fobj): """Load hex file into internal buffer. @param fobj file name or file-like object """ if getattr(fobj, "read", None) is None: fobj = open(fobj, "r") fclose = fobj.close else: fclose = None line = 0 try: decode = self._decode_record try: for s in fobj: line += 1 decode(s, line) except _EndOfFile: pass finally: if fclose: fclose() def load_from_dict(self, dikt): """Load data from dictionary. Dictionary should contain int keys representing addresses. Values should be the data to be stored in those addresses in unsigned char form (i.e. not strings). The contents of the dict will be merged with this object and will overwrite any conflicts. """ s = dikt.copy() for k in s.keys(): if type(k) not in (int, long) or k < 0: raise ValueError('Source dictionary should have only int keys') self._buf.update(s) def write_to_file(self, f): """Write data to file f in HEX format. @param f filename or file-like object for writing """ fwrite = getattr(f, "write", None) if fwrite: fobj = f fclose = None else: fobj = file(f, 'w') fwrite = fobj.write fclose = fobj.close # Translation table for uppercasing hex ascii string. # timeit shows that using hexstr.translate(table) # is faster than hexstr.upper(): # 0.452ms vs. 0.652ms (translate vs. upper) if sys.version_info[0] >= 3: table = bytes(range(256)).upper() else: table = ''.join(chr(i).upper() for i in range(256)) # data addresses = self._buf.keys() addresses.sort() addr_len = len(addresses) if addr_len: minaddr = addresses[0] maxaddr = addresses[-1] if maxaddr > 65535/2: need_offset_record = True else: need_offset_record = False high_ofs = 0 cur_addr = minaddr cur_ix = 0 while cur_addr <= maxaddr: if need_offset_record: bin = array('B', str('\0'*7)) bin[0] = 2 # reclen bin[1] = 0 # offset msb bin[2] = 0 # offset lsb bin[3] = 4 # rectype high_ofs = int(cur_addr>>16) b = divmod(high_ofs*2, 256) bin[4] = b[0] # msb of high_ofs bin[5] = b[1] # lsb of high_ofs bin[6] = (-sum(bin)) & 0x0FF # chksum fwrite(':' + str(hexlify(bin.tostring()).translate(table)) + '\n') while True: # produce one record low_addr = cur_addr & 0x0FFFF # chain_len off by 1 chain_len = min(7, 65535-low_addr, maxaddr-cur_addr) # search continuous chain stop_addr = cur_addr + chain_len if chain_len: ix = bisect_right(addresses, stop_addr, cur_ix, min(cur_ix+chain_len+1, addr_len)) chain_len = ix - cur_ix # real chain_len # there could be small holes in the chain # but we will catch them by try-except later # so for big continuous files we will work # at maximum possible speed else: chain_len = 1 # real chain_len bin = array('B', str('\0'*(5+(chain_len*2)))) b = divmod(low_addr*2, 256) bin[1] = b[0] # msb of low_addr bin[2] = b[1] # lsb of low_addr bin[3] = 0 # rectype try: # if there is small holes we'll catch them for i in range(chain_len): bin[4+(i*2)] = self._buf[cur_addr+i] & 0x00FF bin[4+(i*2)+1] = self._buf[cur_addr+i] >> 8 except KeyError: # we catch a hole so we should shrink the chain chain_len = i bin = bin[:5+(i*2)] bin[0] = chain_len*2 bin[4+(chain_len*2)] = (-sum(bin)) & 0x0FF # chksum fwrite(':' + str(hexlify(bin.tostring()).translate(table)) + '\n') # adjust cur_addr/cur_ix cur_ix += chain_len if cur_ix < addr_len: cur_addr = addresses[cur_ix] else: cur_addr = maxaddr + 1 break high_addr = int(cur_addr>>16) if high_addr > high_ofs: break # end-of-file record fwrite(":00000001FF\n") if fclose: fclose() def get_lowest_addr(self): return sorted(self._buf.keys())[0] def get_highest_addr(self): return sorted(self._buf.keys())[-1] def items(self): return self._buf.items() def __getitem__(self, addr): t = type(addr) if t in (int, long): if addr < 0: raise TypeError('Address should be >= 0.') return self._buf(addr, 0xFFFF) else: raise TypeError('Address has unsupported type: %s' % t) ## # IHexPic Errors Hierarchy: # # IHexPicError - basic error # HexReaderError - general hex reader error # AddressOverlapError - data for the same address overlap # HexRecordError - hex record decoder base error # RecordLengthError - record has invalid length # RecordTypeError - record has invalid type (RECTYP) # RecordChecksumError - record checksum mismatch # EOFRecordError - invalid EOF record (type 01) # ExtendedAddressRecordError - extended address record base error # ExtendedSegmentAddressRecordError - invalid extended segment address record (type 02) # ExtendedLinearAddressRecordError - invalid extended linear address record (type 04) # UnsupportedRecordError - invalid/unsupported record type # _EndOfFile - it's not real error, used internally by hex reader as signal that EOF record found class IHexPicError(Exception): '''Base Exception class for IHexPic module''' _fmt = 'IHexPic base error' #: format string def __init__(self, msg=None, **kw): """Initialize the Exception with the given message. """ self.msg = msg for key, value in kw.items(): setattr(self, key, value) def __str__(self): """Return the message in this Exception.""" if self.msg: return self.msg try: return self._fmt % self.__dict__ except (NameError, ValueError, KeyError), e: return 'Unprintable exception %s: %s' \ % (repr(e), str(e)) class _EndOfFile(IHexPicError): """Used for internal needs only.""" _fmt = 'EOF record reached -- signal to stop read file' class HexReaderError(IHexPicError): _fmt = 'Hex reader base error' class AddressOverlapError(HexReaderError): _fmt = 'Hex file has data overlap at address 0x%(address)X on line %(line)d' class HexRecordError(HexReaderError): _fmt = 'Hex file contains invalid record at line %(line)d' class RecordLengthError(HexRecordError): _fmt = 'Record at line %(line)d has invalid length' class RecordAlignmentError(HexRecordError): _fmt = 'Record at line %(line)d has invalid adress (not 16bit aligned)' class RecordTypeError(HexRecordError): _fmt = 'Record at line %(line)d has invalid record type' class RecordChecksumError(HexRecordError): _fmt = 'Record at line %(line)d has invalid checksum' class EOFRecordError(HexRecordError): _fmt = 'File has invalid End-of-File record' class UnsupportedRecordError(HexRecordError): _fmt = 'File has unsupported record type %(type)d at line %(line)d' class ExtendedAddressRecordError(HexRecordError): _fmt = 'Base class for extended address exceptions' class ExtendedSegmentAddressRecordError(ExtendedAddressRecordError): _fmt = 'Invalid Extended Segment Address Record at line %(line)d' class ExtendedLinearAddressRecordError(ExtendedAddressRecordError): _fmt = 'Invalid Extended Linear Address Record at line %(line)d'