Tuesday 22 August 2017

NEW NEW NEW!!! New GPS read code, display Lat/Lon or Maidenhead Locator, set RTC

I have a couple of new Arduino code sketches for GPS. The problem with my previous code was that it repeatedly read the GPS time, and set the RTC. This took too long and so the time display of the seconds jumped to every two seconds!

So I have completely re-written the code, it now waits for the GPS "fix", programs the RTC, gets the lat & Lon and calculates the Maidenhead Locator, then displays these, but displays the time from the programmed RTC, which then goes by seconds. Phew!!

My header file "Oled_128X64_I2C.h" has been published previously, look back and you will find it.

Just to remind you I use a VK-163 GPS (look on Amazon or ebay) which connects by a 4-way 3.5mm jack, and provides NMEA GPS data at 9600 baud. The block digram of the "VFO" I use is below, it uses an Arduino UNO with an AD9851 synthesiser and MMIC output amplifier giving up to 50mW output. The Arduino also drives an OLED 128x64 pixel display, using a U8g2lib library (look up on GitHUb). This is a great box of tricks and I can use it for many different sketches from GPS to WSPR, JT65, RTTY, PSK31, QRSS and DFCW...

Screen Shot 2017 08 31 at 09 31 29

Download here

CODE LAT & LON DISPLAY

// GPS_DATE_TIME_LATLON
// V1.0 22-8-17 after fix, set RTC, then display RTC Time & Date
// jack
// TIP +5V
// ring 13 (RX LOW, FROMGPS)
// ring 12 (TX LOW, TOGPS)
// GND

// HEADRERS & LIBRARIES
#include "Oled_128X64_I2C.h"
#include "SoftwareSerial.h"

// CONNECTIONS
#define FROMGPS 13
#define TOGPS 12
#define SW 4

// I2C COMMS DATA
// RTC address
#define RTCADDR 0x68

// OBJECTS
SoftwareSerial gps(FROMGPS, TOGPS);

// GLOBAL VARIABLES
char gpsbuf[200];   // GPS input buffer

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

// data converted to numeric (decimal)  bytes
byte hrs, mns, sec;
byte yr, mth, dy;
byte dow;
double lat, lon;

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


// SETUP
void setup() {
  // pin modes
  pinMode(FROMGPS, INPUT);
  pinMode(TOGPS, OUTPUT);
  pinMode(SW, INPUT_PULLUP);

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

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

  strcpy(fix, "V"); // init no fix
}

// LOOP
void loop() {
  dispUpdate();                // init display AWTG GPS

  do {                         // read sentence until fix confirmed
    getGPS("$GPRMC", gpsbuf);    // get $GPRMC
    fieldsGPS(gpsbuf);           // dig out ASCII fields
  } while (strcmp(fix, "A") != 0);

  convert();                 // convert Time & Date to numeric values
  dow = calcDow(yr, mth, dy);  // calulate day of week
  setRTC();                    // program RTC with GPS time

  lat = convertPos(la, ns);    // convert Lat & Lon to numeric
  lon = convertPos(lo, ew);
  findMH(mh, lat, lon);        // find Maidenhead Locator

  while (!button()) {          // update Time & Date display from RTC
    readRTC();                 // read RTC
    dispUpdate();
  }
  strcpy(fix, "V");            // button pressed restart
}

// button pressed?
bool button() {
  if (digitalRead(SW) == LOW) { // button pressed?
    while (!digitalRead(SW)); // wait for release
    return true;
  }
  else {
    return false;
  }
}

// GPS FUNCTIONS
// get sentence into buffer, for example
//   $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
void getGPS(char *sntc, char *buf) {
  do {                            // find sntc
    getline(buf);
  } while (strncmp(buf, sntc, 6) != 0);
}

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

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

// find GPS fields
void fieldsGPS(char *buf) {
  // extract strings from $GPRMC fields
  xtract(buf, 1, tm);          // time HHMMSS
  xtract(buf, 2, fix);         // fix A or V
  xtract(buf, 9, dt);          // date YYMMDD

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

// 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
}

// convert GPS ASCII Time and Date to decimal bytes
void convert() {

  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
}

