Sophie Dexter
/
Just4Trionic
Just4Trionic - CAN and BDM FLASH programmer for Saab cars
Trionic5.cpp
- Committer:
- Just4pLeisure
- Date:
- 2010-05-19
- Revision:
- 0:e0b964252a05
File content as of revision 0:e0b964252a05:
/******************************************************************************* Trionic5.cpp - By Sophie Dexter, April 2010 This C++ module provides functions for reading and writing the FLASH chips and SRAM in Trionic5 ECUs. (Writing the adaption data back to SRAM not done yet). Some functions need an additional 'bootloader' program to be sent to the T5 ECU before they can be used. These functions are: Identifying the T5 ECU type and FLASH chips, dumping the FLASH chips, erasing the FLASH chips, writing to the FLASH chips and calculating the FLASH chips' checksum. My version of the bootloader, BOOTY.S19, includes some features not in other bootloaders; identifying the ECU and FLASH chip types, a 'safer' way of dumping the FLASH chips and the ability to program AMD 29F010 type FLASH chips (NOTE that AMD 29F010 FLASH programming is experimental at this stage to say the least) ******************************************************************************** WARNING: Use at your own risk, sadly this software comes with no guarantees. This software is provided 'free' and in good faith, but the author does not accept liability for any damage arising from its use. *******************************************************************************/ #include "Trionic5.h" // RS232 on USB connection Serial pc2(USBTX, USBRX); // tx, rx // A timer for timing how long things take to happen Timer timer; // We use CAN on mbed pins 29(CAN_TXD) and 30(CAN_RXD). CAN can_temp(p30, p29); // Need to create this to be able to read and write files on the mbed 'disk' LocalFileSystem local("local"); void Trionic5() { // Start the CAN bus system // Note that at the moment this is only for T5 ECUs at 615 kbits CANOpen(); int counter = 0; //counter used for testing things !?! Trionic5ShowHelp(); while (1) { // send received messages to the pc over USB connection // This function displays any CAN messages that are 'missed' by the other functions // Can messages might be 'missed' because they are received after a 'timeout' period // or because they weren't expected, e.g. if the T5 ECU resets for some reason Trionic5ShowCANMessage(); // Wait for a single command character from the PC if (pc2.readable()) { // char command[30] = "w0068c600000000000000"; // command = pc2.gets(); char c = (pc2.getc()); if ((c=='h') || (c=='H')) Trionic5ShowHelp(); // Print help if (c=='\e') return; // 'ESC' key to go back to mbed CAN tool menu // if ((c=='s') or (c=='S')) { if (c=='s') Trionic5GetSymbolTable(); // Get the Symbol Table if (c=='S') T5ReadCmnd(T5SYMBOLS); // if ((c=='v') or (c=='V')) { if (c=='v') Trionic5GetVersion(); // Get the Trionic5 software version string if (c=='V') T5ReadCmnd(T5VERSION); // if ((c=='r') or (c=='R')) { if (c=='r') Trionic5GetAdaptionData(); // Read Adaption Data from RAM and write it to a file if (c=='R') T5RAMCmnd(counter++); if (c=='\r') T5ReadCmnd(CR); // CR - send CR type message // if ((c=='a') or (c=='A')) { if (c=='a') { // Get a single symbol from the Symbol Table char symbol[40]; T5GetSymbol(symbol); printf("%s",symbol); } if (c=='A') T5Ack(); // Just send an 'ACK' message if ((c=='b') or (c=='B')) Trionic5SendBootLoader(); // Send a Bootloader file to the T5 ECU if ((c=='c') or (c=='C')) Trionic5GetChecksum(); // Get Checksum from ECU (Bootloader must be uploaded first) if ((c=='q') or (c=='Q')) Trionic5BootloaderReset(); // Exit the BootLoader and restart the T5 ECU if ((c=='e') or (c=='E')) Trionic5EraseFLASH(); // Erase the FLASH chips if ((c=='t') or (c=='T')) Trionic5GetChipTypes(); // Read back the FLASH chip types // if ((c=='d') or (c=='D')) Trionic5DumpFLASH(); // DUMP the T5 ECU BIN file stored in the FLASH chips // if (c=='d') Trionic5DumpFLASH(); // DUMP the T5 ECU BIN file stored in the FLASH chips // if (c=='D') Trionic5DumpFLASH2(); // DUMP the T5 ECU BIN file stored in the FLASH chips if ((c=='d') or (c=='D')) Trionic5DumpFLASH2(); // DUMP the T5 ECU BIN file stored in the FLASH chips if ((c=='f') or (c=='F')) Trionic5SendFLASHUpdate(); // Send a FLASH update file to the T5 ECU if (c=='3') Trionic5SendC3Message(); // Send the C3 message - should get last used address 0x7FFFF } } } // // Trionic5ShowHelp // // Displays a list of things that can be done with the T5 ECU. // // inputs: none // return: none // void Trionic5ShowHelp() { printf("Trionic 5 Command Menu\r\n"); printf("======================\r\n"); printf("b - upload and start MyBooty.S19 bootloader\r\n"); printf("c - get T5 ECU FLASH checksum (need toupload BOOTY.S19 before using this command)\r\n"); printf("d - dump the T5 FLASH BIN file and write it to ORIGINAL.HEX\r\n"); printf("e - erase the FLASH chips in the T5 ECU\r\n"); printf("f - FLASH the upate file MODIFIED.S19 to the T5 ECU\r\n"); printf("e - erase the FLASH chips in the T5 ECU\r\n"); printf("r - read SRAM and write it to ADAPTION.HEX file\r\n"); printf("s - read Symbol Table, display it and write it to SYMBOLS.TXT\r\n"); printf("v - read T5 ECU software version, display it and write it to VERSION.TXT\r\n"); printf("q - exit the bootloader and reset the T5 ECU\r\n"); printf("t - read the FLASH chip type in the T5 ECU\r\n"); printf("3 - read the last used FLASH address in the T5 ECU\r\n"); printf("S - send 's' message (to get symbol table)\r\n"); printf("V - send 'S' message (to get software version)\r\n"); printf("'Enter' Key - send an CR message\r\n"); printf("a - send an ACK\r\n"); printf("A - read a single symbol from the symbol table\r\n"); printf("\r\n"); printf("'ESC' - return to mbed CAN tool Menu\r\n"); printf("\r\n"); printf("h/H - show this help menu\r\n"); return; } // // Trionic5ShowCANMessage // // Displays a CAN message in the RX buffer if there is one. // // inputs: none // return: bool TRUE if there was a message, FALSE if no message. // bool Trionic5ShowCANMessage() { CANMessage can_MsgRx; if (can_temp.read(can_MsgRx)) { printf("w%03x%d", can_MsgRx.id, can_MsgRx.len); for (char i=0; i<can_MsgRx.len; i++) { printf("%02x", can_MsgRx.data[i]); } printf(" %c ", can_MsgRx.data[2]); printf("\r\n"); return TRUE; } return FALSE; } // // Trionic5GetSymbolTable // // Gets the T5 ECU symbol table. // The Symbol Table is sent to the PC and saved to a file, symbols.txt, on the mbed 'local' file system 'disk'. // // inputs: none // return: bool TRUE if there all went OK, FALSE if there was an error // bool Trionic5GetSymbolTable() { FILE *fp = fopen("/local/symbols.txt", "w"); // Open "symbols.txt" on the local file system for writing if (!fp) return FALSE; char symbol[40]; char response = '\0'; T5ReadCmnd(T5SYMBOLS); response = T5WaitResponse(); if (response != '>') return FALSE; // printf("%c",response); T5ReadCmnd(CR); response = T5WaitResponse(); if (response != '>') return FALSE; // printf("%c",response); timer.reset(); timer.start(); do { T5GetSymbol(symbol); printf("%s",symbol); fprintf(fp,"%s",symbol); } while (!StrCmp(symbol,"END\r\n")); timer.stop(); printf("Getting the Symbol Table took %f seconds.\r\n",timer.read()); fclose(fp); return TRUE; } // // Trionic5GetVersion // // Gets the T5 software version string. // The software version is is sent to the PC and saved to a file, version.txt, on the mbed 'local' file system 'disk'. // // inputs: none // return: bool TRUE if there all went OK, FALSE if there was an error // bool Trionic5GetVersion() { FILE *fp = fopen("/local/version.txt", "w"); // Open "version.txt" on the local file system for writing if (!fp) return FALSE; char symbol[40]; char response = '\0'; T5ReadCmnd(T5VERSION); response = T5WaitResponse(); if (response != '>') return FALSE; // printf("%c",response); T5ReadCmnd(CR); response = T5WaitResponse(); if (response != '>') return FALSE; // printf("%c",response); T5GetSymbol(symbol); printf("%s",symbol); if (fp) fprintf(fp,"%s",symbol); else printf("ERROR Could not open version.txt for saving\r\n"); fclose(fp); return TRUE; } // // Trionic5GetAdaptionData // // Gets the adaption data from the T5's SRAM. // The adaption data is stored in a hex file, adaption.hex, on the mbed 'local' file system 'disk'. // // Reading the Adaption data from SRAM takes about 16 seconds. // // inputs: none // return: bool TRUE if all went OK, FALSE if there was an error. // bool Trionic5GetAdaptionData() { printf("getting adaption data\r\n"); FILE *fp = fopen("/local/adaption.hex", "w"); // Open "adaption.hex" on the local file system for writing if (!fp) return FALSE; printf("opened adaption data file\r\n"); unsigned int address = 5; // Mysterious reason for starting at 5 !!! char RAMdata[6]; timer.reset(); timer.start(); for (int i=0; i<5462; i++) { // Mysterious number 5462 is 0x8000 / 6 - rounded up !!! T5ReadRAM(RAMdata, address); address += 6; for (int j=0; j<6; j++) { if (((i*6) + j) < T5RAMSIZE) fputc(RAMdata[j],fp); } } timer.stop(); printf("Getting the RAM Data took %f seconds.\r\n",timer.read()); fclose(fp); return TRUE; } // // Trionic5SendBootLoader // // Sends a 'bootloader' file, booty.s19 to the T5 ECU. // The 'bootloader' is stored on the mbed 'local' file system 'disk' and must be in S19 format. // // The 'bootloader' is then able to dump or reFLASH the T5 ECU FLASH chips - this is the whole point of the exercise :-) // // Sending the 'bootloader' to the T5 ECU takes just over 1 second. // // inputs: none // return: bool TRUE if all went OK, // FALSE if the 'bootloader' wasn't sent for some reason. // bool Trionic5SendBootLoader() { FILE *fp = fopen("/local/MyBooty.S19", "r"); // Open "booty.s19" on the local file system for reading if (!fp) return FALSE; int c = 0; // for some reason fgetc returns an int instead of a char int count = 0; // count of bytes in the S-record can be 0xFF = 255 in decimal int asize = 0; // 2,3 or 4 bytes in address unsigned int address = 0; // address to put S-record int checksum = 0; // checksum check at the end of each S-record line char msg[8]; // Construct the bootloader frame for uploading bool sent = FALSE; timer.reset(); timer.start(); while (!sent) { do c = fgetc (fp); // get characters until we get an 'S' (throws away \n,\r characters and other junk) while (c != 'S' && c != EOF); // if (c == EOF) return '\a'; c = fgetc(fp); // get the S-record type 1/2/3 5 7/8/9 (Return if EOF reached) // if ((c = fgetc(fp)) == EOF) return '\a'; // get the S-record type 1/2/3 5 7/8/9 (Return if EOF reached) switch (c) { case '0': break; // Skip over S0 header record case '1': case '2': case '3': asize = 1 + c - '0'; // 2, 3 or 4 bytes for address address = 0; // get the number of bytes (in ascii format) in this S-record line // there must be at least the address and the checksum so return with an error if less than this! if ((c = SRecGetByte(fp)) < (asize + 1)) break; // if ((c = SRecGetByte(fp)) < 3) return '\a'; count = c; checksum = c; // get the address for (int i=0; i<asize; i++) { c = SRecGetByte(fp); checksum += c; address <<= 8; address |= c; count--; } // send a bootloader address message T5SendBootAddress(address, (count-1)); // T5WaitResponsePrint(); T5WaitResponse(); // get and send the bootloader frames for this S-record // NOTE the last frame sent may have less than 7 real data bytes but 7 bytes are always sent. In this case the unnecessary bytes // are repeated from the previous frame. This is OK because the T5 ECU knows how many bytes to expect (because the count of bytes // in the S-Record is sent with the upload address) and ignores any extra bytes in the last frame. for (int i=0; i<count-1; i++) { c = SRecGetByte(fp); checksum += c; msg[1+(i%7)] = c; if (i%7 == 0) msg[0]=i; // set the index number if ((i%7 == 6) || (i == count - 2)) { T5SendBootFrame(msg); // T5WaitResponsePrint(); T5WaitResponse(); } } // get the checksum if (((checksum += SRecGetByte(fp)) | 0xFF) != 0xFF) break; // if ((checksum += SRecGetByte(fp)) != 0xff) return '\a'; break; case '5': break; // Skip over S5 record types case '7': case '8': case '9': asize = 11 - (c - '0'); // 2, 3 or 4 bytes for address // get the number of bytes (in ascii format) in this S-record line there must be just the address and the checksum // so return with an error if other than this! printf("Starting the bootloader, "); if ((c = SRecGetByte(fp)) != (asize + 1)) break; // if ((c = SRecGetByte(fp)) < 3) return '\a'; printf("so far so good"); checksum = c; // get the address for (int i=0; i<asize; i++) { c = SRecGetByte(fp); checksum += c; address <<= 8; address |= c; } printf("calculated the address, "); // get the checksum // if (((checksum += SRecGetByte(fp)) | 0xFF) != 0xFF) break; // if ((checksum += SRecGetByte(fp)) != 0xff) return '\a'; printf("checksum is OK, "); T5StartBootLoader(address); // T5WaitResponsePrint(); sent = TRUE; printf("and we're done :-) \r\n"); break; // Some kind of invalid S-record type so break default: printf("oops - didn't recognise that S-Record \r\n"); return FALSE; // return SREC_FORMAT; } } timer.stop(); printf("Uploading and starting the bootloader took %f seconds. \r\n",timer.read()); fclose(fp); return TRUE; } // // Trionic5GetChecksum // // Calculates the checksum of the FLASH in the T5 ECU. // The 'bootloader', booty.s19, must be loaded before this function can be used. // The 'bootloader' actually calculates the checksum and compares it with the // value stored in the 'header' region at the end of the FLASH. // // The bootloader sends a single CAN message with the result e.g. // w00C8C800CAFEBABE0808 // 00C - T5 response messages have an CAN id of 00C // 8 - All T5 messages have a message length of 8 bytes // C8 - This is the checksum message type // 00 - 00 means OK, the checksum calculation matches the stored value which in this case is: // CAFEBABE - lol :-) // // w00C8C801FFFFFFFF0808 // 01 - 01 means calculated value doesn't matched the stored value // FFFFFFFF - in this case the stored value is FFFFFFFF - the chips might be erased // // Calculating the checksum takes a little under 2 seconds. // // inputs: none // return: bool TRUE if all went OK, // bool Trionic5GetChecksum() { timer.reset(); timer.start(); T5BootCSumCmnd(); T5WaitResponsePrint(); timer.stop(); printf("T5 ECU took took %f seconds to calculate it's checksum.\r\n",timer.read()); return TRUE; } // // Trionic5BootloaderReset // // Exits the Bootloader and restart the T5 ECU // // inputs: none // outputs: bool TRUE if all went OK. // bool Trionic5BootloaderReset() { T5BootResetCmnd(); T5WaitResponsePrint(); return TRUE; } // // Trionic5GetChipTypes // // Gets the FLASH chip type fitted. // // NOTE the bootloader must be loaded in order to use this function. // // CAN messages from the T5 ECU with FLASH data look like this: // // C9,00,aa,aa,aa,aa,mm,dd, // // C9 lets us know its a FLASH id message // // aa,aa,aa,aa is the FLASH start address which we can use to work out if this is a T5.2 or T5.5 ECU // 0x00020000 - T5.2 // 0x00040000 - T5.5 // // mm = Manufacturer id. These can be: // 0x89 - Intel // 0x31 - CSI/CAT // 0x01 - AMD // 0x1F - Atmel // // dd = Device id. These can be: // 0xB8 - Intel _or_ CSI 28F512 (Fiited by Saab in T5.2) // 0xB4 - Intel _or_ CSI 28F010 (Fitted by Saab in T5.5) // 0x25 - AMD 28F512 (Fiited by Saab in T5.2) // 0xA7 - AMD 28F010 (Fitted by Saab in T5.5) // 0x20 - AMD 29F010 (Some people have put these in their T5.5) // 0x5D - Atmel 29C512 (Some people mave have put these in their T5.2) // 0xD5 - Atmel 29C010 (Some people have put these in their T5.5) // // mm = 0xFF, dd == 0xF7 probably means that the programming voltage isn't right. // // Finding out which ECU type and FLASH chips are fitted takes under a second. // // inputs: none // return: bool TRUE if all went OK. // bool Trionic5GetChipTypes() { timer.reset(); timer.start(); T5BootFLASHType(); T5WaitResponsePrint(); timer.stop(); printf("Getting the FLASH chip id took %f seconds.\r\n",timer.read()); return TRUE; } // // Trionic5EraseFLASH // // Erases the FLASH Chips. // // NOTE the bootloader must be loaded in order to use this function. // // CAN messages from the T5 ECU with FLASH erase command look like this: // // C0,cc,08,08,08,08,08,08 // // C0 tells us this is a response to the FLASH erase command. // // cc is a code that tells us what happened: // 00 - FLASH was erased OK // 01 - Could not erase FLASH chips to 0xFF // 02 - Could not write 0x00 to 28F FLASH chips // 03 - Unrecognised FLASH chip type (or maybe programming voltage isn't right) // 04 - Intel chips found, but unrecognised type // 05 - AMD chips found, but unrecognised type // 06 - CSI/Catalyst chips found, but unrecognised type // 07 - Atmel chips found - Atmel chips don't need to be erased // // Erasing 28F type FLASH chips takes around 22 seconds. 29F chips should be erased much more quickly. // // inputs: none // return: bool TRUE if all went OK. // bool Trionic5EraseFLASH() { timer.reset(); timer.start(); T5BootEraseCmnd(); T5WaitResponsePrint(); timer.stop(); printf("Erasing the FLASH took %f seconds.\r\n",timer.read()); return TRUE; } // // Trionic5DumpFLASH // // Dumps the FLASH chip BIN file, original.hex to the mbed 'disk' // // NOTE the bootloader must be loaded in order to use this function. // // CAN messages from the T5 ECU with FLASH data look like this: // // A6,mm,dd,dd,dd,dd,08,08 // // A6 lets us know its a FLASH data message // mm tells us if there is any more data // mm == 04 means at least 4 more bytes to come // mm == 00 means that these are the last FLASH data bytes // dd,dd,dd,dd are 4 FLASH data bytes // The last 2 bytes are 08,08 they aren't and don't meean anything // (It would be more efficient to dump 6 bytes at a time because less // CAN messages would be needed but it was easier to write the // bootloader to send just 4 bytes so that's what I did) // // Dumping FLASH chips in a T5.5 ECU takes around 88 seconds. Dumping T5.2 ECUs should take about half of this time // // inputs: none // return: bool TRUE if all went OK. // bool Trionic5DumpFLASH() { printf("getting FLASH BIN file\r\n"); FILE *fp = fopen("/local/original.hex", "w"); // Open "original.hex" on the local file system for writing if (!fp) return FALSE; printf("opened FLASH BIN file\r\n"); char FLASHdata[8]; timer.reset(); timer.start(); // int count = 0; do { T5BootDumpFLASHCmnd(); // T5WaitResponsePrint(); T5WaitFLASH(FLASHdata); for (int j=0; j<4; j++) { fputc(FLASHdata[j+2],fp); // count++; } } while (FLASHdata[1] != 0); timer.stop(); // printf("count = 0x%x\r\n", count); printf("Getting the FLASH BIN took %f seconds.\r\n",timer.read()); fclose(fp); return TRUE; } // // Trionic5DumpFLASH2 // // Gets the adaption data from the T5's SRAM. // The adaption data is stored in a hex file, adaption.hex, on the mbed 'local' file system 'disk'. // // Reading the Adaption data from SRAM takes about 16 seconds. // // inputs: none // return: bool TRUE if all went OK, FALSE if there was an error. // bool Trionic5DumpFLASH2() { printf("getting FLASH BIN file\r\n"); FILE *fp = fopen("/local/original.hex", "w"); // Open "adaption.hex" on the local file system for writing if (!fp) return FALSE; printf("opened FLASH BIN file\r\n"); unsigned int address = 0x40005; // Mysterious reason for starting at 5 !!! char FLASHdata[6]; timer.reset(); timer.start(); for (int i=0; i<43690; i++) { // Mysterious number 43690 is 0x40000 / 6 T5ReadFLASH(FLASHdata, address); address += 6; for (int j=0; j<6; j++) { fputc(FLASHdata[j],fp); } } address = 0x7FFFF; T5ReadFLASH(FLASHdata, address); for (int j=0; j<4; j++) { fputc(FLASHdata[j+2],fp); } timer.stop(); printf("Getting the FLASH BIN took %f seconds.\r\n",timer.read()); fclose(fp); return TRUE; } // // Trionic5SendFLASHUpdate // // Sends a FLASH update file, modified.s19 to the T5 ECU. // The FLASH update file is stored on the local file system and must be in S19 format. // // FLASHing a T5.5 ECU takes around 90 seconds. FLASHing T5.2 ECUs should take about half of this time // // inputs: none // return: bool TRUE if all went OK, // FALSE if the FLASH update failed for some reason. // bool Trionic5SendFLASHUpdate() { FILE *fp = fopen("/local/modified.s19", "r"); // Open "modified.s19" on the local file system for reading if (!fp) return FALSE; printf("Opened MODIFIED.S19 "); int c = 0; // for some reason fgetc returns an int instead of a char int count = 0; // count of bytes in the S-record can be 0xFF = 255 in decimal int asize = 0; // 2,3 or 4 bytes in address unsigned int address = 0; // address to put S-record int checksum = 0; // checksum check at the end char msg[8]; // Construct the bootloader frame for uploading bool sent = FALSE; timer.reset(); timer.start(); while (!sent) { do c = fgetc (fp); // get characters until we get an 'S' (throws away \n,\r characters and other junk) while (c != 'S' && c != EOF); // if (c == EOF) return '\a'; c = fgetc(fp); // get the S-record type 1/2/3 5 7/8/9 (Return if EOF reached) // if ((c = fgetc(fp)) == EOF) return '\a'; // get the S-record type 1/2/3 5 7/8/9 (Return if EOF reached) switch (c) { case '0': break; // Skip over S0 header record case '1': case '2': case '3': asize = 1 + c - '0'; // 2, 3 or 4 bytes for address address = 0; // get the number of bytes (in ascii format) in this S-record line // there must be at least the address and the checksum so return with an error if less than this! if ((c = SRecGetByte(fp)) < (asize + 1)) break; // if ((c = SRecGetByte(fp)) < 3) return '\a'; count = c; checksum = c; // get the address for (int i=0; i<asize; i++) { c = SRecGetByte(fp); checksum += c; address <<= 8; address |= c; count--; } address += 0x40000; // Only for T5.5 at the moment !!! // send a bootloader address message T5SendBootAddress(address, (count-1)); // T5WaitResponsePrint(); T5WaitResponse(); // get and send the bootloader frames for this S-record // NOTE the last frame sent may have less than 7 real data bytes but 7 bytes are always sent. In this case the unnecessary bytes // are repeated from the previous frame. This is OK because the T5 ECU knows how many bytes to expect (because the count of bytes // in the S-Record is sent with the upload address) and ignores any extra bytes in the last frame. for (int i=0; i<count-1; i++) { c = SRecGetByte(fp); checksum += c; msg[1+(i%7)] = c; if (i%7 == 0) msg[0]=i; // set the index number if ((i%7 == 6) || (i == count - 2)) { T5SendBootFrame(msg); // T5WaitResponsePrint(); T5WaitResponse(); } } // get the checksum if (((checksum += SRecGetByte(fp)) | 0xFF) != 0xFF) break; // if ((checksum += SRecGetByte(fp)) != 0xff) return '\a'; break; case '5': break; // Skip over S5 record types case '7': case '8': case '9': sent = TRUE; printf("and we're done :-) \r\n"); break; // Some kind of invalid S-record type so break default: printf("oops - didn't recognise that S-Record \r\n"); return FALSE; // return SREC_FORMAT; } } timer.stop(); printf("Uploading the FLASH update file took %f seconds. \r\n",timer.read()); fclose(fp); return TRUE; } // // Trionic5SendC3Message // // Sends a C3 Message to the T5 ECU. The reply should contain the last used FLASH address // which is always 0x0007FFFF // // inputs: none // return: bool TRUE if all went OK, // bool Trionic5SendC3Message() { T5BootC3Command(); T5WaitResponsePrint(); printf("The Last used FLASH address message is:\r\n"); return TRUE; }