Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Extended Address record type mode saving in 'write_hex_file' function #41

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Contributors:
* Stefan Schmitt
* Theo Sbrissa
* Zachary Clifford
* Piotr Korowacki
* "durexyl" @ GitHub
* "erki1993" @ GitHub
* "mentaal" @ GitHub
7 changes: 7 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
IntelHex releases
*****************

2.X (2020-XX-XX)
----------------
* API changes: ``IntelHex.write_hex_file`` method: add support for new
parameter: ``ext_addr_mode = linear | segment | none | auto``.
Option dicedes how Extended Address records are resolved.
Default value is ``linear`` for backward comaptibility. (Piotr Korowacki)

2.3.0 (2020-10-20)
------------------
* Add ``IntelHex.find()`` method to find a given byte pattern. (Scott Armitage)
Expand Down
31 changes: 29 additions & 2 deletions docs/manual/part2-5.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ including HEX, bin, or python dictionaries.
You can write out HEX data contained in object by method ``.write_hex_file(f)``.
Parameter ``f`` should be filename or file-like object.
Note that this can include builtins like sys.stdout.
Also you can use the universal tofile.
Also you can use the universal ``tofile``.

To convert data of IntelHex object to HEX8 file format without actually saving it
to disk you can use the builtin StringIO file-like object, e.g.::
Expand All @@ -25,10 +25,37 @@ Variable ``hexstr`` will contain a string with the content of a HEX8 file.
You can customize hex file output with following optional arguments
to ``write_hex_file`` call:

* ``write_start_addr`` - you can disable start address record in new hex file;
* ``write_start_addr`` - you can disable start address record in new hex file.
* ``eolstyle`` - you can force ``CRLF`` line endings in new hex file.
* ``byte_count`` - you can control how many bytes should be written to each
data record.
* ``ext_addr_mode`` - you can decide how extended address records should be
resolved and written in new hex file (explained below).

Extended Address records are describing an address in binary space from which
a block of data is being written. Normally without those record we could write
only 64KB of binary data into one single ``.hex`` file. This is because in
one signle data record there are only 2 bytes for address. To have possibility
to write more, Extended Records are needed to increase address resolution.
Currently there are two types of records defined in IntelHex format:

* ``Extended Segment Address`` [02] - you can write up to 1MB of binary data
* ``Extended Linear Address`` [04] - you can write up to 4GB of binary data

There are 4 modes given by ``ext_addr_mode`` parameter to support every type
of memory address resolution:

* ``linear`` - forces to use Extended Linear Address records,
in most of cases this mode is commonly desired (default).
* ``segment`` - forces to use Extended Segment Address records.
* ``none`` - forces to not use any Extended Address records (legacy option).
* ``auto`` - automatically decides which mode to use using last known adress
where data need to be written.

Whenever data overflow for different adres resoution is detected adequat
exception will thrown. Mixing of ``linear`` and ``segment`` records isn't
allowed. There won't be any Extened Address records will written in
any mode if data to write need to be placed in address under 64KB.


Data converters
Expand Down
99 changes: 80 additions & 19 deletions intelhex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def _get_eol_textfile(eolstyle, platform):
raise ValueError("wrong eolstyle %s" % repr(eolstyle))
_get_eol_textfile = staticmethod(_get_eol_textfile)

def write_hex_file(self, f, write_start_addr=True, eolstyle='native', byte_count=16):
def write_hex_file(self, f, write_start_addr=True, eolstyle='native', byte_count=16, ext_addr_mode='linear'):
"""Write data to file f in HEX format.

@param f filename or file-like object for writing
Expand All @@ -556,20 +556,55 @@ def write_hex_file(self, f, write_start_addr=True, eolstyle='native', byte_count
for output file on different platforms.
Supported eol styles: 'native', 'CRLF'.
@param byte_count number of bytes in the data field
@param ext_addr_mode used this to decide which record type to
to write for Extended Address records:
Linear (32-bit addressing) or Segment (20-bit addressing)
or without Extended Address records (16-bit addressing)
Supported modes: 'linear', 'segment', 'none', 'auto'.
"""
if byte_count > 255 or byte_count < 1:
raise ValueError("wrong byte_count value: %s" % byte_count)
fwrite = getattr(f, "write", None)
if fwrite:
fobj = f
fclose = None
else:
fobj = open(f, 'w')
fwrite = fobj.write
fclose = fobj.close

eol = IntelHex._get_eol_textfile(eolstyle, sys.platform)

