// // Name: ac.c // // // Revision History // // 04 Feb 2002 V1.00 Initial release. // // 30 Mar 2002 V1.01 Incremental release for web site. // // 21 Nov 2002 V1.02 Flight tested. Incremental release for web site. // // 11 Dec 2002 V1.03 Incremental release for web site. // // 09 Feb 2003 V1.04 Payload E flight test, and // added camera point/shoot control. // // 09 Apr 2003 V1.05 Fixed NMEA-0183 checksum calculation and converted to upper case, // removed heading for magnetic corection field in $GPRMC message, // removed support for electronic attitude indicator, // inverted APRS/Com frequency selection, and // removed support for camera. // // 25 Mar 2006 V1.06 Updates to work as APRS beacon. // // // Copyright (c) 2002-2006 Michael Gray, KD7LMO // // 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 // // Disable debug information because it is hard to run a debug cable 20 miles to the aircraft. #nodebug #memmap xmem // PROGRAMMING NOTE: Since we don't have embedded C++ for this processor, // we try to use C in a manner that emulates classes and methods. Each method // and variable is prefaced by the name of the class. The classes // have a method that acts as a constructor and sometimes a destructor. // Even though variables are declared as global, they are only accessed // through the appropriate class method. The order of the classes and // variables is not important in the application because we only access // the class through a method. // To avoid confusion in the sizes of short, int, and long on 8, 16, and 32 bit processors, // we will be very explicit. The typedefs MUST be checked when cross compiling or changing // compilers. NOTE: There are others, i.e. uint8, uint16_t, int16_t, etc. that are defined // in a system library. typedef char bool; typedef char int8_t; typedef unsigned char uint8_t; typedef int16 int16_t; typedef uint16 uint16_t; typedef long int32_t; typedef unsigned long uint32_t; // Boolean flags. #define TRUE 1 #define FALSE 0 // Allocate buffers for the serial ports. #define AINBUFSIZE 15 #define AOUTBUFSIZE 15 #define CINBUFSIZE 255 #define COUTBUFSIZE 255 #define DINBUFSIZE 255 #define DOUTBUFSIZE 255 // Public methods and data structures for each class. typedef struct { uint16_t faultCount, badHeaderCRCCount, invalidHeaderCount, badDataCRCCount, bufferOverflow; uint16_t carrierDetectCount, rxPacketCount; uint16_t txPacketCount; uint8_t lastRxSQE; } COM_STATS; typedef struct { bool reverseFlag; uint8_t min, max, nominal; } CONFIG_SERVO_INFO; typedef struct { bool updateFlag; uint8_t month, day, hours, minutes, seconds; uint16_t year; int32_t latitude, longitude, altitude, altitudeFeet; uint16_t vSpeed, hSpeed, heading, dop, status; uint8_t trackedSats, visibleSats; int32_t lastAltitude; int16_t vSpeedAccum; } GPSPOSITION; uint8_t analogRead(); void analogWrite (uint8_t value); void configCalcCRC(); void configDefault(); uint8_t configInit(); uint8_t configGetComTxDelay(); int16_t configGetPitchOffset(); int16_t configGetRollOffset(); uint16_t configGetGyroOffset(); void configSetGyroOffset(uint16_t offset); CONFIG_SERVO_INFO *configGetServoInfo(); void configSetPitchOffset(int16_t offset); void configSetRollOffset(int16_t offset); void flightInit(); void flightProcessCommand (uint8_t *message); void flightProcessGPS(); void flightRun(); void flightSendLogPacket(); void gpsClearUpdateFlag(); GPSPOSITION *gpsGetData(); void gpsInit(); uint8_t gpsIsUpdated(); void gpsReadData(); void gpsSendMessage (uint8_t *message, uint8_t length); void sysChan1(); void sysChan2(); uint16_t sysCRC16 (uint8_t *buffer, uint16_t length); void sysDelay (uint32_t delayMS); void sysInit(); void sysInterruptEnable(); bool sysIsCarrierDetect(); void sysLEDOff(); void sysLEDOn(); uint8_t sysNMEAChecksum (uint8_t *buffer, uint16_t length); uint8_t sysParseHexDigit(uint8_t digit); void sysPTTOff(); void sysPTTOn(); root void sysRuntimeErrHandler(); void sysSelfTest(); int16_t sysStringToScaledInt(char *buffer, char **newBuffer); root interrupt void sysTimerISR(); void sysTimeTick(); void tempInit(); int16_t tempGetExtTemp(); uint8_t tempWriteAndGetAck(uint8_t data); uint8_t tempReadWithAck (bool ack); void tempMasterStart(); void tempMasterStop(); uint8_t tlmGetRate(); void tlmGPGGAPacket(); void tlmGPRMCPacket(); void tlmInit(); bool tlmIsAPRSActive(); bool tlmSetRate(uint8_t rate); void tlmStatusPacket(); void tlmUpdateTick(); void tncSendPacket(uint8_t *message); root interrupt void tncTimerISR(); /** * Class to handle configuration items. */ typedef struct { uint16_t crc; uint8_t tncTxDelay, comTxDelay; uint8_t callSign[7], destCallSign[7], relay1CallSign[7], relay2CallSign[7]; uint8_t callSignBalloonSSID, callSignAircraftSSID, relay1SSID, relay2SSID; uint16_t bootCount; } CONFIG_STRUCT; protected CONFIG_STRUCT config; /** * Set the default configuration parameters. */ void configDefault() { uint8_t i; // Clear everything in case we don't initialize something. memset (&config, 0, sizeof(CONFIG_STRUCT)); // Station ID, relay path, and destination call sign and SSID. strcpy (config.callSign, "KD7LMO"); config.callSignBalloonSSID = 12; config.callSignAircraftSSID = 7; strcpy (config.relay1CallSign, "GATE "); config.relay1SSID = 0; strcpy (config.relay2CallSign, "WIDE3 "); config.relay2SSID = 3; strcpy (config.destCallSign, "APRS "); // Number of TNC flag bytes sent before data stream starts. 1 byte = 6.6mS config.tncTxDelay = 45; // Number of mS before data is sent over the high-speed com link. config.comTxDelay = 60; // Count the number of system boots. config.bootCount = 0; } /** * Calculate and set the configuration parameter block CRC. */ void configCalcCRC() { config.crc = sysCRC16 ((uint8_t *) &config + 2, sizeof(CONFIG_STRUCT) - 2); } uint8_t configGetComTxDelay() { return config.comTxDelay; } /** * Get the station callsign. * * @param pointer to callsign string */ uint8_t *configGetCallSign() { return config.callSign; } /** * Get the target callsign for the AX.25 packet. * * @param pointer to callsign string */ uint8_t *configGetDestCallSign() { return config.destCallSign; } uint8_t *configGetRelay1CallSign() { return config.relay1CallSign; } uint8_t *configGetRelay2CallSign() { return config.relay2CallSign; } uint8_t configGetRelay1SSID() { return config.relay1SSID; } uint8_t configGetRelay2SSID() { return config.relay2SSID; } uint8_t configGetBalloonSSID() { return config.callSignBalloonSSID; } /** * Get the TNC transmit delay period. * * @return TNC transmit delay period in 8-bit time period */ uint8_t configGetTNCTxDelay() { return config.tncTxDelay; } /** * Initialize configuration subsystem. */ uint8_t configInit() { // If the configuration CRC is not valid, then set default vaules. if (config.crc != sysCRC16((uint8_t *) &config + 2, sizeof(CONFIG_STRUCT) - 2)) { configDefault(); configCalcCRC(); return FALSE; } ++config.bootCount; configCalcCRC(); return TRUE; } /** * Class to handle the GPS receiver. */ #define GPS_START 0 #define GPS_START2 1 #define GPS_COMMAND 2 #define GPS_COMMAND_POSITION 3 #define GPS_COMMAND_RXID 4 #define GPS_READMESSAGE 5 #define GPS_READID 6 #define GPS_CHECKSUMMESSAGE 7 #define GPS_EOMCR 8 #define GPS_EOMLF 9 #define GPS_ID_LENGTH 288 // Structures that stores the GPS information. GPSPOSITION gpsPosition; // State machine used to parse GPS data stream. uint8_t gpsMode; // Pointer to buffer that holds GPS messages. uint16_t gpsIndex; // Verifies checksum of message from GPS. uint8_t gpsChecksum; // Buffers to hold GPS data. uint8_t gpsBuffer[80], gpsID[GPS_ID_LENGTH]; // Flag to indicate the GPS ID is ready. bool gpsIDFlag; /** * Clear the flag that indicates the GPS ID string has been received. */ void gpsClearIDFlag() { gpsIDFlag = FALSE; } /** * Clear the flag that indicates the GPS data has been processed. When a new * GPS message is received the flag is set. */ void gpsClearUpdateFlag() { gpsPosition.updateFlag = FALSE; } /** * Get a pointer to the data structure of last valid set of GPS data. The structure * contains all GPS information including position, time of day, and solution accuracy. * * @return pointer to last valid set of GPS data */ GPSPOSITION *gpsGetData() { return &gpsPosition; } char *gpsGetID() { return gpsID; } /** * Configure the GPS receiver. */ void gpsInit() { uint8_t i; // Clear the structure that stores the position message and ID. for (i = 0; i < sizeof(GPSPOSITION); ++i) *((uint8_t *) &gpsPosition + i) = 0; // Clear the memory that stores the GPS ID string. strcpy (gpsID, ""); gpsIDFlag = FALSE; // State machine used for parsing the binary GPS string. gpsMode = GPS_START; } /** * Determine if the GPS ID has been updated since the <b>gpsClearIDFlag</b> method * was last called. * * @return true if GPS ID is ready; otherwise false */ uint8_t gpsIsIDReady() { return gpsIDFlag; } /** * Determine if the GPS data has been updated since the <b>gpsClearUpdateFlag</b> method * was last called. * * @return true if GPS data is updated; otherwise false */ uint8_t gpsIsUpdated() { return gpsPosition.updateFlag; } /** * Sends a binary message to the GPS receiver. The message * contains only the data between the start of message '@@' and * the checksum end EOM. THe rest is generated by this method. * * @param message pointer to array of binary data * @param length length of message in bytes */ void gpsSendMessage (uint8_t *message, uint8_t length) { uint8_t i, checksum; // Send the message start characters. serCputc ('@'); serCputc ('@'); // Send each character while calculating the checksum. checksum = 0; for (i = 0; i < length; ++i) { checksum ^= message[i]; serCputc (message[i]); } // END for // Now send the checksum, <CR>, and <LF>. serCputc (checksum); serCputc (13); serCputc (10); } /** * Parse the @@Hb (Short position/message) report. * */ void gpsParsePositionMessage() { char buffer[80]; // 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 = (gpsBuffer[2] << 8) | gpsBuffer[3]; gpsPosition.hours = gpsBuffer[4]; gpsPosition.minutes = gpsBuffer[5]; gpsPosition.seconds = gpsBuffer[6]; gpsPosition.latitude = ((int32_t) gpsBuffer[11] << 24) | ((int32_t) gpsBuffer[12] << 16) | ((int32_t) gpsBuffer[13] << 8) | (int32_t) gpsBuffer[14]; gpsPosition.longitude = ((int32_t) gpsBuffer[15] << 24) | ((int32_t) gpsBuffer[16] << 16) | ((int32_t) gpsBuffer[17] << 8) | (int32_t) gpsBuffer[18]; gpsPosition.altitude = ((int32_t) gpsBuffer[19] << 24) | ((int32_t) gpsBuffer[20] << 16) | ((int32_t) gpsBuffer[21] << 8) | (int32_t) gpsBuffer[22]; gpsPosition.altitudeFeet = gpsPosition.altitude * 100l / 3048l; gpsPosition.vSpeed = (gpsBuffer[27] << 8) | gpsBuffer[28]; gpsPosition.hSpeed = (gpsBuffer[29] << 8) | gpsBuffer[30]; gpsPosition.heading = (gpsBuffer[31] << 8) | gpsBuffer[32]; gpsPosition.dop = (gpsBuffer[33] << 8) | gpsBuffer[34]; gpsPosition.visibleSats = gpsBuffer[35]; gpsPosition.trackedSats = gpsBuffer[36]; gpsPosition.status = (gpsBuffer[37] << 8) | gpsBuffer[38]; // Generate a VSI (Vertical Speed Indicator). gpsPosition.vSpeedAccum = (int16_t) (gpsPosition.altitude - gpsPosition.lastAltitude) + gpsPosition.vSpeedAccum - (gpsPosition.vSpeedAccum >> 2); gpsPosition.lastAltitude = gpsPosition.altitude; } /** * Read and validate the GPS message. When a valid message has been read, the structure * <b>gpsMessage</b> is updated. */ void gpsReadData() { uint16_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<CR><LF> while ((value = serCgetc()) != -1) switch (gpsMode) { // Wait for the first @ case GPS_START: if (value == '@') gpsMode = GPS_START2; break; case GPS_START2: if (value == '@') gpsMode = GPS_COMMAND; else gpsMode = GPS_START; break; case GPS_COMMAND: if (value == 'H') gpsMode = GPS_COMMAND_POSITION; else if (value == 'C') gpsMode = GPS_COMMAND_RXID; else gpsMode = GPS_START; break; case GPS_COMMAND_POSITION: if (value == 'b') { gpsMode = GPS_READMESSAGE; gpsIndex = 0; gpsChecksum = 0; gpsChecksum ^= 'H'; gpsChecksum ^= 'b'; } else gpsMode = GPS_START; break; case GPS_COMMAND_RXID: if (value == 'j') { gpsMode = GPS_READID; gpsIndex = 0; } else gpsMode = GPS_START; break; case GPS_READMESSAGE: gpsChecksum ^= value; gpsBuffer[gpsIndex++] = value; if (gpsIndex == 47) gpsMode = GPS_CHECKSUMMESSAGE; break; case GPS_READID: // Save each byte of the string. gpsID[gpsIndex] = value; // Once all the data is read, set a flag to indicate it is ready. if (++gpsIndex == GPS_ID_LENGTH - 1) { gpsID[gpsIndex] = 0; gpsIDFlag = TRUE; gpsMode = GPS_START; } break; case GPS_CHECKSUMMESSAGE: if (gpsChecksum == value) gpsMode = GPS_EOMCR; else gpsMode = GPS_START; break; case GPS_EOMCR: if (value == 13) gpsMode = GPS_EOMLF; else gpsMode = GPS_START; break; case GPS_EOMLF: // Once we have the last character, convert the binary message to something usable. if (value == 10) gpsParsePositionMessage(); gpsMode = GPS_START; break; } // END switch } // Define the I/O pins. #define SYS_LED 2 #define SYS_CHAN 4 #define SYS_PTT 6 #define SYS_CARRIERDETECT 2 // CLK_RATE, Timer A5 value to generate 2mS interrupt // 115 * (1 / 11.0592) * 192 = 2mS // 38 * (1 / 3.6864) * 192 = 2mS #define SYS_TIMER_A5_DELAY 115 // CLK_RATE, Timer A1 value used to feed timer A5 and B. #define SYS_TIMER_A1_DELAY 191 // Heartbeat LED counter. uint16_t sysLEDCounter; // System operation time in seconds. uint16_t sysUptime; /** * Wait <b>delayMS</b> before returning. * * @param delayMS delay time in milliseconds */ void sysDelay(uint32_t delayMS) { uint32_t timerTick; timerTick = MS_TIMER; while (MS_TIMER - timerTick < delayMS); } /** * Return the number of seconds since the application was started. * * @return uptime in seconds */ uint16_t sysGetUptime() { return sysUptime; } /** * Initialize the internal system controls. */ void sysInit() { // ****** Configure PORT-A ****** // Set all output pins with LED on to indicate startup. WrPortI(PADR, &PADRShadow, 0x04); WrPortI(SPCR, NULL, 0x84); // ****** Configure PORT-D ****** // All outputs LOW. WrPortI(PDDR, &PDDRShadow, 0x00); // All ports are input. WrPortI(PDDDR, &PDDDRShadow, 0x00); // PORT D all open drain. WrPortI(PDDCR, NULL, 0xff); // Clock everything on PCLK/2 WrPortI(PDCR, NULL, 0x00); // WrPortI(PDFR, NULL, 0x00); // ****** Configure PORT-E for COM radio and general I/O ****** // Configure PE7 as CS for external I/O. WrPortI (IB7CR, NULL, 0x48); // Set PE2, PE4, PE5, and PE7 as outputs. WrPortI (PEDDR, NULL, 0xb4); // Configure PE7 as I/O strobe line. WrPortI (PEFR, NULL, 0x80); // Clock PE4-7 bits on timer B1 interrupt. WrPortI(PECR, NULL, 0x20); // Configure serial port for servo control, GPS, and debug port. serAopen (38400); serCopen (9600); serDopen (38400); // ****** Configure Timer A for heartbeat interrupt ****** // Set the timer A ISR. SetVectIntern (0x0a, sysTimerISR); // Set Timer A1 value that feeds timer A5 and B. // rate = (timerValue + 1) * clockPeriod WrPortI (TAT1R, &TAT1RShadow, SYS_TIMER_A1_DELAY); // Set timer A5 value that provides 2mS heartbeat interrupt. WrPortI (TAT5R, &TAT5RShadow, SYS_TIMER_A5_DELAY); // ****** Configure Timer B for TNC 1200 baud interrupt ****** // Set the timer B ISR. SetVectIntern (0x0b, tncTimerISR); // Setup our system variables. sysLEDCounter = 0; sysUptime = 0; serAputs ("System booted!\n\r"); } /** * Enable the system interrupts. */ void sysInterruptEnable() { // External interrupt priority 1 on INT0A, INT1A falling edge. WrPortI (I0CR, NULL, 0x05); WrPortI (I1CR, NULL, 0x05); // Clear the interrupt pending flag. RdPortI (TACSR); // Timer A5 clocked by timer A1, all other timers clocked by pclk/2, interrupt priority 2. WrPortI (TACR, &TACRShadow, 0x22); // Enable timer A1 and timer A5 interrupts. WrPortI (TACSR, &TACSRShadow, 0x21); // Timer B clocked by timer A1, interrupt priority 3. WrPortI (TBCR, NULL, 0x07); // Clear the timer B compare register to 0 for the next interrupt. WrPortI (TBM1R, NULL, 0); WrPortI (TBL1R, NULL, 0); // Clear the interrupt pending flag. RdPortI (TBCSR); // Enable Timer B and match register interrupts. WrPortI (TBCSR, NULL, 0x03); } /** * Process the timer A5 interrupt that occurs every 2mS. This method hits the watch dog timer, * controls the heartbeat LED, and processes the background communication tasks. */ root interrupt void sysTimerISR() { // Clear the timer A5 interrupt. RdPortI (TACSR); // Flash the heartbeat LED for 100mS every second. if (++sysLEDCounter == 500) { sysLEDCounter = 0; sysLEDOn(); } else if (sysLEDCounter == 50) sysLEDOff(); } /** * This function should be called once a second by an accurate time base, i.e. GPS. */ void sysTimeTick() { ++sysUptime; } /** * Turn on the system board LED. */ void sysLEDOn() { BitWrPortI (PADR, &PADRShadow, 1, SYS_LED); } /** * Turn off the system board LED. */ void sysLEDOff() { BitWrPortI (PADR, &PADRShadow, 0, SYS_LED); } /** * Turn on the transmitter. */ void sysPTTOn() { BitWrPortI (PADR, &PADRShadow, 1, SYS_PTT); } /** * Turn off the transmitter. */ void sysPTTOff() { BitWrPortI (PADR, &PADRShadow, 0, SYS_PTT); } /** * Select radio channel 1. */ void sysChan1() { BitWrPortI (PADR, &PADRShadow, 1, SYS_CHAN); } /** * Select radio channel 2. */ void sysChan2() { BitWrPortI (PADR, &PADRShadow, 0, SYS_CHAN); } /** * Calculate NMEA-0183 message checksum of <b>buffer</b> that is <b>length</b> bytes long. * * @param buffer Pointer to data buffer. * @param length number of bytes in buffer. * * @return checksum of buffer */ uint8_t sysNMEAChecksum (uint8_t *buffer, uint16_t length) { uint16_t i; uint8_t checksum; checksum = 0; for (i = 0; i < length; ++i) checksum ^= buffer[i]; return checksum; } /** * Determine if carrier is present at radio. * * @return true if carrier is present; otherwise false */ bool sysIsCarrierDetect() { if (BitRdPortI (PBDR, SYS_CARRIERDETECT) == 0) return TRUE; return FALSE; } /** * Handle run time errors. */ root void sysRuntimeErrHandler() { static int error, xpc, addr; static char buffer[80]; static uint32_t timerTick; // get all the relevant parameters off the stack #ifndef WIN32 #asm ld hl, (sp+@SP+2) ld (error), hl ; get the runtime error code ld hl, (sp+@SP+6) ld (xpc), hl ; get the XPC where exception() was called ld hl, (sp+@SP+8) ld (addr), hl ; get the address exception() was called #endasm #endif sprintf (buffer, "Run time error %d\n\r", error); serAputs (buffer); // Wait for message to be displayed. timerTick = MS_TIMER; while (MS_TIMER - timerTick < 500); // This is a simple error handler, just reboot. forceSoftReset(); } void sysSelfTest() { uint32_t timerTick; // Wait for the MAX-232 charge pumps to start and receiver data buffer to clear. timerTick = MS_TIMER; while (MS_TIMER - timerTick < 200); } /** * Calculate the CRC-16 CCITT of <b>buffer</b> that is <b>length</b> bytes long. * * @param buffer Pointer to data buffer. * @param length number of bytes in data buffer * * @return CRC-16 of buffer[0 .. length] */ uint16_t sysCRC16 (uint8_t *buffer, uint16_t length) { uint16_t i, bit, crc, value; crc = 0xffff; 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; } /** * Convert the ASCII hex character <b>digit</b> to a binary value. * * @param digit ASCII hex character * * @return binary value of digit; 0 if character invalid */ uint8_t sysParseHexDigit(uint8_t digit) { if (digit >= '0' && digit <= '9') return digit - '0'; if (digit >= 'a' && digit <= 'f') return digit - 'a' + 10; if (digit >= 'A' && digit <= 'F') return digit - 'A' + 10; return 0; } /** * Convert the string <b>buffer</b> to a scaled integer. * * @buffer pointer to string buffer * @newBuffer pointer to end of convereted string * * @return scaled integer */ int16_t sysStringToScaledInt(char *buffer, char **newBuffer) { int16_t value; bool parseFlag, negativeFlag; value = 0; parseFlag = TRUE; negativeFlag = FALSE; // Parse the input string until we get to the end or run out of valid characters. while (*buffer != 0 && parseFlag == TRUE) { if (*buffer == '-') negativeFlag = TRUE; else if (*buffer >= '0' && *buffer <= '9') value = value * 10 + (*buffer - '0'); else if (*buffer != '.') parseFlag = FALSE; ++buffer; } // END while // Set a pointer to the last parsed character. *newBuffer = buffer - 1; // Handle signed numbers and we are done. if (negativeFlag) return -value; else return value; } /** * Class to handle digital temperature sensors. */ // Define the I2C clock/data lines. #define TEMP_CLK 5 #define TEMP_DATA 3 #define tempClockHigh() BitWrPortI(PDDDR, &PDDDRShadow, 0, TEMP_CLK) #define tempClockLow() BitWrPortI(PDDDR, &PDDDRShadow, 1, TEMP_CLK) #define tempClock() BitRdPortI(PDDR, TEMP_CLK) #define tempDataHigh() BitWrPortI(PDDDR, &PDDDRShadow, 0, TEMP_DATA) #define tempDataLow() BitWrPortI(PDDDR, &PDDDRShadow, 1, TEMP_DATA) #define tempData() BitRdPortI(PDDR, TEMP_DATA) nodebug int16_t tempGetExtTemp() { uint8_t returnCode, retryCount; int16_t temp; retryCount = 0; while (retryCount < 5) { tempMasterStart(); returnCode = tempWriteAndGetAck (0x91); temp = tempReadWithAck (TRUE) << 8; temp = temp | tempReadWithAck (FALSE); tempMasterStop(); if (returnCode == 0) return (temp * 9 / 64) + 320; ++retryCount; } } /** * Initialize the digital temperarture sensor. */ void tempInit() { uint8_t i; tempDataHigh(); tempClockLow(); for (i = 0; i < 4; ++i) tempMasterStop(); } void tempMasterStart() { tempClockHigh(); tempDataHigh(); tempDataLow(); tempClockLow(); tempDataHigh(); } void tempMasterStop() { tempDataLow(); tempClockHigh(); tempDataHigh(); } nodebug uint8_t tempWriteAndGetAck(uint8_t data) { uint8_t i, returnState; // Bit bang out each bit, MSB first. for (i = 0; i < 8; ++i) { if ((data & 0x80) == 0x80) tempDataHigh(); else tempDataLow(); tempClockHigh(); tempClockLow(); data = data << 1; } // END for tempDataHigh(); tempClockHigh(); returnState = tempData(); tempClockLow(); return returnState; } nodebug uint8_t tempReadWithAck (bool ack) { uint8_t i, value; value = 0; for (i = 0; i < 8; ++i) { value = value << 1; tempClockHigh(); value |= (tempData() & 0x01); tempClockLow(); } // END for tempDataHigh(); if (ack) { tempDataLow(); tempClockHigh(); tempClockLow(); tempDataHigh(); } // END if return value; } /** * Class to handle the telemetry data. */ /** * Prepare and send a NMEA GPGGA packet via the 1200 baud TNC interface. */ void tlmGPGGAPacket() { uint32 coord, coordMin; uint8_t dirFlag, buffer[80], message[80]; // Generate the GPGGA message. sprintf (message, "$GPGGA,"); // UTC is replaced with flight time sprintf (buffer, "%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds); strcat (message, buffer); // Latitude value. coord = gpsPosition.latitude; if (gpsPosition.latitude < 0) { coord = gpsPosition.latitude * -1; dirFlag = 0; } else { dirFlag = 1; coord = gpsPosition.latitude; } coordMin = (coord % 3600000) / 6; sprintf (buffer, "%02ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000)); strcat (message, buffer); if (dirFlag == 1) strcat (message, "N,"); else strcat (message, "S,"); // Longitude value. if (gpsPosition.longitude < 0) { coord = gpsPosition.longitude * - 1; dirFlag = 0; } else { dirFlag = 1; coord = gpsPosition.longitude; } coordMin = (coord % 3600000) / 6; sprintf (buffer, "%03ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000)); strcat (message, buffer); if (dirFlag == 1) strcat (message, "E,"); else strcat (message, "W,"); // GPS status where 0: not available, 1: available if ((gpsPosition.status & 0x8000) == 0x8000) strcat (message, "1,"); else strcat (message, "0,"); // Number of visibile birds. sprintf (buffer, "%02d,", gpsPosition.visibleSats); strcat (message, buffer); // DOP sprintf (buffer, "%d.%01d,", gpsPosition.dop / 10, gpsPosition.dop % 10); strcat (message, buffer); // Altitude in meters. sprintf (buffer, "%ld.0,M,,M,,", (int32_t) (gpsPosition.altitude / 100l)); strcat (message, buffer); // Checksum, we add 1 to skip over the $ character. sprintf (buffer, "*%02X", sysNMEAChecksum(message + 1, strlen(message) - 1)); strcat (message, buffer); tncSendPacket (message); } /** * Prepare and send a NMEA GPRMC packet via the 1200 baud TNC interface. */ void tlmGPRMCPacket() { uint32 coord, coordMin, temp; uint8_t dirFlag, buffer[80], message[80]; // Generate the GPRMC message. sprintf (message, "$GPRMC,"); // UTC is replaced with flight time sprintf (buffer, "%02d%02d%02d,", gpsPosition.hours, gpsPosition.minutes, gpsPosition.seconds); strcat (message, buffer); // GPS status. if ((gpsPosition.status & 0x8000) == 0x8000) strcat (message, "A,"); else strcat (message, "V,"); // Latitude value. coord = gpsPosition.latitude; if (gpsPosition.latitude < 0) { coord = gpsPosition.latitude * -1; dirFlag = 0; } else { dirFlag = 1; coord = gpsPosition.latitude; } coordMin = (coord % 3600000) / 6; sprintf (buffer, "%02ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000)); strcat (message, buffer); if (dirFlag == 1) strcat (message, "N,"); else strcat (message, "S,"); // Longitude value. if (gpsPosition.longitude < 0) { coord = gpsPosition.longitude * - 1; dirFlag = 0; } else { dirFlag = 1; coord = gpsPosition.longitude; } coordMin = (coord % 3600000) / 6; sprintf (buffer, "%03ld%02ld.%04ld,", (uint32) (coord / 3600000), (uint32) (coordMin / 10000), (uint32) (coordMin % 10000)); strcat (message, buffer); if (dirFlag == 1) strcat (message, "E,"); else strcat (message, "W,"); // Speed knots and heading. temp = (int32_t) gpsPosition.hSpeed * 75000 / 385826; sprintf (buffer, "%d.%d,%d.%d,", (int16) (temp / 10), (int16) (temp % 10), gpsPosition.heading / 10, gpsPosition.heading % 10); strcat (message, buffer); // Date sprintf (buffer, "%02d%02d%02d,,", gpsPosition.day, gpsPosition.month, gpsPosition.year % 100); strcat (message, buffer); // Checksum, skip over the $ character. sprintf (buffer, "*%02X", sysNMEAChecksum(message + 1, strlen(message) - 1)); strcat (message, buffer); tncSendPacket (message); } uint8_t tlmIndex; /** * Initialize the telemetry class */ void tlmInit() { tlmIndex = 0; } /** * Prepare and send a status packet '>' via the 1200 baud TNC interface. */ void tlmStatusPacket() { char buffer[80], message[80]; uint16_t flightTime, value; GPSPOSITION *gpsPosition; // Get the data we need for the packet. gpsPosition = gpsGetData(); // Status message header. sprintf (message, ">ANSR "); // Current flight time. flightTime = sysGetUptime(); sprintf (buffer, "%02d:%02d:%02d ", flightTime / 3600, (flightTime / 60) % 60, flightTime % 60); strcat (message, buffer); // Show GPS information if it is report a 3D fix, otherwise show blank fields. if ((gpsPosition->status & 0xe000) == 0xe000) { sprintf (buffer, "%ld' %ld'/min ", (int32_t) (gpsPosition->altitude * 100l / 3048l), (int32_t) (gpsPosition->vSpeedAccum * 6000l / 12192l)); strcat (message, buffer); } else strcat (message, "---' ---'/min "); // GPS status if ((gpsPosition->status & 0xe000) == 0xe000){ sprintf (buffer, "%d.%dpdop ", gpsPosition->dop / 10, gpsPosition->dop % 10); strcat (message, buffer); } else if ((gpsPosition->status & 0xe000) == 0xc000) { sprintf (buffer, "%d.%dhdop ", gpsPosition->dop / 10, gpsPosition->dop % 10); strcat (message, buffer); } else strcat (message, "NoGPS "); // Aircraft computer bus voltage. // value = analogGetBusVoltage(); TODO // sprintf (buffer, "%d.%dv ", value / 10, value % 10); // strcat (message, buffer); // Internal temp. // value = analogGetIntTemp(); TODO // sprintf (buffer, "%d.%dF ", value / 10, abs(value % 10)); // strcat (message, buffer); tncSendPacket (message); } /** * This function is called every time we get a GPS message update (once a second). */ void tlmUpdateTick() { switch (gpsGetData()->seconds) { case 7: case 25: case 37: case 55: switch (tlmIndex) { case 0: tlmStatusPacket(); break; case 1: tlmGPGGAPacket(); break; case 2: tlmGPRMCPacket(); break; } // END swich tlmIndex = (tlmIndex + 1) % 3; break; } // END switch } /** * Class to handle the 1200 baud TNC functions. */ #define TNC_M0 2 #define TNC_M1 4 #define TNC_DATA 5 #define TNC_TX_WAIT 0 #define TNC_TX_SYNC 1 #define TNC_TX_DATA 2 #define TNC_TX_END 3 #define TNC_MAX_MESSAGE 255 // CLK_RATE, Timer B delta between bit time interrupts. // 48 * (1 / 11.0592) * 192 = 833.3 uS = 1200 bps // 16 * (1 / 3.684) * 192 = 833.3uS = 1200 bps #define TNC_BIT_CLOCK_RATE 48 uint16_t tncTimerCompare, tncIndex, tncLength, tncTxPacketCount; uint8_t tncBitCount, tncShift, tncLastBit, tncMode, tncTransmit; uint8_t tncBitStuff, tncBuffer[TNC_MAX_MESSAGE]; void tncInit() { tncTimerCompare = 0; tncShift = 0; tncIndex = 0; tncMode = TNC_TX_WAIT; tncTransmit = FALSE; // Counter of all the packets we handle. tncTxPacketCount = 0; // Disable the CMX614 output. BitWrPortI (PEDR, &PEDRShadow, 0, TNC_DATA); BitWrPortI (PEDR, &PEDRShadow, 0, TNC_M0); BitWrPortI (PEDR, &PEDRShadow, 1, TNC_M1); // Turn off the transmitter and select channel 2. sysPTTOff(); sysChan2(); } root interrupt void tncTimerISR() { // Clear the interrupt pending flag. RdPortI (TBCSR); // Interrupt again in 833uS (1 bit time). tncTimerCompare = (tncTimerCompare + TNC_BIT_CLOCK_RATE) & 0x3ff; WrPortI (TBM1R, NULL, (tncTimerCompare >> 2) & 0xc0); WrPortI (TBL1R, NULL, tncTimerCompare & 0xff); // Process based on the state machine. switch (tncMode) { case TNC_TX_WAIT: break; 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; BitWrPortI (PEDR, &PEDRShadow, tncLastBit, TNC_DATA); // 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. // TNCTxDelay() bytes * 8 bits/byte * 833uS/bit = x mS if (++tncIndex == configGetTNCTxDelay()) { tncIndex = 0; tncShift = tncBuffer[0]; tncBitStuff = 0; tncMode = TNC_TX_DATA; } // END if } 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; BitWrPortI (PEDR, &PEDRShadow, tncLastBit, TNC_DATA); 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; BitWrPortI (PEDR, &PEDRShadow, tncLastBit, TNC_DATA); // 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; BitWrPortI (PEDR, &PEDRShadow, tncLastBit, TNC_DATA); // If all the bits were shifted, get the next one. if (++tncBitCount == 8) { tncBitCount = 0; tncShift = 0x7e; // Transmit two closing flags. if (++tncIndex == 2) { // Keep track of how many packets we have sent. ++tncTxPacketCount; // Reset to the receive mode. tncIndex = 0; tncShift = 0; tncMode = TNC_TX_WAIT; // Change the MODEM to receive mode. BitWrPortI (PEDR, &PEDRShadow, 0, TNC_M0); BitWrPortI (PEDR, &PEDRShadow, 1, TNC_M1); BitWrPortI (PEDR, &PEDRShadow, 0, TNC_DATA); // Turn off the transmitter and go back to the primary channel. sysPTTOff(); return; } // END if } else tncShift = tncShift >> 1; break; } // END switch } /** * Send an AX.25 packet via the RF interface. This function enables the transmitter, * prepares the output buffer, and sets the interrupt mode. * * @param message pointer to NULL terminated message string */ void tncSendPacket(uint8_t *message) { uint16_t i, crc; uint8_t *outBuffer, *callSign; // serAputs ("sending '"); // serAputs (message); // serAputs ("\n\r"); // Turn on the MODEM TX mode. BitWrPortI (PEDR, &PEDRShadow, 1, TNC_DATA); BitWrPortI (PEDR, &PEDRShadow, 1, TNC_M0); BitWrPortI (PEDR, &PEDRShadow, 0, TNC_M1); // Key the transmitter. sysPTTOn(); // Set a pointer to our output buffer. outBuffer = tncBuffer; // Includes source (7), dest (7), control field (1), protocol ID (1), and end of message (1). tncLength = 17; // Set the destination address. callSign = configGetDestCallSign(); for (i = 0; i < 6; ++i) *outBuffer++ = *callSign++ << 1; // Set destination to SSID-0 *outBuffer++ = 0x60; // Set the source address. callSign = configGetCallSign(); for (i = 0; i < 6; ++i) *outBuffer++ = *callSign++ << 1; // Set the SSID. *outBuffer++ = 0x60 | (configGetBalloonSSID() << 1); // Add relay path 1. callSign = configGetRelay1CallSign(); if (*callSign != NULL) { for (i = 0; i < 6; ++i) *outBuffer++ = *callSign++ << 1; *outBuffer++ = 0x60 | (configGetRelay1SSID() << 1); tncLength += 7; } // END if // Add relay path 2. callSign = configGetRelay2CallSign(); if (*callSign != NULL) { for (i = 0; i < 6; ++i) *outBuffer++ = *callSign++ << 1; *outBuffer++ = 0x60 | (configGetRelay2SSID() << 1); tncLength += 7; } // END if // Set bit-0 of the last SSID. *(outBuffer - 1) |= 0x01; // Set the control field (UI) and protocol ID. *outBuffer++ = 0x03; *outBuffer++ = 0xf0; // Save the message. while (*message != 0) { *outBuffer++ = *message++; ++tncLength; } // Add the end of message character. *outBuffer++ = 0x0d; // Calculate and append the CRC. crc = sysCRC16(tncBuffer, tncLength); *outBuffer++ = crc & 0xff; *outBuffer = (crc >> 8) & 0xff; // Update the length to include the CRC bytes. tncLength += 2; // Prepare for the ISR. tncBitCount = 0; tncShift = 0x7e; tncLastBit = 0; tncIndex = 0; tncMode = TNC_TX_SYNC; } /** * This class flies the aircraft. */ #define FLIGHT_WAIT_DATA 0 #define FLIGHT_WAIT_TX 1 #define FLIGHT_WAIT_ACK 2 #define FLIGHT_FAST_RETRY 800 #define FLIGHT_SLOW_RETRY 30000 // State machine used to determine if we are wait for flight data to send, waiting for the transmit to complete, or waiting for an ACK. uint8_t flightDownlinkStatus; // A flag to indicate the GPS has not provided us with an update. uint8_t flightGPSFault; // The maximum processing time of the main loop. uint32_t flightMaxProcTime; // Count the number of times we try to transmit a packet. uint8_t flightRetryCount; // Flag that is set when a flight data ack packet is received. bool flightAckBlock; // The time in mS the last flight data packet was sent. uint32_t flightLastPacketTime; // Most recent GPS update. GPSPOSITION *flightGPS; bool flightLogRunFlag; /** * Get everything read for flight. */ void flightInit() { // Set a pointer to the GPS data set. flightGPS = gpsGetData(); // Set the rest of the defaults. flightGPSFault = FALSE; flightAckBlock = FALSE; flightLastPacketTime = MS_TIMER; flightDownlinkStatus = FLIGHT_WAIT_DATA; flightMaxProcTime = 0; flightRetryCount = 0; flightLogRunFlag = TRUE; } void flightEnableLog() { flightLogRunFlag = TRUE; } void flightDisableLog() { flightLogRunFlag = FALSE; } bool flightIsLogEnabled() { return flightLogRunFlag; } /** * Process the new GPS message. */ void flightProcessGPS() { char buffer[80]; sprintf (buffer, "%02d:%02d:%02d 0x%04X %d %d %d.%01d ", flightGPS->hours, flightGPS->minutes, flightGPS->seconds, flightGPS->status , flightGPS->visibleSats, flightGPS->trackedSats, flightGPS->dop / 10, flightGPS->dop % 10); // serAputs (buffer); sprintf (buffer, "%ld,%ld,%ld\n\r", flightGPS->latitude, flightGPS->longitude, flightGPS->altitude); // serAputs (buffer); // Clear the update flag so we know when we get a new GPS message. gpsClearUpdateFlag(); } void flightDataRecorder() { } /** * The main control loop that controls everything. */ void flightRun() { char buffer[80]; uint32_t startTime, procTime, gpsTime; gpsTime = MS_TIMER; // The main control loop. while (TRUE) { startTime = MS_TIMER; // Read and process the serial data from the GPS. gpsReadData(); // Process new GPS messages, update the telemetry, and keep track of the last GPS messsage. if (gpsIsUpdated()) { gpsTime = MS_TIMER; flightProcessGPS(); tlmUpdateTick(); sysTimeTick(); // sprintf (buffer, "temp = %d\015\012", tempGetExtTemp()); // serAputs (buffer); if (gpsIsIDReady()) { serAputs (gpsGetID()); gpsClearIDFlag(); } // END if // sprintf (buffer, "pos = %d, cmd = %d\n\r", servoPosition[0], servoCommand[0]); // serCputs(buffer); } // END if // Read and process the analog input board. // analogUpdate(); // If the GPS hasn't updated in the last 1.5 seconds, send a message to request data. if (MS_TIMER - gpsTime > 1500) { gpsTime = MS_TIMER; gpsSendMessage ("Hb\001", 3); } // END if // Process the flight data recorder. flightDataRecorder(); // Record the maximum processing time through the main loop and display it. procTime = MS_TIMER - startTime; // Display the maximum processing time of the main loop. if (procTime > flightMaxProcTime) { flightMaxProcTime = procTime; sprintf (buffer, "time = %ldmS\015\012", flightMaxProcTime); // serAputs (buffer); } // END if } // END while } /** * Time to fly! */ main() { // Initialize the system and configuration classes. These must be set for the other classes. sysInit(); configInit(); // Now initialize the rest of the classes in alphabetical order. flightInit(); gpsInit(); tlmInit(); tncInit(); // Turn off the LED to indicate init is complete and we are turning on interrupts. sysLEDOff(); // Turn on the interrupts. sysInterruptEnable(); // Make sure everything is alright. sysSelfTest(); // Now execute the main routine flightRun(); // We should never get to this point, but if we do reset. forceSoftReset(); }