//
//  Name:         pc.c
//
//
//  Revision History - update sysGetVersion():
//
//   M. Gray       23 Jul 2001  V1.00  Initial release.
//
//   M. Gray       25 Oct 2001  V1.01  Added '>' symbol to preface APRS status messages,
//                                     added crossband repeater functions,
//                                     reduced size of serial I/O buffers,
//                                     changed GEOID height in GPGGA message to NULL field,
//                                     changed order of telemetry data,
//                                     corrected VSI (Vertical Speed Indicator),
//                                     corrected spurious interrupt fault with processor,
//                                     replaced GPS 2D/3D status with pdop/hdop value,
//                                     removed heat sink temp from camera control module,
//                                     fixed startup delay timer, and
//                                     upgraded analog input to 20-bit ADC.
//
//   M. Gray        01 May 2002  V1.02 Send GPGGA and GPRMC to video overlay on alternating seconds,
//                                     added hour and minutes to bus voltage recording,
//                                     command to turn ATV on and off,
//                                     extended cut down to 10 seconds, and
//                                     corrected analog static port 16/32 bit conversion problem.
//
//   M. Gray       12 June 2002  V1.03 Send hex NMEA checksum as upper case characters,
//                                     replaced \015\012 sequence with \n\r for clarity,
//                                     updated plain text status strings on external serial port,
//                                     added hysterisas to CTCSS tone decoder input,
//                                     reversed output level for '52' and '58' DTMF commands,
//                                     added TNC message feedback for DTMF commands, and
//                                     cleaned up source code and documentation.
//
//   M. Gray       30 June 2002  V1.04 Added BEGIN and END tags to binary memory dump,
//                                     updated sysBootMessage() to use telemetry methods,
//                                     added UTC time stamp to every LOG_TYPE_COORD record,
//                                     removed UTC time stamp from bus voltage,
//                                     added user abort to data download,
//                                     moved log temp/bus voltage before coord,
//                                     removed cursor control character '\014' from init string,
//                                     added '412' DTMF command to send $GPGGA message on demand,
//                                     record full 32-bit altitude value in native resolution,
//                                     added different size log blocks to work around memory map limitations,
//                                     expanded memory logging to 456KB out of 512KB, and
//                                     removed logging from gpsParseMessage() to log corruption during gpsInit().
//
//   M. Gray        5 Nov 2002  V1.05  Enabled configuration memory check after reboot (accidently disabled during debug),
//                                     changed packet NMEA packets from flight time to UTC time,
//                                     change packet APRS to report on secondary frequency using UTC time as trigger,
//                                     added CW ident to cross band frequency,
//                                     removed support for ISS transmit, and
//                                     added support for cross link modes.
//
//   M. Gray        22 Nov 2002 V1.06  Change temperature log rate to 15 seconds,
//                                     added run time error handler, and
//                                     added landing estimate module.
//
//   M. Gray         9 Feb 2003 V1.07  Added port map list,
//                                     added digital humidity/temp sensor,
//                                     changed SSID to 0 for prediction packet,
//                                     set prediction to APRS freq at 15 and 45 seconds,
//                                     and add command to factory reset GPS engine.
//
//   M. Gray         5 Apr 2003 V1.08  Added GATE,WIDE3-3 back to TNC messages,
//                                     fixed NMEA-0183 checksum calculation, and
//                                     removed heading for magnetic corection field in $GPRMC message.
//
//   M. Gray         1 Jul 2003 V1.09  Added internal/external digital temperature sensors,
//                                     upgraded to Rabbit 8.01 compiler, and
//                                     and added support for new pressure sensor.
//
//   M. Gray        18 Oct 2003 V1.10  Added support for short range transmitter to control remote playloads/cut down.
//
//   M. Gray        13 Nov 2003 V1.11  Added DTMF pass through time out,
//                                     change remote cut down command rate to 2 seconds,
//                                     updated landing estimate polynomial, and
//                                     added peak altitude display to cut down message.
//
//   M. Gray         5 Mar 2004 V1.12  Added support for wireless link to ATV transmitter.
//
//
//  COPYRIGHT (c) 2001-2003 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 and enable code storage in extended memory except for interrupt service methods.
#ifndef WIN32
#nodebug
#memmap xmem
#endif

// Define the size of the RS-232 buffers used for NMEA-0183 message and GPS.
#define CINBUFSIZE 255
#define COUTBUFSIZE 255

#define PORTD_BUFFER_SIZE 255
#define DINBUFSIZE PORTD_BUFFER_SIZE
#define DOUTBUFSIZE PORTD_BUFFER_SIZE

// 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 int8;
typedef long int32;

// We simulate the Rabbit enviroment for cross compiling in Linux or Visual Studio.
// The primary reason is because they offer a much better IDE or development suite.
#ifdef WIN32

#pragma pack (1)
#pragma warning (disable: 4057)
#pragma warning (disable: 4100)
#pragma warning (disable: 4244)
#pragma warning (disable: 4305)

typedef unsigned char uint8;
typedef unsigned int uint16;
typedef int int16;
typedef unsigned long uint32;

#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 TAT5R 32

#define SEC_TIMER 1
#define MS_TIMER 1000

#define PI 3.14159265358

uint16 PADRShadow, PDDRShadow, PEDRShadow, TAT1RShadow, TACSRShadow, TACRShadow, TAT4RShadow, TAT5RShadow, PDDDRShadow;
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 (uint8 value) { putchar(value); };
void serDputs (uint8 *str) { puts (str); };
void serDclose() {};
int serDwrFree() { return 0; };
uint8 serDgetc () { return 0; };
void clockDoublerOn() { };
void clockDoublerOff() { };
void SetVectExtern2000 (uint8 vector, void func()) {};
void SetVectIntern (uint8 vector, void func()) {};
uint8 VdGetFreeWd (uint8 timeOut) { return 0; };
void VdHitWd (uint8 value) {};
void root2xmem (uint32 src, void *dest, uint32 size) { };
void xmem2root (void *dest, uint32 src, uint32 size) { };
long xalloc (uint32 size) { return 0; };
void forceSoftReset() { };
void defineErrorHandler (void (*cbMethod)()) { };
#endif  // END of cross platform support.

// Hardware I/O port map
//
// Port A
//
// 7 ANALOG_CSADC
// 6 ANALOG_CLK
// 5
// 4 RADIO_TX_FREQ_CH1, RADIO_TX2
// 3 ANALOG_DOUT
// 2 RADIO_TX_FREQ_CH2
// 1 LOCAL_IO_PORT1
// 0 LOCAL_IO_PORT0, RADIO_TX1
//
//
// Port B
//
// 7
// 6
// 5 DTMF D3
// 4 DTMF D2
// 3 DTMF D1
// 2 DTMF D0
// 1 DTMF Status
// 0 ANALOG_DIN
//
//
// Port D
//
// 7 HUM_TEMP_SCK / TEMP_CLK
// 6 HUM_TEMP_DATA / TEMP_DATA
// 5 RADIO_DATA_RX2
// 4 RADIO_DATA_RX1
// 3 RADIO_RX_FREQ_CH1
// 2 TNC_M1
// 1 TNC_DATA
// 0 TNC_M0
//
//
// Port E
//
// 7 RADIO_CTCSS_DETECT
// 6 RADIO_CARRIER_DETECT
// 5 REMOTE_TX
// 4 REMOTE_DATA
// 3 RADIO_AUDIO2
// 2 RADIO_AUDIO1
// 1 TNC Input Interrupt
// 0 TNC Input Interrupt

// Public methods and data structures for each class.
int8 analogGetBusVoltage();
int16 analogGetExtTemp();
int16 analogGetIntTemp();
void analogInit();
uint32 analogRead(uint8 channel);
void analogUpdate();
uint16 analogGetStatic();

void camAutoPan();
void camInit();
void camParseMessage();
void camReadMessage();
void camResetTimer();
void camUpdateTick();

uint8 cmdProcess();

void configCalcCRC();
void configDefault();
uint8 configInit();

typedef struct {
    uint8 updateFlag, month, day, hours, minutes, seconds;
    uint16 year;
    int32 latitude, longitude, altitudeMeters, altitudeFeet;
    uint16 vSpeed, hSpeed, heading, dop, status;
    uint16 trackedSats, visibleSats;
    int32 lastAltitude;
    int16 vSpeedAccum;
} GPSPOSITION_STRUCT;

void gpsClearUpdate();
GPSPOSITION_STRUCT *gpsGetGPSData();
uint8 gpsInit();
uint8 gpsIsUpdate();
void gpsLogData();
void gpsParsePositionMessage();
void gpsReadMessage();
void gpsSendMessage(uint8 *, uint8);

void localIOInit();
void localIOStartPort1Timer();
void localIOUpdateTick();

// The record type identifiers.
#define LOG_TYPE_TIMESTAMP 0x10
#define LOG_TYPE_COORD 0x11
#define LOG_TYPE_TEMP_HUM 0x12
#define LOG_TYPE_BUSVOLTAGE 0x13
#define LOG_NEXT_BLOCK 0x14
#define LOG_TYPE_TEMP 0x15

void logBlockStart(uint8 recordID);
void logClear();
int32 logGetBlock1();
int32 logGetBlock2();
uint32 logGetBlockSize(int32 logBlock);
void logInit();
void logInt16 (int16 value);
void logInt32 (int32 value);
void logUint16 (uint16 value);
void logUint8 (uint8 value);

typedef struct {
    float lat, lon;
    int32 alt;
} COORD;

typedef struct {
    float dist, head, trackError;
} COURSE;

typedef struct {
    uint32 timeStamp;
    uint16 timeInterval;
    COURSE course;
    COORD coord;
} NAV_WINDDATA;

void navCourse (COORD *coord1, COORD *coord2, COURSE *course);
void navDistRadial (COORD *current, COORD *next, float d, float tc);
float navGetDist (COURSE *course);
float navGetHead (COURSE *course);
uint16 navGetWindDataCount();
void navInit();
void navLaunch();
void navRadToDeg (COORD *coord);
void navSetDegFCoord (float lat, float lon, COORD *coord);
void navSetDegICoord (int32 lat, int32 lon, COORD *coord);

// Port A bits used to select transmit frequency.
#define RADIO_TX_FREQ_CH1 4
#define RADIO_TX_FREQ_CH2 2

// Port D bit used to select receive frequency.
#define RADIO_RX_FREQ_CH1 3

// Mode flags used to select transmit frequency.
#define RADIO_TX_APRS 0
#define RADIO_TX_DOWNLINK 1
#define RADIO_TX_CROSSBAND_LOW 2
#define RADIO_TX_CROSSBAND_HIGH 3

void radioDTMFDecode();
void radioInit();
void radioInitTone (uint16 *tone);
void radioSetTxFreq(uint8 mode);
void radioUpdate();
void radioUpdateTone();

/// Remote command to display text at coordinates (x, y) in color c.
#define COMMAND_TITLE 0x31

/// Remote command to execute auto focus camera sequence.
#define COMMAND_FOCUS 0x32

/// Remote command to set the frequency.
#define COMMAND_SET_FREQ 0x33

/// Remote command to set the PA state.
#define COMMAND_SET_PA 0x34

/// Remote command to set the time.
#define COMMAND_SET_TIME 0x35

void remoteTransmit (uint8 command, char *message, uint8 length);

void sysBootMessage(uint8, uint8);
uint16 sysCRC16 (uint8 *, uint16);
void sysDelay(uint32 delayMS);
char *sysGetVersion();
void sysInit();
void sysInterruptEnable();
uint8 sysNMEAChecksum (uint8 *, uint16);

#ifdef WIN32
void sysRuntimeErrHandler();
#else
root void sysRuntimeErrHandler();
#endif

void sysTimerISR();

void tempInit();
int16 tempGetExtTemp();
int16 tempGetIntTemp();
uint8 tempWriteAndGetAck(uint8 data);
uint8 tempReadWithAck (uint8 ack);
void tempMasterStart();
void tempMasterStop();

#define TLM_LOCAL 0
#define TLM_CAM_COMPUTER 1
#define TLM_GPS 2
#define TLM_STATUS 3
#define TLM_NMEA 4
#define TLM_PACKET 5
#define TLM_VER 6
#define TLM_MESSAGE 7

#define TLM_TYPE_STATUS 0x00
#define TLM_TYPE_GPGGA 0x01
#define TLM_TYPE_GPRMC 0x02

void tlmCreateMessage(uint8 *message);
void tlmCreateReport (uint8 reportType, uint8 packetType);
void tlmGPGGAPacket();
void tlmGPRMCPacket();
void tlmGPSStatus();
void tlmInit();
void tlmReport();
void tlmPositionReport();
void tlmPacketReport();
void tlmSetEstimatePacket();
void tlmStatus();
void tlmUpdate();

void tncExternalISR();
void tncInit();
void tncSendPacket(uint8 *);
void tncTimer();

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

/*
 *   Class to handle the analog interface board.  Utilizes the LTC2428 ADC.
 */

// Define the pins used for I/O control.
#define ANALOG_CSADC 7
#define ANALOG_CLK 6
#define ANALOG_DOUT 3
#define ANALOG_DIN 0

// Define the analog channel port numbers.
#define ANALOG_STATIC 0
#define ANALOG_BUSVOLT 2
#define ANALOG_EXTTEMP 3
#define ANALOG_INTTEMP 6
#define ANALOG_PITOT 7

// Return value to indicate ADC convertion is not complete.
#define ANALOG_NOT_READY 0xffffffff

// Last value read from ADC channel.
uint32 analogData[8];

// Index to the last channel read.
uint8 analogChannel, analogLastChannel;

/**
 *   The analog bus voltage in 0.1 volt units.
 *
 *   @return analog bus voltage
 */
int8 analogGetBusVoltage()
{
    return (uint8) ((int32) (153l * (analogData[ANALOG_BUSVOLT] & 0xfffff) / 1048575l));
}

/**
 *   External temperature in 0.1 degrees F.
 *
 *   @return external temperature
 */
int16 analogGetExtTemp()
{
    return (int16) ((int32) (5000l * (analogData[ANALOG_EXTTEMP] & 0xfffff) / 1048575l)) - 1235;
}

/**
 *   Internal temperature in 0.1 degrees F.
 *
 *   @return internal temperature
 */
int16 analogGetIntTemp()
{
    return (int16) ((int32) (5000l * (analogData[ANALOG_INTTEMP] & 0xfffff) / 1048575l)) - 1235;
}


/**
 *   Preasure in 0.01 PSI.  Transfer function x = 5.0 VDC * 15 PSI / 0xfffff * 4.5 VDC
 *   0.83 = 0.25 * (15 / 4.5) = Offset * (FullScalePSI / FullScaleV)
 *
 *   @return preasure
 */
uint16 analogGetStatic()
{
    return (uint16) ((uint32) ((200l * (analogData[ANALOG_STATIC] & 0xfffff) / 125829l) - 83l));
}

/**
 *   Initialize the analog interface board.
 */
void analogInit()
{
    uint8 i;

    // Clear the analog buffers.
    for (i = 0; i < 8; ++i)
        analogData[i] = 0;

    // Set the ADC for external clocking.
    BitWrPortI (PADR, &PADRShadow, 1, ANALOG_CSADC);
    BitWrPortI (PADR, &PADRShadow, 0, ANALOG_CLK);
    BitWrPortI (PADR, &PADRShadow, 0, ANALOG_CSADC);

    // Start the conversion on the first channel.
    analogChannel = 0;
    analogLastChannel = 0;
    analogRead (0);
}

/**
 *  Read the ADC on the analog interface board.  This method returns the value
 *  of the last channel that was selected.
 *
 *  @param channel number of <i>next</i> channel to read
 *
 *  @return 4-bit status and 20-bit value of last channel programmed
 */
uint32 analogRead(uint8 channel)
{
    uint8 i, mask;
    uint32 data;

    // Only process when we get the /EOC.
    if (BitRdPortI(PBDR, ANALOG_DIN) == 1)
        return ANALOG_NOT_READY;

    // Shift 24-bits from the ADC.  These include status and data.
    data = 0;

    for (i = 0; i < 24; ++i) {
        BitWrPortI (PADR, &PADRShadow, 0, ANALOG_CLK);
        BitWrPortI (PADR, &PADRShadow, 1, ANALOG_CLK);

        data = data << 1;
        data |= BitRdPortI (PBDR, ANALOG_DIN);
    } // END for

    BitWrPortI (PADR, &PADRShadow, 1, ANALOG_CSADC);

    // Set the enable bit in the ADC control value.
    channel |= 0x08;

    // Select the next channel to process.
    for (mask = 0x08; mask != 0; mask = mask >> 1) {
        if ((channel & mask) != 0x00)
            BitWrPortI (PADR, &PADRShadow, 1, ANALOG_DOUT);
        else
            BitWrPortI (PADR, &PADRShadow, 0, ANALOG_DOUT);

        BitWrPortI (PADR, &PADRShadow, 0, ANALOG_CLK);
        BitWrPortI (PADR, &PADRShadow, 1, ANALOG_CLK);
    } // END for

    BitWrPortI (PADR, &PADRShadow, 0, ANALOG_CLK);
    BitWrPortI (PADR, &PADRShadow, 0, ANALOG_CSADC);

    return data;
}

/**
 *    Update and read the analog channel values.  This method is called periodically
 *    to check if the ADC conversion is complete.  If it is, it reads the ADC value
 *    and selects the next channel to process.
 */
