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);
171 void tncHighRate(bool_t state);
172 void tnc1200TimerTick();
173 void tncTxByte (uint8_t value);
174 void tncTxPacket(void);
179 * @defgroup DDS AD9954 DDS (Direct Digital Synthesizer)
181 * Functions to control the Analog Devices AD9954 DDS.
186 /// Number of digits in DDS frequency to FTW conversion.
187 #define DDS_FREQ_TO_FTW_DIGITS 9
189 /// Array of multiplication factors used to convert frequency to the FTW.
190 const uint32_t DDS_MULT[DDS_FREQ_TO_FTW_DIGITS] = { 11, 7, 7, 3, 4, 8, 4, 9, 1 };
192 /// Array of divisors used to convert frequency to the FTW.
193 const uint32_t DDS_DIVISOR[DDS_FREQ_TO_FTW_DIGITS - 1] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
195 /// Lookup table to convert dB amplitude scale in 0.5 steps to a linear DDS scale factor.
196 const uint16_t DDS_AMP_TO_SCALE[] =
198 16383, 15467, 14601, 13785, 13013, 12286, 11598, 10949, 10337, 9759, 9213, 8697,
199 8211, 7752, 7318, 6909, 6522, 6157, 5813, 5488, 5181, 4891, 4617, 4359, 4115, 3885, 3668, 3463,
200 3269, 3086, 2913, 2750, 2597, 2451, 2314, 2185, 2062, 1947, 1838, 1735, 1638
204 /// Frequency Word List - 4.0KHz FM frequency deviation at 81.15MHz (445.950MHz)
205 const uint32_t freqTable[256] =
207 955418300, 955419456, 955420611, 955421765, 955422916, 955424065, 955425210, 955426351,
208 955427488, 955428618, 955429743, 955430861, 955431971, 955433073, 955434166, 955435249,
209 955436322, 955437385, 955438435, 955439474, 955440500, 955441513, 955442511, 955443495,
210 955444464, 955445417, 955446354, 955447274, 955448176, 955449061, 955449926, 955450773,
211 955451601, 955452408, 955453194, 955453960, 955454704, 955455426, 955456126, 955456803,
212 955457457, 955458088, 955458694, 955459276, 955459833, 955460366, 955460873, 955461354,
213 955461809, 955462238, 955462641, 955463017, 955463366, 955463688, 955463983, 955464250,
214 955464489, 955464701, 955464884, 955465040, 955465167, 955465266, 955465337, 955465380,
215 955465394, 955465380, 955465337, 955465266, 955465167, 955465040, 955464884, 955464701,
216 955464489, 955464250, 955463983, 955463688, 955463366, 955463017, 955462641, 955462238,
217 955461809, 955461354, 955460873, 955460366, 955459833, 955459276, 955458694, 955458088,
218 955457457, 955456803, 955456126, 955455426, 955454704, 955453960, 955453194, 955452408,
219 955451601, 955450773, 955449926, 955449061, 955448176, 955447274, 955446354, 955445417,
220 955444464, 955443495, 955442511, 955441513, 955440500, 955439474, 955438435, 955437385,
221 955436322, 955435249, 955434166, 955433073, 955431971, 955430861, 955429743, 955428618,
222 955427488, 955426351, 955425210, 955424065, 955422916, 955421765, 955420611, 955419456,
223 955418300, 955417144, 955415989, 955414836, 955413684, 955412535, 955411390, 955410249,
224 955409113, 955407982, 955406857, 955405740, 955404629, 955403528, 955402435, 955401351,
225 955400278, 955399216, 955398165, 955397126, 955396100, 955395088, 955394089, 955393105,
226 955392136, 955391183, 955390246, 955389326, 955388424, 955387540, 955386674, 955385827,
227 955385000, 955384192, 955383406, 955382640, 955381896, 955381174, 955380474, 955379797,
228 955379143, 955378513, 955377906, 955377324, 955376767, 955376235, 955375728, 955375246,
229 955374791, 955374362, 955373959, 955373583, 955373234, 955372912, 955372618, 955372350,
230 955372111, 955371900, 955371716, 955371560, 955371433, 955371334, 955371263, 955371220,
231 955371206, 955371220, 955371263, 955371334, 955371433, 955371560, 955371716, 955371900,
232 955372111, 955372350, 955372618, 955372912, 955373234, 955373583, 955373959, 955374362,
233 955374791, 955375246, 955375728, 955376235, 955376767, 955377324, 955377906, 955378513,
234 955379143, 955379797, 955380474, 955381174, 955381896, 955382640, 955383406, 955384192,
235 955385000, 955385827, 955386674, 955387540, 955388424, 955389326, 955390246, 955391183,
236 955392136, 955393105, 955394089, 955395088, 955396100, 955397126, 955398165, 955399216,
237 955400278, 955401351, 955402435, 955403528, 955404629, 955405740, 955406857, 955407982,
238 955409113, 955410249, 955411390, 955412535, 955413684, 955414836, 955415989, 955417144
242 * Set DDS frequency tuning word. The output frequency is equal to RefClock * (ftw / 2 ^ 32).
244 * @param ftw Frequency Tuning Word
246 void ddsSetFTW (uint32_t ftw)
248 int x = ftw - freqTable[0];
249 putchar (x > 0 ? 0xc0 : 0x40);
255 * @defgroup sys System Library Functions
257 * Generic system functions similiar to the run-time C library.
263 * Calculate the CRC-16 CCITT of buffer that is length bytes long.
264 * The crc parameter allow the calculation on the CRC on multiple buffers.
266 * @param buffer Pointer to data buffer.
267 * @param length number of bytes in data buffer
268 * @param crc starting value
270 * @return CRC-16 of buffer[0 .. length]
272 uint16_t sysCRC16(uint8_t *buffer, uint8_t length, uint16_t crc)
274 uint8_t i, bit, value;
276 for (i = 0; i < length; ++i)
280 for (bit = 0; bit < 8; ++bit)
282 crc ^= (value & 0x01);
283 crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 );
294 * @defgroup rtc Real Time Interrupt tick
296 * Manage the built-in real time interrupt. The interrupt clock PRI is 104uS (9600 bps).
301 /// A counter that ticks every 100mS.
304 /// Counts the number of 104uS interrupts for a 100mS time period.
305 uint16_t timeInterruptCount;
307 /// Counts the number of 100mS time periods in 1 second.
310 /// System time in seconds.
313 /// System time in minutes.
316 /// System time in hours.
319 /// Desired LED duty cycle 0 to 9 where 0 = 0% and 9 = 90%.
320 uint8_t timeDutyCycle;
322 /// Current value of the timer 1 compare register used to generate 104uS interrupt rate (9600bps).
323 uint16_t timeCompare;
325 /// 16-bit NCO where the upper 8-bits are used to index into the frequency generation table.
328 /// Audio tone NCO update step (phase).
329 uint16_t timeNCOFreq;
331 /// Counter used to deciminate down from the 104uS to 833uS interrupt rate. (9600 to 1200 baud)
332 uint8_t timeLowRateCount;
334 /// Flag set true once per second.
335 bool_t timeUpdateFlag;
337 /// Flag that indicate the flight time should run.
340 /// The change in the CCP_1 register for each 104uS (9600bps) interrupt period.
341 #define TIME_RATE 125
344 * Running 8-bit counter that ticks every 100mS.
346 * @return 100mS time tick
348 uint8_t timeGetTicks()
354 * Initialize the real-time clock.
359 timeInterruptCount = 0;
364 timeCompare = TIME_RATE;
365 timeUpdateFlag = false;
367 timeLowRateCount = 0;
368 timeNCOFreq = 0x2000;
373 * Function return true once a second based on real-time clock.
375 * @return true on one second tick; otherwise false
377 bool_t timeIsUpdate()
381 timeUpdateFlag = false;
389 * Set a flag to indicate the flight time should run. This flag is typically set when the payload
392 void timeSetRunFlag()
398 * Timer interrupt handler called every 104uS (9600 times/second).
402 // Setup the next interrupt for the operational mode.
403 timeCompare += TIME_RATE;
405 ddsSetFTW (freqTable[timeNCO >> 8]);
407 timeNCO += timeNCOFreq;
409 if (++timeLowRateCount == 8)
411 timeLowRateCount = 0;
419 * @defgroup tnc TNC (Terminal Node Controller)
421 * Functions that provide a subset of the TNC functions.
426 /// The number of start flag bytes to send before the packet message. (360bits * 1200bps = 300mS)
427 #define TNC_TX_DELAY 45
429 /// The size of the TNC output buffer.
430 #define TNC_BUFFER_SIZE 80
432 /// States that define the current mode of the 1200 bps (A-FSK) state machine.
435 /// Stand by state ready to accept new message.
438 /// 0x7E bit stream pattern used to define start of APRS message.
441 /// Transmit the AX.25 header that contains the source/destination call signs, APRS path, and flags.
444 /// Transmit the message data.
447 /// Transmit the end flag sequence.
449 } TNC_TX_1200BPS_STATE;
451 /// Enumeration of the messages we can transmit.
454 /// Startup message that contains software version information.
457 /// Plain text status message.
460 /// Message that contains GPS NMEA-0183 $GPGGA message.
463 /// Message that contains GPS NMEA-0183 $GPRMC message.
467 /// AX.25 compliant packet header that contains destination, station call sign, and path.
468 /// 0x76 for SSID-11, 0x78 for SSID-12
469 uint8_t TNC_AX25_HEADER[30] = {
470 'A' << 1, 'P' << 1, 'R' << 1, 'S' << 1, ' ' << 1, ' ' << 1, 0x60, \
471 'K' << 1, 'D' << 1, '7' << 1, 'S' << 1, 'Q' << 1, 'G' << 1, 0x76, \
472 'G' << 1, 'A' << 1, 'T' << 1, 'E' << 1, ' ' << 1, ' ' << 1, 0x60, \
473 'W' << 1, 'I' << 1, 'D' << 1, 'E' << 1, '3' << 1, ' ' << 1, 0x67, \
477 /// The next bit to transmit.
480 /// Current mode of the 1200 bps state machine.
481 TNC_TX_1200BPS_STATE tncMode;
483 /// Counter for each bit (0 - 7) that we are going to transmit.
486 /// A shift register that holds the data byte as we bit shift it for transmit.
489 /// Index into the APRS header and data array for each byte as we transmit it.
492 /// The number of bytes in the message portion of the AX.25 message.
495 /// A copy of the last 5 bits we've transmitted to determine if we need to bit stuff on the next bit.
498 /// Pointer to TNC buffer as we save each byte during message preparation.
499 uint8_t *tncBufferPnt;
501 /// The type of message to tranmit in the next packet.
502 TNC_MESSAGE_TYPE tncPacketType;
504 /// Buffer to hold the message portion of the AX.25 packet as we prepare it.
505 uint8_t tncBuffer[TNC_BUFFER_SIZE];
507 /// Flag that indicates we want to transmit every 5 seconds.
508 bool_t tncHighRateFlag;
511 * Initialize the TNC internal variables.
516 tncMode = TNC_TX_READY;
517 tncPacketType = TNC_BOOT_MESSAGE;
518 tncHighRateFlag = false;
522 * Determine if the hardware if ready to transmit a 1200 baud packet.
524 * @return true if ready; otherwise false
528 if (tncMode == TNC_TX_READY)
534 void tncHighRate(bool_t state)
536 tncHighRateFlag = state;
540 * Determine if the seconds value timeSeconds is a valid time slot to transmit
541 * a message. Time seconds is in UTC.
543 * @param timeSeconds UTC time in seconds
545 * @return true if valid time slot; otherwise false
547 bool_t tncIsTimeSlot (uint8_t timeSeconds)
551 if ((timeSeconds % 5) == 0)
571 * Method that is called every 833uS to transmit the 1200bps A-FSK data stream.
572 * The provides the pre and postamble as well as the bit stuffed data stream.
574 void tnc1200TimerTick()
576 // Set the A-FSK frequency.
577 if (tncTxBit == 0x00)
578 timeNCOFreq = 0x2000;
580 timeNCOFreq = 0x3aab;
585 // Generate a test signal alteranting between high and low tones.
586 tncTxBit = (tncTxBit == 0 ? 1 : 0);
590 // The variable tncShift contains the lastest data byte.
591 // NRZI enocde the data stream.
592 if ((tncShift & 0x01) == 0x00) {
599 // When the flag is done, determine if we need to send more or data.
600 if (++tncBitCount == 8)
605 // Once we transmit x mS of flags, send the data.
606 // txDelay bytes * 8 bits/byte * 833uS/bit = x mS
607 if (++tncIndex == TNC_TX_DELAY)
610 tncShift = TNC_AX25_HEADER[0];
612 tncMode = TNC_TX_HEADER;
615 tncShift = tncShift >> 1;
619 // Determine if we have sent 5 ones in a row, if we have send a zero.
620 if (tncBitStuff == 0x1f)
631 // The variable tncShift contains the lastest data byte.
632 // NRZI enocde the data stream.
633 if ((tncShift & 0x01) == 0x00) {
640 // Save the data stream so we can determine if bit stuffing is
641 // required on the next bit time.
642 tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;
644 // If all the bits were shifted, get the next byte.
645 if (++tncBitCount == 8)
649 // After the header is sent, then send the data.
650 if (++tncIndex == sizeof(TNC_AX25_HEADER))
653 tncShift = tncBuffer[0];
654 tncMode = TNC_TX_DATA;
656 tncShift = TNC_AX25_HEADER[tncIndex];
659 tncShift = tncShift >> 1;
664 // Determine if we have sent 5 ones in a row, if we have send a zero.
665 if (tncBitStuff == 0x1f)
676 // The variable tncShift contains the lastest data byte.
677 // NRZI enocde the data stream.
678 if ((tncShift & 0x01) == 0x00) {
685 // Save the data stream so we can determine if bit stuffing is
686 // required on the next bit time.
687 tncBitStuff = ((tncBitStuff << 1) | (tncShift & 0x01)) & 0x1f;
689 // If all the bits were shifted, get the next byte.
690 if (++tncBitCount == 8)
694 // If everything was sent, transmit closing flags.
695 if (++tncIndex == tncLength)
699 tncMode = TNC_TX_END;
701 tncShift = tncBuffer[tncIndex];
704 tncShift = tncShift >> 1;
709 // The variable tncShift contains the lastest data byte.
710 // NRZI enocde the data stream.
711 if ((tncShift & 0x01) == 0x00) {
718 // If all the bits were shifted, get the next one.
719 if (++tncBitCount == 8)
724 // Transmit two closing flags.
727 tncMode = TNC_TX_READY;
732 tncShift = tncShift >> 1;
739 * Method that is called every 104uS to transmit the 9600bps FSK data stream.
741 void tnc9600TimerTick()
747 * Generate the plain text position packet. Data is written through the tncTxByte
750 void tncPositionPacket(void)
752 int32_t latitude = 45.4694766 * 10000000;
753 int32_t longitude = -122.7376250 * 10000000;
754 uint32_t altitude = 10000;
763 char lat_sign = 'N', lon_sign = 'E';
767 latitude = -latitude;
772 longitude = -longitude;
775 lat_deg = latitude / 10000000;
776 latitude -= lat_deg * 10000000;
778 lat_min = latitude / 10000000;
779 latitude -= lat_min * 10000000;
780 lat_frac = (latitude + 50000) / 100000;
782 lon_deg = longitude / 10000000;
783 longitude -= lon_deg * 10000000;
785 lon_min = longitude / 10000000;
786 longitude -= lon_min * 10000000;
787 lon_frac = (longitude + 50000) / 100000;
789 c = sprintf ((char *) tncBufferPnt, "=%02u%02u.%02u%c\\%03u%02u.%02u%cO /A=%06u\015",
790 lat_deg, lat_min, lat_frac, lat_sign,
791 lon_deg, lon_min, lon_frac, lon_sign,
792 altitude * 100 / 3048);
798 * Prepare an AX.25 data packet. Each time this method is called, it automatically
799 * rotates through 1 of 3 messages.
801 * @param dataMode enumerated type that specifies 1200bps A-FSK or 9600bps FSK
803 void tncTxPacket(void)
807 // Set a pointer to our TNC output buffer.
808 tncBufferPnt = tncBuffer;
810 // Set the message length counter.
815 // Calculate the CRC for the header and message.
816 crc = sysCRC16(TNC_AX25_HEADER, sizeof(TNC_AX25_HEADER), 0xffff);
817 crc = sysCRC16(tncBuffer, tncLength, crc ^ 0xffff);
819 // Save the CRC in the message.
820 *tncBufferPnt++ = crc & 0xff;
821 *tncBufferPnt = (crc >> 8) & 0xff;
823 // Update the length to include the CRC bytes.
826 // Prepare the variables that are used in the real-time clock interrupt.
831 tncMode = TNC_TX_SYNC;
833 // Turn on the PA chain.
834 // output_high (IO_PTT);
836 // Wait for the PA chain to power up.
840 // output_high (IO_OSK);
842 // Log the battery and reference voltage just after we key the transmitter.
844 while (tncMode != TNC_TX_READY)