Because of the Thanksgiving holiday in the U.S., TI E2E™ design support forum responses may be delayed from November 25 through December 2. Thank you for your patience.

This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TMS320F28378D: Vector CANape Intel Hex File To C2000 Hex File Converter

Part Number: TMS320F28378D
Other Parts Discussed in Thread: UNIFLASH

The following assumes that the slave ECU does not support flashing by XCP.

UniFlash expects hex files to have romwidth=16 for C2000 devices. In short, if you try to download a regular Intel Hex file with UniFlash to a C2000 device it won't program as intended. More info:

 https://e2e.ti.com/support/tools/code-composer-studio-group/ccs/f/code-composer-studio-forum/1224488/uniflash-c2000-hex-file-format-compatibility


I am using Vector CANape for data acquisition and calibration. After a calibration session CANape can save an image file (Intel hex) of memory containing the calibration constants (CHARACTERISTICS in ASAP2 speak), the idea being that the file is flashed to the device to freeze the calibration changes in that particular device without having to recompile the software.

The challenge then is how to convert the CANape true Intel hex file to a UniFlash C2000 hex file (romwidth=16) so that CANape (or any other MCD tool) can be used in an automotive workflow. My solution is to parse the CANape hex file using a Python script to place the octets in the right position at the right address that allows UniFlash to program a C2000 device. Please find below the Python script.

 It expects a bespoke addressing scheme outlined in this thread https://e2e.ti.com/support/microcontrollers/c2000-microcontrollers-group/c2000/f/c2000-microcontrollers-forum/1225978/tms320f28379d-implementation-of-xcp-on-can-for-c2000 where the ASAP2 address space is 2 x the device address space. Granted this is a very specific use case but the script could be modified so that other 3rd party tools generating true Intel hex files could be made to work with C2000 / UniFlash.

