Final Project

My Idea

Everytime I go on a run, I’m super annoyed that I have to carry around my phone to track my run. People have weird arm bands to hold their phones, but that has always looked awkward to me.

If you don’t want to carry around a phone but you still want to track your runs, you have to carry around a device that has a dedicated GPS. For instance, Garmin running watches can do the job. However, the cheapest devices with GPS are ~$170. GPS technology can be had for less than $25, which means this technology is more accessible than you might imagine. One use case that I imagine is a high school track or cross country team - when I did cross country in high school, everyone wanted a running watch but only the most serious/wealthy students on the team would get one.

For my final project, I want to create a really portable and cheap GPS watch. It could be used for runs, bike rides, or even just to get data about where you are at all times in an open way. Hooking the device up to a laptop will allow data ETL that will allow for upload to Strava, etc..

Picking Parts

GPS module

I browsed tons of different parts online. Aliexpress sells a lot of really cheap components, on the order of ~$5. However, the shipping time is up to a month, and there is bad data support.

I ended up asking other students in the class for their recommendations. Someone strongly recommended Adafruit’s Ultimate GPS Module They were $29.95 each plus shipping, minus a $5 coupon code and I ended up buying two. The great thing about these GPS modules is that there is tons of support for them. There is an Arduino library that allows for logic to be easily implemented. There’s also a Python module for easily parsing the data. Finally, the module is super light - it’s only 4 grams. A typical GPS watch weights well over 70g, which means that we’re off to a good start for weight.

Battery

I decided to go with a Lithium Poly 110maH instead of a traditional coin battery. I like the rechargability. I link to this later on.

Breakout Board

When the parts arrived, I realized that I had made a sort of time-costly mistake by buying just the components and not the breakout board (which would have cost an extra $10). This means that if I want to do prototyping without completing destroying my GPS module by soldering and unsoldering it repeatedly, I would need to make my own breakout board to do testing on.

I had a lot of trouble figuring out what what making a breakout board entails. Because we always make boards with microcontrollers and all components soldered onto said board, I didn’t really understand the concept at all.

I began by looking at the Eagle schematic that Adafruit provides, documenting it’s breakout board - this is a really nice thing for them to do considering that it probably loses them money. The breakout board was pretty complicated, consisting of many fancy component setups for voltage regulation and the like.

Adafruit breakout board
pic

I decided to vastly simplify the board, just for the sake of simplicity. I ended up not using any sort of voltage regulation, in hopes that I don’t screw myself over at some point and fry my module.

I have design files for these, but there’s no point in linking them because I go on to create a way better board that could also theoretically serve as a breakout board.

This is what the board looks like after milling: Breakout board soldered

The next step was to test it. I used an Arduino Uno and jumper wires to test the pins.

Unfortunately, I ran into an issue after creating this breakout board, which should have been very good, which prevented me from testing my homemade breakout board. I ripped the FTDI header off of the breakout board, and then I was unable to desolder the GPS component, despite generously using the heat gun. Very sad.

Between this event and the next time that I thought about my final project came make big things week and some other hefty weeks. Before I knew it, it was Thanksgiving break and I needed to go home. I wanted to work on my project, though, so I resorted to buying the Ultimate GPS Module breakout board. In hindsight, I probably should have just bought this board to begin with. I wouldn’t have been able to practically use the second GPS module that I bought for the purposes of a breakout board since that would require another desoldering, which was painful.

Over Thanksgiving break, I was able to hook up the Ultimate GPS Module Breakout Board (created by Lady Ada herself and co.) an Arduino Uno to figure out how to program it. It was during this week that I figured out the programming aspects, which I will talk about next.

The setup looked like this:

arduino setup

Regarding that pesky GPS module component, eventually, Rob helped me desolder the component. Use solder wick to remove all solder that you can before using heat gun to remove a component. This was a valuable lesson that served me well in later weeks. Also, turned out that the GPS module was actually fine. It worked on a test board that I later used.

Programming

Programming a ATmega328P

If you want to program the ATMega328P bare, it’s important to have the Arduino settings correct. You might think that you can automatically use the Arduino/Genuino Uno board option since that Arduino uses the ATMega328P. However, it turns out that that is not true because that requires an external oscillator of 16MHz. If you don’t want to go through the hassle of putting this component on your board, you cannot use that Arduino setting.

