<filename>crctool.py
#!/usr/bin/env python3
import sys
import struct
import argparse
from zlib import crc32
from pathlib import Path


def bit_rev(val):
    "Reverse bits of 32-bit words, stored as ints"
    if isinstance(val, int):
        return int('{:032b}'.format(val)[::-1], 2)
    else:
        output = []
        for x in val:
            output.append(int('{:032b}'.format(x)[::-1], 2))
        return output


def calculate_stm32_crc(data):
    """Generate a CRC compatible with the STM32 hardware CRC

        STM's CRC uses a normal CRC32 (MSB first) with a reset value of 0xFFFFFFFF
        zlib uses a reversed CRC32 (LSB first) with a reset value of 0
        To convert from zlib to STM formats we have to:
        - bit reverse every 32-bit word of the input
        - bit reverse the CRC generated by zlib.crc32
        - bit invert the result as an unsigned number
        from https://community.st.com/s/question/0D50X00009XkbXeSAJ/crc32-calculation-mismatch (post 3)
    """
    # TODO test if data len is a multiple of 4
    words = len(data) / 4

    if not words.is_integer():
        raise ValueError("Data size must be a multiple of 4 bytes")

    # convert data into 32 bit words
    data_words = struct.unpack(f'{int(words)}I', data)

    # bit reverse words and convert back to bytes
    data_rev = struct.pack(f'{int(words)}I', *bit_rev(data_words))

    # compute an inital CRC using a reverse 32 bit polynomial
    crc_raw = crc32(data_rev)
    # invert the CRC
    crc = bit_rev(crc_raw) ^ 0xffffffff

    return crc


def insert_crc(data, crc, addr):
    """Insert CRC-32 at addr
        This overwrites the 4 bytes at addr
    """
    data_array = bytearray(data)  # make the data mutable

    # convert CRC to bytes, padded to 4 bytes
    crc_bytes = crc.to_bytes(4, byteorder=sys.byteorder)

    # insert CRC using slicing
    data_array[addr:(addr + 4)] = crc_bytes

    return bytes(data_array)


def ExistingFile(val):
    """ To be used as an argparse argument type
        Tests the file exists without opening it to avoid issues with files
        not being closed: https://bugs.python.org/issue13824
    """
    f = Path(val)
    try:
        if not f.is_file():
            raise argparse.ArgumentTypeError(f'File not found {val}')
    except PermissionError as e:
        raise argparse.ArgumentTypeError(e)

    return f


def main():
    parser = argparse.ArgumentParser(
        description="Compute STM32 compatible CRC32 values"
    )
    parser.add_argument(
        '-w', '--write', action='store_true',
        help="Write the CRC value to the third 32-bit word of the file"
    )
    parser.add_argument(
        '-S', '--start', metavar='start_offset', type=int, default=0,
        help="The address offset from the start of the file begin calculating the CRC from"
    )
    parser.add_argument('file', type=ExistingFile, help="The file to compute the CRC on")

    args = parser.parse_args()

    data = args.file.read_bytes()
    try:
        crc = calculate_stm32_crc(data[args.start:])
    except ValueError as e:
        print(e)
        return

    if args.write:
        data = insert_crc(data, crc, args.start + 8)
        try:
            args.file.write_bytes(data)
        except PermissionError():
            print(f"Failed to write {args.file}, CRC is {crc:8X}")
    else:
        print(f"{crc:8X}")


if __name__ == "__main__":
    main()

<filename>crctool.py
#!/usr/bin/env python3
import sys
import struct
import argparse
from zlib import crc32
from pathlib import Path


def bit_rev(val):
    "Reverse bits of 32-bit words, stored as ints"
    if isinstance(val, int):
        return int('{:032b}'.format(val)[::-1], 2)
    else:
        output = []
        for x in val:
            output.append(int('{:032b}'.format(x)[::-1], 2))
        return output


