GPS Remote Data Collection
with Arduino
d. bodnar   revised 02-21-2015

For the last few weeks I have been experimenting with two interesting devices.  The first is a small (about 7/8" x 5/8" x 1/8") RF transceiver that is described in some detail here.   I have found the range to be excellent for such a small device.

The other device is a small GPS receiver that can simultaneously track as many as 12 satellites.  It has only 4 pins, VCC, GND, TX and RX.  The data from the unit is sent via serial with a default speed of 9600 baud.  This is described here.  Note that the software uses the GPS at 4800 baud.  Changing this rate is described here.

Objective
My primary objective for this exercise it to place the GPS outside, with a clear view of the sky, and have the transceiver send the position data into the house for analysis.

This will also allow me to develop routines to collect and transmit other data with the RF units.

Schematic
The schematic shows how I connected them together along with an LCD display to show the data that is received.  This, of course, is not needed on the transmitter version.  Pins A4 and A5 are not shown on the drawing as they are not on the two edges of the board.  On most Arduino Mini's they are either at one of the board or just inside of A2 and A3.
Two voltage regulators were used, one 3.3 volt and one 5 volt.  This was done to accommodate the LCD display which will work at 3.3 volts, but without much contrast.  Running it from 5 volts resolved the issue.
The LEDs are optional.   One blinks when packets are received while the other lights when 3 or more satellites are being tracked.
The antenna is a 3.2" piece of wire.  It works best if a like-sized wire is connected to ground and aligned opposite to it (i.e. the antenna wire goes up, the ground wire goes down below it).

 

The completed receive unit is shown here.  The transceiver is circled in red.  The LCD shows data from the transmitter that is sending GPS info and temperature.
Alt = altitude, Avg = average altitude, Sat = # of satellites being tracked, hdop is horizontal dilution of precision (accuracy) in 1/100ths, the next line is longitude and latitude, count is the number of sent packets and temp is temperature.

 Software - Transmitter  GPS_Receive_RF-pair-v4.ino
#include <RFM69.h>   // for some reason this needs to be first or will not compile!

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#include <SPI.h>
#include <SPIFlash.h>


#define I2C_ADDR    0x27 // <<----- Add your address here.  Find it from I2C Scanner
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7



#define NODEID      99
#define NETWORKID   100
#define GATEWAYID   1
#define FREQUENCY   RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define KEY         "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define LED         9
#define LED2        8
#define SERIAL_BAUD 9600
#define ACK_TIME    30  // # of ms to wait for an ack


#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 3 on the Arduino
#define ONE_WIRE_BUS 5

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
float ftemp = 0;
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);


int TRANSMITPERIOD = 300; //transmit a packet to gateway so often (in ms)
byte sendSize = 0;
boolean requestACK = false;
//SPIFlash flash(8, 0xEF40); //was 30 EF40 for 16mbit windbond chip
RFM69 radio;

typedef struct {
  int           nodeId; //store this nodeId
  unsigned long uptime; //uptime in ms
  long temp; //float         temp;   //temperature maybe?
  int AAltitude;
  float LLat;
  float LLon;
  float TTemperature;
  int SendCount;
}
Payload;
Payload theData;

int CCounter = 0;


int potPin = A3;    // select the input pin for the potentiometer
int potValue = 0;  // variable to store the value coming from the pot
byte buffer[10];
LiquidCrystal_I2C	lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);