What I ended up doing was installing some boards that are specific to the ATMega328P from Github. They can be found here, and the instructions to install are relatively simple and explained in the README file. The default clock on the ATMega328P is 8Mhz. Thus, I put my settings on internal 8Mhz and the rest you can see from this image:

Arduino settings

This was smooth sailing.

Programming on Mac

I realized just during final project development that it is not that hard to program boards on Mac. All that needs to be done is installation of avrdude and some FTDI library. THis an all be Googled, but FTDI instructions are here and avrdude instructions are here. Pretty convenient, although usbtiny programmers seem to have issues with me for some reason. Ended up doing a lot of programming on the GalliumOS Linux machine in the Harvard shop.

GPS stuff

The core thing that I want to do in my project is save GPS coordinates during an event like a run or bike ride. To this end, I knew that I would need to figure out where on the board to store this information. It turns out that there is a eally efficient way to store GPS data in a format called NMEA. This data format is super interesting - every recording is only 15 bytes - this includes time, elevation, latitude, longitude, and more. The result is that it’s possible to store a lot in a little. Originally, I planned to use some sort of memory solution that would give me megabytes of data. However, I realized that it would be way easier to use the onboard logging feature that the Ultimate GPS module already has. 32KB stores about 2000 recordings, which is plenty for many runs. The default recording interval is 10 seconds. I ended up changing this to 5 seconds in order to get better data.

I was able to get this working using the Adafruit breakout board connected to an Arduino, with an interface. This is documented here. Further, I created an interface to automate the data dumping and erasing from the logger. THis is again documented here.

My final GPS-related code doesn’t look like what it does documented here. I will include all of my code at the end of this section, though.

Another necessity is the conversion from NMEA format to a format that is common in the GPS world. It turns out that this format is called GPX. Unfortunately, there was no existing library that converts NMEA outputs to GPX formate (which is an XML-like format). This gave me the fun job of writing one myself! Luckily, there was a NMEA to JSON converter. I converted NMEA to JSON, then JSON to GPX. All my code can be found here on my Github! I hope that this will be useful for someone else who is hoping to use this library. Maybe Adafruit would find this useful.

OLED

I thought it would be boring for what I made to simply record stuff and not show anything. I wasn’t sure what I wanted to show, but I figured something at all would be nice aesthetically and practically. I decided to buy an OLED screen, which we heard about during the Output Devices week in class. The one I bought is called the DIYMall 0.96” BLue and Yellow I2C OLED screen and can be purchased on Amazon here for 9 bucks, at least when I purchased in around November of 2018. Apparently these prices have been going down throughout history, so this is a very good thing.

Programming the OLED turned out to be relatively straightforward. Adafruit sells their own OLED screens that are of this format, and they have a bunch of libraries that make putting stuff on OLEDs super easy. The requirements are installation of a library called Adafruit_SSD1306, which can be found here. It’s also necessary to install a library called Adafruit GFX, which can be done straight from the Arduino IDE. From here, using the example code called ssd1306_128x64_i2c makes life super simple for programming. One important thing to note is that you must switch the register that the display uses from 0x3D to 0x3C, or else it will not program! I learned this the hard way and had to do a bunch of fiddling and Google-foo.

Another thing that is super important to note that if you are programming on a microcontroller that has practical RAM limitations, it will probably be necessary for you to not take full advantage of the very nice 128x64 screen because it simply takes up too much ram since each pixel must be put in a buffer. For instance, it is impossible to have both a 128x64 screen as well as SoftwareSerial going on an ATMega328P because of RAM limitations (2K SRAM). I simply set SCREEN_HEIGHT to 32. This let me have enough RAM to have Software Serial communication with the GPS module. This was a super weird bug though! It came up when I was trying to integrate both OLED and the GPS module, both of which were working very well individually. Then, all of a sudden, the OLED was failing to initialize.

I got excited the first time that I got the OLED working, to say the least:

iamgod

Another thing to note is that the default code in the example code is super big. You don’t need all that code in order to do basic things like display a string, which is probably most people’s use cases.

I do highly recommend this component, though. Super easy from a programming perspective.

This is most of the stuff I had to worry about whilst coding. Flash memory wasn’t too important since the ATMega328P has 32KB. I ended up using around 22KB. My main Arduino file was this:

