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:
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()