/*
   This sample code demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/
static const int RXPin = 3, TXPin = 4;
static const uint32_t GPSBaud = 4800;

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

void setup()
{
  pinMode(LED2, OUTPUT);
  digitalWrite(LED2, LOW);
  lcd.begin (20, 4); //  <<----- My LCD was 16x2
  lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE); // Switch on the backlight
  lcd.setBacklight(HIGH);
  lcd.home (); // go home
  lcd.print("GPS Test Display");
  lcd.setCursor(0, 1);
  lcd.print("d. bodnar 02-17-15");
  lcd.setCursor(0, 2);
  lcd.print("Units = MPH & Feet");
  Serial.begin(115200);
  ss.begin(GPSBaud);

  radio.initialize(FREQUENCY, NODEID, NETWORKID);
  radio.setHighPower(); //uncomment only for RFM69HW!
  radio.encrypt(KEY);
  char buff[50];
  sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY == RF69_433MHZ ? 433 : FREQUENCY == RF69_868MHZ ? 868 : 915);
  Serial.println(buff);
  sensors.begin();
  // if (flash.initialize())
  //    Serial.println("SPI Flash Init OK!");
  // else
  //    Serial.println("SPI Flash Init FAIL! (is chip present?)");



  Serial.println(F("FullExample.ino"));
  Serial.println(F("An extensive example of many interesting TinyGPS++ features"));
  Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println(F("by Mikal Hart"));
  Serial.println();
  Serial.println(F("Sats HDOP Latitude   Longitude   Fix  Date       Time     Date Alt    Course Speed Card  Distance Course Card  Chars Sentences Checksum"));
  Serial.println(F("          (deg)      (deg)       Age                      Age  (m)    --- from GPS ----  ---- to London  ----  RX    RX        Fail"));
  Serial.println(F("---------------------------------------------------------------------------------------------------------------------------------------"));
}
long lastPeriod = -1;
void loop()
{
  static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002;

  printInt(gps.satellites.value(), gps.satellites.isValid(), 5);
  printInt(gps.hdop.value(), gps.hdop.isValid(), 5);
  printFloat(gps.location.lat(), gps.location.isValid(), 11, 6);
  printFloat(gps.location.lng(), gps.location.isValid(), 12, 6);
  printInt(gps.location.age(), gps.location.isValid(), 5);
  printDateTime(gps.date, gps.time);
  printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2);
  printFloat(gps.course.deg(), gps.course.isValid(), 7, 2);
  printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2);
  printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.value()) : "*** ", 6);


  unsigned long distanceKmToLondon =
    (unsigned long)TinyGPSPlus::distanceBetween(
      gps.location.lat(),
      gps.location.lng(),
      LONDON_LAT,
      LONDON_LON) / 1000;
  printInt(distanceKmToLondon, gps.location.isValid(), 9);

  double courseToLondon =
    TinyGPSPlus::courseTo(
      gps.location.lat(),
      gps.location.lng(),
      LONDON_LAT,
      LONDON_LON);

  printFloat(courseToLondon, gps.location.isValid(), 7, 2);

  const char *cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon);

  printStr(gps.location.isValid() ? cardinalToLondon : "*** ", 6);

  printInt(gps.charsProcessed(), true, 6);
  printInt(gps.sentencesWithFix(), true, 10);
  printInt(gps.failedChecksum(), true, 9);
  // Serial.println();
  Serial.print(" 6 dig test "); Serial.println(gps.location.lat(), 6);
  smartDelay(1000);

  if (millis() > 5000 && gps.charsProcessed() < 10)
    Serial.println(F("No GPS data received: check wiring"));

  lcd.clear();
  float Satellites = gps.satellites.value();
  lcd.setCursor(0, 0); lcd.print("Sat.= "); lcd.print(Satellites, 0);
  lcd.setCursor(9, 0);
  float hdop = gps.hdop.value(); lcd.print("hdop= "); lcd.print(hdop, 0);
  lcd.setCursor(0, 1);
  float SpeedM  = gps.speed.mps();
  lcd.print("Speed= "); lcd.print(SpeedM, 1);
  lcd.setCursor(0, 2);
  float altitude = gps.altitude.feet();

  lcd.print("alt.= "); lcd.print(altitude, 0);
  lcd.setCursor(0, 3);
  float latitude = gps.location.lat();
  lcd.print(latitude, 6);
  lcd.setCursor(10, 3);
  float longitude = gps.location.lng();
  lcd.print(longitude, 6);
  if (Satellites >= 3)
    digitalWrite(LED2, HIGH);
  else
    digitalWrite(LED2, LOW);

  if (Serial.available() > 0)
  {
    char input = Serial.read();
    if (input >= 48 && input <= 57) //[0,9]
    {
      TRANSMITPERIOD = 100 * (input - 48);
      if (TRANSMITPERIOD == 0) TRANSMITPERIOD = 1000;
      Serial.print("\nChanging delay to ");
      Serial.print(TRANSMITPERIOD);
      Serial.println("ms\n");
    }

    if (input == 'r') //d=dump register values
      radio.readAllRegs();
    //if (input == 'E') //E=enable encryption
    //  radio.encrypt(KEY);
    //if (input == 'e') //e=disable encryption
    //  radio.encrypt(null);

    /*  if (input == 'd') //d=dump flash area
      {
        Serial.println("Flash content:");
        int counter = 0;

        while (counter <= 256) {
          Serial.print(flash.readByte(counter++), HEX);
          Serial.print('.');
        }
        while (flash.busy());
        Serial.println();
      }
      if (input == 'e')
      {
        Serial.print("Erasing Flash chip ... ");
        flash.chipErase();
        while (flash.busy());
        Serial.println("DONE");
      }
      if (input == 'i')
      {
        Serial.print("DeviceID: ");
        word jedecid = flash.readDeviceId();
        Serial.println(jedecid, HEX);
      }
      */
  }


  //check for any received packets
  if (radio.receiveDone())
  {
    Serial.print('[');
    Serial.print(radio.SENDERID, DEC);
    Serial.print("] ");
    //for (byte i = 0; i < radio.DATALEN; i++)    //These lines were removed to prevent a compile error
    //  Serial.print((char)radio.DATA[i]);       // due to LCD library incompatibility with the RF library
    Serial.print("   [RX_RSSI:");
    Serial.print(radio.readRSSI());
    Serial.print("]");

    if (radio.ACKRequested())
    {
      radio.sendACK();
      Serial.print(" - ACK sent");
      delay(10);
    }
    Blink(LED, 5);
    Serial.println();
  }



  // Serial.print("Requesting temperatures...");
  sensors.requestTemperatures(); // Send the command to get temperatures
  // Serial.println("DONE");

  Serial.print("Temperature for the device 1 (index 0) is: ");
  Serial.print(sensors.getTempCByIndex(0));
  ftemp = sensors.getTempCByIndex(0);
  ftemp = ftemp * 9;
  ftemp = ftemp / 5;
  ftemp = ftemp + 32;
  Serial.print(" F= ");
  Serial.println(ftemp);
  int currPeriod = millis() / TRANSMITPERIOD;
  if (currPeriod != lastPeriod)
  {
    //fill in the struct with new values
    theData.nodeId = Satellites;
    theData.uptime = Satellites; // working
    theData.temp = hdop ; //hdop works
    theData.AAltitude = altitude;
    theData.LLat = latitude;
    theData.LLon = longitude;
    theData.TTemperature = ftemp;
    CCounter = ++CCounter  ;
    theData.SendCount = CCounter;
    //  float LLat;
    //  float LLon;

    //    Serial.print("Sending struct (");
    //    Serial.print(sizeof(theData));
    //    Serial.print(" bytes) ... ");
    if (radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)))
      //      Serial.print(" ok!");
      //    else Serial.print(" nothing...");
      //    Serial.println();
      Blink(LED, 3);
    lastPeriod = currPeriod;
  }


}

// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}

static void printFloat(float val, bool valid, int len, int prec)
{
  if (!valid)
  {
    while (len-- > 1)
      Serial.print('*');
    Serial.print(' ');
  }
  else
  {
    Serial.print(val, prec);
    int vi = abs((int)val);
    int flen = prec + (val < 0.0 ? 2 : 1); // . and -
    flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i = flen; i < len; ++i)
      Serial.print(' ');
  }
  smartDelay(0);
}

static void printInt(unsigned long val, bool valid, int len)
{
  char sz[32] = "*****************";
  if (valid)
    sprintf(sz, "%ld", val);
  sz[len] = 0;
  for (int i = strlen(sz); i < len; ++i)
    sz[i] = ' ';
  if (len > 0)
    sz[len - 1] = ' ';
  Serial.print(sz);
  smartDelay(0);
}

static void printDateTime(TinyGPSDate &d, TinyGPSTime &t)
{
  if (!d.isValid())
  {
    Serial.print(F("********** "));
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
    Serial.print(sz);
  }

  if (!t.isValid())
  {
    Serial.print(F("******** "));
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
    Serial.print(sz);
  }

  printInt(d.age(), d.isValid(), 5);
  smartDelay(0);
}

static void printStr(const char *str, int len)
{
  int slen = strlen(str);
  for (int i = 0; i < len; ++i)
    Serial.print(i < slen ? str[i] : ' ');
  smartDelay(0);
}


