//
//  Name:         gs.c
//
//
//  Revision History
//
//   M. Gray       04 Feb 2002       V1.00  Initial release.
//
//   M. Gray       30 May Mar 2002   V1.01  Incremental release for web site.
//
//   M. Gray       
//
// 
//  Copyright (c) 2002 Michael Gray, KD7LMO
// 
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

// Disable debug information.
#ifndef WIN32
#nodebug
#endif

// PROGRAMMING NOTE: Since we don't have embedded C++ for this processor,
// we try to use C in a manner that emulates classes and methods.  Each method
// and variable is prefaced by the name of the class.  The classes
// have a method that acts as a constructor and sometimes a destructor.
// Even though variables are declared as global, they are only accessed
// through the appropriate class method.  The order of the classes and
// variables is not important in the application because we only access
// the class through a method.

// To avoid confusion in the sizes of short, int, and long on 8, 16, and 32 bit processors,
// we will be very explicit.  The typedefs MUST be checked when cross compiling or changing
// compilers.
typedef char bool;
typedef char int8;
typedef long int32;

// Boolean flags.
#define TRUE 1
#define FALSE 0

#ifdef WIN32
#define NULL 0
#endif

// Allocate buffers for the serial ports.
//#define CINBUFSIZE 511
//#define COUTBUFSIZE 511

#define DINBUFSIZE 2047
#define DOUTBUFSIZE 2047

// We simulate the rabbit enviroment for cross compiling in Visual Studio.
#ifdef WIN32
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.H>
#define PADR 0
#define PBDR 1
#define PDDR 3
#define PEDR 4
#define TBM1R 10
#define TBL1R 11
#define TBCSR 12
#define TBCR 14
#define TAT1R 15
#define SPCR 17
#define PDFR 18
#define PDDDR 19
#define PDDCR 20
#define PDCR 21
#define PEDDR 22
#define PEFR 23
#define I0CR 24
#define TACR 25
#define TACSR 26
#define TAT4R 27
#define PECR 28
#define IB7CR 29
#define I1CR 30
#define TBCLR 31

#define SEC_TIMER 1
#define MS_TIMER 1000

#define PI 3.14159265358

// Variables and functions provided by Rabbit library.
uint16 PADRShadow, PDDRShadow, PEDRShadow, TAT1RShadow, TACSRShadow, TACRShadow, TAT4RShadow;
void WrPortI (uint16 port, uint16 *shadow, uint16 value) {};
void WrPortE (uint16 port, uint16 *shadow, uint16 value) {};
void BitWrPortI (uint16 port, uint16 *shadow, uint8 value, uint8 bit) {};
uint16 BitRdPortI (uint16 port, uint8 bit) { return 0; };
uint16 RdPortI (uint16 port) { return 0; };
uint16 RdPortE (uint16 port) { return 0; };  
uint16 serCgetc () { return 0; };
void serAopen (uint16 baudrate) { };
void serCopen (uint16 baudrate) { };
void serDopen (uint16 baudrate) { };
void serAputc (uint8 value) { putchar(value); };
void serAputs (uint8 *str) { puts (str); };
void serCputc (uint8 value) { putchar(value); };
void serCputs (uint8 *str) { puts (str); };
void serDputc (uint16 value) { putchar(value); };
void serDputs (uint8 *str) { puts (str); };
uint8 serDgetc () { return 0; };
void clockDoublerOn() { };
void SetVectExtern2000 (uint8 vector, void func()) {};
void SetVectIntern (uint8 vector, void func()) {};
uint8 VdGetFreeWd (uint8 timeOut) { return 0; };
void VdHitWd (uint8 value) {};
void forceSoftReset() {};
#endif


// Public methods and data structures for each class.
typedef struct {
    uint16 faultCount, badHeaderCRCCount, invalidHeaderCount, badDataCRCCount, bufferOverflow;
    uint16 carrierDetectCount, rxPacketCount;
    uint16 txPacketCount;
} COM_STATS;

typedef struct {
    uint8 packetNumber;
    uint8 hours, minutes, seconds;
    int32 latitude, longitude;
    int16 gpsAltitude, presAltitude;
    int16 gpsVSI, presVSI;
    uint16 gpsSpeed, airSpeed;
    uint16 gpsHeading, compassHeading;
    int16 roll, pitch;
    uint16 dist, heading;
    uint8 busVoltage;
    int16 intTemp, extTemp;
    uint16 gpsStatus, gpsDOP;
    uint8 trackedSats, visibleSats;
    uint8 lastRxSQE;
} TLM_STATUS;

void cncClearCounters();
void cncClearRxBuffer();
uint8 *cncGetRxBuffer();
void cncInit();
void cncReadData();
uint8 cncIsRxReady();
uint8 cncTxPacket (uint8 messageType, uint8 *data, uint16 length);

void comAckPacket(uint8 *message, uint16 length);
void comClearCounters();
void comClearRxBuffer();
uint8 *comGetRxBuffer();
uint8 comGetSQE();
COM_STATS *comGetStats();
void comInit();
uint8 comIsCRCBad();
uint8 comIsRxReady();
void comRx();
void comTimer();
uint8 comTxPacket(uint8 messageType, uint8 *data, uint16 length);

uint8 configInit();
uint8 configGetComTxDelay();

uint16 sysCRC16 (uint8 *buffer, uint16 length);
void sysDelay (uint32 delayMS);
void sysInit();
void sysInterruptEnable();
bool sysIsCarrierDetect();
void sysTxLEDOff();
void sysTxLEDOn();
void sysRxLEDOff();
void sysRxLEDOn();
uint8 sysNMEAChecksum (uint8 *buffer, uint16 length);
uint8 sysParseHexDigit(uint8 digit);
void sysPTTOff();
void sysPTTOn();
void sysSelfTest();

#ifdef WIN32
void sysTimerISR();
#else
interrupt void sysTimerISR();
#endif

/**
 *   Class to handle command and control
 */
#define CNC_START 0
#define CNC_HEADER 1
#define CNC_LENGTH1 2
#define CNC_LENGTH2 3
#define CNC_READDATA 4

#define CNC_HEADER_MSB 0x8a
#define CNC_HEADER_LSB 0xd7

#define CNC_BUFFER_SIZE 580

