Source code for hexrec.formats.sqtp

# Copyright (c) 2013-2024, Andrea Zoppi
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. 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.
#
# 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 HOLDER 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.

r"""Microchip Serial Quick Time Programming format.

See Also:
    `<https://developerhelp.microchip.com/xwiki/bin/view/software-tools/ipe/sqtp-file-format-specification/>`_
"""

from typing import Any
from typing import Iterable
from typing import Optional
from typing import Sequence
from typing import cast as _cast

from ..base import AnyBytes
from .ihex import IhexFile
from .ihex import IhexRecord


[docs] def from_numbers( numbers: Iterable[int], length: int = 2, start: int = 0, retlw: Optional[int] = None, wordsize: int = 2, byteorder: str = 'little', forcedela: bool = False, ) -> IhexFile: r"""Creates a file from numbers. Given a sequence of numbers, it creates an *Intel HEX* file with the special record sequence and addressing of *Microchip SQTP*. Args: numbers (list of int): Sequence of serial numbers. length (int): Serial number byte size. start (int): Start word address of a serial number within the target memory. retlw (int): If ``None``, this has no effect. If a byte integer is given, it must be the equivalent of the ``RETLW`` *opcode* of the target processor. The ``RETLW`` byte is put after each byte of the serial number. wordsize (int): Memory word size (2 or 4 bytes). byteorder (str): By default, *Microchip SQTP* uses ``little`` endian. Provide ``big`` for the alternative integer byte order. forcedela (bool): Forces *Extended Linear Address* generation. Returns: :class:`IhexFile`: *Microchip SQTP* file as special *Intel HEX* format. Examples: `<https://developerhelp.microchip.com/xwiki/bin/view/software-tools/ipe/sqtp-file-format-specification/examples/>`_. >>> from hexrec.formats.sqtp import from_numbers >>> # Program Memory - PIC18F1220 >>> file = from_numbers(range(5), retlw=0x0C) >>> _ = file.print() :04000000000C000CE4 :04000000010C000CE3 :04000000020C000CE2 :04000000030C000CE1 :04000000040C000CE0 :00000001FF >>> # Program Memory - PIC32MX360F512L >>> file = from_numbers(range(5), length=4, start=0x1D000000, wordsize=4) >>> _ = file.print() :02000004740086 :0400000000000000FC :0400000001000000FB :0400000002000000FA :0400000003000000F9 :0400000004000000F8 :00000001FF >>> # User ID - PIC12F1501 (with correct checksums) >>> numbers = [0xCF7E, 0xC590, 0x110B, 0xF3F2, 0x681C] >>> file = from_numbers(numbers, start=0x8000, retlw=0x34) >>> _ = file.print() :020000040001F9 :040000007E34CF3447 :040000009034C5343F :040000000B34113478 :04000000F234F334AF :040000001C34683410 :00000001FF >>> file = from_numbers(numbers, start=0x8000, retlw=0x34, byteorder='big') >>> _ = file.print() :020000040001F9 :04000000CF347E3447 :04000000C53490343F :0400000011340B3478 :04000000F334F234AF :0400000068341C3410 :00000001FF >>> # User ID - PIC32MX360F512L >>> file = from_numbers(range(5), length=4, start=0x1FC02FF0, wordsize=4) >>> _ = file.print() :020000047F007B :04BFC000000000007D :04BFC000010000007C :04BFC000020000007B :04BFC000030000007A :04BFC0000400000079 :00000001FF >>> # Auxiliary Memory - dsPIC33EP256MU806 >>> file = from_numbers(range(5), length=4, start=0x7FC000, wordsize=4) >>> _ = file.print() :0200000401FFFA :0400000000000000FC :0400000001000000FB :0400000002000000FA :0400000003000000F9 :0400000004000000F8 :00000001FF >>> # Boot Memory - PIC32MX110F016B >>> numbers = [0xC78E2639, 0xE277B71F, 0x3D7E1E03, 0xE2646FD5, 0xA7C293F9] >>> file = from_numbers(numbers, length=4, start=0x1FC00000, wordsize=4) >>> _ = file.print() :020000047F007B :0400000039268EC748 :040000001FB777E2CD :04000000031E7E3D20 :04000000D56F64E272 :04000000F993C2A707 :00000001FF >>> file = from_numbers(numbers, length=4, start=0x1FC00000, wordsize=4, byteorder='big') >>> _ = file.print() :020000047F007B :04000000C78E263948 :04000000E277B71FCD :040000003D7E1E0320 :04000000E2646FD572 :04000000A7C293F907 :00000001FF >>> # EEPROM - PIC12F1840 >>> file = from_numbers(range(5), forcedela=True) >>> _ = file.print() :020000040000FA :020000000000FE :020000000100FD :020000000200FC :020000000300FB :020000000400FA :00000001FF >>> # EEPROM - PIC18F1220 (with actual start address 0x00780000) >>> file = from_numbers(range(5), start=0x00780000, forcedela=True) >>> _ = file.print() :0200000400F00A :020000000000FE :020000000100FD :020000000200FC :020000000300FB :020000000400FA :00000001FF """ byteorder = _cast(Any, byteorder) strings = [ int(number).to_bytes(length, byteorder) for number in numbers ] file = from_strings( strings, start=start, retlw=retlw, wordsize=wordsize, forcedela=forcedela, ) return file
[docs] def from_strings( strings: Sequence[AnyBytes], start: int = 0, retlw: Optional[int] = None, wordsize: int = 2, forcedela: bool = False, ) -> IhexFile: r"""Creates a file from byte strings. Given a sequence of byte strings, it creates an *Intel HEX* file with the special record sequence and addressing of *Microchip SQTP*. All the `strings` must be the same length, between the minimum word size of the processor (minimum 2) and 256. Args: strings (list of bytes): Sequence of byte strings. start (int): Start word address of a byte string within the target memory. retlw (int): If ``None``, this has no effect. If a byte integer is given, it must be the equivalent of the ``RETLW`` *opcode* of the target processor. The ``RETLW`` byte is put after each byte of the byte string. wordsize (int): Memory word size (2 or 4 bytes). forcedela (bool): Forces *Extended Linear Address* generation. Returns: :class:`IhexFile`: *Microchip SQTP* file as special *Intel HEX* format. Examples: >>> from hexrec.formats.sqtp import from_strings >>> strings = [b'abcdefghijklm', b'nopqrstuvwxyz', b'ABCDEFGHIJKLM', b'NOPQRSTUVWXYZ'] >>> file = from_strings(strings, forcedela=True) >>> _ = file.print() :020000040000FA :0D0000006162636465666768696A6B6C6DB8 :0D0000006E6F707172737475767778797A0F :0D0000004142434445464748494A4B4C4D58 :0D0000004E4F505152535455565758595AAF :00000001FF >>> file = from_strings(strings, start=0x1FC02FF0, wordsize=4) >>> _ = file.print() :020000047F007B :0DBFC0006162636465666768696A6B6C6D39 :0DBFC0006E6F707172737475767778797A90 :0DBFC0004142434445464748494A4B4C4DD9 :0DBFC0004E4F505152535455565758595A30 :00000001FF >>> file = from_strings(strings, start=0x8000, retlw=0x34) >>> _ = file.print() :020000040001F9 :1A0000006134623463346434653466346734683469346A346B346C346D3407 :1A0000006E346F3470347134723473347434753476347734783479347A345E :1A0000004134423443344434453446344734483449344A344B344C344D34A7 :1A0000004E344F3450345134523453345434553456345734583459345A34FE :00000001FF """ wordsize = wordsize.__index__() if wordsize != 2 and wordsize != 4: raise ValueError('invalid word size') length = max((len(s) for s in strings), default=wordsize) if not wordsize <= length <= 256: raise ValueError('invalid string length') if any(len(s) != length for s in strings): raise ValueError('inconsistent string length') start = start.__index__() if not 0 <= start <= 0x3FFFFFFF: raise ValueError('start address overflow') if start % wordsize: raise ValueError('misaligned start address') retlw_bytes = bytes([retlw or 0]) * (length * 2) records = [] address = start * wordsize extension = address >> 16 address &= 0xFFFF if extension or forcedela: record = IhexRecord.create_extended_linear_address(extension) records.append(record) for string in strings: if retlw is None: data = string else: data = bytearray(retlw_bytes) data[::2] = string record = IhexRecord.create_data(address, data) records.append(record) record = IhexRecord.create_end_of_file() records.append(record) file = IhexFile.from_records(records) return file
[docs] def to_numbers( file: IhexFile, retlw: bool = False, byteorder: str = 'little', ) -> Sequence[int]: r"""Extracts numbers from a file. Given a *Microchip SQTP* file (as special *Intel HEX* format), it extracts numbers from *data* records. Warnings: This algorithm ignores addressing. It just takes *data* records and converts them into numbers. Please provide valid *Microchip SQTP* files only. Args: file (:class:`IhexFile`): *Microchip SQTP* file as special *Intel HEX* format. retlw (bool): The ``RETLW`` byte is put after each byte of the byte string. If true, it ignores the ``RETLW`` bytes. byteorder (str): By default, *Microchip SQTP* uses ``little`` endian. Provide ``big`` for the alternative integer byte order. Returns: list of int: Sequence of serial numbers. Examples: `<https://developerhelp.microchip.com/xwiki/bin/view/software-tools/ipe/sqtp-file-format-specification/examples/>`_. >>> from hexrec import IhexFile >>> from hexrec.formats.sqtp import to_numbers >>> # Program Memory - PIC18F1220 >>> file = IhexFile.parse(b''' ... :04000000000C000CE4 ... :04000000010C000CE3 ... :04000000020C000CE2 ... :04000000030C000CE1 ... :04000000040C000CE0 ... :00000001FF ... ''') >>> to_numbers(file, retlw=True) [0, 1, 2, 3, 4] >>> # Program Memory - PIC32MX360F512L >>> file = IhexFile.parse(b''' ... :02000004740086 ... :0400000000000000FC ... :0400000001000000FB ... :0400000002000000FA ... :0400000003000000F9 ... :0400000004000000F8 ... :00000001FF ... ''') >>> to_numbers(file) [0, 1, 2, 3, 4] >>> # User ID - PIC12F1501 (with correct checksums) >>> file = IhexFile.parse(b''' ... :020000040001F9 ... :040000007E34CF3447 ... :040000009034C5343F ... :040000000B34113478 ... :04000000F234F334AF ... :040000001C34683410 ... :00000001FF ... ''') >>> to_numbers(file, retlw=True) [53118, 50576, 4363, 62450, 26652] >>> to_numbers(file, retlw=True, byteorder='big') [32463, 37061, 2833, 62195, 7272] >>> # User ID - PIC32MX360F512L >>> file = IhexFile.parse(b''' ... :020000047F007B ... :04BFC000000000007D ... :04BFC000010000007C ... :04BFC000020000007B ... :04BFC000030000007A ... :04BFC0000400000079 ... :00000001FF ... ''') >>> to_numbers(file) [0, 1, 2, 3, 4] >>> # Auxiliary Memory - dsPIC33EP256MU806 >>> file = IhexFile.parse(b''' ... :0200000401FFFA ... :0400000000000000FC ... :0400000001000000FB ... :0400000002000000FA ... :0400000003000000F9 ... :0400000004000000F8 ... :00000001FF ... ''') >>> to_numbers(file) [0, 1, 2, 3, 4] >>> # Boot Memory - PIC32MX110F016B >>> file = IhexFile.parse(b''' ... :020000047F007B ... :0400000039268EC748 ... :040000001FB777E2CD ... :04000000031E7E3D20 ... :04000000D56F64E272 ... :04000000F993C2A707 ... :00000001FF ... ''') >>> to_numbers(file) [3347981881, 3799496479, 1031675395, 3798233045, 2814546937] >>> to_numbers(file, byteorder='big') [958828231, 532117474, 52330045, 3580847330, 4187210407] >>> # EEPROM - PIC12F1840 >>> file = IhexFile.parse(b''' ... :020000040000FA ... :020000000000FE ... :020000000100FD ... :020000000200FC ... :020000000300FB ... :020000000400FA ... :00000001FF ... ''') >>> to_numbers(file) [0, 1, 2, 3, 4] >>> # EEPROM - PIC18F1220 (with actual start address 0x00780000) >>> file = IhexFile.parse(b''' ... :0200000400F00A ... :020000000000FE ... :020000000100FD ... :020000000200FC ... :020000000300FB ... :020000000400FA ... :00000001FF ... ''') >>> to_numbers(file) [0, 1, 2, 3, 4] """ byteorder = _cast(Any, byteorder) numbers = [ int.from_bytes(string, byteorder) for string in to_strings(file, retlw=retlw) ] return numbers
[docs] def to_strings( file: IhexFile, retlw: bool = False, ) -> Sequence[AnyBytes]: r"""Extracts byte strings from a file. Given a *Microchip SQTP* file (as special *Intel HEX* format), it extracts byte strings from *data* records. Warnings: This algorithm ignores addressing. It just takes *data* records. Please provide valid *Microchip SQTP* files only. Args: file (:class:`IhexFile`): *Microchip SQTP* file as special *Intel HEX* format. retlw (bool): The ``RETLW`` byte is put after each byte of the byte string. If true, it ignores the ``RETLW`` bytes. Returns: list of int: Sequence of byte strings. Examples: >>> from hexrec import IhexFile >>> from hexrec.formats.sqtp import to_strings >>> file = IhexFile.parse(b''' ... :020000040000FA ... :0D0000006162636465666768696A6B6C6DB8 ... :0D0000006E6F707172737475767778797A0F ... :0D0000004142434445464748494A4B4C4D58 ... :0D0000004E4F505152535455565758595AAF ... :00000001FF ... ''') >>> to_strings(file) [b'abcdefghijklm', b'nopqrstuvwxyz', b'ABCDEFGHIJKLM', b'NOPQRSTUVWXYZ'] >>> file = IhexFile.parse(b''' ... :020000047F007B ... :0DBFC0006162636465666768696A6B6C6D39 ... :0DBFC0006E6F707172737475767778797A90 ... :0DBFC0004142434445464748494A4B4C4DD9 ... :0DBFC0004E4F505152535455565758595A30 ... :00000001FF ... ''') >>> to_strings(file) [b'abcdefghijklm', b'nopqrstuvwxyz', b'ABCDEFGHIJKLM', b'NOPQRSTUVWXYZ'] >>> file = IhexFile.parse(b''' ... :020000040001F9 ... :1A0000006134623463346434653466346734683469346A346B346C346D3407 ... :1A0000006E346F3470347134723473347434753476347734783479347A345E ... :1A0000004134423443344434453446344734483449344A344B344C344D34A7 ... :1A0000004E344F3450345134523453345434553456345734583459345A34FE ... :00000001FF ... ''') >>> to_strings(file, retlw=True) [b'abcdefghijklm', b'nopqrstuvwxyz', b'ABCDEFGHIJKLM', b'NOPQRSTUVWXYZ'] """ strings = [ record.data for record in file.records if record.tag.is_data() ] if retlw: strings = [string[::2] for string in strings] return strings