// Test code for Adafruit GPS modules using MTK3329/MTK3339 driver
//
// This code shows how to listen to the GPS module in an interrupt
// which allows the program to have more 'freedom' - just parse
// when a new NMEA sentence is available! Then access data when
// desired.
//
// Tested and works great with the Adafruit Ultimate GPS module
// using MTK33x9 chipset
//    ------> http://www.adafruit.com/products/746
// Pick one up today at the Adafruit electronics shop
// and help support open source hardware & software! -ada

#include <Adafruit_GPS.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SoftwareSerial.h>

// Declarations for OLED
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// If you're using a GPS module:
// Connect the GPS Power pin to 5V
// Connect the GPS Ground pin to ground
// If using software serial (sketch example default):
//   Connect the GPS TX (transmit) pin to Digital 3
//   Connect the GPS RX (receive) pin to Digital 2
// If using hardware serial (e.g. Arduino Mega):
//   Connect the GPS TX (transmit) pin to Arduino RX1, RX2 or RX3
//   Connect the GPS RX (receive) pin to matching TX1, TX2 or TX3

// If you're using the Adafruit GPS shield, change
// SoftwareSerial mySerial(3, 2); -> SoftwareSerial mySerial(8, 7);
// and make sure the switch is set to SoftSerial

// If using software serial, keep this line enabled
// (you can change the pin numbers to match your wiring):
SoftwareSerial mySerial(A2, A1);

// If using hardware serial (e.g. Arduino Mega), comment out the
// above SoftwareSerial line, and enable this line instead
// (you can change the Serial number to match your wiring):

//HardwareSerial mySerial = Serial1;


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

int count = 0;

void setup()
{

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);

  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time

  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  GPS.sendCommand("$PMTK187,1,5*38");

  if (GPS.LOCUS_StartLogger())
    Serial.println(" STARTED!");
  else
    Serial.println(" no response :(");

  // Request updates on antenna status, comment out to keep quiet
  GPS.sendCommand(PGCMD_ANTENNA);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
    display.display();
    delay(2000);
    display.clearDisplay();
  }

  // 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(1000);
  // Ask for firmware version
  mySerial.println(PMTK_Q_RELEASE);
}


// 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!
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;
    // writing direct to UDR0 is much much faster than Serial.print
    // but only one character can be written at a time.
#endif
}

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

uint32_t timer = millis();
void loop()                     // run over and over again
{
  // in case you are not using the interrupt above, you'll
  // need to 'hand query' the GPS, not suggested :(
  if (! usingInterrupt) {
    // read data from the GPS in the 'main loop'
    char c = GPS.read();
    // if you want to debug, this is a good time to do it!
    if (GPSECHO)
      if (c) Serial.print(c);
  }

  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trytng to print out data
    //Serial.println(GPS.lastNMEA());   // this also sets the newNMEAreceived() flag to false

    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
  }

  // if millis() or timer wraps around, we'll just reset it
  if (timer > millis())  timer = millis();

  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 2000) {
    count++;
    timer = millis(); // reset the timer

    Serial.print("Fix: "); Serial.print((int)GPS.fix);
    Serial.print(" quality: "); Serial.println((int)GPS.fixquality);
    if (GPS.fix) {
      Serial.print("Speed (knots): ");Serial.println(GPS.speed);
      Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
    }
      display.clearDisplay();
      display.setTextSize(2);      // Normal 1:1 pixel scale
      display.setTextColor(WHITE); // Draw white text
      display.setCursor(0, 0);     // Start at top-left corner
      display.cp437(true);         // Use full 256 char 'Code Page 437' font

    if (!GPS.fix){
      display.print("No signal :( ");
      display.print(count);
    } else {
      float speed = GPS.speed;
      display.print(knots_to_pacemin(speed));
      display.print(":");
      int secs = knots_to_pacesec(speed);
      if (secs < 10){
       display.print("0");
      }
      display.print(secs);
      display.println(" ");
      display.print(count);
    }
    display.display();
  }
}

int knots_to_pacemin(float speed) {
  return (int) (speed/1.15078*60);
}

int knots_to_pacesec(float speed) {
  return ((speed/1.15078*60) - (float) knots_to_pacemin(speed)) * 60;
}

Dumping data happened separately - can see my Interfaces week to see how I did that.

Electronics

Choices

I chose the ATMega328P microcontroller because of its popularity and large flash memory. Its popularity makes it well-documented - also, testing stuff can be done with an Arduino Uno which runs on it. I didn’t want to worry about memory.

