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