Note 1: The input half is done by Python IntelHex (https://pypi.org/project/intelhex/). A quick solution would be to use its 'hex2bin' script where the bin file could be used directly with UniFlash as long as you are happy to manually input the starting address. However, I wanted a self-contained programmable file and furthermore in exactly the same format that is output from CCS by the hex2000 utility. The latter means I can file compare the compiler output "calibration" with the CANape output calibration.

Note 2: Python IntelHex has a class IntelHex16bit but I could not get this to output a C2000 hex file.

# Python program to create a word addressable HEX file suitable for TI UniFlash
# to program a C2000 device from a byte addressable Intel HEX file generated by CANape.
#
# It expects that the CANape (XCP / ASAP2) addresses are 2 x C2000 device addresses.
#
# Usage : python.exe CANapeHex2UniFlashHex.py canape_input.hex uniflash_output.hex
#
# Tested with Python 3.10.

import sys

# Class for the input Intel hex file. Credit: https://pypi.org/project/intelhex/
from intelhex import IntelHex


class UniFlashDict:
    '''Creates a dictionary of values with additional metadata suitable for UniFlash hex file construction from an IntelHex object'''

    def __init__(this, intelhex_obj):
        # New empty dictionary.
        this.dict = dict()

        # Get the ASAP2 Address range of Intel HEX file but 
        # halve to give the Device Address.
        this.min_addr = int(intelhex_obj.minaddr()/2)
        this.max_addr = int(intelhex_obj.maxaddr()/2)

        # Prepare values used for record construction.
        this.ext_linear_addr = this.min_addr >> 16 & 0xFFFF
        this.seg_base_addr   = this.min_addr & 0xFFFF

        # Iterate through the ASAP2 byte addressed dictionary and create a word addressed dictionary.
        idx_uf = this.min_addr
        idx_ih = intelhex_obj.minaddr()
        while idx_ih < intelhex_obj.maxaddr():
            this.dict[idx_uf] = intelhex_obj[idx_ih] | (intelhex_obj[idx_ih + 1] << 8)
            # Increment ASAP2 address by two bytes.
            idx_ih += 2
            # Increment C2000 address by one word.
            idx_uf += 1

    def minaddr(this):
        '''The absolute start address.'''
        return this.min_addr
        
    def maxaddr(this):
        '''The absolute end address.'''
        return this.max_addr

    def extended_linear_address(this):
        '''Returns the two data bytes (big endian) which specify the upper 16 bits of the 32 bit absolute address
           for all subsequent type 00 records; these upper address bits apply until the next 04 record.'''
        return this.ext_linear_addr

    def segment_base_address(this):
        '''The 16-bit starting address for the data.'''
        return this.seg_base_addr


def update_checksum( current_csum, new_data, num_of_bytes ):
    '''Updates the running total of the input checksum with new data.'''
    idx = num_of_bytes
    ret_csum = current_csum
    temp_new_data = new_data

    while(idx > 0):
        ret_csum += temp_new_data & 0xFF
        temp_new_data =  temp_new_data >> 8
        idx -= 1

    return ret_csum
   
def finalize_checksum( current_csum ):
    '''Returns the LSB of the two's complement of the input.'''
    ret_csum = current_csum
    
    ret_csum = ~ret_csum       # Invert
    ret_csum = ret_csum + 1    # Add 1
    ret_csum = ret_csum & 0xFF # Keep LSB only.

    return ret_csum


def main():
    DATA_REC_TYPE = 0x00
    DATA_REC_TYPE_STR = f'{DATA_REC_TYPE:0{2}X}'
    DATA_REC_BYTE_COUNT = 0x20 # Fixed at 32 bytes.
    DATA_REC_BYTE_COUNT_STR = f'{DATA_REC_BYTE_COUNT:0{2}X}'

    EXT_LIN_ADDR_REC_TYPE = 0x02000004
    EXT_LIN_ADDR_REC_TYPE_STR = f'{EXT_LIN_ADDR_REC_TYPE:0{8}X}'

    EOF_REC_STR = ':00000001FF'

    try:
        # Get an instance of the Intel HEX file input.
        ih_in = IntelHex(sys.argv[1])

        # Open/create a file for the output.
        uf_out_file = open(sys.argv[2], "w")

        # Get an instance of the UniFlash dictionary.
        uf_dict = UniFlashDict(ih_in)

        # Prepare and write the Extended Linear Address record. e.g. ":020000040009F1"
        record_str = ':' + EXT_LIN_ADDR_REC_TYPE_STR + f'{uf_dict.extended_linear_address():0{4}X}'
        record_csum = update_checksum(0,EXT_LIN_ADDR_REC_TYPE,4)

        record_csum = update_checksum(record_csum,uf_dict.extended_linear_address(),4)
        record_csum = finalize_checksum(record_csum)

        # Append checksum byte.
        record_str += f'{record_csum:0{2}X}'

        uf_out_file.write(record_str+"\n")

        # Get the address for the first record.
        record_start_address = uf_dict.segment_base_address()     

        # Initialise loop variables.
        idx = uf_dict.minaddr()
        word_counter = 0

        # Loop through all dictionary values and create multiple data records in the output file.
        while idx < (uf_dict.minaddr() + len(uf_dict.dict)):
           
            if word_counter == 0:
                # At the start of the record, concatenate the byte count, address and record type. e.g. ":20C13000"
                record_str = ':' + DATA_REC_BYTE_COUNT_STR + f'{record_start_address:0{4}X}' + DATA_REC_TYPE_STR
                record_csum = DATA_REC_BYTE_COUNT + ((record_start_address & 0xFF00) >> 8) + (record_start_address & 0x00FF) + DATA_REC_TYPE
            
            # Process a word.
            # Concatenate dictionary values (words)
            record_str += f'{uf_dict.dict[idx]:0{4}X}'
            # Keep a running total of each word (2 bytes) for the checksum.
            record_csum = update_checksum(record_csum, uf_dict.dict[idx], 2)

            # Keep a count of how many words have been processed for each record.
            word_counter += 1

            # After sixteen words: Finalize checksum, write to the file and prepare for a new record.
            if word_counter == 16:
                # Finalize checksum.
                record_csum = finalize_checksum( record_csum )

                # Append checksum byte.
                record_str += f'{record_csum:0{2}X}'

                # Record is now complete so output to file with a new line.
                uf_out_file.write(record_str+"\n")

                # Calculate next start address. i.e. Add 16 (words).
                record_start_address += 16
                # Force prepartion of a new record.
                word_counter = 0

            # Move to the next word in the dictionary.
            idx += 1

        # Write the End Of File record. It doesn't change so can use a literal.
        record_str = EOF_REC_STR

        uf_out_file.write(record_str+"\n")

        uf_out_file.close()

    except Exception as e:
      print("\nError! " + repr(e))


if (len(sys.argv) != 3):
    print("Usage:")
    print("python.exe IntelHex2UniFlashHex.py intel.hex uniflash.hex")
else:
    main()