/** 
 * @mainpage HF-APRS Beacon
 *
 * @section overview_sec Overview
 *
 * The goal of the HF-APRS system is to create a position reporting system that provides nationwide tracking of a 
 * balloon from a single location.  Although it is possible to use VHF or UHF APRS beacons, the range is limited to 
 * line of site.  Although this is a large footprint from 100,000' and above, it still does not provide nationwide 
 * coverage.  The HF-APRS system generates a PSK-31, 300 baud FSK, 1200 baud A-FSK, and 9600 FSK signal in a large 
 * number of HF bands synchronized to UTC time.

* * * @section history_sec Revision History * * @subsection v107 V1.07 * 23 Dec 2006, (A) Added missing case statement to switch that new compiler flagged as an error, * (B) changed the ballast pump arm time from 2 hours to 90 minutes, * (C) changed float altitude from RVSM IFR 30,000' to non-IFR 30,500', and * (D) increased APRS A-FSK output power on 50.62 by 3dB. * * @subsection v106 V1.06 * 18 Dec 2005, (A) Changes to support long duration flight, * (B) corrected port B I/O direction for diagnostic port booting, * (C) added LM92 temperature sensor, * (D) added M25P80 flash device, * (E) corrected size of LOG_COORD block when searching for end of log, and * (F) added interrupt protection to logging to deconflict DDS and flash.

* * @subsection v105 V1.05 * 4 Sep 2005, Additional in-line documentation (doxygen).

* * @subsection v104 V1.04 * 27 Aug 2005, (A) Changed to single PSK-31 frequency, * (B) updates to TNC to generate phase coherent 1200bps A-FSK APRS signal, * (C) changed PC_HOST output pin to B7 from B6, * (D) updated GPS engine to use native Motorola binary mode, * (E) corrected race condition when GPS engine time jumps from startup to GPS to UTC, * (F) add engineering mode interface and support and, * (G) started to update doxygen tags.

* * @subsection v103 V1.03 * 26 Jun 2004, (A) Changed 1200 bps, A-FSK APRS status packet to include web link, * (B) removed 'Balloon' from 3rd line of PSK-31 text, * (C) modified I/O to support more accurate 1-PPS for TCXO correction, and * (D) added list of 3 frequencies and amplitudes for PSK-31 band tuning.

* * @subsection v102 V1.02 * 17 Jun 2004, ANSR-19 test flight.

* * @subsection v101 V1.01 * 6 Jan 2004, Pre-flight release that includes all functions.

* * @subsection v100 V1.00 * 21 Dec 2003, Initial release.

* * * @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 * */ // Hardware specific configuration. #include <18f252.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 // 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 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 names to the hardware pins. /// Flash Chip Select - Port A0 #define IO_FLASH_CS PIN_A0 #define IO_CUT_DOWN PIN_A1 #define IO_BALLAST_PUMP PIN_A2 #define IO_PS0 PIN_A3 #define IO_UPDATE PIN_A5 #define IO_CS PIN_B0 #define IO_PA PIN_B2 #define IO_GPS_PWR PIN_B3 #define IO_LED PIN_B4 #define IO_PS1 PIN_C0 #define IO_OSK PIN_C2 // Serial port enable RCSTA #bit SPEN = 0xfab.7 bool_t cutDownIsActivate(); /// Constants to select the DDS mode in method ddsSetMode typedef enum { /// Place the DDS in low power mode DDS_MODE_POWERDOWN, /// 1200 baud A-FSK APRS DDS_MODE_APRS, /// PSK-31 DDS_MODE_PSK31, /// 300 baud FSK APRS DDS_MODE_HF_APRS } DDS_MODE; void ddsInit(); void ddsPhase (bool_t phase); void ddsSetOutputScale (uint16_t scale); void ddsSetAmplitude (uint8_t amplitude); inline void ddsPTT (bool_t state); void ddsSetFreq (uint32_t freq); void ddsSetFTW (uint32_t ftw); void ddsSetMode (DDS_MODE mode); void flashErase(); uint8_t flashGetByte (); uint8_t flashReadElectronicSignature(); 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); uint8_t flashReadES(); /// Type of GPS fix. typedef enum { /// No GPS FIX GPS_NO_FIX, /// 2D (Latitude/Longitude) fix. GPS_2D_FIX, /// 3D (Latitude/Longitude/Altitude) fix. GPS_3D_FIX } GPS_FIX_TYPE; /// 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; /// Last verified GPS message received. GPSPOSITION_STRUCT gpsPosition; void gpsInit(); bool_t gpsIsReady(); GPS_FIX_TYPE gpsGetFixType(); int32_t gpsGetPeakAltitude(); void gpsPowerOn(); void gpsPowerOff(); bool_t gpsSetup(); void gpsUpdate(); int16_t lm92GetTemp(); /// Define the log record types. typedef enum { /// Time stamp the log was started. LOG_BOOTED = 0xb4, /// GPS coordinates. LOG_COORD = 0xa5, /// Temperature Value 1 LOG_TEMPERATURE_1 = 0x96, /// Bus voltage. LOG_VOLTAGE = 0x87, /// Temperature Value 2 LOG_TEMPERATURE_2 = 0x78, /// Pump activity and altitude. LOG_PUMP = 0x69 } LOG_TYPE; void logInit(); uint32_t logGetAddress(); void logSetDisableFlag (bool_t disableFlag); void logType (LOG_TYPE type); void logUint8 (uint8_t value); void logInt16 (int16_t value); void logInt32 (int32_t value); bool_t psk31CreateDataPacket(); void psk31Init(); void psk31TimeUpdate(); void psk31TxPacket(uint8_t minutes); void psk31TxByte (uint8_t value); void psk31TxNull (); void psk31TxString (char *string, uint8_t length); bool_t serialHasData(); void serialInit(); uint8_t serialRead(); void serialUpdate(); uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc); inline void sysPAOutput (bool_t state); // Constants for time duty cycle in method timeSetDutyCycle #define TIME_DUTYCYCLE_0 0 #define TIME_DUTYCYCLE_20 2 #define TIME_DUTYCYCLE_70 7 // Constants for the time base mode in method timeSetMode typedef enum { TIME_MODE_APRS, TIME_MODE_PSK31, TIME_MODE_HF_APRS } TIME_MODE; uint8_t timeGetTicks(); void timeInit(); uint8_t timeGetHours(); uint8_t timeGetMinutes(); void timeSetDutyCycle (uint8_t dutyCycle); void timeSetMode (TIME_MODE mode); void timeUpdate(); // Modes for the packet state machine. enum TNC_PACKET_TYPE { TNC_BOOT_MESSAGE, TNC_STATUS, TNC_GGA, TNC_RMC }; void tncInit(); bool_t tncIsFree(); void tncTimeUpdate(); void tncTxByte (uint8_t value); void tncTxPacket(TNC_PACKET_TYPE tncPacketType); /** * @defgroup Ballast Ballast Control * * Functions to control the ballast pump for altitude. * * @{ */ /// Number of consecutive 3D GPS fixes required to assert altitude as valid. #define BALLAST_3D_FIX_COUNT 10 /// Altitude in feet used to control ballast pump. #define BALLAST_HOLD_ALTITUDE 30500l /// Ballast pump state machine. enum BALLAST_MODE { /// Wait 90 minutes after boot or higher than BALLAST_HOLD_ALTITUDE to activate ballast pump. WAIT_ACTIVATION, /// Determine if altitude is less than BALLAST_HOLD_ALTITUDE. CONTROL_ALTITUDE, /// Run the ballast pump. PUMP_RUN, /// Time delay after ballast pump runs to determine effects of fluid loss. PUMP_RUN_DELAY }; /// Number of times the ballast pump has been commanded to run. uint16_t ballastPumpCount; /// Count of 3D fixes required to assert GPS altitude is valid. uint8_t ballast3DFixCount; /// Counter used to track number of seconds pump is on or off. uint8_t ballastTimeCount; /// Enumerated type that indicates the current ballast mode. BALLAST_MODE ballastMode; /// Flag used to enable/disable ballast control. bool_t ballastState; /** * Return the number of times the ballast pump has been commanded to run. * * @return run count */ uint16_t ballastGetPumpCount() { return ballastPumpCount; } /** * Setup the ballast controls system. */ void ballastInit() { ballastPumpCount = 0; ballastTimeCount = 0; ballast3DFixCount = 0; ballastState = true; ballastMode = WAIT_ACTIVATION; } /** * Set the ballast control operation. * * @param state true to enable pump operation; otherwise false */ void ballastSetState (bool_t state) { ballastState = state; } /** * Log the ballast pump command on or off. * * @param state boolean that indicates if pump was turned on or off */ void ballastLogEvent (bool_t state) { logType (LOG_PUMP); logUint8 ((state ? 0x01 : 0x00)); logUint8 (gpsPosition.hours); logUint8 (gpsPosition.minutes); logUint8 (gpsPosition.seconds); logInt32 (gpsPosition.altitudeCM); } /** * Function that should be called once a second with each GPS fix to determine * ballast pump operation. */ void ballastCheck() { if (!ballastState) { ballast3DFixCount = 0; return; } // END if if (gpsGetFixType() == GPS_3D_FIX) { if (ballast3DFixCount < BALLAST_3D_FIX_COUNT) ++ballast3DFixCount; } else ballast3DFixCount = 0; // State machine that controls the ballast pump operation. switch (ballastMode) { case WAIT_ACTIVATION: if (ballast3DFixCount == BALLAST_3D_FIX_COUNT) if (gpsPosition.altitudeFeet > BALLAST_HOLD_ALTITUDE) { ++ballastPumpCount; ballastMode = CONTROL_ALTITUDE; } // END if // If we aren't at altitude after 90 minutes, enable the ballast pump. if (timeGetHours() == 1 && timeGetMinutes() == 30) { ++ballastPumpCount; ballastMode = CONTROL_ALTITUDE; } break; case CONTROL_ALTITUDE: if (ballast3DFixCount == BALLAST_3D_FIX_COUNT) if (gpsPosition.altitudeFeet < BALLAST_HOLD_ALTITUDE) { output_high (IO_BALLAST_PUMP); ballastLogEvent (true); ballastTimeCount = 0; ballastMode = PUMP_RUN; } // END if break; case PUMP_RUN: if (++ballastTimeCount == 5) { output_low (IO_BALLAST_PUMP); ballastLogEvent (false); ++ballastPumpCount; ballastTimeCount = 0; ballastMode = PUMP_RUN_DELAY; } // END if break; case PUMP_RUN_DELAY: if (++ballastTimeCount == 60) ballastMode = CONTROL_ALTITUDE; break; } // END switch } /** @} */ /** * @defgroup CutDown Cut Down control * * Functions to fire the cut down nichrome wire system. * * @{ */ /// Count of 3D fixes required to assert GPS altitude is valid. #define CUTDOWN_3D_FIX_COUNT 10 /// Counter used to track how long the nichrome wire has been active. uint8_t cutDownTimer; /// Count of 3D fixes required to assert GPS altitude is valid. uint8_t cutDown3DFixCount; /// Flag that is set when the cut down has been fired. bool_t cutDownFlag; /** * Determine if cut down has been activated. * * @return true if cutdown activated; otehrwise false */ bool_t cutDownIsActivate() { return cutDownFlag; } /** * Initialize the cut down control system. */ void cutDownInit() { cutDownFlag = false; cutDownTimer = 0; cutDown3DFixCount = 0; } /** * Activate the I/O and timers for the cutdown. */ void cutDownActivate() { output_high (IO_CUT_DOWN); cutDownTimer = 7; cutDownFlag = true; } /** * Fucntion that is called each time a new GPS message position report is received. */ void cutDownCheck() { // Turn off the timer after a period of time. if (cutDownTimer != 0) if (--cutDownTimer == 0) output_low (IO_CUT_DOWN); // Once the cut down has fired, we are done with it. if (cutDownFlag) return; // Terminate the flight after 49 hours. (One hour on the ground, 48 in flight). if (timeGetHours() == 49) cutDownActivate(); // We need to see a number of consecutive 3D GPS fixes to use the altitude. if (gpsGetFixType() == GPS_3D_FIX) { if (cutDown3DFixCount < CUTDOWN_3D_FIX_COUNT) ++cutDown3DFixCount; } else cutDown3DFixCount = 0; // If we are EAST of 83 degrees WEST latitude, terminate the flight. if (cutDown3DFixCount == CUTDOWN_3D_FIX_COUNT) if (gpsPosition.longitude > -298800000) cutDownActivate(); } /** @} */ /** * @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 /// 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, 1, 8, 4, 8, 1, 0, 6, 6 }; /// 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 - 5.0KHz FM frequency deviation at 50.62Mhz const uint32_t freqTable[256] = { 566175115 ,566176488 ,566177860 ,566179229 ,566180597 ,566181961 ,566183321 ,566184676 ,566186026 ,566187368 ,566188704 ,566190031 ,566191349 ,566192658 ,566193956 ,566195242 ,566196517 ,566197778 ,566199026 ,566200260 ,566201478 ,566202680 ,566203866 ,566205035 ,566206185 ,566207317 ,566208429 ,566209522 ,566210593 ,566211644 ,566212672 ,566213677 ,566214660 ,566215618 ,566216552 ,566217462 ,566218345 ,566219203 ,566220034 ,566220838 ,566221615 ,566222363 ,566223083 ,566223774 ,566224436 ,566225068 ,566225670 ,566226242 ,566226783 ,566227292 ,566227770 ,566228217 ,566228631 ,566229014 ,566229364 ,566229681 ,566229965 ,566230216 ,566230434 ,566230619 ,566230770 ,566230888 ,566230972 ,566231023 ,566231040 ,566231023 ,566230972 ,566230888 ,566230770 ,566230619 ,566230434 ,566230216 ,566229965 ,566229681 ,566229364 ,566229014 ,566228631 ,566228217 ,566227770 ,566227292 ,566226783 ,566226242 ,566225670 ,566225068 ,566224436 ,566223774 ,566223083 ,566222363 ,566221615 ,566220838 ,566220034 ,566219203 ,566218345 ,566217462 ,566216552 ,566215618 ,566214660 ,566213677 ,566212672 ,566211644 ,566210593 ,566209522 ,566208429 ,566207317 ,566206185 ,566205035 ,566203866 ,566202680 ,566201478 ,566200260 ,566199026 ,566197778 ,566196517 ,566195242 ,566193956 ,566192658 ,566191349 ,566190031 ,566188704 ,566187368 ,566186026 ,566184676 ,566183321 ,566181961 ,566180597 ,566179229 ,566177860 ,566176488 ,566175115 ,566173743 ,566172371 ,566171001 ,566169634 ,566168270 ,566166910 ,566165555 ,566164205 ,566162862 ,566161527 ,566160200 ,566158882 ,566157573 ,566156275 ,566154989 ,566153714 ,566152453 ,566151205 ,566149971 ,566148753 ,566147551 ,566146365 ,566145196 ,566144046 ,566142914 ,566141802 ,566140709 ,566139638 ,566138587 ,566137559 ,566136554 ,566135571 ,566134613 ,566133678 ,566132769 ,566131886 ,566131028 ,566130197 ,566129393 ,566128616 ,566127868 ,566127148 ,566126457 ,566125795 ,566125163 ,566124561 ,566123989 ,566123448 ,566122939 ,566122460 ,566122014 ,566121599 ,566121217 ,566120867 ,566120550 ,566120266 ,566120015 ,566119797 ,566119612 ,566119461 ,566119343 ,566119259 ,566119208 ,566119191 ,566119208 ,566119259 ,566119343 ,566119461 ,566119612 ,566119797 ,566120015 ,566120266 ,566120550 ,566120867 ,566121217 ,566121599 ,566122014 ,566122460 ,566122939 ,566123448 ,566123989 ,566124561 ,566125163 ,566125795 ,566126457 ,566127148 ,566127868 ,566128616 ,566129393 ,566130197 ,566131028 ,566131886 ,566132769 ,566133678 ,566134613 ,566135571 ,566136554 ,566137559 ,566138587 ,566139638 ,566140709 ,566141802 ,566142914 ,566144046 ,566145196 ,566146365 ,566147551 ,566148753 ,566149971 ,566151205 ,566152453 ,566153714 ,566154989 ,566156275 ,566157573 ,566158882 ,566160200 ,566161527 ,566162862 ,566164205 ,566165555 ,566166910 ,566168270 ,566169634 ,566171001 ,566172371 ,566173743 }; /** * Initialize the DDS regsiters and RAM. */ void ddsInit() { // Set default I/O for the DDS. output_high (IO_CS); output_low (IO_PS1); output_low (IO_PS0); output_low (IO_OSK); output_low (IO_UPDATE); // 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 ); // Turn off the output. output_low (IO_OSK); ddsSetMode (DDS_MODE_POWERDOWN); // ASF (Amplitude Scale Factor) to full scale (0x3fff). ddsSetOutputScale (0x3fff); // ARR (Amplitude Ramp Rate) to 15.5mS for OSK output_low (IO_CS); spi_write (DDS_AD9954_ARR); spi_write (181); output_high (IO_CS); // CFR2 (Control Function Register No. 2) output_low (IO_CS); spi_write (0x01); spi_write (0x00); // Unused register bits spi_write (0x00); spi_write (0xa4); // 20x reference clock multipler, high VCO range, nominal charge pump current 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); } /** * 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 the output phase. * * @param phase true for 180 degree phase shift; false for 0 degree phase shift */ void ddsPhase (bool_t phase) { // Set the POW0 (Phase Offset Word 0) to 0 or 180 degrees. output_low (IO_CS); spi_write (0x05); if (phase) { spi_write (0x20); spi_write (0x00); } else { spi_write (0x00); spi_write (0x00); } output_high (IO_CS); // Strobe the DDS to write the phase change. output_high (IO_UPDATE); output_low (IO_UPDATE); } /** * Turn on the DDS output. * * @param state true to activate; otherwise false */ inline void ddsPTT (bool_t state) { if (state) output_high (IO_OSK); else output_low (IO_OSK); } /** * 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); } /** * 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) { 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_APRS: // CFR0 (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 (0x02); // SDIO input only spi_write (0x40); // Power down comparator circuit output_high (IO_CS); break; case DDS_MODE_PSK31: // CFR0 (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 (0x02); // SDIO input only spi_write (0x40); // Power down comparator circuit output_high (IO_CS); break; case DDS_MODE_HF_APRS: // CFR0 (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 (0x20); // Enable linear sweep spi_write (0x02); // SDIO input only spi_write (0x40); // Power down comparator circuit output_high (IO_CS); // NOTE: The sweep rate requires 1/4 of a bit time to transition. // 200Hz delta = 2236 counts (200Hz / 384MHz) * 2 ^ 32 // SYNC_CLK = 96MHz 1/96MHz * 2236 * 36 = 838uS // NLSCW (Negative Linear Sweep Control Word) output_low (IO_CS); spi_write (DDS_AD9954_NLSCW); spi_write (36); // Falling sweep ramp rate word spi_write (0x00); // Delta frequency tuning word spi_write (0x00); spi_write (0x00); spi_write (0x01); output_high (IO_CS); // PLSCW (Positive Linear Sweep Control Word) output_low (IO_CS); spi_write (DDS_AD9954_PLSCW); spi_write (36); // Falling sweep ramp rate word spi_write (0x00); // Delta frequency tuning word spi_write (0x00); spi_write (0x00); spi_write (0x01); output_high (IO_CS); break; } // END switch // Strobe the DDS to change the mode. output_high (IO_UPDATE); output_low (IO_UPDATE); } /** @} */ /** * @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, "\n\rErasing flash..."); flashErase(); fprintf (PC_HOST, "done.\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, "%03lx ", 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"); } /** * 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, Toggle RF (P)A, Toggle Ban(d)\n\r"); fprintf (PC_HOST, " Toggle (n)ichrome wire, Toggle ballast p(u)mp\n\r"); fprintf (PC_HOST, " (t)emperature sensor value\n\r"); fprintf (PC_HOST, " (f)requencey down, (F)requency up - 10Hz step\n\r"); fprintf (PC_HOST, " (c)hannel down, (C)hannel up - 1KHz step\n\r"); fprintf (PC_HOST, " (a)mplitude down, (A)mplitude up - 0.5 dB steps\n\r"); fprintf (PC_HOST, " Re(b)oot processor\n\r"); fprintf (PC_HOST, " e(x)it engineering mode\n\r"); } /** * Process commands through the external RS-232 port. */ void diagPort() { bool_t diagDoneFlag, ledFlag, paFlag, showSettingsFlag, phaseFlag, cutdownFlag, pumpFlag; bool_t lowBandFlag; uint8_t command, amplitude; int16_t temperature, tcxo; 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; cutdownFlag = false; lowBandFlag = true; pumpFlag = false; // Flag that indicate we should show the current carrier frequency. showSettingsFlag = false; // Set the initial carrier frequency and amplitude. freqHz = 14070450; amplitude = 100; phaseFlag = false; // 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 's': fprintf (PC_HOST, "Flash Electronic Signature 0x%02x\n\r", flashReadElectronicSignature()); break; // Toggle the system LED on/off. case 'l': ledFlag = (ledFlag ? false : true); output_bit (IO_LED, ledFlag); break; // Display the help menu. case 'h': case 'H': case '?': diagMenu(); break; case 'r': diagReadFlash(); break; // Change the carrier frequency in 100Hz steps. case 'f': freqHz -= 100; ddsSetFreq (freqHz); // Display the new frequency. showSettingsFlag = true; break; case 'F': freqHz += 100; ddsSetFreq (freqHz); // Display the new frequency. showSettingsFlag = true; break; // Change the carrier frequency in 10KHz steps. case 'c': freqHz -= 10000; ddsSetFreq (freqHz); // Display the new frequency. showSettingsFlag = true; break; case 'C': freqHz += 10000; ddsSetFreq (freqHz); // Display the new frequency. showSettingsFlag = true; 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 'd': if (lowBandFlag) { lowBandFlag = false; freqHz = 50620000; } else { lowBandFlag = true; freqHz = 14070450; } // Display the new band setting. showSettingsFlag = true; break; // Toggle the PA on/off. case 'p': paFlag = (paFlag ? false : true); sysPAOutput (paFlag); output_bit (IO_OSK, paFlag); if (paFlag) { ddsSetMode (DDS_MODE_PSK31); ddsSetFreq (freqHz); ddsSetAmplitude (amplitude); ddsPhase (phaseFlag); } else ddsSetMode (DDS_MODE_POWERDOWN); break; case 'x': diagDoneFlag = true; break; case 't': temperature = lm92GetTemp(); fprintf (PC_HOST, "%ld.%01ld degF\n\r", temperature / 10, temperature % 10); break; case 'b': disable_interrupts (GLOBAL); fprintf (PC_HOST, "rebooting...\n\r\n\r"); reset_cpu(); break; case '1': gpsPowerOn(); tcxo = 0x0000; while (!kbhit(PC_HOST)) { if (CCP_2 != tcxo) { fprintf (PC_HOST, "%ld %ld\n\r", CCP_2, CCP_2 - tcxo); tcxo = CCP_2; } } // END while gpsPowerOff(); break; case 'n': cutdownFlag = (cutdownFlag ? false : true); output_bit (IO_CUT_DOWN, cutdownFlag); break; case 'u': pumpFlag = (pumpFlag ? false : true); output_bit (IO_BALLAST_PUMP, pumpFlag); 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, "%02ld.%06ldMHz ", freqHz / 1000000, freqHz % 1000000); 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 flash Flash Manager * * Functions to control the ST MP25P80 serial flash device. * * @{ */ /** * 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 (IO_FLASH_CS); // Read Status Register (RDSR) flash command. flashSendByte (0x05); status = flashGetByte(); output_high (IO_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 (IO_FLASH_CS); // Read Data Byte(s) (READ) flash command. flashSendByte (0x03); flashSendAddress (address); for (i = 0; i < length; ++i) *block++ = flashGetByte(); output_high (IO_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 (IO_FLASH_CS); // Write Enable (WREN) flash command. flashSendByte (0x06); output_high (IO_FLASH_CS); output_low (IO_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 (IO_FLASH_CS); // Write this block of data. while (flashIsWriteInProgress()); output_low (IO_FLASH_CS); // Write Enable (WREN) flash command. flashSendByte (0x06); output_high (IO_FLASH_CS); output_low (IO_FLASH_CS); // Page Program (PP) flash command. flashSendByte (0x02); flashSendAddress (address); } // END if } // END for output_high (IO_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 (IO_FLASH_CS); // Write Enable (WREN) flash command. flashSendByte (0x06); output_high (IO_FLASH_CS); output_low (IO_FLASH_CS); // Bulk Erase (BE) flash command. flashSendByte (0xc7); output_high (IO_FLASH_CS); while (flashIsWriteInProgress()); } /** * Read the flash electronic signature byte. This method is typically used * for engineering and test functions. * * @return electronic signature */ uint8_t flashReadElectronicSignature() { uint8_t electronicSignature; output_low (IO_FLASH_CS); flashSendByte (0xab); flashSendByte (0x00); flashSendByte (0x00); flashSendByte (0x00); electronicSignature = flashGetByte(); output_high (IO_FLASH_CS); return electronicSignature; } /** * 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() { return spi_read(0x00); } /** * Initialize the flash memory subsystem. */ void flashInit() { // I/O lines to control flash. output_high (IO_FLASH_CS); } /** * 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) { spi_write (value); } /** * 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) { spi_write ((address >> 16) & 0xff); spi_write ((address >> 8) & 0xff); spi_write (address & 0xff); } /** @} */ /** * @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; /** * 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)); // Capture CCP2 on the rising edge of the GPS 1-PPS signal. setup_ccp2 (CCP_CAPTURE_RE); } /** * 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_20); 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. SPEN = true; } /** * Turn off the GPS engine power and serial interface. */ void gpsPowerOff() { // Disable the UART and the transmit line. SPEN = false; // 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 255 /// 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; /// Flag that disable writes to flash. bool_t logDisableFlag; /** * 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() { if (logDisableFlag) return; // 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; // Flag that disable writes to flash. logDisableFlag = false; 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_1: case LOG_TEMPERATURE_2: logAddress += 3; break; case LOG_VOLTAGE: logAddress += 5; break; case LOG_PUMP: logAddress += 9; break; case 0xff: endFound = true; break; default: ++logAddress; } // END switch } while (logAddress < 0x100000 && !endFound); fprintf (PC_HOST, "done.\n\rLog contains %ld bytes.\n\r", logAddress); logIndex = 0; } /** * Enable and disable flash write operations. This is used to share the SPI port between the * flash and DDS. * * @param disableFlag disable */ void logSetDisableFlag (bool_t disableFlag) { logDisableFlag = disableFlag; } /** * 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[logIndex++] = type; } /** * 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 * * @{ */ #use i2c (master, scl=PIN_B5, sda=PIN_B1) /** * Read the LM92 temperature value in 0.1 degrees F. * * @return 0.1 degrees F */ int16_t lm92GetTemp() { int16_t value; int32_t temp; // 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(); // 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 psk31 PSK 31 modulator * * PSK-31 waveform and data stream modulator. * * @{ */ const uint16_t PSK31_VARICODE[] = { 0xAAC0, // ASCII = 0 1010101011 0xB6C0, // ASCII = 1 1011011011 0xBB40, // ASCII = 2 1011101101 0xDDC0, // ASCII = 3 1101110111 0xBAC0, // ASCII = 4 1011101011 0xD7C0, // ASCII = 5 1101011111 0xBBC0, // ASCII = 6 1011101111 0xBF40, // ASCII = 7 1011111101 0xBFC0, // ASCII = 8 1011111111 0xEF00, // ASCII = 9 11101111 0xE800, // ASCII = 10 11101 0xDBC0, // ASCII = 11 1101101111 0xB740, // ASCII = 12 1011011101 0xF800, // ASCII = 13 11111 0xDD40, // ASCII = 14 1101110101 0xEAC0, // ASCII = 15 1110101011 0xBDC0, // ASCII = 16 1011110111 0xBD40, // ASCII = 17 1011110101 0xEB40, // ASCII = 18 1110101101 0xEBC0, // ASCII = 19 1110101111 0xD6C0, // ASCII = 20 1101011011 0xDAC0, // ASCII = 21 1101101011 0xDB40, // ASCII = 22 1101101101 0xD5C0, // ASCII = 23 1101010111 0xDEC0, // ASCII = 24 1101111011 0xDF40, // ASCII = 25 1101111101 0xEDC0, // ASCII = 26 1110110111 0xD540, // ASCII = 27 1101010101 0xD740, // ASCII = 28 1101011101 0xEEC0, // ASCII = 29 1110111011 0xBEC0, // ASCII = 30 1011111011 0xDFC0, // ASCII = 31 1101111111 0x8000, // ASCII = ' ' 1 0xFF80, // ASCII = '!' 111111111 0xAF80, // ASCII = '"' 101011111 0xFA80, // ASCII = '#' 111110101 0xED80, // ASCII = '$' 111011011 0xB540, // ASCII = '%' 1011010101 0xAEC0, // ASCII = '&' 1010111011 0xBF80, // ASCII = ''' 101111111 0xFB00, // ASCII = '(' 11111011 0xF700, // ASCII = ')' 11110111 0xB780, // ASCII = '*' 101101111 0xEF80, // ASCII = '+' 111011111 0xEA00, // ASCII = ',' 1110101 0xD400, // ASCII = '-' 110101 0xAE00, // ASCII = '.' 1010111 0xD780, // ASCII = '/' 110101111 0xB700, // ASCII = '0' 10110111 0xBD00, // ASCII = '1' 10111101 0xED00, // ASCII = '2' 11101101 0xFF00, // ASCII = '3' 11111111 0xBB80, // ASCII = '4' 101110111 0xAD80, // ASCII = '5' 101011011 0xB580, // ASCII = '6' 101101011 0xD680, // ASCII = '7' 110101101 0xD580, // ASCII = '8' 110101011 0xDB80, // ASCII = '9' 110110111 0xF500, // ASCII = ':' 11110101 0xDE80, // ASCII = ';' 110111101 0xF680, // ASCII = '<' 111101101 0xAA00, // ASCII = '=' 1010101 0xEB80, // ASCII = '>' 111010111 0xABC0, // ASCII = '?' 1010101111 0xAF40, // ASCII = '@' 1010111101 0xFA00, // ASCII = 'A' 1111101 0xEB00, // ASCII = 'B' 11101011 0xAD00, // ASCII = 'C' 10101101 0xB500, // ASCII = 'D' 10110101 0xEE00, // ASCII = 'E' 1110111 0xDB00, // ASCII = 'F' 11011011 0xFD00, // ASCII = 'G' 11111101 0xAA80, // ASCII = 'H' 101010101 0xFE00, // ASCII = 'I' 1111111 0xFE80, // ASCII = 'J' 111111101 0xBE80, // ASCII = 'K' 101111101 0xD700, // ASCII = 'L' 11010111 0xBB00, // ASCII = 'M' 10111011 0xDD00, // ASCII = 'N' 11011101 0xAB00, // ASCII = 'O' 10101011 0xD500, // ASCII = 'P' 11010101 0xEE80, // ASCII = 'Q' 111011101 0xAF00, // ASCII = 'R' 10101111 0xDE00, // ASCII = 'S' 1101111 0xDA00, // ASCII = 'T' 1101101 0xAB80, // ASCII = 'U' 101010111 0xDA80, // ASCII = 'V' 110110101 0xAE80, // ASCII = 'W' 101011101 0xBA80, // ASCII = 'X' 101110101 0xBD80, // ASCII = 'Y' 101111011 0xAB40, // ASCII = 'Z' 1010101101 0xFB80, // ASCII = '[' 1111101110 0xF780, // ASCII = '\' 111101111 0xFD80, // ASCII = ']' 111111011 0xAFC0, // ASCII = '^' 1010111111 0xB680, // ASCII = '_' 101101101 0xB7C0, // ASCII = '`' 1011011111 0xB000, // ASCII = 'a' 1011 0xBE00, // ASCII = 'b' 1011111 0xBC00, // ASCII = 'c' 101111 0xB400, // ASCII = 'd' 101101 0xC000, // ASCII = 'e' 11 0xF400, // ASCII = 'f' 111101 0xB600, // ASCII = 'g' 1011011 0xAC00, // ASCII = 'h' 101011 0xD000, // ASCII = 'i' 1101 0xF580, // ASCII = 'j' 111101011 0xBF00, // ASCII = 'k' 10111111 0xD800, // ASCII = 'l' 11011 0xEC00, // ASCII = 'm' 111011 0xF000, // ASCII = 'n' 1111 0xE000, // ASCII = 'o' 111 0xFC00, // ASCII = 'p' 111111 0xDF80, // ASCII = 'q' 110111111 0xA800, // ASCII = 'r' 10101 0xB800, // ASCII = 's' 10111 0xA000, // ASCII = 't' 101 0xDC00, // ASCII = 'u' 110111 0xF600, // ASCII = 'v' 1111011 0xD600, // ASCII = 'w' 1101011 0xDF00, // ASCII = 'x' 11011111 0xBA00, // ASCII = 'y' 1011101 0xEA80, // ASCII = 'z' 111010101 0xADC0, // ASCII = '{' 1010110111 0xDD80, // ASCII = '|' 110111011 0xAD40, // ASCII = '}' 1010110101 0xB5C0, // ASCII = '~' 1011010111 0xED40 // ASCII = 127 1110110101 }; /// The maximum ASCII length of a PSK 31 message. #define PSK_MAX_PACKET 160 /// PSK-31 state machine. enum PSK_MODE { /// Ready to accept a new message. PSK_WAIT_MSG, /// Send the sync pattern at the start of the message. This is typically 200-300mS. PSK_TX_SYNC, /// Send the text message data. PSK_TX_MESSAGE, /// Send the end sequence. PSK_TX_END }; /// The UTC time in seconds to send a PSK31 data stream. #define PSK31_TIMESLOT 0 /// Number of 0 bits to transmit at start of message. #define PSK_SYNC_LENGTH 32 /// Number of 0 bits to transmit at end of message. #define PSK_END_LENGTH 8 /// PSK 31 frequency and amplitude list for minutes 0 through 2 modulo 2. const uint32_t PSK31_FREQ_LIST[] = { 14070450, 14070450, 14070450 }; const uint16_t PSK31_AMP_LIST[] = { 60, 60, 60 }; /// Counts the number of 1mS time slices in each 32mS of a bit time. uint8_t pskCount; /// The current PSK output character. uint16_t pskData; /// The current DDS phase offset where true is 180 degreees and false is 0 degrees. bool_t pskPhase; /// Buffer that holds PSK packet. char pskBuffer[PSK_MAX_PACKET]; /// State machine variable that indicates current transmit state. PSK_MODE pskMode; /// Last 4 bits that were transmitted. uint8_t pskLastBits; /// Index into the PSK packet buffer. uint8_t pskIndex; /// Flag that is set when PSK-31 message is completed. bool_t pskMessageDoneFlag; /** * * Create the PSK 31 packet. */ bool_t psk31CreateDataPacket() { int32_t temp; int16_t temperature; uint32_t coord, coordMin; // Record the temperature pre-transmit. temperature = lm92GetTemp(); if (temperature == 0x13f) temperature = lm92GetTemp(); logType (LOG_TEMPERATURE_1); logInt16 (temperature); // Line 1 - Header printf (psk31TxByte, "\n\n\nBalloon QSL www.kd7lmo.net\n"); // Line 2 - Data with units. printf (psk31TxByte, "%02d%02dutc ", gpsPosition.hours, gpsPosition.minutes); // Altitude in feet. if (gpsPosition.altitudeFeet > 1000) printf (psk31TxByte, "%ld,", gpsPosition.altitudeFeet / 1000); printf (psk31TxByte, "%03ldft ", gpsPosition.altitudeFeet % 1000); // Latitude value. coord = gpsPosition.latitude; if (gpsPosition.latitude < 0) { coord = gpsPosition.latitude * -1; printf (psk31TxByte, "S"); } else { coord = gpsPosition.latitude; printf (psk31TxByte, "N"); } coordMin = (coord % 3600000) / 600; printf (psk31TxByte, "%02ldd%02ld.%02ldm ", (uint32_t) (coord / 3600000), (uint32_t) (coordMin / 100), (uint32_t) (coordMin % 100)); // Longitude value. if (gpsPosition.longitude < 0) { coord = gpsPosition.longitude * - 1; printf (psk31TxByte, "W"); } else { coord = gpsPosition.longitude; printf (psk31TxByte, "E"); } coordMin = (coord % 3600000) / 600; printf (psk31TxByte, "%03ldd%02ld.%02ldm ", (uint32_t) (coord / 3600000), (uint32_t) (coordMin / 100), (uint32_t) (coordMin % 100)); // Speed MPH and heading. temp = (int32_t) gpsPosition.hSpeed * 500 / 22352; printf (psk31TxByte, "%ldmph@%ldd ", temp, gpsPosition.heading / 10); printf (psk31TxByte, "%ld.%lddop ", gpsPosition.dop / 10, gpsPosition.dop % 10); printf (psk31TxByte, "%ld.%ldF ", temperature / 10, abs(temperature % 10)); printf (psk31TxByte, "%ldp ", ballastGetPumpCount()); if (cutDownIsActivate()) printf (psk31TxByte, "*"); printf (psk31TxByte, "\n"); // Line 3 - Header / ident printf (psk31TxByte, "QSL www.kd7lmo.net de AC7ZT\n"); // End of message line feeds. printf (psk31TxByte, "\n\n\n"); // NULL terminate the string. psk31TxNull(); return true; } /** * Initialize the PSK 31 modulator. */ void psk31Init() { pskCount = 0; pskData = 0x0000; pskIndex = 0; pskMode = PSK_WAIT_MSG; pskPhase = false; pskLastBits = 0x00; pskMessageDoneFlag = false; } /** * Determine if the hardware if ready to a PSK 31 packet. * * @return true if ready; otherwise false */ bool_t psk31IsFree() { if (pskMode == PSK_WAIT_MSG) return true; return false; } /** * This method should be called every 1 milliseconds by the timer interrupt. */ void psk31TimeUpdate() { // If we are waiting for a message, just return. if (pskMode == PSK_WAIT_MSG) return; // If our next bit is 0 (zero), ramp down the carrier. if (pskCount == 15) if ((pskData & 0x8000) == 0x0000) output_low (IO_OSK); // Every 32mS process the next bit. if (++pskCount == 32) { pskCount = 0; switch (pskMode) { case PSK_TX_SYNC: // Just toggle the phase during the sync sequence. if (pskPhase) pskPhase = false; else pskPhase = true; ddsPhase (pskPhase); // After we send the start sequence, send the message. if (--pskIndex == 0) { pskMode = PSK_TX_MESSAGE; pskIndex = 1; pskData = PSK31_VARICODE[pskBuffer[0]]; pskLastBits = 0x0f; } // END if // Ramp the carrier back on. output_high (IO_OSK); break; case PSK_TX_MESSAGE: // Keep track of the last 4 bits we transmitted. pskLastBits = (pskLastBits << 1) & 0x0f; // If the data bit is a zero, toggle the phase. if ((pskData & 0x8000) == 0x0000) { if (pskPhase) pskPhase = false; else pskPhase = true; ddsPhase (pskPhase); } else pskLastBits |= 0x01; // Shift to the next bit. pskData = pskData << 1; // If the last 2 bits we sent were 0, then get the next byte. if ((pskLastBits & 0x0c) == 0x00) // Stop when we find the NULL character. if (pskBuffer[pskIndex] == 0) { pskIndex = PSK_END_LENGTH; pskMode = PSK_TX_END; pskData = 0x0000; } else { // Get the next character in the buffer. pskData = PSK31_VARICODE[pskBuffer[pskIndex++]]; // Reset the last bits shift register. pskLastBits = 0x0f; } // END if-else output_high (IO_OSK); break; case PSK_TX_END: // Just toggle the phase during the end sequence. if (pskPhase) pskPhase = false; else pskPhase = true; ddsPhase (pskPhase); if (--pskIndex == 0) { // Turn off the carrier. output_low (IO_OSK); // Turn off the PA. sysPAOutput (false); ddsSetMode (DDS_MODE_POWERDOWN); pskMessageDoneFlag = true; // We are ready for a new mesasge. pskMode = PSK_WAIT_MSG; // Enable access to the flash that shares the DDS SPI port. logSetDisableFlag (false); } else output_high (IO_OSK); break; } // END switch } // END if } /** * Generate and start transmision of PSK-31 message. If a message is already * being transmitted, nothing occurs. The UTC time in minutes is required to set * the DDS frequency. * * @param minutes UTC time in minutes */ void psk31TxPacket(uint8_t minutes) { // We can't create a packet if one is already in progress. if (pskMode != PSK_WAIT_MSG || !tncIsFree()) return; // Only transmit every 6 minutes if ((minutes % 6) != 0) return; // Disable access to the flash that shares the SPI port. logSetDisableFlag (true); // Set the pointer to the start of the buffer. pskIndex = 0; // If the packet generations fails, display a warning message. if (!psk31CreateDataPacket()) { pskIndex = 0; printf (psk31TxByte, "Ballooon QSL www.kd7lmo.net * Unable to generate telemetry\n\n"); psk31TxNull(); } // Select the PSK-31 real-time clock mode. timeSetMode (TIME_MODE_PSK31); // Configure the DDS for PSK-31 mode. ddsSetMode (DDS_MODE_PSK31); // Set the DDS frequency based on the time schedule that repeats every 6 minutes. ddsSetFreq (PSK31_FREQ_LIST[minutes % 3]); ddsSetAmplitude (PSK31_AMP_LIST[minutes % 3]); // Turn on the PA. sysPAOutput (true); // Prepare the variables that are used in the real-time clock interrupt. pskIndex = PSK_SYNC_LENGTH; pskCount = 0; pskData = 0x0000; pskMode = PSK_TX_SYNC; } /** * Write value to the PSK 31 buffer. Maintain the pointer * to the buffer. The value pskIndex must be set to 0 (zero) * before calling this function for the first time. * * @param value to save to telemetry buffer */ void psk31TxByte (uint8_t value) { if (pskIndex == PSK_MAX_PACKET) return; pskBuffer[pskIndex++] = value; } /** * Write length characters of string. */ void psk31TxString (char *string, uint8_t length) { while (length-- != 0) psk31TxByte (*string++); } void psk31TxNull () { psk31TxByte (0x00); } bool_t psk31IsMessageDone() { if (pskMessageDoneFlag) { pskMessageDoneFlag = false; return true; } return false; } /** @} */ /** * @defgroup serial Serial Port FIFO * * FIFO for the built-in serial port. * * @{ */ /// Note this size 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; /// Buffer 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. * * @{ */ /** * 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)); } /** * 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; } /** * Control the PA output. * * @param state true to turn on PA; otherwise false */ inline void sysPAOutput (bool_t state) { if (state) output_low (IO_PA); else output_high (IO_PA); } /** @} */ /** * @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 1mS 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. uint8_t timeDutyCycle; /// Time compare register 1 value. uint16_t timeCompare; /// The CCP1 timer delta between each interrupt uint16_t timeRate; /// Flag set true once per second. bool_t timeUpdateFlag; /// Flag that indicate the flight time should run. bool_t timeRunFlag; /// The number of interrupts in a 100mS time period. The value changes based on the current mode. uint16_t timeInterruptCountRollOver; /// The current time base mode. TIME_MODE timeMode; /// The change in the CCP_1 register for each interrupt period. 1mS for PSK31, 104uS for 9600 baud APRS, 3.3mS for 300 baud HF-APRS #define TIME_RATE_PSK31 4800 #define TIME_RATE_APRS 500 #define TIME_RATE_HF_APRS 16000 /// The number of interrupts in a 100mS time period. #define TIME_ROLLOVER_PSK31 100 #define TIME_ROLLOVER_APRS 960 #define TIME_ROLLOVER_HF_APRS 30 /** * 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_APRS; timeUpdateFlag = false; timeRate = TIME_RATE_APRS; timeRunFlag = false; timeUpdateFlag = false; timeInterruptCountRollOver = TIME_ROLLOVER_APRS; timeMode = TIME_MODE_APRS; // Configure CCP1 to interrupt at 1mS for PSK31 or 833uS for 1200 baud APRS CCP_1 = timeRate; set_timer1(timeCompare); setup_ccp1( CCP_COMPARE_INT ); setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 ); } /** * Get the time since boot in hours. * * @return run time in hours */ uint8_t timeGetHours() { return timeHours; } /** * Get the time since boot in minutes. * * @return run time in minutes */ uint8_t timeGetMinutes() { return timeMinutes; } /** * 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; } /** * Set the time base for PSK31 or APRS. * * @param mode TIME_MODE_APRS or TIME_MODE_PSK31 constant */ void timeSetMode (TIME_MODE mode) { // If we are already in the desired mode, we don't need to do anything. if (mode == timeMode) return; // Save the new mode. timeMode = mode; // Disable interrupts while we adjust the time information. disable_interrupts (INT_CCP1); switch (timeMode) { case TIME_MODE_APRS: timeRate = TIME_RATE_APRS; timeInterruptCountRollOver = TIME_ROLLOVER_APRS; break; case TIME_MODE_PSK31: timeRate = TIME_RATE_PSK31; timeInterruptCountRollOver = TIME_ROLLOVER_PSK31; break; case TIME_MODE_HF_APRS: timeRate = TIME_RATE_HF_APRS; timeInterruptCountRollOver = TIME_ROLLOVER_HF_APRS; break; } // END switch // If the current interrupt count is past the roller over state, move it back. // This means we will loose an interrupt period, but we don't really care. timeInterruptCount = timeInterruptCount % timeInterruptCountRollOver; // Restart the interrupts and we are back on-line. enable_interrupts (INT_CCP1); } // This function gets called every 1mS. #INT_CCP1 void timeUpdate() { // Pin used to measure CPU load in interrupt. output_high (PIN_B1); // Setup the next interrupt for the operational mode. timeCompare += timeRate; CCP_1 = timeCompare; // Call the appropriate time routine. if (timeMode == TIME_MODE_PSK31) psk31TimeUpdate(); else tncTimeUpdate(); // Read the GPS serial port and save any incoming characters. serialUpdate(); // Count the number of milliseconds required for the tenth second counter. if (++timeInterruptCount == timeInterruptCountRollOver) { 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 } // END if timeMinutes } // 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 // Pin used to measure CPU load in interrupt. output_low (PIN_B1); } /** @} */ /** * @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. (300mS) #define TNC_TX_DELAY 45 /// The UTC time in seconds to send an APRS packet. #define TNC_TIMESLOT 55 /// TNC state machine. enum TNC_MODE { /// Ready to accept a new message. TNC_TX_READY, /// Send the sync pattern at the start of the message. This is typically 200-300mS. TNC_TX_SYNC, /// Send the header that contains the callsign, path, and SSID information. TNC_TX_HEADER, /// Send the text message data. TNC_TX_DATA, /// Send the end sequence. TNC_TX_END }; // The size of the TNC output buffer. #define TNC_BUFFER_SIZE 80 // The dwell duration in bits at each idle test tone. #define TNC_DWELL_TIME 3600 // The packet header. 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 }; uint8_t tncLastBit; uint8_t tncPacketType; /// Current mode of the 1200 bps state machine. TNC_MODE 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; /// Buffer to hold the message portion of the AX.25 packet as we prepare it. uint8_t tncBuffer[TNC_BUFFER_SIZE]; // 1200 baud counter that determine which test signal to generate uint16_t tncTestCount; /// 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; void tncInit() { tncLastBit = 0; tncMode = TNC_TX_READY; tncTestCount = 0; tncPacketType = TNC_BOOT_MESSAGE; timeNCO = 0x00; timeLowRateCount = 0; timeNCOFreq = 0x2000; } /** * 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; } // This ISR is called when the timer register matches the compare register. // The interrupt rate is programmed to 833uS or 1 bit time at 1200 baud. void tncTimeUpdate() { // If we are waiting for a message, just return. if (tncMode == TNC_TX_READY) return; // Set the DDS frequency. ddsSetFTW (freqTable[timeNCO >> 8]); // Adjust the NCO phase. timeNCO += timeNCOFreq; if (++timeLowRateCount == 8) { timeLowRateCount = 0; // Set the A-FSK frequency. if (tncLastBit == 0x00) timeNCOFreq = 0x2000; else timeNCOFreq = 0x3aab; switch (tncMode) { case TNC_TX_SYNC: // The variable tncShift contains the lastest data byte. // NRZI enocde the data stream. if ((tncShift & 0x01) == 0x00) if (tncLastBit == 0) tncLastBit = 1; else tncLastBit = 0; // When the flag is done, determine if we need to send more or data. if (++tncBitCount == 8) { tncBitCount = 0; tncShift = 0x7e; // Once we transmit x mS of flags, send the data. // txDelay bytes * 8 bits/byte * 833uS/bit = x mS if (++tncIndex == TNC_TX_DELAY) { tncIndex = 0; tncShift = TNC_AX25_HEADER[0]; tncBitStuff = 0; tncMode = TNC_TX_HEADER; } // END if } else tncShift = tncShift >> 1; break; case TNC_TX_HEADER: // Determine if we have sent 5 ones in a row, if we have send a zero. if (tncBitStuff == 0x1f) { if (tncLastBit == 0) tncLastBit = 1; else tncLastBit = 0; tncBitStuff = 0x00; return; } // END if // The variable tncShift contains the lastest data byte. // NRZI enocde the data stream. if ((tncShift & 0x01) == 0x00) if (tncLastBit == 0) tncLastBit = 1; else tncLastBit = 0; // Save the data stream so we can determine if bit stuffing is // required on the next bit time. tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f; // If all the bits were shifted, get the next byte. if (++tncBitCount == 8) { tncBitCount = 0; // After the header is sent, then send the data. if (++tncIndex == sizeof(TNC_AX25_HEADER)) { tncIndex = 0; tncShift = 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 (tncLastBit == 0) tncLastBit = 1; else tncLastBit = 0; tncBitStuff = 0x00; return; } // END if // The variable tncShift contains the lastest data byte. // NRZI enocde the data stream. if ((tncShift & 0x01) == 0x00) if (tncLastBit == 0) tncLastBit = 1; else tncLastBit = 0; // Save the data stream so we can determine if bit stuffing is // required on the next bit time. tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f; // If all the bits were shifted, get the next byte. if (++tncBitCount == 8) { tncBitCount = 0; // If everything was sent, transmit closing flags. if (++tncIndex == tncLength) { tncIndex = 0; tncShift = 0x7e; tncMode = TNC_TX_END; } else tncShift = 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 (tncLastBit == 0) tncLastBit = 1; else tncLastBit = 0; // If all the bits were shifted, get the next one. if (++tncBitCount == 8) { tncBitCount = 0; tncShift = 0x7e; // Transmit closing flags. if (++tncIndex == 2) { // Turn off the DDS and PA (PTT). ddsPTT (false); sysPAOutput (false); ddsSetMode (DDS_MODE_POWERDOWN); tncMode = TNC_TX_READY; // Disable access to the flash that shares the SPI port. logSetDisableFlag (false); return; } // END if } else tncShift = tncShift >> 1; break; } // END switch } // END if } /** * Write value 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 value to save to telemetry buffer */ void tncTxByte (uint8_t value) { *tncBufferPnt++ = value; ++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 plain text status packet. Data is written through the tncTxByte * callback function. */ void tncStatusPacket() { int16_t temperature; // Plain text telemetry. printf (tncTxByte, ">Balloon "); // Display the flight time. printf (tncTxByte, "%03U:%02U:%02U ", timeHours, timeMinutes, timeSeconds); // Altitude in feet. printf (tncTxByte, "%ld' ", gpsPosition.altitudeFeet); // 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); // Internal temperature. temperature = lm92GetTemp(); printf (tncTxByte, "%ld.%ldF ", temperature / 10, abs(temperature % 10)); // Print web address link. printf (tncTxByte, "QSL www.kd7lmo.net"); } /** * 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)); } /** * Prepare an AX.25 data packet. Each time this method is called, it automatically * rotates through 1 of 4 messages. */ void tncTxPacket(TNC_PACKET_TYPE tncPacketType) { uint16_t crc; // Only transmit if there is not another message in progress and PSK 31 isn't using the hardware. if (tncMode != TNC_TX_READY || !psk31IsFree()) return; // Disable access to the flash that shares the SPI port. logSetDisableFlag (true); // 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 PSK-31/APRS Beacon - V1.07"); break; case TNC_STATUS: tncStatusPacket(); break; case TNC_GGA: tncGPGGAPacket(); break; case TNC_RMC: tncGPRMCPacket(); 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; // Select the APRS real-time clock mode. timeSetMode (TIME_MODE_APRS); // Configure the DDS for APRS mode. ddsSetMode (DDS_MODE_APRS); // Set for maximum output level. Yes, 6.0dBC on the DDS is correct since the PA operates in compression above HF. ddsSetAmplitude (60); // Prepare the variables that are used in the real-time clock interrupt. tncBitCount = 0; tncShift = 0x7e; tncLastBit = 0; tncIndex = 0; tncMode = TNC_TX_SYNC; // Turn on the DSS and PA (PTT). ddsPTT (true); sysPAOutput (true); } /** @} */ // This is where we go after reset. void main() { uint8_t utcSeconds, utcMinutes, lockLostCounter; // Set the initial output state before we configure the output direction. output_low (IO_GPS_PWR); output_high (IO_LED); output_low (IO_CUT_DOWN); output_high (IO_PA); // Disable the ADC so we can use PORT A digital I/O. setup_adc_ports( NO_ANALOGS ); // Configure the I/O ports. set_tris_a (0x00); set_tris_b (0x62); set_tris_c (0x92); // Setup the subsystems. ballastInit(); cutDownInit(); gpsInit(); timeInit(); psk31Init(); serialInit(); tncInit(); // Wait for the power converter to stabilize and the subsystems to reset. delay_ms (400); // Program the DDS. ddsInit(); // Finally setup the log. We do this after the DDS because the SPI port gets set there. logInit(); // Turn off the LED just as we enable the interrupts. 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. output_high (IO_GPS_PWR); // Allow the GPS engine to boot. delay_ms (100); // Initialize the GPS engine. while (!gpsSetup()); // 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); // Counter and time to send packets if the GPS engine not available. lockLostCounter = 0; utcSeconds = gpsPosition.seconds; utcMinutes = gpsPosition.minutes; // Transmit software version packet on start up. tncTxPacket(TNC_BOOT_MESSAGE); // This is the main loop where we wait for timer ticks. for (;;) { // Read the GPS engine serial port FIFO and process the GPS data. gpsUpdate(); if (gpsIsReady()) { // Start the flight timer when we get our first valid 3D fix and turn off the status LED. if (gpsGetFixType() == GPS_3D_FIX) { timeSetRunFlag(); timeSetDutyCycle (TIME_DUTYCYCLE_0); } // END if // Transmit the packets in the desired time slots. switch (gpsPosition.seconds) { case TNC_TIMESLOT: switch (gpsPosition.minutes % 3) { case 0: tncTxPacket(TNC_STATUS); break; case 1: tncTxPacket(TNC_GGA); break; case 2: tncTxPacket(TNC_RMC); break; } // END switch break; case PSK31_TIMESLOT: psk31TxPacket(gpsPosition.minutes); break; } // END switch // Re-sync our internal clock to the UTC. utcSeconds = gpsPosition.seconds; lockLostCounter = 0; // Log the data to flash. if ((gpsPosition.seconds % 10) == 0) sysLogGPSData(); // Run the ballast pump as required. ballastCheck(); cutDownCheck(); } // END if // Processing that occurs once a second. if (timeUpdateFlag) { // We maintain the UTC time in minutes and seconds in case the GPS engine stop operating. if (++utcSeconds == 60) { utcSeconds = 0; if (++utcMinutes == 60) utcMinutes = 0; } // END if // If we loose the GPS for more than 5 seconds, we will determine when to send a packet based on internal time. if (lockLostCounter == 5) { // Transmit the packets in the desired time slots. switch (utcSeconds) { case TNC_TIMESLOT: switch (gpsPosition.minutes % 3) { case 0: tncTxPacket(TNC_STATUS); break; case 1: tncTxPacket(TNC_GGA); break; case 2: tncTxPacket(TNC_RMC); break; } // END switch break; case PSK31_TIMESLOT: psk31TxPacket(utcMinutes); break; } // END switch } else ++lockLostCounter; // Set the flag to indicate we have processed this time period. timeUpdateFlag = false; } // END if if (psk31IsMessageDone()) { // Record the temperature post-transmit. logType (LOG_TEMPERATURE_2); logInt16 (lm92GetTemp()); } // END if } // END while }