void analogUpdate()
{
    uint32 data;

    // Just exit if the conversion is not complete.
    if ((data = analogRead(analogChannel)) == ANALOG_NOT_READY)
        return;

    // Save the data for the last channel we read.
    analogData[analogLastChannel] = data;

    // Save the new channel so we know where to put the results.
    analogLastChannel = analogChannel;

    // Sequentially select each channel in the system.
    switch (analogChannel) {
        case ANALOG_STATIC:
            analogChannel = ANALOG_BUSVOLT;
            break;

        case ANALOG_BUSVOLT:
            analogChannel = ANALOG_EXTTEMP;
            break;

        case ANALOG_EXTTEMP:
            analogChannel = ANALOG_INTTEMP;
            break;

        case ANALOG_INTTEMP:
            analogChannel = ANALOG_PITOT;
            break;

        case ANALOG_PITOT:
            analogChannel = ANALOG_STATIC;
                break;
    } // END switch
}

void atvOverlayUpdate()
{
    uint32 coord, coordMin, spd;
    uint8 dirFlag;
    char message[40], buffer[25];
    GPSPOSITION_STRUCT *gpsData;

    gpsData = gpsGetGPSData();

    switch (gpsData->seconds) {
        case 0:
            if (gpsData->hours < 7)
                buffer[0] =  gpsData->hours + 17;
            else
                buffer[0] = gpsData->hours - 7;

            buffer[1] = gpsData->minutes;

            remoteTransmit (COMMAND_SET_TIME, buffer, strlen(buffer));

            break;

        case 1:
        case 3:
            strcpy (buffer, "  http://kd7lmo.net");
            remoteTransmit (COMMAND_TITLE, buffer, strlen(buffer));
            break;

        case 14:
        case 16:
            // Latitude value.
            coord = gpsData->latitude;

            if (gpsData->latitude < 0) {
                coord = gpsData->latitude * -1;
                dirFlag = 0;
            } else {
                dirFlag = 1;
                coord = gpsData->latitude;
            }

            if (dirFlag == 1)
                strcpy (message, "N");
            else
                strcpy (message, "S");

            coordMin = (coord % 3600000) / 6;
            sprintf (buffer, "%02ld%02ld.%03ld ", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000) / 10);
            strcat (message, buffer);

            // Longitude value.
            if (gpsData->longitude < 0) {
                coord = gpsData->longitude * - 1;
                dirFlag = 0;
            } else {
                dirFlag = 1;
                coord = gpsData->longitude;
            }

            if (dirFlag == 1)
                strcat (message, "E");
            else
                strcat (message, "W");

            coordMin = (coord % 3600000) / 6;
            sprintf (buffer, "%03ld%02ld.%03ld", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000) / 10);
            strcat (message, buffer);

            remoteTransmit (COMMAND_TITLE, message, strlen(message));
            break;

        case 29:
        case 31:
            // Show GPS information if it is alive, otherwise show blank fields.
            if ((gpsData->status & 0xe000) == 0xe000) {
                sprintf (message, "%ld' %ld'/m ", gpsData->altitudeFeet, (int32) (gpsData->vSpeedAccum * 6000l / 12192l));
            }
                strcpy (message, "---' ---'/m ");

            // GPS status
            if ((gpsData->status & 0xe000) == 0xe000){
                sprintf (buffer, "%d.%d 3D", gpsData->dop / 10, gpsData->dop % 10);
                strcat (message, buffer);
            } else
                if ((gpsData->status & 0xe000) == 0xc000) {
                    sprintf (buffer, "%d.%d 2D", gpsData->dop / 10, gpsData->dop % 10);
                    strcat (message, buffer);
            } else
                strcat (message, "nogps");

            remoteTransmit (COMMAND_TITLE, message, strlen(message));
            break;

        case 44:
        case 46:
            // Speed knots and heading.
            spd = (int32) gpsData->hSpeed * 75000 / 385826;
            sprintf (message, "%d.%dkts AT %d.%ddeg", (int16) (spd / 10), (int16) (spd % 10), gpsData->heading / 10, gpsData->heading % 10);
            remoteTransmit (COMMAND_TITLE, message, strlen(message));
            break;

    } // END switch

}


/**
 *   Class to handle the camera control.
 */
#define CAM_START 1
#define CAM_HEAD2 2
#define CAM_HEAD3 3
#define CAM_DATA 4
#define CAM_MAX_BUFFER 100

#define CAM_PAN_DISABLE 0
#define CAM_PAN_UP_HOR_DOWN 1
#define CAM_PAN_UP_HOR 2
#define CAM_PAN_HOR_DOWN 3
#define CAM_PAN_UP_DOWN 4

#define CAM_UP_POS 0
#define CAM_HOR_POS 1
#define CAM_DOWN_POS 2

// Time periods in units of 5mS.  200 = 1 second
// Time between each pan command. (2 minutes)
#define CAM_PAN_PERIOD 24000

uint8 camMode, camIndex, camBuffer[CAM_MAX_BUFFER], camOutBuffer[CAM_MAX_BUFFER];
uint8 camPanMode, camPosition, camDownloadMode;
uint16 camAnalog1, camAnalog2, camAnalog3, camPanCountDown, camPanPeriod;

void camAutoPan()
{
    // Reset the count down timer when it reachs 0.
    if (camPanCountDown == 0) {
        camPanCountDown = camPanPeriod;

        // Determine the next camera position based on state machine.
        switch (camPanMode) {
            case CAM_PAN_UP_HOR_DOWN:
                switch (camPosition) {
                    case CAM_UP_POS:
                        camPosition = CAM_HOR_POS;
                        serDputs ("fc horizon\n\r");
                        break;

                    case CAM_HOR_POS:
                        camPosition = CAM_DOWN_POS;
                        serDputs ("fc down\n\r");
                        break;

                    case CAM_DOWN_POS:
                        camPosition = CAM_UP_POS;
                        serDputs ("fc up\n\r");
                        break;
                } // END camPosition switch
                break;

            case CAM_PAN_UP_HOR:
                switch (camPosition) {
                    case CAM_UP_POS:
                        camPosition = CAM_HOR_POS;
                        serDputs ("fc horizon\n\r");
                        break;

                    case CAM_HOR_POS:
                    case CAM_DOWN_POS:
                        camPosition = CAM_UP_POS;
                        serDputs ("fc up\n\r");
                        break;
                } // END camPosition switch
                break;

            case CAM_PAN_HOR_DOWN:
                switch (camPosition) {
                    case CAM_HOR_POS:
                        camPosition = CAM_DOWN_POS;
                        serDputs ("fc down\n\r");
                        break;

                    case CAM_UP_POS:
                    case CAM_DOWN_POS:
                        camPosition = CAM_HOR_POS;
                        serDputs ("fc horizon\n\r");
                        break;
                } // END camPosition switch
                break;

            case CAM_PAN_UP_DOWN:
                switch (camPosition) {
                    case CAM_UP_POS:
                        camPosition = CAM_DOWN_POS;
                        serDputs ("fc down\n\r");
                        break;

                    case CAM_HOR_POS:
                    case CAM_DOWN_POS:
                        camPosition = CAM_UP_POS;
                        serDputs ("fc up\n\r");
                        break;
                } // END camPosition switch
                break;
        } // END switch
    } // END if
}

/**
 *    Initalize the camera control communications.  The camera controller is a seperate module
 *    that is connected via an asyc serial port.
 */
void camInit()
{
    camPanMode = CAM_PAN_UP_HOR_DOWN;
    camPosition = CAM_HOR_POS;
    camPanCountDown = 0;
    camPanPeriod = CAM_PAN_PERIOD;
    camMode = CAM_START;
    camAnalog1 = 0;
    camAnalog2 = 0;
    camAnalog3 = 0;
    strcpy (camOutBuffer, "no com from camera computer");
    camDownloadMode = FALSE;
}

/**
 *    Parse and save the data elements that are in the string from the camera controller.
 */
void camParseMessage()
{
    uint16 i;

    i = 0;

    // Save the raw camera control string.
    strcpy (camOutBuffer, camBuffer);

    // Skip to the first comma.
    while (camBuffer[i] != 0 && camBuffer[i] != ',')
        ++i;

    if (camBuffer[i] == 0)
        return;

    // Skip to the second comma.
    ++i;
    while (camBuffer[i] != 0 && camBuffer[i] != ',')
        ++i;

    if (camBuffer[i] == 0)
        return;

    ++i;
    camAnalog1 = atoi (camBuffer + i);

    while (camBuffer[i] != 0 && camBuffer[i] != ',')
        ++i;

    if (camBuffer[i] == 0)
        return;

    ++i;
    camAnalog2 = atoi (camBuffer + i);

    while (camBuffer[i] != 0 && camBuffer[i] != ',')
        ++i;

    if (camBuffer[i] == 0)
        return;

    camAnalog3 = atoi (camBuffer + i + 1);

    // Save the results for telemetry display and include the parsed values if sucessful.
    sprintf (camOutBuffer, "'%s', %d, %d, %d", camBuffer, camAnalog1, camAnalog2, camAnalog3);
}

/**
 *    Read the camera control message from the serial port.  The message must start with $CC and end
 *    with a <CR> or <LF>.
 */
void camReadMessage()
{
    uint32 i, j;
    int32 logBase;
    int serialData;
    uint8 readyFlag, value, buffer[80];

    while ((serialData = serDgetc()) != -1) {
        value = (serialData & 0xff);

        switch (camMode) {
            case CAM_START:
                if (value == '$')
                    camMode = CAM_HEAD2;
                break;

            case CAM_HEAD2:
                if (value == 'C')
                    camMode = CAM_HEAD3;
                else
                    camMode = CAM_START;
                break;

            case CAM_HEAD3:
                switch (value) {
                    case 'D':
                        // Disable packets and the transmitter while downloading.
                        camDownloadMode = TRUE;

                        // Change the baud rate to allow for faster download and wait for the '*' character.
                        serDclose();

                        serDopen (57600);

                        readyFlag = FALSE;

                        while (!readyFlag) {
                            sysDelay (500);
                            serDputs ("*");

                            if (serDgetc() == '*')
                                readyFlag = TRUE;
                        }

                        // Send the size of the log blocks in bytes.
                        sprintf (buffer, "\n\rBEGIN %ld %ld\n\r", logGetBlockSize(logGetBlock1()), logGetBlockSize(logGetBlock2()));
                        serDputs (buffer);

                        // Dump each block of log memory.
                        logBase = logGetBlock1();

                        while (logBase != 0) {
                            i = 0;

                            // Dump a complete block of memory.
                            while (i < logGetBlockSize(logBase)) {
                                sprintf (buffer, "%06lx ", i);
                                serDputs (buffer);

                                for (j = 0; j < 16; ++j) {
                                    xmem2root (&value, logBase + i, 1);
                                    sprintf (buffer, "%02x ", value);
                                    serDputs (buffer);
                                    ++i;
                                } // END for

                                serDputs ("\n\r");

                                // If the operator sends the '*' character, abort the download.
                                if (serDgetc() == '*') {
                                    i = logGetBlockSize(logBase);
                                    logBase = 0;
                                } // END if
                            } // END while i < logGetBlockSize()

                            if (logBase == logGetBlock1())
                                logBase = logGetBlock2();
                            else
                                logBase = 0;

                        } // END while logBase != 0

                        // Send the final message and wait for it to transmit before setting baud rate.
                        serDputs ("\n\rEND\n\rBaud rate changed, press '*' to continue.");
                        while (serDwrFree() != PORTD_BUFFER_SIZE);

                        serDclose();
                        serDopen (4800);

                        // Wait for the operator before continuing.
                        while (serDgetc() != '*');

                        // Enable the packet transmit.
                        camDownloadMode = FALSE;
                        break;

                    case 'C':
                        camMode = CAM_DATA;
                        camIndex = 0;
                        break;

                    case '!':
                        serDputs ("alive\n\r");
                        camMode = CAM_START;
                        break;

                    default:
                        camMode = CAM_START;
                        break;
                } // END switch

                break;

            case CAM_DATA:
                camBuffer[camIndex] = value;

                // If we see a <CR> or <LF>, then NULL terminate the string.
                if (value == 13 || value == 10) {
                    camBuffer[camIndex] = 0;
                    camParseMessage();
                    camMode = CAM_START;
                }

                // If we fill the buffer without a <CR> or <LF>, the data wasn't good.
                if (++camIndex == CAM_MAX_BUFFER)
                    camMode = CAM_START;
                break;
        } // END switch

    } // END while
}

/**
 *    Reset the camera control timer.
 */
void camResetTimer()
{
    camPanCountDown = 0;
}

/**
 *    This method is called periodically to update the internal timer.
 */
void camUpdateTick()
{
    if (camPanCountDown != 0)
        --camPanCountDown;
}

/**
 *   Class to handle the configuration data.
 */
typedef struct {
    uint16 crc;
    uint8 analogFilter, analogReadCount, txDelay;
    int32 internalTempOffset, externalTempOffset, busVoltageScale;
    uint8 callSign[7], destCallSign[7], relayCallSign1[7], relayCallSign2[7];
    uint8 callSignSSID, callSignLandingZoneSSID, relayCallSignSSID1, relayCallSignSSID2;
    uint16 bootCount, flightTime;
} CONFIG_STRUCT;

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

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

/**
 *    Set the default configuration parameters.
 */
void configDefault()
{
    char buffer[40];

    // Station ID, relay path, and destination call sign and SSID.
    strcpy (config.callSign, "KD7LMO");
    config.callSignSSID = 11;
    config.callSignLandingZoneSSID = 0;
    strcpy (config.destCallSign, "APRS  ");
    strcpy (config.relayCallSign1, "GATE  ");
    strcpy (config.relayCallSign2, "WIDE3 ");
    config.relayCallSignSSID1 = 0;
    config.relayCallSignSSID2 = 3;

    // Number of TNC flag bytes sent before data stream starts.  (350mS) 1 byte = 6.6mS
    config.txDelay = 53;

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

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

    serDputs ("done.\n\rAllocating log space...");

    // Allocate and initialize the log subsystem.
    logInit();
    logClear();

    // Let the user know what is going on.
    sprintf (buffer, "done.  blk1 0x%lx, blk2 0x%lx\n\r", logGetBlock1(), logGetBlock2());
    serDputs (buffer);
}

/**
 *    Initialize configuration subsystem.
 */
uint8 configInit()
{
    // Let the user know what is going on.
    serDputs ("Checking configuration...");

    // If the configuration CRC is not valid, then set default vaules.
    if (config.crc != sysCRC16((uint8 *) &config + 2, sizeof(CONFIG_STRUCT) - 2)) {
        serDputs ("invalid CRC.\n\rSetting defaults...");
        configDefault();
        navInit();
        serDputs ("Setting CRC...");
        configCalcCRC();
        serDputs ("done.\n\r");
        return FALSE;
    }

    // We just increment the boot counter to keep track of power on reset or power faults.
    ++config.bootCount;
    configCalcCRC();

    // Lets everyone know it is good.
    serDputs ("good.\n\r");

    return TRUE;
}


/**
 *   Class to handle the GPS receiver.
 */
#define GPS_START 0
#define GPS_START2 1
#define GPS_COMMAND1 2
#define GPS_COMMAND2 3
#define GPS_READMESSAGE 4
#define GPS_CHECKSUMMESSAGE 5
#define GPS_EOMCR 6
#define GPS_EOMLF 7


GPSPOSITION_STRUCT gpsPosition;
uint8 gpsMode, gpsIndex, gpsChecksum, gpsBuffer[80];

/**
 *   Clear the GPS update flag.  The flag is set when a new GPS message is parsed
 *   and cleared by this method.
 *
 */
void gpsClearUpdate()
{
    gpsPosition.updateFlag = FALSE;
}

GPSPOSITION_STRUCT *gpsGetGPSData()
{
    return &gpsPosition;
}

/**
 *   Configure the GPS receiver.
 */
uint8 gpsInit()
{
    uint8 retryCount;
    uint32 gpsStartTime;

    serDputs ("Checking GPS status...");

    // Clear the structure that stores the position message.
    memset (&gpsPosition, 0, sizeof(GPSPOSITION_STRUCT));

    // State machine used for parsing the binary GPS string.
    gpsMode = GPS_START;

    // Get everything set.
    gpsStartTime = SEC_TIMER;
    retryCount = 0;
    gpsPosition.updateFlag = FALSE;

    // Try 10 seconds to get a valid GPS engine response.
    while (retryCount < 10) {
        // Wait for 1 second to pass.
        while (gpsStartTime == SEC_TIMER);
        gpsStartTime = SEC_TIMER;

        // Parse the incoming GPS message.
        gpsReadMessage();

        // Wait for the GPS message seconds value to change.
        if (gpsPosition.updateFlag == TRUE) {
            gpsPosition.updateFlag = FALSE;
            serDputs ("good.\n\r");
            return TRUE;
        }

        // Tell the GPS to send a status message once a second.
        gpsSendMessage ("Hb\001", 3);

        serDputs (".");
        ++retryCount;
    } // END while

    serDputs ("fault.\n\r");
    return FALSE;
}

/**
 *   Determine if the GPS position has updated.
 *
 *   @return true if updated; otherwise false
 */
uint8 gpsIsUpdate()
{
    return gpsPosition.updateFlag;
}

/**
 *   Log the current GPS position and analog telemetry values.
 */
void gpsLogData()
{
    uint8 status;

    // Log the bus voltage once a minute.
    if (gpsPosition.seconds == 0) {
        logBlockStart (LOG_TYPE_BUSVOLTAGE);
        logUint8 (analogGetBusVoltage());
    } // END if

    // Log the internal and external temperature every 15 seconds.
    if (gpsPosition.seconds == 0 || gpsPosition.seconds == 15 || gpsPosition.seconds == 30 || gpsPosition.seconds == 45) {
        logBlockStart (LOG_TYPE_TEMP);
        logInt16 (tempGetIntTemp());
        logInt16 (analogGetIntTemp());
        logInt16 (tempGetExtTemp());
    }

    // Log the data.
    logBlockStart (LOG_TYPE_COORD);
    logUint8 (gpsPosition.hours);
    logUint8 (gpsPosition.minutes);
    logUint8 (gpsPosition.seconds);
    logInt32 (gpsPosition.latitude);
    logInt32 (gpsPosition.longitude);
    logInt32 (gpsPosition.altitudeMeters);

    if ((gpsPosition.status & 0xe000) == 0xe000)
        status = 0x80;
    else
        status = 0x00;

    logUint8 ((uint8) ((gpsPosition.dop & 0x7f) | status));
    logUint8 ((uint8) ((gpsPosition.visibleSats << 4) | gpsPosition.trackedSats));
    logUint16 ((uint16) ((analogData[ANALOG_STATIC] & 0xfffff) >> 4));
    logUint16 ((uint16) ((analogData[ANALOG_PITOT] & 0xfffff) >> 4));
}

