#!/usr/bin/env python """Generates C code for the nonvolatile memory (NVM) data structures. """ # Imports import argparse import codecs import datetime import glob import logging import os import pathlib import pprint import shutil import colorama import openpyxl APP_NAME = "KTag Autocode Generator" __author__ = "Joe Kearney" __version__ = "00.03" NVM_SPREADSHEET_FILENAME = "2020TPC Nonvolatile Memory.xlsx" SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) DESTINATION_PATHS = ("2020TPCApp1.cydsn/NVM", "2020TPCAppNoDFU.cydsn/NVM") # See https://developer.arm.com/documentation/dui0472/m/c-and-c---implementation-details/basic-data-types-in-arm-c-and-c-- # and # https://en.wikibooks.org/wiki/C_Programming/inttypes.h CortexM_type_sizes_in_bytes = { "bool": 1, "int8_t": 1, "uint8_t": 1, "int16_t": 2, "uint16_t": 2, "int32_t": 4, "uint32_t": 4, "float": 4, "int64_t": 8, "uint64_t": 8, "double": 8, } # Initialize the pretty-printer. pp = pprint.PrettyPrinter(indent=4) # Openpyxl helper function from https://stackoverflow.com/questions/23562366/how-do-i-get-value-present-in-a-merged-cell def getValueWithMergeLookup(sheet, cell): idx = cell.coordinate for my_range in sheet.merged_cells.ranges: merged_cells = list(openpyxl.utils.rows_from_range(str(my_range))) for row in merged_cells: if idx in row: # If this is a merged cell, # return the first cell of the merge range coordinates_of_first_cell = merged_cells[0][0] return sheet[coordinates_of_first_cell].value return sheet[idx].value # Set up logging. rootLogger = logging.getLogger() rootLogger.setLevel(logging.DEBUG) fileHandler = logging.FileHandler('Autocode.log', mode='w') logFormatter = logging.Formatter( '{asctime} {name} {levelname:8s} {message}', style='{') fileHandler.setFormatter(logFormatter) rootLogger.addHandler(fileHandler) consoleHandler = logging.StreamHandler() consoleHandler.setFormatter(logFormatter) rootLogger.addHandler(consoleHandler) logger = logging.getLogger('autocode.py') def main(): version_information = APP_NAME + ' version ' + __version__ logger.info(version_information) colorama.init(autoreset=True) parser = argparse.ArgumentParser( description='Generate autocode', epilog=version_information) commands = parser.add_mutually_exclusive_group() commands.add_argument( '-n', '--nvm', help='Generate nonvolatile memory autocode', action='store_true') parser.add_argument('-v', '--version', action='version', version=version_information) args = parser.parse_args() d = vars(args) if (d['nvm'] == True): generate_NVM_autocode() else: # If no commands were specified, show the help and exit. parser.parse_args(['-h']) def generate_NVM_autocode(): logger.info('Generating nonvolatile memory autocode...') wb = openpyxl.load_workbook( filename=NVM_SPREADSHEET_FILENAME) ws = wb['NVM'] headerColumnsByName = dict() for rowIndex, row in enumerate(ws.iter_rows(min_row=1, max_row=1), start=1): for columnIndex, cell in enumerate(row, start=0): headerColumnsByName[cell.value] = columnIndex NVMItemsByID = dict() NVMLocations = set() NVMEntriesByLocation = dict() for rowIndex, row in enumerate(ws.iter_rows(min_row=2)): if (row[headerColumnsByName['ID']].value is not None): item_ID = int(row[headerColumnsByName['ID']].value) nvm_item = dict() nvm_item['ID'] = item_ID nvm_item['Location'] = row[headerColumnsByName['Location']].value if nvm_item['Location'] != 'None': if nvm_item['Location'] not in NVMLocations: # This is a new location. NVMLocations.add(nvm_item['Location']) NVMEntriesByLocation[nvm_item['Location']] = list() nvm_item['Name'] = row[headerColumnsByName['Item Name']].value nvm_item['Item Name in Code'] = nvm_item['Name'].replace( ' ', '_') nvm_item['Item Shorthand Macro'] = 'NVM_' + \ nvm_item['Name'].upper().replace(' ', '_') nvm_item['Entry'] = row[headerColumnsByName['Entry Name']].value nvm_item['Entry Name in Code'] = 'NVM_' + \ nvm_item['Entry'].replace(' ', '_') nvm_item['Entry Type Name'] = nvm_item['Entry Name in Code'] + '_T' nvm_item['RAM Entry Name in Code'] = 'NVM_' + \ nvm_item['Entry'].replace(' ', '_') nvm_item['Datatype'] = row[headerColumnsByName['Datatype']].value # Validate the datatype and get the size. if nvm_item['Datatype'] not in CortexM_type_sizes_in_bytes.keys(): logger.error('NVM item ' + str(item_ID) + ' on row ' + str(rowIndex + 2) + ' has unknown datatype \"' + str(nvm_item['Datatype']) + '\"!') else: nvm_item['Data Size in Bytes'] = CortexM_type_sizes_in_bytes[nvm_item['Datatype']] nvm_item['Default Value'] = row[headerColumnsByName['Default Value']].value nvm_item['Description'] = row[headerColumnsByName['Description']].value nvm_item['Notes'] = row[headerColumnsByName['Notes']].value # pp.pprint(nvm_item) if nvm_item['Entry'] not in NVMEntriesByLocation[nvm_item['Location']]: NVMEntriesByLocation[nvm_item['Location']].append( nvm_item['Entry']) NVMItemsByID[item_ID] = nvm_item for location in NVMLocations: logger.info('Generating code for ' + location + ' NVM.') with codecs.open('NVM_' + location + 'EEPROMEntries.h', 'w', "utf-8") as f: f.write("/*\n") f.write( r" * __ ________ _____ ______ __ " + "\n") f.write( r" * / //_/_ __/___ _____ _ / ___/____ __ _______________ / ____/___ ____/ /__ " + "\n") f.write( r" * / ,< / / / __ `/ __ `/ \__ \/ __ \/ / / / ___/ ___/ _ \ / / / __ \/ __ / _ \ " + "\n") f.write( r" * / /| | / / / /_/ / /_/ / ___/ / /_/ / /_/ / / / /__/ __/ / /___/ /_/ / /_/ / __/ " + "\n") f.write( r" * /_/ |_|/_/ \__,_/\__, / /____/\____/\__,_/_/ \___/\___/ \____/\____/\__,_/\___/ " + "\n") f.write( r" * /____/ " + "\n") f.write(" *\n") f.write( " * 🃞 THIS FILE IS PART OF THE KTAG SOURCE CODE. Visit https://ktag.clubk.club/ for more. 🃞\n") f.write(" *\n") f.write(" */\n") f.write('\n') f.write('/** \\file\n') f.write( ' * \\brief [Autogenerated] This file declares the ' + location + ' EEPROM entries.\n') f.write(' *\n') f.write( ' * \\note AUTOGENERATED: This file was generated automatically on ' + '{dt:%A}, {dt:%B} {dt.day}, {dt.year} at {dt:%r}'.format(dt=datetime.datetime.now()) + '.\n') f.write(' * DO NOT MODIFY THIS FILE MANUALLY!\n') f.write(' */\n') f.write('\n') f.write('#ifndef NVM_' + location.upper() + 'EEPROMENTRIES_H\n') f.write('#define NVM_' + location.upper() + 'EEPROMENTRIES_H\n') f.write('\n') f.write('#ifdef __cplusplus\n') f.write('extern "C" {\n') f.write('#endif\n') f.write('\n') f.write('/* Preprocessor and Type Definitions */\n') f.write('\n') for entry in NVMEntriesByLocation[location]: entry_name_in_code = 'NVM_' + entry.replace(' ', '_') entry_type_name = entry_name_in_code + '_T' f.write('typedef struct __attribute__((packed))\n') f.write('{\n') # List all the items in this entry. for item_ID in NVMItemsByID: item = NVMItemsByID[item_ID] if item['Entry'] == entry: if item['Description'] is not None: f.write(' //! ' + item['Description'] + '\n') f.write(' ' + item['Datatype'] + ' ' + item['Item Name in Code'] + ';\n') f.write('} ' + entry_type_name + ';\n') f.write('\n') f.write('\n') f.write('/* Include Files */\n') f.write('\n') f.write('/* Public Variables */\n') f.write('\n') for entry in NVMEntriesByLocation[location]: entry_name_in_code = 'NVM_' + entry.replace(' ', '_') entry_type_name = entry_name_in_code + '_T' f.write('extern NVM_EEPROMEntry_T ' + entry_name_in_code + ';\n') f.write('\n') f.write('extern NVM_EEPROMEntry_T * const NVM_' + location + 'EEPROMEntries[];\n') f.write('extern const uint8_t NVM_N_' + location.upper() + '_EEPROM_ENTRIES;\n') f.write('\n') f.write('// Shorthand macros, to save you time.\n') for item_ID in NVMItemsByID: item = NVMItemsByID[item_ID] if item['Location'] == location: f.write('#define ' + item['Item Shorthand Macro'] + ' (((' + item['Entry Type Name'] + '*)' + item['Entry Name in Code'] + '.Value)->' + item['Item Name in Code'] + ')\n') f.write('#define ' + item['Item Shorthand Macro'] + '_ENTRY_PTR (&' + item['Entry Name in Code'] + ')\n') f.write('\n') f.write('\n') f.write('#ifdef __cplusplus\n') f.write('}\n') f.write('#endif\n') f.write('\n') f.write('#endif // NVM_' + location.upper() + 'EEPROMENTRIES_H\n') f.write('\n') with codecs.open('NVM_' + location + 'EEPROMEntries.c', 'w', "utf-8") as f: f.write("/*\n") f.write( r" * __ ________ _____ ______ __ " + "\n") f.write( r" * / //_/_ __/___ _____ _ / ___/____ __ _______________ / ____/___ ____/ /__ " + "\n") f.write( r" * / ,< / / / __ `/ __ `/ \__ \/ __ \/ / / / ___/ ___/ _ \ / / / __ \/ __ / _ \ " + "\n") f.write( r" * / /| | / / / /_/ / /_/ / ___/ / /_/ / /_/ / / / /__/ __/ / /___/ /_/ / /_/ / __/ " + "\n") f.write( r" * /_/ |_|/_/ \__,_/\__, / /____/\____/\__,_/_/ \___/\___/ \____/\____/\__,_/\___/ " + "\n") f.write( r" * /____/ " + "\n") f.write(" *\n") f.write(" * This file is part of the KTag project, a DIY laser tag game with customizable features and wide interoperability.\n") f.write( " * 🛡️ 🃞\n") f.write(" *\n") f.write(" */\n") f.write('\n') f.write('/** \\file\n') f.write( ' * \\brief [Autogenerated] This file defines the ' + location + ' EEPROM entries.\n') f.write(' *\n') f.write( ' * \\note AUTOGENERATED: This file was generated automatically on ' + '{dt:%A}, {dt:%B} {dt.day}, {dt.year} at {dt:%r}'.format(dt=datetime.datetime.now()) + '.\n') f.write(' * DO NOT MODIFY THIS FILE MANUALLY!\n') f.write(' */\n') f.write('\n') f.write('/* Include Files */\n') f.write('#include "KTag.h"\n') f.write('\n') f.write('/* EEPROM Entries */\n') f.write('\n') f.write(r'/** \defgroup NVM_' + location.upper() + '_EEPROM NVM ' + location + ' EEPROM\n') f.write(' *\n') f.write(' * The ' + location + ' EEPROM is divided into logical "entries", represented by instances of the #NVM_EEPROMEntry_T type.\n') f.write(' * At startup, these entries are loaded into their respective RAM copies by NVM_Init' + location + 'EEPROM(). The application\n') f.write(' * then updates the RAM copies directly, and requests that the NVM_' + location + 'EEPROMTask() save these back to the EEPROM\n') f.write(' * when necessary.\n') f.write(' * @{ */\n') f.write('\n') # Here is the magic: keep track of the locations in EE as we create the data structures. current_EE_address_in_bytes = 0 for entry in NVMEntriesByLocation[location]: entry_name_in_code = 'NVM_' + entry.replace(' ', '_') entry_RAM_name_in_code = 'RAM_' + entry.replace(' ', '_') entry_default_name_in_code = 'DEFAULT_' + \ entry.replace(' ', '_') entry_type_name = entry_name_in_code + '_T' f.write('static ' + entry_type_name + ' ' + entry_RAM_name_in_code + ';\n') f.write('\n') f.write('static const ' + entry_type_name + ' ' + entry_default_name_in_code + ' = \n') f.write('{\n') # Assign defaults to all the items in this entry, and calculate the total size. entry_size_in_bytes = 0 CRC_size_in_bytes = 2 for item_ID in NVMItemsByID: item = NVMItemsByID[item_ID] if item['Entry'] == entry: if item['Description'] is not None: f.write(' //! ' + item['Description'] + '\n') f.write( ' .' + item['Item Name in Code'] + ' = ' + str(item['Default Value']) + ',\n') entry_size_in_bytes += item['Data Size in Bytes'] f.write('};\n') f.write('\n') f.write('NVM_EEPROMEntry_T ' + entry_name_in_code + ' = \n') f.write('{\n') f.write(' //! Size == sizeof(' + entry_type_name + ')\n') f.write(' .Size = ' + str(entry_size_in_bytes) + ',\n') f.write(' .EE_Address = ' + str(current_EE_address_in_bytes) + ',\n') current_EE_address_in_bytes += entry_size_in_bytes f.write(' .EE_CRC_Address = ' + str(current_EE_address_in_bytes) + ',\n') current_EE_address_in_bytes += CRC_size_in_bytes f.write(' .Value = (uint8_t *)&' + entry_RAM_name_in_code + ',\n') f.write(' .Default = (uint8_t *)&' + entry_default_name_in_code + ',\n') f.write(' .State = NVM_STATE_UNINITIALIZED\n') f.write('};\n') f.write('\n') f.write('/** @} */\n') f.write('\n') f.write('NVM_EEPROMEntry_T * const NVM_' + location + 'EEPROMEntries[] =\n') f.write('{\n') for entry in NVMEntriesByLocation[location]: entry_name_in_code = 'NVM_' + entry.replace(' ', '_') f.write(' &' + entry_name_in_code + ',\n') f.write('};\n') f.write('\n') f.write('//! Size of the #NVM_' + location + 'EEPROMEntries array (i.e. the number of ' + location + ' EEPROM entries).\n') f.write('const uint8_t NVM_N_' + location.upper() + '_EEPROM_ENTRIES = (uint8_t) (sizeof(NVM_' + location + 'EEPROMEntries) / sizeof(NVM_EEPROMEntry_T *));\n') f.write('\n') for destination in DESTINATION_PATHS: destination = os.path.normpath(os.path.join(SCRIPT_PATH, destination)) logger.info("Copying files to " + destination + ".") for file in glob.glob("NVM_*.[ch]"): shutil.copy2(file, destination) logger.info("Cleaning up.") for file in glob.glob("NVM_*.[ch]"): path = pathlib.Path(file) path.unlink() logger.info('Nonvolatile memory autocode generation completed.') if __name__ == "__main__": main()