//
//  Name:          beacon.c
//
//
//  Revision History:
//
//   M. Gray       25 Sep 2001  V1.00  Initial release.  Flew ANSR-3 and ANSR-4.
//
//   M. Gray        5 Dec 2001  V1.01  Changed startup message and 
//                                     applied #SEPARATE pragma to several methods for memory usage.
//                                      
//   M. Gray        7 Oct 2002  V1.02  Changed to single interrupt source to prevent TNC bit jitter,
//                                     added serial port FIFO,
//                                     changed GPS parse engine scheme,
//
//
//  COPYRIGHT (c) 2001-2002 Michael Gray, KD7LMO
// 
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Standard includes.
#include "16f876.h"
#include <math.c>
#include <stdlib.h>
// Hardware specific configuration registers.
#fuses HS,NOWDT,NOPROTECT,NOBROWNOUT
// These compiler directives set the clock, serial port, and I/O configuration.
#use delay(clock=16000000)
#use rs232(baud=4800, xmit=PIN_C6, rcv=PIN_C7)
#use fast_io(B)
#use fast_io(C)
// We define types that are used for all variables.  These are
// declared because compilers have different sizes for int and long.
typedef signed int int8;
typedef int uint8;
typedef signed long int16;
typedef long uint16;
// Public methods and data structures for each class.
#SEPARATE
int16 adcScaleTemp();
#SEPARATE
void adcUpdate();
#SEPARATE
uint8 gpsInit();
#SEPARATE
uint16 gpsParseAltitude();
#SEPARATE
uint16 gpsParseDOP();
#SEPARATE
uint8 gpsParseIsValid();
#SEPARATE
uint8 gpsParseSatCount();
#SEPARATE
void gpsUpdate();
uint16 sysCRC16(uint8 *buffer, uint8 length, uint16 crc);
// ****************************************************************************
//   ADC
//
static uint16 adcVolt, adcTemp;
/**
 *   Convert the ADC temperature value to 0.1 degF.
 */
#SEPARATE
int16 adcScaleTemp()
{
    struct long32 scalar, temp;
    // The adcTemp value must be shifted because it is part of the low pass filter.
    temp.hi = 0;
    temp.lo = (adcTemp >> 3);
    // Multiply by 1.514. 
    scalar.hi = 0x0001;
    scalar.lo = 0x5cbe;
    mul32 (&temp, &scalar);
    scalar.hi = 0x0006;
    scalar.lo = 0xe646;
    div32 (&temp, &scalar);
    // Subtract the reference voltage.
    return temp.lo - 123;
}
/**
 *   Read and filter the ADC channels for bus voltage and internal temperature.
 */
#SEPARATE
void adcUpdate(void)
{
    // Filter the bus voltage using a single pole low pass filter.
    setup_adc_ports( A_ANALOG );
    set_adc_channel(0);
    delay_us(50);
    adcVolt = read_adc() + adcVolt - (adcVolt >> 3);
    // Filter the internal temperature using a single pole low pass filter.
    setup_adc_ports( A_ANALOG_RA3_RA2_REF );
    set_adc_channel(1);
    delay_us(50);
    adcTemp = read_adc() + adcTemp - (adcTemp >> 3);
}
// ****************************************************************************
//   Serial port
//
// Note this size must be a power of 2, i.e. 2, 4, 8, 16, etc.
#define SERIAL_BUFFER_SIZE 16
#define SERIAL_BUFFER_MASK 0x0f
// Index and buffer for serial port data.
static uint8 serialHead, serialTail, serialBuffer[SERIAL_BUFFER_SIZE];
/**
 *   Read and store any characters in the PIC serial port in a FIFO.
 */
void serialUpdate()
{
    // If there isn't a character in the PIC buffer, just leave.
    if (!kbhit())
        return;
    // Save the value in the FIFO.
    serialBuffer[serialHead] = getch();
    // Move the pointer to the next open space.
    serialHead = (serialHead + 1) & SERIAL_BUFFER_MASK;
}
/**
 *   Get the oldest character from the FIFO.
 *
 *   @return oldest character; 0 if FIFO is empty
 */