def calculate_stm32_crc(data):
    """Generate a CRC compatible with the STM32 hardware CRC

        STM's CRC uses a normal CRC32 (MSB first) with a reset value of 0xFFFFFFFF
        zlib uses a reversed CRC32 (LSB first) with a reset value of 0
        To convert from zlib to STM formats we have to:
        - bit reverse every 32-bit word of the input
        - bit reverse the CRC generated by zlib.crc32
        - bit invert the result as an unsigned number
        from https://community.st.com/s/question/0D50X00009XkbXeSAJ/crc32-calculation-mismatch (post 3)
    """
    # TODO test if data len is a multiple of 4
    words = len(data) / 4

    if not words.is_integer():
        raise ValueError("Data size must be a multiple of 4 bytes")

    # convert data into 32 bit words
    data_words = struct.unpack(f'{int(words)}I', data)

    # bit reverse words and convert back to bytes
    data_rev = struct.pack(f'{int(words)}I', *bit_rev(data_words))

    # compute an inital CRC using a reverse 32 bit polynomial
    crc_raw = crc32(data_rev)
    # invert the CRC
    crc = bit_rev(crc_raw) ^ 0xffffffff

    return crc


def insert_crc(data, crc, addr):
    """Insert CRC-32 at addr
        This overwrites the 4 bytes at addr
    """
    data_array = bytearray(data)  # make the data mutable

    # convert CRC to bytes, padded to 4 bytes
    crc_bytes = crc.to_bytes(4, byteorder=sys.byteorder)

    # insert CRC using slicing
    data_array[addr:(addr + 4)] = crc_bytes

    return bytes(data_array)


def ExistingFile(val):
    """ To be used as an argparse argument type
        Tests the file exists without opening it to avoid issues with files
        not being closed: https://bugs.python.org/issue13824
    """
    f = Path(val)
    try:
        if not f.is_file():
            raise argparse.ArgumentTypeError(f'File not found {val}')
    except PermissionError as e:
        raise argparse.ArgumentTypeError(e)

    return f


def main():
    parser = argparse.ArgumentParser(
        description="Compute STM32 compatible CRC32 values"
    )
    parser.add_argument(
        '-w', '--write', action='store_true',
        help="Write the CRC value to the third 32-bit word of the file"
    )
    parser.add_argument(
        '-S', '--start', metavar='start_offset', type=int, default=0,
        help="The address offset from the start of the file begin calculating the CRC from"
    )
    parser.add_argument('file', type=ExistingFile, help="The file to compute the CRC on")

    args = parser.parse_args()

    data = args.file.read_bytes()
    try:
        crc = calculate_stm32_crc(data[args.start:])
    except ValueError as e:
        print(e)
        return

    if args.write:
        data = insert_crc(data, crc, args.start + 8)
        try:
            args.file.write_bytes(data)
        except PermissionError():
            print(f"Failed to write {args.file}, CRC is {crc:8X}")
    else:
        print(f"{crc:8X}")


if __name__ == "__main__":
    main()

#!/usr/bin/env python3 Generate a CRC compatible with the STM32 hardware CRC          STM's CRC uses a normal CRC32 (MSB first) with a reset value of 0xFFFFFFFF         zlib uses a reversed CRC32 (LSB first) with a reset value of 0         To convert from zlib to STM formats we have to:         - bit reverse every 32-bit word of the input         - bit reverse the CRC generated by zlib.crc32         - bit invert the result as an unsigned number         from https://community.st.com/s/question/0D50X00009XkbXeSAJ/crc32-calculation-mismatch (post 3) # TODO test if data len is a multiple of 4 # convert data into 32 bit words # bit reverse words and convert back to bytes # compute an inital CRC using a reverse 32 bit polynomial # invert the CRC Insert CRC-32 at addr         This overwrites the 4 bytes at addr # make the data mutable # convert CRC to bytes, padded to 4 bytes # insert CRC using slicing To be used as an argparse argument type         Tests the file exists without opening it to avoid issues with files         not being closed: https://bugs.python.org/issue13824