Just4Trionic - CAN and BDM FLASH programmer for Saab cars

Dependencies:   mbed

Revision:
0:e0b964252a05
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Trionic5.cpp	Wed May 19 12:39:18 2010 +0000
@@ -0,0 +1,728 @@
+/*******************************************************************************
+
+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;
+}