uint8 serialRead()
{
    uint8 value;
    // Make sure we have something to return.
    if (serialHead == serialTail)
        return 0;
    // Save the value.
    value = serialBuffer[serialTail];
    // Update the pointer.
    serialTail = (serialTail + 1) & SERIAL_BUFFER_MASK;
    return value;
}
/**
 *   Determine if the FIFO contains data.
 *
 *   @return true if data present; otherwise false
 */
boolean serialHasData()
{
    if (serialHead == serialTail)
        return false;
    return true;
}
// ****************************************************************************
//   System methods.
//
/**
 *    Calculate the CRC-16 CCITT of <b>buffer</b> that is <b>length</b> bytes long.
 *    The <b>crc</b> parameter allow the calculation on the CRC on multiple buffers.
 *
 *    @param buffer Pointer to data buffer.
 *    @param length number of bytes in data buffer
 *    @param crc starting value
 *
 *    @return CRC-16 of buffer[0 .. length]
 */
uint16 sysCRC16(uint8 *buffer, uint8 length, uint16 crc)
{
    uint8 i, bit, value;
    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;
}
// ****************************************************************************
//   Telemetry
//
#define TLM_BOOT_MESSAGE 0
#define TLM_STATUS 1
#define TLM_GGA 2
#define TLM_RMC 3
#define TLM_BUFFER_SIZE 80
static uint8 tlmPacketType, tlmBuffer[TLM_BUFFER_SIZE];
// ****************************************************************************
//   Timers
//
static uint8 timeInterruptCount, timeTicks, time100mS, timeSeconds, timeMinutes, timeHours;
static uint8 timeDutyCycle;
static boolean timeUpdateFlag;
#define TIME_DUTYCYCLE_10 1
#define TIME_DUTYCYCLE_50 5
// This function gets called every 833uS or 1 TNC bit time.
void timeUpdate()
{
    // Count the number of interrupt so we update the timer every 120 bit times or 100mS.
    if (++timeInterruptCount < 120)
        return;
    timeInterruptCount = 0;
    // This timer just ticks every 100mS and is used for general timing.
    ++timeTicks;
    // Roll the counter over every second.
    if (++time100mS == 10) {
        time100mS = 0;
        // We set this flag true every second.
        timeUpdateFlag = true;
        // Maintain a Real Time Clock.
        if (++timeSeconds == 60) {
            timeSeconds = 0;
            if (++timeMinutes == 60) {
                timeMinutes = 0;
                ++timeHours;
            } // END if
        } // END if
    } // END if
    // Flash the status LED at timeDutyCycle % per second.  We use the duty cycle for mode feedback.
    if (time100mS > timeDutyCycle)
        output_high (PIN_B4);
    else
        output_low (PIN_B4);
}
// ****************************************************************************
//   GPS
//
#define GPS_WAIT_MSG 0
#define GPS_GGA_MSG 1
#define GPS_RMC_MsG 2
#define GPS_BUFFER_SIZE 80
static uint8 gpsState, gpsIndex;
static uint8 gpsBuffer[GPS_BUFFER_SIZE]; 
static char GPS_GGA_TEXT[7] = "$GPGGA";
static char GPS_RMC_TEXT[7] = "$GPRMC";
//$GPGGA,170536.00,3258.1749,N,11141.6454,W,1,11,0.7,21722.0,M,,M,,*65
//$GPRMC,170007.00,A,3258.6018,N,11141.7458,W,25.8,77.5,011201,,*20
/**
 *   Parse the altitude in tens of feet from the NMEA-0183 $GPGGA message.
 *
 *   @return altitude in tens of feet; otherwise 0 if invalid message
 */