I also elected to use as few components as possible, making decisions such as not including external oscillators. This was to make the board as small as possible.

I needed to include a battery, so I decided to use a 110mAh lithium poly battery that we stock in the Harvard Lab. It is this one. Originally, I wanted to use a bigger battery, but it was just not good for form factor and I also realized my board’s power consumption was not that bad.

Board Design

I probably spent over 24 hours working on designing my board to make it as small as possible. The first thing that I did was make a generic ATMega328P board. This was super helpful because it would allow me to isolate errors in comparison to this vanilla board.

The components needed were:

  • Mega328P
  • 2x 10K resistor
  • Switch
  • 1x 1k resistor
  • 2x3 header
  • FTDI header
  • LED
  • JST 2 pin (for battery, you can remove this if you don’t have)

I kept my design pretty tight to save space. I wanted to practice saving space for my final board.

Vanilla 328 schematic 328P schematic

Vanilla 328 board 328P board

Milled and soldered, this is what it looks like, save the power connector.

Vanilla done

Please feel free to contact me if you have any questions about this board.

Also, note that I used this board during networking week to communicate with the Ultimate GPS breakout board instead of using the Arduino Uno. This was a cool lesson. Here’s a video of that setup:

After making thhis board, it was time to make my final board. At first, I made a board that would accomodate my OLED. After testing that board and finding that it worked, I added the GPS component. Finally, I probably spent over 10 hours making everything super compact and nice for purposes of form factor. The final result is the following.

Final schematic Final schematic

Final board Final board

Note that I used a program called Paintbrush for Mac to get rid of some of the holes that my design caused. Also note that I included an extra pad in case I wanted to add an extra component that requires GND and a pin, such as a button.

I think that I really learned a lot about Eagle doing this. I really got to fiddle around and push the limits of board design, fitting things as compactly as possible. For instance, I chose to use 3 pins of FTDI headers instead of 6. This saved a lot of space. Originally, I ran into tons of issues because I used 2 pins and discovered from helpful MIT people that I need to ground things to the computer in order to have serial communication (TX, RX).

Another bug thing to realize is that the Fab FTDIs have TX and RX labelled with respect to the perspective of the computer. This means that when connecting microcontrollers to the FTDI, you must connect RX to TX and TX to RX. This causes me tons and tons of headache, probably over 10 hours.

Electronics mistakes that I made and lessons I learned

  • Used a double sided board, which gave me electrical issues because the four pins of my OLED were electrically connected. I took a drill bit and manually removed a bunch of copper in order to solve this problem.
  • Turn on verbose error messages when trying to debug a board that is not programming correctly. The messages help. For instance, invalid device signature of 0x000000 means that microcontroller is likely fried (you get same result when trying to program the air) while 0xFFFFFFFF likely means that there is some sort of short.

Milling and Soldering

I don’t really have too much to say about milling. Milling is always a struggle, and in December of 2018, milling at Harvard was sort of a nightmare. We were super low on clean single-sided boards, the sacrificial layer of our Roland SRM-20 had seen better days, etc..

But the biggest nightmare was figuring out how to mill the dang ATMega328P. I actually went through 7 millings before finally arriving on settings that worked for me. I documented all of that stuff here fairly well. Please don’t make the mistakes that I did and figure out some custom settings for milling that work.

Soldering was exhausting - I guess that soldering is sort of stress relieving in some ways, but it is super stressful in some cases, in my opinion.

One thing that I got really good at was soldering the ATMega328Ps. One thing to note is that it is basically impossibe not to accidentally connect adjacent pins with solder. However, this is actually sort of a feature and not a bug of the soldering process of this microcontroller! Taking the copper braid to these connections leaves you with nicely soldered pins that have a Goldilocks amount of solder on them. Further, it lets you be sloppy because you can just apply a crap ton of solder and then fix things later! However, I think that the 328Ps fry sort of easily, so be careful of that.

soldered board

Note that I didn’t solder everything on immediately. I first soldered on all the components necessary to make the board work at a bare minimum to load the bootloader. Only then did I proceed to solder the OLED and then the GPS module (the most delicate / expensive part).

Another nightmare was the low number of JST 2 pin connectors that I bought. These were the default connectors on the batteries that we had, and I only bought two of them from Adafruit. One I lost and the other I had to use many, many times over. This was a huge hassle, and I had to get really good at unsoldering the component without damaging it. That’s what I get for not being insightful / being cheap.

Final Board

My final board looked like it does above.

A few tweaks that I made that you might note notice just from a cursory glance:

  • Shortened the FTDI headers a little so that they fit on the board - electrical connection is still just as good, in my experience
  • Shortened the 2x3 headers - this was again for form factor. Run into trouble sometimes, but the form factor is worth it
  • Hot glued the FTDI header on a little

Watch Case and CAD in general

One decision I had to make was what kind of watch strap to get. This would dictate my watch case making. After some research, I found that the quick release watch strap mechanism is very popular. So I decided to go with this. I ended up buying this watch strap in the 20mm size. It seemed pretty good to me and 20mm seems to be a standard size.

I then found a Fusion 360 watch design that had quick release attachers on a watch case. The design I found was this. borrowed_watch

I knew that my watch case would have to be a square, so I cut the quick release attachers from this watch and designed my own square chassis. It was a big pain to make the design parametric, but you’ll find the design file quite nice I think! You can vary the height, with, length, and wall thickness with no difficulty.

I went through several iterations of the watch because I didn’t understand what quick release sizing meant! I eventually realized that the metric refers to the squashed length of the metal bar attacher.

Further, I found the dimensions that I wanted and adjusted things based on the actual dimension of my fully stuffed board.

measureing

iterations

The final watch case:

final case

I used the Sindoh 3D printer to print this with black PLA.

Here is my design file and a picture of the design:

watch_design

Fusion file

Overall, I thought that designing this watch was a good culmination for all my CAD skills. I used a ton of features of Fusion and felt pretty accomplished for designing something that fits super well with my stuffed board.

Putting it all together

Assembling the watch was sort of fun. It fits like pretty fit, and it looks rather nice.

fit together

I ran into A LOT of coding issues and changed my code many times before getting everything to behave decently. As of now, I can go outside, have the display on, and see that there is actively a GPS signal reading.

However, I am running into an issue where for some reason, the logger is not working when I put it outside. I hope that this issue will get fixed, but in the meantime, all I can do is show that all of the parts individually work. Having it successfully log is just a software thing that for some reason that I cannot figure out does not work.

A few hours later, at the ripe time of 8am the morning before presentation time, I have finally figured out my issues. Lots of it was just finding a specific open space outside to wait and get a fix. I think that before, I was simply being too lazy - I was also moving around because I wanted to stay warm and it is extremely cold recently.

I want to emphasize how much of a pain it is to work on a GPS project - literally every time I want to test something I have to go outside and stroll around for a minute or two. This might be pleasurable in December at Stanford, but not in Cambridge, Massachusetts. Alas, everything finally works! I have a board that successfully logs GPS data and also displays the speed, as measured by the GPS module itself. I have found that this speed has quite high variance, but it seems to be generally decently accurate.

Some media of everything in action:

When there is no signal:

no signal

When there is a signal (getting a nonsensical value but that’s besides the point):

signal

As of now, I am also still waiting on my Amazon package of the 20mm watch band. It was supposed to arrive Saturday but has somehow got pushed back and now I’m in crunch time. Hopefully I will have it by class time, or else I will head to the Target on Mass Ave and pick one up.

I was able to use the library I wrote to convert the output from these travellings into GPS files on Strava!

Strava 1 Strava 2

Viability

I’m pretty proud of what I’ve made. With some minor adjustments, this could really be something that is near the level of usefuleness as a consumer product.

One of these changes is switching from FTDI to MicroUSB. I have had issues with the FTDI falling off, and it’s just a bulky solution.

Another is that the GPS signal, at least empirically for me, is quite weak. I think an external antennae could improve it

Formwise, my watch is 30mm wide, 40 mm long, and 10 mm tall, all with a little bit of slack and also a 2mm shell. The stuffed board weights 15g, and the chassis weights 6g. I don’t know how much the band weighs right now, but I believe that the total will be less than 50 grams. This is actually a lot less than some consumer GPS watches! Our watch will be less hefty!

Finally got my strap, put it together, and voila!

wrist_pic

Future Work

  • It would be great to have microUSB - I just didn’t have time to do this.
  • More aesthetic OLED
  • Microcontroller with more RAM
  • Get some sleep