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