#SEPARATE
uint16 gpsParseAltitude()
{
    uint8 i, count;
    struct long32 altitude, scalar;
    // Count the number of field delimiters until we find the desired one.
    i = 0;
    count = 0;
    while (gpsBuffer[i] != 0 && count != 9)
        if (gpsBuffer[i++] == ',')
            ++count;
    if (count != 9)
        return 0;
    // Convert meters to tens of feet.  ft = (10000 * meters) / 3048
    altitude.hi = 0;
    altitude.lo = atol(gpsBuffer + i);
    scalar.hi = 0;
    scalar.lo = 10000;
    mul32 (&altitude, &scalar);
    scalar.hi = 0;
    scalar.lo = 30480;
    div32 (&altitude, &scalar);
    return altitude.lo;
}
/**
 *   Parse the DOP in tenths from the NMEA-0183 $GPGGA message.
 *
 *   @return DOP; otherwise 0 if invalid message
 */
#SEPARATE
uint16 gpsParseDOP()
{
    uint8 i, count, dop;
    // Count the number of field delimiters until we find the desired one.
    i = 0;
    count = 0;
    while (gpsBuffer[i] != 0 && count != 8)
        if (gpsBuffer[i++] == ',')
            ++count;
    if (count != 8)
        return 0;
    // Parse the DOP value x.x or xx.x.  Return 0 if the field is NULL.
    dop = 0;
    while (gpsBuffer[i] != 0 && gpsBuffer[i] != ',') {
        if (gpsBuffer[i] >= '0' && gpsBuffer[i] <= '9')
            dop = (dop * 10) + (gpsBuffer[i] - '0');
        ++i;
    } // END while
    return dop;
}
/**
 *   Determine if NMEA-0183 $GPGGA navigation fix is valid.
 *
 *   @return true for valid fix; otherwise false
 */
#SEPARATE
boolean gpsParseIsValid()
{
    uint8 i, count;
    // Count the number of field delimiters until we find the desired one.
    i = 0;
    count = 0;
    while (gpsBuffer[i] != 7 && count != 6)
        if (gpsBuffer[i++] == ',')
            ++count;
    // Indicate the solution is bad if the parse failed or the fix valid is set to '0'.
    if (count != 6 || gpsBuffer[i] == '0')
        return false;
    return true;
}
/**
 *   Verify the GPS engine is sending GGA and RMC messages.  If not,
 *   configure the GPS engine to send the proper data.
 *
 *   @return TRUE if GPS engine operation; otherwise FALSE
 */
#SEPARATE
boolean gpsInit()
{
    uint8 startTime;
    // We wait 4 seconds for a GPS update to give the engine time to start.
    startTime = timeTicks;
    while (timeTicks - startTime < 40) {
        // Read the serial FIFO and process the GPS messages.
        gpsUpdate();
        // If the GPS state changed, then the GPS is running.
        if (gpsState != GPS_WAIT_MSG) {
            timeDutyCycle = TIME_DUTYCYCLE_10;            
            return true;
        }
    } // END while
    // Set the baud rate to 9600 for the Motorola binary mode.
#asm
    MOVLW  0x05
    MOVWF  0xfaf
#endasm
    // Wait for the UART buffer to empty.
    delay_ms(10);
    // Put the GPS engine in NMEA mode.
    puts ("@@Ci\001\053\015\012");
    // Wait for the GPS to switch modes.
    delay_ms(500);
    // Set the baud rate to 4800 for NMEA messages.
#asm
    MOVLW  0x0b
    MOVWF  0xfaf
#endasm
    // Send a couple <CR><LF> to clear the GPS engine buffer.
    delay_ms(100);
    puts ("\015\012");
    delay_ms(100);
    puts ("\015\012");
    delay_ms(100);
    // Tell the GPS to send GGA and RMC NMEA messages.
    puts ("$PMOTG,GGA,0001\015\012");
    delay_ms(100);
    puts ("$PMOTG,RMC,0001\015\012");
    delay_ms(100);
    return false;
}
/**
 *   Read the serial FIFO and process the GPS messages.
 */
