//
// 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();
}