/**
 *   Parse the Motorola @@Hb (Short position/message) report.
 *
 */
void gpsParsePositionMessage()
{
    // Convert the binary stream into data elements.  We will scale to the desired units
    // as the values are used.
    gpsPosition.updateFlag = TRUE;
    gpsPosition.month = gpsBuffer[0];
    gpsPosition.day = gpsBuffer[1];
    gpsPosition.year = (gpsBuffer[2] << 8) | gpsBuffer[3];
    gpsPosition.hours = gpsBuffer[4];
    gpsPosition.minutes = gpsBuffer[5];
    gpsPosition.seconds = gpsBuffer[6];
    gpsPosition.latitude = ((int32) gpsBuffer[11] << 24) | ((int32) gpsBuffer[12] << 16) | ((int32) gpsBuffer[13] << 8) | (int32) gpsBuffer[14];
    gpsPosition.longitude = ((int32) gpsBuffer[15] << 24) | ((int32) gpsBuffer[16] << 16) | ((int32) gpsBuffer[17] << 8) | gpsBuffer[18];
    gpsPosition.altitudeMeters = ((int32) gpsBuffer[19] << 24) | ((int32) gpsBuffer[20] << 16) | ((int32) gpsBuffer[21] << 8) | gpsBuffer[22];
    gpsPosition.altitudeFeet = gpsPosition.altitudeMeters * 100l / 3048l;
    gpsPosition.vSpeed = (gpsBuffer[27] << 8) | gpsBuffer[28];
    gpsPosition.hSpeed = (gpsBuffer[29] << 8) | gpsBuffer[30];
    gpsPosition.heading = (gpsBuffer[31] << 8) | gpsBuffer[32];
    gpsPosition.dop = (gpsBuffer[33] << 8) | gpsBuffer[34];
    gpsPosition.visibleSats = gpsBuffer[35];
    gpsPosition.trackedSats = gpsBuffer[36];
    gpsPosition.status = (gpsBuffer[37] << 8) | gpsBuffer[38];

    // Generate a VSI (Vertical Speed Indicator) by low pass filtering the change in altitude.
    gpsPosition.vSpeedAccum = (int16) (gpsPosition.altitudeMeters - gpsPosition.lastAltitude) + gpsPosition.vSpeedAccum - (gpsPosition.vSpeedAccum >> 2);
    gpsPosition.lastAltitude = gpsPosition.altitudeMeters;
}

/**
 *   Read and validate the GPS message.  When a valid message has been read, the structure
 *   <b>gpsMessage</b> is updated.
 */
void gpsReadMessage()
{
    uint16 value;

    // This state machine handles each characters as it is read from the GPS serial port.
    // We are looking for the GPS mesage @@Hb ... C<CR><LF>
    while ((value = serCgetc()) != -1)
        switch (gpsMode) {
            // Wait for the first @
            case GPS_START:
                if (value == '@')
                    gpsMode = GPS_START2;
                break;

            case GPS_START2:
                if (value == '@')
                    gpsMode = GPS_COMMAND1;
                else
                    gpsMode = GPS_START;
                break;

            case GPS_COMMAND1:
                if (value == 'H')
                    gpsMode = GPS_COMMAND2;
                else
                    gpsMode = GPS_START;
                break;

            case GPS_COMMAND2:
                if (value == 'b') {
                    gpsMode = GPS_READMESSAGE;
                    gpsIndex = 0;
                    gpsChecksum = 0;
                    gpsChecksum ^= 'H';
                    gpsChecksum ^= 'b';
                } else
                    gpsMode = GPS_START;
                break;

            case GPS_READMESSAGE:
                gpsChecksum ^= value;
                gpsBuffer[gpsIndex++] = (uint8) value;

                if (gpsIndex == 47)
                    gpsMode = GPS_CHECKSUMMESSAGE;

                break;

            case GPS_CHECKSUMMESSAGE:
                if (gpsChecksum == value)
                    gpsMode = GPS_EOMCR;
                else
                    gpsMode = GPS_START;
                break;

            case GPS_EOMCR:
                if (value == 13)
                    gpsMode = GPS_EOMLF;
                else
                    gpsMode = GPS_START;
                break;

            case GPS_EOMLF:
                // Once we have the last character, convert the binary message to something usable.
                if (value == 10)
                    gpsParsePositionMessage();

                gpsMode = GPS_START;
                break;
        } // END switch
}

/**
 *    Sends a binary message to the GPS receiver.  The message
 *    contains only the data between the start of message '@@' and
 *    the checksum end EOM.  THe rest is generated by this method.
 *
 *    @param message pointer to array of binary data
 *    @param length length of message in bytes
 */
void gpsSendMessage (uint8 *message, uint8 length)
{
    uint8 i, checksum;

    // Send the message start characters.
    serCputc ('@');
    serCputc ('@');

    // Send each character while calculating the checksum.
    checksum = 0;

    for (i = 0; i < length; ++i) {
        checksum ^= message[i];
        serCputc (message[i]);
    } // END for

    // Now send the checksum, <CR>, and <LF>.
    serCputc (checksum);
    serCputc (13);
    serCputc (10);
}

/**
 *   Class to handle the local I/O functions.
 */

// Time periods in units of 5mS.  200 = 1 second
// Time to hold cut down I/O port high.  (10 seconds).
#define LOCAL_IO_CUTDOWN_PERIOD 2000

// Defin the bit maps for the local I/O ports.
#define LOCAL_IO_PORT0 0
#define LOCAL_IO_PORT1 1

uint16 localIOCountDown;

/**
 *    Initalize the local I/O control and timers.  NOTE: The I/O ports are set to low
 *    in the <b>rabbitInit</b> method.
 */
void localIOInit()
{
    localIOCountDown = 0;
}

/**
 *   Turn on output 1 and start a timer that will turn it off after a period of time.
 */
void localIOStartPort1Timer()
{
    BitWrPortI (PADR, &PADRShadow, 1, LOCAL_IO_PORT1);
    localIOCountDown = LOCAL_IO_CUTDOWN_PERIOD;
}

/**
 *    This method is called periodically to update the internal timer.
 */
void localIOUpdateTick()
{
    // Shut off port 1 after the timer expires.
    if (localIOCountDown != 0)
        if (--localIOCountDown == 0)
            BitWrPortI (PADR, &PADRShadow, 0, LOCAL_IO_PORT1);
}

/**
 *   Class to handle the flight data recorder log.
 */

// The size of each log block.  A log block can not exceed 0x3ffff bytes, so we break it into two sections.
#define LOG_SIZE_BLOCK1 0x40000
#define LOG_SIZE_BLOCK2 0x32000

#ifdef WIN32
uint8 logFlag;
int32 logPointer, logBlock1, logBlock2;
uint32 logIndex;
#else
protected uint8 logFlag;
protected int32 logPointer, logBlock1, logBlock2;
protected uint32 logIndex;
#endif

/**
 *    Verifies the log has space left, changes blocks if required, and saves the <b>recordID</b>.
 *
 *    @param recordID of the next block
 *
 */
void logBlockStart(uint8 recordID)
{
    // Only write to the log if it is enabled.
    if (!logFlag)
        return;

    // If we are in block 1 and out of space, switch to block 2.
    if (logPointer == logBlock1 && logIndex + 32 > LOG_SIZE_BLOCK1) {
        // Record a value so the decoder knows to switch blocks.
        logUint8 (LOG_NEXT_BLOCK);

        // Switch to block 2.
        logPointer = logBlock2;
        logIndex = 0;
    } // END if

    // If we are in block 2 and out of space, just stop logging.
    if (logPointer == logBlock2 && logIndex + 32 > LOG_SIZE_BLOCK2) {
        logFlag = FALSE;
        return;
    } // END if

    // Just record the ID of this block.
    logUint8 (recordID);
}

/**
 *    Clear and enable the data log.
 */
void logClear()
{
    logPointer = logBlock1;
    logIndex = 0;
    logFlag = TRUE;
}

/**
 *    Return an extended memory pointer to log block 1.
 *
 *    @return extended memory pointer
 */
int32 logGetBlock1()
{
    return logBlock1;
}

/**
 *    Return an extended memory pointer to log block 2.
 *
 *    @return extended memory pointer
 */
int32 logGetBlock2()
{
    return logBlock2;
}

/**
 *   The size of the log block referenced by <b>logBase</b>.
 *
 *   @return log block size in bytes
 */
uint32 logGetBlockSize(int32 logBase)
{
    if (logBase == logBlock1)
        return LOG_SIZE_BLOCK1;

    if (logBase == logBlock2)
        return LOG_SIZE_BLOCK2;

    return 0;
}

/**
 *   Initialize the log system.
 */
void logInit()
{
    logBlock1 = xalloc (LOG_SIZE_BLOCK1);
    logBlock2 = xalloc (LOG_SIZE_BLOCK2);
}

/**
 *    Save the signed, 16-bit <b>value</b> in the log.
 */
void logInt16 (int16 value)
{
    if (logFlag) {
        root2xmem (logPointer + logIndex, &value, 2);
        logIndex += 2;
    }
}

/**
 *    Save the signed, 32-bit <b>value</b> in the log.
 */
void logInt32 (int32 value)
{
    if (logFlag) {
        root2xmem (logPointer + logIndex, &value, 4);
        logIndex += 4;
    }
}

/**
 *    Save the unsigned, 16-bit <b>value</b> in the log.
 */
void logUint16 (uint16 value)
{
    if (logFlag) {
        root2xmem (logPointer + logIndex, &value, 2);
        logIndex += 2;
    }
}

/**
 *    Save the unsigned, 8-bit <b>value</b> in the log.
 */
void logUint8 (uint8 value)
{
    if (logFlag) {
        root2xmem (logPointer + logIndex, &value, 1);
        ++logIndex;
    }
}

/**
 *   Class to navigation.
 */

#define NAV_INTERVAL 500
#define NAV_BLOCKCOUNT 200

#ifdef WIN32
uint8 burstDetect;
int32 maxAltitude;
NAV_WINDDATA windData[NAV_BLOCKCOUNT];
#else
protected uint8 burstDetect;
protected int32 maxAltitude;
protected NAV_WINDDATA windData[NAV_BLOCKCOUNT];
#endif

/**
 *    Calculate the distance and heading from <b>coord1</b> to <b>coord2</b>.  The
 *    coordinate values must be in radians.  The result saved in <b>course</b> is
 *    in units of radians.
 *
 *    @param coord1 start location
 *    @param coord2 end location
 *    @param course distance and heading
 */
void navCourse (COORD *coord1, COORD *coord2, COURSE *course)
{
    float d, lat1, lon1, lat2, lon2, angle;

    lat1 = coord1->lat;
    lon1 = coord1->lon;

    lat2 = coord2->lat;
    lon2 = coord2->lon;

    d = 2 * asin(sqrt( sin((lat1-lat2)/2)*sin((lat1-lat2)/2) + cos(lat1)*cos(lat2)*sin((lon1-lon2)/2)*sin((lon1-lon2)/2) ));
    course->dist = d;

    if (d < 0.00000027) {
        course->head = 0;
        return;
    }

    if (sin(lon2-lon1) < 0) {
        angle = (sin(lat2)-sin(lat1)*cos(d)) / (sin(d)*cos(lat1));

        if (angle < -1.0)
            angle = -1.0;

        if (angle > 1.0)
            angle = 1.0;

        course->head = acos(angle);
    } else {
        angle = (sin(lat2)-sin(lat1)*cos(d)) / (sin(d)*cos(lat1));

        if (angle < -1.0)
            angle = -1.0;

        if (angle > 1.0)
            angle = 1.0;

        course->head = 2*PI - acos( angle );
    }
}

/**
 *   Use a 4th order polynomial to calculate the descent rate in feet/second.
 *
 *   @param alt altitude in feet
 *
 *   @return descent in feet per second
 */
float navDescentRate (float alt)
{
    return -1E-18*alt*alt*alt*alt + 3E-13*alt*alt*alt - 8E-09*alt*alt + 0.0004*alt + 13.522;  // 4th order ANSR-13

//    return -5E-18*alt*alt*alt*alt + 9E-13*alt*alt*alt - 4E-08*alt*alt + 0.0010*alt + 9.3197; // 4th order ANSR-9
//    return -1E-18*alt*alt*alt*alt + 4E-13*alt*alt*alt - 2E-08*alt*alt + 0.0012*alt + 6.0886;  // 4th order ANSR-8
}

/**
 *   Add the vector with a length <b>d</b> and true course <b>tc</b> to the
 *   lat/long <b>current</b> and save the result in <b>next</b>.
 *
 *   @param current lat/lon coordinates
 *   @param next lat/lon after vector added
 *   @param d distance in radians
 *   @param tc true course in radians
 */
void navDistRadial (COORD *current, COORD *next, float d, float tc)
{
    next->lat = asin(sin(current->lat) * cos(d) + cos(current->lat) * sin(d) * cos(tc));
    next->lon = current->lon - asin(sin(tc) * sin(d) / cos(next->lat));
}

/**
 *    Return the course distance in nautical miles.
 *
 *    @return distance
 */
float navGetDist (COURSE *course)
{
    return course->dist * 180.0 * 60.0 / PI;
}


int32 navGetMaxAltitude()
{
    return maxAltitude;
}

/**
 *  Get the number of wind data vectors currently saved.
 *
 *  @return number of wind vectors
 */
uint16 navGetWindDataCount()
{
    uint16 i;

    for (i = 0; i < NAV_BLOCKCOUNT; ++i)
        if (windData[i].coord.alt == 0)
            return i;

    return NAV_BLOCKCOUNT;
}

/**
 *   Initialize the navigation data arrays.
 */
void navInit()
{
    uint16 i;
    NAV_WINDDATA *wind;

    for (i = 0; i < NAV_BLOCKCOUNT; ++i) {
        wind = windData + i;

        wind->timeStamp = 0;
        wind->timeInterval = 0;

        wind->course.dist = 0;
        wind->course.head = 0;
        wind->course.trackError = 0;

        wind->coord.lat = 0;
        wind->coord.lon = 0;
        wind->coord.alt = 0;
    } // END for

    maxAltitude = 0;
    burstDetect = FALSE;
}

void navLaunch(GPSPOSITION_STRUCT *gps)
{
    navInit();

    navSetDegICoord(gps->latitude, gps->longitude, &windData[0].coord);
    windData[0].coord.alt = gps->altitudeFeet;
    windData[0].timeStamp = gps->hours * 3600 + gps->minutes * 60 + gps->seconds;
}

/**
 *   Convert coordinates from radians to decimal degrees.
 *
 *   @param coord pointer to coordinate pair
 */
void navRadToDeg (COORD *coord)
{
    coord->lat *= 180.0 / PI;
    coord->lon *= -180.0 / PI;
}

/**
 *   Convert coordinates (lat, lon) to radians and store in coord.
 *
 *   @param lat in degrees where north is positive
 *   @param lon in degrees where east is positive
 *   @param coord coordinate pair in radians
 */
void navSetDegFCoord (float lat, float lon, COORD *coord)
{
    coord->lat = lat * PI / 180.0;
    coord->lon = -lon * PI / 180.0;
}

/**
 *   Convert coordinates (lat, lon) to radians and store in coord.  The (lat, lon) pair
 *   is expected in milli-seconds of arc.  The GPS engine provides the location in
 *   this format.
 *
 *   @param lat in milli-seconds of arc where north is positive
 *   @param lon in milli-seconds of arc where east is positive
 *   @param coord coordinate pair in radians
 */
void navSetDegICoord (int32 lat, int32 lon, COORD *coord)
{
    coord->lat = ((float) lat / 3600000.0) * PI / 180.0;
    coord->lon = -((float) lon / 3600000.0) * PI / 180.0;
}

/**
 *    Course heading in true degrees.
 *
 *    @return heading
 */
float navGetHead (COURSE *course)
{
    return course->head * 180.0 / PI;
}

uint8 navUpdate (GPSPOSITION_STRUCT *gps, COORD *landingZone)
{
    uint16 i;
    int32 alt;
    float segmentRate, distance;
    COORD positionEstimate, next;

    // Track the maximum altitude.
    if (gps->altitudeFeet > maxAltitude)
        maxAltitude = gps->altitudeFeet;

    // Calculate the landing after burst.
    if (burstDetect) {
        // Find the last entry in the table.
        for (i = 0; i < NAV_BLOCKCOUNT && gps->altitudeFeet > windData[i].coord.alt; ++i);

        if (i < 2)
            return FALSE;

        --i;
        alt = gps->altitudeFeet;

        navSetDegICoord (gps->latitude, gps->longitude, &positionEstimate);

        while (i != 0) {
            segmentRate = ((float) (alt - windData[i].coord.alt)) / navDescentRate((float) windData[i].coord.alt);
            distance = windData[i].course.dist * segmentRate / (float) windData[i].timeInterval;

            navDistRadial (&positionEstimate, &next, distance, windData[i].course.head);
            positionEstimate = next;

            alt = windData[i].coord.alt;
            --i;
        }

        *landingZone = positionEstimate;

        return TRUE;
    } // END if

    // Set a flag to indicate we have a burst condition.
    if (gps->altitudeFeet + 500 < maxAltitude) {
        burstDetect = TRUE;
        return FALSE;
    }

    // Find the last entry in the table.
    for (i = 0; i < NAV_BLOCKCOUNT && windData[i].coord.alt != 0; ++i);

    if (i == NAV_BLOCKCOUNT)
        return FALSE;

    // Calculate the wind speed for this altitude interval.
    if (gps->altitudeFeet > windData[i - 1].coord.alt + NAV_INTERVAL) {
        // Save the current position as the end point for this segment.
        navSetDegICoord(gps->latitude, gps->longitude, &windData[i].coord);
        windData[i].coord.alt = gps->altitudeFeet;
        windData[i].timeStamp = gps->hours * 3600 + gps->minutes * 60 + gps->seconds;

        // Calcualte the course and heading from the last element in this segment.
        navCourse (&windData[i - 1].coord, &windData[i].coord, &windData[i].course);

        // Calculate the time interval for this segment.
        windData[i].timeInterval = (uint16) (windData[i].timeStamp - windData[i - 1].timeStamp);
    } // END if

    return FALSE;
}

