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