// // Name: gs.c // // // Revision History // // M. Gray 04 Feb 2002 V1.00 Initial release. // // M. Gray 30 May Mar 2002 V1.01 Incremental release for web site. // // M. Gray // // // Copyright (c) 2002 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. #ifndef WIN32 #nodebug #endif // 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. typedef char bool; typedef char int8; typedef long int32; // Boolean flags. #define TRUE 1 #define FALSE 0 #ifdef WIN32 #define NULL 0 #endif // Allocate buffers for the serial ports. //#define CINBUFSIZE 511 //#define COUTBUFSIZE 511 #define DINBUFSIZE 2047 #define DOUTBUFSIZE 2047 // We simulate the rabbit enviroment for cross compiling in Visual Studio. #ifdef WIN32 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.H> #define PADR 0 #define PBDR 1 #define PDDR 3 #define PEDR 4 #define TBM1R 10 #define TBL1R 11 #define TBCSR 12 #define TBCR 14 #define TAT1R 15 #define SPCR 17 #define PDFR 18 #define PDDDR 19 #define PDDCR 20 #define PDCR 21 #define PEDDR 22 #define PEFR 23 #define I0CR 24 #define TACR 25 #define TACSR 26 #define TAT4R 27 #define PECR 28 #define IB7CR 29 #define I1CR 30 #define TBCLR 31 #define SEC_TIMER 1 #define MS_TIMER 1000 #define PI 3.14159265358 // Variables and functions provided by Rabbit library. uint16 PADRShadow, PDDRShadow, PEDRShadow, TAT1RShadow, TACSRShadow, TACRShadow, TAT4RShadow; void WrPortI (uint16 port, uint16 *shadow, uint16 value) {}; void WrPortE (uint16 port, uint16 *shadow, uint16 value) {}; void BitWrPortI (uint16 port, uint16 *shadow, uint8 value, uint8 bit) {}; uint16 BitRdPortI (uint16 port, uint8 bit) { return 0; }; uint16 RdPortI (uint16 port) { return 0; }; uint16 RdPortE (uint16 port) { return 0; }; uint16 serCgetc () { return 0; }; void serAopen (uint16 baudrate) { }; void serCopen (uint16 baudrate) { }; void serDopen (uint16 baudrate) { }; void serAputc (uint8 value) { putchar(value); }; void serAputs (uint8 *str) { puts (str); }; void serCputc (uint8 value) { putchar(value); }; void serCputs (uint8 *str) { puts (str); }; void serDputc (uint16 value) { putchar(value); }; void serDputs (uint8 *str) { puts (str); }; uint8 serDgetc () { return 0; }; void clockDoublerOn() { }; void SetVectExtern2000 (uint8 vector, void func()) {}; void SetVectIntern (uint8 vector, void func()) {}; uint8 VdGetFreeWd (uint8 timeOut) { return 0; }; void VdHitWd (uint8 value) {}; void forceSoftReset() {}; #endif // Public methods and data structures for each class. typedef struct { uint16 faultCount, badHeaderCRCCount, invalidHeaderCount, badDataCRCCount, bufferOverflow; uint16 carrierDetectCount, rxPacketCount; uint16 txPacketCount; } COM_STATS; typedef struct { uint8 packetNumber; uint8 hours, minutes, seconds; int32 latitude, longitude; int16 gpsAltitude, presAltitude; int16 gpsVSI, presVSI; uint16 gpsSpeed, airSpeed; uint16 gpsHeading, compassHeading; int16 roll, pitch; uint16 dist, heading; uint8 busVoltage; int16 intTemp, extTemp; uint16 gpsStatus, gpsDOP; uint8 trackedSats, visibleSats; uint8 lastRxSQE; } TLM_STATUS; void cncClearCounters(); void cncClearRxBuffer(); uint8 *cncGetRxBuffer(); void cncInit(); void cncReadData(); uint8 cncIsRxReady(); uint8 cncTxPacket (uint8 messageType, uint8 *data, uint16 length); void comAckPacket(uint8 *message, uint16 length); void comClearCounters(); void comClearRxBuffer(); uint8 *comGetRxBuffer(); uint8 comGetSQE(); COM_STATS *comGetStats(); void comInit(); uint8 comIsCRCBad(); uint8 comIsRxReady(); void comRx(); void comTimer(); uint8 comTxPacket(uint8 messageType, uint8 *data, uint16 length); uint8 configInit(); uint8 configGetComTxDelay(); uint16 sysCRC16 (uint8 *buffer, uint16 length); void sysDelay (uint32 delayMS); void sysInit(); void sysInterruptEnable(); bool sysIsCarrierDetect(); void sysTxLEDOff(); void sysTxLEDOn(); void sysRxLEDOff(); void sysRxLEDOn(); uint8 sysNMEAChecksum (uint8 *buffer, uint16 length); uint8 sysParseHexDigit(uint8 digit); void sysPTTOff(); void sysPTTOn(); void sysSelfTest(); #ifdef WIN32 void sysTimerISR(); #else interrupt void sysTimerISR(); #endif /** * Class to handle command and control */ #define CNC_START 0 #define CNC_HEADER 1 #define CNC_LENGTH1 2 #define CNC_LENGTH2 3 #define CNC_READDATA 4 #define CNC_HEADER_MSB 0x8a #define CNC_HEADER_LSB 0xd7 #define CNC_BUFFER_SIZE 580 // First byte used to define message type. #define CNC_MESSAGE_CMD 0x10 #define CNC_MESSAGE_CMDACK 0x11 #define CNC_MESSAGE_TELEMETRY 0x12 #define CNC_MESSAGE_LOG_STATUS 0x13 #define CNC_MESSAGE_OPS_STATE 0x14 // Second byte used to define mnemonics and status. #define CNC_COMMAND_TLMRATE 0x20 #define CNC_COMMAND_APRS 0x21 #define CNC_COMMAND_SETORIGIN 0x22 #define CNC_COMMAND_LOG 0x23 #define CNC_COMMAND_OPS_STATE 0x24 #define CNC_COMMAND_ZERO_INSTRUMENTS 0x25 #define CNC_COMMAND_TEST_A 0x26 #define CNC_COMMAND_TEST_B 0x27 #define CNC_STATUS_TIMEOUT 0x80 // Third byte used to define mnemonic constants. #define CNC_LOG_DUMP 0x00 #define CNC_LOG_CLEAR 0x01 typedef struct { uint16 bufferOverflow, rxPacketCount, txPacketCount; } CNCSTATS; // State machine used to read binary data stream. uint8 cncMode; // Length of the receive and temporary receive buffers. uint16 cncRxLength, cncTempRxLength; // Index to the buffers. uint16 cncIndex; // Flag to indicate if the receive buffer is in use. bool cncRxBusyFlag; // Transmit, receive, and temporary buffers. uint8 cncTempBuffer[CNC_BUFFER_SIZE + 1]; uint8 cncRxBuffer[CNC_BUFFER_SIZE + 1]; // Structure that has stats on C and C. CNCSTATS cncStats; /** * Clear the com link statistical database. */ void cncClearCounters() { cncStats.bufferOverflow = 0; cncStats.rxPacketCount = 0; cncStats.txPacketCount = 0; } /** * Clear the receive buffer ready flag. This flag must be cleared after a message is processed in * order to get a new message. If the flag is not cleared in time, the <b>bufferOverflow</b> statistic * value is incremented. */ void cncClearRxBuffer() { cncRxBusyFlag = FALSE; } void cncInit() { cncClearCounters(); cncMode = CNC_START; cncRxBusyFlag = FALSE; } /** * Determine if a message is waiting in the receive buffer. * * @return true if message ready; otherwise false */ uint8 cncIsRxReady() { return cncRxBusyFlag; } uint8 *cncGetRxBuffer() { return cncRxBuffer; } void cncReadData() { uint16 i, value; // This state machine handles each characters as it is read from C and C serial port. // We are looking for the binary message <header><length><messageType><data ...> while ((value = serDgetc()) != -1) switch (cncMode) { // Wait for the MSB of the header. case CNC_START: if (value == CNC_HEADER_MSB) cncMode = CNC_HEADER; break; case CNC_HEADER: if (value == CNC_HEADER_LSB) cncMode = CNC_LENGTH1; else cncMode = CNC_START; break; case CNC_LENGTH1: cncTempRxLength = value << 8; cncMode = CNC_LENGTH2; break; case CNC_LENGTH2: cncTempRxLength |= value; if (cncTempRxLength == 0 || cncTempRxLength > CNC_BUFFER_SIZE) cncMode = CNC_START; else { cncMode = CNC_READDATA; cncIndex = 0; } break; case CNC_READDATA: cncTempBuffer[cncIndex] = value; if (++cncIndex == cncTempRxLength) { if (cncRxBusyFlag) ++cncStats.bufferOverflow; else { // Copy the temporary buffer to the receive buffer. for (i = 0; i < cncIndex; ++i) cncRxBuffer[i] = cncTempBuffer[i]; // Just in case the sender doesn't NULL terminate a string, we will. cncRxBuffer[i] = 0; // Keep track of how many packets we have received. ++cncStats.rxPacketCount; // Set the flag to indicate we have a new message. cncRxBusyFlag = TRUE; } // END if-else cncRxBusyFlag cncMode = CNC_START; } break; } // END switch } /** * Send a data packet over the C and C link. * * @param data pointer to data block * @param 4-bit message type * @param length of message in bytes * * @return true if messaged queue for transmit; otherwise false */ uint8 cncTxPacket (uint8 messageType, uint8 *data, uint16 length) { uint16 i; // Add one to the length to include the message type. ++length; // Check the length. if (length > CNC_BUFFER_SIZE) return FALSE; // Send the message header, length, type, and data. serDputc (CNC_HEADER_MSB); serDputc (CNC_HEADER_LSB); serDputc ((length >> 8) & 0xff); serDputc (length & 0xff); serDputc (messageType); for (i = 0; i < length; ++i) serDputc (data[i]); ++cncStats.txPacketCount; return TRUE; } /** * Class to handle the primary com radio. */ // CMX909 memory mapped registers. #define COM_CMX909_DATA 0xe000 #define COM_CMX909_COMMAND 0xe001 #define COM_CMX909_STATUS 0xe001 #define COM_CMX909_CONTROL 0xe002 #define COM_CMX909_DQ 0xe002 #define COM_CMX909_MODE 0xe003 // CMX909 receive mode commands. #define COM_CMD_SFH 0x01 #define COM_CMD_R3H 0x02 #define COM_CMD_RDB 0x03 #define COM_CMD_SFS 0x04 #define COM_CMD_RSB 0x05 #define COM_CMD_LFSB 0x06 #define COM_CMD_RESET 0x07 // CMX909 control bit masks. #define COM_AQLEV_MASK 0x40 #define COM_AQBC_MASK 0x80 // CMX909 transmit mode commands. #define COM_CMD_T7H 0x01 #define COM_CMD_TDB 0x03 #define COM_CMD_TQB 0x04 #define COM_CMD_TSB 0x05 #define COM_CMD_TSO 0x06 // CMX909 mode commands. #define COM_CMD_PSBIXT 0x0f // Frame headers. #define COM_BASE_BITSYNC 0xcc #define COM_MOBLE_BITSYNC 0x33 // Frame sync bytes. #define COM_FS_MSB 0xa5 #define COM_FS_LSB 0xb8 // Frame header bytes. #define COM_FH_MSB 0x39 #define COM_FH_LSB 0xd0 // Comm link state machine. #define COM_WAIT_CARRIER 0 #define COM_WAIT_RXDELAY 1 #define COM_SEARCH_FS 2 #define COM_WAIT_FRAME 3 #define COM_READ_DATA 4 #define COM_TX_PREPARE 5 #define COM_WAIT_TXDELAY 6 #define COM_TX_DATA 7 #define COM_TX_FINAL 8 // Message types. 0 - 7 are from mobile to base, 8-15 are from base to mobile. #define COM_MESSAGE_CMDACK 0 #define COM_MESSAGE_TELEMETRY 1 #define COM_MESSAGE_DUMPBUFER 2 #define COM_MESSAGE_CMD 8 // First byte used to define command and control messages. #define COM_COMMAND_RESET 0x00 #define COM_COMMAND_TLMRATE 0x01 #define COM_COMMAND_APRS 0x02 #define COM_COMMAND_CLRCOMMCNT 0x03 #define COM_COMMAND_SETORIGIN 0x04 #define COM_COMMAND_PING 0x05 #define COM_COMMAND_LOG_DUMP 0x06 #define COM_COMMAND_LOG_CLEAR 0x07 #define COM_COMMAND_ZERO_INSTRUMENTS 0x08 #define COM_COMMAND_TEST_A 0x09 #define COM_COMMAND_TEST_B 0x0a // Buffer that includes 32 blocks of data and a 1 byte message type. #define COM_BUFFER_SIZE 577 // Time to wait in mS * 2 before starting the frame sync search. #define COM_RXDELAY_TIME 5 // State machine used to determine processing in ISR. uint8 comState; // State variable to keep track of when carrier detect changes. uint8 comLastCarrierDetect; // Timer used to determine when to receive or transmit data after the transmitter is keyed or the carrier is detected. uint8 comTxRxDelay; // Number of blocks remaining to receive or transmit. uint8 comBlockCount; // Number of 18 byte blocks in the transmit buffer. uint8 comTxBlockCount; // Receive signal quality estimate and accumulator. uint8 comSQE; uint16 comSQEAccum; // Structure that has stats on communcations. COM_STATS comStats; // Flag to indicate if any of the data blocks had CRC errors during receive. bool comRxCRCFlag, comRxTempCRCFlag; // Message time stamp. uint32 comRxTimeStamp, comRxTempTimeStamp; // Index to the temporary buffer. uint16 comIndex; // Flag to indicate if the receive buffer is in use. bool comRxBusyFlag; // Flag to indicate data is ready to be transmitted. bool comTxReadyFlag; // Defines the number of blocks in each message type. The message type is determined by the lower nibble of the second frame header byte. const uint8 comFrameBlockCount[] = { 1, 3, 32, 255, 255, 255, 255, 255, 1, 255, 255, 255, 255, 255, 255, 255 }; // Defines the S/N ratio for a given SQE value. const uint8 comSQEtoSNRTable[] = { 26, 39, 55, 75, 97, 115, 135, 154, 171, 190, 255 }; // Transmit, receive, and temporary buffers. uint8 comTempBuffer[COM_BUFFER_SIZE]; uint8 comRxBuffer[COM_BUFFER_SIZE]; uint8 comTxBuffer[COM_BUFFER_SIZE]; // Common message packets. const uint8 comPingMessage[] = { COM_COMMAND_PING }; /** * Send an ack message in response to <b>message</b>. The ack includes * a transmit packet count and SQE of the message. * * @param message pointer to message to ack * @param length length of original message */ void comAckPacket(uint8 *message, uint16 length) { uint8 i, buffer[18]; // Return the a packet counter and SQE of the packet to acknowledge. buffer[0] = comStats.txPacketCount & 0xff; buffer[1] = comSQE; // Assemble the message and zero pad. for (i = 0; i < length && i < 16; ++i) buffer[i + 2] = message[i]; while (i < 16) { buffer[i + 2] = 0; ++i; } // END while comTxPacket(COM_MESSAGE_CMDACK, buffer, 18); } /** * Clear the com link statistical database. */ void comClearCounters() { comStats.badDataCRCCount = 0; comStats.badHeaderCRCCount = 0; comStats.bufferOverflow = 0; comStats.carrierDetectCount = 0; comStats.faultCount = 0; comStats.invalidHeaderCount = 0; comStats.rxPacketCount = 0; comStats.txPacketCount = 0; } /** * Clear the receive buffer ready flag. This flag must be cleared after a message is processed in * order to get a new message. If the flag is not cleared in time, the <b>bufferOverflow</b> statistic * value is incremented. */ void comClearRxBuffer() { comRxBusyFlag = FALSE; } /** * Process the CMX909 IRQ requests. */ #ifdef WIN32 void comExternalISR() #else interrupt void comExternalISR() #endif { uint8 startTime; uint16 i; uint8 status, frameHeader1, frameHeader2; // Get the status to determine the interrupt state. status = RdPortE (COM_CMX909_STATUS); switch (comState) { case COM_SEARCH_FS: // Check the DIBOVF and BFREE bits. if ((status & 0x50) != 0x40) { comState = COM_WAIT_CARRIER; ++comStats.faultCount; return; } // Wait 12-bit times. startTime = RdPortI (TBCLR); while (((RdPortI(TBCLR) - startTime) & 0xff) < 80); // Search for the frame header. WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_SFH); comState = COM_WAIT_FRAME; break; case COM_WAIT_FRAME: // We'll time stamp when we get the frame header. comRxTempTimeStamp = MS_TIMER; // Verify the frame was receieved from the base station without any CRC errors. if ((status & 0x5a) != 0x40) { comState = COM_WAIT_CARRIER; ++comStats.badHeaderCRCCount; return; } // END if // Turn on the LED to indicate we have a packet header with a good CRC. sysRxLEDOn(); // Verify the frame is valid. frameHeader1 = RdPortE (COM_CMX909_DATA); frameHeader2 = RdPortE (COM_CMX909_DATA); if (frameHeader1 != COM_FH_MSB || ((frameHeader2 & 0xf0) != COM_FH_LSB)) { comState = COM_WAIT_CARRIER; ++comStats.invalidHeaderCount; return; } // END if // Determine the number of blocks to receive based on the lower nibble of the frame header. comBlockCount = comFrameBlockCount[frameHeader2 & 0x0f]; // If the block count is 255, then we have an invalid messsage type in the frame header. if (comBlockCount == 255) { comState = COM_WAIT_CARRIER; ++comStats.invalidHeaderCount; return; } // Start the read process. WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RDB); // Place the message type in the buffer and get ready to read the data. comTempBuffer[0] = frameHeader2 & 0x0f; comIndex = 1; comRxTempCRCFlag = FALSE; comState = COM_READ_DATA; comSQEAccum = 0; break; case COM_READ_DATA: // Check the DIBOVF and BFREE bits. if ((status & 0x50) != 0x40) { comState = COM_WAIT_CARRIER; ++comStats.faultCount; return; } // If we have a CRC error in this data block, then put zero values in the data buffer. if ((status & 0x08) != 0x00) { for (i = 0; i < 18; ++i) comTempBuffer[comIndex++] = 0; comRxTempCRCFlag = TRUE; } else { // Read the 18 data bytes. for (i = 0; i < 18; ++i) comTempBuffer[comIndex++] = RdPortE(COM_CMX909_DATA); // Add the SQE to the accumulator. comSQEAccum += RdPortE(COM_CMX909_DQ); } // END if-else // If we have all the blocks, save the data. if (--comBlockCount == 0) { // If busy flag hasn't been cleared, we have no where to put the message. if (comRxBusyFlag) ++comStats.bufferOverflow; else { // Copy the temporary buffer to the receive buffer. for (i = 0; i < comIndex; ++i) comRxBuffer[i] = comTempBuffer[i]; // Just in case the sender doesn't NULL terminate a string, we will. comRxBuffer[i] = 0; // Keep track of how many packets we have received. ++comStats.rxPacketCount; // Average the SQE value over the number of packets in the message. comSQE = comSQEAccum / comFrameBlockCount[comTempBuffer[0]]; // Set the CRC flag. comRxCRCFlag = comRxTempCRCFlag; // Save the time stamp. comRxTimeStamp = comRxTempTimeStamp; // Set the flag to indicate we have a new message. comRxBusyFlag = TRUE; } // END if-else comRxBusyFlag comState = COM_WAIT_CARRIER; return; } // Get the next data block. WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RDB); break; case COM_TX_DATA: if (comBlockCount-- == 0) { comState = COM_TX_FINAL; WrPortE (COM_CMX909_DATA, NULL, 0x33); WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_TSB); } for (i = 0; i < 18; ++i) WrPortE (COM_CMX909_DATA, NULL, comTxBuffer[comIndex++]); WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_TDB); break; case COM_TX_FINAL: comTxReadyFlag = FALSE; sysPTTOff(); sysTxLEDOff(); comState = COM_WAIT_CARRIER; // Keep track of the packets we send. ++comStats.txPacketCount; // Reset after we are done. WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET); // Enable the IRQ output, invert transmit/receive bits, and enable scrambler. WrPortE (COM_CMX909_MODE, NULL, 0xd0); break; // If we get an interrupt and aren't in a known state, just reset to receive mode. default: ++comStats.faultCount; comState = COM_WAIT_CARRIER; break; } // END switch } /** * Get a pointer to the communications statistics block. * * @return pointer to stats block */ COM_STATS *comGetStats() { return &comStats; } /** * Get a pointer to the receive buffer of the last message. * * @return receive buffer */ uint8 *comGetRxBuffer() { return comRxBuffer; } /** * Get the signal quality estimate of the last message that was received. * * @return Signal Quality Estimate */ uint8 comGetSQE() { return comSQE; } /** * Setup the primary communications channel. */ void comInit() { // Set class variables. comLastCarrierDetect = 0; comState = COM_WAIT_CARRIER; comRxBusyFlag = FALSE; comSQE = 0; comRxTimeStamp = 0; comRxTempTimeStamp = 0; // Clear the communications counters. comClearCounters(); // Reset the CMX909 WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET); // Configure clock divider for 512 (7200 baud), peak average, and narrow bandwidth receive. WrPortE (COM_CMX909_CONTROL, NULL, 0x65); // Enable the IRQ output, invert transmit/receive bits, and enable scrambler. WrPortE (COM_CMX909_MODE, NULL, 0xd0); } /** * Determine if a CRC error occured in the last data packet. * * @return true if CRC error occured; otherwise false */ uint8 comIsCRCBad() { return comRxCRCFlag; } /** * Determine if communcations buffer contains a new message. After the message is processed, * the method <b>comClearBuffer</b> should be called. * * @return true if buffer has a new message; otherwise false */ uint8 comIsRxReady() { return comRxBusyFlag; } /** * Convert the SQE value retrun by the CMX909 into an S/N estimate. The CMX909 determine * an S/N value from 3 to 12. * * @param sqe value from CMX909 during receive * * @return S/N estimate */ uint8 comSQEtoSNR(uint8 sqe) { uint8 i, snr; snr = 0; // Go down the table until we find the range we are located in. for (i = 0; i < sizeof(comSQEtoSNRTable) / sizeof(uint8); ++i) { if (sqe < comSQEtoSNRTable[i]) return snr; if (snr == 0) snr = 3; else ++snr; } // If we go through the whole table, then the SNR is better than 12dB. return 12; } /** * This method is called every 2mS and is used to process com radio functions. */ void comTimer() { uint8 carrierDetect; if (!comTxReadyFlag) // Detect a transition in the carrier detect signal. if ((carrierDetect = sysIsCarrierDetect()) != comLastCarrierDetect) { comLastCarrierDetect = carrierDetect; // Reset the CMX909 for any carrier detect transition. WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET); // Process the carrier detected going active. if (carrierDetect) { comState = COM_WAIT_RXDELAY; comTxRxDelay = COM_RXDELAY_TIME; ++comStats.carrierDetectCount; } else { comState = COM_WAIT_CARRIER; sysRxLEDOff(); } // Once we've detect a carrier change, we'll just exit. return; } // END if carrierDetect switch (comState) { case COM_WAIT_CARRIER: // If we are waiting for the carrier to go active, then we can transmit. if (comTxReadyFlag) { // Set the state machine to count down until we are ready to transmit data. comState = COM_WAIT_TXDELAY; // Key the transmitter and turn on the TX LED. sysPTTOn(); sysTxLEDOn(); // Reset the CMX909 WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_RESET); // Enable the IRQ output, transmit, and enable scrambler. WrPortE (COM_CMX909_MODE, NULL, 0xb0); // Write the frame header. WrPortE (COM_CMX909_DATA, NULL, COM_BASE_BITSYNC); WrPortE (COM_CMX909_DATA, NULL, COM_BASE_BITSYNC); WrPortE (COM_CMX909_DATA, NULL, COM_FS_MSB); WrPortE (COM_CMX909_DATA, NULL, COM_FS_LSB); WrPortE (COM_CMX909_DATA, NULL, COM_FH_MSB); WrPortE (COM_CMX909_DATA, NULL, COM_FH_LSB | (comTxBuffer[0] & 0x0f)); comBlockCount = comTxBlockCount; } // END if break; case COM_WAIT_RXDELAY: if (--comTxRxDelay == 0) { comState = COM_SEARCH_FS; // Search for the frame sync bytes. WrPortE (COM_CMX909_DATA, NULL, COM_FS_MSB); WrPortE (COM_CMX909_DATA, NULL, COM_FS_LSB); WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_LFSB | COM_AQBC_MASK | COM_AQLEV_MASK); } // END if break; case COM_WAIT_TXDELAY: if (--comTxRxDelay == 0) { // Set the state machine to transmit data starting at the first data byte. comState = COM_TX_DATA; comIndex = 1; // Send the header data. WrPortE (COM_CMX909_COMMAND, NULL, COM_CMD_T7H); } // END if break; } // END switch } /** * Send a data packet over the primary channel. The message type is a 4-bit value * that is sent as part of the frame header. The type is used to determine the * number of data blocks that will be received. * * @param 4-bit message type * @param data pointer to data block * @param length of message in bytes * * @return true if messaged queue for transmit; otherwise false */ uint8 comTxPacket (uint8 messageType, uint8 *data, uint16 length) { uint16 i; // If we are transmitting a packet already, then we can't send another. if (comTxReadyFlag) return FALSE; // This is a hard coded length based on the CMX909 com chip. if (length > 575) return FALSE; // Carrier only delay period. comTxRxDelay = configGetComTxDelay() >> 1; // Copy the message to the transmit buffer. comTxBuffer[0] = messageType; for (i = 0; i < length; ++i) comTxBuffer[i + 1] = data[i]; comTxBlockCount = ((length - 1) / 18) + 1; // Pad any blocks that are incomplete with 0 data. for (i = length; i < 18 * (uint16) comTxBlockCount; ++i) comTxBuffer[i + 1] = 0; // Set this flag to tell the background timer to transmit the packet when able. comTxReadyFlag = TRUE; // The packet is buffered, so let them know. return TRUE; } uint8 comRetryTxPacket() { // If we are transmitting a packet already, then we can't send another. if (comTxReadyFlag) return FALSE; // Carrier only delay period. comTxRxDelay = configGetComTxDelay() >> 1; // Set this flag to tell the background timer to transmit the packet when able. comTxReadyFlag = TRUE; // The packet is buffered, so let them know. return TRUE; } /** * Class to handle configuration items. */ typedef struct { uint16 crc; uint8 tncTxDelay, comTxDelay; uint8 callSign[7], destCallSign[7], relay1CallSign[7], relay2CallSign[7]; uint8 callSignSSID, relay1SSID, relay2SSID; double gpsStartLat, gpsStartLong; uint16 bootCount, flightTime; } CONFIG_STRUCT; #ifdef WIN32 CONFIG_STRUCT config; #else protected CONFIG_STRUCT config; #endif /** * Set the default configuration parameters. */ void configDefault() { // Station ID, relay path, and destination call sign and SSID. strcpy (config.callSign, "KD7LMO"); config.callSignSSID = 11; 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 com link. config.comTxDelay = 40; // Default starting location. config.gpsStartLat = 33.618766; config.gpsStartLong = -111.733483; // Count the number of system boots. config.bootCount = 0; // Flight operation time. config.flightTime = 0; } /** * Calculate and set the configuration parameter block CRC. */ void configCalcCRC() { config.crc = sysCRC16 ((uint8 *) &config + 2, sizeof(CONFIG_STRUCT) - 2); } uint8 *configGetCallSign() { return config.callSign; } uint8 *configGetDestCallSign() { return config.destCallSign; } uint16 configGetFlightTime() { return config.flightTime; } uint8 *configGetRelay1CallSign() { return config.relay1CallSign; } uint8 *configGetRelay2CallSign() { return config.relay2CallSign; } uint8 configGetRelay1SSID() { return config.relay1SSID; } uint8 configGetRelay2SSID() { return config.relay2SSID; } uint8 configGetSSID() { return config.callSignSSID; } uint8 configGetTNCTxDelay() { return config.tncTxDelay; } uint8 configGetComTxDelay() { return config.comTxDelay; } /** * Initialize configuration subsystem. */ uint8 configInit() { // TODO Remove me configDefault(); // If the configuration CRC is not valid, then set default vaules. if (config.crc != sysCRC16((uint8 *) &config + 2, sizeof(CONFIG_STRUCT) - 2)) { configDefault(); configCalcCRC(); return FALSE; } ++config.bootCount; configCalcCRC(); return TRUE; } /** * Class to handle general system functions. */ // Define the I/O pins. #define SYS_TXLED 0 #define SYS_RXLED 1 #define SYS_PTT 1 #define SYS_CARRIERDETECT 1 /** * Wait <b>delayMS</b> before returning. * * @param delayMS delay time in milliseconds */ void sysDelay(uint32 delayMS) { uint32 timerTick; timerTick = MS_TIMER; while (MS_TIMER - timerTick < delayMS); } /** * Initialize the internal system controls. */ void sysInit() { // Run as fast as we can. clockDoublerOn(); // Configure serial port for command and control. serDopen (38400); // ****** Configure PORT-A ****** // Make it all outputs in the off state. WrPortI(SPCR, NULL, 0x84); WrPortI(PADR, &PADRShadow, 0x00); // ****** Configure PORT-D ****** // Tx and Rx LEDs on WrPortI(PDDR, &PDDRShadow, 0x00); // Port D no alt TXA/TXB WrPortI(PDFR, NULL, 0x00); // Port D all outputs. WrPortI(PDDDR, NULL, 0xff); // PORT D push-pull outputs. WrPortI(PDDCR, 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); // ****** Configure Timer A for heartbeat interrupt ****** // Set the timer A ISR. SetVectIntern (0x0a, sysTimerISR); // Set Timer A1 value that feeds timer A4 and B. // rate = (timerValue + 1) * clockPeriod WrPortI (TAT1R, &TAT1RShadow, 191); // Set timer A4 value that provides 2mS heartbeat interrupt. WrPortI (TAT4R, &TAT4RShadow, 96); // ****** Configure Timer B for time delay ****** // Timer B clocked by timer A1. WrPortI (TBCR, NULL, 0x04); } void sysEnableInterrupt() { // Set external ISR to priority 1. SetVectExtern2000 (0x01, comExternalISR); // 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 A4 clocked by timer A1, all other timers clocked by pclk/2, interrupt priority 2. WrPortI (TACR, &TACRShadow, 0x12); // Enable timer A1 and timer A4 interrupts. WrPortI (TACSR, &TACSRShadow, 0x11); } /** * Process the timer A4 interrupt that occurs every 2mS. This method hits the watch dog timer, * controls the heartbeat LED, and processes the background communication tasks. */ #ifdef WIN32 void sysTimerISR() #else interrupt void sysTimerISR() #endif { // Clear the timer A4 interrupt. RdPortI (TACSR); // Call the com radio processor. comTimer(); } /** * Turn on the transmit LED. */ void sysTxLEDOn() { BitWrPortI (PDDR, &PDDRShadow, 0, SYS_TXLED); } /** * Turn off the transmit LED. */ void sysTxLEDOff() { BitWrPortI (PDDR, &PDDRShadow, 1, SYS_TXLED); } /** * Turn on the transmit LED. */ void sysRxLEDOn() { BitWrPortI (PDDR, &PDDRShadow, 0, SYS_RXLED); } /** * Turn off the transmit LED. */ void sysRxLEDOff() { BitWrPortI (PDDR, &PDDRShadow, 1, SYS_RXLED); } /** * Turn on the transmitter. */ void sysPTTOn() { BitWrPortI (PADR, &PADRShadow, 1, SYS_PTT); } /** * Turn off the transmitter. */ void sysPTTOff() { BitWrPortI (PADR, &PADRShadow, 0, SYS_PTT); } /** * 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 sysNMEAChecksum (uint8 *buffer, uint16 length) { uint16 i; uint8 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; } void sysSelfTest() { uint32 timerTick; // Wait for the MAX-232 charge pumps to start and display the LEDs for 1.5 seconds. timerTick = MS_TIMER; while (MS_TIMER - timerTick < 1500); sysTxLEDOff(); sysRxLEDOff(); } /** * 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 sysCRC16 (uint8 *buffer, uint16 length) { uint16 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 sysParseHexDigit(uint8 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; } /** * This class runs the ground station. */ // Number of milliseconds between the ping packet. #define FLIGHT_PING_TIME 15000 // Constants for back ground task state machine. #define GROUND_WAIT 0 #define GROUND_REQ_LOG_SIZE 1 #define GROUND_WAIT_LOG_SIZE 2 #define GROUND_START_DUMP_DATA 3 #define GROUND_REQ_DUMP_DATA 4 #define GROUND_WAIT_DUMP_DATA 5 #define GROUND_RX_DUMP_DATA 6 // The number of bytes in each block of log data we dump. #define GROUND_DUMP_SIZE 575 // Number of times to resend a packet. #define GROUND_RETRY_COUNT 5 // Number of milliseconds to wait for a short packet ack #define GROUND_WAIT_SHORT 800 #define GROUND_WAIT_LONG 1400 // Timer to keep track of when to send the ping packet. uint32 groundPingTimer; // Timer to keep track of when the last packet was sent. (Used for ACK). uint32 groundPacketTimer; // Counter of number of times a packet was sent. uint8 groundRetryCounter; // Back ground task state machine. uint8 groundTask; // Number of bytes in the log. uint32 groundDumpSize; // The current block number that is being processed. uint16 groundDumpBlock; // The total number of GROUND_DUMP_SIZE blocks in the log. uint16 groundTotalBlocks; /** * Get everything read for flight. */ void groundInit() { groundTask = GROUND_WAIT; groundPacketTimer = 0; groundRetryCounter = 0; groundTotalBlocks = 0; } /** * Process the command and control commands from the handheld computer. */ void groundProcessCandC() { uint8 *cncCommand, command[16]; cncCommand = cncGetRxBuffer(); // Process based on the message type. if (cncCommand[0] == CNC_MESSAGE_CMD) switch (cncCommand[1]) { // Set the telemetry rate in seconds where 0 is no telemetry. case CNC_COMMAND_TLMRATE: // Create and send the command. command[0] = COM_COMMAND_TLMRATE; command[1] = cncCommand[2]; comTxPacket (COM_MESSAGE_CMD, command, 2); break; // Enable/disable the APRS packet report. case CNC_COMMAND_APRS: // Create and send the command. command[0] = COM_COMMAND_APRS; command[1] = cncCommand[2]; comTxPacket (COM_MESSAGE_CMD, command, 2); break; // Set the flight origin. case CNC_COMMAND_SETORIGIN: // Create and send the command. command[0] = COM_COMMAND_SETORIGIN; comTxPacket (COM_MESSAGE_CMD, command, 1); break; // Log commands, dump and clear. case CNC_COMMAND_LOG: switch (cncCommand[2]) { // Send a packet to clear the log. case CNC_LOG_CLEAR: command[0] = COM_COMMAND_LOG_CLEAR; comTxPacket (COM_MESSAGE_CMD, command, 1); break; // Set the background state machine to dump the log memory. case CNC_LOG_DUMP: groundTask = GROUND_REQ_LOG_SIZE; break; } // END switch; break; case CNC_COMMAND_ZERO_INSTRUMENTS: // Create and send the command. command[0] = COM_COMMAND_ZERO_INSTRUMENTS; comTxPacket (COM_MESSAGE_CMD, command, 1); break; case CNC_COMMAND_TEST_A: // Create and send the command. command[0] = COM_COMMAND_TEST_A; comTxPacket (COM_MESSAGE_CMD, command, 1); break; case CNC_COMMAND_TEST_B: // Create and send the command. command[0] = COM_COMMAND_TEST_B; comTxPacket (COM_MESSAGE_CMD, command, 1); break; } // END switch // Clear the buffer so we can get the next message. cncClearRxBuffer(); } /** * Process the background tasks. */ void groundProcessBackGround() { uint8 command[16]; uint8 *comCommand; // Get a pointer to the com link receive buffer. comCommand = comGetRxBuffer(); // A state machine to process back ground tasks. switch (groundTask) { // Don't do anything while we wait for a task to start. case GROUND_WAIT: break; // A log dump request has been made by the CNC computer. case GROUND_REQ_LOG_SIZE: // Set the size and offset to zero to retrieve the log size. command[0] = COM_COMMAND_LOG_DUMP; *((uint32 *) (command + 1)) = 0; *((uint16 *) (command + 5)) = 0; comTxPacket(COM_MESSAGE_CMD, command, 7); // Set the state machine and timers to wait for the log size. groundTask = GROUND_WAIT_LOG_SIZE; groundPacketTimer = MS_TIMER; groundRetryCounter = 0; break; // Wait for the moble unit to return the log size. case GROUND_WAIT_LOG_SIZE: if (MS_TIMER - groundPacketTimer > GROUND_WAIT_SHORT) { // Time out if we don't get an answer to the log size query. if (groundRetryCounter++ == GROUND_RETRY_COUNT) { groundTask = GROUND_WAIT; command[0] = CNC_STATUS_TIMEOUT; cncTxPacket (CNC_MESSAGE_LOG_STATUS, command, 1); return; } // END if // Request the packet again. comRetryTxPacket(); groundPacketTimer = MS_TIMER; } // END if break; // Process the requst for the first block of data. case GROUND_START_DUMP_DATA: // We get the log size from the command ACK packet. groundDumpSize = *((uint32 *) (comCommand + 4)); // Dump one block if the log size is 0. if (groundDumpSize == 0) groundTotalBlocks = 1; else groundTotalBlocks = (uint16) (((groundDumpSize - 1) / GROUND_DUMP_SIZE) + 1); groundDumpBlock = 0; case GROUND_REQ_DUMP_DATA: // Send a log dump command. command[0] = COM_COMMAND_LOG_DUMP; *((uint32 *) (command + 1)) = groundDumpBlock * GROUND_DUMP_SIZE; *((uint16 *) (command + 5)) = GROUND_DUMP_SIZE; comTxPacket (COM_MESSAGE_CMD, command, 7); // Set the state machine to wait for the data packet, the time out timer, and the retry counter. groundTask = GROUND_WAIT_DUMP_DATA; groundPacketTimer = MS_TIMER; groundRetryCounter = 0; break; case GROUND_WAIT_DUMP_DATA: if (MS_TIMER - groundPacketTimer > GROUND_WAIT_LONG) { // Time out if we don't get an answer to the log request. if (groundRetryCounter++ == GROUND_RETRY_COUNT) { groundTask = GROUND_WAIT; command[0] = CNC_STATUS_TIMEOUT; cncTxPacket (CNC_MESSAGE_LOG_STATUS, command, 1); return; } // END if // Request the packet again. comRetryTxPacket(); groundPacketTimer = MS_TIMER; } // END if break; case GROUND_RX_DUMP_DATA: cncTxPacket (CNC_MESSAGE_CMDACK, comCommand + 1, 575); if (--groundTotalBlocks != 0) { ++groundDumpBlock; groundTask = GROUND_REQ_DUMP_DATA; sysDelay (25); } else groundTask = GROUND_WAIT; break; } // END switch } /** * Process the command received from the high speed comm link. */ void groundProcessCom() { uint8 *comCommand, cncCommand[8]; comCommand = comGetRxBuffer(); switch (comCommand[0]) { case COM_MESSAGE_TELEMETRY: cncTxPacket (CNC_MESSAGE_TELEMETRY, comCommand + 1, sizeof(TLM_STATUS)); break; case COM_MESSAGE_CMDACK: if (comCommand[3] == COM_COMMAND_LOG_DUMP && groundTask == GROUND_WAIT_LOG_SIZE) groundTask = GROUND_START_DUMP_DATA; if (comCommand[3] == COM_COMMAND_TLMRATE || comCommand[3] == COM_COMMAND_APRS) { cncCommand[0] = comCommand[4]; cncCommand[1] = comCommand[5]; cncTxPacket (CNC_MESSAGE_OPS_STATE, cncCommand, 2); } // END if break; case COM_MESSAGE_DUMPBUFER: // if (groundTask == GROUND_WAIT_DUMP_DATA) groundTask = GROUND_RX_DUMP_DATA; break; } // END switch // Clear the buffer so we can get the next message. comClearRxBuffer(); } /** * The main control loop that controls everything. */ void groundRun() { // Offset the time so we send our first ping message a second after we start. groundPingTimer = MS_TIMER - (FLIGHT_PING_TIME - 1000); // The main control loop. while (1) { // Read and process data from the handheld terminal. cncReadData(); if (cncIsRxReady()) groundProcessCandC(); // Process any waiting com link messages. if (comIsRxReady()) groundProcessCom(); // Process the back ground commands such as memory dump. groundProcessBackGround(); // Send a periodic ping message to determine if the system is alive. if (MS_TIMER - groundPingTimer > FLIGHT_PING_TIME) { // comTxPacket (COM_COMMAND_PING, comPingMessage, sizeof(comPingMessage)); groundPingTimer = MS_TIMER; } } } main() { // Initialize the system and configuration classes. These must be set for the other classes. sysInit(); configInit(); // Now initialize the rest of the classes. comInit(); cncInit(); groundInit(); // Turn on the interrupts. sysEnableInterrupt(); // Make sure everything is alright. sysSelfTest(); // Now execute the main routine groundRun(); // We should never get to this point, but if we do reset. forceSoftReset(); }