/**
 *   Class to handle short range, local remote devices.
 */

// Port E bits used for control and data.
#define REMOTE_DATA 4
#define REMOTE_TX 5

#define REMOTE_MAX_MESSAGE 160

#define REMOTE_SELFTEST 0x21
#define REMOTE_CUTDOWN 0x22


// Control state machine.
uint8 remoteBit, remoteIndex, remoteMessageLen, remoteTransmitByte, remoteBuffer[REMOTE_MAX_MESSAGE], remoteMessageCount;
uint8 remoteCommand, remoteRepeatCount;

void remoteInit()
{
    // Put the transmiter in low power mode.
    BitWrPortI (PEDR, &PEDRShadow, 0, REMOTE_TX);
    BitWrPortI (PEDR, &PEDRShadow, 0, REMOTE_DATA);

   remoteRepeatCount = 0;
   remoteMessageCount = 0;
   remoteBit = 0;
   remoteMessageLen = 0;
   remoteIndex = REMOTE_MAX_MESSAGE;
   remoteTransmitByte = 0x00;
}

/**
 *  Encode the <b>value</b> into a 16-bit manchester encoded bit stream in
 *  the <b>buffer</b>.  Move the buffer pointer forward two bytes.
 *
 *  @param value to encode
 *  @param buffer to write values to
 */
void remoteManchesterEncode(uint8 value, char **buffer)
{
    uint8 i, encodedValue;

   for (i = 0, encodedValue = 0; i < 4; ++i) {
        encodedValue = (encodedValue << 2) | ((value & 0x80) ? 0x02 : 0x01);
        value = value << 1;
   } // END for

   **buffer = encodedValue;
   ++(*buffer);


   for (i = 0, encodedValue = 0; i < 4; ++i) {
        encodedValue = (encodedValue << 2) | ((value & 0x80) ? 0x02 : 0x01);
        value = value << 1;
   } // END for

   **buffer = encodedValue;
   ++(*buffer);
}

void remoteSend()
{
    char buffer[10];
    uint8 hold;

    if (remoteRepeatCount == 0)
        return;

    --remoteRepeatCount;

    sprintf (buffer, "%02x%02x%02x", remoteMessageCount, remoteMessageCount ^ 0xff, remoteRepeatCount);

    ++remoteMessageCount;

    hold = remoteRepeatCount;
    remoteRepeatCount = 0;
    remoteTransmit (remoteCommand, buffer, 6);
    remoteRepeatCount = hold;
}

void remoteSelfTest()
{
    remoteCommand = REMOTE_SELFTEST;
    remoteRepeatCount = 5;
}

void remoteCutDown()
{
    remoteCommand = REMOTE_CUTDOWN;
    remoteRepeatCount = 10;
}

/**
 *  This method is called at the 1200 baud bit rate (833uS) to send each bit in the
 *  remote message.
 */
void remoteTimer()
{
    uint8 i;

   // If the index into the buffer is at the end, there is nothing to do.
   if (remoteIndex == REMOTE_MAX_MESSAGE)
    return;

    // The first thing we do is write the bit to prevent output jitter.
    BitWrPortI (PEDR, &PEDRShadow, ((remoteTransmitByte & 0x80) ? 1 : 0), REMOTE_DATA);

   // Shift out 8 bits in the register before we get the next byte.
    if (++remoteBit != 8) {
      remoteTransmitByte = remoteTransmitByte << 1;
      return;
    } // END if

   // If we at the last byte, set the index to the EOM and shutdown the transmitter.
    if (remoteIndex == remoteMessageLen) {
    remoteIndex = REMOTE_MAX_MESSAGE;
        BitWrPortI (PEDR, &PEDRShadow, 0, REMOTE_TX);
      return;
    }

   // Get the next byte in the buffer.
   remoteTransmitByte = remoteBuffer[remoteIndex++];

   // Reset the bit counter.
    remoteBit = 0;
}

/**
 *   Prepare the remote message for transmission.  If a transmission is already in
 *   progress, the message is not sent.
 *
 *   @param command 8-bit command value
 *   @param message variable length binary message to send
 *   @param length of the message in bytes
 */
void remoteTransmit (uint8 command, char *message, uint8 length)
{
    uint8 i, *buffer;
    uint8 text[80], crcBuffer[(REMOTE_MAX_MESSAGE - 20) / 2];
    uint16 crc;

    if (remoteRepeatCount != 0)
        return;

   // Ignore this message because we are already transmitting.
   if (remoteIndex != REMOTE_MAX_MESSAGE)
       return;

   // Make sure the message isn't too long.
   if (2 * length > REMOTE_MAX_MESSAGE - 16)
       return;

    if (command == COMMAND_TITLE)
        message[20] = 0;

//   serDputs ("*** Transmitting....\n\r");

   // Turn on the the transmitter and key it.
    BitWrPortI (PEDR, &PEDRShadow, 1, REMOTE_DATA);
    BitWrPortI (PEDR, &PEDRShadow, 1, REMOTE_TX);

   // Fill in the bit stream.

   // Set a pointer to the output buffer.
   buffer = remoteBuffer;

   // Bit sync
   remoteManchesterEncode (0xcc, &buffer);
   remoteManchesterEncode (0xcc, &buffer);

   // Frame sync.
   remoteManchesterEncode (0x18, &buffer);
   remoteManchesterEncode (0x47, &buffer);
   remoteManchesterEncode (0x70, &buffer);
   remoteManchesterEncode (0x25, &buffer);

   // Command and length.
   remoteManchesterEncode (command, &buffer);
   remoteManchesterEncode (length, &buffer);

    crcBuffer[0] = command;
    crcBuffer[1] = length;

    // Command data.
    for (i = 0; i < length; ++i) {
        remoteManchesterEncode (message[i], &buffer);
        crcBuffer[i + 2] = message[i];
    }

    crc = sysCRC16 (crcBuffer, length + 2);

    remoteManchesterEncode (crc >> 8, &buffer);
    remoteManchesterEncode (crc & 0xff, &buffer);

    // We will transmit 8 ones for the first byte.
    remoteTransmitByte = 0xff;

    // Counter to keep track of which bit we are sending.
    remoteBit = 0;

    // The length of the message in bytes.
    remoteMessageLen = 2 * length + 20;

    // An index into the buffer that contains the message.
    remoteIndex = 0;

//  for (i = 0; i < remoteMessageLen; ++i) {
//  sprintf (text, "%d 0x%x\n\r", i, remoteBuffer[i]);
//    serDputs (text);
//  } // END if
}

/**
 *   Class to handle system and generic functions.
 */

/**
 *   Display the system start message via the TNC.
 */
void sysBootMessage(uint8 configStatus, uint8 gpsStatus)
{
    uint8 buffer[80];

    // Assemble a start up message for use on the TNC and RS-232 ports.
    if (configStatus == TRUE)
        sprintf (buffer, "%s - memory OK, boot count %d, ", sysGetVersion(), config.bootCount);
    else
        sprintf (buffer, "%s - memory initialized, ", sysGetVersion());

    if (gpsStatus == TRUE)
        strcat (buffer, "GPS active");
    else
        strcat (buffer, "GPS fault");

    // Now send it to the RS-232 port and TNC.
    serDputs (buffer);
    serDputs ("\n\r");
    tlmCreateMessage(buffer);
}

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

/**
 *    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);
}

/**
 *   Return the software version string.
 *
 *   @return software version string
 */
char *sysGetVersion()
{
    return ">ANSR Payload Computer V1.12";
}

/**
 *   Configures the on chip ports.  Rather than have each subsystem setup
 *   their ports, we do it all in one place to reduce the chance of making a mistake.
 */
void sysInit()
{
    // We don't need to run that fast, so slow down to save power.
    clockDoublerOff();

    // Configure serial port C for GPS.
    serCopen (9600);

    // Configure serial port D for NMEA/flight computer control.
    serDopen (4800);

    serDputs ("\n\rInitializing...");

    // ****** Configure PORT-A ******
    // All outputs low.
    WrPortI (PADR, &PADRShadow, 0xc0);

    // Port A all outputs.
    WrPortI (SPCR, NULL, 0x84);


    // ******  Configure PORT-D ******
    // All outputs LOW.
    WrPortI(PDDR, &PDDRShadow, 0x00);

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

    // Port D PD0-5 are outputs, PD6-7 inputs
    WrPortI(PDDDR, &PDDDRShadow, 0x3f);

    // PORT D PD0-5 push-pull, PD6-7 open drain
    WrPortI(PDDCR, NULL, 0xc0);

    // Clock PD0-3 bits on timer B1 interrupt.
    WrPortI(PDCR, NULL, 0x02);


    // ******  Configure PORT-E ******
    // Port E, PE0, PE1, PE6, PE7 as inputs, PE2-PE5 as outputs
    WrPortI (PEDDR, NULL, 0x3c);

    // Port E all pins as I/O.
    WrPortI (PEFR, NULL, 0x00);


    // ****** Configure External Interrupt for TNC ******
    // Set external ISR to priority 3.
    SetVectExtern2000 (0x03, tncExternalISR);


    // ****** Configure Timer A & B for TNC and radio ******
    // Set the timer B ISR.
    SetVectIntern (0x0b, tncTimer);

    // Set the timer A4 ISR.
    SetVectIntern (0x0a, sysTimerISR);

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

    // Set timer A4 value that provides 5mS radio interrupt.
    WrPortI (TAT5R, &TAT5RShadow, 168);

    // Tell the world we are alive.
    serDputs ("done\n\r");
}

/**
 *   Enable the Rabbit interrupts.
 */
void sysInterruptEnable()
{
    // External interrupt priority 3 on INT0A, INT1A either edge
    WrPortI (I0CR, NULL, 0x0f);
    WrPortI (I1CR, NULL, 0x0f);

    // Timer B clocked by timer A1, interrupt priority 2.
    WrPortI (TBCR, NULL, 0x06);

    // Set timer B compare registers to get the interrupt started.
    WrPortI (TBM1R, NULL, 0);
    WrPortI (TBL1R, NULL, 0);

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

    // Enable Timer B and match register interrupts.
    WrPortI (TBCSR, NULL, 0x03);

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

    // Timer A5 clocked by timer A1, all other timers clocked by pclk/2, interrupt priority 1.
    WrPortI (TACR, &TACRShadow, 0x21);

    // Enable timer A1 and timer A5 interrupts.
    WrPortI (TACSR, &TACSRShadow, 0x21);
}

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

#ifdef WIN32
void sysRuntimeErrHandler()
#else
root void sysRuntimeErrHandler()
#endif
{
    static int error, xpc, addr;

#ifndef WIN32
    // get all the relevant parameters off the stack
#asm
    ld      hl, (sp+@SP+2)
    ld      (error), hl     ; get the runtime error code
    ld      hl, (sp+@SP+6)
    ld      (xpc), hl       ; get the XPC where exception() was called
    ld      hl, (sp+@SP+8)
    ld      (addr), hl      ; get the address exception() was called
#endasm

#endif

    // This is a simple error handler, just reboot.
    forceSoftReset();
}

/**
 *   System timer function that is called every 5mS.
 */
#ifdef WIN32
void sysTimerISR()
#else
interrupt void sysTimerISR()
#endif
{
    // Clear the timer interrupt.
    RdPortI (TACSR);

    // Radio handler periodic timer.
    radioUpdate();

    // Local I/O periodic timer.
    localIOUpdateTick();

    // Camera control periodic timer.
    camUpdateTick();
}


/**
 *   Class to handle the TNC functions.
 */

// Port D bits used for TNC control.
#define TNC_M0 0
#define TNC_DATA 1
#define TNC_M1 2

#define TNC_MAX_RX 300
#define TNC_MAX_MESSAGE 255

#define TNC_RX_FLAG 0
#define TNC_RX_DATA 1
#define TNC_RX_PARSE 2
#define TNC_TX_PREPARE 3
#define TNC_TX_SYNC 4
#define TNC_TX_DATA 5
#define TNC_TX_END 6

uint16 tncTimerCompare, tncIndex, tncLength, tncTxPacketCount, tncRxPacketCount, tncProcPacketCount;
uint8 tncBitCount, tncBitTime, tncShift, tncRx, tncLastBit, tncMode, tncTransmit;
uint8 tncBitStuff, tncBuffer[TNC_MAX_RX], tncSSIDOverrideFlag, tncRemoteTick;

struct {
    uint8 sourceCallSign[7], sourceSSID;
    uint8 destCallSign[7], destSSID;
    uint8 message[TNC_MAX_MESSAGE];
} tncPacket;

#ifdef WIN32
void tncExternalISR()
#else
interrupt void tncExternalISR()
#endif
{
    // The bit timer is synced on the falling edge of any receive bit.  Two clock
    // times later (408uS), we read and proces the bit value.
    tncBitTime = 0;
}

/**
 *   Configure the TNC.
 */
void tncInit()
{
    // Configure the internal state machine.
    tncTimerCompare = 0;
    tncShift = 0;
    tncIndex = 0;
    tncMode = TNC_RX_FLAG;

    // Flag used to indicate the TNC is transmitting.
    tncTransmit = FALSE;

    // Flag to indicate we should select an alternate SID.
    tncSSIDOverrideFlag = FALSE;

    // Keep track of which tick we process the remote control transmitter.
    tncRemoteTick = 0;

    // Counter of all the packets we handle.
    tncTxPacketCount = 0;
    tncRxPacketCount = 0;
    tncProcPacketCount = 0;

    // Disable the CMX614 output.
    BitWrPortI (PDDR, &PDDRShadow, 0, TNC_DATA);
    BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M0);
    BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M1);
}

/**
 *    Transfer the AX.25 packet to the <b>tncPacket</b> structure.
 *
 *    @return TRUE if packet was valid; otherwise false
 */
int tncParsePacket()
{
    uint16 i, j, crc;

    // Make sure the CRC is good.
    crc = sysCRC16 (tncBuffer, tncLength - 2);

    if ((crc & 0xff) != tncBuffer[tncLength - 2])
        return FALSE;

    if ((crc >> 8) != tncBuffer[tncLength - 1])
        return FALSE;

    // Parse the dest call sign.
    for (i = 0; i < 6 && tncBuffer[i] != 0x40; ++i)
        tncPacket.destCallSign[i] = tncBuffer[i] >> 1;

    tncPacket.destCallSign[i] = 0;
    tncPacket.destSSID = (tncBuffer[6] >> 1) & 0x0f;

    // Parse the source call sign.
    for (i = 0; i < 6 && tncBuffer[7 + i] != 0x40; ++i)
        tncPacket.sourceCallSign[i] = tncBuffer[7 + i] >> 1;

    tncPacket.sourceCallSign[i] = 0;
    tncPacket.sourceSSID = (tncBuffer[13] >> 1) & 0x0f;

    // Skip over the optional repeater paths.
    for (i = 13; i < 76 && i < tncLength && (tncBuffer[i] & 0x01) == 0x00; i += 7);

    // Copy the message to our buffer.
    for (j = 0, i += 3; i < tncLength && tncBuffer[i] != 0x0d; ++i, ++j)
        tncPacket.message[j] = tncBuffer[i];

    tncPacket.message[j] = 0;

    return TRUE;
}

void tncSetAlternateSSID()
{
    tncSSIDOverrideFlag = TRUE;
}

/**
 *   Send an AX.25 packet via the RF interface.  This function enables the transmitter,
 *   prepares the output buffer, and sets the interrupt mode.  The data is bit shifted
 *   in the timer interrupt service routine.
 *
 *   @param message pointer to NULL terminate message string
 */
