Friday, 22 September 2017

LATEST Sketches and Libraries

Download.

VFO talks to PA for Band & TXRX

I have developed a way for my VFO to send data to my PA to tell it to switch bands or RX/TX mode. Both VFO & PA have a 4-way jack connection, which is connected to the Arduino UNO like this GND-pin12-pin13-+5V, +5V is on the tip.

In the VFO code is used to output a serial 9600 data transmission on pin 12 as a command to the PA. This is simply encoded 1 = 40m, 2 = 30m, 3 = 20m, 4 = Transmit, 5 = Receive. The PA receives the serial data on pin 12 and either switches the band replays of the LPFs, or changes the antenna switch from TX to RX.

CODE for both SERIAL OUT and SERIAL IN

// SERIALOUT output serial integer on TX 12
// V1.0 21-9-2017 use with SERIALIN on 2nd Arduino
// connect with Jack <-> Jack

// HEADRES & LIBRARIES
#include "SoftwareSerial.h"
#include "Oled.h"

// CONNECTIONS
// TX on 12 for VFO to send commands
#define TX 12
#define RX 13

// OBJECTS
SoftwareSerial out(RX, TX);

// GLOBAL VARIABLES
int band;

// SETUP
void setup() {
  out.begin(9600);
  while (!Serial);

  oled.begin();
}

// LOOP - test output 1,2,3,4
void loop() {
  // seq 1-3 band no. out, 4 = TX, 5 = RX
  for (band = 1; band < 7; band++) {
    out.println(band);
    dispUpdate();
    delay(2000);
  }
}

// PICTURE LOOP
// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsg(20, 20, "Band");
    dispNumL(70, 20, band, 0);
  } while ( oled.nextPage() );
}


// SERIALIN input an integer on RX 12
// V1.0 21-9-2017 use with SERIALOUT on source Arduino
// connect with Jack <-> Jack

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

// CONNECTIONS
// RX on 12 for PA commands, VFO sends commands on TX = 12
#define RX 12
#define TX 13


// OBJECTS
SoftwareSerial in(RX, TX);

//GLOBAL VARIABLES
int band = 1;  // 1 = 40m, 2 = 30m, 3 = 20m
bool TxRx = false; // RX

// SETUP
void setup() {
  in.begin(9600);
  while (!Serial);

  oled.begin();

  dispUpdate();
}

// LOOP
void loop() {
  if(GetBus()) {
    // change band
    // switch TXRX
  }
  dispUpdate();
}

// GETBUS
// bus input for Band or TXRX?
bool GetBus() {
  int cmd;
  
  if (in.available()) {
    if ((cmd = getInt()) != false) {
      switch (cmd) {
        case 1:        // change band
        case 2:
        case 3:
          band = cmd;
          break;
        case 4:
          TxRx = true; // TX
          break;
        case 5:
          TxRx = false;  // RX
          break;
        default:
          band = 1;
          TxRx = false;
          break;
      }
    }
    return true;
  }
  return false;
}

// GETINT
// get an integer from serial input pin RX (12)
int getInt() {

  if (in.available()) {
    return in.parseInt();
  }
  else
    return false;
}

// PICTURE LOOP
// picture loop, display init data
void dispUpdate() {
  oled.firstPage();
  do {
    dispMsgL(20, 20, "Band");
    if (band < 4)
      dispNumL(70, 20, band, 0);
    if (!TxRx)
      dispMsgL(40, 50, "RX");
    else
      dispMsgL(40, 50, "TX");
  } while ( oled.nextPage() );
}

Sunday, 10 September 2017

LATEST Sketches and libraries

Download here.

The fight to read an ASCII string into Arduino

If you have a terminal program sending characters of a string input at 9600 baud to the USB port of an Arduino, then they arrive in just over 1ms per character. In 1ms the Arduino can do a lot of things.

So a program like this won't work,

//  GET  MESSAGE
// Try to get a message in from the terminal
void getMsg(char *m) }
  int p;
  
  p = 0;
  while (Serial.available() > 0) {
   m[p++] = Serial.read();
  }
}

Why? Because after the first character arrives and Serial.Available() says it has done so, the next line Serial.read() happens very fast, a long time before the next character at 9600 baud arrives. So the next pass of the while loop fails, as there is no character in the RAM buffer.

What to do? Well wait for the next character before trying to read it, of course. Like this

// GET MESSAGE
// get line m (EOL '\n')
bool getMsg(char *m) {
  int p;

  if (Serial.available() > 0) {                 // data available ?
    p = 0;
    while ((m[p] = Serial.read()) != '\n') {    // end of line?
      p++;
      while (!Serial.available());              // wait for next char
    }
    m[p] = '\0';                                // terminate string
    return true;                                // data received
  }
  return false;                                 // no data
}

Here you can see that after the first iteration of the while loop and the Serial.read() we wait for the next character to arrive.

The next problem is to know when all the string's characters have been read, and this is done by marking the end of the input with a special character - in this case the program expects a LF or '\n'. You must make sure your terminal is set up to end the line it sends with this character.

Screen Shot 2017 09 10 at 17 13 15

Saturday, 2 September 2017

Got a new toy - USB Scope

Elektor magazine just offered me a deal I could not refuse. A SmartScope from LabNation. This is a 100ms/s sampling scope with software for PC & MacOS. Here the display of a 7.1MHz output from my VFO.

IMG 1453

Screen Shot 2017 09 02 at 14 57 14

Thursday, 24 August 2017

LATEST Sketches and libraries

Here's the 24 Aug 2017 update of all my current sketches and libraries. Mainly tidying up and checking functionality on both AD9851 & Si5351 synthesiser platforms.

Download

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() );
}