DIY Arduino APRS Tracker

Since I wanted to have an APRS tracker, that I can use with any radio, but didn’t want to spend a lot of money, I found a project called MicroModem. It uses a R2R resistor ladder for audio output and if you want to add a display, it can even decode AFSK1200 packets. I took the existing APRS library that was developed for this board.

Since I used GPS for another AVR project already, I included the GPS library from Adafruit again.

After putting everything together on a breadboard, i did a first test and realized, that the audio was somehow interrupted by something. When commenting out the GPS part of the code, everything worked perfectly… except having a hard coded position. So it was time to hook up the oscilloscope to the audio out pin and the GPS RX pin.

photo_2016-09-09_15-39-59

yellow: audio | blue: gps serial data

You can see, that the parser for the received NMEA messages is clearly slowing down the audio, when a messaged comes it, even without using interrupts for parsing. The simpliest solution that came to my mind, was to disable the serial port right before TX function and reactivate again afterwards. This only worked for the first message, but if a second message came in before the transmission stopped, it was interrupted there too. Luckily libAPRS uses an LED, that indicates transmission. So i simply wait for the LED to turn of until I activate the serial port again to avoid delay functions.

yellow: audio | blue: gps serial data

yellow: audio | blue: gps serial data

Finally it was working, so I decided to make an Arduino shield for testing it portably, but I want to build a standalone board soon.

img_20160909_151944

I added a jumper to pin 8 to switch between two different APRS SSIDs & symbols for portable and mobile (car) usage. The pinout of the header pins from top to bottom was intended to be audio out, gnd, gnd, PTT, Vin, but sadly my TYT TH-9800 doesn’t provide enough current to power the whole board, so I removed the Vin connection again and power the board over USB, but left the header as it is, in case I need it in the future. The upper left ‘reset’ button was rewired to pin 2 in case i need that too.

I actually wanted to put locationUpdate(); in the ISR, but it doesn’t seem to work inside any ISR, even if I use a timer with a lower priority or disable interrupts while transmitting. However instead of using a delay function or a counter to schedule the transmission, i decided to use the most accurate way – GPS time. Every 30th second of a minute (or any other chosen value) it will transmit audio.

#include <LibAPRS.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

// gps serial TX to pin 12
SoftwareSerial gpsSerial(12, 11);

Adafruit_GPS GPS(&gpsSerial);
#define OPEN_SQUELCH false
#define ADC_REFERENCE REF_5V

char lastsec = 0;

void setup() {
  Serial.begin(115200);

  pinMode( 8, INPUT_PULLUP );
  pinMode( 13, OUTPUT );
  
  APRS_init(ADC_REFERENCE, OPEN_SQUELCH);
  
  // jumper settings
  if( digitalRead(8) == LOW ){
  // jumper to GND for mobile usage with car symbol
    APRS_setCallsign("NOCALL", 9);
    APRS_setSymbol('>');
  } else {
  // jumper HIGH by internal pullup resistor for portable usage
    APRS_setCallsign("NOCALL", 7);
    APRS_setSymbol('[');
  }
  
  Serial.println("MicroModem APRS Tracker");
  GPS.begin(9600);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PGCMD_ANTENNA);

  // enable tim0 interrupt at value 0x7F
  OCR0A = 0x7F;
  TIMSK0 |= _BV(OCIE0A);
  
  delay(1000);
  gpsSerial.println(PMTK_Q_RELEASE);
}

// tim0 ISR for gps parser
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))
      return;
  }
}

void locationUpdate() {
  char lats[10],latvs[10];
  char lons[10],lonvs[10];
  
  // the only way i found to set the variables the correct way for libAPRS
  // since sprintf doesn't like float on AVRs
  dtostrf(GPS.latitude, 6, 2, latvs);
  dtostrf(GPS.longitude, 6, 2, lonvs);
      
  sprintf(lats, "%s%c", latvs, GPS.lat);
  if( GPS.longitude >= 10000 ){
    sprintf(lons, "%s%c", lonvs, GPS.lon);
  } else {
    sprintf(lons, "0%s%c", lonvs, GPS.lon);
  }
    
  APRS_setLat(lats);
  APRS_setLon(lons);

  char *comment = "Arduino APRS Tracker";
      
  // turn off soft serial to stop interrupting tx
  gpsSerial.end();

  // TX
  APRS_sendLoc(comment, strlen(comment));

  // read blue TX LED pin and wait till TX has finished (PB1, Pin 9)
  while(bitRead(PORTB,1));

  //start soft serial again
  GPS.begin(9600);
}

void loop() {
  if( GPS.fix ){
    // transmit every 30th second of a minute
    if ( (( GPS.seconds == 30 ) && ( lastsec != GPS.seconds )) ) {
      locationUpdate();
    }
  } else {
    Serial.println("no GPS fix");
    delay(1000);
  }
  lastsec = GPS.seconds;
}

// need to keep that unless i edit libAPRS (fucnt. only needed for rx, but compiler wants it)
void aprs_msg_callback(struct AX25Msg *msg) {
}

I want to mention, that i slightly modified libAPRS to stop the RX interrupts if the ADC gets some floating values. Just comment out the following line in AFSK.cpp: AFSK_adc_isr(AFSK_modem, ((int16_t)((ADC) >> 2) – 128));