// First byte used to define message type.
#define CNC_MESSAGE_CMD 0x10
#define CNC_MESSAGE_CMDACK 0x11
#define CNC_MESSAGE_TELEMETRY 0x12
#define CNC_MESSAGE_LOG_STATUS 0x13
#define CNC_MESSAGE_OPS_STATE 0x14

// Second byte used to define mnemonics and status.
#define CNC_COMMAND_TLMRATE 0x20
#define CNC_COMMAND_APRS 0x21
#define CNC_COMMAND_SETORIGIN 0x22
#define CNC_COMMAND_LOG 0x23
#define CNC_COMMAND_OPS_STATE 0x24
#define CNC_COMMAND_ZERO_INSTRUMENTS 0x25
#define CNC_COMMAND_TEST_A 0x26
#define CNC_COMMAND_TEST_B 0x27

#define CNC_STATUS_TIMEOUT 0x80

// Third byte used to define mnemonic constants.
#define CNC_LOG_DUMP 0x00
#define CNC_LOG_CLEAR 0x01

typedef struct {
    uint16 bufferOverflow, rxPacketCount, txPacketCount;
} CNCSTATS;

// State machine used to read binary data stream.
uint8 cncMode;

// Length of the receive and temporary receive buffers.
uint16 cncRxLength, cncTempRxLength;

// Index to the buffers.
uint16 cncIndex;

// Flag to indicate if the receive buffer is in use.
bool cncRxBusyFlag;

// Transmit, receive, and temporary buffers.
uint8 cncTempBuffer[CNC_BUFFER_SIZE + 1];
uint8 cncRxBuffer[CNC_BUFFER_SIZE + 1];

// Structure that has stats on C and C.
CNCSTATS cncStats;

/**
 *   Clear the com link statistical database.
 */
void cncClearCounters()
{
    cncStats.bufferOverflow = 0;
    cncStats.rxPacketCount = 0;
    cncStats.txPacketCount = 0;
}

/**
 *   Clear the receive buffer ready flag.  This flag must be cleared after a message is processed in
 *   order to get a new message.  If the flag is not cleared in time, the <b>bufferOverflow</b> statistic
 *   value is incremented.
 */
void cncClearRxBuffer()
{
    cncRxBusyFlag = FALSE;
}

void cncInit()
{
    cncClearCounters();

    cncMode = CNC_START;
    cncRxBusyFlag = FALSE;
}

/**
 *   Determine if a message is waiting in the receive buffer.
 *
 *   @return true if message ready; otherwise false
 */
uint8 cncIsRxReady()
{
    return cncRxBusyFlag;
}

uint8 *cncGetRxBuffer()
{
    return cncRxBuffer;
}

void cncReadData()
{
    uint16 i, value;
    
    // This state machine handles each characters as it is read from C and C serial port.
    // We are looking for the binary message <header><length><messageType><data ...>
    while ((value = serDgetc()) != -1)
        switch (cncMode) {
            // Wait for the MSB of the header.
            case CNC_START:
                if (value == CNC_HEADER_MSB)
                    cncMode = CNC_HEADER;
                break;

            case CNC_HEADER:
                if (value == CNC_HEADER_LSB)
                    cncMode = CNC_LENGTH1;
                else
                    cncMode = CNC_START;
                break;

            case CNC_LENGTH1:
                cncTempRxLength = value << 8;
                cncMode = CNC_LENGTH2;
                break;

            case CNC_LENGTH2:
                cncTempRxLength |= value;

                if (cncTempRxLength == 0 || cncTempRxLength > CNC_BUFFER_SIZE)
                    cncMode = CNC_START;
                else {
                    cncMode = CNC_READDATA;
                    cncIndex = 0;
                }
                break;

            case CNC_READDATA:
                cncTempBuffer[cncIndex] = value;

                if (++cncIndex == cncTempRxLength) {
                    if (cncRxBusyFlag)
                        ++cncStats.bufferOverflow;
                    else {
                        // Copy the temporary buffer to the receive buffer.
                        for (i = 0; i < cncIndex; ++i)
                            cncRxBuffer[i] = cncTempBuffer[i];

                        // Just in case the sender doesn't NULL terminate a string, we will.
                        cncRxBuffer[i] = 0;

                        // Keep track of how many packets we have received.
                        ++cncStats.rxPacketCount;

                        // Set the flag to indicate we have a new message.
                        cncRxBusyFlag = TRUE;
                    } // END if-else cncRxBusyFlag

                    cncMode = CNC_START;
                }
                break;
        } // END switch
}

/**
 *   Send a data packet over the C and C link.
 *
 *   @param data pointer to data block
 *   @param 4-bit message type
 *   @param length of message in bytes
 *
 *   @return true if messaged queue for transmit; otherwise false
 */
uint8 cncTxPacket (uint8 messageType, uint8 *data, uint16 length)
{
    uint16 i;

    // Add one to the length to include the message type.
    ++length;

    // Check the length.
    if (length > CNC_BUFFER_SIZE)
        return FALSE;

    // Send the message header, length, type, and data.
    serDputc (CNC_HEADER_MSB);
    serDputc (CNC_HEADER_LSB);

    serDputc ((length >> 8) & 0xff);
    serDputc (length & 0xff);

    serDputc (messageType);

    for (i = 0; i < length; ++i)
        serDputc (data[i]);

    ++cncStats.txPacketCount;

    return TRUE;
}

/**
 *   Class to handle the primary com radio.
 */
 
// CMX909 memory mapped registers.
#define COM_CMX909_DATA 0xe000
#define COM_CMX909_COMMAND 0xe001
#define COM_CMX909_STATUS 0xe001
#define COM_CMX909_CONTROL 0xe002
#define COM_CMX909_DQ 0xe002
#define COM_CMX909_MODE 0xe003

// CMX909 receive mode commands.
#define COM_CMD_SFH 0x01
#define COM_CMD_R3H 0x02
#define COM_CMD_RDB 0x03
#define COM_CMD_SFS 0x04
#define COM_CMD_RSB 0x05
#define COM_CMD_LFSB 0x06
#define COM_CMD_RESET 0x07

// CMX909 control bit masks.
#define COM_AQLEV_MASK 0x40
#define COM_AQBC_MASK 0x80

// CMX909 transmit mode commands.
#define COM_CMD_T7H 0x01
#define COM_CMD_TDB 0x03
#define COM_CMD_TQB 0x04
#define COM_CMD_TSB 0x05
#define COM_CMD_TSO 0x06

// CMX909 mode commands.
#define COM_CMD_PSBIXT 0x0f