#SEPARATE
void gpsUpdate() 
{
    uint8 value;
    // Only update the buffer if we are waiting for a message.
    if (gpsState != GPS_WAIT_MSG)
        return;
    // Continue to process until the FIFO is empty.
    while (serialHasData()) {
        // Get the character value.
        value = serialRead();
        // Save each character to the telemetry buffer to transmit later.
        gpsBuffer[gpsIndex] = value;
        // If we filled the buffer, then something is wrong and we need to reset.
        if (gpsIndex == GPS_BUFFER_SIZE)
            gpsIndex = 0;
        // At the EOL determine and set the GPS state.
        if (value == 10 && gpsIndex > 15) {
            gpsBuffer[gpsIndex] = 0;
            if (strncmp(gpsBuffer, GPS_GGA_TEXT, 6) == 0) {
                gpsState = GPS_GGA_MSG;
                return;
            } // END if
            if (strncmp(gpsBuffer, GPS_RMC_TEXT, 6) == 0) {
                gpsState = GPS_RMC_MSG;
                return;
            } // END if
            // This messsage wasn't recognized, so just start over.
            gpsIndex = 0;
        } else
            ++gpsIndex;
    } // END while
}
/**
 *   Parse the tracking satelite count from the NMEA-0183 $GPGGA message.
 *
 *   @return sat count; otherwise 0 if invalid message
 */
#SEPARATE
uint8 gpsParseSatCount()
{
    uint8 i, count;
    // Count the number of field delimiters until we find the desired one.
    i = 0;
    count = 0;
    while (gpsBuffer[i] != 0 && count != 7)
        if (gpsBuffer[i++] == ',')
            ++count;
    if (count != 7)
        return 0;
    // Parse the sat count field.
    return ((gpsBuffer[i] - '0') * 10) + (gpsBuffer[i + 1] - '0');
}
/**
 *   Parse the seconds from the UTC time in the $GPGGA message.
 *
 *   @return sat count; otherwise 0 if invalid message
 */
uint8 gpsParseSeconds()
{
    return ((gpsBuffer[9] - '0') * 10) + (gpsBuffer[10] - '0');
}
/**
 *   Wait 1.5 seconds for the GPS message <b>messageType</b>.
 *
 *   @return TRUE if successful; otherwise false
 */