void Blink(byte PIN, int DELAY_MS)
{
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN, HIGH);
  delay(DELAY_MS);
  digitalWrite(PIN, LOW);
}

 

 

 Software - Receiver GPS_Receive_RF-pair-LCD-v5.ino
/*
Note that the LCD library for I2C is not the original one but from http://www.divshare.com/download/launch/24151116-7dc
the original had a conflict with the RF library

*/


#include <RFM69.h>



#include <LiquidCrystal_I2C.h>;
//#include <LCD.h>//
#include <SPI.h>
#include <SPIFlash.h>

#include <Wire.h>


#define I2C_ADDR    0x27 // <<----- Add your address here.  Find it from I2C Scanner
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

#define NODEID      1
#define NETWORKID   100
#define FREQUENCY   RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
#define KEY         "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
#define LED         9
#define LED2        8
#define SERIAL_BAUD 9600
#define ACK_TIME    30  // # of ms to wait for an ack

RFM69 radio;
//SPIFlash flash(8, 0xEF40); //was 30 EF40 for 16mbit windbond chip
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network

typedef struct {
  int           nodeId; //store this nodeId
  unsigned long uptime; //uptime in ms
  long temp; //float         temp;   //temperature maybe?
  int AAltitude;
  float LLat;
  float LLon;
  float TTemperature;
  int SendCount;
}
Payload;
Payload theData;
long AvgAltitude = 0;
int Readings = 0;
int MINhdop = 999;
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 16 chars and 2 line display


void setup() {
  pinMode(LED2, OUTPUT);
  digitalWrite(LED2, LOW);

  lcd.init();                      // initialize the lcd

  // Print a message to the LCD.
  lcd.backlight();
  lcd.home (); // go home
  lcd.print("GPS Test Display");
  lcd.setCursor(0, 1);
  lcd.print("d. bodnar 02-17-15");
  lcd.setCursor(0, 2);
  lcd.print("Units = MPH & Feet");;
  Serial.begin(SERIAL_BAUD);
  delay(10);
  radio.initialize(FREQUENCY, NODEID, NETWORKID);
  radio.setHighPower(); //uncomment only for RFM69HW!
  radio.encrypt(KEY);
  radio.promiscuous(promiscuousMode);
  char buff[50];
  sprintf(buff, "\nListening at %d Mhz...", FREQUENCY == RF69_433MHZ ? 433 : FREQUENCY == RF69_868MHZ ? 868 : 915);
  Serial.println(buff);
  // if (flash.initialize())
  //   Serial.println("SPI Flash Init OK!");
  //  else
  // Serial.println("SPI Flash Init FAIL! (is chip present?)");
}

