Tuesday, April 17, 2018

OLED GPS display on Arduino with a little C++

 The Arduino language is actually C++ but you never see that unless you look at library source code which tends to be a C++ class. I've written a little code to read the $4.10 GPS and display a bit of info on a small OLED display using the excellent Adafruit library.

To avoid spaghetti code I put the parsing of the NMEA string into a class. This is a quick hack but you don't see many examples like this so here you go.

Just for fun I've boxed this little project up all held together with hot glue and running from a 3.3V LiPo cell. The low price of the GPS along with the low price of an Arduino Nano Pro makes this a very attractive platform for building embedded computing devices.




/*********************************************************************
Display info from a cheap GPS on an Adafruit OLD display
*********************************************************************/

#include 
#include 
#include 
#include 

SoftwareSerial GPS(2,3); // rx pin = 2, tx pin for GPS

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

class NmeaRecord {
  public:
    NmeaRecord();
    void clearFields();
    bool parseLine(char *line); // return true if useful
    
    // GPGGA fields
    enum {
      eLabel,
      eTimeStamp,
      eLat,
      eLatNS,
      eLon,
      eLonEW,
      eFix,
      eSatellites,
      eHorizDilution,
      eAltitude,
      eHeight,
      eEmpty1,
      eEmpty2,
      eChecksum
    };
    // string lengths, +1 for null terminator
    enum {
      kLabelLen = 7,
      kTimeStampLen = 11,
      kLatLen = 9,
      kLatNSLen = 2,
      kLonLen = 10,
      kLonEWLen = 2,
      kFixLen = 2,
      kSatellitesLen = 3,
      kHorizontalDilutionLen = 4,
      kAltitudeLen = 7
    };
    char label[kLabelLen];
    char timeStamp[kTimeStampLen];
    char lat[kLatLen];
    char latNS[kLatNSLen];
    char lon[kLonLen];
    char lonEW[kLonEWLen];
    char fix[kFixLen];
    char satellites[kSatellitesLen];
    char horizontalDilution[kHorizontalDilutionLen];
    char altitude[kAltitudeLen];
  private:
    void storeField(int index, char*field);
};

NmeaRecord nmeaRecord = NmeaRecord();

void setup()   { 
  GPS.begin(9600);               
  //Serial.begin(115200);
  //Serial.println("started");
  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  // init done
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Looking for satellites...");
}


void loop() {
  
  String line = GPS.readStringUntil('$');
  
  // GPGGA,054540.000,3346.7737,S,15113.2178,E,2,12,0.99,94.2,M,21.9
  if(nmeaRecord.parseLine(line.c_str())) {
    display.clearDisplay();
    display.setCursor(0,0);
    display.print("UTC: ");
    display.print(formatTime(nmeaRecord.timeStamp));
    display.print(" sats=");
    display.println(nmeaRecord.satellites);
  
    display.print("Lat: ");
    display.print(formatLat(nmeaRecord.lat));
    display.print(",");
    display.println(nmeaRecord.latNS);
  
    display.print("Lng: ");
    display.print(formatLng(nmeaRecord.lon));
    display.print(",");
    display.println(nmeaRecord.lonEW);
  
    display.print("Alt: ");
    display.print(nmeaRecord.altitude);
    display.print("m   fix=");
    display.print(nmeaRecord.fix);
    display.display();
  }
}


NmeaRecord::NmeaRecord() {
  this->clearFields();
}

// return true if it's a useful line
bool NmeaRecord::parseLine(char *line) {
  char *token;
  token = strtok(line, ",");
  if(strcmp(token, "GPGGA") != 0) {
    return false; // don't parse
  }
  int fieldIndex = 0;
  while(token != NULL) {
    this->storeField(fieldIndex, token);
    fieldIndex++;
    token = strtok(NULL, ",");
  }
  return true;
}

void NmeaRecord::clearFields() {
  strcpy(this->label, "");
  strcpy(this->timeStamp, "");
  strcpy(this->lat, "");
  strcpy(this->latNS, "");
  strcpy(this->lon, "");
  strcpy(this->lonEW, "");
  strcpy(this->fix, "");
  strcpy(this->satellites, "0");
  strcpy(this->horizontalDilution, "");
  strcpy(this->altitude, "");
}

void NmeaRecord::storeField(int index, char*field) {
  Serial.print("Storefield index = ");
    Serial.print(index);
    Serial.print(", field = ");
    Serial.println(field);
    
  switch(index) {
    case eLabel:
      strncpy(label, field, kLabelLen);
      break;
    case eTimeStamp:
      strncpy(timeStamp, field, kTimeStampLen);
      Serial.print("got timeStamp = ");
      Serial.println(this->timeStamp);
      break;
    case eLat:
      strncpy(lat, field, kLatLen);
      break;
    case eLatNS:
      strncpy(latNS, field, kLatNSLen);
      break;
    case eLon:
      strncpy(lon, field, kLonLen);
      break;
    case eLonEW:
      strncpy(lonEW, field, kLonEWLen);
      break;
    case eFix:
      strncpy(fix, field, kFixLen);
      break;
    case eSatellites:
      strncpy(satellites, field, kSatellitesLen);
      break;
    case eHorizDilution:
      strncpy(horizontalDilution, field, kHorizontalDilutionLen);
      break;
    case eAltitude:
      strncpy(altitude, field, kAltitudeLen);
      break;
  }
}