// Frame headers.
#define COM_BASE_BITSYNC 0xcc
#define COM_MOBLE_BITSYNC 0x33

// Frame sync bytes.
#define COM_FS_MSB 0xa5
#define COM_FS_LSB 0xb8

// Frame header bytes.
#define COM_FH_MSB 0x39
#define COM_FH_LSB 0xd0

// Comm link state machine.
#define COM_WAIT_CARRIER 0
#define COM_WAIT_RXDELAY 1
#define COM_SEARCH_FS 2
#define COM_WAIT_FRAME 3
#define COM_READ_DATA 4
#define COM_TX_PREPARE 5
#define COM_WAIT_TXDELAY 6
#define COM_TX_DATA 7
#define COM_TX_FINAL 8

// Message types.  0 - 7 are from mobile to base, 8-15 are from base to mobile.
#define COM_MESSAGE_CMDACK 0
#define COM_MESSAGE_TELEMETRY 1
#define COM_MESSAGE_DUMPBUFER 2

#define COM_MESSAGE_CMD 8

// First byte used to define command and control messages.
#define COM_COMMAND_RESET 0x00
#define COM_COMMAND_TLMRATE 0x01
#define COM_COMMAND_APRS 0x02
#define COM_COMMAND_CLRCOMMCNT 0x03
#define COM_COMMAND_SETORIGIN 0x04
#define COM_COMMAND_PING 0x05
#define COM_COMMAND_LOG_DUMP 0x06
#define COM_COMMAND_LOG_CLEAR 0x07
#define COM_COMMAND_ZERO_INSTRUMENTS 0x08
#define COM_COMMAND_TEST_A 0x09
#define COM_COMMAND_TEST_B 0x0a


// Buffer that includes 32 blocks of data and a 1 byte message type.
#define COM_BUFFER_SIZE 577

// Time to wait in mS * 2 before starting the frame sync search.
#define COM_RXDELAY_TIME 5

// State machine used to determine processing in ISR.
uint8 comState;

// State variable to keep track of when carrier detect changes.
uint8 comLastCarrierDetect;

// Timer used to determine when to receive or transmit data after the transmitter is keyed or the carrier is detected.
uint8 comTxRxDelay;

// Number of blocks remaining to receive or transmit.
uint8 comBlockCount;

// Number of 18 byte blocks in the transmit buffer.
uint8 comTxBlockCount;

// Receive signal quality estimate and accumulator.
uint8 comSQE;
uint16 comSQEAccum;

// Structure that has stats on communcations.
COM_STATS comStats;

// Flag to indicate if any of the data blocks had CRC errors during receive.
bool comRxCRCFlag, comRxTempCRCFlag;

// Message time stamp.
uint32 comRxTimeStamp, comRxTempTimeStamp;

// Index to the temporary buffer.
uint16 comIndex;

// Flag to indicate if the receive buffer is in use.
bool comRxBusyFlag;

// Flag to indicate data is ready to be transmitted.
bool comTxReadyFlag;

// Defines the number of blocks in each message type.  The message type is determined by the lower nibble of the second frame header byte.
const uint8 comFrameBlockCount[] = { 1, 3, 32, 255,  255, 255, 255, 255,  1, 255, 255, 255,  255, 255, 255, 255 };

// Defines the S/N ratio for a given SQE value.
const uint8 comSQEtoSNRTable[] = { 26, 39, 55, 75, 97, 115, 135, 154, 171, 190, 255 };

// Transmit, receive, and temporary buffers.
uint8 comTempBuffer[COM_BUFFER_SIZE];
uint8 comRxBuffer[COM_BUFFER_SIZE];
uint8 comTxBuffer[COM_BUFFER_SIZE];

// Common message packets.
const uint8 comPingMessage[] = { COM_COMMAND_PING };

/**
 *    Send an ack message in response to <b>message</b>.  The ack includes
 *    a transmit packet count and SQE of the message.
 *
 *    @param message pointer to message to ack
 *    @param length length of original message
 */
void comAckPacket(uint8 *message, uint16 length)
{
    uint8 i, buffer[18];

    // Return the a packet counter and SQE of the packet to acknowledge.
    buffer[0] = comStats.txPacketCount & 0xff;
    buffer[1] = comSQE;
    
    // Assemble the message and zero pad.
    for (i = 0; i < length && i < 16; ++i)
        buffer[i + 2] = message[i];
        
    while (i < 16) {
        buffer[i + 2] = 0;
        ++i;
    } // END while

    comTxPacket(COM_MESSAGE_CMDACK, buffer, 18);
}

/**
 *   Clear the com link statistical database.
 */
void comClearCounters()
{
    comStats.badDataCRCCount = 0;
    comStats.badHeaderCRCCount = 0;
    comStats.bufferOverflow = 0;
    comStats.carrierDetectCount = 0;
    comStats.faultCount = 0;
    comStats.invalidHeaderCount = 0;
    comStats.rxPacketCount = 0;
    comStats.txPacketCount = 0;
}

/**
 *   Clear the receive buffer ready flag.  This flag must be cleared after a message is processed in
 *   order to get a new message.  If the flag is not cleared in time, the <b>bufferOverflow</b> statistic
 *   value is incremented.
 */
void comClearRxBuffer()
{
    comRxBusyFlag = FALSE;
}

/**
 *   Process the CMX909 IRQ requests.
 */