addresses = dict_keys(self._buf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

addresses.sort()
addr_len = len(addresses)
minaddr = addresses[0] if addr_len else 0
maxaddr = addresses[-1] if addr_len else 0

# make parameter case-insensitive
ext_addr_mode = ext_addr_mode.lower()
# resolve extended address type
if ext_addr_mode == 'linear':
# enforces Extended Linear Address record type (default)
extaddr_rectyp = 4
elif ext_addr_mode == 'segment':
# enforces Extended Segment Address record type
extaddr_rectyp = 2
elif ext_addr_mode == 'none':
# enforces no Extended Address records format
extaddr_rectyp = 0
elif ext_addr_mode == 'auto':
# Extended Address record type is resolved by Max Address
if maxaddr > 0x0FFFFF:
extaddr_rectyp = 4
else: # 1MB sapcing is max for Segement
extaddr_rectyp = 2
else:
raise ValueError('ext_addr_mode should be one of:'
' "linear", "segment", "none", "auto";'
' got %r instead' % ext_addr_mode)

# check max address with resolved format
if addr_len:
if extaddr_rectyp == 4 and maxaddr > 0xFFFFFFFF:
raise LinearSpacingOverflowError(overflwd_len=(maxaddr-0xFFFFFFFF))
elif extaddr_rectyp == 2 and maxaddr > 0x0FFFFF:
raise SegmentSpacingOverflowError(overflwd_len=(maxaddr-0x0FFFFF))
elif extaddr_rectyp == 0 and maxaddr > 0xFFFF:
raise BasicSpacingOverflowError(overflwd_len=(maxaddr-0xFFFF))

# Translation table for uppercasing hex ascii string.
# timeit shows that using hexstr.translate(table)
# is faster than hexstr.upper():
Expand All @@ -581,6 +616,15 @@ def write_hex_file(self, f, write_start_addr=True, eolstyle='native', byte_count
# Python 2
table = ''.join(chr(i).upper() for i in range_g(256))

fwrite = getattr(f, "write", None)
if fwrite:
fobj = f
fclose = None
else:
fobj = open(f, 'w')
fwrite = fobj.write
fclose = fobj.close

# start address record if any
if self.start_addr and write_start_addr:
keys = dict_keys(self.start_addr)
Expand Down Expand Up @@ -623,19 +667,13 @@ def write_hex_file(self, f, write_start_addr=True, eolstyle='native', byte_count
raise InvalidStartAddressValueError(start_addr=self.start_addr)

# data
addresses = dict_keys(self._buf)
addresses.sort()
addr_len = len(addresses)
if addr_len:
minaddr = addresses[0]
maxaddr = addresses[-1]

if maxaddr > 65535:
need_offset_record = True
else:
need_offset_record = False
high_ofs = 0

high_ofs = 0
cur_addr = minaddr
cur_ix = 0

Expand All @@ -645,9 +683,15 @@ def write_hex_file(self, f, write_start_addr=True, eolstyle='native', byte_count
bin[0] = 2 # reclen
bin[1] = 0 # offset msb
bin[2] = 0 # offset lsb
bin[3] = 4 # rectyp
high_ofs = int(cur_addr>>16)
bin[3] = extaddr_rectyp # rectyp

if extaddr_rectyp == 4:
high_ofs = int(cur_addr>>16)
else: # extaddr_rectyp == 2:
# 0x00X0000 => 0xX000
high_ofs = int((cur_addr & 0x00F0000) >> 4)
b = divmod(high_ofs, 256)

bin[4] = b[0] # msb of high_ofs
bin[5] = b[1] # lsb of high_ofs
bin[6] = (-sum(bin)) & 0x0FF # chksum
Expand Down Expand Up @@ -1096,7 +1140,7 @@ def bin2hex(fin, fout, offset=0):
return 1

try:
h.tofile(fout, format='hex')
h.write_hex_file(fout)
except IOError:
e = sys.exc_info()[1] # current exception
txt = "ERROR: Could not write to file: %s: %s" % (fout, str(e))
Expand Down Expand Up @@ -1281,6 +1325,10 @@ def ascii_hex_to_int(ascii):
# BadAccess16bit - not enough data to read 16 bit value (deprecated, see NotEnoughDataError)
# NotEnoughDataError - not enough data to read N contiguous bytes
# EmptyIntelHexError - requested operation cannot be performed with empty object
# OverflowError - data overflow general error
# LinearSpacingOverflowError - 32-bit address spacing data overflow
# SegmentSpacingOverflowError - 20-bit address spacing data overflow
# BasicSpacingOverflowError - 16-bit address spacing data overflow

class IntelHexError(Exception):
'''Base Exception class for IntelHex module'''
Expand Down Expand Up @@ -1370,3 +1418,16 @@ class BadAccess16bit(NotEnoughDataError):

class EmptyIntelHexError(IntelHexError):
_fmt = "Requested operation cannot be executed with empty object"


class OverflowError(IntelHexError):
_fmt = 'Base class for overflowed data'

class LinearSpacingOverflowError(OverflowError):
_fmt = 'Data overflow Linear (32-bit) address spacing. Overflowed data: %(overflwd_len)d bytes'

class SegmentSpacingOverflowError(OverflowError):
_fmt = 'Data overflow Segment (20-bit) address spacing. Overflowed data: %(overflwd_len)d bytes'

class BasicSpacingOverflowError(OverflowError):
_fmt = 'Data overflow 16-bit address spacing. Overflowed data: %(overflwd_len)d bytes'
Loading