# HG changeset patch # User mkanning@CL-SEC241-10.cedarville.edu # Date 2012-11-14 10:21:07 # Node ID d58797c8572cd7626c0b5c738d3b36e44d0ac1d1 # Parent 469487d2238c043db5e3141dabbeae52d3d7eee2 Added APRS library files to master/lib. Also had update conflict... not sure how to handle merge. diff --git a/master/master/lib/aprs_trackuino/config.h b/master/master/lib/aprs_trackuino/config.h new file mode 100644 --- /dev/null +++ b/master/master/lib/aprs_trackuino/config.h @@ -0,0 +1,213 @@ +/* trackuino copyright (C) 2010 EA5HAV Javi + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + + +// -------------------------------------------------------------------------- +// THIS IS THE TRACKUINO FIRMWARE CONFIGURATION FILE. YOUR CALLSIGN AND +// OTHER SETTINGS GO HERE. +// +// NOTE: all pins are Arduino based, not the Atmega chip. Mapping: +// http://www.arduino.cc/en/Hacking/PinMapping +// -------------------------------------------------------------------------- + + +// -------------------------------------------------------------------------- +// APRS config (aprs.c) +// -------------------------------------------------------------------------- + +// Set your callsign and SSID here. Common values for the SSID are +// (from http://zlhams.wikidot.com/aprs-ssidguide): +// +// - Balloons: 11 +// - Cars: 9 +// - Home: 0 +// - IGate: 5 +#define S_CALLSIGN "MYCALL" +#define S_CALLSIGN_ID 11 + +// Destination callsign: APRS (with SSID=0) is usually okay. +#define D_CALLSIGN "APRS" +#define D_CALLSIGN_ID 0 + +// Digipeating paths: +// (read more about digipeating paths here: http://wa8lmf.net/DigiPaths/ ) +// The recommended digi path for a balloon is WIDE2-1 or pathless. The default +// is pathless. Uncomment the following two lines for WIDE2-1 path: +#define DIGI_PATH1 "WIDE2" +#define DIGI_PATH1_TTL 1 + +// APRS comment: this goes in the comment portion of the APRS message. You +// might want to keep this short. The longer the packet, the more vulnerable +// it is to noise. +#define APRS_COMMENT "Trackuino reminder: replace callsign with your own" + + +// -------------------------------------------------------------------------- +// AX.25 config (ax25.cpp) +// -------------------------------------------------------------------------- + +// TX delay in milliseconds +#define TX_DELAY 300 + +// -------------------------------------------------------------------------- +// Tracker config (trackuino.pde) +// -------------------------------------------------------------------------- + +// APRS packets are slotted so that multiple trackers can be used without +// them stepping on one another. The transmission times are governed by +// the formula: +// +// APRS_SLOT (seconds) + n * APRS_PERIOD (seconds) +// +// When launching multiple balloons, use the same APRS_PERIOD in all balloons +// and set APRS_SLOT so that the packets are spaced equally in time. +// Eg. for two balloons and APRS_PERIOD = 60, set APRS_SLOT to 0 and 30, +// respectively. The first balloon will transmit at 00:00:00, 00:01:00, +// 00:02:00, etc. amd the second balloon will transmit at 00:00:30, 00:01:30, +// 00:02:30, etc. +#define APRS_SLOT 0 // seconds. -1 disables slotted transmissions +#define APRS_PERIOD 60 // seconds + +// GPS baud rate (in bits per second). This is also the baud rate at which +// debug data will be printed out the serial port. +#define GPS_BAUDRATE 9600 + + +// -------------------------------------------------------------------------- +// Modem config (afsk.cpp) +// -------------------------------------------------------------------------- + +// AUDIO_PIN is the audio-out pin. The audio is generated by timer 2 using +// PWM, so the only two options are pins 3 and 11. +// Pin 11 doubles as MOSI, so I suggest using pin 3 for PWM and leave 11 free +// in case you ever want to interface with an SPI device. +#define AUDIO_PIN 3 + +// -------------------------------------------------------------------------- +// Radio config (radio_hx1.cpp) +// -------------------------------------------------------------------------- + +// This is the PTT pin +#define PTT_PIN 4 + +// -------------------------------------------------------------------------- +// Sensors config (sensors.cpp) +// -------------------------------------------------------------------------- + +// Most of the sensors.cpp functions use internal reference voltages (either +// AVCC or 1.1V). If you want to use an external reference, you should +// uncomment the following line: +// +// #define USE_AREF +// +// BEWARE! If you hook up an external voltage to the AREF pin and +// accidentally set the ADC to any of the internal references, YOU WILL +// FRY YOUR AVR. +// +// It is always advised to connect the AREF pin through a pull-up resistor, +// whose value is defined here in ohms (set to 0 if no pull-up): +// +#define AREF_PULLUP 4700 +// +// Since there is already a 32K resistor at the ADC pin, the actual +// voltage read will be VREF * 32 / (32 + AREF_PULLUP) +// +// Read more in the Arduino reference docs: +// http://arduino.cc/en/Reference/AnalogReference?from=Reference.AREF + +// Pin mappings for the internal / external temperature sensors. VS refers +// to (arduino) digital pins, whereas VOUT refers to (arduino) analog pins. +#define INTERNAL_LM60_VS_PIN 6 +#define INTERNAL_LM60_VOUT_PIN 0 +#define EXTERNAL_LM60_VS_PIN 7 +#define EXTERNAL_LM60_VOUT_PIN 1 + +// Units for temperature sensors (Added by: Kyle Crockett) +// 1 = Celsius, 2 = Kelvin, 3 = Fahrenheit +#define TEMP_UNIT 1 + +// Calibration value in the units selected. Use integer only. +#define CALIBRATION_VAL 0 + +// Resistors divider for the voltage meter (ohms) +#define VMETER_R1 10000 +#define VMETER_R2 3300 + +// Voltage meter analog pin +#define VMETER_PIN 2 + +// -------------------------------------------------------------------------- +// Buzzer config (buzzer.cpp) +// -------------------------------------------------------------------------- + +// Type of buzzer (0=active, 1=passive). An active buzzer is driven by a +// DC voltage. A passive buzzer needs a PWM signal. +#define BUZZER_TYPE 0 + +// When using a passive buzzer, specify the PWM frequency here. Choose one +// that maximizes the volume according to the buzzer's datasheet. Not all +// the frequencies are valid, check out the buzzer_*.cpp code. On Arduino, +// it must be between L and 65535, where L = F_CPU / 65535 and F_CPU is the +// clock rate in hertzs. For 16 MHz Arduinos, this gives a lower limit of +// 245 Hz. +#define BUZZER_FREQ 895 // Hz + +// These are the number of seconds the buzzer will stay on/off alternately +#define BUZZER_ON_TIME 1 // secs +#define BUZZER_OFF_TIME 2 // secs + +// This option disables the buzzer above BUZZER_ALTITUDE meters. This is a +// float value, so make it really high (eg. 1000000.0 = 1 million meters) +// if you want it to never stop buzzing. +#define BUZZER_ALTITUDE 3000.0 // meters (1 ft = 0.3048 m) + +// The options here are pin 9 or 10 +#define BUZZER_PIN 9 + +// -------------------------------------------------------------------------- +// Debug +// -------------------------------------------------------------------------- + +// This is the LED pin (13 on Arduinos). The LED will be on while the AVR is +// running and off while it's sleeping, so its brightness gives an indication +// of the CPU activity. +#define LED_PIN 13 + +// Debug info includes printouts from different modules to aid in testing and +// debugging. +// +// 1. To properly receive debug information, only connect the Arduino RX pin +// to the GPS TX pin, and leave the Arduino TX pin disconnected. +// +// 2. On the serial monitor, set the baudrate to GPS_BAUDRATE (above), +// usually 9600. +// +// 3. When flashing the firmware, disconnect the GPS from the RX pin or you +// will get errors. + +// #define DEBUG_GPS // GPS sentence dump and checksum validation +// #define DEBUG_AX25 // AX.25 frame dump +// #define DEBUG_MODEM // Modem ISR overrun and profiling +// #define DEBUG_RESET // AVR reset +// #define DEBUG_SENS // Sensors + + +#endif + diff --git a/master/master/lib/aprs_trackuino/gps.c b/master/master/lib/aprs_trackuino/gps.c new file mode 100644 --- /dev/null +++ b/master/master/lib/aprs_trackuino/gps.c @@ -0,0 +1,362 @@ +/* trackuino copyright (C) 2010 EA5HAV Javi + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "gps.h" +#include +#include + +// Module declarations +static void parse_sentence_type(const char * token); +static void parse_time(const char *token); +static void parse_status(const char *token); +static void parse_lat(const char *token); +static void parse_lat_hemi(const char *token); +static void parse_lon(const char *token); +static void parse_lon_hemi(const char *token); +static void parse_speed(const char *token); +static void parse_course(const char *token); +static void parse_altitude(const char *token); + +// Module types +typedef void (*t_nmea_parser)(const char *token); + +enum t_sentence_type { + SENTENCE_UNK, + SENTENCE_GGA, + SENTENCE_RMC +}; + + +// Module constants +static const t_nmea_parser unk_parsers[] = { + parse_sentence_type, // $GPxxx +}; + +static const t_nmea_parser gga_parsers[] = { + NULL, // $GPGGA + parse_time, // Time + NULL, // Latitude + NULL, // N/S + NULL, // Longitude + NULL, // E/W + NULL, // Fix quality + NULL, // Number of satellites + NULL, // Horizontal dilution of position + parse_altitude, // Altitude + NULL, // "M" (mean sea level) + NULL, // Height of GEOID (MSL) above WGS84 ellipsoid + NULL, // "M" (mean sea level) + NULL, // Time in seconds since the last DGPS update + NULL // DGPS station ID number +}; + +static const t_nmea_parser rmc_parsers[] = { + NULL, // $GPRMC + parse_time, // Time + parse_status, // A=active, V=void + parse_lat, // Latitude, + parse_lat_hemi, // N/S + parse_lon, // Longitude + parse_lon_hemi, // E/W + parse_speed, // Speed over ground in knots + parse_course, // Track angle in degrees (true) + NULL, // Date (DDMMYY) + NULL, // Magnetic variation + NULL // E/W +}; + +static const int NUM_OF_UNK_PARSERS = (sizeof(unk_parsers) / sizeof(t_nmea_parser)); +static const int NUM_OF_GGA_PARSERS = (sizeof(gga_parsers) / sizeof(t_nmea_parser)); +static const int NUM_OF_RMC_PARSERS = (sizeof(rmc_parsers) / sizeof(t_nmea_parser)); + +// Module variables +static t_sentence_type sentence_type = SENTENCE_UNK; +static bool at_checksum = false; +static unsigned char our_checksum = '$'; +static unsigned char their_checksum = 0; +static char token[16]; +static int num_tokens = 0; +static unsigned int offset = 0; +static bool active = false; +static char gga_time[7] = "", rmc_time[7] = ""; +static char new_time[7]; +static uint32_t new_seconds; +static float new_lat; +static float new_lon; +static char new_aprs_lat[9]; +static char new_aprs_lon[10]; +static float new_course; +static float new_speed; +static float new_altitude; + +// Public (extern) variables, readable from other modules +char gps_time[7]; // HHMMSS +uint32_t gps_seconds = 0; // seconds after midnight +float gps_lat = 0; +float gps_lon = 0; +char gps_aprs_lat[9]; +char gps_aprs_lon[10]; +float gps_course = 0; +float gps_speed = 0; +float gps_altitude = 0; + +// Module functions +unsigned char from_hex(char a) +{ + if (a >= 'A' && a <= 'F') + return a - 'A' + 10; + else if (a >= 'a' && a <= 'f') + return a - 'a' + 10; + else if (a >= '0' && a <= '9') + return a - '0'; + else + return 0; +} + +void parse_sentence_type(const char *token) +{ + if (strcmp(token, "$GPGGA") == 0) { + sentence_type = SENTENCE_GGA; + } else if (strcmp(token, "$GPRMC") == 0) { + sentence_type = SENTENCE_RMC; + } else { + sentence_type = SENTENCE_UNK; + } +} + +void parse_time(const char *token) +{ + // Time can have decimals (fractions of a second), but we only take HHMMSS + strncpy(new_time, token, 6); + // Terminate string + new_time[6] = '\0'; + + new_seconds = + ((new_time[0] - '0') * 10 + (new_time[1] - '0')) * 60 * 60UL + + ((new_time[2] - '0') * 10 + (new_time[3] - '0')) * 60 + + ((new_time[4] - '0') * 10 + (new_time[5] - '0')); +} + +void parse_status(const char *token) +{ + // "A" = active, "V" = void. We shoud disregard void sentences + if (strcmp(token, "A") == 0) + active = true; + else + active = false; +} + +void parse_lat(const char *token) +{ + // Parses latitude in the format "DD" + "MM" (+ ".M{...}M") + char degs[3]; + if (strlen(token) >= 4) { + degs[0] = token[0]; + degs[1] = token[1]; + degs[2] = '\0'; + new_lat = atof(degs) + atof(token + 2) / 60; + } + // APRS-ready latitude + strncpy(new_aprs_lat, token, 7); + new_aprs_lat[7] = '\0'; +} + +void parse_lat_hemi(const char *token) +{ + if (token[0] == 'S') + new_lat = -new_lat; + new_aprs_lat[7] = token[0]; + new_aprs_lon[8] = '\0'; +} + +void parse_lon(const char *token) +{ + // Longitude is in the format "DDD" + "MM" (+ ".M{...}M") + char degs[4]; + if (strlen(token) >= 5) { + degs[0] = token[0]; + degs[1] = token[1]; + degs[2] = token[2]; + degs[3] = '\0'; + new_lon = atof(degs) + atof(token + 3) / 60; + } + // APRS-ready longitude + strncpy(new_aprs_lon, token, 8); + new_aprs_lon[8] = '\0'; +} + +void parse_lon_hemi(const char *token) +{ + if (token[0] == 'W') + new_lon = -new_lon; + new_aprs_lon[8] = token[0]; + new_aprs_lon[9] = '\0'; +} + +void parse_speed(const char *token) +{ + new_speed = atof(token); +} + +void parse_course(const char *token) +{ + new_course = atof(token); +} + +void parse_altitude(const char *token) +{ + new_altitude = atof(token); +} + + +// +// Exported functions +// +void gps_setup() { + strcpy(gps_time, "000000"); + strcpy(gps_aprs_lat, "0000.00N"); + strcpy(gps_aprs_lon, "00000.00E"); +} + +bool gps_decode(char c) +{ + int ret = false; + + switch(c) { + case '\r': + case '\n': + // End of sentence + + if (num_tokens && our_checksum == their_checksum) { +#ifdef DEBUG_GPS + Serial.print(" (OK!) "); + Serial.print(millis()); +#endif + // Return a valid position only when we've got two rmc and gga + // messages with the same timestamp. + switch (sentence_type) { + case SENTENCE_UNK: + break; // Keeps gcc happy + case SENTENCE_GGA: + strcpy(gga_time, new_time); + break; + case SENTENCE_RMC: + strcpy(rmc_time, new_time); + break; + } + + // Valid position scenario: + // + // 1. The timestamps of the two previous GGA/RMC sentences must match. + // + // 2. We just processed a known (GGA/RMC) sentence. Suppose the + // contrary: after starting up this module, gga_time and rmc_time + // are both equal (they're both initialized to ""), so (1) holds + // and we wrongly report a valid position. + // + // 3. The GPS has a valid fix. For some reason, the Venus 634FLPX + // reports 24 deg N, 121 deg E (the middle of Taiwan) until a valid + // fix is acquired: + // + // $GPGGA,120003.000,2400.0000,N,12100.0000,E,0,00,0.0,0.0,M,0.0,M,,0000*69 (OK!) + // $GPGSA,A,1,,,,,,,,,,,,,0.0,0.0,0.0*30 (OK!) + // $GPRMC,120003.000,V,2400.0000,N,12100.0000,E,000.0,000.0,280606,,,N*78 (OK!) + // $GPVTG,000.0,T,,M,000.0,N,000.0,K,N*02 (OK!) + + if (sentence_type != SENTENCE_UNK && // Known sentence? + strcmp(gga_time, rmc_time) == 0 && // RMC/GGA times match? + active) { // Valid fix? + // Atomically merge data from the two sentences + strcpy(gps_time, new_time); + gps_seconds = new_seconds; + gps_lat = new_lat; + gps_lon = new_lon; + strcpy(gps_aprs_lat, new_aprs_lat); + strcpy(gps_aprs_lon, new_aprs_lon); + gps_course = new_course; + gps_speed = new_speed; + gps_altitude = new_altitude; + ret = true; + } + } +#ifdef DEBUG_GPS + if (num_tokens) + Serial.println(); +#endif + at_checksum = false; // CR/LF signals the end of the checksum + our_checksum = '$'; // Reset checksums + their_checksum = 0; + offset = 0; // Prepare for the next incoming sentence + num_tokens = 0; + sentence_type = SENTENCE_UNK; + break; + + case '*': + // Handle as ',', but prepares to receive checksum (ie. do not break) + at_checksum = true; + our_checksum ^= c; + + case ',': + // Process token + token[offset] = '\0'; + our_checksum ^= c; // Checksum the ',', undo the '*' + + // Parse token + switch (sentence_type) { + case SENTENCE_UNK: + if (num_tokens < NUM_OF_UNK_PARSERS && unk_parsers[num_tokens]) + unk_parsers[num_tokens](token); + break; + case SENTENCE_GGA: + if (num_tokens < NUM_OF_GGA_PARSERS && gga_parsers[num_tokens]) + gga_parsers[num_tokens](token); + break; + case SENTENCE_RMC: + if (num_tokens < NUM_OF_RMC_PARSERS && rmc_parsers[num_tokens]) + rmc_parsers[num_tokens](token); + break; + } + + // Prepare for next token + num_tokens++; + offset = 0; +#ifdef DEBUG_GPS + Serial.print(c); +#endif + break; + + default: + // Any other character + if (at_checksum) { + // Checksum value + their_checksum = their_checksum * 16 + from_hex(c); + } else { + // Regular NMEA data + if (offset < 15) { // Avoid buffer overrun (tokens can't be > 15 chars) + token[offset] = c; + offset++; + our_checksum ^= c; + } + } +#ifdef DEBUG_GPS + Serial.print(c); +#endif + } + return ret; +} + diff --git a/master/master/lib/aprs_trackuino/gps.cpp b/master/master/lib/aprs_trackuino/gps.cpp new file mode 100644 --- /dev/null +++ b/master/master/lib/aprs_trackuino/gps.cpp @@ -0,0 +1,363 @@ +/* trackuino copyright (C) 2010 EA5HAV Javi + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "gps.h" +#include +#include +#include + +// Module declarations +static void parse_sentence_type(const char * token); +static void parse_time(const char *token); +static void parse_status(const char *token); +static void parse_lat(const char *token); +static void parse_lat_hemi(const char *token); +static void parse_lon(const char *token); +static void parse_lon_hemi(const char *token); +static void parse_speed(const char *token); +static void parse_course(const char *token); +static void parse_altitude(const char *token); + +// Module types +typedef void (*t_nmea_parser)(const char *token); + +enum t_sentence_type { + SENTENCE_UNK, + SENTENCE_GGA, + SENTENCE_RMC +}; + + +// Module constants +static const t_nmea_parser unk_parsers[] = { + parse_sentence_type, // $GPxxx +}; + +static const t_nmea_parser gga_parsers[] = { + NULL, // $GPGGA + parse_time, // Time + NULL, // Latitude + NULL, // N/S + NULL, // Longitude + NULL, // E/W + NULL, // Fix quality + NULL, // Number of satellites + NULL, // Horizontal dilution of position + parse_altitude, // Altitude + NULL, // "M" (mean sea level) + NULL, // Height of GEOID (MSL) above WGS84 ellipsoid + NULL, // "M" (mean sea level) + NULL, // Time in seconds since the last DGPS update + NULL // DGPS station ID number +}; + +static const t_nmea_parser rmc_parsers[] = { + NULL, // $GPRMC + parse_time, // Time + parse_status, // A=active, V=void + parse_lat, // Latitude, + parse_lat_hemi, // N/S + parse_lon, // Longitude + parse_lon_hemi, // E/W + parse_speed, // Speed over ground in knots + parse_course, // Track angle in degrees (true) + NULL, // Date (DDMMYY) + NULL, // Magnetic variation + NULL // E/W +}; + +static const int NUM_OF_UNK_PARSERS = (sizeof(unk_parsers) / sizeof(t_nmea_parser)); +static const int NUM_OF_GGA_PARSERS = (sizeof(gga_parsers) / sizeof(t_nmea_parser)); +static const int NUM_OF_RMC_PARSERS = (sizeof(rmc_parsers) / sizeof(t_nmea_parser)); + +// Module variables +static t_sentence_type sentence_type = SENTENCE_UNK; +static bool at_checksum = false; +static unsigned char our_checksum = '$'; +static unsigned char their_checksum = 0; +static char token[16]; +static int num_tokens = 0; +static unsigned int offset = 0; +static bool active = false; +static char gga_time[7] = "", rmc_time[7] = ""; +static char new_time[7]; +static uint32_t new_seconds; +static float new_lat; +static float new_lon; +static char new_aprs_lat[9]; +static char new_aprs_lon[10]; +static float new_course; +static float new_speed; +static float new_altitude; + +// Public (extern) variables, readable from other modules +char gps_time[7]; // HHMMSS +uint32_t gps_seconds = 0; // seconds after midnight +float gps_lat = 0; +float gps_lon = 0; +char gps_aprs_lat[9]; +char gps_aprs_lon[10]; +float gps_course = 0; +float gps_speed = 0; +float gps_altitude = 0; + +// Module functions +unsigned char from_hex(char a) +{ + if (a >= 'A' && a <= 'F') + return a - 'A' + 10; + else if (a >= 'a' && a <= 'f') + return a - 'a' + 10; + else if (a >= '0' && a <= '9') + return a - '0'; + else + return 0; +} + +void parse_sentence_type(const char *token) +{ + if (strcmp(token, "$GPGGA") == 0) { + sentence_type = SENTENCE_GGA; + } else if (strcmp(token, "$GPRMC") == 0) { + sentence_type = SENTENCE_RMC; + } else { + sentence_type = SENTENCE_UNK; + } +} + +void parse_time(const char *token) +{ + // Time can have decimals (fractions of a second), but we only take HHMMSS + strncpy(new_time, token, 6); + // Terminate string + new_time[6] = '\0'; + + new_seconds = + ((new_time[0] - '0') * 10 + (new_time[1] - '0')) * 60 * 60UL + + ((new_time[2] - '0') * 10 + (new_time[3] - '0')) * 60 + + ((new_time[4] - '0') * 10 + (new_time[5] - '0')); +} + +void parse_status(const char *token) +{ + // "A" = active, "V" = void. We shoud disregard void sentences + if (strcmp(token, "A") == 0) + active = true; + else + active = false; +} + +void parse_lat(const char *token) +{ + // Parses latitude in the format "DD" + "MM" (+ ".M{...}M") + char degs[3]; + if (strlen(token) >= 4) { + degs[0] = token[0]; + degs[1] = token[1]; + degs[2] = '\0'; + new_lat = atof(degs) + atof(token + 2) / 60; + } + // APRS-ready latitude + strncpy(new_aprs_lat, token, 7); + new_aprs_lat[7] = '\0'; +} + +void parse_lat_hemi(const char *token) +{ + if (token[0] == 'S') + new_lat = -new_lat; + new_aprs_lat[7] = token[0]; + new_aprs_lon[8] = '\0'; +} + +void parse_lon(const char *token) +{ + // Longitude is in the format "DDD" + "MM" (+ ".M{...}M") + char degs[4]; + if (strlen(token) >= 5) { + degs[0] = token[0]; + degs[1] = token[1]; + degs[2] = token[2]; + degs[3] = '\0'; + new_lon = atof(degs) + atof(token + 3) / 60; + } + // APRS-ready longitude + strncpy(new_aprs_lon, token, 8); + new_aprs_lon[8] = '\0'; +} + +void parse_lon_hemi(const char *token) +{ + if (token[0] == 'W') + new_lon = -new_lon; + new_aprs_lon[8] = token[0]; + new_aprs_lon[9] = '\0'; +} + +void parse_speed(const char *token) +{ + new_speed = atof(token); +} + +void parse_course(const char *token) +{ + new_course = atof(token); +} + +void parse_altitude(const char *token) +{ + new_altitude = atof(token); +} + + +// +// Exported functions +// +void gps_setup() { + strcpy(gps_time, "000000"); + strcpy(gps_aprs_lat, "0000.00N"); + strcpy(gps_aprs_lon, "00000.00E"); +} + +bool gps_decode(char c) +{ + int ret = false; + + switch(c) { + case '\r': + case '\n': + // End of sentence + + if (num_tokens && our_checksum == their_checksum) { +#ifdef DEBUG_GPS + Serial.print(" (OK!) "); + Serial.print(millis()); +#endif + // Return a valid position only when we've got two rmc and gga + // messages with the same timestamp. + switch (sentence_type) { + case SENTENCE_UNK: + break; // Keeps gcc happy + case SENTENCE_GGA: + strcpy(gga_time, new_time); + break; + case SENTENCE_RMC: + strcpy(rmc_time, new_time); + break; + } + + // Valid position scenario: + // + // 1. The timestamps of the two previous GGA/RMC sentences must match. + // + // 2. We just processed a known (GGA/RMC) sentence. Suppose the + // contrary: after starting up this module, gga_time and rmc_time + // are both equal (they're both initialized to ""), so (1) holds + // and we wrongly report a valid position. + // + // 3. The GPS has a valid fix. For some reason, the Venus 634FLPX + // reports 24 deg N, 121 deg E (the middle of Taiwan) until a valid + // fix is acquired: + // + // $GPGGA,120003.000,2400.0000,N,12100.0000,E,0,00,0.0,0.0,M,0.0,M,,0000*69 (OK!) + // $GPGSA,A,1,,,,,,,,,,,,,0.0,0.0,0.0*30 (OK!) + // $GPRMC,120003.000,V,2400.0000,N,12100.0000,E,000.0,000.0,280606,,,N*78 (OK!) + // $GPVTG,000.0,T,,M,000.0,N,000.0,K,N*02 (OK!) + + if (sentence_type != SENTENCE_UNK && // Known sentence? + strcmp(gga_time, rmc_time) == 0 && // RMC/GGA times match? + active) { // Valid fix? + // Atomically merge data from the two sentences + strcpy(gps_time, new_time); + gps_seconds = new_seconds; + gps_lat = new_lat; + gps_lon = new_lon; + strcpy(gps_aprs_lat, new_aprs_lat); + strcpy(gps_aprs_lon, new_aprs_lon); + gps_course = new_course; + gps_speed = new_speed; + gps_altitude = new_altitude; + ret = true; + } + } +#ifdef DEBUG_GPS + if (num_tokens) + Serial.println(); +#endif + at_checksum = false; // CR/LF signals the end of the checksum + our_checksum = '$'; // Reset checksums + their_checksum = 0; + offset = 0; // Prepare for the next incoming sentence + num_tokens = 0; + sentence_type = SENTENCE_UNK; + break; + + case '*': + // Handle as ',', but prepares to receive checksum (ie. do not break) + at_checksum = true; + our_checksum ^= c; + + case ',': + // Process token + token[offset] = '\0'; + our_checksum ^= c; // Checksum the ',', undo the '*' + + // Parse token + switch (sentence_type) { + case SENTENCE_UNK: + if (num_tokens < NUM_OF_UNK_PARSERS && unk_parsers[num_tokens]) + unk_parsers[num_tokens](token); + break; + case SENTENCE_GGA: + if (num_tokens < NUM_OF_GGA_PARSERS && gga_parsers[num_tokens]) + gga_parsers[num_tokens](token); + break; + case SENTENCE_RMC: + if (num_tokens < NUM_OF_RMC_PARSERS && rmc_parsers[num_tokens]) + rmc_parsers[num_tokens](token); + break; + } + + // Prepare for next token + num_tokens++; + offset = 0; +#ifdef DEBUG_GPS + Serial.print(c); +#endif + break; + + default: + // Any other character + if (at_checksum) { + // Checksum value + their_checksum = their_checksum * 16 + from_hex(c); + } else { + // Regular NMEA data + if (offset < 15) { // Avoid buffer overrun (tokens can't be > 15 chars) + token[offset] = c; + offset++; + our_checksum ^= c; + } + } +#ifdef DEBUG_GPS + Serial.print(c); +#endif + } + return ret; +} + diff --git a/master/master/lib/aprs_trackuino/gps.h b/master/master/lib/aprs_trackuino/gps.h new file mode 100644 --- /dev/null +++ b/master/master/lib/aprs_trackuino/gps.h @@ -0,0 +1,40 @@ +/* trackuino copyright (C) 2010 EA5HAV Javi + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __GPS_H__ +#define __GPS_H__ + +#include +#include +#include +#include + +extern char gps_time[7]; // HHMMSS +extern uint32_t gps_seconds; // seconds after midnight +extern char gps_date[7]; // DDMMYY +extern float gps_lat; +extern float gps_lon; +extern char gps_aprs_lat[9]; +extern char gps_aprs_lon[10]; +extern float gps_course; +extern float gps_speed; +extern float gps_altitude; + +void gps_setup(); +bool gps_decode(char c); + +#endif diff --git a/master/master/lib/aprs_trackuino/trackuino.pde b/master/master/lib/aprs_trackuino/trackuino.pde new file mode 100644 --- /dev/null +++ b/master/master/lib/aprs_trackuino/trackuino.pde @@ -0,0 +1,142 @@ +/* trackuino copyright (C) 2010 EA5HAV Javi + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// Mpide 22 fails to compile Arduino code because it stupidly defines ARDUINO +// as an empty macro (hence the +0 hack). UNO32 builds are fine. Just use the +// real Arduino IDE for Arduino builds. Optionally complain to the Mpide +// authors to fix the broken macro. +#if (ARDUINO + 0) == 0 +#error "Oops! We need the real Arduino IDE (version 22 or 23) for Arduino builds." +#error "See trackuino.pde for details on this" + +// Refuse to compile on arduino version 21 or lower. 22 includes an +// optimization of the USART code that is critical for real-time operation +// of the AVR code. +#elif (ARDUINO + 0) < 22 +#error "Oops! We need Arduino 22 or 23" +#error "See trackuino.pde for details on this" + +// Arduino 1.0+ introduced backwards-incompatible changes in the serial lib. +#elif (ARDUINO + 1) >= 100 +#error "Ooops! We don't support Arduino 1.0+ (yet). Please use 22 or 23" +#error "See trackuino.pde for details on this" + +#endif + + +// Trackuino custom libs +#include "config.h" +#include "afsk_avr.h" +#include "afsk_pic32.h" +#include "aprs.h" +#include "buzzer.h" +#include "gps.h" +#include "pin.h" +#include "power.h" +#include "sensors_avr.h" +#include "sensors_pic32.h" + +// Arduino/AVR libs +#include +#include + +// Module constants +static const uint32_t VALID_POS_TIMEOUT = 2000; // ms + +// Module variables +static int32_t next_aprs = 0; + + +void setup() +{ + pinMode(LED_PIN, OUTPUT); + pin_write(LED_PIN, LOW); + + Serial.begin(GPS_BAUDRATE); +#ifdef DEBUG_RESET + Serial.println("RESET"); +#endif + + buzzer_setup(); + afsk_setup(); + gps_setup(); + sensors_setup(); + +#ifdef DEBUG_SENS + Serial.print("Ti="); + Serial.print(sensors_int_lm60()); + Serial.print(", Te="); + Serial.print(sensors_ext_lm60()); + Serial.print(", Vin="); + Serial.println(sensors_vin()); +#endif + + // Do not start until we get a valid time reference + // for slotted transmissions. + if (APRS_SLOT >= 0) { + do { + while (! Serial.available()) + power_save(); + } while (! gps_decode(Serial.read())); + + next_aprs = millis() + 1000 * + (APRS_PERIOD - (gps_seconds + APRS_PERIOD - APRS_SLOT) % APRS_PERIOD); + } + else { + next_aprs = millis(); + } + // TODO: beep while we get a fix, maybe indicating the number of + // visible satellites by a series of short beeps? +} + +void get_pos() +{ + // Get a valid position from the GPS + int valid_pos = 0; + uint32_t timeout = millis(); + do { + if (Serial.available()) + valid_pos = gps_decode(Serial.read()); + } while ( (millis() - timeout < VALID_POS_TIMEOUT) && ! valid_pos) ; + + if (valid_pos) { + if (gps_altitude > BUZZER_ALTITUDE) { + buzzer_off(); // In space, no one can hear you buzz + } else { + buzzer_on(); + } + } +} + +void loop() +{ + // Time for another APRS frame + if ((int32_t) (millis() - next_aprs) >= 0) { + get_pos(); + aprs_send(); + next_aprs += APRS_PERIOD * 1000L; + while (afsk_busy()) ; + power_save(); + +#ifdef DEBUG_MODEM + // Show modem ISR stats from the previous transmission + afsk_debug(); +#endif + } + + power_save(); // Incoming GPS data or interrupts will wake us up +}