Just4Trionic - CAN and BDM FLASH programmer for Saab cars

Dependencies:   mbed

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;
}