#ifdef WIN32
void comExternalISR()
#else 
interrupt void comExternalISR()
#endif
{
    uint8 startTime;
    uint16 i;
    uint8 status, frameHeader1, frameHeader2;
    
    // Get the status to determine the interrupt state.
    status = RdPortE (COM_CMX909_STATUS);

    switch (comState) {
        case COM_SEARCH_FS:
            // Check the DIBOVF and BFREE bits.
            if ((status & 0x50) != 0x40) {
                comState = COM_WAIT_CARRIER;
                ++comStats.faultCount;
                return;
            }

            // Wait 12-bit times.            
            startTime = RdPortI (TBCLR);
            while (((RdPortI(TBCLR) - startTime) & 0xff) < 80);

            // Search for the frame header.
            WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_SFH);
            comState = COM_WAIT_FRAME;
            break;

        case COM_WAIT_FRAME:
            // We'll time stamp when we get the frame header.
            comRxTempTimeStamp = MS_TIMER;

            // Verify the frame was receieved from the base station without any CRC errors.
            if ((status & 0x5a) != 0x40) {
                comState = COM_WAIT_CARRIER;
                ++comStats.badHeaderCRCCount;
                return;
            } // END if

            // Turn on the LED to indicate we have a packet header with a good CRC.
            sysRxLEDOn();

            // Verify the frame is valid.
            frameHeader1 = RdPortE (COM_CMX909_DATA);
            frameHeader2 = RdPortE (COM_CMX909_DATA);

            if (frameHeader1 != COM_FH_MSB || ((frameHeader2 & 0xf0) != COM_FH_LSB)) {
                comState = COM_WAIT_CARRIER;
                ++comStats.invalidHeaderCount;
                return;
            } // END if

            // Determine the number of blocks to receive based on the lower nibble of the frame header.
            comBlockCount = comFrameBlockCount[frameHeader2 & 0x0f];

            // If the block count is 255, then we have an invalid messsage type in the frame header.
            if (comBlockCount == 255) {
                comState = COM_WAIT_CARRIER;
                ++comStats.invalidHeaderCount;
                return;
            }

            // Start the read process.
            WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RDB);

            // Place the message type in the buffer and get ready to read the data.
            comTempBuffer[0] = frameHeader2 & 0x0f;
            comIndex = 1;
            comRxTempCRCFlag = FALSE;
            comState = COM_READ_DATA;
            comSQEAccum = 0;
            break;

        case COM_READ_DATA:
            // Check the DIBOVF and BFREE bits.
            if ((status & 0x50) != 0x40) {
                comState = COM_WAIT_CARRIER;
                ++comStats.faultCount;
                return;
            }

            // If we have a CRC error in this data block, then put zero values in the data buffer.
            if ((status & 0x08) != 0x00) {
                for (i = 0; i < 18; ++i)
                    comTempBuffer[comIndex++] = 0;

                comRxTempCRCFlag = TRUE;
            } else {
                // Read the 18 data bytes.
                for (i = 0; i < 18; ++i)
                    comTempBuffer[comIndex++] = RdPortE(COM_CMX909_DATA);

                // Add the SQE to the accumulator.
                comSQEAccum += RdPortE(COM_CMX909_DQ);
            } // END if-else

            // If we have all the blocks, save the data.
            if (--comBlockCount == 0) {
                // If busy flag hasn't been cleared, we have no where to put the message.
                if (comRxBusyFlag)
                    ++comStats.bufferOverflow;
                else {
                    // Copy the temporary buffer to the receive buffer.
                    for (i = 0; i < comIndex; ++i)
                        comRxBuffer[i] = comTempBuffer[i];

                    // Just in case the sender doesn't NULL terminate a string, we will.
                    comRxBuffer[i] = 0;

                    // Keep track of how many packets we have received.
                    ++comStats.rxPacketCount;

                    // Average the SQE value over the number of packets in the message.
                    comSQE = comSQEAccum / comFrameBlockCount[comTempBuffer[0]];

                    // Set the CRC flag.
                    comRxCRCFlag = comRxTempCRCFlag;

                    // Save the time stamp.
                    comRxTimeStamp = comRxTempTimeStamp;

                    // Set the flag to indicate we have a new message.
                    comRxBusyFlag = TRUE;
                } // END if-else comRxBusyFlag

                comState = COM_WAIT_CARRIER;

                return;
            }

            // Get the next data block.
            WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RDB);

            break;

        case COM_TX_DATA:
            if (comBlockCount-- == 0) {
                comState = COM_TX_FINAL;
                WrPortE (COM_CMX909_DATA, NULL, 0x33);
                WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_TSB);
            }

            for (i = 0; i < 18; ++i)
                WrPortE (COM_CMX909_DATA, NULL, comTxBuffer[comIndex++]);

            WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_TDB);
            break;

        case COM_TX_FINAL:
            comTxReadyFlag = FALSE;
            sysPTTOff();
            sysTxLEDOff();
            comState = COM_WAIT_CARRIER;

            // Keep track of the packets we send.
            ++comStats.txPacketCount;

            // Reset after we are done.
            WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET);

            // Enable the IRQ output, invert transmit/receive bits, and enable scrambler.
            WrPortE (COM_CMX909_MODE, NULL, 0xd0);
            break;

        // If we get an interrupt and aren't in a known state, just reset to receive mode.
        default:
            ++comStats.faultCount;
            comState = COM_WAIT_CARRIER;
            break;
    } // END switch

}

/**
 *    Get a pointer to the communications statistics block.
 *
 *    @return pointer to stats block
 */
COM_STATS *comGetStats()
{
    return &comStats;
}

/**
 *    Get a pointer to the receive buffer of the last message.
 *
 *    @return receive buffer
 */
uint8 *comGetRxBuffer()
{
    return comRxBuffer;
}

/**
 *   Get the signal quality estimate of the last message that was received.
 *
 *   @return Signal Quality Estimate
 */
uint8 comGetSQE()
{
    return comSQE;
}

/** 
 *   Setup the primary communications channel.
 */
void comInit()
{
    // Set class variables.
    comLastCarrierDetect = 0;
    comState = COM_WAIT_CARRIER;
    comRxBusyFlag = FALSE;
    comSQE = 0;
    comRxTimeStamp = 0;
    comRxTempTimeStamp = 0;

    // Clear the communications counters.
    comClearCounters();

    // Reset the CMX909
    WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET);

    // Configure clock divider for 512 (7200 baud), peak average, and narrow bandwidth receive.
    WrPortE (COM_CMX909_CONTROL, NULL, 0x65);

    // Enable the IRQ output, invert transmit/receive bits, and enable scrambler.
    WrPortE (COM_CMX909_MODE, NULL, 0xd0);
}

/**
 *   Determine if a CRC error occured in the last data packet.
 *
 *   @return true if CRC error occured; otherwise false
 */
uint8 comIsCRCBad()
{
    return comRxCRCFlag;
}

/**
 *   Determine if communcations buffer contains a new message.  After the message is processed,
 *   the method <b>comClearBuffer</b> should be called.
 * 
 *   @return true if buffer has a new message; otherwise false
 */
uint8 comIsRxReady()
{
    return comRxBusyFlag;
}

/**
 *   Convert the SQE value retrun by the CMX909 into an S/N estimate.  The CMX909 determine
 *   an S/N value from 3 to 12.
 *
 *   @param sqe value from CMX909 during receive
 *
 *   @return S/N estimate
 */