String formatTime(String timeString) {
  String newTime = timeString.substring(0,2);
  newTime += ":";
  newTime += timeString.substring(2,4);
  newTime += ":";
  newTime += timeString.substring(4,6);
  return newTime;
}

String formatLat(String raw) {
  // 4807.038,N   Latitude 48 deg 07.038' N
  String result = raw.substring(0,2);
  result += " deg ";
  result += raw.substring(2,7);
  return result;
}

String formatLng(String raw) {
  //  01131.000,E  Longitude 11 deg 31.000' E
  String result = raw.substring(0,3);
  result += " deg ";
  result += raw.substring(3,8);
  return result;
}

Monday, April 16, 2018

The VK3ZZC "Horror" transmitter

A fond memory from my early days of ham radio was talking with Ralph, VK3ZZC, on 2m as he drifted up and down. To my amazement he was using a home brew valve transmitter he dubbed the "horror mitters".

I just noticed that he has written a post about this project here. It's worth having a look through all of Ralph's site as there's lots of good stuff.

In my view this transmitter is a thing of beauty.

(Photo from Ralph's site, used with permission).

Low cost Neo-6M GPS works well

Ross, VK1UN, tipped me off to these GPS modules available from Aliexpress for AU$4.10. At that price I ordered 2. Here's the ad as it appears at the moment.


Here's me running it via a USB serial device supplying 3.3V. The specs say it will run from 3.3 - 5V. Default baud rate is 9600 and amazingly these devices have the 1Hz PPM output which used to only be available on the more expensive modules.


With a little bit of tinkering I've now got it displaying on an OLED display. Makes a nice clock for the shack.


Amazing that a device costing $4 can receive satellites.

Sunday, April 15, 2018

Vanlife: another tour south

I'm back from a tour south from Sydney ending up in Melbourne. Gradually I'm learning how to find places to stay that I like.

My preference is for places that are very quiet and look out at water or nice bush. The most up to date reference is the WikiCamps app. (This is a very poorly designed app, but the information in it is good). Reviews of camp sites are naturally in terms of what the reviewer likes so you tend to find great reviews for crowded sites that happen to have clean toilets.

Caravan parks can be nice if the unpowered sites are well away from the powered sites where the giant caravans tend to go.

The trick seems to be to find locations on WikiCamps and then check them out from Google's satellite view to get an idea of the layout.

On this trip I met up with my sister Jane and her partner Paul who have a lovely "teardrop" trailer.


It's very compact and presumably easy to tow. You sleep inside but it's pretty cramped but comfortable.

I also met a wonderful French Canadian couple who had sailed to Australia and are now touring around in a van.


It's fascinating to see how vans are configured. They shared red wine with me which they gleefully said had come from Aldi and wasn't too bad for $5.

One trick I've discovered is sleeping in the bush and then visiting the sea for breakfast and a swim.


It's lovely sitting in the van and having vastly different views from day to day.



The configuration is gradually changing. These days, when parked, I fold the passenger seat down and place the fridge there so there's more cabin space. I have a small folding chair from Banggood and this was great on a day when there was rain.


The mosquito net (there were mosquitoes and wasps at this site) is held up with magnets.

The van is going pretty well but the oil light came on and it needed a bit over a litre of oil to be filled up again. It's now done 378,000 Km and I'm not sure if it's normal to be using some oil like this.

Tuesday, April 03, 2018

WSPR Watch iOS app updated



Since retiring from full time work over a year ago, I've had a break from iOS programming. Doing something as a job is a great way to lose interest in it as a hobby.

Recently, after a dalliance with Google's excellent cross platform framework Flutter (and the Dart language),  I decided to have a play with the latest version of Swift, 4.1. I'm pretty familiar with the Cocoa frameworks but in line with Swift conventions many of the APIs have changed. Happily, Apple's documentation browser is excellent these days.

WSPR Watch is a free app I wrote some years ago basically for myself to provide a quick way to check for WSPR (Weak Signal Propagation Reporter) signal reports from a phone without having to use a web browser.

Over the past week or so I re-wrote the app in Swift 4.1 and found this a very pleasant environment to work in. A problem with the rapid changes in the Swift language in recent years is that when you search for how to do something you'll find a version of the code that isn't quite right with the latest Swift. This problem will hopefully diminish over time.

The other thing that's changed dramatically for the better is the whole process of submitting an app to the Apple store. What used to take a week now takes hours and the process is much more straight forward than it used to be. App signing used to be a buggy mess but now seems to work reliably too.

Thanks to my beta testers and particularly Ross, VK1UN, for bug reports and feature suggestions.