2 * http://ad7zj.net/kd7lmo/aprsbeacon_code.html
4 * @mainpage Pico Beacon
6 * @section overview_sec Overview
8 * The Pico Beacon is an APRS based tracking beacon that operates in the UHF 420-450MHz band. The device utilizes a
9 * Microchip PIC 18F2525 embedded controller, Motorola M12+ GPS engine, and Analog Devices AD9954 DDS. The device is capable
10 * of generating a 1200bps A-FSK and 9600 bps FSK AX.25 compliant APRS (Automatic Position Reporting System) message.
14 * @section history_sec Revision History
16 * @subsection v305 V3.05
17 * 23 Dec 2006, Change include; (1) change printf format width to conform to ANSI standard when new CCS 4.xx compiler released.
20 * @subsection v304 V3.04
21 * 10 Jan 2006, Change include; (1) added amplitude control to engineering mode,
22 * (2) corrected number of bytes reported in log,
23 * (3) add engineering command to set high rate position reports (5 seconds), and
24 * (4) corrected size of LOG_COORD block when searching for end of log.
26 * @subsection v303 V3.03
27 * 15 Sep 2005, Change include; (1) removed AD9954 setting SDIO as input pin,
28 * (2) additional comments and Doxygen tags,
29 * (3) integration and test code calculates DDS FTW,
30 * (4) swapped bus and reference analog input ports (hardware change),
31 * (5) added message that indicates we are reading flash log and reports length,
32 * (6) report bus voltage in 10mV steps, and
33 * (7) change log type enumerated values to XORed nibbles for error detection.
36 * @subsection v302 V3.02
37 * 6 Apr 2005, Change include; (1) corrected tracked satellite count in NMEA-0183 $GPGGA message,
38 * (2) Doxygen documentation clean up and additions, and
39 * (3) added integration and test code to baseline.
42 * @subsection v301 V3.01
43 * 13 Jan 2005, Renamed project and files to Pico Beacon.
46 * @subsection v300 V3.00
47 * 15 Nov 2004, Change include; (1) Micro Beacon extreme hardware changes including integral transmitter,
48 * (2) PIC18F2525 processor,
49 * (3) AD9954 DDS support functions,
50 * (4) added comments and formatting for doxygen,
51 * (5) process GPS data with native Motorola protocol,
52 * (6) generate plain text $GPGGA and $GPRMC messages,
53 * (7) power down GPS 5 hours after lock,
54 * (8) added flight data recorder, and
55 * (9) added diagnostics terminal mode.
58 * @subsection v201 V2.01
59 * 30 Jan 2004, Change include; (1) General clean up of in-line documentation, and
60 * (2) changed temperature resolution to 0.1 degrees F.
63 * @subsection v200 V2.00
64 * 26 Oct 2002, Change include; (1) Micro Beacon II hardware changes including PIC18F252 processor,
66 * (3) GPS power control,
67 * (4) additional ADC input, and
68 * (5) LM60 temperature sensor.
71 * @subsection v101 V1.01
72 * 5 Dec 2001, Change include; (1) Changed startup message, and
73 * (2) applied SEPARATE pragma to several methods for memory usage.
76 * @subsection v100 V1.00
77 * 25 Sep 2001, Initial release. Flew ANSR-3 and ANSR-4.
83 * @section copyright_sec Copyright
85 * Copyright (c) 2001-2009 Michael Gray, KD7LMO
90 * @section gpl_sec GNU General Public License
92 * This program is free software; you can redistribute it and/or modify
93 * it under the terms of the GNU General Public License as published by
94 * the Free Software Foundation; either version 2 of the License, or
95 * (at your option) any later version.
97 * This program is distributed in the hope that it will be useful,
98 * but WITHOUT ANY WARRANTY; without even the implied warranty of
99 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100 * GNU General Public License for more details.
102 * You should have received a copy of the GNU General Public License
103 * along with this program; if not, write to the Free Software
104 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
110 * @section design Design Details
112 * Provides design details on a variety of the components that make up the Pico Beacon.
118 * @page power Power Consumption
120 * Measured DC power consumption.
122 * 3VDC prime power current
127 * 18mA Processor running, all I/O off
131 * 120mA GPS running w/antenna
133 * 250mA DDS running and GPS w/antenna
135 * 420mA DDS running, GPS w/antenna, and PA chain on with no RF
150 typedef int32_t int32;
154 // Public methods, constants, and data structures for each class.
156 /// Operational modes of the AD9954 DDS for the ddsSetMode function.
159 /// Device has not been initialized.
160 DDS_MODE_NOT_INITIALIZED,
162 /// Device in lowest power down mode.
165 /// Generate FM modulated audio tones.
168 /// Generate true FSK tones.
173 void ddsSetAmplitude (uint8_t amplitude);
174 void ddsSetOutputScale (uint16_t amplitude);
175 void ddsSetFSKFreq (uint32_t ftw0, uint32_t ftw1);
176 void ddsSetFreq (uint32_t freq);
177 void ddsSetFTW (uint32_t ftw);
178 void ddsSetMode (DDS_MODE mode);
186 /// 2D (Latitude/Longitude) fix.
189 /// 3D (Latitude/Longitude/Altitude) fix.
193 /// GPS Position information.
196 /// Flag that indicates the position information has been updated since it was last checked.
199 /// Month in UTC time.
202 /// Day of month in UTC time.
205 /// Hours in UTC time.
208 /// Minutes in UTC time.
211 /// Seconds in UTC time.
214 /// Year in UTC time.
217 /// Latitude in milli arc-seconds where + is North, - is South.
220 /// Longitude in milli arc-seconds where + is East, - is West.
226 /// Calculated altitude in feet
227 int32_t altitudeFeet;
229 /// 3D speed in cm/second.
232 /// 2D speed in cm/second.
235 /// Heading units of 0.1 degrees.
238 /// DOP (Dilution of Precision)
241 /// 16-bit number that represents status of GPS engine.
244 /// Number of tracked satellites used in the fix position.
247 /// Number of visible satellites.
249 } GPSPOSITION_STRUCT;
251 GPSPOSITION_STRUCT gpsPosition;
255 GPS_FIX_TYPE gpsGetFixType();
256 int32_t gpsGetPeakAltitude();
261 uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc);
263 uint8_t timeGetTicks();
265 void timeSetDutyCycle (uint8_t dutyCycle);
268 /// Operational modes of the TNC for the tncSetMode function.
271 /// No operation waiting for setup and configuration.
274 /// 1200 bps using A-FSK (Audio FSK) tones.
277 /// 9600 bps using true FSK tones.
283 void tncHighRate(bool_t state);
284 void tncSetMode (TNC_DATA_MODE dataMode);
285 void tnc1200TimerTick();
286 void tnc9600TimerTick();
287 void tncTxByte (uint8_t value);
288 void tncTxPacket(TNC_DATA_MODE dataMode);
293 * @defgroup DDS AD9954 DDS (Direct Digital Synthesizer)
295 * Functions to control the Analog Devices AD9954 DDS.
300 /// AD9954 CFR1 - Control functions including RAM, profiles, OSK, sync, sweep, SPI, and power control settings.
301 #define DDS_AD9954_CFR1 0x00
303 /// AD9954 CFR2 - Control functions including sync, PLL multiplier, VCO range, and charge pump current.
304 #define DDS_AD9954_CFR2 0x01
306 /// AD9954 ASF - Auto ramp rate speed control and output scale factor (0x0000 to 0x3fff).
307 #define DDS_AD9954_ASF 0x02
309 /// AD9954 ARR - Amplitude ramp rate for OSK function.
310 #define DDS_AD9954_ARR 0x03
312 /// AD9954 FTW0 - Frequency tuning word 0.
313 #define DDS_AD9954_FTW0 0x04
315 /// AD9954 FTW1 - Frequency tuning word 1
316 #define DDS_AD9954_FTW1 0x06
318 /// AD9954 NLSCW - Negative Linear Sweep Control Word used for spectral shaping in FSK mode
319 #define DDS_AD9954_NLSCW 0x07
321 /// AD9954 PLSCW - Positive Linear Sweep Control Word used for spectral shaping in FSK mode
322 #define DDS_AD9954_PLSCW 0x08
324 /// AD9954 RSCW0 - RAM Segment Control Word 0
325 #define DDS_AD9954_RWCW0 0x07
327 /// AD9954 RSCW0 - RAM Segment Control Word 1
328 #define DDS_AD9954_RWCW1 0x08
330 /// AD9954 RAM segment
333 /// Current operational mode.
336 /// Number of digits in DDS frequency to FTW conversion.
337 #define DDS_FREQ_TO_FTW_DIGITS 9
339 /// Array of multiplication factors used to convert frequency to the FTW.
340 const uint32_t DDS_MULT[DDS_FREQ_TO_FTW_DIGITS] = { 11, 7, 7, 3, 4, 8, 4, 9, 1 };
342 /// Array of divisors used to convert frequency to the FTW.
343 const uint32_t DDS_DIVISOR[DDS_FREQ_TO_FTW_DIGITS - 1] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
345 /// Lookup table to convert dB amplitude scale in 0.5 steps to a linear DDS scale factor.
346 const uint16_t DDS_AMP_TO_SCALE[] =
348 16383, 15467, 14601, 13785, 13013, 12286, 11598, 10949, 10337, 9759, 9213, 8697,
349 8211, 7752, 7318, 6909, 6522, 6157, 5813, 5488, 5181, 4891, 4617, 4359, 4115, 3885, 3668, 3463,
350 3269, 3086, 2913, 2750, 2597, 2451, 2314, 2185, 2062, 1947, 1838, 1735, 1638
354 /// Frequency Word List - 4.0KHz FM frequency deviation at 81.15MHz (445.950MHz)
355 const uint32_t freqTable[256] =
357 955418300, 955419456, 955420611, 955421765, 955422916, 955424065, 955425210, 955426351,
358 955427488, 955428618, 955429743, 955430861, 955431971, 955433073, 955434166, 955435249,
359 955436322, 955437385, 955438435, 955439474, 955440500, 955441513, 955442511, 955443495,
360 955444464, 955445417, 955446354, 955447274, 955448176, 955449061, 955449926, 955450773,
361 955451601, 955452408, 955453194, 955453960, 955454704, 955455426, 955456126, 955456803,
362 955457457, 955458088, 955458694, 955459276, 955459833, 955460366, 955460873, 955461354,
363 955461809, 955462238, 955462641, 955463017, 955463366, 955463688, 955463983, 955464250,
364 955464489, 955464701, 955464884, 955465040, 955465167, 955465266, 955465337, 955465380,
365 955465394, 955465380, 955465337, 955465266, 955465167, 955465040, 955464884, 955464701,
366 955464489, 955464250, 955463983, 955463688, 955463366, 955463017, 955462641, 955462238,
367 955461809, 955461354, 955460873, 955460366, 955459833, 955459276, 955458694, 955458088,
368 955457457, 955456803, 955456126, 955455426, 955454704, 955453960, 955453194, 955452408,
369 955451601, 955450773, 955449926, 955449061, 955448176, 955447274, 955446354, 955445417,
370 955444464, 955443495, 955442511, 955441513, 955440500, 955439474, 955438435, 955437385,
371 955436322, 955435249, 955434166, 955433073, 955431971, 955430861, 955429743, 955428618,
372 955427488, 955426351, 955425210, 955424065, 955422916, 955421765, 955420611, 955419456,
373 955418300, 955417144, 955415989, 955414836, 955413684, 955412535, 955411390, 955410249,
374 955409113, 955407982, 955406857, 955405740, 955404629, 955403528, 955402435, 955401351,
375 955400278, 955399216, 955398165, 955397126, 955396100, 955395088, 955394089, 955393105,
376 955392136, 955391183, 955390246, 955389326, 955388424, 955387540, 955386674, 955385827,
377 955385000, 955384192, 955383406, 955382640, 955381896, 955381174, 955380474, 955379797,
378 955379143, 955378513, 955377906, 955377324, 955376767, 955376235, 955375728, 955375246,
379 955374791, 955374362, 955373959, 955373583, 955373234, 955372912, 955372618, 955372350,
380 955372111, 955371900, 955371716, 955371560, 955371433, 955371334, 955371263, 955371220,
381 955371206, 955371220, 955371263, 955371334, 955371433, 955371560, 955371716, 955371900,
382 955372111, 955372350, 955372618, 955372912, 955373234, 955373583, 955373959, 955374362,
383 955374791, 955375246, 955375728, 955376235, 955376767, 955377324, 955377906, 955378513,
384 955379143, 955379797, 955380474, 955381174, 955381896, 955382640, 955383406, 955384192,
385 955385000, 955385827, 955386674, 955387540, 955388424, 955389326, 955390246, 955391183,
386 955392136, 955393105, 955394089, 955395088, 955396100, 955397126, 955398165, 955399216,
387 955400278, 955401351, 955402435, 955403528, 955404629, 955405740, 955406857, 955407982,
388 955409113, 955410249, 955411390, 955412535, 955413684, 955414836, 955415989, 955417144
392 * Set DDS frequency tuning word. The output frequency is equal to RefClock * (ftw / 2 ^ 32).
394 * @param ftw Frequency Tuning Word
396 void ddsSetFTW (uint32_t ftw)
399 int x = ftw - freqTable[0];
400 putchar (x > 0 ? 0xff : 0x0);
401 // printf ("%d %d\n", id++, x > 0 ? 1 : 0);
405 * Convert frequency in hertz to 32-bit DDS FTW (Frequency Tune Word).
407 * @param freq frequency in Hertz
410 void ddsSetFreq(uint32_t freq)
415 // To avoid rounding errors with floating point math, we do a long multiply on the data.
416 ftw = freq * DDS_MULT[0];
418 for (i = 0; i < DDS_FREQ_TO_FTW_DIGITS - 1; ++i)
419 ftw += (freq * DDS_MULT[i+1]) / DDS_DIVISOR[i];
425 * Set DDS frequency tuning word for the FSK 0 and 1 values. The output frequency is equal
426 * to RefClock * (ftw / 2 ^ 32).
428 * @param ftw0 frequency tuning word for the FSK 0 value
429 * @param ftw1 frequency tuning word for the FSK 1 value
431 void ddsSetFSKFreq (uint32_t ftw0, uint32_t ftw1)
433 // printf ("ftw0 %d ftw1 %d\n", ftw0, ftw1);
437 * Set the DDS to run in A-FSK, FSK, or PSK31 mode
439 * @param mode DDS_MODE_APRS, DDS_MODE_PSK31, or DDS_MODE_HF_APRS constant
441 void ddsSetMode (DDS_MODE mode)
443 // printf ("mode %d\n", mode);
449 * @defgroup GPS Motorola M12+ GPS Engine
451 * Functions to control the Motorola M12+ GPS engine in native binary protocol mode.
456 /// The maximum length of a binary GPS engine message.
457 #define GPS_BUFFER_SIZE 50
459 /// GPS parse engine state machine values.
462 /// 1st start character '@'
465 /// 2nd start character '@'
468 /// Upper case 'A' - 'Z' message type
471 /// Lower case 'a' - 'z' message type
474 /// 0 - xx bytes based on message type 'Aa'
480 /// End of message - Carriage Return
483 /// End of message - Line Feed
485 } GPS_PARSE_STATE_MACHINE;
487 /// Index into gpsBuffer used to store message data.
490 /// State machine used to parse the GPS message stream.
491 GPS_PARSE_STATE_MACHINE gpsParseState;
493 /// Buffer to store data as it is read from the GPS engine.
494 uint8_t gpsBuffer[GPS_BUFFER_SIZE];
496 /// Peak altitude detected while GPS is in 3D fix mode.
497 int32_t gpsPeakAltitude;
499 /// Checksum used to verify binary message from GPS engine.
502 /// Last verified GPS message received.
503 GPSPOSITION_STRUCT gpsPosition;
506 * Get the type of fix.
508 * @return gps fix type enumeration
510 GPS_FIX_TYPE gpsGetFixType()
512 // The upper 3-bits determine the fix type.
513 switch (gpsPosition.status & 0xe000)
527 * Peak altitude detected while GPS is in 3D fix mode since the system was booted.
529 * @return altitude in feet
531 int32_t gpsGetPeakAltitude()
533 return gpsPeakAltitude;
537 * Initialize the GPS subsystem.
541 // Initial parse state.
542 gpsParseState = GPS_START1;
544 // Assume we start at sea level.
547 // Clear the structure that stores the position message.
548 memset (&gpsPosition, 0, sizeof(GPSPOSITION_STRUCT));
550 // Setup the timers used to measure the 1-PPS time period.
551 // setup_timer_3(T3_INTERNAL | T3_DIV_BY_1);
552 // setup_ccp2 (CCP_CAPTURE_RE | CCP_USE_TIMER3);
556 * Determine if new GPS message is ready to process. This function is a one shot and
557 * typically returns true once a second for each GPS position fix.
559 * @return true if new message available; otherwise false
564 if (gpsPosition.updateFlag)
566 gpsPosition.updateFlag = false;
574 * Calculate NMEA-0183 message checksum of buffer that is length bytes long.
576 * @param buffer pointer to data buffer.
577 * @param length number of bytes in buffer.
579 * @return checksum of buffer
581 uint8_t gpsNMEAChecksum (uint8_t *buffer, uint8_t length)
587 for (i = 0; i < length; ++i)
588 checksum ^= buffer[i];
594 * Verify the GPS engine is sending the @@Hb position report message. If not,
595 * configure the GPS engine to send the desired report.
597 * @return true if GPS engine operation; otherwise false
601 uint8_t startTime, retryCount;
603 // We wait 10 seconds for the GPS engine to respond to our message request.
604 startTime = timeGetTicks();
607 while (++retryCount < 10)
609 // Read the serial FIFO and process the GPS messages.
612 // If a GPS data set is available, then GPS is operational.
615 // timeSetDutyCycle (TIME_DUTYCYCLE_10);
619 if (timeGetTicks() > startTime)
621 puts ("@@Hb\001\053\015\012");
631 * Parse the Motorola @@Hb (Short position/message) report.
633 void gpsParsePositionMessage()
635 // Convert the binary stream into data elements. We will scale to the desired units
636 // as the values are used.
637 gpsPosition.updateFlag = true;
639 gpsPosition.month = gpsBuffer[0];
640 gpsPosition.day = gpsBuffer[1];
641 gpsPosition.year = ((uint16_t) gpsBuffer[2] << 8) | gpsBuffer[3];
642 gpsPosition.hours = gpsBuffer[4];
643 gpsPosition.minutes = gpsBuffer[5];
644 gpsPosition.seconds = gpsBuffer[6];
645 gpsPosition.latitude = ((int32) gpsBuffer[11] << 24) | ((int32) gpsBuffer[12] << 16) | ((int32) gpsBuffer[13] << 8) | (int32) gpsBuffer[14];
646 gpsPosition.longitude = ((int32) gpsBuffer[15] << 24) | ((int32) gpsBuffer[16] << 16) | ((int32) gpsBuffer[17] << 8) | gpsBuffer[18];
647 gpsPosition.altitudeCM = ((int32) gpsBuffer[19] << 24) | ((int32) gpsBuffer[20] << 16) | ((int32) gpsBuffer[21] << 8) | gpsBuffer[22];
648 gpsPosition.altitudeFeet = gpsPosition.altitudeCM * 100l / 3048l;
649 gpsPosition.vSpeed = ((uint16_t) gpsBuffer[27] << 8) | gpsBuffer[28];
650 gpsPosition.hSpeed = ((uint16_t) gpsBuffer[29] << 8) | gpsBuffer[30];
651 gpsPosition.heading = ((uint16_t) gpsBuffer[31] << 8) | gpsBuffer[32];
652 gpsPosition.dop = ((uint16_t) gpsBuffer[33] << 8) | gpsBuffer[34];
653 gpsPosition.visibleSats = gpsBuffer[35];
654 gpsPosition.trackedSats = gpsBuffer[36];
655 gpsPosition.status = ((uint16_t) gpsBuffer[37] << 8) | gpsBuffer[38];
657 // Update the peak altitude if we have a valid 3D fix.
658 if (gpsGetFixType() == GPS_3D_FIX)
659 if (gpsPosition.altitudeFeet > gpsPeakAltitude)
660 gpsPeakAltitude = gpsPosition.altitudeFeet;
664 * Turn on the GPS engine power and serial interface.
668 // 3.0 VDC LDO control line.
669 // output_high (IO_GPS_PWR);
674 * Turn off the GPS engine power and serial interface.
678 // 3.0 VDC LDO control line.
679 // output_low (IO_GPS_PWR);
686 * @defgroup sys System Library Functions
688 * Generic system functions similiar to the run-time C library.
694 * Calculate the CRC-16 CCITT of buffer that is length bytes long.
695 * The crc parameter allow the calculation on the CRC on multiple buffers.
697 * @param buffer Pointer to data buffer.
698 * @param length number of bytes in data buffer
699 * @param crc starting value
701 * @return CRC-16 of buffer[0 .. length]
703 uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc)
705 uint8_t i, bit, value;
707 for (i = 0; i < length; ++i)
711 for (bit = 0; bit < 8; ++bit)
713 crc ^= (value & 0x01);
714 crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 );
725 * @defgroup rtc Real Time Interrupt tick
727 * Manage the built-in real time interrupt. The interrupt clock PRI is 104uS (9600 bps).
732 /// A counter that ticks every 100mS.
735 /// Counts the number of 104uS interrupts for a 100mS time period.
736 uint16_t timeInterruptCount;
738 /// Counts the number of 100mS time periods in 1 second.
741 /// System time in seconds.
744 /// System time in minutes.
747 /// System time in hours.
750 /// Desired LED duty cycle 0 to 9 where 0 = 0% and 9 = 90%.
751 uint8_t timeDutyCycle;
753 /// Current value of the timer 1 compare register used to generate 104uS interrupt rate (9600bps).
754 uint16_t timeCompare;
756 /// 16-bit NCO where the upper 8-bits are used to index into the frequency generation table.
759 /// Audio tone NCO update step (phase).
760 uint16_t timeNCOFreq;
762 /// Counter used to deciminate down from the 104uS to 833uS interrupt rate. (9600 to 1200 baud)
763 uint8_t timeLowRateCount;
765 /// Current TNC mode (standby, 1200bps A-FSK, or 9600bps FSK)
766 TNC_DATA_MODE tncDataMode;
768 /// Flag set true once per second.
769 bool_t timeUpdateFlag;
771 /// Flag that indicate the flight time should run.
774 /// The change in the CCP_1 register for each 104uS (9600bps) interrupt period.
775 #define TIME_RATE 125
778 * Running 8-bit counter that ticks every 100mS.
780 * @return 100mS time tick
782 uint8_t timeGetTicks()
788 * Initialize the real-time clock.
793 timeInterruptCount = 0;
798 timeCompare = TIME_RATE;
799 timeUpdateFlag = false;
801 timeLowRateCount = 0;
802 timeNCOFreq = 0x2000;
803 tncDataMode = TNC_MODE_STANDBY;
808 * Function return true once a second based on real-time clock.
810 * @return true on one second tick; otherwise false
812 bool_t timeIsUpdate()
816 timeUpdateFlag = false;
824 * Set a flag to indicate the flight time should run. This flag is typically set when the payload
827 void timeSetRunFlag()
833 * Timer interrupt handler called every 104uS (9600 times/second).
837 // Setup the next interrupt for the operational mode.
838 timeCompare += TIME_RATE;
839 // CCP_1 = timeCompare;
843 case TNC_MODE_STANDBY:
846 case TNC_MODE_1200_AFSK:
847 ddsSetFTW (freqTable[timeNCO >> 8]);
849 timeNCO += timeNCOFreq;
851 if (++timeLowRateCount == 8)
853 timeLowRateCount = 0;
858 case TNC_MODE_9600_FSK:
867 * @defgroup tnc TNC (Terminal Node Controller)
869 * Functions that provide a subset of the TNC functions.
874 /// The number of start flag bytes to send before the packet message. (360bits * 1200bps = 300mS)
875 #define TNC_TX_DELAY 45
877 /// The size of the TNC output buffer.
878 #define TNC_BUFFER_SIZE 80
880 /// States that define the current mode of the 1200 bps (A-FSK) state machine.
883 /// Stand by state ready to accept new message.
886 /// 0x7E bit stream pattern used to define start of APRS message.
889 /// Transmit the AX.25 header that contains the source/destination call signs, APRS path, and flags.
892 /// Transmit the message data.
895 /// Transmit the end flag sequence.
897 } TNC_TX_1200BPS_STATE;
899 /// Enumeration of the messages we can transmit.
902 /// Startup message that contains software version information.
905 /// Plain text status message.
908 /// Message that contains GPS NMEA-0183 $GPGGA message.
911 /// Message that contains GPS NMEA-0183 $GPRMC message.
915 /// AX.25 compliant packet header that contains destination, station call sign, and path.
916 /// 0x76 for SSID-11, 0x78 for SSID-12
917 uint8_t TNC_AX25_HEADER[30] = {
918 'A' << 1, 'P' << 1, 'R' << 1, 'S' << 1, ' ' << 1, ' ' << 1, 0x60, \
919 'K' << 1, 'D' << 1, '7' << 1, 'S' << 1, 'Q' << 1, 'G' << 1, 0x76, \
920 'G' << 1, 'A' << 1, 'T' << 1, 'E' << 1, ' ' << 1, ' ' << 1, 0x60, \
921 'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '3' << 1, ' ' << 1, 0x67, \
925 /// The next bit to transmit.
928 /// Current mode of the 1200 bps state machine.
929 TNC_TX_1200BPS_STATE tncMode;
931 /// Counter for each bit (0 - 7) that we are going to transmit.
934 /// A shift register that holds the data byte as we bit shift it for transmit.
937 /// Index into the APRS header and data array for each byte as we transmit it.
940 /// The number of bytes in the message portion of the AX.25 message.
943 /// A copy of the last 5 bits we've transmitted to determine if we need to bit stuff on the next bit.
946 /// Pointer to TNC buffer as we save each byte during message preparation.
947 uint8_t *tncBufferPnt;
949 /// The type of message to tranmit in the next packet.
950 TNC_MESSAGE_TYPE tncPacketType;
952 /// Buffer to hold the message portion of the AX.25 packet as we prepare it.
953 uint8_t tncBuffer[TNC_BUFFER_SIZE];
955 /// Flag that indicates we want to transmit every 5 seconds.
956 bool_t tncHighRateFlag;
959 * Initialize the TNC internal variables.
964 tncMode = TNC_TX_READY;
965 tncPacketType = TNC_BOOT_MESSAGE;
966 tncHighRateFlag = false;
970 * Determine if the hardware if ready to transmit a 1200 baud packet.
972 * @return true if ready; otherwise false
976 if (tncMode == TNC_TX_READY)
982 void tncHighRate(bool_t state)
984 tncHighRateFlag = state;
988 * Configure the TNC for the desired data mode.
990 * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK
992 void tncSetMode(TNC_DATA_MODE dataMode)
996 case TNC_MODE_1200_AFSK:
997 ddsSetMode (DDS_MODE_AFSK);
1000 case TNC_MODE_9600_FSK:
1001 ddsSetMode (DDS_MODE_FSK);
1003 // FSK tones at 445.947 and 445.953 MHz
1004 ddsSetFSKFreq (955382980, 955453621);
1008 tncDataMode = dataMode;
1012 * Determine if the seconds value timeSeconds is a valid time slot to transmit
1013 * a message. Time seconds is in UTC.
1015 * @param timeSeconds UTC time in seconds
1017 * @return true if valid time slot; otherwise false
1019 bool_t tncIsTimeSlot (uint8_t timeSeconds)
1021 if (tncHighRateFlag)
1023 if ((timeSeconds % 5) == 0)
1029 switch (timeSeconds)
1043 * Method that is called every 833uS to transmit the 1200bps A-FSK data stream.
1044 * The provides the pre and postamble as well as the bit stuffed data stream.
1046 void tnc1200TimerTick()
1048 // Set the A-FSK frequency.
1049 if (tncTxBit == 0x00)
1050 timeNCOFreq = 0x2000;
1052 timeNCOFreq = 0x3aab;
1057 // Generate a test signal alteranting between high and low tones.
1058 tncTxBit = (tncTxBit == 0 ? 1 : 0);
1062 // The variable tncShift contains the lastest data byte.
1063 // NRZI enocde the data stream.
1064 if ((tncShift & 0x01) == 0x00)
1070 // When the flag is done, determine if we need to send more or data.
1071 if (++tncBitCount == 8)
1076 // Once we transmit x mS of flags, send the data.
1077 // txDelay bytes * 8 bits/byte * 833uS/bit = x mS
1078 if (++tncIndex == TNC_TX_DELAY)
1081 tncShift = TNC_AX25_HEADER[0];
1083 tncMode = TNC_TX_HEADER;
1086 tncShift = tncShift >> 1;
1090 // Determine if we have sent 5 ones in a row, if we have send a zero.
1091 if (tncBitStuff == 0x1f)
1102 // The variable tncShift contains the lastest data byte.
1103 // NRZI enocde the data stream.
1104 if ((tncShift & 0x01) == 0x00)
1110 // Save the data stream so we can determine if bit stuffing is
1111 // required on the next bit time.
1112 tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;
1114 // If all the bits were shifted, get the next byte.
1115 if (++tncBitCount == 8)
1119 // After the header is sent, then send the data.
1120 if (++tncIndex == sizeof(TNC_AX25_HEADER))
1123 tncShift = tncBuffer[0];
1124 tncMode = TNC_TX_DATA;
1126 tncShift = TNC_AX25_HEADER[tncIndex];
1129 tncShift = tncShift >> 1;
1134 // Determine if we have sent 5 ones in a row, if we have send a zero.
1135 if (tncBitStuff == 0x1f)
1146 // The variable tncShift contains the lastest data byte.
1147 // NRZI enocde the data stream.
1148 if ((tncShift & 0x01) == 0x00)
1154 // Save the data stream so we can determine if bit stuffing is
1155 // required on the next bit time.
1156 tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;
1158 // If all the bits were shifted, get the next byte.
1159 if (++tncBitCount == 8)
1163 // If everything was sent, transmit closing flags.
1164 if (++tncIndex == tncLength)
1168 tncMode = TNC_TX_END;
1170 tncShift = tncBuffer[tncIndex];
1173 tncShift = tncShift >> 1;
1178 // The variable tncShift contains the lastest data byte.
1179 // NRZI enocde the data stream.
1180 if ((tncShift & 0x01) == 0x00)
1186 // If all the bits were shifted, get the next one.
1187 if (++tncBitCount == 8)
1192 // Transmit two closing flags.
1193 if (++tncIndex == 2)
1195 tncMode = TNC_TX_READY;
1197 // Tell the TNC time interrupt to stop generating the frequency words.
1198 tncDataMode = TNC_MODE_STANDBY;
1201 // output_low (IO_OSK);
1202 // output_low (IO_PTT);
1203 ddsSetMode (DDS_MODE_POWERDOWN);
1208 tncShift = tncShift >> 1;
1215 * Method that is called every 104uS to transmit the 9600bps FSK data stream.
1217 void tnc9600TimerTick()
1223 * Write character to the TNC buffer. Maintain the pointer
1224 * and length to the buffer. The pointer tncBufferPnt and tncLength
1225 * must be set before calling this function for the first time.
1227 * @param character to save to telemetry buffer
1229 void tncTxByte (uint8_t character)
1231 *tncBufferPnt++ = character;
1236 tncPrintf(char *fmt, ...)
1242 c = vsprintf(tncBufferPnt, fmt, ap);
1249 * Generate the GPS NMEA standard UTC time stamp. Data is written through the tncTxByte
1250 * callback function.
1254 // UTC of position fix.
1255 tncPrintf ("%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds);
1259 * Generate the GPS NMEA standard latitude/longitude fix. Data is written through the tncTxByte
1260 * callback function.
1265 uint32_t coord, coordMin;
1268 coord = gpsPosition.latitude;
1270 if (gpsPosition.latitude < 0)
1272 coord = gpsPosition.latitude * -1;
1275 coord = gpsPosition.latitude;
1279 coordMin = (coord % 3600000) / 6;
1280 tncPrintf ("%02ld%02ld.%04ld,%c,", (uint32_t) (coord / 3600000), (uint32_t) (coordMin / 10000), (uint32_t) (coordMin % 10000), dirChar);
1284 if (gpsPosition.longitude < 0)
1286 coord = gpsPosition.longitude * - 1;
1289 coord = gpsPosition.longitude;
1293 coordMin = (coord % 3600000) / 6;
1294 tncPrintf ("%03ld%02ld.%04ld,%c,", (uint32_t) (coord / 3600000), (uint32_t) (coordMin / 10000), (uint32_t) (coordMin % 10000), dirChar);
1299 * Generate the GPS NMEA-0183 $GPGGA packet. Data is written through the tncTxByte
1300 * callback function.
1302 void tncGPGGAPacket()
1304 // Generate the GPGGA message.
1305 tncPrintf ("$GPGGA,");
1307 // Standard NMEA time.
1310 // Standard NMEA-0183 latitude/longitude.
1313 // GPS status where 0: not available, 1: available
1314 if (gpsGetFixType() != GPS_NO_FIX)
1319 // Number of visible birds.
1320 tncPrintf ("%02d,", gpsPosition.trackedSats);
1323 tncPrintf ("%ld.%01ld,", gpsPosition.dop / 10, gpsPosition.dop % 10);
1325 // Altitude in meters.
1326 tncPrintf ("%ld.%02ld,M,,M,,", (int32_t) (gpsPosition.altitudeCM / 100l), (int32_t) (gpsPosition.altitudeCM % 100));
1328 // Checksum, we add 1 to skip over the $ character.
1329 tncPrintf ("*%02X", gpsNMEAChecksum(tncBuffer + 1, tncLength - 1));
1333 * Generate the GPS NMEA-0183 $GPRMC packet. Data is written through the tncTxByte
1334 * callback function.
1336 void tncGPRMCPacket()
1340 // Generate the GPRMC message.
1341 tncPrintf ("$GPRMC,");
1343 // Standard NMEA time.
1347 if (gpsGetFixType() != GPS_NO_FIX)
1352 // Standard NMEA-0183 latitude/longitude.
1355 // Speed knots and heading.
1356 temp = (int32_t) gpsPosition.hSpeed * 75000 / 385826;
1357 tncPrintf ("%ld.%ld,%ld.%ld,", (int16_t) (temp / 10), (int16_t) (temp % 10), gpsPosition.heading / 10, gpsPosition.heading % 10);
1360 tncPrintf ("%02d%02d%02ld,,", gpsPosition.day, gpsPosition.month, gpsPosition.year % 100);
1362 // Checksum, skip over the $ character.
1363 tncPrintf ("*%02X", gpsNMEAChecksum(tncBuffer + 1, tncLength - 1));
1367 * Generate the plain text status packet. Data is written through the tncTxByte
1368 * callback function.
1370 void tncStatusPacket(int16_t temperature)
1374 // Plain text telemetry.
1375 tncPrintf (">ANSR ");
1377 // Display the flight time.
1378 tncPrintf ("%02U:%02U:%02U ", timeHours, timeMinutes, timeSeconds);
1380 // Altitude in feet.
1381 tncPrintf ("%ld' ", gpsPosition.altitudeFeet);
1383 // Peak altitude in feet.
1384 tncPrintf ("%ld'pk ", gpsGetPeakAltitude());
1387 tncPrintf ("%lu.%lu", gpsPosition.dop / 10, gpsPosition.dop % 10);
1389 // The text 'pdop' for a 3D fix, 'hdop' for a 2D fix, and 'dop' for no fix.
1390 switch (gpsGetFixType())
1397 tncPrintf ("hdop ");
1402 tncPrintf ("pdop ");
1406 // Number of satellites in the solution.
1407 tncPrintf ("%utrk ", gpsPosition.trackedSats);
1409 // Display main bus voltage.
1410 // voltage = adcGetMainBusVolt();
1411 // tncPrintf ("%lu.%02luvdc ", voltage / 100, voltage % 100);
1413 // Display internal temperature.
1414 // tncPrintf ("%ld.%01ldF ", temperature / 10, abs(temperature % 10));
1416 // Print web address link.
1417 tncPrintf ("www.altusmetrum.org");
1421 * Prepare an AX.25 data packet. Each time this method is called, it automatically
1422 * rotates through 1 of 3 messages.
1424 * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK
1426 void tncTxPacket(TNC_DATA_MODE dataMode)
1428 int16_t temperature;
1431 // Only transmit if there is not another message in progress.
1432 if (tncMode != TNC_TX_READY)
1435 // Configure the DDS for the desired operational.
1436 tncSetMode (dataMode);
1438 // Set a pointer to our TNC output buffer.
1439 tncBufferPnt = tncBuffer;
1441 // Set the message length counter.
1444 // Determine the contents of the packet.
1445 switch (tncPacketType)
1447 case TNC_BOOT_MESSAGE:
1448 tncPrintf (">MegaMetrum v1.0 Beacon");
1450 // Select the next packet we will generate.
1451 tncPacketType = TNC_STATUS;
1455 tncStatusPacket(temperature);
1457 // Select the next packet we will generate.
1458 tncPacketType = TNC_GGA;
1464 // Select the next packet we will generate.
1465 tncPacketType = TNC_RMC;
1471 // Select the next packet we will generate.
1472 tncPacketType = TNC_STATUS;
1476 // Add the end of message character.
1479 // Calculate the CRC for the header and message.
1480 crc = sysCRC16(TNC_AX25_HEADER, sizeof(TNC_AX25_HEADER), 0xffff);
1481 crc = sysCRC16(tncBuffer, tncLength, crc ^ 0xffff);
1483 // Save the CRC in the message.
1484 *tncBufferPnt++ = crc & 0xff;
1485 *tncBufferPnt = (crc >> 8) & 0xff;
1487 // Update the length to include the CRC bytes.
1490 // Prepare the variables that are used in the real-time clock interrupt.
1495 tncMode = TNC_TX_SYNC;
1497 // Turn on the PA chain.
1498 // output_high (IO_PTT);
1500 // Wait for the PA chain to power up.
1504 // output_high (IO_OSK);
1506 // Log the battery and reference voltage just after we key the transmitter.
1508 while (tncMode != TNC_TX_READY)
1518 uint8_t streamIndex;
1521 uint8_t bitStream[] = { 0x10, 0x20, 0x30 };
1528 value = bitStream[0];
1533 counter += 0x10622d;
1535 // CCP_1 = (uint16_t) ((counter >> 16) & 0xffff);
1537 if ((value & 0x80) == 0x80)
1538 setup_ccp1 (CCP_COMPARE_SET_ON_MATCH);
1540 setup_ccp1 (CCP_COMPARE_CLR_ON_MATCH);
1542 if (++bitIndex == 8)
1546 if (++streamIndex == sizeof(bitStream))
1551 value = bitStream[streamIndex];
1557 // This is where we go after reset.
1558 int main(int argc, char **argv)
1560 uint8_t i, utcSeconds, lockLostCounter;
1564 // Configure the basic systems.
1567 // Wait for the power converter chains to stabilize.
1570 // Setup the subsystems.
1582 // Transmit software version packet on start up.
1583 tncTxPacket(TNC_MODE_1200_AFSK);
1586 // Counters to send packets if the GPS time stamp is not available.
1587 lockLostCounter = 5;
1590 // This is the main loop that process GPS data and waits for the once per second timer tick.
1593 // Read the GPS engine serial port FIFO and process the GPS data.
1598 // Start the flight timer when we get a valid 3D fix.
1599 if (gpsGetFixType() == GPS_3D_FIX)
1602 // Generate our packets based on the GPS time.
1603 if (tncIsTimeSlot(gpsPosition.seconds))
1604 tncTxPacket(TNC_MODE_1200_AFSK);
1606 // Sync the internal clock to GPS UTC time.
1607 utcSeconds = gpsPosition.seconds;
1609 // This counter is reset every time we receive the GPS message.
1610 lockLostCounter = 0;
1612 // Log the data to flash.
1614 } // END if gpsIsReady
1616 // Processing that occurs once a second.
1619 // We maintain the UTC time in seconds if we shut off the GPS engine or it fails.
1620 if (++utcSeconds == 60)
1623 // If we loose information for more than 5 seconds,
1624 // we will determine when to send a packet based on internal time.
1625 if (lockLostCounter == 5)
1627 if (tncIsTimeSlot(utcSeconds))
1628 tncTxPacket(TNC_MODE_1200_AFSK);
1632 // Update the ADC filters.
1635 if (timeHours == 5 && timeMinutes == 0 && timeSeconds == 0)
1638 } // END if timeIsUpdate