uint8 comSQEtoSNR(uint8 sqe)
{
    uint8 i, snr;

    snr = 0;

    // Go down the table until we find the range we are located in.
    for (i = 0; i < sizeof(comSQEtoSNRTable) / sizeof(uint8); ++i) {
        if (sqe < comSQEtoSNRTable[i])
            return snr;

        if (snr == 0)
            snr = 3;
        else
            ++snr;
    }

    // If we go through the whole table, then the SNR is better than 12dB.
    return 12;
}

/**
 *   This method is called every 2mS and is used to process com radio functions.
 */
void comTimer()
{
    uint8 carrierDetect;

    if (!comTxReadyFlag)
        // Detect a transition in the carrier detect signal.
        if ((carrierDetect = sysIsCarrierDetect()) != comLastCarrierDetect) {
            comLastCarrierDetect = carrierDetect;

            // Reset the CMX909 for any carrier detect transition.
            WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET);

            // Process the carrier detected going active.
            if (carrierDetect) {
                comState = COM_WAIT_RXDELAY;
                comTxRxDelay = COM_RXDELAY_TIME;
                ++comStats.carrierDetectCount;
            } else {
                comState = COM_WAIT_CARRIER;
                sysRxLEDOff();
            }            

            // Once we've detect a carrier change, we'll just exit.
            return;
        } // END if carrierDetect

    switch (comState) {
        case COM_WAIT_CARRIER:
            // If we are waiting for the carrier to go active, then we can transmit.
            if (comTxReadyFlag) {
                // Set the state machine to count down until we are ready to transmit data.
                comState = COM_WAIT_TXDELAY;

                // Key the transmitter and turn on the TX LED.
                sysPTTOn();
                sysTxLEDOn();

                // Reset the CMX909
                WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET);

                // Enable the IRQ output, transmit, and enable scrambler.
                WrPortE (COM_CMX909_MODE, NULL, 0xb0);

                // Write the frame header.
                WrPortE (COM_CMX909_DATA, NULL, COM_BASE_BITSYNC);
                WrPortE (COM_CMX909_DATA, NULL, COM_BASE_BITSYNC);
                WrPortE (COM_CMX909_DATA, NULL, COM_FS_MSB);
                WrPortE (COM_CMX909_DATA, NULL, COM_FS_LSB);
                WrPortE (COM_CMX909_DATA, NULL, COM_FH_MSB);
                WrPortE (COM_CMX909_DATA, NULL, COM_FH_LSB | (comTxBuffer[0] & 0x0f));

                comBlockCount = comTxBlockCount;
            } // END if
            break;

        case COM_WAIT_RXDELAY:
            if (--comTxRxDelay == 0) {
                comState = COM_SEARCH_FS;

                // Search for the frame sync bytes.
                WrPortE (COM_CMX909_DATA, NULL, COM_FS_MSB);
                WrPortE (COM_CMX909_DATA, NULL, COM_FS_LSB);
                WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_LFSB | COM_AQBC_MASK | COM_AQLEV_MASK);
            } // END if
            break;

        case COM_WAIT_TXDELAY:
            if (--comTxRxDelay == 0) {
                // Set the state machine to transmit data starting at the first data byte.
                comState = COM_TX_DATA;
                comIndex = 1;

                // Send the header data.
                WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_T7H);
            } // END if
            break;
    } // END switch
}

/**
 *   Send a data packet over the primary channel.  The message type is a 4-bit value
 *   that is sent as part of the frame header.  The type is used to determine the
 *   number of data blocks that will be received.
 *
 *   @param 4-bit message type
 *   @param data pointer to data block
 *   @param length of message in bytes
 *
 *   @return true if messaged queue for transmit; otherwise false
 */
uint8 comTxPacket (uint8 messageType, uint8 *data, uint16 length)
{
    uint16 i;

    // If we are transmitting a packet already, then we can't send another.
    if (comTxReadyFlag)
        return FALSE;

    // This is a hard coded length based on the CMX909 com chip.
    if (length > 575)
        return FALSE;

    // Carrier only delay period.
    comTxRxDelay = configGetComTxDelay() >> 1;

    // Copy the message to the transmit buffer.
    comTxBuffer[0] = messageType;

    for (i = 0; i < length; ++i)
        comTxBuffer[i + 1] = data[i];

    comTxBlockCount = ((length - 1) / 18) + 1;

    // Pad any blocks that are incomplete with 0 data.
    for (i = length; i < 18 * (uint16) comTxBlockCount; ++i)
        comTxBuffer[i + 1] = 0;

    // Set this flag to tell the background timer to transmit the packet when able.
    comTxReadyFlag = TRUE;

    // The packet is buffered, so let them know.
    return TRUE;
}

uint8 comRetryTxPacket()
{
    // If we are transmitting a packet already, then we can't send another.
    if (comTxReadyFlag)
        return FALSE;

    // Carrier only delay period.
    comTxRxDelay = configGetComTxDelay() >> 1;

    // Set this flag to tell the background timer to transmit the packet when able.
    comTxReadyFlag = TRUE;

    // The packet is buffered, so let them know.
    return TRUE;        
}

/**
 *   Class to handle configuration items.
 */
typedef struct {
    uint16 crc;
    uint8 tncTxDelay, comTxDelay;
    uint8 callSign[7], destCallSign[7], relay1CallSign[7], relay2CallSign[7];
    uint8 callSignSSID, relay1SSID, relay2SSID;
    double gpsStartLat, gpsStartLong;
    uint16 bootCount, flightTime;
} CONFIG_STRUCT;

#ifdef WIN32
CONFIG_STRUCT config;
#else
protected CONFIG_STRUCT config;
#endif

/**
 *    Set the default configuration parameters.
 */
void configDefault()
{
    // Station ID, relay path, and destination call sign and SSID.
    strcpy (config.callSign, "KD7LMO");
    config.callSignSSID = 11;
    
    strcpy (config.relay1CallSign, "GATE  ");
    config.relay1SSID = 0;
    
    strcpy (config.relay2CallSign, "WIDE3 ");
    config.relay2SSID = 3;
    
    strcpy (config.destCallSign, "APRS  ");

    // Number of TNC flag bytes sent before data stream starts.  1 byte = 6.6mS
    config.tncTxDelay = 45;

    // Number of mS before data is sent over com link.
    config.comTxDelay = 40;

    // Default starting location.
    config.gpsStartLat = 33.618766;
    config.gpsStartLong = -111.733483;

    // Count the number of system boots.
    config.bootCount = 0;

    // Flight operation time.
    config.flightTime = 0;
}

/**
 *    Calculate and set the configuration parameter block CRC.
 */
void configCalcCRC()
{
    config.crc = sysCRC16 ((uint8 *) &config + 2, sizeof(CONFIG_STRUCT) - 2);
}

uint8 *configGetCallSign()
{
    return config.callSign;
}

uint8 *configGetDestCallSign()
{
    return config.destCallSign;
}

uint16 configGetFlightTime()
{
    return config.flightTime;
}

uint8 *configGetRelay1CallSign()
{
    return config.relay1CallSign;
}

uint8 *configGetRelay2CallSign()
{
    return config.relay2CallSign;
}

uint8 configGetRelay1SSID()
{
    return config.relay1SSID;
}

uint8 configGetRelay2SSID()
{
    return config.relay2SSID;
}

uint8 configGetSSID()
{
    return config.callSignSSID;
}

uint8 configGetTNCTxDelay()
{
    return config.tncTxDelay;
}

uint8 configGetComTxDelay()
{
    return config.comTxDelay;
}

/**
 *    Initialize configuration subsystem.
 */
uint8 configInit()
{
// TODO Remove me
configDefault();

    // If the configuration CRC is not valid, then set default vaules.
    if (config.crc != sysCRC16((uint8 *) &config + 2, sizeof(CONFIG_STRUCT) - 2)) {
        configDefault();
        configCalcCRC();
        return FALSE;
    }

    ++config.bootCount;
    configCalcCRC();

    return TRUE;
}

/**
 *   Class to handle general system functions.
 */
// Define the I/O pins.
#define SYS_TXLED 0
#define SYS_RXLED 1
#define SYS_PTT 1
#define SYS_CARRIERDETECT 1

/**
 *    Wait <b>delayMS</b> before returning.
 *
 *    @param delayMS delay time in milliseconds
 */
void sysDelay(uint32 delayMS)
{
    uint32 timerTick;
    
    timerTick = MS_TIMER;
    while (MS_TIMER - timerTick < delayMS);
}

/**
 *   Initialize the internal system controls.
 */
void sysInit()
{
    // Run as fast as we can.
    clockDoublerOn();

    // Configure serial port for command and control.
    serDopen (38400);

    // ******  Configure PORT-A ******
    // Make it all outputs in the off state.
    WrPortI(SPCR, NULL, 0x84);
    WrPortI(PADR, &PADRShadow, 0x00);
    

    // ******  Configure PORT-D ****** 
    // Tx and Rx LEDs on
    WrPortI(PDDR, &PDDRShadow, 0x00);

    // Port D no alt TXA/TXB
    WrPortI(PDFR, NULL, 0x00);

    // Port D all outputs.
    WrPortI(PDDDR, NULL, 0xff);

    // PORT D push-pull outputs.
    WrPortI(PDDCR, NULL, 0x00);


    // ******  Configure PORT-E for COM radio and general I/O ******
    // Configure PE7 as CS for external I/O.
    WrPortI (IB7CR, NULL, 0x48);

    // Set PE2, PE4, PE5, and PE7 as outputs.
    WrPortI (PEDDR, NULL, 0xb4);

    // Configure PE7 as I/O strobe line.
    WrPortI (PEFR, NULL, 0x80);

   
    // ****** Configure Timer A for heartbeat interrupt ****** 
    // Set the timer A ISR.
    SetVectIntern (0x0a, sysTimerISR);

    // Set Timer A1 value that feeds timer A4 and B.
    // rate = (timerValue + 1) * clockPeriod
    WrPortI (TAT1R, &TAT1RShadow, 191);

    // Set timer A4 value that provides 2mS heartbeat interrupt.
    WrPortI (TAT4R, &TAT4RShadow, 96);

    // ****** Configure Timer B for time delay ****** 
    // Timer B clocked by timer A1.
    WrPortI (TBCR, NULL, 0x04);

}

void sysEnableInterrupt()
{
    // Set external ISR to priority 1.
    SetVectExtern2000 (0x01, comExternalISR);

    // External interrupt priority 1 on INT0A, INT1A falling edge.
    WrPortI (I0CR, NULL, 0x05);
    WrPortI (I1CR, NULL, 0x05);

    // Clear the interrupt pending flag.
    RdPortI (TACSR);

    // Timer A4 clocked by timer A1, all other timers clocked by pclk/2, interrupt priority 2.
    WrPortI (TACR, &TACRShadow, 0x12);
    
    // Enable timer A1 and timer A4 interrupts.
    WrPortI (TACSR, &TACSRShadow, 0x11);
}

/**
 *   Process the timer A4 interrupt that occurs every 2mS.  This method hits the watch dog timer, 
 *   controls the heartbeat LED, and processes the background communication tasks.
 */
#ifdef WIN32
void sysTimerISR()
#else
interrupt void sysTimerISR()
#endif
{
    // Clear the timer A4 interrupt.
    RdPortI (TACSR);

    // Call the com radio processor.
    comTimer();
}

/**
 *   Turn on the transmit LED.
 */ 
void sysTxLEDOn()
{
    BitWrPortI (PDDR, &PDDRShadow, 0, SYS_TXLED);
}

/**
 *   Turn off the transmit LED.
 */
void sysTxLEDOff()
{
    BitWrPortI (PDDR, &PDDRShadow, 1, SYS_TXLED);
}

/**
 *   Turn on the transmit LED.
 */ 
void sysRxLEDOn()
{
    BitWrPortI (PDDR, &PDDRShadow, 0, SYS_RXLED);
}

/**
 *   Turn off the transmit LED.
 */
void sysRxLEDOff()
{
    BitWrPortI (PDDR, &PDDRShadow, 1, SYS_RXLED);
}

/**
 *    Turn on the transmitter.
 */
void sysPTTOn()
{
    BitWrPortI (PADR, &PADRShadow, 1, SYS_PTT);
}   

/**
 *    Turn off the transmitter.
 */
void sysPTTOff()
{
    BitWrPortI (PADR, &PADRShadow, 0, SYS_PTT);
}


