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
149 typedef int32_t int32;
153 // Public methods, constants, and data structures for each class.
156 void ddsSetAmplitude (uint8_t amplitude);
157 void ddsSetOutputScale (uint16_t amplitude);
158 void ddsSetFSKFreq (uint32_t ftw0, uint32_t ftw1);
159 void ddsSetFreq (uint32_t freq);
160 void ddsSetFTW (uint32_t ftw);
162 uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc);
164 uint8_t timeGetTicks();
166 void timeSetDutyCycle (uint8_t dutyCycle);
170 void tnc1200TimerTick();
171 void tncTxByte (uint8_t value);
172 void tncTxPacket(void);
177 * @defgroup DDS AD9954 DDS (Direct Digital Synthesizer)
179 * Functions to control the Analog Devices AD9954 DDS.
184 /// Number of digits in DDS frequency to FTW conversion.
185 #define DDS_FREQ_TO_FTW_DIGITS 9
187 /// Array of multiplication factors used to convert frequency to the FTW.
188 const uint32_t DDS_MULT[DDS_FREQ_TO_FTW_DIGITS] = { 11, 7, 7, 3, 4, 8, 4, 9, 1 };
190 /// Array of divisors used to convert frequency to the FTW.
191 const uint32_t DDS_DIVISOR[DDS_FREQ_TO_FTW_DIGITS - 1] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
193 /// Lookup table to convert dB amplitude scale in 0.5 steps to a linear DDS scale factor.
194 const uint16_t DDS_AMP_TO_SCALE[] =
196 16383, 15467, 14601, 13785, 13013, 12286, 11598, 10949, 10337, 9759, 9213, 8697,
197 8211, 7752, 7318, 6909, 6522, 6157, 5813, 5488, 5181, 4891, 4617, 4359, 4115, 3885, 3668, 3463,
198 3269, 3086, 2913, 2750, 2597, 2451, 2314, 2185, 2062, 1947, 1838, 1735, 1638
202 /// Frequency Word List - 4.0KHz FM frequency deviation at 81.15MHz (445.950MHz)
203 const uint32_t freqTable[256] =
205 955418300, 955419456, 955420611, 955421765, 955422916, 955424065, 955425210, 955426351,
206 955427488, 955428618, 955429743, 955430861, 955431971, 955433073, 955434166, 955435249,
207 955436322, 955437385, 955438435, 955439474, 955440500, 955441513, 955442511, 955443495,
208 955444464, 955445417, 955446354, 955447274, 955448176, 955449061, 955449926, 955450773,
209 955451601, 955452408, 955453194, 955453960, 955454704, 955455426, 955456126, 955456803,
210 955457457, 955458088, 955458694, 955459276, 955459833, 955460366, 955460873, 955461354,
211 955461809, 955462238, 955462641, 955463017, 955463366, 955463688, 955463983, 955464250,
212 955464489, 955464701, 955464884, 955465040, 955465167, 955465266, 955465337, 955465380,
213 955465394, 955465380, 955465337, 955465266, 955465167, 955465040, 955464884, 955464701,
214 955464489, 955464250, 955463983, 955463688, 955463366, 955463017, 955462641, 955462238,
215 955461809, 955461354, 955460873, 955460366, 955459833, 955459276, 955458694, 955458088,
216 955457457, 955456803, 955456126, 955455426, 955454704, 955453960, 955453194, 955452408,
217 955451601, 955450773, 955449926, 955449061, 955448176, 955447274, 955446354, 955445417,
218 955444464, 955443495, 955442511, 955441513, 955440500, 955439474, 955438435, 955437385,
219 955436322, 955435249, 955434166, 955433073, 955431971, 955430861, 955429743, 955428618,
220 955427488, 955426351, 955425210, 955424065, 955422916, 955421765, 955420611, 955419456,
221 955418300, 955417144, 955415989, 955414836, 955413684, 955412535, 955411390, 955410249,
222 955409113, 955407982, 955406857, 955405740, 955404629, 955403528, 955402435, 955401351,
223 955400278, 955399216, 955398165, 955397126, 955396100, 955395088, 955394089, 955393105,
224 955392136, 955391183, 955390246, 955389326, 955388424, 955387540, 955386674, 955385827,
225 955385000, 955384192, 955383406, 955382640, 955381896, 955381174, 955380474, 955379797,
226 955379143, 955378513, 955377906, 955377324, 955376767, 955376235, 955375728, 955375246,
227 955374791, 955374362, 955373959, 955373583, 955373234, 955372912, 955372618, 955372350,
228 955372111, 955371900, 955371716, 955371560, 955371433, 955371334, 955371263, 955371220,
229 955371206, 955371220, 955371263, 955371334, 955371433, 955371560, 955371716, 955371900,
230 955372111, 955372350, 955372618, 955372912, 955373234, 955373583, 955373959, 955374362,
231 955374791, 955375246, 955375728, 955376235, 955376767, 955377324, 955377906, 955378513,
232 955379143, 955379797, 955380474, 955381174, 955381896, 955382640, 955383406, 955384192,
233 955385000, 955385827, 955386674, 955387540, 955388424, 955389326, 955390246, 955391183,
234 955392136, 955393105, 955394089, 955395088, 955396100, 955397126, 955398165, 955399216,
235 955400278, 955401351, 955402435, 955403528, 955404629, 955405740, 955406857, 955407982,
236 955409113, 955410249, 955411390, 955412535, 955413684, 955414836, 955415989, 955417144
240 * Set DDS frequency tuning word. The output frequency is equal to RefClock * (ftw / 2 ^ 32).
242 * @param ftw Frequency Tuning Word
244 void ddsSetFTW (uint32_t ftw)
246 int x = ftw - freqTable[0];
247 putchar (x > 0 ? 0xc0 : 0x40);
253 * @defgroup sys System Library Functions
255 * Generic system functions similiar to the run-time C library.
261 * Calculate the CRC-16 CCITT of buffer that is length bytes long.
262 * The crc parameter allow the calculation on the CRC on multiple buffers.
264 * @param buffer Pointer to data buffer.
265 * @param length number of bytes in data buffer
266 * @param crc starting value
268 * @return CRC-16 of buffer[0 .. length]
270 uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc)
272 uint8_t i, bit, value;
274 for (i = 0; i < length; ++i)
278 for (bit = 0; bit < 8; ++bit)
280 crc ^= (value & 0x01);
281 crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 );
292 * @defgroup rtc Real Time Interrupt tick
294 * Manage the built-in real time interrupt. The interrupt clock PRI is 104uS (9600 bps).
299 /// A counter that ticks every 100mS.
302 /// Counts the number of 104uS interrupts for a 100mS time period.
303 uint16_t timeInterruptCount;
305 /// Counts the number of 100mS time periods in 1 second.
308 /// System time in seconds.
311 /// System time in minutes.
314 /// System time in hours.
317 /// Desired LED duty cycle 0 to 9 where 0 = 0% and 9 = 90%.
318 uint8_t timeDutyCycle;
320 /// Current value of the timer 1 compare register used to generate 104uS interrupt rate (9600bps).
321 uint16_t timeCompare;
323 /// 16-bit NCO where the upper 8-bits are used to index into the frequency generation table.
326 /// Audio tone NCO update step (phase).
327 uint16_t timeNCOFreq;
329 /// Counter used to deciminate down from the 104uS to 833uS interrupt rate. (9600 to 1200 baud)
330 uint8_t timeLowRateCount;
332 /// Flag set true once per second.
333 bool_t timeUpdateFlag;
335 /// Flag that indicate the flight time should run.
338 /// The change in the CCP_1 register for each 104uS (9600bps) interrupt period.
339 #define TIME_RATE 125
342 * Running 8-bit counter that ticks every 100mS.
344 * @return 100mS time tick
346 uint8_t timeGetTicks()
352 * Initialize the real-time clock.
357 timeInterruptCount = 0;
362 timeCompare = TIME_RATE;
363 timeUpdateFlag = false;
365 timeLowRateCount = 0;
366 timeNCOFreq = 0x2000;
371 * Function return true once a second based on real-time clock.
373 * @return true on one second tick; otherwise false
375 bool_t timeIsUpdate()
379 timeUpdateFlag = false;
387 * Set a flag to indicate the flight time should run. This flag is typically set when the payload
390 void timeSetRunFlag()
396 * Timer interrupt handler called every 104uS (9600 times/second).
400 // Setup the next interrupt for the operational mode.
401 timeCompare += TIME_RATE;
403 ddsSetFTW (freqTable[timeNCO >> 8]);
405 timeNCO += timeNCOFreq;
407 if (++timeLowRateCount == 8)
409 timeLowRateCount = 0;
417 * @defgroup tnc TNC (Terminal Node Controller)
419 * Functions that provide a subset of the TNC functions.
424 /// The number of start flag bytes to send before the packet message. (360bits * 1200bps = 300mS)
425 #define TNC_TX_DELAY 45
427 /// The size of the TNC output buffer.
428 #define TNC_BUFFER_SIZE 80
430 /// States that define the current mode of the 1200 bps (A-FSK) state machine.
433 /// Stand by state ready to accept new message.
436 /// 0x7E bit stream pattern used to define start of APRS message.
439 /// Transmit the AX.25 header that contains the source/destination call signs, APRS path, and flags.
442 /// Transmit the message data.
445 /// Transmit the end flag sequence.
447 } TNC_TX_1200BPS_STATE;
449 /// Enumeration of the messages we can transmit.
452 /// Startup message that contains software version information.
455 /// Plain text status message.
458 /// Message that contains GPS NMEA-0183 $GPGGA message.
461 /// Message that contains GPS NMEA-0183 $GPRMC message.
465 /// AX.25 compliant packet header that contains destination, station call sign, and path.
466 /// 0x76 for SSID-11, 0x78 for SSID-12
467 uint8_t TNC_AX25_HEADER[30] = {
468 'A' << 1, 'P' << 1, 'R' << 1, 'S' << 1, ' ' << 1, ' ' << 1, 0x60, \
469 'K' << 1, 'D' << 1, '7' << 1, 'S' << 1, 'Q' << 1, 'G' << 1, 0x76, \
470 'G' << 1, 'A' << 1, 'T' << 1, 'E' << 1, ' ' << 1, ' ' << 1, 0x60, \
471 'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '3' << 1, ' ' << 1, 0x67, \
475 /// The next bit to transmit.
478 /// Current mode of the 1200 bps state machine.
479 TNC_TX_1200BPS_STATE tncMode;
481 /// Counter for each bit (0 - 7) that we are going to transmit.
484 /// A shift register that holds the data byte as we bit shift it for transmit.
487 /// Index into the APRS header and data array for each byte as we transmit it.
490 /// The number of bytes in the message portion of the AX.25 message.
493 /// A copy of the last 5 bits we've transmitted to determine if we need to bit stuff on the next bit.
496 /// Pointer to TNC buffer as we save each byte during message preparation.
497 uint8_t *tncBufferPnt;
499 /// The type of message to tranmit in the next packet.
500 TNC_MESSAGE_TYPE tncPacketType;
502 /// Buffer to hold the message portion of the AX.25 packet as we prepare it.
503 uint8_t tncBuffer[TNC_BUFFER_SIZE];
506 * Initialize the TNC internal variables.
511 tncMode = TNC_TX_READY;
512 tncPacketType = TNC_BOOT_MESSAGE;
516 * Method that is called every 833uS to transmit the 1200bps A-FSK data stream.
517 * The provides the pre and postamble as well as the bit stuffed data stream.
519 void tnc1200TimerTick()
521 // Set the A-FSK frequency.
522 if (tncTxBit == 0x00)
523 timeNCOFreq = 0x2000;
525 timeNCOFreq = 0x3aab;
530 // Generate a test signal alteranting between high and low tones.
531 tncTxBit = (tncTxBit == 0 ? 1 : 0);
535 // The variable tncShift contains the lastest data byte.
536 // NRZI enocde the data stream.
537 if ((tncShift & 0x01) == 0x00) {
544 // When the flag is done, determine if we need to send more or data.
545 if (++tncBitCount == 8)
550 // Once we transmit x mS of flags, send the data.
551 // txDelay bytes * 8 bits/byte * 833uS/bit = x mS
552 if (++tncIndex == TNC_TX_DELAY)
555 tncShift = TNC_AX25_HEADER[0];
557 tncMode = TNC_TX_HEADER;
560 tncShift = tncShift >> 1;
564 // Determine if we have sent 5 ones in a row, if we have send a zero.
565 if (tncBitStuff == 0x1f)
576 // The variable tncShift contains the lastest data byte.
577 // NRZI enocde the data stream.
578 if ((tncShift & 0x01) == 0x00) {
585 // Save the data stream so we can determine if bit stuffing is
586 // required on the next bit time.
587 tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;
589 // If all the bits were shifted, get the next byte.
590 if (++tncBitCount == 8)
594 // After the header is sent, then send the data.
595 if (++tncIndex == sizeof(TNC_AX25_HEADER))
598 tncShift = tncBuffer[0];
599 tncMode = TNC_TX_DATA;
601 tncShift = TNC_AX25_HEADER[tncIndex];
604 tncShift = tncShift >> 1;
609 // Determine if we have sent 5 ones in a row, if we have send a zero.
610 if (tncBitStuff == 0x1f)
621 // The variable tncShift contains the lastest data byte.
622 // NRZI enocde the data stream.
623 if ((tncShift & 0x01) == 0x00) {
630 // Save the data stream so we can determine if bit stuffing is
631 // required on the next bit time.
632 tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;
634 // If all the bits were shifted, get the next byte.
635 if (++tncBitCount == 8)
639 // If everything was sent, transmit closing flags.
640 if (++tncIndex == tncLength)
644 tncMode = TNC_TX_END;
646 tncShift = tncBuffer[tncIndex];
649 tncShift = tncShift >> 1;
654 // The variable tncShift contains the lastest data byte.
655 // NRZI enocde the data stream.
656 if ((tncShift & 0x01) == 0x00) {
663 // If all the bits were shifted, get the next one.
664 if (++tncBitCount == 8)
669 // Transmit two closing flags.
672 tncMode = TNC_TX_READY;
677 tncShift = tncShift >> 1;
684 * Generate the plain text position packet. Data is written through the tncTxByte
687 void tncPositionPacket(void)
689 int32_t latitude = 45.4694766 * 10000000;
690 int32_t longitude = -122.7376250 * 10000000;
691 uint32_t altitude = 10000;
700 char lat_sign = 'N', lon_sign = 'E';
704 latitude = -latitude;
709 longitude = -longitude;
712 lat_deg = latitude / 10000000;
713 latitude -= lat_deg * 10000000;
715 lat_min = latitude / 10000000;
716 latitude -= lat_min * 10000000;
717 lat_frac = (latitude + 50000) / 100000;
719 lon_deg = longitude / 10000000;
720 longitude -= lon_deg * 10000000;
722 lon_min = longitude / 10000000;
723 longitude -= lon_min * 10000000;
724 lon_frac = (longitude + 50000) / 100000;
726 c = sprintf ((char *) tncBufferPnt, "=%02u%02u.%02u%c\\%03u%02u.%02u%cO /A=%06u\015",
727 lat_deg, lat_min, lat_frac, lat_sign,
728 lon_deg, lon_min, lon_frac, lon_sign,
729 altitude * 100 / 3048);
735 * Prepare an AX.25 data packet. Each time this method is called, it automatically
736 * rotates through 1 of 3 messages.
738 * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK
740 void tncTxPacket(void)
744 // Set a pointer to our TNC output buffer.
745 tncBufferPnt = tncBuffer;
747 // Set the message length counter.
752 // Calculate the CRC for the header and message.
753 crc = sysCRC16(TNC_AX25_HEADER, sizeof(TNC_AX25_HEADER), 0xffff);
754 crc = sysCRC16(tncBuffer, tncLength, crc ^ 0xffff);
756 // Save the CRC in the message.
757 *tncBufferPnt++ = crc & 0xff;
758 *tncBufferPnt = (crc >> 8) & 0xff;
760 // Update the length to include the CRC bytes.
763 // Prepare the variables that are used in the real-time clock interrupt.
768 tncMode = TNC_TX_SYNC;
770 // Turn on the PA chain.
771 // output_high (IO_PTT);
773 // Wait for the PA chain to power up.
777 // output_high (IO_OSK);
779 // Log the battery and reference voltage just after we key the transmitter.
781 while (tncMode != TNC_TX_READY)