+/**
+ * http://ad7zj.net/kd7lmo/aprsbeacon_code.html
+ *
+ * @mainpage Pico Beacon
+ *
+ * @section overview_sec Overview
+ *
+ * The Pico Beacon is an APRS based tracking beacon that operates in the UHF 420-450MHz band. The device utilizes a
+ * Microchip PIC 18F2525 embedded controller, Motorola M12+ GPS engine, and Analog Devices AD9954 DDS. The device is capable
+ * of generating a 1200bps A-FSK and 9600 bps FSK AX.25 compliant APRS (Automatic Position Reporting System) message.
+
+
+ *
+ * @section history_sec Revision History
+ *
+ * @subsection v305 V3.05
+ * 23 Dec 2006, Change include; (1) change printf format width to conform to ANSI standard when new CCS 4.xx compiler released.
+ *
+ *
+ * @subsection v304 V3.04
+ * 10 Jan 2006, Change include; (1) added amplitude control to engineering mode,
+ * (2) corrected number of bytes reported in log,
+ * (3) add engineering command to set high rate position reports (5 seconds), and
+ * (4) corrected size of LOG_COORD block when searching for end of log.
+ *
+ * @subsection v303 V3.03
+ * 15 Sep 2005, Change include; (1) removed AD9954 setting SDIO as input pin,
+ * (2) additional comments and Doxygen tags,
+ * (3) integration and test code calculates DDS FTW,
+ * (4) swapped bus and reference analog input ports (hardware change),
+ * (5) added message that indicates we are reading flash log and reports length,
+ * (6) report bus voltage in 10mV steps, and
+ * (7) change log type enumerated values to XORed nibbles for error detection.
+ *
+ *
+ * @subsection v302 V3.02
+ * 6 Apr 2005, Change include; (1) corrected tracked satellite count in NMEA-0183 $GPGGA message,
+ * (2) Doxygen documentation clean up and additions, and
+ * (3) added integration and test code to baseline.
+ *
+ *
+ * @subsection v301 V3.01
+ * 13 Jan 2005, Renamed project and files to Pico Beacon.
+ *
+ *
+ * @subsection v300 V3.00
+ * 15 Nov 2004, Change include; (1) Micro Beacon extreme hardware changes including integral transmitter,
+ * (2) PIC18F2525 processor,
+ * (3) AD9954 DDS support functions,
+ * (4) added comments and formatting for doxygen,
+ * (5) process GPS data with native Motorola protocol,
+ * (6) generate plain text $GPGGA and $GPRMC messages,
+ * (7) power down GPS 5 hours after lock,
+ * (8) added flight data recorder, and
+ * (9) added diagnostics terminal mode.
+ *
+ *
+ * @subsection v201 V2.01
+ * 30 Jan 2004, Change include; (1) General clean up of in-line documentation, and
+ * (2) changed temperature resolution to 0.1 degrees F.
+ *
+ *
+ * @subsection v200 V2.00
+ * 26 Oct 2002, Change include; (1) Micro Beacon II hardware changes including PIC18F252 processor,
+ * (2) serial EEPROM,
+ * (3) GPS power control,
+ * (4) additional ADC input, and
+ * (5) LM60 temperature sensor.
+ *
+ *
+ * @subsection v101 V1.01
+ * 5 Dec 2001, Change include; (1) Changed startup message, and
+ * (2) applied SEPARATE pragma to several methods for memory usage.
+ *
+ *
+ * @subsection v100 V1.00
+ * 25 Sep 2001, Initial release. Flew ANSR-3 and ANSR-4.
+ *
+
+
+ *
+ *
+ * @section copyright_sec Copyright
+ *
+ * Copyright (c) 2001-2009 Michael Gray, KD7LMO
+
+
+ *
+ *
+ * @section gpl_sec GNU General Public License
+ *
+ * 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
+ *
+
+
+ *
+ *
+ * @section design Design Details
+ *
+ * Provides design details on a variety of the components that make up the Pico Beacon.
+ *
+ * @subpage power
+ */
+
+/**
+ * @page power Power Consumption
+ *
+ * Measured DC power consumption.
+ *
+ * 3VDC prime power current
+
+ *
+ * 7mA Held in reset
+
+ * 18mA Processor running, all I/O off
+
+ * 110mA GPS running
+
+ * 120mA GPS running w/antenna
+
+ * 250mA DDS running and GPS w/antenna
+
+ * 420mA DDS running, GPS w/antenna, and PA chain on with no RF
+
+ * 900mA Transmit
+
+ *
+ */
+
+// Hardware specific configuration.
+#include <18f2525.h>
+#device ADC=10
+
+// NOTE: Even though we are using an external clock, we set the HS oscillator mode to
+// make the PIC 18F252 work with our external clock which is a clipped 1V P-P sine wave.
+#fuses HS,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOLVP
+
+// C runtime library definitions.
+#include
+#include
+
+// These compiler directives set the clock, SPI/I2C ports, and I/O configuration.
+
+// TCXO frequency
+#use delay(clock=19200000)
+
+// Engineering and data extracation port.
+#use rs232(baud=57600, xmit=PIN_B7, rcv=PIN_B6, STREAM=PC_HOST)
+
+// GPS engine
+#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
+
+#use i2c (master, scl=PIN_C3, sda=PIN_C4)
+
+#use fast_io(A)
+#use fast_io(B)
+#use fast_io(C)
+
+// We define types that are used for all variables. These are declared
+// because each processor has a different sizes for int and long.
+// The PIC compiler defines int8_t, int16_t, and int32_t.
+
+/// Boolean value { false, true }
+typedef boolean bool_t;
+
+/// Signed 8-bit number in the range -128 through 127.
+typedef signed int8 int8_t;
+
+/// Unsigned 8-bit number in the range 0 through 255.
+typedef unsigned int8 uint8_t;
+
+/// Signed 16-bit number in the range -32768 through 32767.
+typedef signed int16 int16_t;
+
+/// Unsigned 16-bit number in the range 0 through 65535.
+typedef unsigned int16 uint16_t;
+
+/// Signed 32-bit number in the range -2147483648 through 2147483647.
+typedef signed int32 int32_t;
+
+/// Unsigned 32-bit number in the range 0 through 4294967296.
+typedef unsigned int32 uint32_t;
+
+// Function and structure prototypes. These are declared at the start of
+// the file much like a C++ header file.
+
+// Map I/O pin names to hardware pins.
+
+/// Heartbeat LED - Port A2
+#define IO_LED PIN_A2
+
+/// AD9954 DDS Profile Select 0 - Port A3
+#define IO_PS0 PIN_A3
+
+/// UHF amplifier and PA chain - Port A4
+#define IO_PTT PIN_A4
+
+/// AD9954 DDS Update - Port A5
+#define IO_UPDATE PIN_A5
+
+/// AD9954 CS (Chip Select) - Port B0
+#define IO_CS PIN_B0
+
+/// GPS Engine Power - Port B1
+#define IO_GPS_PWR PIN_B1
+
+/// AD9954 DDS Profile Select 1 - Port C0
+#define IO_PS1 PIN_C0
+
+/// AD9954 DDS OSK (Output Shift Key) - Port C2
+#define IO_OSK PIN_C2
+
+/// GPS engine serial transmit pin - Port C6
+#define IO_GPS_TXD PIN_C6
+
+// Public methods, constants, and data structures for each class.
+
+/// Operational modes of the AD9954 DDS for the ddsSetMode function.
+enum DDS_MODE
+{
+ /// Device has not been initialized.
+ DDS_MODE_NOT_INITIALIZED,
+
+ /// Device in lowest power down mode.
+ DDS_MODE_POWERDOWN,
+
+ /// Generate FM modulated audio tones.
+ DDS_MODE_AFSK,
+
+ /// Generate true FSK tones.
+ DDS_MODE_FSK
+};
+
+void ddsInit();
+void ddsSetAmplitude (uint8_t amplitude);
+void ddsSetOutputScale (uint16_t amplitude);
+void ddsSetFSKFreq (uint32_t ftw0, uint32_t ftw1);
+void ddsSetFreq (uint32_t freq);
+void ddsSetFTW (uint32_t ftw);
+void ddsSetMode (DDS_MODE mode);
+
+void flashErase();
+uint8_t flashGetByte ();
+void flashReadBlock(uint32_t address, uint8_t *block, uint16_t length);
+void flashSendByte(uint8_t value);
+void flashSendAddress(uint32_t address);
+void flashWriteBlock(uint32_t address, uint8_t *block, uint8_t length);
+
+/// Type of GPS fix.
+enum GPS_FIX_TYPE
+{
+ /// No GPS FIX
+ GPS_NO_FIX,
+
+ /// 2D (Latitude/Longitude) fix.
+ GPS_2D_FIX,
+
+ /// 3D (Latitude/Longitude/Altitude) fix.
+ GPS_3D_FIX
+};
+
+/// GPS Position information.
+typedef struct
+{
+ /// Flag that indicates the position information has been updated since it was last checked.
+ bool_t updateFlag;
+
+ /// Month in UTC time.
+ uint8_t month;
+
+ /// Day of month in UTC time.
+ uint8_t day;
+
+ /// Hours in UTC time.
+ uint8_t hours;
+
+ /// Minutes in UTC time.
+ uint8_t minutes;
+
+ /// Seconds in UTC time.
+ uint8_t seconds;
+
+ /// Year in UTC time.
+ uint16_t year;
+
+ /// Latitude in milli arc-seconds where + is North, - is South.
+ int32_t latitude;
+
+ /// Longitude in milli arc-seconds where + is East, - is West.
+ int32_t longitude;
+
+ /// Altitude in cm
+ int32_t altitudeCM;
+
+ /// Calculated altitude in feet
+ int32_t altitudeFeet;
+
+ /// 3D speed in cm/second.
+ uint16_t vSpeed;
+
+ /// 2D speed in cm/second.
+ uint16_t hSpeed;
+
+ /// Heading units of 0.1 degrees.
+ uint16_t heading;
+
+ /// DOP (Dilution of Precision)
+ uint16_t dop;
+
+ /// 16-bit number that represents status of GPS engine.
+ uint16_t status;
+
+ /// Number of tracked satellites used in the fix position.
+ uint8_t trackedSats;
+
+ /// Number of visible satellites.
+ uint8_t visibleSats;
+} GPSPOSITION_STRUCT;
+
+void gpsInit();
+bool_t gpsIsReady();
+GPS_FIX_TYPE gpsGetFixType();
+int32_t gpsGetPeakAltitude();
+void gpsPowerOn();
+bool_t gpsSetup();
+void gpsUpdate();
+
+int16_t lm92GetTemp();
+
+/// Define the log record types.
+enum LOG_TYPE
+{
+ /// Time stamp the log was started.
+ LOG_BOOTED = 0xb4,
+
+ /// GPS coordinates.
+ LOG_COORD = 0xa5,
+
+ /// Temperature
+ LOG_TEMPERATURE = 0x96,
+
+ /// Bus voltage.
+ LOG_VOLTAGE = 0x87
+};
+
+void logInit();
+uint32_t logGetAddress();
+void logType (LOG_TYPE type);
+void logUint8 (uint8_t value);
+void logInt16 (int16_t value);
+
+bool_t serialHasData();
+void serialInit();
+uint8_t serialRead();
+void serialUpdate();
+
+uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc);
+void sysInit();
+void sysLogVoltage();
+
+/// 0% duty cycle (LED Off) constant for function timeSetDutyCycle
+#define TIME_DUTYCYCLE_0 0
+
+/// 10% duty cycle constant for function timeSetDutyCycle
+#define TIME_DUTYCYCLE_10 1
+
+/// 70% duty cycle constant for function timeSetDutyCycle
+#define TIME_DUTYCYCLE_70 7
+
+uint8_t timeGetTicks();
+void timeInit();
+void timeSetDutyCycle (uint8_t dutyCycle);
+void timeUpdate();
+
+/// Operational modes of the TNC for the tncSetMode function.
+enum TNC_DATA_MODE
+{
+ /// No operation waiting for setup and configuration.
+ TNC_MODE_STANDBY,
+
+ /// 1200 bps using A-FSK (Audio FSK) tones.
+ TNC_MODE_1200_AFSK,
+
+ /// 9600 bps using true FSK tones.
+ TNC_MODE_9600_FSK
+};
+
+void tncInit();
+bool_t tncIsFree();
+void tncHighRate(bool_t state);
+void tncSetMode (TNC_DATA_MODE dataMode);
+void tnc1200TimerTick();
+void tnc9600TimerTick();
+void tncTxByte (uint8_t value);
+void tncTxPacket(TNC_DATA_MODE dataMode);
+
+/**
+ * @defgroup ADC Analog To Digital Converter
+ *
+ * Control and manage the on board PIC A/D converter.
+ *
+ * @{
+ */
+
+/// Filtered voltages using a single pole, low pass filter.
+uint16_t adcMainBusVolt;
+
+/// PIC ADC Channel number of the reference voltage.
+#define ADC_REF 0
+
+/// PIC ADC Channel number of the main bus voltage.
+#define ADC_MAINBUS 1
+
+/// Input diode drop in units of 0.01 volts.
+#define MAIN_BUS_VOLT_OFFSET 20
+
+/**
+ * Intialize the ADC subsystem.
+ */
+void adcInit()
+{
+ // Setup the ADC.
+ setup_adc_ports(AN0_TO_AN1);
+ setup_adc( ADC_CLOCK_DIV_32 );
+
+ // Zero the ADC filters.
+ adcMainBusVolt = 0;
+}
+
+/**
+ * Filtered main bus voltage in 10mV resolution.
+ *
+ * @return voltage in 10mV steps
+ */
+uint16_t adcGetMainBusVolt()
+{
+ uint32_t volts;
+
+ volts = (uint32_t) (adcMainBusVolt >> 3);
+
+ volts = (volts * 330l) / 1023l;
+
+ return (uint16_t) volts + MAIN_BUS_VOLT_OFFSET;
+}
+
+/**
+ * Get the current ADC value for the main bus voltage.
+ *
+ * @return ADC value in the range 0 to 1023
+ */
+uint16_t adcRawBusVolt()
+{
+ set_adc_channel(ADC_MAINBUS);
+ delay_us(50);
+ return read_adc();
+}
+
+/**
+ * Get the current ADC value for the reference source voltage.
+ *
+ * @return ADC value in the range 0 to 1023
+ */
+uint16_t adcRawRefVolt()
+{
+ set_adc_channel(ADC_REF);
+ delay_us(50);
+ return read_adc();
+}
+
+/**
+ * Read and filter the ADC channels for bus voltages.
+ */
+void adcUpdate(void)
+{
+ // Filter the bus voltage using a single pole low pass filter.
+ set_adc_channel(ADC_MAINBUS);
+ delay_us(50);
+ adcMainBusVolt = read_adc() + adcMainBusVolt - (adcMainBusVolt >> 3);
+}
+
+/** @} */
+
+
+/**
+ * @defgroup diag Diagnostics and Control
+ *
+ * Functions for diagnostics and control of the hardware and flight data recorder.
+ *
+ * @{
+ */
+
+/// Number of bytes per line to display when reading flight data recorder.
+#define DIAG_BYTES_PER_LINE 32
+
+/**
+ * Process the command to erase the data logger flash.
+ */
+void diagEraseFlash()
+{
+ // Confirm we want to erase the flash with the key sequence 'yes' .
+ fprintf (PC_HOST, "Are you sure (yes)? ");
+
+ if (fgetc(PC_HOST) != 'y')
+ return;
+
+ if (fgetc(PC_HOST) != 'e')
+ return;
+
+ if (fgetc(PC_HOST) != 's')
+ return;
+
+ if (fgetc(PC_HOST) != 13)
+ return;
+
+ // User feedback and erase the part.
+ fprintf (PC_HOST, "Erasing flash...");
+
+ flashErase();
+
+ fprintf (PC_HOST, "done.\n\r");
+}
+
+/**
+ * Display the engineering mode menu.
+ */
+void diagMenu()
+{
+ // User interface.
+ fprintf (PC_HOST, "Options: (e)rase Flash, (r)ead Flash\n\r");
+ fprintf (PC_HOST, " Toggle (L)ED\n\r");
+ fprintf (PC_HOST, " (P)TT - Push To Transmit\n\r");
+ fprintf (PC_HOST, " (f)requencey down, (F)requency up - 1KHz step\n\r");
+ fprintf (PC_HOST, " (c)hannel down, (C)hannel up - 25KHz step\n\r");
+ fprintf (PC_HOST, " (a)mplitude down, (A)mplitude up - 0.5 dB steps\n\r");
+ fprintf (PC_HOST, " e(x)it engineering mode\n\r");
+}
+
+/**
+ * Process the command to dump the contents of the data logger flash.
+ */
+void diagReadFlash()
+{
+ bool_t dataFoundFlag, userStopFlag;
+ uint8_t i, buffer[DIAG_BYTES_PER_LINE];
+ uint32_t address;
+
+ // Set the initial conditions to read the flash.
+ address = 0x0000;
+ userStopFlag = false;
+
+ do
+ {
+ // Read each block from the flash device.
+ flashReadBlock (address, buffer, DIAG_BYTES_PER_LINE);
+
+ // This flag will get set if any data byte is not equal to 0xff (erase flash state)
+ dataFoundFlag = false;
+
+ // Display the address.
+ fprintf (PC_HOST, "%08lx ", address);
+
+ // Display each byte in the line.
+ for (i = 0; i < DIAG_BYTES_PER_LINE; ++i)
+ {
+ fprintf (PC_HOST, "%02x", buffer[i]);
+
+ // Set this flag if the cell is not erased.
+ if (buffer[i] != 0xff)
+ dataFoundFlag = true;
+
+ // Any key will abort the transfer.
+ if (kbhit(PC_HOST))
+ userStopFlag = true;
+ } // END for
+
+ // at the end of each line.
+ fprintf (PC_HOST, "\n\r");
+
+ // Advance to the next block of memory.
+ address += DIAG_BYTES_PER_LINE;
+ } while (dataFoundFlag && !userStopFlag);
+
+ // Feedback to let the user know why the transfer stopped.
+ if (userStopFlag)
+ fprintf (PC_HOST, "User aborted download!\n\r");
+}
+
+void diag1PPS()
+{
+ uint16_t timeStamp, lastTimeStamp;
+
+ lastTimeStamp = 0x0000;
+
+ gpsPowerOn();
+
+ for (;;)
+ {
+ timeStamp = CCP_2;
+
+ if (timeStamp != lastTimeStamp)
+ {
+ delay_ms (10);
+
+ timeStamp = CCP_2;
+
+ fprintf (PC_HOST, "%lu %lu\n\r", timeStamp, (timeStamp - lastTimeStamp));
+
+ lastTimeStamp = timeStamp;
+ }
+ }
+}
+
+/**
+ * Process diagnostic commands through the debug RS-232 port.
+ */
+void diagPort()
+{
+ bool_t diagDoneFlag, ledFlag, paFlag, showSettingsFlag;
+ uint8_t command, amplitude;
+ uint32_t freqHz;
+
+ // If the input is low, we aren't connected to the RS-232 device so continue to boot.
+ if (!input(PIN_B6))
+ return;
+
+ fprintf (PC_HOST, "Engineering Mode\n\r");
+ fprintf (PC_HOST, "Application Built %s %s\n\r", __DATE__, __TIME__);
+
+ // Current state of the status LED.
+ ledFlag = false;
+ output_bit (IO_LED, ledFlag);
+
+ // This flag indicates we are ready to leave the diagnostics mode.
+ diagDoneFlag = false;
+
+ // Current state of the PA.
+ paFlag = false;
+
+ // Flag that indicate we should show the current carrier frequency.
+ showSettingsFlag = false;
+
+ // Set the initial carrier frequency and amplitude.
+ freqHz = 445950000;
+ amplitude = 0;
+
+ // Wait for the exit command.
+ while (!diagDoneFlag)
+ {
+ // Wait for the user command.
+ command = fgetc(PC_HOST);
+
+ // Decode and process the key stroke.
+ switch (command)
+ {
+ case 'e':
+ diagEraseFlash();
+ logInit();
+ break;
+
+ case 'l':
+ case 'L':
+ ledFlag = (ledFlag ? false : true);
+ output_bit (IO_LED, ledFlag);
+ break;
+
+ case 'h':
+ case 'H':
+ case '?':
+ diagMenu();
+ break;
+
+ case 'r':
+ diagReadFlash();
+ break;
+
+ case 't':
+ tncHighRate (true);
+ fprintf (PC_HOST, "Set high rate TNC.\n\r");
+ break;
+
+ case 'f':
+ freqHz -= 1000;
+ ddsSetFreq (freqHz);
+
+ // Display the new frequency.
+ showSettingsFlag = true;
+ break;
+
+ case 'F':
+ freqHz += 1000;
+ ddsSetFreq (freqHz);
+
+ // Display the new frequency.
+ showSettingsFlag = true;
+ break;
+
+ case 'c':
+ freqHz -= 25000;
+ ddsSetFreq (freqHz);
+
+ // Display the new frequency.
+ showSettingsFlag = true;
+ break;
+
+ case 'C':
+ freqHz += 25000;
+ ddsSetFreq (freqHz);
+
+ // Display the new frequency.
+ showSettingsFlag = true;
+ break;
+
+ case 'p':
+ case 'P':
+ ddsSetFreq (freqHz);
+
+ paFlag = (paFlag ? false : true);
+ output_bit (IO_PTT, paFlag);
+ output_bit (IO_OSK, paFlag);
+
+ if (paFlag)
+ {
+ ddsSetMode (DDS_MODE_AFSK);
+ ddsSetAmplitude (amplitude);
+ } else
+ ddsSetMode (DDS_MODE_POWERDOWN);
+
+ break;
+
+ case 'a':
+ if (amplitude != 200)
+ {
+ amplitude += 5;
+ ddsSetAmplitude (amplitude);
+
+ // Display the new amplitude.
+ showSettingsFlag = true;
+ }
+ break;
+
+ case 'A':
+ if (amplitude != 0)
+ {
+ amplitude -= 5;
+ ddsSetAmplitude (amplitude);
+
+ // Display the new amplitude.
+ showSettingsFlag = true;
+ }
+ break;
+
+ case 'g':
+ diag1PPS();
+ break;
+
+ case 'x':
+ diagDoneFlag = true;
+ break;
+
+ default:
+ fprintf (PC_HOST, "Invalid command. (H)elp for menu.\n\r");
+ break;
+ } // END switch
+
+ // Display the results of any user requests or commands.
+ if (showSettingsFlag)
+ {
+ showSettingsFlag = false;
+
+ fprintf (PC_HOST, "%03ld.%03ld MHz ", freqHz / 1000000, (freqHz / 1000) % 1000);
+ fprintf (PC_HOST, "%d.%01ddBc\n\r", amplitude / 10, amplitude % 10);
+
+ } // END if
+
+ } // END while
+
+ // Let the user know we are done with this mode.
+ fprintf (PC_HOST, "Exit diagnostic mode.\n\r");
+
+ return;
+}
+
+/** @} */
+
+
+/**
+ * @defgroup DDS AD9954 DDS (Direct Digital Synthesizer)
+ *
+ * Functions to control the Analog Devices AD9954 DDS.
+ *
+ * @{
+ */
+
+/// AD9954 CFR1 - Control functions including RAM, profiles, OSK, sync, sweep, SPI, and power control settings.
+#define DDS_AD9954_CFR1 0x00
+
+/// AD9954 CFR2 - Control functions including sync, PLL multiplier, VCO range, and charge pump current.
+#define DDS_AD9954_CFR2 0x01
+
+/// AD9954 ASF - Auto ramp rate speed control and output scale factor (0x0000 to 0x3fff).
+#define DDS_AD9954_ASF 0x02
+
+/// AD9954 ARR - Amplitude ramp rate for OSK function.
+#define DDS_AD9954_ARR 0x03
+
+/// AD9954 FTW0 - Frequency tuning word 0.
+#define DDS_AD9954_FTW0 0x04
+
+/// AD9954 FTW1 - Frequency tuning word 1
+#define DDS_AD9954_FTW1 0x06
+
+/// AD9954 NLSCW - Negative Linear Sweep Control Word used for spectral shaping in FSK mode
+#define DDS_AD9954_NLSCW 0x07
+
+/// AD9954 PLSCW - Positive Linear Sweep Control Word used for spectral shaping in FSK mode
+#define DDS_AD9954_PLSCW 0x08
+
+/// AD9954 RSCW0 - RAM Segment Control Word 0
+#define DDS_AD9954_RWCW0 0x07
+
+/// AD9954 RSCW0 - RAM Segment Control Word 1
+#define DDS_AD9954_RWCW1 0x08
+
+/// AD9954 RAM segment
+#define DDS_RAM 0x0b
+
+/// Current operational mode.
+DDS_MODE ddsMode;
+
+/// Number of digits in DDS frequency to FTW conversion.
+#define DDS_FREQ_TO_FTW_DIGITS 9
+
+/// Array of multiplication factors used to convert frequency to the FTW.
+const uint32_t DDS_MULT[DDS_FREQ_TO_FTW_DIGITS] = { 11, 7, 7, 3, 4, 8, 4, 9, 1 };
+
+/// Array of divisors used to convert frequency to the FTW.
+const uint32_t DDS_DIVISOR[DDS_FREQ_TO_FTW_DIGITS - 1] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
+
+/// Lookup table to convert dB amplitude scale in 0.5 steps to a linear DDS scale factor.
+const uint16_t DDS_AMP_TO_SCALE[] =
+{
+ 16383, 15467, 14601, 13785, 13013, 12286, 11598, 10949, 10337, 9759, 9213, 8697,
+ 8211, 7752, 7318, 6909, 6522, 6157, 5813, 5488, 5181, 4891, 4617, 4359, 4115, 3885, 3668, 3463,
+ 3269, 3086, 2913, 2750, 2597, 2451, 2314, 2185, 2062, 1947, 1838, 1735, 1638
+};
+
+
+/// Frequency Word List - 4.0KHz FM frequency deviation at 81.15MHz (445.950MHz)
+const uint32_t freqTable[256] =
+{
+ 955418300, 955419456, 955420611, 955421765, 955422916, 955424065, 955425210, 955426351,
+ 955427488, 955428618, 955429743, 955430861, 955431971, 955433073, 955434166, 955435249,
+ 955436322, 955437385, 955438435, 955439474, 955440500, 955441513, 955442511, 955443495,
+ 955444464, 955445417, 955446354, 955447274, 955448176, 955449061, 955449926, 955450773,
+ 955451601, 955452408, 955453194, 955453960, 955454704, 955455426, 955456126, 955456803,
+ 955457457, 955458088, 955458694, 955459276, 955459833, 955460366, 955460873, 955461354,
+ 955461809, 955462238, 955462641, 955463017, 955463366, 955463688, 955463983, 955464250,
+ 955464489, 955464701, 955464884, 955465040, 955465167, 955465266, 955465337, 955465380,
+ 955465394, 955465380, 955465337, 955465266, 955465167, 955465040, 955464884, 955464701,
+ 955464489, 955464250, 955463983, 955463688, 955463366, 955463017, 955462641, 955462238,
+ 955461809, 955461354, 955460873, 955460366, 955459833, 955459276, 955458694, 955458088,
+ 955457457, 955456803, 955456126, 955455426, 955454704, 955453960, 955453194, 955452408,
+ 955451601, 955450773, 955449926, 955449061, 955448176, 955447274, 955446354, 955445417,
+ 955444464, 955443495, 955442511, 955441513, 955440500, 955439474, 955438435, 955437385,
+ 955436322, 955435249, 955434166, 955433073, 955431971, 955430861, 955429743, 955428618,
+ 955427488, 955426351, 955425210, 955424065, 955422916, 955421765, 955420611, 955419456,
+ 955418300, 955417144, 955415989, 955414836, 955413684, 955412535, 955411390, 955410249,
+ 955409113, 955407982, 955406857, 955405740, 955404629, 955403528, 955402435, 955401351,
+ 955400278, 955399216, 955398165, 955397126, 955396100, 955395088, 955394089, 955393105,
+ 955392136, 955391183, 955390246, 955389326, 955388424, 955387540, 955386674, 955385827,
+ 955385000, 955384192, 955383406, 955382640, 955381896, 955381174, 955380474, 955379797,
+ 955379143, 955378513, 955377906, 955377324, 955376767, 955376235, 955375728, 955375246,
+ 955374791, 955374362, 955373959, 955373583, 955373234, 955372912, 955372618, 955372350,
+ 955372111, 955371900, 955371716, 955371560, 955371433, 955371334, 955371263, 955371220,
+ 955371206, 955371220, 955371263, 955371334, 955371433, 955371560, 955371716, 955371900,
+ 955372111, 955372350, 955372618, 955372912, 955373234, 955373583, 955373959, 955374362,
+ 955374791, 955375246, 955375728, 955376235, 955376767, 955377324, 955377906, 955378513,
+ 955379143, 955379797, 955380474, 955381174, 955381896, 955382640, 955383406, 955384192,
+ 955385000, 955385827, 955386674, 955387540, 955388424, 955389326, 955390246, 955391183,
+ 955392136, 955393105, 955394089, 955395088, 955396100, 955397126, 955398165, 955399216,
+ 955400278, 955401351, 955402435, 955403528, 955404629, 955405740, 955406857, 955407982,
+ 955409113, 955410249, 955411390, 955412535, 955413684, 955414836, 955415989, 955417144
+};
+
+/**
+ * Initialize the DDS regsiters and RAM.
+ */
+void ddsInit()
+{
+ // Setup the SPI port for the DDS interface.
+ setup_spi( SPI_MASTER | SPI_L_TO_H | SPI_CLK_DIV_4 | SPI_XMIT_L_TO_H );
+
+ // Set the initial DDS mode. The ddsSetMode function uses this value to make the desired DDS selections.
+ ddsMode = DDS_MODE_NOT_INITIALIZED;
+
+ // Set the DDS operational mode.
+ ddsSetMode (DDS_MODE_POWERDOWN);
+
+ // Set the output to full scale.
+ ddsSetOutputScale (0x3fff);
+
+ // CFR2 (Control Function Register No. 2)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_CFR2);
+
+ spi_write (0x00); // Unused register bits
+ spi_write (0x00);
+ spi_write (0x9c); // 19x reference clock multipler, high VCO range, nominal charge pump current
+ output_high (IO_CS);
+
+ // ARR (Amplitude Ramp Rate) to 15mS for OSK
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_ARR);
+
+ spi_write (83);
+ output_high (IO_CS);
+
+ // Strobe the part so we apply the updates.
+ output_high (IO_UPDATE);
+ output_low (IO_UPDATE);
+}
+
+/**
+ * Set DDS amplitude value in the range 0 to 16383 where 16383 is full scale. This value is a
+ * linear multiplier and needs to be scale for RF output power in log scale.
+ *
+ * @param scale in the range 0 to 16383
+ */
+void ddsSetOutputScale (uint16_t scale)
+{
+ // Set ASF (Amplitude Scale Factor)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_ASF);
+
+ spi_write ((scale >> 8) & 0xff);
+ spi_write (scale & 0xff);
+
+ output_high (IO_CS);
+
+ // Strobe the DDS to set the amplitude.
+ output_high (IO_UPDATE);
+ output_low (IO_UPDATE);
+}
+
+/**
+ * Set the DDS amplitude in units of dBc of full scale where 1 is 0.1 dB. For example, a value of 30 is 3dBc
+ * or a value of 85 is 8.5dBc.
+ *
+ * @param amplitude in 0.1 dBc of full scale
+ */
+void ddsSetAmplitude (uint8_t amplitude)
+{
+ // Range limit based on the lookup table size.
+ if (amplitude > 200)
+ return;
+
+ // Set the linear DDS ASF (Amplitude Scale Factor) based on the dB lookup table.
+ ddsSetOutputScale (DDS_AMP_TO_SCALE[amplitude / 5]);
+
+ // Toggle the DDS output low and then high to force it to ramp to the new output level setting.
+ output_low (IO_OSK);
+ delay_ms(25);
+
+ output_high (IO_OSK);
+ delay_ms(25);
+}
+
+/**
+ * Set DDS frequency tuning word. The output frequency is equal to RefClock * (ftw / 2 ^ 32).
+ *
+ * @param ftw Frequency Tuning Word
+ */
+void ddsSetFTW (uint32_t ftw)
+{
+ // Set FTW0 (Frequency Tuning Word 0)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_FTW0);
+
+ spi_write ((ftw >> 24) & 0xff);
+ spi_write ((ftw >> 16) & 0xff);
+ spi_write ((ftw >> 8) & 0xff);
+ spi_write (ftw & 0xff);
+
+ output_high (IO_CS);
+
+ // Strobe the DDS to set the frequency.
+ output_high (IO_UPDATE);
+ output_low (IO_UPDATE);
+}
+
+/**
+ * Convert frequency in hertz to 32-bit DDS FTW (Frequency Tune Word).
+ *
+ * @param freq frequency in Hertz
+ *
+ */
+void ddsSetFreq(uint32_t freq)
+{
+ uint8_t i;
+ uint32_t ftw;
+
+ // To avoid rounding errors with floating point math, we do a long multiply on the data.
+ ftw = freq * DDS_MULT[0];
+
+ for (i = 0; i < DDS_FREQ_TO_FTW_DIGITS - 1; ++i)
+ ftw += (freq * DDS_MULT[i+1]) / DDS_DIVISOR[i];
+
+ ddsSetFTW (ftw);
+}
+
+/**
+ * Set DDS frequency tuning word for the FSK 0 and 1 values. The output frequency is equal
+ * to RefClock * (ftw / 2 ^ 32).
+ *
+ * @param ftw0 frequency tuning word for the FSK 0 value
+ * @param ftw1 frequency tuning word for the FSK 1 value
+ */
+void ddsSetFSKFreq (uint32_t ftw0, uint32_t ftw1)
+{
+ // Set FTW0 (Frequency Tuning Word 0)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_FTW0);
+
+ spi_write ((ftw0 >> 24) & 0xff);
+ spi_write ((ftw0 >> 16) & 0xff);
+ spi_write ((ftw0 >> 8) & 0xff);
+ spi_write (ftw0 & 0xff);
+
+ output_high (IO_CS);
+
+ // Set FTW0 (Frequency Tuning Word 1)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_FTW1);
+
+ spi_write ((ftw1 >> 24) & 0xff);
+ spi_write ((ftw1 >> 16) & 0xff);
+ spi_write ((ftw1 >> 8) & 0xff);
+ spi_write (ftw1 & 0xff);
+
+ output_high (IO_CS);
+
+ // Strobe the DDS to set the frequency.
+ output_high (IO_UPDATE);
+ output_low (IO_UPDATE);
+}
+
+/**
+ * Set the DDS to run in A-FSK, FSK, or PSK31 mode
+ *
+ * @param mode DDS_MODE_APRS, DDS_MODE_PSK31, or DDS_MODE_HF_APRS constant
+ */
+void ddsSetMode (DDS_MODE mode)
+{
+ // Save the current mode.
+ ddsMode = mode;
+
+ switch (mode)
+ {
+ case DDS_MODE_POWERDOWN:
+ // CFR1 (Control Function Register No. 1)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_CFR1);
+
+ spi_write (0x00);
+ spi_write (0x00);
+ spi_write (0x00);
+ spi_write (0xf0); // Power down all subsystems.
+ output_high (IO_CS);
+ break;
+
+ case DDS_MODE_AFSK:
+ // CFR1 (Control Function Register No. 1)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_CFR1);
+
+ spi_write (0x03); // OSK Enable and Auto OSK keying
+ spi_write (0x00);
+ spi_write (0x00);
+ spi_write (0x40); // Power down comparator circuit
+ output_high (IO_CS);
+ break;
+
+ case DDS_MODE_FSK:
+ // CFR1 (Control Function Register No. 1)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_CFR1);
+
+ spi_write (0x03); // Clear RAM Enable, OSK Enable, Auto OSK keying
+ spi_write (0x00);
+ spi_write (0x00);
+ spi_write (0x40); // Power down comparator circuit
+ output_high (IO_CS);
+
+ // NOTE: The sweep rate requires 1/4 of a bit time (26uS) to transition.
+ // 6KHz delta = 70641 counts = (6KHz / 364.8MHz) * 2 ^ 32
+ // SYNC_CLK = 91.2MHz 1/91.2MHz * 70641 * 1/29 = 26.7uS
+
+ // NLSCW (Negative Linear Sweep Control Word)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_NLSCW);
+
+ spi_write (1); // Falling sweep ramp rate word
+ spi_write (0x00); // Delta frequency tuning word
+ spi_write (0x00);
+ spi_write (0x00);
+ spi_write (250);
+ output_high (IO_CS);
+
+ // PLSCW (Positive Linear Sweep Control Word)
+ output_low (IO_CS);
+ spi_write (DDS_AD9954_PLSCW);
+
+ spi_write (1); // Rising sweep ramp rate word
+ spi_write (0x00); // Delta frequency tuning word
+ spi_write (0x00);
+ spi_write (0x00);
+ spi_write (250);
+ output_high (IO_CS);
+ break;
+ } // END switch
+
+ // Strobe the DDS to change the mode.
+ output_high (IO_UPDATE);
+ output_low (IO_UPDATE);
+}
+
+/** @} */
+
+/**
+ * @defgroup flash Flash Manager
+ *
+ * Functions to control the ST MP25P80 serial flash device.
+ *
+ * @{
+ */
+
+/// Flash Chip Select - Port B3
+#define FLASH_CS PIN_B3
+
+/// Flash Clock - Port B5
+#define FLASH_CLK PIN_B5
+
+/// Flash Data Input - Port B4
+#define FLASH_D PIN_B4
+
+/// Flash Data Output - Port B2
+#define FLASH_Q PIN_B2
+
+/**
+ * Determine if a flash write or erase operation is currently in progress.
+ *
+ * @return true if write/erase in progress
+ */
+bool_t flashIsWriteInProgress()
+{
+ uint8_t status;
+
+ output_low (FLASH_CS);
+
+ // Read Status Register (RDSR) flash command.
+ flashSendByte (0x05);
+
+ status = flashGetByte();
+
+ output_high (FLASH_CS);
+
+ return (((status & 0x01) == 0x01) ? true : false);
+}
+
+/**
+ * Read a block of memory from the flash device.
+ *
+ * @param address of desired location in the range 0x00000 to 0xFFFFF (1MB)
+ * @param block pointer to locate of data block
+ * @param length number of bytes to read
+ */
+void flashReadBlock(uint32_t address, uint8_t *block, uint16_t length)
+{
+ uint16_t i;
+
+ output_low (FLASH_CS);
+
+ // Read Data Byte(s) (READ) flash command.
+ flashSendByte (0x03);
+ flashSendAddress (address);
+
+ for (i = 0; i < length; ++i)
+ *block++ = flashGetByte();
+
+ output_high (FLASH_CS);
+}
+
+/**
+ * Write a block of memory to the flash device.
+ *
+ * @param address of desired location in the range 0x00000 to 0xFFFFF (1MB)
+ * @param block pointer data block to write
+ * @param length number of bytes to write
+ */
+void flashWriteBlock(uint32_t address, uint8_t *block, uint8_t length)
+{
+ uint8_t i;
+
+ output_low (FLASH_CS);
+ // Write Enable (WREN) flash command.
+ flashSendByte (0x06);
+ output_high (FLASH_CS);
+
+ output_low (FLASH_CS);
+ // Page Program (PP) flash command.
+ flashSendByte (0x02);
+ flashSendAddress (address);
+
+ for (i = 0; i < length; ++i)
+ {
+ // Send each byte in the data block.
+ flashSendByte (*block++);
+
+ // Track the address in the flash device.
+ ++address;
+
+ // If we cross a page boundary (a page is 256 bytes) we need to stop and send the address again.
+ if ((address & 0xff) == 0x00)
+ {
+ output_high (FLASH_CS);
+
+ // Write this block of data.
+ while (flashIsWriteInProgress());
+
+ output_low (FLASH_CS);
+ // Write Enable (WREN) flash command.
+ flashSendByte (0x06);
+ output_high (FLASH_CS);
+
+ output_low (FLASH_CS);
+ // Page Program (PP) flash command.
+ flashSendByte (0x02);
+ flashSendAddress (address);
+ } // END if
+ } // END for
+
+ output_high (FLASH_CS);
+
+ // Wait for the final write operation to complete.
+ while (flashIsWriteInProgress());
+}
+
+/**
+ * Erase the entire flash device (all locations set to 0xff).
+ */
+void flashErase()
+{
+ output_low (FLASH_CS);
+ // Write Enable (WREN) flash command.
+ flashSendByte (0x06);
+ output_high (FLASH_CS);
+
+ output_low (FLASH_CS);
+ // Bulk Erase (BE) flash command.
+ flashSendByte (0xc7);
+ output_high (FLASH_CS);
+
+ while (flashIsWriteInProgress());
+}
+
+/**
+ * Read a single byte from the flash device through the serial interface. This function
+ * only controls the clock line. The chip select must be configured before calling
+ * this function.
+ *
+ * @return byte read from device
+ */
+uint8_t flashGetByte()
+{
+ uint8_t i, value;
+
+ value = 0;
+
+ // Bit bang the 8-bits.
+ for (i = 0; i < 8; ++i)
+ {
+ // Data is ready on the rising edge of the clock.
+ output_high (FLASH_CLK);
+
+ // MSB is first, so shift left.
+ value = value << 1;
+
+ if (input (FLASH_Q))
+ value = value | 0x01;
+
+ output_low (FLASH_CLK);
+ } // END for
+
+ return value;
+}
+
+/**
+ * Initialize the flash memory subsystem.
+ */
+void flashInit()
+{
+ // I/O lines to control flash.
+ output_high (FLASH_CS);
+ output_low (FLASH_CLK);
+ output_low (FLASH_D);
+}
+
+/**
+ * Write a single byte to the flash device through the serial interface. This function
+ * only controls the clock line. The chip select must be configured before calling
+ * this function.
+ *
+ * @param value byte to write to device
+ */
+void flashSendByte(uint8_t value)
+{
+ uint8_t i;
+
+ // Bit bang the 8-bits.
+ for (i = 0; i < 8; ++i)
+ {
+ // Drive the data input pin.
+ if ((value & 0x80) == 0x80)
+ output_high (FLASH_D);
+ else
+ output_low (FLASH_D);
+
+ // MSB is first, so shift leeft.
+ value = value << 1;
+
+ // Data is accepted on the rising edge of the clock.
+ output_high (FLASH_CLK);
+ output_low (FLASH_CLK);
+ } // END for
+}
+
+/**
+ * Write the 24-bit address to the flash device through the serial interface. This function
+ * only controls the clock line. The chip select must be configured before calling
+ * this function.
+ *
+ * @param address 24-bit flash device address
+ */
+void flashSendAddress(uint32_t address)
+{
+ uint8_t i;
+
+ // Bit bang the 24-bits.
+ for (i = 0; i < 24; ++i)
+ {
+ // Drive the data input pin.
+ if ((address & 0x800000) == 0x800000)
+ output_high (FLASH_D);
+ else
+ output_low (FLASH_D);
+
+ // MSB is first, so shift left.
+ address = address << 1;
+
+ // Data is accepted on the rising edge of the clock.
+ output_high (FLASH_CLK);
+ output_low (FLASH_CLK);
+ } // END for
+}
+
+/** @} */
+
+/**
+ * @defgroup GPS Motorola M12+ GPS Engine
+ *
+ * Functions to control the Motorola M12+ GPS engine in native binary protocol mode.
+ *
+ * @{
+ */
+
+/// The maximum length of a binary GPS engine message.
+#define GPS_BUFFER_SIZE 50
+
+/// GPS parse engine state machine values.
+enum GPS_PARSE_STATE_MACHINE
+{
+ /// 1st start character '@'
+ GPS_START1,
+
+ /// 2nd start character '@'
+ GPS_START2,
+
+ /// Upper case 'A' - 'Z' message type
+ GPS_COMMAND1,
+
+ /// Lower case 'a' - 'z' message type
+ GPS_COMMAND2,
+
+ /// 0 - xx bytes based on message type 'Aa'
+ GPS_READMESSAGE,
+
+ /// 8-bit checksum
+ GPS_CHECKSUMMESSAGE,
+
+ /// End of message - Carriage Return
+ GPS_EOMCR,
+
+ /// End of message - Line Feed
+ GPS_EOMLF
+};
+
+/// Index into gpsBuffer used to store message data.
+uint8_t gpsIndex;
+
+/// State machine used to parse the GPS message stream.
+GPS_PARSE_STATE_MACHINE gpsParseState;
+
+/// Buffer to store data as it is read from the GPS engine.
+uint8_t gpsBuffer[GPS_BUFFER_SIZE];
+
+/// Peak altitude detected while GPS is in 3D fix mode.
+int32_t gpsPeakAltitude;
+
+/// Checksum used to verify binary message from GPS engine.
+uint8_t gpsChecksum;
+
+/// Last verified GPS message received.
+GPSPOSITION_STRUCT gpsPosition;
+
+/**
+ * Get the type of fix.
+ *
+ * @return gps fix type enumeration
+ */
+GPS_FIX_TYPE gpsGetFixType()
+{
+ // The upper 3-bits determine the fix type.
+ switch (gpsPosition.status & 0xe000)
+ {
+ case 0xe000:
+ return GPS_3D_FIX;
+
+ case 0xc000:
+ return GPS_2D_FIX;
+
+ default:
+ return GPS_NO_FIX;
+ } // END switch
+}
+
+/**
+ * Peak altitude detected while GPS is in 3D fix mode since the system was booted.
+ *
+ * @return altitude in feet
+ */
+int32_t gpsGetPeakAltitude()
+{
+ return gpsPeakAltitude;
+}
+
+/**
+ * Initialize the GPS subsystem.
+ */
+void gpsInit()
+{
+ // Initial parse state.
+ gpsParseState = GPS_START1;
+
+ // Assume we start at sea level.
+ gpsPeakAltitude = 0;
+
+ // Clear the structure that stores the position message.
+ memset (&gpsPosition, 0, sizeof(GPSPOSITION_STRUCT));
+
+ // Setup the timers used to measure the 1-PPS time period.
+ setup_timer_3(T3_INTERNAL | T3_DIV_BY_1);
+ setup_ccp2 (CCP_CAPTURE_RE | CCP_USE_TIMER3);
+}
+
+/**
+ * Determine if new GPS message is ready to process. This function is a one shot and
+ * typically returns true once a second for each GPS position fix.
+ *
+ * @return true if new message available; otherwise false
+ */
+bool_t gpsIsReady()
+{
+ if (gpsPosition.updateFlag)
+ {
+ gpsPosition.updateFlag = false;
+ return true;
+ } // END if
+
+ return false;
+}
+
+/**
+ * Calculate NMEA-0183 message checksum of buffer that is length bytes long.
+ *
+ * @param buffer pointer to data buffer.
+ * @param length number of bytes in buffer.
+ *
+ * @return checksum of buffer
+ */
+uint8_t gpsNMEAChecksum (uint8_t *buffer, uint8_t length)
+{
+ uint8_t i, checksum;
+
+ checksum = 0;
+
+ for (i = 0; i < length; ++i)
+ checksum ^= buffer[i];
+
+ return checksum;
+}
+
+/**
+ * Verify the GPS engine is sending the @@Hb position report message. If not,
+ * configure the GPS engine to send the desired report.
+ *
+ * @return true if GPS engine operation; otherwise false
+ */
+bool_t gpsSetup()
+{
+ uint8_t startTime, retryCount;
+
+ // We wait 10 seconds for the GPS engine to respond to our message request.
+ startTime = timeGetTicks();
+ retryCount = 0;
+
+ while (++retryCount < 10)
+ {
+ // Read the serial FIFO and process the GPS messages.
+ gpsUpdate();
+
+ // If a GPS data set is available, then GPS is operational.
+ if (gpsIsReady())
+ {
+ timeSetDutyCycle (TIME_DUTYCYCLE_10);
+ return true;
+ }
+
+ if (timeGetTicks() > startTime)
+ {
+ puts ("@@Hb\001\053\015\012");
+ startTime += 10;
+ } // END if
+
+ } // END while
+
+ return false;
+}
+
+/**
+ * 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 = ((uint16_t) 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.altitudeCM = ((int32) gpsBuffer[19] << 24) | ((int32) gpsBuffer[20] << 16) | ((int32) gpsBuffer[21] << 8) | gpsBuffer[22];
+ gpsPosition.altitudeFeet = gpsPosition.altitudeCM * 100l / 3048l;
+ gpsPosition.vSpeed = ((uint16_t) gpsBuffer[27] << 8) | gpsBuffer[28];
+ gpsPosition.hSpeed = ((uint16_t) gpsBuffer[29] << 8) | gpsBuffer[30];
+ gpsPosition.heading = ((uint16_t) gpsBuffer[31] << 8) | gpsBuffer[32];
+ gpsPosition.dop = ((uint16_t) gpsBuffer[33] << 8) | gpsBuffer[34];
+ gpsPosition.visibleSats = gpsBuffer[35];
+ gpsPosition.trackedSats = gpsBuffer[36];
+ gpsPosition.status = ((uint16_t) gpsBuffer[37] << 8) | gpsBuffer[38];
+
+ // Update the peak altitude if we have a valid 3D fix.
+ if (gpsGetFixType() == GPS_3D_FIX)
+ if (gpsPosition.altitudeFeet > gpsPeakAltitude)
+ gpsPeakAltitude = gpsPosition.altitudeFeet;
+}
+
+/**
+ * Turn on the GPS engine power and serial interface.
+ */
+void gpsPowerOn()
+{
+ // 3.0 VDC LDO control line.
+ output_high (IO_GPS_PWR);
+
+ // Enable the UART and the transmit line.
+#asm
+ bsf 0xFAB.7
+#endasm
+}
+
+/**
+ * Turn off the GPS engine power and serial interface.
+ */
+void gpsPowerOff()
+{
+ // Disable the UART and the transmit line.
+#asm
+ bcf 0xFAB.7
+#endasm
+
+ // 3.0 VDC LDO control line.
+ output_low (IO_GPS_PWR);
+}
+
+/**
+ * Read the serial FIFO and process complete GPS messages.
+ */
+void gpsUpdate()
+{
+ uint8_t 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
+ while (serialHasData())
+ {
+ // Get the character value.
+ value = serialRead();
+
+ // Process based on the state machine.
+ switch (gpsParseState)
+ {
+ case GPS_START1:
+ if (value == '@')
+ gpsParseState = GPS_START2;
+ break;
+
+ case GPS_START2:
+ if (value == '@')
+ gpsParseState = GPS_COMMAND1;
+ else
+ gpsParseState = GPS_START1;
+ break;
+
+ case GPS_COMMAND1:
+ if (value == 'H')
+ gpsParseState = GPS_COMMAND2;
+ else
+ gpsParseState = GPS_START1;
+ break;
+
+ case GPS_COMMAND2:
+ if (value == 'b')
+ {
+ gpsParseState = GPS_READMESSAGE;
+ gpsIndex = 0;
+ gpsChecksum = 0;
+ gpsChecksum ^= 'H';
+ gpsChecksum ^= 'b';
+ } else
+ gpsParseState = GPS_START1;
+ break;
+
+ case GPS_READMESSAGE:
+ gpsChecksum ^= value;
+ gpsBuffer[gpsIndex++] = value;
+
+ if (gpsIndex == 47)
+ gpsParseState = GPS_CHECKSUMMESSAGE;
+
+ break;
+
+ case GPS_CHECKSUMMESSAGE:
+ if (gpsChecksum == value)
+ gpsParseState = GPS_EOMCR;
+ else
+ gpsParseState = GPS_START1;
+ break;
+
+ case GPS_EOMCR:
+ if (value == 13)
+ gpsParseState = GPS_EOMLF;
+ else
+ gpsParseState = GPS_START1;
+ break;
+
+ case GPS_EOMLF:
+ // Once we have the last character, convert the binary message to something usable.
+ if (value == 10)
+ gpsParsePositionMessage();
+
+ gpsParseState = GPS_START1;
+ break;
+ } // END switch
+ } // END while
+}
+
+/** @} */
+
+
+/**
+ * @defgroup log Flight Data Recorder
+ *
+ * Functions to manage and control the flight data recorder
+ *
+ * @{
+ */
+
+/// Number of bytes to buffer before writing to flash memory.
+#define LOG_WRITE_BUFFER_SIZE 40
+
+/// Last used address in flash memory.
+uint32_t logAddress;
+
+/// Temporary buffer that holds data before it is written to flash device.
+uint8_t logBuffer[LOG_WRITE_BUFFER_SIZE];
+
+/// Current index into log buffer.
+uint8_t logIndex;
+
+/**
+ * Last used address in flash memory. This location is where the next log data will
+ * be written.
+ *
+ * @return 24-bit flash memory address
+ */
+uint32_t logGetAddress()
+{
+ return logAddress;
+}
+
+/**
+ * Write the contents of the temporary log buffer to the flash device. If the buffer
+ * is empty, nothing is done.
+ */
+void logFlush()
+{
+ // We only need to write if there is data.
+ if (logIndex != 0)
+ {
+ flashWriteBlock (logAddress, logBuffer, logIndex);
+ logAddress += logIndex;
+ logIndex = 0;
+ } // END if
+}
+
+/**
+ * Prepare the flight data recorder for logging.
+ */
+void logInit()
+{
+ uint8_t buffer[8];
+ bool_t endFound;
+
+ fprintf (PC_HOST, "Searching for end of flash log...");
+
+ logAddress = 0x0000;
+ endFound = false;
+
+ // Read each logged data block from flash to determine how long it is.
+ do
+ {
+ // Read the data log entry type.
+ flashReadBlock (logAddress, buffer, 1);
+
+ // Based on the log entry type, we'll skip over the data contained in the entry.
+ switch (buffer[0])
+ {
+ case LOG_BOOTED:
+ logAddress += 7;
+ break;
+
+ case LOG_COORD:
+ logAddress += 26;
+ break;
+
+ case LOG_TEMPERATURE:
+ logAddress += 3;
+ break;
+
+ case LOG_VOLTAGE:
+ logAddress += 5;
+ break;
+
+ case 0xff:
+ endFound = true;
+ break;
+
+ default:
+ ++logAddress;
+ } // END switch
+ } while (logAddress < 0x100000 && !endFound);
+
+ fprintf (PC_HOST, "done. Log contains %ld bytes.\n\r", logAddress);
+
+ logIndex = 0;
+}
+
+/**
+ * Start a entry in the data log.
+ *
+ * @param type of log entry, i.e. LOG_BOOTED, LOG_COORD, etc.
+ */
+void logType (LOG_TYPE type)
+{
+ // Only add the new entry if there is space.
+ if (logAddress >= 0x100000)
+ return;
+
+ // Write the old entry first.
+ logFlush();
+
+ // Save the type and set the log buffer pointer.
+ logBuffer[0] = type;
+ logIndex = 1;
+}
+
+/**
+ * Save an unsigned, 8-bit value in the log.
+ *
+ * @param value unsigned, 8-bit value
+ */
+void logUint8 (uint8_t value)
+{
+ logBuffer[logIndex++] = value;
+}
+
+/**
+ * Save a signed, 16-bit value in the log.
+ *
+ * @param value signed, 16-bit value
+ */
+void logInt16 (int16_t value)
+{
+ logBuffer[logIndex++] = (value >> 8) & 0xff;
+ logBuffer[logIndex++] = value & 0xff;
+}
+
+/**
+ * Save an unsigned, 16-bit value in the log.
+ *
+ * @param value unsigned, 16-bit value
+ */
+void logUint16 (uint16_t value)
+{
+ logBuffer[logIndex++] = (value >> 8) & 0xff;
+ logBuffer[logIndex++] = value & 0xff;
+}
+
+/**
+ * Save a signed, 32-bit value in the log.
+ *
+ * @param value signed, 32-bit value
+ */
+void logInt32 (int32_t value)
+{
+ logBuffer[logIndex++] = (value >> 24) & 0xff;
+ logBuffer[logIndex++] = (value >> 16) & 0xff;
+ logBuffer[logIndex++] = (value >> 8) & 0xff;
+ logBuffer[logIndex++] = value & 0xff;
+}
+
+/** @} */
+
+/**
+ * @defgroup LM92 LM92 temperature sensor
+ *
+ * Read and control the National Semiconductor LM92 I2C temperature sensor
+ *
+ * @{
+ */
+
+/**
+ * Read the LM92 temperature value in 0.1 degrees F.
+ *
+ * @return 0.1 degrees F
+ */
+int16_t lm92GetTemp()
+{
+ int16_t value;
+ int32_t temp;
+
+ // Set the SDA and SCL to input pins to control the LM92.
+ set_tris_c (0x9a);
+
+ // Read the temperature register value.
+ i2c_start();
+ i2c_write(0x97);
+ value = ((int16_t) i2c_read() << 8);
+ value = value | ((int16_t) i2c_read() & 0x00f8);
+ i2c_stop();
+
+ // Set the SDA and SCL back to outputs for use with the AD9954 because we share common clock pins.
+ set_tris_c (0x82);
+
+ // LM92 register 0.0625degC/bit 9 10 9
+ // ------------- * -------------- * - * -- = -- + 320
+ // 8 5 64
+
+ // Convert to degrees F.
+ temp = (int32_t) value;
+ temp = ((temp * 9l) / 64l) + 320;
+
+ return (int16_t) temp;
+}
+
+/** @} */
+
+
+/**
+ * @defgroup serial Serial Port FIFO
+ *
+ * FIFO for the built-in serial port.
+ *
+ * @{
+ */
+
+/// Size of serial port FIFO in bytes. It must be a power of 2, i.e. 2, 4, 8, 16, etc.
+#define SERIAL_BUFFER_SIZE 64
+
+/// Mask to wrap around at end of circular buffer. (SERIAL_BUFFER_SIZE - 1)
+#define SERIAL_BUFFER_MASK 0x3f
+
+/// Index to the next free location in the buffer.
+uint8_t serialHead;
+
+/// Index to the next oldest data in the buffer.
+uint8_t serialTail;
+
+/// Circular buffer (FIFO) to hold serial data.
+uint8_t serialBuffer[SERIAL_BUFFER_SIZE];
+
+/**
+ * Determine if the FIFO contains data.
+ *
+ * @return true if data present; otherwise false
+ */
+bool_t serialHasData()
+{
+ if (serialHead == serialTail)
+ return false;
+
+ return true;
+}
+
+/**
+ * Initialize the serial processor.
+ */
+void serialInit()
+{
+ serialHead = 0;
+ serialTail = 0;
+}
+
+/**
+ * Get the oldest character from the FIFO.
+ *
+ * @return oldest character; 0 if FIFO is empty
+ */
+uint8_t serialRead()
+{
+ uint8_t 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;
+}
+
+/**
+ * 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.
+ while (kbhit())
+ {
+ // Save the value in the FIFO.
+ serialBuffer[serialHead] = getc();
+
+ // Move the pointer to the next open space.
+ serialHead = (serialHead + 1) & SERIAL_BUFFER_MASK;
+ }
+}
+
+/** @} */
+
+/**
+ * @defgroup sys System Library Functions
+ *
+ * Generic system functions similiar to the run-time C library.
+ *
+ * @{
+ */
+
+/**
+ * Calculate the CRC-16 CCITT of buffer that is length bytes long.
+ * The crc 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_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc)
+{
+ uint8_t 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;
+}
+
+/**
+ * Initialize the system library and global resources.
+ */
+void sysInit()
+{
+ gpsPowerOff ();
+ output_high (IO_LED);
+
+ output_high (IO_CS);
+ output_low (IO_PS1);
+ output_low (IO_PS0);
+ output_low (IO_OSK);
+ output_low (IO_UPDATE);
+ output_low (IO_PTT);
+ output_low (IO_GPS_TXD);
+
+ // Configure the port direction (input/output).
+ set_tris_a (0xc3);
+ set_tris_b (0x44);
+ set_tris_c (0x82);
+
+ // Display a startup message during boot.
+ fprintf (PC_HOST, "System booted.\n\r");
+}
+
+/**
+ * Log the current GPS position.
+ */
+void sysLogGPSData()
+{
+ // Log the data.
+ logType (LOG_COORD);
+ logUint8 (gpsPosition.hours);
+ logUint8 (gpsPosition.minutes);
+ logUint8 (gpsPosition.seconds);
+ logInt32 (gpsPosition.latitude);
+ logInt32 (gpsPosition.longitude);
+ logInt32 (gpsPosition.altitudeCM);
+
+ logUint16 (gpsPosition.vSpeed);
+ logUint16 (gpsPosition.hSpeed);
+ logUint16 (gpsPosition.heading);
+
+ logUint16 (gpsPosition.status);
+
+ logUint8 ((uint8_t) (gpsPosition.dop & 0xff));
+ logUint8 ((uint8_t) ((gpsPosition.visibleSats << 4) | gpsPosition.trackedSats));
+}
+
+/**
+ * Log the ADC values of the bus and reference voltage values.
+ */
+void sysLogVoltage()
+{
+ logType (LOG_VOLTAGE);
+ logUint16 (adcRawBusVolt());
+ logUint16 (adcRawRefVolt());
+}
+
+/** @} */
+
+/**
+ * @defgroup rtc Real Time Interrupt tick
+ *
+ * Manage the built-in real time interrupt. The interrupt clock PRI is 104uS (9600 bps).
+ *
+ * @{
+ */
+
+/// A counter that ticks every 100mS.
+uint8_t timeTicks;
+
+/// Counts the number of 104uS interrupts for a 100mS time period.
+uint16_t timeInterruptCount;
+
+/// Counts the number of 100mS time periods in 1 second.
+uint8_t time100ms;
+
+/// System time in seconds.
+uint8_t timeSeconds;
+
+/// System time in minutes.
+uint8_t timeMinutes;
+
+/// System time in hours.
+uint8_t timeHours;
+
+/// Desired LED duty cycle 0 to 9 where 0 = 0% and 9 = 90%.
+uint8_t timeDutyCycle;
+
+/// Current value of the timer 1 compare register used to generate 104uS interrupt rate (9600bps).
+uint16_t timeCompare;
+
+/// 16-bit NCO where the upper 8-bits are used to index into the frequency generation table.
+uint16_t timeNCO;
+
+/// Audio tone NCO update step (phase).
+uint16_t timeNCOFreq;
+
+/// Counter used to deciminate down from the 104uS to 833uS interrupt rate. (9600 to 1200 baud)
+uint8_t timeLowRateCount;
+
+/// Current TNC mode (standby, 1200bps A-FSK, or 9600bps FSK)
+TNC_DATA_MODE tncDataMode;
+
+/// Flag set true once per second.
+bool_t timeUpdateFlag;
+
+/// Flag that indicate the flight time should run.
+bool_t timeRunFlag;
+
+/// The change in the CCP_1 register for each 104uS (9600bps) interrupt period.
+#define TIME_RATE 125
+
+/**
+ * Running 8-bit counter that ticks every 100mS.
+ *
+ * @return 100mS time tick
+ */
+uint8_t timeGetTicks()
+{
+ return timeTicks;
+}
+
+/**
+ * Initialize the real-time clock.
+ */
+void timeInit()
+{
+ timeTicks = 0;
+ timeInterruptCount = 0;
+ time100mS = 0;
+ timeSeconds = 0;
+ timeMinutes = 0;
+ timeHours = 0;
+ timeDutyCycle = TIME_DUTYCYCLE_70;
+ timeCompare = TIME_RATE;
+ timeUpdateFlag = false;
+ timeNCO = 0x00;
+ timeLowRateCount = 0;
+ timeNCOFreq = 0x2000;
+ tncDataMode = TNC_MODE_STANDBY;
+ timeRunFlag = false;
+
+ // Configure CCP1 to interrupt at 1mS for PSK31 or 833uS for 1200 baud APRS
+ CCP_1 = TIME_RATE;
+ set_timer1(timeCompare);
+ setup_ccp1( CCP_COMPARE_INT );
+ setup_timer_1( T1_INTERNAL | T1_DIV_BY_4 );
+}
+
+/**
+ * Function return true once a second based on real-time clock.
+ *
+ * @return true on one second tick; otherwise false
+ */
+bool_t timeIsUpdate()
+{
+ if (timeUpdateFlag)
+ {
+ timeUpdateFlag = false;
+ return true;
+ } // END if
+
+ return false;
+}
+
+/**
+ * Set the blink duty cycle of the heartbeat LED. The LED blinks at a 1Hz rate.
+ *
+ * @param dutyCycle TIME_DUTYCYCLE_xx constant
+ */
+void timeSetDutyCycle (uint8_t dutyCycle)
+{
+ timeDutyCycle = dutyCycle;
+}
+
+/**
+ * Set a flag to indicate the flight time should run. This flag is typically set when the payload
+ * lifts off.
+ */
+void timeSetRunFlag()
+{
+ timeRunFlag = true;
+}
+
+#INT_CCP1
+/**
+ * Timer interrupt handler called every 104uS (9600 times/second).
+ */
+void timeUpdate()
+{
+ // Setup the next interrupt for the operational mode.
+ timeCompare += TIME_RATE;
+ CCP_1 = timeCompare;
+
+ switch (tncDataMode)
+ {
+ case TNC_MODE_STANDBY:
+ break;
+
+ case TNC_MODE_1200_AFSK:
+ ddsSetFTW (freqTable[timeNCO >> 8]);
+
+ timeNCO += timeNCOFreq;
+
+ if (++timeLowRateCount == 8)
+ {
+ timeLowRateCount = 0;
+ tnc1200TimerTick();
+ } // END if
+ break;
+
+ case TNC_MODE_9600_FSK:
+ tnc9600TimerTick();
+ break;
+ } // END switch
+
+ // Read the GPS serial port and save any incoming characters.
+ serialUpdate();
+
+ // Count the number of milliseconds required for the tenth second counter.
+ if (++timeInterruptCount == 960)
+ {
+ 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 (timeRunFlag)
+ if (++timeSeconds == 60)
+ {
+ timeSeconds = 0;
+
+ if (++timeMinutes == 60)
+ {
+ timeMinutes = 0;
+ ++timeHours;
+ } // END if timeMinutes
+ } // END if timeSeconds
+ } // END if time100mS
+
+ // Flash the status LED at timeDutyCycle % per second. We use the duty cycle for mode feedback.
+ if (time100mS >= timeDutyCycle)
+ output_low (IO_LED);
+ else
+ output_high (IO_LED);
+ } // END if
+}
+
+/** @} */
+
+/**
+ * @defgroup tnc TNC (Terminal Node Controller)
+ *
+ * Functions that provide a subset of the TNC functions.
+ *
+ * @{
+ */
+
+/// The number of start flag bytes to send before the packet message. (360bits * 1200bps = 300mS)
+#define TNC_TX_DELAY 45
+
+/// The size of the TNC output buffer.
+#define TNC_BUFFER_SIZE 80
+
+/// States that define the current mode of the 1200 bps (A-FSK) state machine.
+enum TNC_TX_1200BPS_STATE
+{
+ /// Stand by state ready to accept new message.
+ TNC_TX_READY,
+
+ /// 0x7E bit stream pattern used to define start of APRS message.
+ TNC_TX_SYNC,
+
+ /// Transmit the AX.25 header that contains the source/destination call signs, APRS path, and flags.
+ TNC_TX_HEADER,
+
+ /// Transmit the message data.
+ TNC_TX_DATA,
+
+ /// Transmit the end flag sequence.
+ TNC_TX_END
+};
+
+/// Enumeration of the messages we can transmit.
+enum TNC_MESSAGE_TYPE
+{
+ /// Startup message that contains software version information.
+ TNC_BOOT_MESSAGE,
+
+ /// Plain text status message.
+ TNC_STATUS,
+
+ /// Message that contains GPS NMEA-0183 $GPGGA message.
+ TNC_GGA,
+
+ /// Message that contains GPS NMEA-0183 $GPRMC message.
+ TNC_RMC
+};
+
+/// AX.25 compliant packet header that contains destination, station call sign, and path.
+/// 0x76 for SSID-11, 0x78 for SSID-12
+uint8_t 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 };
+
+
+/// The next bit to transmit.
+uint8_t tncTxBit;
+
+/// Current mode of the 1200 bps state machine.
+TNC_TX_1200BPS_STATE tncMode;
+
+/// Counter for each bit (0 - 7) that we are going to transmit.
+uint8_t tncBitCount;
+
+/// A shift register that holds the data byte as we bit shift it for transmit.
+uint8_t tncShift;
+
+/// Index into the APRS header and data array for each byte as we transmit it.
+uint8_t tncIndex;
+
+/// The number of bytes in the message portion of the AX.25 message.
+uint8_t tncLength;
+
+/// A copy of the last 5 bits we've transmitted to determine if we need to bit stuff on the next bit.
+uint8_t tncBitStuff;
+
+/// Pointer to TNC buffer as we save each byte during message preparation.
+uint8_t *tncBufferPnt;
+
+/// The type of message to tranmit in the next packet.
+TNC_MESSAGE_TYPE tncPacketType;
+
+/// Buffer to hold the message portion of the AX.25 packet as we prepare it.
+uint8_t tncBuffer[TNC_BUFFER_SIZE];
+
+/// Flag that indicates we want to transmit every 5 seconds.
+bool_t tncHighRateFlag;
+
+/**
+ * Initialize the TNC internal variables.
+ */
+void tncInit()
+{
+ tncTxBit = 0;
+ tncMode = TNC_TX_READY;
+ tncPacketType = TNC_BOOT_MESSAGE;
+ tncHighRateFlag = false;
+}
+
+/**
+ * Determine if the hardware if ready to transmit a 1200 baud packet.
+ *
+ * @return true if ready; otherwise false
+ */
+bool_t tncIsFree()
+{
+ if (tncMode == TNC_TX_READY)
+ return true;
+
+ return false;
+}
+
+void tncHighRate(bool_t state)
+{
+ tncHighRateFlag = state;
+}
+
+/**
+ * Configure the TNC for the desired data mode.
+ *
+ * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK
+ */
+void tncSetMode(TNC_DATA_MODE dataMode)
+{
+ switch (dataMode)
+ {
+ case TNC_MODE_1200_AFSK:
+ ddsSetMode (DDS_MODE_AFSK);
+ break;
+
+ case TNC_MODE_9600_FSK:
+ ddsSetMode (DDS_MODE_FSK);
+
+ // FSK tones at 445.947 and 445.953 MHz
+ ddsSetFSKFreq (955382980, 955453621);
+ break;
+ } // END switch
+
+ tncDataMode = dataMode;
+}
+
+/**
+ * Determine if the seconds value timeSeconds is a valid time slot to transmit
+ * a message. Time seconds is in UTC.
+ *
+ * @param timeSeconds UTC time in seconds
+ *
+ * @return true if valid time slot; otherwise false
+ */
+bool_t tncIsTimeSlot (uint8_t timeSeconds)
+{
+ if (tncHighRateFlag)
+ {
+ if ((timeSeconds % 5) == 0)
+ return true;
+
+ return false;
+ } // END if
+
+ switch (timeSeconds)
+ {
+ case 0:
+ case 15:
+ case 30:
+ case 45:
+ return true;
+
+ default:
+ return false;
+ } // END switch
+}
+
+/**
+ * Method that is called every 833uS to transmit the 1200bps A-FSK data stream.
+ * The provides the pre and postamble as well as the bit stuffed data stream.
+ */
+void tnc1200TimerTick()
+{
+ // Set the A-FSK frequency.
+ if (tncTxBit == 0x00)
+ timeNCOFreq = 0x2000;
+ else
+ timeNCOFreq = 0x3aab;
+
+ switch (tncMode)
+ {
+ case TNC_TX_READY:
+ // Generate a test signal alteranting between high and low tones.
+ tncTxBit = (tncTxBit == 0 ? 1 : 0);
+ break;
+
+ case TNC_TX_SYNC:
+ // The variable tncShift contains the lastest data byte.
+ // NRZI enocde the data stream.
+ if ((tncShift & 0x01) == 0x00)
+ if (tncTxBit == 0)
+ tncTxBit = 1;
+ else
+ tncTxBit = 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 (tncTxBit == 0)
+ tncTxBit = 1;
+ else
+ tncTxBit = 0;
+
+ tncBitStuff = 0x00;
+ return;
+ } // END if
+
+ // The variable tncShift contains the lastest data byte.
+ // NRZI enocde the data stream.
+ if ((tncShift & 0x01) == 0x00)
+ if (tncTxBit == 0)
+ tncTxBit = 1;
+ else
+ tncTxBit = 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 = tncBuffer[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 (tncTxBit == 0)
+ tncTxBit = 1;
+ else
+ tncTxBit = 0;
+
+ tncBitStuff = 0x00;
+ return;
+ } // END if
+
+ // The variable tncShift contains the lastest data byte.
+ // NRZI enocde the data stream.
+ if ((tncShift & 0x01) == 0x00)
+ if (tncTxBit == 0)
+ tncTxBit = 1;
+ else
+ tncTxBit = 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 = 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 (tncTxBit == 0)
+ tncTxBit = 1;
+ else
+ tncTxBit = 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;
+
+ // Tell the TNC time interrupt to stop generating the frequency words.
+ tncDataMode = TNC_MODE_STANDBY;
+
+ // Key off the DDS.
+ output_low (IO_OSK);
+ output_low (IO_PTT);
+ ddsSetMode (DDS_MODE_POWERDOWN);
+
+ return;
+ } // END if
+ } else
+ tncShift = tncShift >> 1;
+
+ break;
+ } // END switch
+}
+
+/**
+ * Method that is called every 104uS to transmit the 9600bps FSK data stream.
+ */
+void tnc9600TimerTick()
+{
+
+}
+
+/**
+ * Write character to the TNC 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 character to save to telemetry buffer
+ */
+void tncTxByte (uint8_t character)
+{
+ *tncBufferPnt++ = character;
+ ++tncLength;
+}
+
+/**
+ * Generate the GPS NMEA standard UTC time stamp. Data is written through the tncTxByte
+ * callback function.
+ */
+void tncNMEATime()
+{
+ // UTC of position fix.
+ printf (tncTxByte, "%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds);
+}
+
+/**
+ * Generate the GPS NMEA standard latitude/longitude fix. Data is written through the tncTxByte
+ * callback function.
+ */
+void tncNMEAFix()
+{
+ uint8_t dirChar;
+ uint32_t coord, coordMin;
+
+ // Latitude value.
+ coord = gpsPosition.latitude;
+
+ if (gpsPosition.latitude < 0)
+ {
+ coord = gpsPosition.latitude * -1;
+ dirChar = 'S';
+ } else {
+ coord = gpsPosition.latitude;
+ dirChar = 'N';
+ }
+
+ coordMin = (coord % 3600000) / 6;
+ printf (tncTxByte, "%02ld%02ld.%04ld,%c,", (uint32_t) (coord / 3600000), (uint32_t) (coordMin / 10000), (uint32_t) (coordMin % 10000), dirChar);
+
+
+ // Longitude value.
+ if (gpsPosition.longitude < 0)
+ {
+ coord = gpsPosition.longitude * - 1;
+ dirChar = 'W';
+ } else {
+ coord = gpsPosition.longitude;
+ dirChar = 'E';
+ }
+
+ coordMin = (coord % 3600000) / 6;
+ printf (tncTxByte, "%03ld%02ld.%04ld,%c,", (uint32_t) (coord / 3600000), (uint32_t) (coordMin / 10000), (uint32_t) (coordMin % 10000), dirChar);
+
+}
+
+/**
+ * Generate the GPS NMEA-0183 $GPGGA packet. Data is written through the tncTxByte
+ * callback function.
+ */
+void tncGPGGAPacket()
+{
+ // Generate the GPGGA message.
+ printf (tncTxByte, "$GPGGA,");
+
+ // Standard NMEA time.
+ tncNMEATime();
+
+ // Standard NMEA-0183 latitude/longitude.
+ tncNMEAFix();
+
+ // GPS status where 0: not available, 1: available
+ if (gpsGetFixType() != GPS_NO_FIX)
+ printf (tncTxByte, "1,");
+ else
+ printf (tncTxByte, "0,");
+
+ // Number of visible birds.
+ printf (tncTxByte, "%02d,", gpsPosition.trackedSats);
+
+ // DOP
+ printf (tncTxByte, "%ld.%01ld,", gpsPosition.dop / 10, gpsPosition.dop % 10);
+
+ // Altitude in meters.
+ printf (tncTxByte, "%ld.%02ld,M,,M,,", (int32_t) (gpsPosition.altitudeCM / 100l), (int32_t) (gpsPosition.altitudeCM % 100));
+
+ // Checksum, we add 1 to skip over the $ character.
+ printf (tncTxByte, "*%02X", gpsNMEAChecksum(tncBuffer + 1, tncLength - 1));
+}
+
+/**
+ * Generate the GPS NMEA-0183 $GPRMC packet. Data is written through the tncTxByte
+ * callback function.
+ */
+void tncGPRMCPacket()
+{
+ uint32_t temp;
+
+ // Generate the GPRMC message.
+ printf (tncTxByte, "$GPRMC,");
+
+ // Standard NMEA time.
+ tncNMEATime();
+
+ // GPS status.
+ if (gpsGetFixType() != GPS_NO_FIX)
+ printf (tncTxByte, "A,");
+ else
+ printf (tncTxByte, "V,");
+
+ // Standard NMEA-0183 latitude/longitude.
+ tncNMEAFix();
+
+ // Speed knots and heading.
+ temp = (int32_t) gpsPosition.hSpeed * 75000 / 385826;
+ printf (tncTxByte, "%ld.%ld,%ld.%ld,", (int16_t) (temp / 10), (int16_t) (temp % 10), gpsPosition.heading / 10, gpsPosition.heading % 10);
+
+ // Date
+ printf (tncTxByte, "%02d%02d%02ld,,", gpsPosition.day, gpsPosition.month, gpsPosition.year % 100);
+
+ // Checksum, skip over the $ character.
+ printf (tncTxByte, "*%02X", gpsNMEAChecksum(tncBuffer + 1, tncLength - 1));
+}
+
+/**
+ * Generate the plain text status packet. Data is written through the tncTxByte
+ * callback function.
+ */
+void tncStatusPacket(int16_t temperature)
+{
+ uint16_t voltage;
+
+ // Plain text telemetry.
+ printf (tncTxByte, ">ANSR ");
+
+ // Display the flight time.
+ printf (tncTxByte, "%02U:%02U:%02U ", timeHours, timeMinutes, timeSeconds);
+
+ // Altitude in feet.
+ printf (tncTxByte, "%ld' ", gpsPosition.altitudeFeet);
+
+ // Peak altitude in feet.
+ printf (tncTxByte, "%ld'pk ", gpsGetPeakAltitude());
+
+ // GPS hdop or pdop
+ printf (tncTxByte, "%lu.%lu", gpsPosition.dop / 10, gpsPosition.dop % 10);
+
+ // The text 'pdop' for a 3D fix, 'hdop' for a 2D fix, and 'dop' for no fix.
+ switch (gpsGetFixType())
+ {
+ case GPS_NO_FIX:
+ printf (tncTxByte, "dop ");
+ break;
+
+ case GPS_2D_FIX:
+ printf (tncTxByte, "hdop ");
+ break;
+
+
+ case GPS_3D_FIX:
+ printf (tncTxByte, "pdop ");
+ break;
+ } // END switch
+
+ // Number of satellites in the solution.
+ printf (tncTxByte, "%utrk ", gpsPosition.trackedSats);
+
+ // Display main bus voltage.
+ voltage = adcGetMainBusVolt();
+ printf (tncTxByte, "%lu.%02luvdc ", voltage / 100, voltage % 100);
+
+ // Display internal temperature.
+ printf (tncTxByte, "%ld.%01ldF ", temperature / 10, abs(temperature % 10));
+
+ // Print web address link.
+ printf (tncTxByte, "www.kd7lmo.net");
+}
+
+/**
+ * Prepare an AX.25 data packet. Each time this method is called, it automatically
+ * rotates through 1 of 3 messages.
+ *
+ * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK
+ */
+void tncTxPacket(TNC_DATA_MODE dataMode)
+{
+ int16_t temperature;
+ uint16_t crc;
+
+ // Only transmit if there is not another message in progress.
+ if (tncMode != TNC_TX_READY)
+ return;
+
+ // Log the battery and reference voltage before we start the RF chain.
+ sysLogVoltage();
+
+ // We need to read the temperature sensor before we setup the DDS since they share a common clock pin.
+ temperature = lm92GetTemp();
+
+ // Log the system temperature every time we transmit a packet.
+ logType (LOG_TEMPERATURE);
+ logInt16 (temperature);
+
+ // Configure the DDS for the desired operational.
+ tncSetMode (dataMode);
+
+ // Set a pointer to our TNC output buffer.
+ tncBufferPnt = tncBuffer;
+
+ // Set the message length counter.
+ tncLength = 0;
+
+ // Determine the contents of the packet.
+ switch (tncPacketType)
+ {
+ case TNC_BOOT_MESSAGE:
+ printf (tncTxByte, ">ANSR Pico Beacon - V3.05");
+
+ // Select the next packet we will generate.
+ tncPacketType = TNC_STATUS;
+ break;
+
+ case TNC_STATUS:
+ tncStatusPacket(temperature);
+
+ // Select the next packet we will generate.
+ tncPacketType = TNC_GGA;
+ break;
+
+ case TNC_GGA:
+ tncGPGGAPacket();
+
+ // Select the next packet we will generate.
+ tncPacketType = TNC_RMC;
+ break;
+
+ case TNC_RMC:
+ tncGPRMCPacket();
+
+ // Select the next packet we will generate.
+ tncPacketType = TNC_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(tncBuffer, 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 the variables that are used in the real-time clock interrupt.
+ tncBitCount = 0;
+ tncShift = 0x7e;
+ tncTxBit = 0;
+ tncIndex = 0;
+ tncMode = TNC_TX_SYNC;
+
+ // Turn on the PA chain.
+ output_high (IO_PTT);
+
+ // Wait for the PA chain to power up.
+ delay_ms (10);
+
+ // Key the DDS.
+ output_high (IO_OSK);
+
+ // Log the battery and reference voltage just after we key the transmitter.
+ sysLogVoltage();
+}
+
+/** @} */
+
+uint32_t counter;
+
+uint8_t bitIndex;
+uint8_t streamIndex;
+uint8_t value;
+
+uint8_t bitStream[] = { 0x10, 0x20, 0x30 };
+
+void init()
+{
+ counter = 0;
+ bitIndex = 0;
+ streamIndex = 0;
+ value = bitStream[0];
+}
+
+void test()
+{
+ counter += 0x10622d;
+
+ CCP_1 = (uint16_t) ((counter >> 16) & 0xffff);
+
+ if ((value & 0x80) == 0x80)
+ setup_ccp1 (CCP_COMPARE_SET_ON_MATCH);
+ else
+ setup_ccp1 (CCP_COMPARE_CLR_ON_MATCH);
+
+ if (++bitIndex == 8)
+ {
+ bitIndex = 0;
+
+ if (++streamIndex == sizeof(bitStream))
+ {
+ streamIndex = 0;
+ }
+
+ value = bitStream[streamIndex];
+ } else
+ value = value << 1;
+}
+
+// This is where we go after reset.
+void main()
+{
+ uint8_t i, utcSeconds, lockLostCounter;
+
+test();
+
+ // Configure the basic systems.
+ sysInit();
+
+ // Wait for the power converter chains to stabilize.
+ delay_ms (100);
+
+ // Setup the subsystems.
+ adcInit();
+ flashInit();
+ gpsInit();
+ logInit();
+ timeInit();
+ serialInit();
+ tncInit();
+
+ // Program the DDS.
+ ddsInit();
+
+ // Turn off the LED after everything is configured.
+ output_low (IO_LED);
+
+ // Check for the diagnostics plug, otherwise we'll continue to boot.
+ diagPort();
+
+ // Setup our interrupts.
+ enable_interrupts(GLOBAL);
+ enable_interrupts(INT_CCP1);
+
+ // Turn on the GPS engine.
+ gpsPowerOn();
+
+ // Allow the GPS engine to boot.
+ delay_ms (250);
+
+ // Initialize the GPS engine.
+ while (!gpsSetup());
+
+ // Charge the ADC filters.
+ for (i = 0; i < 32; ++i)
+ adcUpdate();
+
+ // Log startup event.
+ logType (LOG_BOOTED);
+ logUint8 (gpsPosition.month);
+ logUint8 (gpsPosition.day);
+ logUint8 (gpsPosition.year & 0xff);
+
+ logUint8 (gpsPosition.hours);
+ logUint8 (gpsPosition.minutes);
+ logUint8 (gpsPosition.seconds);
+
+ // Transmit software version packet on start up.
+ tncTxPacket(TNC_MODE_1200_AFSK);
+
+ // Counters to send packets if the GPS time stamp is not available.
+ lockLostCounter = 5;
+ utcSeconds = 55;
+
+ // This is the main loop that process GPS data and waits for the once per second timer tick.
+ for (;;)
+ {
+ // Read the GPS engine serial port FIFO and process the GPS data.
+ gpsUpdate();
+
+ if (gpsIsReady())
+ {
+ // Start the flight timer when we get a valid 3D fix.
+ if (gpsGetFixType() == GPS_3D_FIX)
+ timeSetRunFlag();
+
+ // Generate our packets based on the GPS time.
+ if (tncIsTimeSlot(gpsPosition.seconds))
+ tncTxPacket(TNC_MODE_1200_AFSK);
+
+ // Sync the internal clock to GPS UTC time.
+ utcSeconds = gpsPosition.seconds;
+
+ // This counter is reset every time we receive the GPS message.
+ lockLostCounter = 0;
+
+ // Log the data to flash.
+ sysLogGPSData();
+ } // END if gpsIsReady
+
+ // Processing that occurs once a second.
+ if (timeIsUpdate())
+ {
+ // We maintain the UTC time in seconds if we shut off the GPS engine or it fails.
+ if (++utcSeconds == 60)
+ utcSeconds = 0;
+
+ // If we loose information for more than 5 seconds,
+ // we will determine when to send a packet based on internal time.
+ if (lockLostCounter == 5)
+ {
+ if (tncIsTimeSlot(utcSeconds))
+ tncTxPacket(TNC_MODE_1200_AFSK);
+ } else
+ ++lockLostCounter;
+
+ // Update the ADC filters.
+ adcUpdate();
+
+ if (timeHours == 5 && timeMinutes == 0 && timeSeconds == 0)
+ gpsPowerOff();
+
+ } // END if timeIsUpdate
+
+ } // END for
+}
+
+
+