/**
 *   Calculate NMEA-0183 message checksum of <b>buffer</b> that is <b>length</b> bytes long.
 *
 *   @param buffer Pointer to data buffer.
 *   @param length number of bytes in buffer.
 *
 *   @return checksum of buffer
 */
uint8 sysNMEAChecksum (uint8 *buffer, uint16 length)
{
    uint16 i;
    uint8 checksum;

    checksum = 0;
    
    for (i = 0; i < length; ++i)
        checksum ^= buffer[i];

    return checksum;
}

/**
 *    Determine if carrier is present at radio.
 *
 *    @return true if carrier is present; otherwise false
 */
bool sysIsCarrierDetect()
{
    if (BitRdPortI (PBDR, SYS_CARRIERDETECT) == 0)
        return TRUE;

    return FALSE;
}

void sysSelfTest()
{
    uint32 timerTick;

    // Wait for the MAX-232 charge pumps to start and display the LEDs for 1.5 seconds.
    timerTick = MS_TIMER;
    while (MS_TIMER - timerTick < 1500);

    sysTxLEDOff();
    sysRxLEDOff();
}

/**
 *    Calculate the CRC-16 CCITT of <b>buffer</b> that is <b>length</b> bytes long.
 *
 *    @param buffer Pointer to data buffer.
 *    @param length number of bytes in data buffer
 *
 *    @return CRC-16 of buffer[0 .. length]
 */
uint16 sysCRC16 (uint8 *buffer, uint16 length)
{
    uint16 i, bit, crc, value;

    crc = 0xffff;

    for (i = 0; i < length; ++i) {
        value = buffer[i];

        for (bit = 0; bit < 8; ++bit) {
            crc ^= (value & 0x01);
            crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 );
            value = value >> 1;
        } // END for
    } // END for

    return crc ^ 0xffff;
}

/**
 *    Convert the ASCII hex character <b>digit</b> to a binary value.
 *
 *    @param digit ASCII hex character
 *
 *    @return binary value of digit; 0 if character invalid
 */
uint8 sysParseHexDigit(uint8 digit)
{
    if (digit >= '0' && digit <= '9')
        return digit - '0';

    if (digit >= 'a' && digit <= 'f')
        return digit - 'a' + 10;

    if (digit >= 'A' && digit <= 'F')
        return digit - 'A' + 10;

    return 0;
}

/**
 *   This class runs the ground station.
 */
 
// Number of milliseconds between the ping packet.
#define FLIGHT_PING_TIME 15000

// Constants for back ground task state machine.
#define GROUND_WAIT 0
#define GROUND_REQ_LOG_SIZE 1
#define GROUND_WAIT_LOG_SIZE 2
#define GROUND_START_DUMP_DATA 3
#define GROUND_REQ_DUMP_DATA 4
#define GROUND_WAIT_DUMP_DATA 5
#define GROUND_RX_DUMP_DATA 6

// The number of bytes in each block of log data we dump.
#define GROUND_DUMP_SIZE 575

// Number of times to resend a packet.
#define GROUND_RETRY_COUNT 5

// Number of milliseconds to wait for a short packet ack
#define GROUND_WAIT_SHORT 800
#define GROUND_WAIT_LONG 1400

// Timer to keep track of when to send the ping packet.
uint32 groundPingTimer;

// Timer to keep track of when the last packet was sent.  (Used for ACK).
uint32 groundPacketTimer;

// Counter of number of times a packet was sent.
uint8 groundRetryCounter;

// Back ground task state machine.
uint8 groundTask;

// Number of bytes in the log.
uint32 groundDumpSize;

// The current block number that is being processed.
uint16 groundDumpBlock;

// The total number of GROUND_DUMP_SIZE blocks in the log.
uint16 groundTotalBlocks;

/**
 *    Get everything read for flight.
 */
void groundInit()
{
    groundTask = GROUND_WAIT;
    groundPacketTimer = 0;
    groundRetryCounter = 0;
    groundTotalBlocks = 0;
}

/**
 *   Process the command and control commands from the handheld computer.
 */
void groundProcessCandC()
{
    uint8 *cncCommand, command[16];

    cncCommand = cncGetRxBuffer();

    // Process based on the message type.
    if (cncCommand[0] == CNC_MESSAGE_CMD)
        switch (cncCommand[1]) {
            // Set the telemetry rate in seconds where 0 is no telemetry.
            case CNC_COMMAND_TLMRATE:
                // Create and send the command.
                command[0] = COM_COMMAND_TLMRATE;
                command[1] = cncCommand[2];
                comTxPacket (COM_MESSAGE_CMD, command, 2);
                break;

            // Enable/disable the APRS packet report.
            case CNC_COMMAND_APRS:
                // Create and send the command.
                command[0] = COM_COMMAND_APRS;
                command[1] = cncCommand[2];
                comTxPacket (COM_MESSAGE_CMD, command, 2);
                break;

            // Set the flight origin.
            case CNC_COMMAND_SETORIGIN:
                // Create and send the command.
                command[0] = COM_COMMAND_SETORIGIN;
                comTxPacket (COM_MESSAGE_CMD, command, 1);
                break;

            // Log commands, dump and clear.
            case CNC_COMMAND_LOG:
                switch (cncCommand[2]) {
                    // Send a packet to clear the log.
                    case CNC_LOG_CLEAR:
                        command[0] = COM_COMMAND_LOG_CLEAR;
                        comTxPacket (COM_MESSAGE_CMD, command, 1);
                        break;

                    // Set the background state machine to dump the log memory.
                    case CNC_LOG_DUMP:
                        groundTask = GROUND_REQ_LOG_SIZE;
                        break;
                } // END switch;
                break;

            case CNC_COMMAND_ZERO_INSTRUMENTS:
                // Create and send the command.
                command[0] = COM_COMMAND_ZERO_INSTRUMENTS;
                comTxPacket (COM_MESSAGE_CMD, command, 1);
                break;

            case CNC_COMMAND_TEST_A:
                // Create and send the command.
                command[0] = COM_COMMAND_TEST_A;
                comTxPacket (COM_MESSAGE_CMD, command, 1);          
                break;

            case CNC_COMMAND_TEST_B:
                // Create and send the command.
                command[0] = COM_COMMAND_TEST_B;
                comTxPacket (COM_MESSAGE_CMD, command, 1);          
                break;
        } // END switch

    // Clear the buffer so we can get the next message.
    cncClearRxBuffer();
}

/**
 *   Process the background tasks.
 */
