Wednesday 16 August 2017

Some Arduino routines for GPS

There are some very cheap GPS receivers on the market today, notably GPS originally designed for plugging into dash-cams. These receivers have a 4-way 3.5mm jack connection, and send out 9600 baud serial data in ASCII format.

You can also buy very cheap Arduino Nano boards and cheap OLED 128x64 pixel displays.

Putting this all together you can have a useful tool for amateur radio to display your latitude, longitude, Maidenhead Locator, date and time. Here are few of the code snippets useful for handling the data

HEADER definitions, libraries, defines and variables

#include "Oled_128X64_I2C.h"
#include "SoftwareSerial.h"
#include "Wire.h"

// GPS connections,
#define FROMGPS 12
#define TOGPS 13
#define SW 4

// GPS data buffer
char gpsbuf[200];

// data extracted from $GPRMC, ACSII
char tm[20];        // time HHMMSS
char fix[5];        // fix A|V, init void
char dt[20];        // date YYMMDD
char la[15];        // latitude
char ns[2];         // NS
char lo[15];        // longitude
char ew[2];         // EW

// Maidenhead Locator
char mh[10] = "";

// Date, Time Lat & lon decimal
byte hrs, mns, sec;
byte yr, mth, dy;
byte dow;
double lat, lon;

// Serial object RX TX
SoftwareSerial gps(FROMGPS, TOGPS);


CODE for setup
void setup() {
  // pins
  pinMode(FROMGPS, INPUT);
  pinMode(TOGPS, OUTPUT);
  pinMode(SW, INPUT_PULLUP);

  // I2C init
  Wire.begin();

  // OLED init, I2C addr 0x3C
  oled.begin();

  // GPS serial init
  gps.begin(9600);

  strcpy(fix, "V");
  dispUpdate();
}


The basic loop CODE
void loop() {
  getGPS();                      // get GPS, extract data

  if (strcmp(fix, "A") == 0) {   // when GPS Aquired
    getDateTime();
    getMH();
    dispUpdate();
  }
}

And finally the functions
// get RMC line data
void getGPS() {
  do {
    getline(gpsbuf);
  } while (strncmp(gpsbuf, "$GPRMC", 6) != 0);

  // extract strings from $GPRMC fields
  xtract(gpsbuf, 1, tm);          // time HHMMSS
  xtract(gpsbuf, 2, fix);         // fix A or V
  xtract(gpsbuf, 9, dt);          // date YYMMDD

  xtract(gpsbuf, 3, la);          // latitude
  xtract(gpsbuf, 4, ns);          // NS
  xtract(gpsbuf, 5, lo);          // longitude
  xtract(gpsbuf, 6, ew);          // EW
}

// get a line from the GPS, inc /r/n, add /0
void getline(char *out) {
  char c;
  int p;

  p = 0;                         // buffer pointer
  do {
    if (gps.available() > 0) {   // data?
      c = gps.read();            // read character
      out[p++] = c;              // put in buffer
    }
  } while ( c != '\n' );          // stop on /n
  out[p] = '\0';                 // terminate string
}

// extract field and return string in outbuf
void xtract(char *in, int field, char *out) {
  int ip = 0;                    // input buffer pointer
  int op = 0;                    // output buffer pointer
  int f = 0;                     // field counter

  while (f < field) {            // find start of field, ip
    while (in[ip++] != ',');
    f++;
  }

  while (in[ip] != ',')  {      // scan to next ','
    out[op++] = in[ip++];       // copy in to out
  }
  out[op] = '\0';               // terminate out string
}

// ================ Date & Time, Dow =================
void getDateTime() {
  // get GPS data in bytes, calc dow
  hrs = strtob(tm, 0);           // HH....
  mns = strtob(tm, 2);           // ..MM..
  sec = strtob(tm, 4);           // ....SS
  dy  = strtob(dt, 0);           // DD....
  mth = strtob(dt, 2);           // ..MM..
  yr  = strtob(dt, 4);           // ....YY
  dow = calcDow(yr, mth, dy);

}

// convert ASCII (0-99), starting at bp, to byte
byte strtob(char *in, int bp) {
  char out[20];

  strncpy(out, in + bp, 2);     // copy 2 char
  return (byte)atoi(out);       // return byte
}

// calc dow
byte calcDow(byte year, byte month, byte day)
{
  unsigned long days;
  unsigned int febs;
  unsigned int months[] =
  {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
  };

  days = year * 365;   // days up to year

  febs = year;
  if (month > 2) febs++; // number of completed Februaries

  // add in the leap days
  days += ((febs + 3) / 4);
  days -= ((febs + 99) / 100);
  days += ((febs + 399) / 400);

  days += months[month - 1] + day;

  return (byte)(((days + 5) % 7)); // sun = 0
}

// ====================Maidenhead functions================
// calculate maideng=head locator from lat & lon
void getMH() {
  // extract lat * lon from GPS data
  xtract(gpsbuf, 3, la);
  xtract(gpsbuf, 4, ns);
  xtract(gpsbuf, 5, lo);
  xtract(gpsbuf, 6, ew);

  lat = convertPos(la, ns);
  lon = convertPos(lo, ew);

  calcMH(mh, lat, lon);
}

// convert Lat, Lon strings to decimal +/-NS|EW
double convertPos(char *pos, char *d) {
  double pp, mm, ans;
  int dd;

  pp = atof(pos);                        // get in decimal ddmm.mmmmmmm
  dd = (int)pp / 100;                    // get degrees part
  mm = pp - (100 * dd);                  // get minutes
  ans = dd + (double)mm / 60.0;          // calc decimal degrees

  if (strcmp(d, "N") == 0 || strcmp(d, "E") == 0)  // if positive
    return ans;
  else
    return - ans; // negative
}

// calc MH from lat & lon
void calcMH(char *dst, double fa, double fo) {
  int a1, a2, a3;
  int o1, o2, o3;
  double rd;

  // Latitude
  rd = fa + 90.0;
  a1 = (int)(rd / 10.0);
  rd = rd - (double)a1 * 10.0;
  a2 = (int)(rd);
  rd = rd - (double)a2;
  a3 = (int)(24.0 * rd);

  // Longitude
  rd = fo + 180.0;
  o1 = (int)(rd / 20.0);
  rd = rd - (double)o1 * 20.0;
  o2 = (int)(rd / 2.0);
  rd = rd - 2.0 * (double)o2;
  o3 = (int)(12.0 * rd);

  dst[0] = (char)o1 + 'A';
  dst[1] = (char)a1 + 'A';
  dst[2] = (char)o2 + '0';
  dst[3] = (char)a2 + '0';
  dst[4] = (char)o3 + 'A';
  dst[5] = (char)a3 + 'A';
  dst[6] = '\0';
}


// ============Picture Display ===============
// picture loop
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(55, 0, "GPS");

    if (strcmp(fix, "V") == 0) {
      dispMsgL(10, 25, "NO GPS");
    }
    else if (strcmp(fix, "A") == 0) {
      dispMsg(10, 12, ns);
      dispNum(25, 12, lat, 2);
      dispMsg(75, 12, ew);
      dispNum(90, 12, lon, 2);

      dispMsg(45, 25, mh);
      dispDate(15, 37, dow, dy, mth, yr);
      dispTime(35, 52, hrs, mns, sec);
    }
  } while ( oled.nextPage() );
}

No comments: