Week 11: Interfaces
The Task
Write an application that interfaces with an input and/or output device that you made.
My Idea
I want to keep working on my final project - for this week, I want to write the code that instructs the module to log output every 10 seconds, spit that data out, then parse it into a GPX file for use in Strava.
Planning
A very nice thing about the Ultimate GPS module is that it includes a built-in logger that by default logs every 15 seconds in when it is instructed to begin. The logger has 32KB of flash memory - because each store is 15 bytes, this means that in total, the logger can hold about 16 hours of data at this rate. My goal is to figure out how to log in 5 second intervals - I don’t need to store that much because runs only take ~1 hr, so the logger should realistically be able to log multiple running sessions no matter what before syncing with my computer and dumping the data. Control of the logger is documented here.
Now, all that needs to be done is the actual parsing of documentation and coding.
The Code
It turns out that sending custom (i.e., not predefined by the Adafruit GPS
module) involves a really weird checksum to be added after the command sequence.
The syntax is $<command>*<checksum>
where the checksum is the hexadecimal of
the XORs of all off the bytes between $
and *
. There is a handy calculator
for calculating these checksums.
Further, there is a discrepancy in the notation for commands - it seems that some commands work when there is a comma between the starting letters of each packet (PMTK) and the packet name (seems to always be a three-digit number).I struggled for quite a while figuring this out.
After a bunch of trial and error, I was able to write a program that logs the output of the GPS module every 5 seconds instead of the default 15 seconds. Further, when the letter ‘d’ is inputted, the log is serial dumped (note that there is an option to dump all memory and an option to dump only used memory, and I choose to do the latter in my code). Everytime the letter ‘e’ is inputted, the logger is erased to make room for more logging.
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(3, 2);
Adafruit_GPS GPS(&mySerial);
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO true
// this keeps track of whether we're using the interrupt
// off by default!
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy
void setup()
{
// connect at 115200 so we can read the GPS fast enough and
// also spit it out
Serial.begin(115200);
while (!Serial);
delay(1000);
Serial.println("Adafruit GPS logging start test!");
// 9600 NMEA is the default baud rate for MTK - some use 4800
GPS.begin(9600);
// You can adjust which sentences to have the module emit, below
// Default is RMC + GGA
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
// Default is 1 Hz update rate
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_OFF);
// Sets 5 second logging interval
GPS.sendCommand("$PMTK187,1,5*38");
// the nice thing about this code is you can have a timer0 interrupt go off
// every 1 millisecond, and read data from the GPS for you. that makes the
// loop code a heck of a lot easier!
useInterrupt(true);
delay(500);
Serial.print("\nSTARTING LOGGING....");
if (GPS.LOCUS_StartLogger())
Serial.println(" STARTED!");
else
Serial.println(" no response :(");
delay(1000);
}
void loop() // run over and over again
{
// displays statistics
GPS.sendCommand("$PMTK183*38");
delay(10000);
}
/******************************************************************/
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
char c = GPS.read();
// if you want to debug, this is a good time to do it!
if (GPSECHO && c) {
#ifdef UDR0
UDR0 = c;
// writing direct to UDR0 is much much faster than Serial.print
// but only one character can be written at a time.
#else
Serial.write(c);
#endif
}
if (Serial.available()){
char command = Serial.read();
// dump data
if (command == 'd'){
GPS.sendCommand("$PMTK622,1*29");
}
// erase all data
else if (command == 'e'){
GPS.sendCommand("$PMTK184,1*22");
}
}
}
void useInterrupt(boolean v) {
if (v) {
// Timer0 is already used for millis() - we'll just interrupt somewhere
// in the middle and call the "Compare A" function above
OCR0A = 0xAF;
TIMSK0 |= _BV(OCIE0A);
usingInterrupt = true;
} else {
// do not call the interrupt function COMPA anymore
TIMSK0 &= ~_BV(OCIE0A);
usingInterrupt = false;
}
}
The following is an example of a line of the output. It is in a strange hexadecimal format that must be decoded. `$PMTKLOX,1,5,9825FE5B,04FF7829,42C23B8E,C24A000F,A725FE5B,04D77829,42BE3B8E,C2470069,B625FE5B,04DE7829,42BF3B8E,C2440073,CC25FE5B,04197929,42B93B8E,C24300CE,DB25FE5B,04EC7829,42BE3B8E,C242002B,EA25FE5B,04EB7829,42BF3B8E,C242001C*24
Here is what it looks like on the Arduino serial monitor:
The next step is writing a Python script that parses this output and turns it into GPX files. I used tkinter to do this - when the “dump” button is pressed, data is dumped. When the “erase” button is pressed, data is completely erased from the GPS module’s logger.
Here is the code:
import serial
import time
import sys
import math
import tkinter as tk
port = sys.argv[1]
ser = serial.Serial(port, 115200)
root = tk.Tk()
root.title("Welcome to your GPS!")
root.geometry("300x200")
frame = tk.Frame(root)
frame.pack()
def dump():
ser.write(str.encode('d'))
shit = ser.read_until(str.encode("$PMTK001,622,3*36\r\n"))
print(shit)
def erase():
ser.write(str.encode('e'))
print("erased all parses")
dump_button = tk.Button(frame, text="dump", command=dump)
dump_button.pack(side=tk.LEFT)
erase_button = tk.Button(frame, text="erase all", command=erase)
erase_button.pack(side = tk.RIGHT)
root.mainloop()
This interface is going to get a lot prettier with the end of my final project - stay tuned for cool updates! :)