byte ackCount = 0;
void loop() {
  //process any serial input
  if (Serial.available() > 0)
  {
    char input = Serial.read();
    if (input == 'r') //d=dump all register values
      radio.readAllRegs();
    if (input == 'E') //E=enable encryption
      radio.encrypt(KEY);
    if (input == 'e') //e=disable encryption
      radio.encrypt(null);
    if (input == 'p')
    {
      promiscuousMode = !promiscuousMode;
      radio.promiscuous(promiscuousMode);
      Serial.print("Promiscuous mode ");
      Serial.println(promiscuousMode ? "on" : "off");
    }

    /*  if (input == 'd') //d=dump flash area
      {
        Serial.println("Flash content:");
        int counter = 0;

        while (counter <= 256) {
          Serial.print(flash.readByte(counter++), HEX);
          Serial.print('.');
        }
        while (flash.busy());
        Serial.println();
      }
      if (input == 'D')
      {
        Serial.print("Deleting Flash chip content... ");
        flash.chipErase();
        while (flash.busy());
        Serial.println("DONE");
      }
      if (input == 'i')
      {
        Serial.print("DeviceID: ");
        word jedecid = flash.readDeviceId();
        Serial.println(jedecid, HEX);
      }
      */
  }

  if (radio.receiveDone())
  {
    //   Serial.print('[');
    //   Serial.print(radio.SENDERID, DEC);
    //    Serial.print("] ");
    //    Serial.print(" [RX_RSSI:");
    //    Serial.print(radio.readRSSI());
    //   Serial.print("]");
    if (promiscuousMode)
    {
      Serial.print("to [");
      Serial.print(radio.TARGETID, DEC);
      Serial.print("] ");
    }

    if (radio.DATALEN != sizeof(Payload))
      Serial.print("Invalid payload received, not matching Payload struct!");
    else
    {
      theData = *(Payload*)radio.DATA; //assume radio.DATA actually contains our struct and not something else
      //     Serial.print(" nodeId=");
      //      Serial.print(theData.nodeId);
      int Sats = theData.uptime;
      Serial.print(" Satellites=");
      Serial.print(theData.uptime);
      Serial.print(" hdop=");
      Serial.print(theData.temp);
      if (theData.temp <= MINhdop & theData.temp != 0)
        MINhdop = theData.temp;
      Serial.print(" MINhdop=");
      Serial.print(MINhdop);
      Serial.print("  Altitude=");
      Serial.print(theData.AAltitude);
      AvgAltitude = AvgAltitude + theData.AAltitude;
      Readings = ++Readings;
      int AvgAlt = AvgAltitude / Readings;
      Serial.print(" Av-alt ");
      Serial.print(AvgAlt);
      Serial.print("  Lat=");
      Serial.print(theData.LLat, 6);
      Serial.print("  Lon=");
      Serial.print(theData.LLon, 6);
      Serial.print("  Ftemp=");
      Serial.print(theData.TTemperature, 1);
      Serial.print(" Count=");
      Serial.print(theData.SendCount);
      if (Sats >= 3)
        digitalWrite(LED2, HIGH);
      else
        digitalWrite(LED2, LOW);
      lcd.clear();

      lcd.print("Alt="); lcd.print(theData.AAltitude); lcd.print("  Avg="); lcd.print(AvgAlt);
      lcd.setCursor(0, 1);
      lcd.print("Sat= "); lcd.print(theData.uptime); lcd.print("  hdop= "); lcd.print(theData.temp);
      lcd.setCursor(0, 2);
      lcd.print(theData.LLat, 6); lcd.print(" "); lcd.print(theData.LLon, 6);
      lcd.setCursor(0, 3);
      lcd.print("Cnt "); lcd.print(theData.SendCount); lcd.print("  temp="); lcd.print(theData.TTemperature, 1);

    }

    if (radio.ACKRequested())
    {
      byte theNodeID = radio.SENDERID;
      radio.sendACK();
      //    Serial.print(" - ACK sent.");

      // When a node requests an ACK, respond to the ACK
      // and also send a packet requesting an ACK (every 3rd one only)
      // This way both TX/RX NODE functions are tested on 1 end at the GATEWAY
      /*      if (ackCount++%3==0)
            {
              Serial.print(" Pinging node ");
              Serial.print(theNodeID);
              Serial.print(" - ACK...");
              delay(3); //need this when sending right after reception .. ?
              if (radio.sendWithRetry(theNodeID, "ACK TEST", 8, 0, 50))  // 0 = only 1 attempt, no retries; wait up to 50mS
                Serial.print("ok!");
              else Serial.print("nothing");
            }
            */
    }
    Serial.println();
    Blink(LED, 300);
  }
}

void Blink(byte PIN, int DELAY_MS)
{
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN, HIGH);
  delay(DELAY_MS);
  digitalWrite(PIN, LOW);
}