void tncSendPacket(uint8 *message)
{
    uint16 i, crc;
    uint8 *outBuffer;

    // If we are currently sending, then ignore this packet.
    if (tncMode == TNC_TX_PREPARE || tncMode == TNC_TX_SYNC || tncMode == TNC_TX_DATA || tncMode == TNC_TX_END)
        return;

    tncMode= TNC_TX_PREPARE;

    // Turn on the MODEM TX mode.
    BitWrPortI (PDDR, &PDDRShadow, 1, TNC_DATA);
    BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M0);
    BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M1);

    // Set a pointer to our output buffer.
    outBuffer = tncBuffer;

    // Includes source (7), dest (7), control field (1), protocol ID (1), and end of message (1).
    tncLength = 17;

    // Set the destination address.
    for (i = 0; i < 6; ++i)
        *outBuffer++ = config.destCallSign[i] << 1;

    // Set destiation to SSID-0
    *outBuffer++ = 0x60;

    // Set the source address.
    for (i = 0; i < 6; ++i)
        *outBuffer++ = config.callSign[i] << 1;

    // Set the SSID.
    if (tncSSIDOverrideFlag)
        *outBuffer++ = 0x60 | (config.callSignLandingZoneSSID << 1);
    else
        *outBuffer++ = 0x60 | (config.callSignSSID << 1);

    tncSSIDOverrideFlag = FALSE;

    // Add relay path 1.
    if (*config.relayCallSign1 != 0) {
        for (i = 0; i < 6; ++i)
            *outBuffer++ = config.relayCallSign1[i] << 1;

            *outBuffer++ = 0x60 | (config.relayCallSignSSID1 << 1);

            tncLength += 7;
    } // END if

    // Add relay path 2.
    if (*config.relayCallSign2 != 0) {
        for (i = 0; i < 6; ++i)
            *outBuffer++ = config.relayCallSign2[i] << 1;

            *outBuffer++ = 0x60 | (config.relayCallSignSSID2 << 1);

            tncLength += 7;
    } // END if

    // Set bit-0 of the last SSID.
    *(outBuffer - 1) |= 0x01;

    // Set the control field (UI) and protocol ID.
    *outBuffer++ = 0x03;
    *outBuffer++ = 0xf0;

    // Save the message.
    while (*message != 0) {
        *outBuffer++ = *message++;
        ++tncLength;
    }

    // Add the end of message character.
    *outBuffer++ = 0x0d;

    // Calculate and append the CRC.
    crc = sysCRC16(tncBuffer, tncLength);
    *outBuffer++ = crc & 0xff;
    *outBuffer = (crc >> 8) & 0xff;

    // Update the length to include the CRC bytes.
    tncLength += 2;

    // Prepare for the ISR.
    tncBitCount = 0;
    tncShift = 0x7e;
    tncLastBit = 0;
    tncIndex = 0;
    tncMode = TNC_TX_SYNC;
}

#ifdef WIN32
void tncTimer()
#else
interrupt void tncTimer()
#endif
{
    // Clear the interrupt pending flag.
    RdPortI (TBCSR);

    // Interrupt again in 208uS (4x 1200 baud).
    tncTimerCompare = (tncTimerCompare + 7) & 0x3ff;
    WrPortI (TBM1R, NULL, (tncTimerCompare >> 2) & 0xc0);
    WrPortI (TBL1R, NULL, tncTimerCompare & 0xff);

    // Keep track of when to process the remote transmitter.
    if (++tncRemoteTick == 4) {
        tncRemoteTick = 0;
        remoteTimer();
    } // END if

    // We keep track of the sub bit-time to determine when to sample the bit.
    tncBitTime = (tncBitTime + 1) & 0x03;

    // If we are the mid-bit bit point, process the data.
    if (tncBitTime == 2) {
        // Process based on the rx or tx mode state machine.
        switch (tncMode) {
            case TNC_RX_FLAG:
            case TNC_RX_DATA:
                // NRZI decode the incoming data bit.
                if ((RdPortI(PEDR) & 0x02) == 0x00) {
                    if (tncLastBit == 0)
                        tncShift = (tncShift >> 1) | 0x80;
                    else
                        tncShift = tncShift >> 1;

                    tncLastBit = 0;
                } else {
                    if (tncLastBit == 1)
                        tncShift = (tncShift >> 1) | 0x80;
                    else
                        tncShift = tncShift >> 1;

                    tncLastBit = 1;
                }

                // Check for AX.25 packet flag.
                if (tncShift == 0x7e) {
                    // If we captured 17 bytes and have a flag, then we might have a valid message.
                    if (tncIndex > 16) {
                        tncLength = tncIndex;
                        tncMode = TNC_RX_PARSE;
                        return;
                    } // END if

                    // Reset to the start of our message buffer.
                    tncBitCount = 0;
                    tncIndex = 0;
                    tncMode = TNC_RX_DATA;
                    tncBitStuff = 0;
                    return;
                } // END if tncShift

                // If we haven't seen the 0x7e start flag, then exit.
                if (tncMode == TNC_RX_FLAG)
                    return;

                // Check for bit stuffing.
                tncBitStuff = (tncBitStuff >> 1) | (tncShift & 0x80);

                if ((tncBitStuff & 0xfc) == 0x7c) {
                    tncBitStuff = 0;
                    return;
                } else
                    tncRx = (tncRx >> 1) | (tncShift & 0x80);


                // If we have 8 bits, then save it to the buffer.
                if (++tncBitCount == 8) {

                    // If we over run the buffer, then reset everything.
                    if (tncIndex == TNC_MAX_RX) {
                        tncIndex = 0;
                        tncMode = TNC_RX_FLAG;
                        return;
                    } // END if

                    tncBuffer[tncIndex++] = tncRx;
                    tncBitCount = 0;
                } // END if

            break;

            case TNC_TX_PREPARE:
                break;

            case TNC_TX_SYNC:
                // The variable tncShift contains the lastest data byte.
                // NRZI enocde the data stream.
                if ((tncShift & 0x01) == 0x00)
                    if (tncLastBit == 0)
                        tncLastBit = 1;
                    else
                        tncLastBit = 0;

                BitWrPortI (PDDR, &PDDRShadow, tncLastBit, TNC_DATA);

                // When the flag is done, determine if we need to send more or data.
                if (++tncBitCount == 8) {
                    tncBitCount = 0;
                    tncShift = 0x7e;

                    // Once we transmit x mS of flags, send the data.
                    // txDelay bytes * 8 bits/byte * 833uS/bit = x mS
                    if (++tncIndex == config.txDelay) {
                        tncIndex = 0;
                        tncShift = tncBuffer[0];
                        tncBitStuff = 0;
                        tncMode = TNC_TX_DATA;
                    } // END if
                } else
                    tncShift = tncShift >> 1;
                break;

            case TNC_TX_DATA:
                // Determine if we have sent 5 ones in a row, if we have send a zero.
                if (tncBitStuff == 0x1f) {
                    if (tncLastBit == 0)
                        tncLastBit = 1;
                    else
                        tncLastBit = 0;

                    BitWrPortI (PDDR, &PDDRShadow, tncLastBit, TNC_DATA);
                    tncBitStuff = 0x00;
                    return;
                }    // END if

                // The variable tncShift contains the lastest data byte.
                // NRZI enocde the data stream.
                if ((tncShift & 0x01) == 0x00)
                    if (tncLastBit == 0)
                        tncLastBit = 1;
                    else
                        tncLastBit = 0;

                BitWrPortI (PDDR, &PDDRShadow, tncLastBit, TNC_DATA);

                // Save the data stream so we can determine if bit stuffing is
                // required on the next bit time.
                tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;

                // If all the bits were shifted, get the next byte.
                if (++tncBitCount == 8) {
                    tncBitCount = 0;

                    // If everything was sent, transmit closing flags.
                    if (++tncIndex == tncLength) {
                        tncIndex = 0;
                        tncShift = 0x7e;
                        tncMode = TNC_TX_END;
                    } else
                        tncShift = tncBuffer[tncIndex];

                } else
                    tncShift = tncShift >> 1;

                break;

            case TNC_TX_END:
                // The variable tncShift contains the lastest data byte.
                // NRZI enocde the data stream.
                if ((tncShift & 0x01) == 0x00)
                    if (tncLastBit == 0)
                        tncLastBit = 1;
                    else
                        tncLastBit = 0;

                BitWrPortI (PDDR, &PDDRShadow, tncLastBit, TNC_DATA);

                // If all the bits were shifted, get the next one.
                if (++tncBitCount == 8) {
                    tncBitCount = 0;
                    tncShift = 0x7e;

                    // Transmit two closing flags.
                    if (++tncIndex == 2) {
                        // Keep track of how many packets we have sent.
                        ++tncTxPacketCount;

                        // Reset to the receive mode.
                        tncIndex = 0;
                        tncShift = 0;
                        tncMode = TNC_RX_FLAG;

                        // Change the MODEM to receive mode.
                        BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M0);
                        BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M1);
                        BitWrPortI (PDDR, &PDDRShadow, 0, TNC_DATA);

                        // Clear this flag to indicate we have sent the requested packet.
                        tncTransmit = FALSE;

                        // Select the main frequency.
                        radioSetTxFreq (RADIO_TX_DOWNLINK);
                        return;
                    } // END if
                } else
                    tncShift = tncShift >> 1;
                break;
        } // END switch
    } // END if bitTime
}

/**
 *   Class to handle radio functions.
 */

// Time periods in units of 5mS.  200 = 1 second
// The ID period for the cross band repeater. (5 minutes)
#define RADIO_ID_TIME 60000

// Time the carrier detect must be on or off before a transition is detected. (200 mS)
#define RADIO_SQUELCH_HYST 40

// Maximum transmit time. (2 minutes)
#define RADIO_TX_TIMEOUT 24000

// The amount of time to allow for the entry of a DTMF message. (10 seconds)
#define RADIO_DTMF_PERIOD 2000

// The amount of time required to change the transmit frequency. (50mS)
#define RADIO_FREQ_CHANGE_TIME 10

// The length of time to scan each receive channel. (300mS)
#define RADIO_SCAN_TIME 60

// The length of time after the DTMF pass through mode is activated before it is disabled.
#define RADIO_DTMF_PASS_THROUGH_PERIOD 24000

// Bit numbers used for radio I/O functions.
// NOTE: Port TX1A is the general I/O connector that is used for PTT with Maxcon radios.
#define RADIO_CARRIER_DETECT 6
#define RADIO_CTCSS_DETECT 7
#define RADIO_TX1 0
#define RADIO_TX2 4

#define RADIO_DATA_RX1 4
#define RADIO_DATA_RX2 5
#define RADIO_AUDIO1 2
#define RADIO_AUDIO2 3

// Operating mode.
#define RADIO_CROSSBAND_MODE 0x10
#define RADIO_CROSSLINK_MODE 0x11

// State of receive radio squelch.
#define RADIO_LOW 0x20
#define RADIO_LOW_HIGH 0x21
#define RADIO_HIGH_LOW 0x22
#define RADIO_HIGH 0x23

// Constants used to fill in tone generator structure.
#define RADIO_TONE_END 0x0000
#define RADIO_TONE387 0x8000
#define RADIO_TONE487 0x4000
#define RADIO_TONE1200 0x2000
#define RADIO_TONE2200 0x6000
#define RADIO_TONE0 0xc000

// The maximum number of DTMF digits accepted for a control message.  4-bits/digit, 32-bit word => 8 digits
#define RADIO_DTMF_BUFFERSIZE 8

// The dash and dot timing are set for 13WPM, the character spacing is set for 6.5WPM
#define RADIO_CODE_DOT RADIO_TONE1200 | 13
#define RADIO_CODE_DASH RADIO_TONE1200 | 39
#define RADIO_CODE_INTER_SYMBOL RADIO_TONE0 | 17
#define RADIO_CODE_CHARACTER_SPACE RADIO_TONE0 | 51

// Audio structure where the upper 3-bits is the tone, and lower 13-bits is the duration in units of 5mS.
const uint16 radioToneTimeOut[] = { RADIO_TONE0 | 75, RADIO_TONE387 | 75, RADIO_TONE487 | 75, RADIO_TONE387 | 75, RADIO_TONE487 | 75, RADIO_TONE387 | 75, RADIO_TONE487 | 75, RADIO_TONE_END };
const uint16 radioToneCourtesy[] = { RADIO_TONE0 | 160, RADIO_TONE2200 | 30, RADIO_TONE1200 | 30, RADIO_TONE0 | 75, RADIO_TONE_END };

// KD7LMO ident (-.-  -..  --...  .-..  --  ---)
const uint16 radioToneID[] = {
    RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DASH, RADIO_CODE_CHARACTER_SPACE,
    RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_CHARACTER_SPACE,
    RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_CHARACTER_SPACE,
    RADIO_CODE_DOT, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DOT, RADIO_CODE_CHARACTER_SPACE,
    RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DASH, RADIO_CODE_CHARACTER_SPACE,
    RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DASH, RADIO_CODE_INTER_SYMBOL, RADIO_CODE_DASH, RADIO_CODE_CHARACTER_SPACE,
    RADIO_TONE_END };

// Local variables
uint8 radioMode, radioLastSquelch, radioTransmit, radioTNCRequest, radioTone, radioAudio, radioTransmitTNCFlag;
uint8 radioLastDTMF, radioDTMFIndex, radioDTMFBuffer[RADIO_DTMF_BUFFERSIZE], radioDTMFReady, radioDTMFPassThrough;
uint8 radioSquelch, radioLastGPSPacketTime, radioAltRxFreq, radioFreqChangeTime, radioCarrierDetectChannel;
uint8 radioCrossLinkHighPower, radioScanChannel, radioToneDetect, radioDebugFlag, radioTLMFlag;
uint16 *radioTonePnt, radioLastIDTime, radioTxTimeOut, radioToneCounter;
uint16 radioLowSquelchTime, radioHighSquelchTime, radioDTMFCountDown, radioScanTime, radioListenTimeOut, radioListenTime;
uint16 radioDTMFPassThruTime;

/**
 *    Decode and process the DTMF digits.  The digits are written to the <b>radioDTMFBuffer</b>
 *    in the <b>radioISR</b> method.  When a command complete digit '#' is received,
 *    the <b>radioDTMFReady</b> flag is set TRUE.
 */
void radioDTMFDecode ()
{
    uint8 i;
    uint16 freq;
    uint32 value;
    char buffer[40];

    // If the DTMF flag hasn't been set, then we don't have a DTMF command.
    if (radioDTMFReady == FALSE)
        return;

    // Convert the DTMF decoder data into a 32-bit value.
    // NOTE 0xa is the number '0', 0xb is the '*', 0xc is the '#'
    value = 0;

    for (i = 0; i < radioDTMFIndex; ++i) {
        value = value << 4;
        value = value | (radioDTMFBuffer[i] & 0x0f);
    }

    // Now decode each of the DTMF commands.
    // NOTE: We should use a switch statement for the decoder, but the Rabbit compiler doesn't support
    // a 32-bit value in the switch.
    if (value == 0x5a) {
        camPanMode = CAM_PAN_DISABLE;
        tlmCreateMessage(">ATV Pan Disabled");
    }

    if (value == 0x55) {
        camPanMode = CAM_PAN_UP_HOR_DOWN;
        camResetTimer();
        tlmCreateMessage(">ATV Auto Pan up/hor/down");
    }

    if (value == 0x51) {
        camPanMode = CAM_PAN_UP_HOR;
        camResetTimer();
        tlmCreateMessage(">ATV Auto Pan up/hor");
    }

    if (value == 0x54) {
        camPanMode = CAM_PAN_HOR_DOWN;
        camResetTimer();
        tlmCreateMessage(">ATV Auto Pan hor/down");
    }

    if (value == 0x57) {
        camPanMode = CAM_PAN_UP_DOWN;
        camResetTimer();
        tlmCreateMessage(">ATV Auto Pan up/down");
    }

    if (value == 0x53) {
        serDputs ("fc up\n\r");
        camPanMode = CAM_PAN_DISABLE;
        tlmCreateMessage(">ATV Pan up");
    }

    if (value == 0x56) {
        serDputs ("fc horz\n\r");
        camPanMode = CAM_PAN_DISABLE;
        tlmCreateMessage(">ATV Pan hor");
    }

    if (value == 0x59) {
        serDputs ("fc down\n\r");
        camPanMode = CAM_PAN_DISABLE;
        tlmCreateMessage(">ATV Pan down");
    }

    if (value == 0x52) {
//        serDputs ("fc out 1 0\n\r");

        buffer[0] = 0x00;
        remoteTransmit (COMMAND_SET_PA, buffer, 1);

        tlmCreateMessage(">ATV Power Off");
    }

    if (value == 0x555) {
        buffer[0] = 0x00;
        remoteTransmit (COMMAND_FOCUS, buffer, 1);

        tlmCreateMessage(">ATV Focus");
    }

    if ((value & 0xfff000) == 0x552000) {
        freq = 2000;
        freq += (uint16) (((value >> 8) & 0x0f) * 100);
        freq += (uint16) (((value >> 4) & 0x0f) * 10);
        freq += (uint16) (value& 0x0f);

        buffer[0] = (freq >> 8) & 0xff;
        buffer[1] = freq & 0xff;

        remoteTransmit (COMMAND_SET_FREQ, buffer, 2);

        sprintf (buffer, ">ATV Freq %dMHz set", freq);
        tlmCreateMessage (buffer);
    } // END if

    if (value == 0x58) {
//        serDputs ("fc out 1 1\n\r");

        buffer[0] = 0xff;
        remoteTransmit (COMMAND_SET_PA, buffer, 1);

        tlmCreateMessage(">ATV Power On");
    }

    if (value == 0x411)
        tlmCreateReport(TLM_LOCAL, TLM_TYPE_STATUS);

    if (value == 0x412)
        tlmCreateReport(TLM_LOCAL, TLM_TYPE_GPGGA);

    if (value == 0x413)
        tlmCreateReport(TLM_LOCAL, TLM_TYPE_GPRMC);

    if (value == 0x414)
        tlmCreateReport(TLM_LOCAL, TLM_TYPE_STATUS | 0x80);

    if (value == 0x471) {
        radioMode = RADIO_CROSSBAND_MODE;
        tlmCreateMessage(">Crossband Mode");
    }

    if (value == 0x472) {
        radioMode = RADIO_CROSSLINK_MODE;
        tlmCreateMessage(">ANSR-EOSS Crosslink Mode");
    }

    if (value == 0x473) {
        radioCrossLinkHighPower = FALSE;
        tlmCreateMessage(">Low Power ANSR-EOSS Crosslink Transmit");
    }

    if (value == 0x474) {
        radioCrossLinkHighPower = TRUE;
        tlmCreateMessage(">High Power ANSR-EOSS Crosslink Transmit");
    }

    if (value == 0x475) {
        radioToneDetect = FALSE;
        tlmCreateMessage(">Disabled CTCSS tone detect");
    }

    if (value == 0x476) {
        radioToneDetect = TRUE;
        tlmCreateMessage(">Enabled CTCSS tone detect");
    }

    if (value >= 0x4771 && value <= 0x4776) {
        radioListenTimeOut = (uint16) (1000 * (value & 0x000f));

        if (radioListenTime > radioListenTimeOut)
            radioListenTime = radioListenTimeOut;

        sprintf (buffer, ">Set %d second listen time", 5 * (value & 0x000f));
        tlmCreateMessage(buffer);
    }

    if (value == 0x4778)
        if (radioDebugFlag == TRUE) {
            radioDebugFlag = FALSE;
            tlmCreateMessage (">Disabled Radio Debug");
        } else {
            radioDebugFlag = TRUE;
            tlmCreateMessage (">Enabled Radio Debug");
        } // END if-else

    if (value == 0x4779)
        if (radioTLMFlag == TRUE) {
            radioTLMFlag = FALSE;
            tlmCreateMessage (">Disabled TLM Display");
        } else {
            radioTLMFlag = TRUE;
            tlmCreateMessage (">Enabled TLM Display");
        } // END if-else

    if (value == 0x478) {
        radioDTMFPassThrough = TRUE;

        // Automatically disable the DTMF pass through mode after 2 minutes.
        radioDTMFPassThruTime = RADIO_DTMF_PASS_THROUGH_PERIOD;

        tlmCreateMessage(">Enabled DTMF pass through");
    }

    if (value == 0x479) {
        radioDTMFPassThrough = FALSE;
        tlmCreateMessage(">Disabled DTMF pass through");
    }

    if (value == 0x0000) {
        // Cut down the balloon.
        remoteCutDown();

        // Send a packet to indicate the cutdown has been activated.
          sprintf (buffer, ">ANSR %ld' peak altitude, cut down", navGetMaxAltitude());
          tlmCreateMessage(buffer);
    }

    if (value == 0x31) {
        // Execute the self test.
        remoteSelfTest();

        // Send a packet to indicate the cutdown has been activated.
        tlmCreateMessage(">ANSR Remote Self Test");
    }

    if (value == 0x0)
        forceSoftReset();

    if (value == 0x0) {
        gpsSendMessage ("Cf", 2);

        // Send a packet to indicate the cutdown has been activated.
        tlmCreateMessage(">GPS Engine Reset");
    }

    if (value == 0x0) {
        // Clear the flight data recorder log.
        logClear();

        logBlockStart (LOG_TYPE_TIMESTAMP);
        logUint8 (gpsPosition.month);
        logUint8 (gpsPosition.day);
        logUint16 (gpsPosition.year);
        logUint8 (gpsPosition.hours);
        logUint8 (gpsPosition.minutes);
        logUint8 (gpsPosition.seconds);

        config.flightTime = 0;
        configCalcCRC();

        // Send a packet to indicate the flight time has been reset.
        tlmCreateReport(TLM_LOCAL, TLM_TYPE_STATUS);

        navLaunch(&gpsPosition);
    }

    // Set the buffer back to the start.
    radioDTMFIndex = 0;

    // Set the flag to indicate we processed the current DTMF buffer.
    radioDTMFReady = FALSE;

}