// convert ASCII field starting at field pointer, to byte
byte strtob(char *field, int fp) {
  char out[20];

  strncpy(out, field + fp, 2);   // copy 2 char
  return (byte)atoi(out);        // return byte
}

// 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
}

// DATE OF WEEK CALC
// calc dow, return decimal byte, Sun = 0
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
}

// FIND MAIDENHEAD LOCATOR
void findMH(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';
}

// SET RTC
// set date and time bytes to RTC BCD
void setRTC() {
  // program RTC
  Wire.beginTransmission(RTCADDR);
  Wire.write(0);               // next input at sec register

  Wire.write(decToBcd(sec));   // set seconds
  Wire.write(decToBcd(mns));   // set minutes
  Wire.write(decToBcd(hrs));   // set hours
  Wire.write(decToBcd(dow));   // set day of week
  Wire.write(decToBcd(dy));    // set date (1 to 31)
  Wire.write(decToBcd(mth));   // set month (1-12)
  Wire.write(decToBcd(yr));    // set year (0 to 99)
  Wire.endTransmission();
}

// Convert decimal to BCD
byte decToBcd(byte dec)
{
  return ( (dec / 10 * 16) + (dec % 10) );
}

// READ RTC
void readRTC() {
  // Reset the RTC register pointer
  Wire.beginTransmission(RTCADDR);
  Wire.write(0x00);
  Wire.endTransmission();

  // request 7 bytes from the RTC address
  Wire.requestFrom(RTCADDR, 7);

  // get the time date
  sec = bcdToDec(Wire.read()); // 0 - 59
  mns = bcdToDec(Wire.read()); // 0 - 59
  hrs = bcdToDec(Wire.read() & 0b111111); // mask 12/24 bit
  dow = bcdToDec(Wire.read()); // 0 = Sunday
  dy  = bcdToDec(Wire.read()); // 1 - 31
  mth = bcdToDec(Wire.read()); // 0 = jan
  yr  = bcdToDec(Wire.read()); // ..yy
}

// Convert BCD to decimal numbers
byte bcdToDec(byte val) {
  return ( (val / 16 * 10) + (val % 16) );
}

// PICTURE LOOP
// this version displays lat/lon
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(30, 0, "GPS Lat/Lon");

    if (strcmp(fix, "A") != 0) {   // no fix?
      dispMsgL(30, 15, "AWTG GPS");
    }
    else {
      dispNum(10, 15, lat, 2);
      dispMsg(50, 15, ns);
      dispNum(70, 15, lon, 2);
      dispMsg(110, 15, ew);
      dispDate(15, 32, dow, dy, mth, yr);
      dispTimeL(25, 47, hrs, mns, sec);
    }
  } while ( oled.nextPage() );
}


CODE MAIDENHEAD LOCATOR DISPLAY
// GPS_DATE_TIME_MH
// V1.0 22-8-17 after fix, set RTC, then display RTC Time & Date
// jack
// TIP +5V
// ring 13 (RX LOW, FROMGPS)
// ring 12 (TX LOW, TOGPS)
// GND

// HEADERS & LIBRARIES
#include "Oled_128X64_I2C.h"
#include "SoftwareSerial.h"

// CONNECTIONS
#define FROMGPS 13
#define TOGPS 12
#define SW 4

// I2C COMMS DATA
// RTC address
#define RTCADDR 0x68

// OBJECTS
SoftwareSerial gps(FROMGPS, TOGPS);


// GLOBAL VARIABLES
char gpsbuf[200];   // GPS input buffer

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

// data converted to numeric (decimal)  bytes
byte hrs, mns, sec;
byte yr, mth, dy;
byte dow;
double lat, lon;

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

// SETUP
void setup() {
  // pin modes
  pinMode(FROMGPS, INPUT);
  pinMode(TOGPS, OUTPUT);
  pinMode(SW, INPUT_PULLUP);

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

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

  strcpy(fix, "V"); // init no fix
}

