├── README.md └── bin2hex.py /README.md: -------------------------------------------------------------------------------- 1 | **NOTE: this project is no longer maintained, instead check out https://github.com/bialix/intelhex which does a better job!*** 2 | 3 | bin2hex 4 | ===== 5 | 6 | Simple binary file to Intel HEX file converter (bin to hex) written in python. For usage try $bin2hex.py -h 7 | 8 | License 9 | ===== 10 | 11 | The MIT License 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of this hardware, software, and associated documentation files (the 15 | "Product"), to deal in the Product without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Product, and to permit 18 | persons to whom the Product is furnished to do so, subject to the 19 | following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Product. 23 | 24 | THE PRODUCT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 25 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | PRODUCT OR THE USE OR OTHER DEALINGS IN THE PRODUCT. 31 | -------------------------------------------------------------------------------- /bin2hex.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: bin2hex.py 3 | 4 | Converts a binary file into intel hex format. For usage try $bin2hex.py -h 5 | 6 | 7 | 8 | License 9 | 10 | The MIT License 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a 13 | copy of this hardware, software, and associated documentation files (the 14 | "Product"), to deal in the Product without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Product, and to permit 17 | persons to whom the Product is furnished to do so, subject to the 18 | following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included 21 | in all copies or substantial portions of the Product. 22 | 23 | THE PRODUCT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 24 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 26 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 27 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 28 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 29 | PRODUCT OR THE USE OR OTHER DEALINGS IN THE PRODUCT. 30 | 31 | """ 32 | 33 | import sys 34 | import os 35 | import errno 36 | import optparse 37 | import struct 38 | 39 | HEX_TYPE_DATA = 0 40 | HEX_TYPE_EOF = 1 41 | HEX_TYPE_EXT_SEG_ADDRESS = 2 42 | HEX_TYPE_START_SEG_ADDRESS = 3 43 | HEX_TYPE_EXT_LINEAR_ADDRESS = 4 44 | HEX_TYPE_START_LINEAR_ADDRESS = 5 45 | 46 | HEX_ALLOWED_ADDRESS_TYPES ={ 47 | 0:(1<<16)-1, 48 | 2:(1<<20)-1, 49 | 4:(1<<32)-1, 50 | } 51 | 52 | class HexRecord: 53 | def __init__(self, type, data, checksum = None, address = 0): 54 | self.__type = type 55 | self.__data = data 56 | self.__length = len(data) 57 | self.__address = address 58 | 59 | self.__checksum = self.__length + (address >> 8) + (address & 0xFF) + type 60 | for b in data: 61 | self.__checksum += b 62 | self.__checksum = (~self.__checksum) + 1 63 | self.__checksum = self.__checksum & 0xFF 64 | if (checksum is not None) and (self.__checksum != checksum): 65 | raise Exception("Error: Checksum does not match. Calculated %02X. Given %02X." % (self.__checksum, checksum)) 66 | 67 | def getType(self): 68 | return self.__type 69 | 70 | def getData(self): 71 | return self.__data 72 | 73 | def getAddress(self): 74 | return self.__address 75 | 76 | def getRecord(self): 77 | # return string representation of the record. 78 | recordstr = ":%02X%04X%02X%s%02X" % (self.__length, 79 | self.__address, 80 | self.__type, 81 | "".join(["%02X" % b for b in self.__data]), 82 | self.__checksum) 83 | return recordstr 84 | 85 | def write(self, stream=sys.stdout): 86 | # write the record to stream 87 | stream.write(":%02X%04X%02X" % (self.__length, self.__address, self.__type)) 88 | for b in self.__data: 89 | stream.write("%02X" % b) 90 | stream.write("%02X\n" % self.__checksum) 91 | 92 | 93 | def readHexFile(stream): 94 | records = [] 95 | lineNum = 0 96 | for line in stream: 97 | lineNum += 1 98 | line = line.strip() 99 | if len(line) == 0: 100 | break 101 | 102 | if line[0] != ":": 103 | raise Exception("Error on line %d. Record does not start with ':' character. Starts with '%s'." % (lineNum, line[0])) 104 | 105 | byteCount = int(line[1:3], 16) 106 | address = int(line[3:7], 16) 107 | type = int(line[7:9], 16) 108 | if len(line) != (11 + 2*byteCount): 109 | raise Exception("Bad byteCount on line %d lineNum. Line length is %d chars, expected %d for byteCount %d." % (lineNum, len(line), 11+2*byteCount, byteCount)) 110 | 111 | data = [] 112 | for i in range(byteCount): 113 | hexPair = line[(9+2*i):(9+2*i+2)] 114 | byte = int(hexPair, 16) 115 | data.append(byte) 116 | 117 | checkSum = int(line[-2:], 16) 118 | records.append(HexRecord(type, data, checkSum, address)) 119 | 120 | return records 121 | 122 | def generatehexfile(inputlist, hexsubsettype=4): 123 | ''' From a sorted (by address) list of (address, binaryfilepath) tuples, 124 | produce a hex file string and return it. Assumes arguments are OK. 125 | Only hex subtype 4 is implemented. 126 | ''' 127 | 128 | hexout = [] 129 | 130 | if (hexsubsettype == 4): 131 | recordlength = 32 132 | elif (hexsubsettype == 2): 133 | recordlength = 16 134 | else: 135 | # not implemented 136 | return ''.join(hexout) 137 | 138 | # current address and segment address are carried between subfiles. 139 | curraddr = 0 140 | segaddr = 0 141 | for (addr, binfile) in inputlist: 142 | # open the file for processing 143 | with open(binfile, 'rb') as f: 144 | fsize = os.path.getsize(binfile) 145 | 146 | # set starting address. 147 | if addr >= (curraddr + segaddr): 148 | curraddr = addr - segaddr 149 | 150 | else: 151 | # shouldn't be out of order this way. error. 152 | raise UserWarning("Error: binfiles are out of order. Contact tool smith.") 153 | 154 | # work through the file generating & storing records as we go 155 | while f.tell() != fsize: 156 | # check if we need a new segment 157 | if (curraddr & 0xFFFF0000) != 0: 158 | # set new segaddr 159 | segaddr = (curraddr & 0xFFFF0000) + segaddr 160 | 161 | if hexsubsettype == 4: 162 | hexout.append(HexRecord(HEX_TYPE_EXT_LINEAR_ADDRESS, [(segaddr >> 24) & 0xFF, (segaddr >> 16) & 0xFF]).getRecord()) 163 | elif hexsubsettype == 2: 164 | hexout.append(HexRecord(HEX_TYPE_EXT_SEG_ADDRESS, [(segaddr >> 12) & 0xFF, (segaddr >> 4) & 0xFF]).getRecord()) 165 | else: 166 | raise UserWarning("Error: somehow hexsubsettype is broken, contact tool smith.") 167 | # advance address pointer 168 | curraddr = curraddr & 0x0000FFFF 169 | 170 | # read up to recordlength bytes from the file, don't bridge segment. 171 | if (curraddr + recordlength) > 0x10000: 172 | bytestoread = (curraddr + recordlength) - 0x10000; 173 | else: 174 | bytestoread = recordlength 175 | 176 | bindata = f.read(bytestoread) 177 | # bindata = struct.unpack('B'*len(bindata),bindata) # better to use ord actually 178 | bindata = map(ord, bindata) 179 | hexout.append(HexRecord(HEX_TYPE_DATA, bindata, address=curraddr).getRecord()) 180 | curraddr += len(bindata) 181 | 182 | # add end of file record 183 | hexout.append(HexRecord(HEX_TYPE_EOF, []).getRecord()) 184 | 185 | return hexout 186 | 187 | def checkhextypearg(option, opt, value, parser): 188 | # check hex type argument 189 | if value not in HEX_ALLOWED_ADDRESS_TYPES: 190 | raise optparse.OptionValueError ("Error: HEX format subset type %d not acceptable."%value) 191 | 192 | setattr(parser.values, option.dest, value) 193 | 194 | def commandline_split(option, opt, value, parser): 195 | # check the binary input 196 | binlist = value.split(',') 197 | if len(value.split(','))%2 != 0: 198 | raise optparse.OptionValueError("Error: each input binary must have a corresponding address") 199 | 200 | # convert to list of lists of (address, binfile) 201 | binlist = map(list, zip(*[iter(binlist)]*2)) 202 | binlistout = [] 203 | 204 | # make sure each argument in each pair is OK 205 | for [addr, binfile] in (binlist): 206 | # convert address to int. int() will raise any format errors 207 | rawaddr = addr 208 | if addr.find('0x') == 0: 209 | addr = int(addr, 16) 210 | else: 211 | addr = int(addr) 212 | if addr > 0xFFFFFFFF: 213 | raise optparse.OptionValueError("Error: address (%s, %s) exceeds 4gb."%(rawaddr, binfile)) 214 | 215 | # ensure binfile path is ok, and abs it. 216 | if os.path.isfile(binfile): 217 | binfile = os.path.abspath(binfile) 218 | else: 219 | raise optparse.OptionValueError("Error: binfile path (%s, %s) is unacceptable"%(rawaddr, binfile)) 220 | 221 | # save it to the output list as a tuple (unmodifiable after this), and 222 | # save the converted values to a list for examination later 223 | binlistout.append((addr, binfile)) 224 | 225 | # now check if any file(size) + address will overlap another 226 | for i, binentry1 in enumerate(binlistout): 227 | for j, binentry2 in enumerate(binlistout): 228 | if (binentry1[0] < binentry2[0]) and (binentry1[0] + os.path.getsize(binentry1[1]) > binentry2[0]): 229 | raise optparse.OptionValueError("Error: binfile entry %s overlaps %s"%(str(binlist[i]), str(binlist[j]))) 230 | 231 | # also check if addr + filesize is going to overflow 4gb limit 232 | if binentry1[0] + os.path.getsize(binentry1[1]) > (1<<32)-1: 233 | raise optparse.OptionValueError("Error: binfile entry %s exceeds 4gb limit"%(str(binlist[i]))) 234 | 235 | # sort the output list (by address) 236 | binlistout.sort() 237 | 238 | setattr(parser.values, option.dest, binlistout) 239 | 240 | def process_command_line(argv=None): 241 | ''' 242 | Return a 2-tuple: (settings object, args list). 243 | `argv` is a list of arguments, or `None` for ``sys.argv[1:]``. 244 | ''' 245 | if argv is None: 246 | if len(sys.argv[1:]): 247 | argv = sys.argv[1:] 248 | else: 249 | argv = ['-h'] 250 | 251 | # initialize the parser object: 252 | parser = optparse.OptionParser( 253 | formatter=optparse.TitledHelpFormatter(width=70), 254 | add_help_option=None) 255 | 256 | # define options here: 257 | parser.add_option('-r', '--format', dest='format', type="int", 258 | default=4, action='callback', callback=checkhextypearg, 259 | help='HEX format subtype. 0 is I8HEX, 2 is I16HEX, 4 is I32HEX. Default is %default. ONLY 2 AND 4 ACCEPTED RIGHT NOW.') 260 | parser.add_option('-b', '--binaries', dest='binaries', type='string', 261 | default=None, action='callback', callback=commandline_split, 262 | help='List of binary file inputs and start addresses. Addresses are either decimal or hex (must be prepended with 0x).', metavar='ADDRESS,FILE,ADDRESS,FILE,...') 263 | parser.add_option('-o', '--outfile', dest='outfile', 264 | default=None, 265 | help='Output file path, optional, defaults to first input binary file dot hex.', metavar='PATH') 266 | parser.add_option('-q', '--quiet',action="store_true", dest="quiet", 267 | default=False, 268 | help="Suppress non-critical output on stdout.") 269 | parser.add_option('-v', '--version',dest='version', 270 | action="store_true", 271 | default=False, 272 | help='Print version and exit.') 273 | parser.add_option('-h', '--help', action='help', 274 | help='Show this help message and exit.') 275 | 276 | settings, args = parser.parse_args(argv) 277 | 278 | # check number of arguments, verify values, etc.: 279 | if args: 280 | parser.error('error in arguments; ' 281 | '"%s" ignored.' % (args,)) 282 | 283 | # further process settings & args if necessary 284 | 285 | return settings, args 286 | 287 | if __name__ == "__main__": 288 | # set args and evaluate them 289 | # http://docs.python.org/2/library/optparse.html#optparse-extending-optparse 290 | settings,args = process_command_line() 291 | if settings.version: 292 | print "bin2hex.py %s"%("0.1") 293 | sys.exit(0) 294 | 295 | # make sure the selected hex record type can represent the largest address 296 | maxaddress = HEX_ALLOWED_ADDRESS_TYPES[settings.format] 297 | for (addr, binfile) in settings.binaries: 298 | # don't check filesize, if it's good enough for gnu objcopy it's ok for us. 299 | #if (addr + os.path.getsize(binfile)) > maxaddress: 300 | #print "Error, address+binfile size 0x%0X is too large for format!"%(addr + os.path.getsize(binfile)) 301 | if addr > maxaddress: 302 | print "Error, address size 0x%0X is too large for format!"%(addr) 303 | exit(errno.EINVAL) 304 | 305 | # check output file 306 | try: 307 | if settings.outfile is None: 308 | # set output file based on first input file. 309 | settings.outfile = os.path.splitext(settings.binaries[0][1])[0]+".hex" 310 | # raise ValueError("Output file must be set!") 311 | 312 | # now check the output file, make sure we can open it 313 | with open(settings.outfile, 'w') as f: 314 | pass 315 | except Exception as inst: 316 | print "Error with output file: %s"%inst 317 | sys.exit(errno.EINVAL) 318 | 319 | # now, produce the hex file from the input files and addresses 320 | hexfiledata = generatehexfile(settings.binaries, settings.format) 321 | 322 | # save it to the selected output file 323 | with open(settings.outfile, 'w') as f: 324 | f.write('\n'.join(hexfiledata)) 325 | f.write('\n') # trailing newline 326 | --------------------------------------------------------------------------------