/**
 *    Initialize the radio subsystem.  The radio subsystem includes transmit control,
 *    carrier detect, and audio routing to the DTMF decoder, TNC MODEM, and radio input.
 */
void radioInit()
{
    // Store the state of the squelch line during the last radio ISR.
    radioLastSquelch = RADIO_LOW;

    // Track when we need to ID.
    radioLastIDTime = 0;

    // Timeout clock for DTMF pass through timer.
    radioDTMFPassThruTime = 0;

    // Flag used to indicate if the transmitter is keyed.
    radioTransmit = FALSE;

    // Flag used to indicate if a tone is currently transmitting.
    radioTone = FALSE;

    // Flag used to indicate if the receive audio is routed.
    radioAudio = FALSE;

    // Flag used to indicate a TNC transmit request has been made.
    radioTNCRequest = FALSE;

    // Flag used to indicate a TNC packet should be send when the input receiver carrier detect goes low.
    radioTransmitTNCFlag = FALSE;

    // Record the time the transmitter has been in repeat mode.
    radioTxTimeOut = RADIO_TX_TIMEOUT;

    // Operation mode - standard cross band operation.
    radioMode = RADIO_CROSSBAND_MODE;

    // Used to record the GPS time in seconds the last packet was sent.
    radioLastGPSPacketTime = 0;

    // Record the time used to change transmit frequencies.
    radioFreqChangeTime = 0;

    // Set the squelch hysterisa timers.
    radioLowSquelchTime = RADIO_SQUELCH_HYST;
    radioHighSquelchTime = RADIO_SQUELCH_HYST;

    // Flag to indicate a DTMF string is ready for processing.
    radioDTMFReady = FALSE;

    // State machine used to one-shot a new DTMF character.
    radioLastDTMF = 0;

    // Index value used to store DTMF digits.
    radioDTMFIndex = 0;

    // Flag to pass DTMF tones through without blocking them.
    radioDTMFPassThrough = FALSE;

    // The scan time counter for each channel in cross link mode.
    radioScanTime = RADIO_SCAN_TIME;

    // The channel to scan.
    radioScanChannel = 0;

    // The channel to use for carrier detect.
    radioCarrierDetectChannel = RADIO_CTCSS_DETECT;

    // Flag to indicate we are using high power on the cross link.
    radioCrossLinkHighPower = FALSE;

    // Flag used to indicate if we should use carrier or CTCSS detect on 147.555 MHz freq.
    radioToneDetect = FALSE;

    // The amount of time to receive in carrier detect mode.
    radioListenTimeOut = 1000;

    // The carrier detect mode receive timer.
    radioListenTime = radioListenTimeOut;

    // Flag to indicate if debug information is shown.
    radioDebugFlag = FALSE;
    radioTLMFlag = FALSE;

    // Program the control lines for default operation.
    BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX_FREQ_CH1);
    BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX_FREQ_CH2);
    BitWrPortI (PDDR, &PDDRShadow, 0, RADIO_RX_FREQ_CH1);

    BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX1);
    BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX2);

    BitWrPortI (PEDR, &PEDRShadow, 0, RADIO_AUDIO1);
    BitWrPortI (PEDR, &PEDRShadow, 0, RADIO_AUDIO2);

    BitWrPortI (PDDR, &PDDRShadow, 0, RADIO_DATA_RX1);
    BitWrPortI (PDDR, &PDDRShadow, 1, RADIO_DATA_RX2);
}

/**
 *   Saves and starts playing the audio structure <b>tone</b>.  The structure contains
 *   the frequency and duration of a tone or silent space.  The tone generator is use
 *   to create a courtesy and status tone or a CW message.
 */
void radioInitTone (uint16 *tone)
{
    radioTone = TRUE;
    radioToneCounter = 1;
    radioTonePnt = (uint16 *) tone;
}

/**
 *   Set the transmitter frequency.
 *
 *   @param mode where mode is RADIO_TX_APRS, RADIO_TX_DOWNLINK
 */
void radioSetTxFreq(uint8 mode)
{
    switch (mode) {
        case RADIO_TX_APRS:
            // Select the APRS frequency.
            BitWrPortI (PADR, &PADRShadow, 1, RADIO_TX_FREQ_CH1);
            BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX_FREQ_CH2);

            // Set the timer to allow the transmitter to change frequencies.
            radioFreqChangeTime = RADIO_FREQ_CHANGE_TIME;
            break;

        case RADIO_TX_DOWNLINK:
            // Select the downlink frequency frequency.
            BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX_FREQ_CH1);
            BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX_FREQ_CH2);

            // Set the timer to allow the transmitter to change frequencies.
            radioFreqChangeTime = RADIO_FREQ_CHANGE_TIME;
            break;

        case RADIO_TX_CROSSBAND_LOW:
            // Select the downlink frequency frequency.
            BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX_FREQ_CH1);
            BitWrPortI (PADR, &PADRShadow, 1, RADIO_TX_FREQ_CH2);

            // Set the timer to allow the transmitter to change frequencies.
            radioFreqChangeTime = RADIO_FREQ_CHANGE_TIME;
            break;

        case RADIO_TX_CROSSBAND_HIGH:
            // Select the downlink frequency frequency.
            BitWrPortI (PADR, &PADRShadow, 1, RADIO_TX_FREQ_CH1);
            BitWrPortI (PADR, &PADRShadow, 1, RADIO_TX_FREQ_CH2);

            // Set the timer to allow the transmitter to change frequencies.
            radioFreqChangeTime = RADIO_FREQ_CHANGE_TIME;
            break;
    } // END switch
}

/**
 *    This method is called on a periodic basis (every 5mS) to process the radio operations.  The
 *    operations include carrier detect, transmit timeout, and DTMF decoding.
 */
void radioUpdate()
{
    uint8 radioDTMFDigit;

    // Set the value of radioSquelch based on the current mode and state of the receive channel.
    switch (radioMode) {
        case RADIO_CROSSBAND_MODE:
            // The CTCSS decoder output is not very stable, so debounce the output.
            if (BitRdPortI(PEDR, RADIO_CTCSS_DETECT) == 0) {
                radioHighSquelchTime = RADIO_SQUELCH_HYST;

                if (radioLastSquelch == 0)
                    radioSquelch = RADIO_LOW;
                else {
                    if (--radioLowSquelchTime == 0) {
                        radioLastSquelch = 0;
                        radioSquelch = RADIO_HIGH_LOW;
                    } // END if

                }
            } else {
                radioLowSquelchTime = RADIO_SQUELCH_HYST;

                if (radioLastSquelch == 1)
                    radioSquelch = RADIO_HIGH;
                else {
                    if (--radioHighSquelchTime == 0) {
                        radioLastSquelch = 1;
                        radioSquelch = RADIO_LOW_HIGH;
                    } // END if
                }
            } // END if-else
            break;

        case RADIO_CROSSLINK_MODE:
            // The CTCSS decoder / carrier detect output is not very stable, so debounce it.
            if (BitRdPortI(PEDR, radioCarrierDetectChannel) == 0) {
                radioHighSquelchTime = RADIO_SQUELCH_HYST;

                if (radioLastSquelch == 0)
                    radioSquelch = RADIO_LOW;
                else {
                    if (--radioLowSquelchTime == 0) {
                        radioLastSquelch = 0;
                        radioSquelch = RADIO_HIGH_LOW;
                    } // END if

                }
            } else {
                radioLowSquelchTime = RADIO_SQUELCH_HYST;

                if (radioLastSquelch == 1)
                    radioSquelch = RADIO_HIGH;
                else {
                    if (--radioHighSquelchTime == 0) {
                        radioLastSquelch = 1;
                        radioSquelch = RADIO_LOW_HIGH;
                    } // END if
                }
            } // END if-else

            // Reset the scan time counter if we detect any signal.
            if (BitRdPortI(PEDR, radioCarrierDetectChannel) == 1)
                radioScanTime = RADIO_SCAN_TIME;

            // Select the other channel periodically to check for an incoming signal.
            if (radioSquelch == RADIO_HIGH && radioScanChannel == 1) {
                if (--radioListenTime == 0) {
                    radioListenTime = radioListenTimeOut;
                    radioScanTime = 1;
                    radioLastSquelch = 0;
                } // END if
            } else
                radioListenTime = radioListenTimeOut;

            // When the scan time ends, switch frequencies.  ScanChannel 0: 145.560, 1: 147.555
            if (--radioScanTime == 0) {
                radioScanTime = RADIO_SCAN_TIME;

                if (radioScanChannel == 0) {
                    radioScanChannel = 1;

                    if (radioToneDetect == TRUE)
                        radioCarrierDetectChannel = RADIO_CTCSS_DETECT;
                    else
                        radioCarrierDetectChannel = RADIO_CARRIER_DETECT;

                    // Listen on the 147.555 MHz freq.
                    BitWrPortI (PDDR, &PDDRShadow, 1, RADIO_RX_FREQ_CH1);
                } else {
                    radioScanChannel = 0;
                    radioCarrierDetectChannel = RADIO_CTCSS_DETECT;

                    // Listen on the 145.560 MHz freq.
                    BitWrPortI (PDDR, &PDDRShadow, 0, RADIO_RX_FREQ_CH1);
                }
            } // END if-else
            break;

    } // END switch

    // Turn on the transmitter if the receive radio squelch is opened.
    if (radioSquelch == RADIO_LOW_HIGH) {
        radioTransmit = TRUE;

        // Select the desired output device.
        if (radioMode == RADIO_CROSSLINK_MODE)
            if (radioScanChannel == 1)
                radioSetTxFreq (RADIO_TX_DOWNLINK);
            else {
                if (radioCrossLinkHighPower == FALSE)
                    radioSetTxFreq (RADIO_TX_CROSSBAND_LOW);
                else
                    radioSetTxFreq (RADIO_TX_CROSSBAND_HIGH);
            } // else

        // If the ID timer has expired, reset it the first time we use the radio.
        if (radioLastIDTime == 0)
            radioLastIDTime = RADIO_ID_TIME;
    } // END if


    // Initiate the courtesy tone or ID if the radio squelch is closed.
    if (radioSquelch == RADIO_HIGH_LOW) {
        if (radioTransmitTNCFlag == TRUE) {
            radioTransmitTNCFlag = FALSE;
            radioTNCRequest = TRUE;
            tncTransmit = TRUE;
        } // END if

        if (radioTxTimeOut != 0 && tncTransmit == FALSE) {
            if (radioLastIDTime == 0)
                radioInitTone ((uint16 *) radioToneID);
            else
                radioInitTone ((uint16 *) radioToneCourtesy);
        } // END if

        // Clear the DTMF buffer when the radio breaks.
        radioDTMFIndex = 0;

    } // END if

    // If we get a TNC request flag and we aren't in receive mode, send the packet.
    if (radioTransmitTNCFlag == TRUE && radioSquelch == RADIO_LOW && tncTransmit == FALSE && camDownloadMode == FALSE) {
        radioTransmitTNCFlag = FALSE;
        radioTNCRequest = TRUE;
        tncTransmit = TRUE;
    }

    // If we are in tone mode, update the tone duration/frequency.
    if (radioTone == TRUE)
        radioUpdateTone();

    // Turn off the transmitter when we aren't repeating, if we transmitted too long, or if we aren't sending a tone or packet.
    if ((radioSquelch == RADIO_LOW || radioTxTimeOut == 0) && radioTransmit == TRUE && radioTone == FALSE && tncTransmit == FALSE)
        radioTransmit = FALSE;

    // Route the receive audio if we are transmitting and not sending a packet.
    if (radioSquelch == RADIO_HIGH && radioTransmit == TRUE && tncTransmit == FALSE)
        radioAudio = TRUE;
    else
        radioAudio = FALSE;

    // If we repeat too long, then transmit a disconnect tone and turn off the transmitter.
    if (radioSquelch == RADIO_LOW)
        radioTxTimeOut = RADIO_TX_TIMEOUT;
    else {
        if (radioTxTimeOut != 0)
            --radioTxTimeOut;

        if (radioTxTimeOut == 0 && radioTone == FALSE && tncTransmit == FALSE && radioTransmit == TRUE)
            radioInitTone ((uint16 *) radioToneTimeOut);
    } // END if-else

    // Transmit a packet at 10, 20, 40, and 50 seconds if we aren't in cross link mode.
    if (radioMode == RADIO_CROSSBAND_MODE && radioTransmit == FALSE && tncTransmit == FALSE)
        if (gpsPosition.seconds == 10 || gpsPosition.seconds == 20 || gpsPosition.seconds == 40 || gpsPosition.seconds == 50)
            if (gpsPosition.seconds != radioLastGPSPacketTime) {
                // Record the time of the last packet so we don't get duplicates if it takes less than a second.
                radioLastGPSPacketTime = gpsPosition.seconds;

                // Select the transmitter frequency.
                radioSetTxFreq (RADIO_TX_APRS);

                // Set a flag to turn on the transmitter.
                radioTransmit = TRUE;

                // Set a flag to indicate we are sending a packet.
                tncTransmit = TRUE;

                // Set a flag to tell the main loop to generate a TNC packet.
                radioTNCRequest = TRUE;
            } // END if

    // Transmit an estimate packet at 55 seconds if we aren't in cross link mode.
    if (radioMode == RADIO_CROSSBAND_MODE && radioTransmit == FALSE && tncTransmit == FALSE)
        if (burstDetect == TRUE)
            if (gpsPosition.seconds == 15 || gpsPosition.seconds == 45)
                if (gpsPosition.seconds != radioLastGPSPacketTime) {
                    // Record the time of the last packet so we don't get duplicates if it takes less than a second.
                    radioLastGPSPacketTime = gpsPosition.seconds;

                    // Select the transmitter frequency.
                    radioSetTxFreq (RADIO_TX_APRS);

                    // Set a flag to indicate we are sending a packet.
                    tncTransmit = TRUE;

                    // Set the upper bit to indicate we want an estimate packet.
                    tlmSetEstimatePacket();

                    // Set a flag to tell the main loop to generate a TNC packet.
                    radioTNCRequest = TRUE;
                } // END if

    // Count down our ID timer.
    if (tncTransmit == FALSE && radioLastIDTime != 0) {
        --radioLastIDTime;

        if (radioLastIDTime == 0 && radioTransmit == FALSE) {
            radioTransmit = TRUE;
            radioInitTone ((uint16 *) radioToneID);
        }
    } // END if

    // Process the DTMF decoder
    if (BitRdPortI (PBDR, 1) == 0)
        radioLastDTMF = 0;
    else {
        // Handle a new DTMF value.
        if (radioLastDTMF == 0) {
            radioLastDTMF = 1;

            // Decode the new character.
            radioDTMFDigit = (RdPortI(PBDR) >> 2) & 0x0f;

            // If the '#' key was pressed and we have one other digit process the command.
            if (radioDTMFDigit == 0x0c && radioDTMFIndex != 0)
                radioDTMFReady = TRUE;

            // If this is the first character, start a timer that only allows so much time for a message.
            if (radioDTMFIndex == 0)
                radioDTMFCountDown = RADIO_DTMF_PERIOD;

            // Save the new digit if the buffer isn't full and we don't have a command pending.
            if (radioDTMFDigit != 0x0c && radioDTMFReady == FALSE && radioDTMFIndex != RADIO_DTMF_BUFFERSIZE)
                radioDTMFBuffer[radioDTMFIndex++] = radioDTMFDigit;

        } // END if
    } // END else

    // As soon as we start getting DTMF tones, turn off the audio feed through.
    if (radioDTMFIndex != 0 && radioDTMFPassThrough == FALSE)
        radioAudio = FALSE;

    // Keep track of the time we need to change the transmit frequency.
    if (radioFreqChangeTime != 0)
        --radioFreqChangeTime;

    // Based on the cross band mode, select the proper hardware channels.
    switch (radioMode) {
        case RADIO_CROSSBAND_MODE:
            if (radioFreqChangeTime == 0)
                BitWrPortI (PADR, &PADRShadow, radioTransmit, RADIO_TX1);
            else
                BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX1);

            BitWrPortI (PEDR, &PEDRShadow, radioAudio, RADIO_AUDIO2);
            break;

        case RADIO_CROSSLINK_MODE:
            if (radioFreqChangeTime == 0)
                BitWrPortI (PADR, &PADRShadow, radioTransmit, RADIO_TX1);
            else
                BitWrPortI (PADR, &PADRShadow, 0, RADIO_TX1);

            if (radioScanChannel == 0) {
                BitWrPortI (PEDR, &PEDRShadow, 0, RADIO_AUDIO1);
                BitWrPortI (PEDR, &PEDRShadow, radioAudio, RADIO_AUDIO2);
            } else {
                if (radioToneDetect == FALSE) {
                    BitWrPortI (PEDR, &PEDRShadow, radioAudio, RADIO_AUDIO1);
                    BitWrPortI (PEDR, &PEDRShadow, 0, RADIO_AUDIO2);
                } else {
                    BitWrPortI (PEDR, &PEDRShadow, 0, RADIO_AUDIO1);
                    BitWrPortI (PEDR, &PEDRShadow, radioAudio, RADIO_AUDIO2);
                }   // END if-else

            }  // END if-else

            break;
    } // END switch

    // If the timeout period is reached, then start over.
    if (radioDTMFCountDown != 0)
        --radioDTMFCountDown;
    else
        if (radioDTMFReady == FALSE)
            radioDTMFIndex = 0;

    // Disable DTMF pass through mode after a period of time.
    if (radioDTMFPassThruTime != 0)
        if (--radioDTMFPassThruTime == 0)
            radioDTMFPassThrough = FALSE;
}

/**
 *    This method is called on a periodic basis to set an audio tone based on
 *    the audio structure.  The structure is saved in the <b>radioInitTone</b>
 *    method.
 */
void radioUpdateTone()
{
    // Decrement the tone counter until we get to 0 and then set a new tone.
    if (--radioToneCounter == 0) {
        // The upper 3-bits determine the tone and the lower 13-bits are the duration in units of 5mS.
        switch (*radioTonePnt & 0xe000) {
            case RADIO_TONE_END:
                radioTone = FALSE;
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_DATA);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M0);
                BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M1);
                break;

            case RADIO_TONE0:
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_DATA);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M0);
                BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M1);
                break;

            case RADIO_TONE387:
                BitWrPortI (PDDR, &PDDRShadow, 1, TNC_DATA);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M0);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M1);
                break;

            case RADIO_TONE487:
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_DATA);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M0);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M1);
                break;

            case RADIO_TONE1200:
                BitWrPortI (PDDR, &PDDRShadow, 1, TNC_DATA);
                BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M0);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M1);
                break;

            case RADIO_TONE2200:
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_DATA);
                BitWrPortI (PDDR, &PDDRShadow, 1, TNC_M0);
                BitWrPortI (PDDR, &PDDRShadow, 0, TNC_M1);
                break;
        } // END switch

        // Get the duration this tone will play in units of 5mS.
        radioToneCounter = (*radioTonePnt++ & 0x1fff);
    }
}

/**
 *   Class to handle digital temperature sensors.
 */

// Define the I2C clock/data lines.
#define TEMP_CLK 7
#define TEMP_DATA 6

#define tempClockHigh() BitWrPortI(PDDDR, &PDDDRShadow, 0, TEMP_CLK)
#define tempClockLow() BitWrPortI(PDDDR, &PDDDRShadow, 1, TEMP_CLK)
#define tempClock()   BitRdPortI(PDDR, TEMP_CLK)

#define tempDataHigh() BitWrPortI(PDDDR, &PDDDRShadow, 0, TEMP_DATA)
#define tempDataLow() BitWrPortI(PDDDR, &PDDDRShadow, 1, TEMP_DATA)
#define tempData()   BitRdPortI(PDDR, TEMP_DATA)

/**
 *   Get the digital temperature sensor reading in units of 0.1 degrees F.
 *
 *   @return signed temperature value
 */
int16 tempReadTemp(uint8 channel)
{
    uint8 returnCode, retryCount;
    int32 temp;

    retryCount = 0;

    while (retryCount < 5) {
        tempMasterStart();
        returnCode = tempWriteAndGetAck (channel);

        temp = tempReadWithAck (TRUE) << 8;
        temp = temp | tempReadWithAck (FALSE);

        tempMasterStop();

        if (returnCode == 0)
            return (int16) ((temp * 9 / 64) + 320);

        ++retryCount;
    }

    return 0;
}

/**
 *   Get the digital temperature sensor reading in units of 0.1 degrees F.
 *
 *   @return signed temperature value
 */
int16 tempGetExtTemp()
{
    return tempReadTemp (0x91);
}

/**
 *   Get the digital temperature sensor reading in units of 0.1 degrees F.
 *
 *   @return signed temperature value
 */
int16 tempGetIntTemp()
{
    return tempReadTemp (0x95);
}

/**
 *   Initialize the digital temperarture sensor.
 */
void tempInit()
{
    uint8 i;

    tempDataHigh();
    tempClockLow();

    for (i = 0; i < 4; ++i)
        tempMasterStop();

}

/**
 *   Send the I2C start sequence.
 */
void tempMasterStart()
{
    tempClockHigh();
    tempDataHigh();
    tempDataLow();
    tempClockLow();
    tempDataHigh();
}

/**
 *   Send the I2C stop sequence.
 */
void tempMasterStop()
{
    tempDataLow();
    tempClockHigh();
    tempDataHigh();
}

/**
 *   Send a data byte and read the slave ACK status.
 *
 *   @param data byte value to send
 *
 *   @return slave ACK state 1 or 0
 */
uint8 tempWriteAndGetAck(uint8 data)
{
    uint8 i, returnState;

    // Bit bang out each bit, MSB first.
    for (i = 0; i < 8; ++i) {
        if ((data & 0x80) == 0x80)
            tempDataHigh();
        else
            tempDataLow();

        tempClockHigh();
        tempClockLow();


        data = data << 1;
    } // END for

    tempDataHigh();
    tempClockHigh();

    returnState = tempData();

    tempClockLow();

    return returnState;
}

/**
 *   Read the I2C slave device and set the optional ACK bit.
 *
 *   @param if true, set the ACK after reading the data
 *
 *   @return value read from I2C slave
 */
uint8 tempReadWithAck (uint8 ack)
{
    uint8 i, value;

    value = 0;

    for (i = 0; i < 8; ++i) {
        value = value << 1;

        tempClockHigh();
        value |= (tempData() & 0x01);
        tempClockLow();

    } // END for

    tempDataHigh();

    if (ack) {
        tempDataLow();
        tempClockHigh();
        tempClockLow();
        tempDataHigh();
    } // END if

    return value;
}

/**
 *   Class to handle telemetry.
 */
uint8 tlmReportString[80];
uint8 tlmReportType, tlmPositionReportType, tlmPacketType;

/**
 *   Create a telemetry message that will be sent when the transmitter is clear.
 *
 *   @param message string to send
 */
void tlmCreateMessage(uint8 *message)
{
    // Save a copy of the message.
    strncpy (tlmReportString, message, sizeof(tlmReportString));
    tlmReportString[sizeof(tlmReportString) - 1] = 0;

    // Set the internal state machine to transmit when the input carrier detect is clear.
    tlmReportType = TLM_MESSAGE;
    tlmPacketType = TLM_TYPE_STATUS;
    radioTransmitTNCFlag = TRUE;
}

/**
 *   Create a telemetry report that will be sent when the transmitter is clear.
 *
 *   @param reportType telemetry report, i.e. TLM_LOCAL, TLM_GPS, etc.
 */
void tlmCreateReport(uint8 reportType, uint8 packetType)
{
    // Set the internal state machine to transmit when the input carrier detect is clear.
    tlmReportType = reportType;
    tlmPacketType = packetType;
    radioTransmitTNCFlag = TRUE;
}

/**
 *   Create a landing estimate packet.
 */
void tlmLandingEstimate()
{
    float coord;
    uint8 dirFlag, buffer[80], message[80];
    uint32 coordMin;
    COORD landingZone;

    // Calculate the landing zone.
    if (!navUpdate(&gpsPosition, &landingZone)) {
        radioTransmit = TRUE;
        tncSendPacket (">ANSR Unable to compute landing zone");
        return;
    }

    // Generate the GPGGA message.
    sprintf (message, "$GPGGA,%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds);

    // Set a flag to turn on the transmitter after we have calculated the landing zone.
    radioTransmit = TRUE;

    // Convert lat/lon to degrees.
    navRadToDeg (&landingZone);

    if (landingZone.lat < 0) {
        coord = -landingZone.lat;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = landingZone.lat;
    }

    coordMin = (uint32) ((coord - floor(coord)) * 600000.0);
    sprintf (buffer, "%d%02ld.%04ld,", (uint16) coord, (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (message, buffer);

    if (dirFlag == 1)
        strcat (message, "N,");
    else
        strcat (message, "S,");

    // Longitude value.
    if (landingZone.lon < 0) {
        coord = -landingZone.lon;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = landingZone.lon;
    }

    coordMin = (uint32) ((coord - floor(coord)) * 600000.0);
    sprintf (buffer, "%d%02ld.%04ld,", (uint16) coord, (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (message, buffer);

    if (dirFlag == 1)
        strcat (message, "E,");
    else
        strcat (message, "W,");

    // GPS status where 0: not available, 1: available
    if ((gpsPosition.status & 0x8000) == 0x8000)
        strcat (message, "1,");
    else
        strcat (message, "0,");

    // Number of visibile birds.
    sprintf (buffer, "%02d,", gpsPosition.visibleSats);
    strcat (message, buffer);

    // DOP
    sprintf (buffer, "%d.%01d,", gpsPosition.dop / 10, gpsPosition.dop % 10);
    strcat (message, buffer);

    // Altitude in meters.
    sprintf (buffer, "%ld.0,M,,M,,", (int32) (gpsPosition.altitudeMeters / 100l));
    strcat (message, buffer);

    // Checksum, we add 1 to skip over the $ character.
    sprintf (buffer, "*%02X", sysNMEAChecksum(message + 1, strlen(message) - 1));
    strcat (message, buffer);

    // Set the SSID of the landing zone.
    tncSetAlternateSSID();
    tncSendPacket (message);
}

/**
 *    Create a NMEA $GPGGA packet and send it on the TNC.
 */
void tlmGPGGAPacket()
{
    uint32 coord, coordMin;
    uint8 dirFlag, buffer[80], message[80];

    // Generate the GPGGA message.
    sprintf (message, "$GPGGA,");

    // UTC is replaced with flight time
    sprintf (buffer, "%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds);
    strcat (message, buffer);

    // Latitude value.
    coord = gpsPosition.latitude;

    if (gpsPosition.latitude < 0) {
        coord = gpsPosition.latitude * -1;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = gpsPosition.latitude;
    }

    coordMin = (coord % 3600000) / 6;
    sprintf (buffer, "%02ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (message, buffer);

    if (dirFlag == 1)
        strcat (message, "N,");
    else
        strcat (message, "S,");

    // Longitude value.
    if (gpsPosition.longitude < 0) {
        coord = gpsPosition.longitude * - 1;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = gpsPosition.longitude;
    }

    coordMin = (coord % 3600000) / 6;
    sprintf (buffer, "%03ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (message, buffer);

    if (dirFlag == 1)
        strcat (message, "E,");
    else
        strcat (message, "W,");

    // GPS status where 0: not available, 1: available
    if ((gpsPosition.status & 0x8000) == 0x8000)
        strcat (message, "1,");
    else
        strcat (message, "0,");

    // Number of visibile birds.
    sprintf (buffer, "%02d,", gpsPosition.visibleSats);
    strcat (message, buffer);

    // DOP
    sprintf (buffer, "%d.%01d,", gpsPosition.dop / 10, gpsPosition.dop % 10);
    strcat (message, buffer);

    // Altitude in meters.
    sprintf (buffer, "%ld.0,M,,M,,", (int32) (gpsPosition.altitudeMeters / 100l));
    strcat (message, buffer);

    // Checksum, we add 1 to skip over the $ character.
    sprintf (buffer, "*%02X", sysNMEAChecksum(message + 1, strlen(message) - 1));
    strcat (message, buffer);

    tncSendPacket (message);
}

/**
 *    Create a NMEA $GPRMC packet and send it on the TNC.
 */
void tlmGPRMCPacket()
{
    uint32 coord, coordMin, temp;
    uint8 dirFlag, buffer[80], message[80];

    // Generate the GPRMC message.
    sprintf (message, "$GPRMC,");

    // UTC is replaced with flight time
    sprintf (buffer, "%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds);
    strcat (message, buffer);

    // GPS status.
    if ((gpsPosition.status & 0x8000) == 0x8000)
        strcat (message, "A,");
    else
        strcat (message, "V,");

    // Latitude value.
    coord = gpsPosition.latitude;

    if (gpsPosition.latitude < 0) {
        coord = gpsPosition.latitude * -1;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = gpsPosition.latitude;
    }

    coordMin = (coord % 3600000) / 6;
    sprintf (buffer, "%02ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (message, buffer);

    if (dirFlag == 1)
        strcat (message, "N,");
    else
        strcat (message, "S,");

    // Longitude value.
    if (gpsPosition.longitude < 0) {
        coord = gpsPosition.longitude * - 1;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = gpsPosition.longitude;
    }

    coordMin = (coord % 3600000) / 6;
    sprintf (buffer, "%03ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (message, buffer);

    if (dirFlag == 1)
        strcat (message, "E,");
    else
        strcat (message, "W,");

    // Speed knots and heading.
    temp = (int32) gpsPosition.hSpeed * 75000 / 385826;
    sprintf (buffer, "%d.%d,%d.%d,", (int16) (temp / 10), (int16) (temp % 10), gpsPosition.heading / 10, gpsPosition.heading % 10);
    strcat (message, buffer);

    // Date
    sprintf (buffer, "%02d%02d%02d,,", gpsPosition.day, gpsPosition.month, gpsPosition.year % 100);
    strcat (message, buffer);

    // Checksum, skip over the $ character.
    sprintf (buffer, "*%02X", sysNMEAChecksum(message + 1, strlen(message) - 1));
    strcat (message, buffer);

    tncSendPacket (message);
}

/**
 *    Initialize the telemetry subsystem.
 */
void tlmInit()
{
    // Select the default TNC and NMEA reports.
    tlmReportType = TLM_LOCAL;
    tlmPositionReportType = TLM_NMEA;
    tlmPacketType = TLM_TYPE_STATUS;
    strcpy (tlmReportString, "");
}

/**
 *   Standard telemetry status packet.
 */
void tlmLocal()
{
    int16 temp;
    uint8 buffer[100], message[100];

    // Status message header.
    sprintf (message, ">ANSR ");

    // Current flight time.
    sprintf (buffer, "%02d:%02d:%02d ", config.flightTime / 3600, (config.flightTime / 60) % 60, config.flightTime % 60);
    strcat (message, buffer);

    // Show GPS information if it is alive, otherwise show blank fields.
    if ((gpsPosition.status & 0xe000) == 0xe000) {
        sprintf (buffer, "%ld' %ld'/min ", gpsPosition.altitudeFeet, (int32) (gpsPosition.vSpeedAccum * 6000l / 12192l));
        strcat (message, buffer);
    } else
        strcat (message, "---' ---'/min ");

    // GPS status
    if ((gpsPosition.status & 0xe000) == 0xe000){
        sprintf (buffer, "%d.%dpdop ", gpsPosition.dop / 10, gpsPosition.dop % 10);
        strcat (message, buffer);
    } else
        if ((gpsPosition.status & 0xe000) == 0xc000) {
            sprintf (buffer, "%d.%dhdop ", gpsPosition.dop / 10, gpsPosition.dop % 10);
            strcat (message, buffer);
    } else
        strcat (message, "nogps ");

    // Payload computer bus voltage
    temp = analogGetBusVoltage();
    sprintf (buffer, "%d.%dV ", temp / 10, temp % 10);
    strcat (message, buffer);

    // Internal temp for digital sensor
    temp = tempGetIntTemp();
    sprintf (buffer, "%d.%dF ", temp / 10, abs(temp % 10));
    strcat (message, buffer);

    // Internal temp for analog sensor
    temp = analogGetIntTemp();
    sprintf (buffer, "%d.%dF ", temp / 10, abs(temp % 10));
    strcat (message, buffer);

    // External temp
    temp = tempGetExtTemp();
    sprintf (buffer, "%d.%dF ", temp / 10, abs(temp % 10));
    strcat (message, buffer);

    // Static preasure
//    temp = analogGetStatic();
//    sprintf (buffer, "%d.%02dpsi ", temp / 100, temp % 100);
//    strcat (message, buffer);

    // Camera computer bus voltage
//    sprintf (buffer, "%.1fV ", (float) camAnalog1 / 47.627907);
//    strcat (message, buffer);

    // Internal camera temp
//    sprintf (buffer, "%.0fF ", (double) camAnalog2 / 5.613384);
//    strcat (message, buffer);

//    sprintf (buffer, "%d", navGetWindDataCount());
//    strcat (message, buffer);

    // Report the information.
    tncSendPacket (message);
}

/**
 *   Display low-level telemetry information from the GPS receiver.
 */
void tlmGPSStatus()
{
    uint8 message[100], buffer[100];

    // Date stamp.
    sprintf (message, "%02d/%02d/%04d ", gpsPosition.month, gpsPosition.day, gpsPosition.year);

    // Time stamp.
    sprintf (buffer, "%02d:%02d:%02d ", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds);
    strcat (message, buffer);

    // Show our position in the world.
    sprintf (buffer, "%ld %ld %ld ", gpsPosition.latitude, gpsPosition.longitude, gpsPosition.altitudeFeet);
    strcat (message, buffer);

    // Show our speed.
    sprintf (buffer, "%d %d ", gpsPosition.hSpeed, gpsPosition.vSpeedAccum);
    strcat (message, buffer);

    // GPS receiver status.
    sprintf (buffer, "%d %d %03d %x", gpsPosition.visibleSats, gpsPosition.trackedSats, gpsPosition.dop, gpsPosition.status);
    strcat (message, buffer);

    tncSendPacket (message);
}

/**
 *   Send the user readable hex data stream of a TNC message to the NMEA serial port.
 */
void tlmPacketReport()
{
    uint8 i, buffer[10];

    serDputs (tncPacket.sourceCallSign);
    serDputs (" > ");
    serDputs (tncPacket.destCallSign);
    serDputs (" msg: ");
    serDputs (tncPacket.message);
    serDputs ("\n\rbin: ");

    for (i = 0; i < tncLength; ++i) {
        sprintf (buffer, "%02x ", tncBuffer[i]);
        serDputs (buffer);

        if ((i % 24) == 23)
            serDputs ("\n\r");
    }

    serDputs ("\n\r");
}

void tlmSetEstimatePacket()
{
    tlmPacketType |= 0x80;
}


/**
 *   NMEA GPS position report.
 */
void tlmPositionReport()
{
    uint32 coord, coordMin, temp;
    uint8 dirFlag, buffer[80], message[80], latLong[40];

    // Generate latitude information that is used in both NMEA messages.
    // Latitude value.
    coord = gpsPosition.latitude;

    if (gpsPosition.latitude < 0) {
        coord = gpsPosition.latitude * -1;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = gpsPosition.latitude;
    }

    coordMin = (coord % 3600000) / 6;
    sprintf (latLong, "%02ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));

    if (dirFlag == 1)
        strcat (latLong, "N,");
    else
        strcat (latLong, "S,");

    // Longitude value.
    if (gpsPosition.longitude < 0) {
        coord = gpsPosition.longitude * - 1;
        dirFlag = 0;
    } else {
        dirFlag = 1;
        coord = gpsPosition.longitude;
    }

    coordMin = (coord % 3600000) / 6;
    sprintf (buffer, "%03ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000));
    strcat (latLong, buffer);

    if (dirFlag == 1)
        strcat (latLong, "E,");
    else
        strcat (latLong, "W,");

    if ((config.flightTime & 0x01) == 0x00) {
        // Generate the GPGGA message.
        sprintf (message, "GPGGA,");

        // UTC is replaced with flight time
        sprintf (buffer, "%02d%02d%02d,", config.flightTime / 3600, (config.flightTime / 60) % 60, config.flightTime % 60);
        strcat (message, buffer);

        // Add in the lat, and long information.
        strcat (message, latLong);

        // Always indicate the status is valid for the ATV overlay.
        strcat (message, "1,");

        // Number of visibile birds.
        sprintf (buffer, "%02d,", gpsPosition.visibleSats);
        strcat (message, buffer);

        // DOP
        sprintf (buffer, "%d.%01d,", gpsPosition.dop / 10, gpsPosition.dop % 10);
        strcat (message, buffer);

        sprintf (buffer, "%ld.0,M,,M,,", gpsPosition.altitudeFeet);
        strcat (message, buffer);

        sprintf (buffer, "*%02X", sysNMEAChecksum(message, strlen(message)));
        strcat (message, buffer);

        serDputc ('$');
        serDputs (message);
        serDputs ("\n\r");
    }


    if ((config.flightTime & 0x01) == 0x01) {
        // Generate the GPRMC message.
        sprintf (message, "GPRMC,");

        // UTC is replaced with flight time
        sprintf (buffer, "%02d%02d%02d,", config.flightTime / 3600, (config.flightTime / 60) % 60, config.flightTime % 60);
        strcat (message, buffer);

        // Always indicate the status is valid for the ATV overlay.
        strcat (message, "A,");

        // Add in the lat/long information.
        strcat (message, latLong);

        // Speed (MPH) and heading.
        temp = (int32) gpsPosition.hSpeed * 15000 / 67056;
        sprintf (buffer, "%d.%d,%d.%d,", (int16) (temp / 10), (int16) (temp % 10), gpsPosition.heading / 10, gpsPosition.heading % 10);
        strcat (message, buffer);

        // Date
        sprintf (buffer, "%02d%02d%02d,,", gpsPosition.day, gpsPosition.month, gpsPosition.year % 100);
        strcat (message, buffer);

        sprintf (buffer, "*%02X", sysNMEAChecksum(message, strlen(message)));
        strcat (message, buffer);

        serDputc ('$');
        serDputs (message);
        serDputs ("\n\r");
    }
}

/**
 *   Create the telemetry status packet and transmit it via the RF link.
 */
void tlmReport()
{
    // Select the telemetry stream based on the desired report type.
    switch (tlmReportType) {
        // Standard report.
        case TLM_LOCAL:
            tlmLocal();
            break;

        // Data stream from camera controller.
        case TLM_CAM_COMPUTER:
            tncSendPacket (camOutBuffer);
            break;

        // Telemetry diagnostic information.
        case TLM_GPS:
            tlmGPSStatus();
            break;

        case TLM_STATUS:
            tlmStatus();
            break;

        case TLM_VER:
            tncSendPacket (sysGetVersion());
            tlmReportType = TLM_LOCAL;
            break;

        case TLM_MESSAGE:
            tncSendPacket (tlmReportString);
            tlmReportType = TLM_LOCAL;
            break;

    } // END switch
}

/**
 *   Send the telemetry status report on the TNC.
 */
void tlmStatus()
{
    uint8 buffer[80], message[80];

    // Boot counter.
    sprintf (message, "Boot: %d  ", config.bootCount);

    // Packet counters.
    sprintf (buffer, "Tx: 0x%04x  Rx: 0x%04x  Rx-PC:0x%04x", tncTxPacketCount, tncRxPacketCount, tncProcPacketCount);
    strcat (message, buffer);

    tncSendPacket (message);
}

/**
 *   Update the telemetry measurands.  This routine is called once a second.
 */
void tlmUpdate()
{
    // Update the flight time
    ++config.flightTime;
    configCalcCRC();
}

/**
 *   Class to process TNC commands.
 */
// The maximum number of tokens or entries in a TNC command.
#define CMD_MAX_TOKENS 5

// An array of indexes that points to each token in a string.
uint8 cmdBuffer[TNC_MAX_MESSAGE];

/**
 *   This routine takes the contents of the cmdBuffer[] and parses the commands.
 *   The cmdBuffer[] is filled by the serial ISR.  This routine was modeled after
 *   Java's StringTokenizer class.  Tokens are delimted by a space, tab, or comma.
 *
 *   @return true if command was valid and processed; otherwise false
 */
uint8 cmdProcess()
{
    uint8 token[CMD_MAX_TOKENS];
    uint8 i, tokenCount;

    // Get everything set.
    i = 0;
    tokenCount = 0;

    // We are going to fill the token[] with the index of each token in the command buffer,
    // NULL terminate the token(s), and return the number of tokens in tokenCount.
    while (cmdBuffer[i] != 0) {
        // Skip over the white space to the start of the token.
        while (cmdBuffer[i] == ' ' || cmdBuffer[i] == ',' || cmdBuffer[i] == 9)
            ++i;

        // If we aren't at the end of the command, set this token.
        if (cmdBuffer[i] != 0)
            if (tokenCount != CMD_MAX_TOKENS)
                token[tokenCount++] = i;
            else
                return FALSE;

        // Find the end of the token.
        while (cmdBuffer[i] != ' ' && cmdBuffer[i] != ',' && cmdBuffer[i] != 9 && cmdBuffer[i] != 0) {
            cmdBuffer[i] = tolower(cmdBuffer[i]);
            ++i;
        } // END while


        // If we aren't at the end of the line, terminate the token.
        if (cmdBuffer[i] != 0)
            cmdBuffer[i++] = 0;
    }

    // We need at least two tokens.  (Header & Mnemonic)
    if (tokenCount < 2)
        return FALSE;

    // Check for a valid header.
    if (strcmp(cmdBuffer + token[0], "fc") != 0)
        return FALSE;

    // Process the commands that only have a mnemonic.

    // Display firmware version.  (fc ver)
    if (strcmp(cmdBuffer + token[1], "ver") == 0) {
        tlmCreateReport (TLM_VER, TLM_TYPE_STATUS);
        return TRUE;
    }

    // Reset the processor. (fc reset)
    if (strcmp(cmdBuffer + token[1], "reset") == 0)
        forceSoftReset();

    // Proces the commands that have one operand.
    // We need at least three tokens.  (Header, Mnemonic, and Operand)
    if (tokenCount < 3)
        return FALSE;


    // Control the flight timer (fc time reset)
    if (strcmp (cmdBuffer + token[1], "time") == 0)
        if (strcmp (cmdBuffer + token[2], "reset") == 0) {
            logIndex = 0;
            logFlag = TRUE;

            logBlockStart (LOG_TYPE_TIMESTAMP);
            logUint8 (gpsPosition.month);
            logUint8 (gpsPosition.day);
            logUint16 (gpsPosition.year);
            logUint8 (gpsPosition.hours);
            logUint8 (gpsPosition.minutes);
            logUint8 (gpsPosition.seconds);

            config.flightTime = 0;
            configCalcCRC();

            tlmCreateReport (TLM_LOCAL, TLM_TYPE_STATUS);
            return TRUE;
        }

    // Select the automatic camera control mode.
    if (strcmp (cmdBuffer + token[1], "cam") == 0)
        switch (atoi(cmdBuffer + token[2])) {
            case 0:
                camPanMode = CAM_PAN_DISABLE;
                return TRUE;

            case 1:
                camPanMode = CAM_PAN_UP_HOR_DOWN;
                camResetTimer();
                return TRUE;

            case 2:
                camPanMode = CAM_PAN_UP_HOR;
                camResetTimer();
                return TRUE;

            case 3:
                camPanMode = CAM_PAN_HOR_DOWN;
                camResetTimer();
                return TRUE;

            case 4:
                camPanMode = CAM_PAN_UP_DOWN;
                camResetTimer();
                return TRUE;
        } // END switch

    // Select the telemetry mode based on the mode select value 0 through 9.
    if (strcmp (cmdBuffer + token[1], "tlm") == 0) {
        if (cmdBuffer[token[2]] == '0') {
            tlmCreateReport (TLM_LOCAL, TLM_TYPE_STATUS);
            return TRUE;
        }

        if (cmdBuffer[token[2]] == '1') {
            tlmCreateReport (TLM_CAM_COMPUTER, TLM_TYPE_STATUS);
            return TRUE;
        }

        if (cmdBuffer[token[2]] == '2') {
            tlmCreateReport (TLM_GPS, TLM_TYPE_STATUS);
            return TRUE;
        }

        if (cmdBuffer[token[2]] == '3') {
            tlmCreateReport (TLM_STATUS, TLM_TYPE_STATUS);
            return TRUE;
        }

        if (cmdBuffer[token[2]] == '8') {
            tlmCreateReport (TLM_NMEA, TLM_TYPE_STATUS);
            return TRUE;
        }

        if (cmdBuffer[token[2]] == '9') {
            tlmCreateReport (TLM_PACKET, TLM_TYPE_STATUS);
            return TRUE;
        }
    } // END if

    return FALSE;
}


main()
{
    uint8 configStatus, gpsStatus;
    uint16 count;
    uint32 timerTick;
    char buffer[80];
    COORD landingZone;

    // Configure the hardware system including I/O, serial ports, and timers.
    sysInit();

    // Initialize the subsystems.
    configStatus = configInit();
    tncInit();
    gpsStatus = gpsInit();
    radioInit();
    tlmInit();
    camInit();
    localIOInit();
    analogInit();
    tempInit();
    remoteInit();

    // Turn on the interrupts.
    sysInterruptEnable();

    // Set the run time error handler.
    defineErrorHandler (sysRuntimeErrHandler);

    // Send a message to indicate we have started.
    sysBootMessage(configStatus, gpsStatus);

    // Record the time we start our main loop.
    timerTick = SEC_TIMER;

    count = 0;

    // This is the main program loop that runs until power down.
    while(1) {
        // If the system timer has changed by one second, update the periodic telemetry.
        if (timerTick != SEC_TIMER) {
            ++timerTick;

            // Update the telemetry measurands.
            tlmUpdate();

            // Send the once per second position report to the student payload computer.
            if (radioTLMFlag == TRUE) {
                sprintf (buffer, "%u %d\n\r", (uint16) ((analogData[ANALOG_STATIC] & 0xfffff) >> 4), analogGetStatic());
                serDputs (buffer);
            } else
                if (tlmPositionReportType == TLM_NMEA)
                    tlmPositionReport();

            // Send any required remote packets on odd seconds.
            if ((timerTick & 0x01) == 0x01)
                remoteSend();
        } // END if timerTick

        // Check for a GPS message in the serial buffer.
        gpsReadMessage();

        // If the GPS message has been updated, save it to the log and update the navigation system.
        if (gpsIsUpdate()) {
            gpsLogData();

            atvOverlayUpdate();

            if (!burstDetect)
                navUpdate(&gpsPosition, &landingZone);

            gpsClearUpdate();
        }

        // Check for a camera control message in the serial buffer.
        camReadMessage();

        // Update the analog channels
        analogUpdate();

        // Handle the automatic camera panning functions
        camAutoPan();

         // Process DTMF commands.
        radioDTMFDecode();

        // If a TNC packet request has been made by the radio system, generate the packet now.
        // We process this request outside the timer interrupt routine because it may take longer than 5mS.
        if (radioTNCRequest == TRUE) {
            radioTNCRequest = FALSE;

            // Rotate through each packet type.
            switch (tlmPacketType) {
                case TLM_TYPE_STATUS | 0x80:
                case TLM_TYPE_GPGGA | 0x80:
                case TLM_TYPE_GPRMC | 0x80:
                    tlmPacketType &= ~0x80;
                    tlmLandingEstimate();
                    break;

                case TLM_TYPE_STATUS:
                    tlmReport();

                    // Only change reports types if we are in the local telemetry mode.
                    if (tlmReportType == TLM_LOCAL)
                        tlmPacketType = TLM_TYPE_GPGGA;
                    break;

                case TLM_TYPE_GPGGA:
                    tlmGPGGAPacket();
                    tlmPacketType = TLM_TYPE_GPRMC;
                    break;

                case TLM_TYPE_GPRMC:
                    tlmGPRMCPacket();
                    tlmPacketType = TLM_TYPE_STATUS;
                    break;
            } // END switch
        } // END if

        // Debug messages.

//sprintf (buffer, "%u %u %u %u %u %u %u\n\r", radioLowSquelchTime, radioHighSquelchTime, radioSquelch, tncTransmit, radioLastIDTime, radioTxTimeOut, radioToneCounter);
//serDputs (buffer);

//      sprintf (buffer, "%d %d %d %d %d\n\r", gpsMode, gpsIndex, gpsBuffer[0], gpsBuffer[1], gpsBuffer[2]);
//      serDputs(buffer);

        if (radioDebugFlag == TRUE) {
            sprintf (buffer, "%x %u %u %u %u %u %d %d\n\r", radioMode, radioListenTime, radioListenTimeOut, radioScanTime, radioScanChannel, radioSquelch, radioToneDetect, radioAudio);
            serDputs (buffer);
        } // END if

//sprintf (buffer, "%u %u %u %u %u %u\n\r", radioDTMFIndex, radioDTMFCountDown, radioDTMFBuffer[0], radioDTMFBuffer[1], radioDTMFBuffer[2], radioDTMFBuffer[3]);
//serDputs (buffer);

        // Check for a packet in the TNC buffer.
        if (tncMode == TNC_RX_PARSE) {
            if (tncParsePacket() == TRUE) {
                // Keep track of the total valid packets received.
                ++tncRxPacketCount;

                if (tlmPositionReportType == TLM_PACKET)
                    tlmPacketReport();

                // Only process messages that were sent to this station.
                if (strcmp(tncPacket.destCallSign, config.callSign) == 0) {
                    // Keep track of the packets that were addressed to us.
                    ++tncProcPacketCount;

                    // Copy the message for processing.
                    strcpy (cmdBuffer, tncPacket.message);

                    // If the command was not processed locally pass it to the student payload.
                    if (cmdProcess() == FALSE) {
                        serDputs (tncPacket.message);
                        serDputs ("\n\r");
                    }
                }
            } // END if

            tncMode = TNC_RX_FLAG;
            tncIndex = 0;
        } // END if

    }; // END while()
}