boolean gpsWaitMessage(uint8 messageType)
{
    uint8 startTime;
    // We already have the one we are looking for.
    if (gpsState == messageType)
        return true;
    // Record the current time so we can time out.
    startTime = timeTicks;
    while (startTime - timeTicks < 15) {
        gpsUpdate();
        if (gpsState == messageType)
            return false;
        gpsState = GPS_WAIT_MSG;
    } // END while
    return false;
}
// ****************************************************************************
//   TNC
//
#define TNC_BIT_RATE 833
#define TNC_TX_DELAY 60
#define TNC_TX_READY 0
#define TNC_TX_SYNC 1
#define TNC_TX_HEADER 2
#define TNC_TX_DATA 3
#define TNC_TX_END 4
uint8 TNC_AX25_HEADER[30] = { 
    'A' << 1, 'P' << 1, 'R' << 1, 'S' << 1, ' ' << 1, ' ' << 1, 0x60, \
    'K' << 1, 'D' << 1, '7' << 1, 'L' << 1, 'M' << 1, 'O' << 1, 0x76, \
    'G' << 1, 'A' << 1, 'T' << 1, 'E' << 1, ' ' << 1, ' ' << 1, 0x60, \
    'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '3' << 1, ' ' << 1, 0x67, \
    0x03, 0xf0 };
static uint16 tncTimerCompare;
static uint8 tncLastBit, tncMode, tncBitCount, tncShift, tncIndex, tncLength;
static uint8 tncBitStuff, *tncBufferPnt;
/**
 *    Write <b>value</b> to the telemetry buffer.  Maintain the pointer
 *    and length to the buffer.  The pointer tncBufferPnt and tncLength
 *    must be set before calling this function for the first time.
 * 
 *    @param value to save to telemetry buffer
 */
void tncTxByte (uint8 value)
{
    *tncBufferPnt++ = value;
    ++tncLength;
}
/** 
 *    Prepare an AX.25 data packet.
 */
void tncTxPacket()
{
    uint8 length, startTime, satCount;
    uint16 crc, altitude, temp;
    // Only transmit if there is not another message in progress.
    if (tncMode != TNC_TX_READY)
        return;
    // Set a pointer to our TNC output buffer.
    tncBufferPnt = tlmBuffer;
    // Set the message length counter.
    tncLength = 0;
    // Determine the contents of the packet.
    switch (tlmPacketType) {
        case TLM_BOOT_MESSAGE:
            printf (tncTxByte, ">ANSR Beacon Started, GPS Comm OK, V1.02");
            tlmPacketType = TLM_STATUS;
            break;
        case TLM_STATUS:
            // Get the GPS information to provide altitude and GPS tracking info.
            if (gpsWaitMessage (GPS_GGA_MSG)) {
                altitude = gpsParseAltitude();
                temp = gpsParseDOP();
                satCount = gpsParseSatCount();
            } else
                satCount = 255;
            // Display the telemetry header.
            printf (tncTxByte, ">ANSR Beacon ");
            // Display the flight time.
            printf (tncTxByte, "%02U:%02U:%02U ", timeHours, timeMinutes, timeSeconds);
            // If satCount was set to 255, then we didn't receive GPS data.
            if (satCount != 255) {
                if (altitude != 0)
                    printf (tncTxByte, "%lu0' ", altitude);
                else
                    printf (tncTxByte, "0' ");
                printf (tncTxByte, "%lu.%luhdop ", temp / 10, temp % 10);
                printf (tncTxByte, "%02utrk ", satCount);
            } else
                printf (tncTxByte, "-----' -.-hdop --trk ");
            // Display bus voltage.  The volage in 0.1 VDC = ADC * 0.21
            temp = ((adcVolt >> 3) * 50) / 1024;
            printf (tncTxByte, "+%lu.%luvdc ", temp / 10, temp % 10);
            // Display internal temperature.
            printf (tncTxByte, "%ldF ", adcScaleTemp());
            tlmPacketType = TLM_GGA;
            break;
        case TLM_GGA:
            if (gpsWaitMessage(GPS_GGA_MSG))
                printf (tncTxByte, gpsBuffer);
            else
                printf (tncTxByte, ">Invalid GPS $GPGGA message");
            tlmPacketType = TLM_RMC;
            break;
        case TLM_RMC:
            if (gpsWaitMessage(GPS_RMC_MSG))
                printf (tncTxByte, gpsBuffer);
            else
                printf (tncTxByte, ">Invalid GPS $GPRMC message");
            tlmPacketType = TLM_STATUS;
            break;
    }
    // Add the end of message character.
    printf (tncTxByte, "\015");
    // Calculate the CRC for the header and message.
    crc = sysCRC16(TNC_AX25_HEADER, sizeof(TNC_AX25_HEADER), 0xffff);
    crc = sysCRC16(tlmBuffer, tncLength, crc ^ 0xffff);
    // Save the CRC in the message.
    *tncBufferPnt++ = crc & 0xff;
    *tncBufferPnt = (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;
    // Turn on the MODEM and PTT.
    output_low (PIN_B6);
    output_high (PIN_C3);
}
// This ISR is called when the timer register matches the compare register.
// The interrupt rate is programmed to 833uS or 1 bit time at 1200 baud.
#INT_CCP1
void tncInterrupt()
{
    // Write the MODEM data bit.
    if (tncLastBit == 0)
        output_low (PIN_B1);
    else
        output_high (PIN_B1);
    // Setup for the next interrupt.
    tncTimerCompare += TNC_BIT_RATE;
    CCP_1 = tncTimerCompare;
    switch (tncMode) {
        case TNC_TX_READY:
            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;
                    
            // 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 == TNC_TX_DELAY) {
                    tncIndex = 0;
                    tncShift = TNC_AX25_HEADER[0];
                    tncBitStuff = 0;
                    tncMode = TNC_TX_HEADER;
                } // END if
            } else
                tncShift = tncShift >> 1;
            break;
        case TNC_TX_HEADER:
            // 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;
                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;
            // 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;
                // After the header is sent, then send the data.
                if (++tncIndex == sizeof(TNC_AX25_HEADER)) {
                    tncIndex = 0;
                    tncShift = tlmBuffer[0];
                    tncMode = TNC_TX_DATA;
                } else
                    tncShift = TNC_AX25_HEADER[tncIndex];
            } 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;
                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;
            // 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 = tlmBuffer[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;
            // If all the bits were shifted, get the next one.
            if (++tncBitCount == 8) {
                tncBitCount = 0;
                tncShift = 0x7e;
    
                // Transmit two closing flags.
                if (++tncIndex == 2) {
                    tncMode = TNC_TX_READY;
                    // Turn off the MODEM and PTT.
                    output_high (PIN_B6);
                    output_low (PIN_C3);
                    return;
                } // END if
            } else
                tncShift = tncShift >> 1;
            break;
    } // END switch
    // Now process the timer and serial port.
    timeUpdate();
    serialUpdate();
}
void resetSystem()
{
    // Disable the interrupts and jump to the reset vector.
    disable_interrupts(GLOBAL);
#asm
    goto 0x000
#endasm
}
// This is where we go after reset.
void main()
{
    uint8 i;
    // Configure the output ports.
    set_tris_b (0x2d);
    set_tris_c (0xb7);
    // Put CMX614 in low power mode, TX data low, PTT off, and heart beat LED off.
    output_high (PIN_B7);
    output_high (PIN_B6);
    output_low (PIN_B1);
    output_low (PIN_C3);
    output_high (PIN_B4);
    // Set the time subsystem variables.
    timeInterruptCount = 0;
    timeTicks = 0;
    time100mS = 0;
    timeSeconds = 0;
    timeMinutes = 0;
    timeHours = 0;
    timeUpdateFlag = false;
    timeDutyCycle = TIME_DUTYCYCLE_50;
    // Setup the serial port variables.
    serialHead = 0;
    serialTail = 0;
    // Setup the ADC.
    setup_adc( ADC_CLOCK_DIV_32 );
    adcVolt = 0;
    adcTemp = 0;
    // Configure CCP1 to interrupt at the TNC bit rate.
    tncTimerCompare = TNC_BIT_RATE;
    CCP_1 = tncTimerCompare;
    set_timer1(0);
    setup_ccp1( CCP_COMPARE_INT );
    setup_timer_1( T1_INTERNAL | T1_DIV_BY_4 );
    // Set the TNC subsystem variables.
    tncLastBit = 0;
    tncMode = TNC_TX_READY;
    // Set the telemetry subsystem variables.
    tlmPacketType = TLM_BOOT_MESSAGE;
    // Set the GPS subsystem variables.
    gpsState = GPS_WAIT_MSG;
    gpsIndex = 0;
    // Setup our interrupts.
    enable_interrupts(GLOBAL);    
    enable_interrupts(INT_CCP1);
    // Wait for the power converter to stabilize.
    delay_ms (500);
    // Charge the ADC filters.
    for (i = 0; i < 32; ++i)
        adcUpdate();
    // Setup the GPS engine.
    while (!gpsInit());
    // This is the main loop where we wait for timer ticks.
    while (1) {
        // Read the serial FIFO and process the GPS messages.
        gpsUpdate();
        if (gpsState != GPS_WAIT_MSG) {
            if (gpsState == GPS_GGA_MSG) {
                i = gpsParseSeconds();
                if (i == 0 || i == 30)
                    tncTxPacket();
            } // END if
            gpsState = GPS_WAIT_MSG;
        } // END if
        // Update the ADC once a second.
        if (timeUpdateFlag) {
            adcUpdate();
            timeUpdateFlag = false;
        }
    } // END while
}