diff options
| -rw-r--r-- | src/drivers/ao_aprs.c | 3102 | 
1 files changed, 3102 insertions, 0 deletions
diff --git a/src/drivers/ao_aprs.c b/src/drivers/ao_aprs.c new file mode 100644 index 00000000..79cea49a --- /dev/null +++ b/src/drivers/ao_aprs.c @@ -0,0 +1,3102 @@ +/**  + * 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 +} + + +  | 