void groundProcessBackGround()
{
    uint8 command[16];
    uint8 *comCommand;

    // Get a pointer to the com link receive buffer.
    comCommand = comGetRxBuffer();  

    // A state machine to process back ground tasks.
    switch (groundTask) {
        // Don't do anything while we wait for a task to start.
        case GROUND_WAIT:
            break;

        // A log dump request has been made by the CNC computer.            
        case GROUND_REQ_LOG_SIZE:
            // Set the size and offset to zero to retrieve the log size.
            command[0] = COM_COMMAND_LOG_DUMP;                  
            *((uint32 *) (command + 1)) = 0;
            *((uint16 *) (command + 5)) = 0;
            comTxPacket(COM_MESSAGE_CMD, command, 7);

            // Set the state machine and timers to wait for the log size.
            groundTask = GROUND_WAIT_LOG_SIZE;
            groundPacketTimer = MS_TIMER;
            groundRetryCounter = 0;
            break;

        // Wait for the moble unit to return the log size.          
        case GROUND_WAIT_LOG_SIZE:
            if (MS_TIMER - groundPacketTimer > GROUND_WAIT_SHORT) {
                // Time out if we don't get an answer to the log size query.
                if (groundRetryCounter++ == GROUND_RETRY_COUNT) {
                    groundTask = GROUND_WAIT;

                    command[0] = CNC_STATUS_TIMEOUT;
                    cncTxPacket (CNC_MESSAGE_LOG_STATUS, command, 1);
                    return;
                } // END if

                // Request the packet again.
                comRetryTxPacket();
                groundPacketTimer = MS_TIMER;
                            
            } // END if
            break;

        // Process the requst for the first block of data.
        case GROUND_START_DUMP_DATA:
            // We get the log size from the command ACK packet.
            groundDumpSize = *((uint32 *) (comCommand + 4));

            // Dump one block if the log size is 0.
            if (groundDumpSize == 0)
                groundTotalBlocks = 1;
            else
                groundTotalBlocks = (uint16) (((groundDumpSize - 1) / GROUND_DUMP_SIZE) + 1);
                
            groundDumpBlock = 0;
            
        case GROUND_REQ_DUMP_DATA:
            // Send a log dump command.
            command[0] = COM_COMMAND_LOG_DUMP;
            *((uint32 *) (command + 1)) = groundDumpBlock * GROUND_DUMP_SIZE;
            *((uint16 *) (command + 5)) = GROUND_DUMP_SIZE;
            comTxPacket (COM_MESSAGE_CMD, command, 7);

            // Set the state machine to wait for the data packet, the time out timer, and the retry counter.
            groundTask = GROUND_WAIT_DUMP_DATA;
            groundPacketTimer = MS_TIMER;
            groundRetryCounter = 0;
            break;  

        case GROUND_WAIT_DUMP_DATA:
            if (MS_TIMER - groundPacketTimer > GROUND_WAIT_LONG) {
                // Time out if we don't get an answer to the log request.
                if (groundRetryCounter++ == GROUND_RETRY_COUNT) {
                    groundTask = GROUND_WAIT;

                    command[0] = CNC_STATUS_TIMEOUT;
                    cncTxPacket (CNC_MESSAGE_LOG_STATUS, command, 1);
                    return;
                } // END if

                // Request the packet again.
                comRetryTxPacket();

                groundPacketTimer = MS_TIMER;
                            
            } // END if
            break;

        case GROUND_RX_DUMP_DATA:
            cncTxPacket (CNC_MESSAGE_CMDACK, comCommand + 1, 575);

            if (--groundTotalBlocks != 0) {
                ++groundDumpBlock;
                groundTask = GROUND_REQ_DUMP_DATA;
                sysDelay (25);
            } else
                groundTask = GROUND_WAIT;
            break;

    } // END switch
}

/**
 *   Process the command received from the high speed comm link.
 */
void groundProcessCom()
{
    uint8 *comCommand, cncCommand[8];

    comCommand = comGetRxBuffer();

    switch (comCommand[0]) {
        case COM_MESSAGE_TELEMETRY:
            cncTxPacket (CNC_MESSAGE_TELEMETRY, comCommand + 1, sizeof(TLM_STATUS));
            break;

        case COM_MESSAGE_CMDACK:
            if (comCommand[3] == COM_COMMAND_LOG_DUMP && groundTask == GROUND_WAIT_LOG_SIZE)
                groundTask = GROUND_START_DUMP_DATA;

            if (comCommand[3] == COM_COMMAND_TLMRATE || comCommand[3] == COM_COMMAND_APRS) {
                cncCommand[0] = comCommand[4];
                cncCommand[1] = comCommand[5];
                cncTxPacket (CNC_MESSAGE_OPS_STATE, cncCommand, 2);
            } // END if
            break;

        case COM_MESSAGE_DUMPBUFER:
//          if (groundTask == GROUND_WAIT_DUMP_DATA)
                groundTask = GROUND_RX_DUMP_DATA;
            break;
        
    } // END switch

    // Clear the buffer so we can get the next message.
    comClearRxBuffer();
}

/**
 *   The main control loop that controls everything.
 */
void groundRun()
{
    // Offset the time so we send our first ping message a second after we start.
    groundPingTimer = MS_TIMER - (FLIGHT_PING_TIME - 1000);

    // The main control loop.
    while (1) {
        // Read and process data from the handheld terminal.
        cncReadData();

        if (cncIsRxReady())
            groundProcessCandC();

        // Process any waiting com link messages.
        if (comIsRxReady())
            groundProcessCom();

        // Process the back ground commands such as memory dump.
        groundProcessBackGround();

        // Send a periodic ping message to determine if the system is alive.
        if (MS_TIMER - groundPingTimer > FLIGHT_PING_TIME) {
//            comTxPacket (COM_COMMAND_PING, comPingMessage, sizeof(comPingMessage));
            groundPingTimer = MS_TIMER;
        }
    }
}

main()
{
    // Initialize the system and configuration classes.  These must be set for the other classes.
    sysInit();
    configInit();

    // Now initialize the rest of the classes.
    comInit();
    cncInit();
    groundInit();
    
    // Turn on the interrupts.  
    sysEnableInterrupt();

    // Make sure everything is alright.
    sysSelfTest();
    
    // Now execute the main routine
    groundRun();

    // We should never get to this point, but if we do reset.
    forceSoftReset();   
}