// LOOP
void loop() {
  dispUpdate();                // init display AWTG GPS

  do {                         // read sentence until fix confirmed
    getGPS("$GPRMC", gpsbuf);    // get $GPRMC
    fieldsGPS(gpsbuf);           // dig out ASCII fields
  } while (strcmp(fix, "A") != 0);

  convert();                 // convert Time & Date to numeric values
  dow = calcDow(yr, mth, dy);  // calulate day of week
  setRTC();                    // program RTC with GPS time

  lat = convertPos(la, ns);    // convert Lat & Lon to numeric
  lon = convertPos(lo, ew);
  findMH(mh, lat, lon);        // find Maidenhead Locator

  while (!button()) {          // update Time & Date display from RTC
    readRTC();                 // read RTC
    dispUpdate();
  }
  strcpy(fix, "V");            // button pressed restart
}

// button pressed?
bool button() {
  if (digitalRead(SW) == LOW) { // button pressed?
    while (!digitalRead(SW)); // wait for release
    return true;
  }
  else {
    return false;
  }
}

// GPS FUNCTIONS
// get sentence into buffer, for example
//   $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
void getGPS(char *sntc, char *buf) {
  do {                            // find sntc
    getline(buf);
  } while (strncmp(buf, sntc, 6) != 0);
}

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

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

// find GPS fields
void fieldsGPS(char *buf) {
  // extract strings from $GPRMC fields
  xtract(buf, 1, tm);          // time HHMMSS
  xtract(buf, 2, fix);         // fix A or V
  xtract(buf, 9, dt);          // date YYMMDD

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

// 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
}

// convert GPS ASCII Time and Date to decimal bytes
void convert() {

  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
}

// convert ASCII field starting at field pointer, to byte
byte strtob(char *field, int fp) {
  char out[20];

  strncpy(out, field + fp, 2);   // copy 2 char
  return (byte)atoi(out);        // return byte
}

// 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
}

// DATE OF WEEK CALC
// calc dow, return decimal byte, Sun = 0
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
}

// FIND MAIDENHEAD LOCATOR
void findMH(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';
}

// SET RTC
// set date and time bytes to RTC BCD
void setRTC() {
  // program RTC
  Wire.beginTransmission(RTCADDR);
  Wire.write(0);               // next input at sec register

  Wire.write(decToBcd(sec));   // set seconds
  Wire.write(decToBcd(mns));   // set minutes
  Wire.write(decToBcd(hrs));   // set hours
  Wire.write(decToBcd(dow));   // set day of week
  Wire.write(decToBcd(dy));    // set date (1 to 31)
  Wire.write(decToBcd(mth));   // set month (1-12)
  Wire.write(decToBcd(yr));    // set year (0 to 99)
  Wire.endTransmission();
}

// Convert decimal to BCD
byte decToBcd(byte dec)
{
  return ( (dec / 10 * 16) + (dec % 10) );
}

// READ RTC
void readRTC() {
  // Reset the RTC register pointer
  Wire.beginTransmission(RTCADDR);
  Wire.write(0x00);
  Wire.endTransmission();

  // request 7 bytes from the RTC address
  Wire.requestFrom(RTCADDR, 7);

  // get the time date
  sec = bcdToDec(Wire.read()); // 0 - 59
  mns = bcdToDec(Wire.read()); // 0 - 59
  hrs = bcdToDec(Wire.read() & 0b111111); // mask 12/24 bit
  dow = bcdToDec(Wire.read()); // 0 = Sunday
  dy  = bcdToDec(Wire.read()); // 1 - 31
  mth = bcdToDec(Wire.read()); // 0 = jan
  yr  = bcdToDec(Wire.read()); // ..yy
}

// Convert BCD to decimal numbers
byte bcdToDec(byte val) {
  return ( (val / 16 * 10) + (val % 16) );
}

// PICTURE LOOP
// this version displays MH
// this version displays lat/lon
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(40, 0, "GPS MH");
    
    if (strcmp(fix, "A") != 0) {   // no fix?
      dispMsgL(30, 15, "AWTG GPS");
    }
    else {
      dispMsgL(35, 15, mh);
      dispDate(15, 32, dow, dy, mth, yr);
      dispTimeL(25, 47, hrs, mns, sec);
    }
  } while ( oled.nextPage() );
}

No comments: