Arduino Mini GPS in an Altoids Smalls Tin

Introduction

Here’s the details on my first Arduino project I finished a few months ago.  It’s a functional GPS in an unmodified Altoids Smalls mint tin.  The GPS is built from an Ultimate GPS, 3V Pro Trinket, and 128×32 OLED from Adafruit. Demo/prototype video, build pictures, and source code below.

Demo/Prototype Video

The Build

11924271_10153249018563026_6360598531341269601_n
The parts

Parts list:

11898883_10153249019003026_4236742994506574780_n
Solder the wires onto the battery backpack

Normally it is easier to connect the battery backpack to the Pro Trinket with headers but it adds height. Not much room in the Altoids Smalls tin so had to connect with wire and put the Pro Trinket and battery backpackside by side.

11873707_10153249019023026_1272948015714886154_n
Backpack connected to the Pro Trinket

Pro trinket and battery backpack connected.  Optional wires for on/off switch connected to the battery backpack.

17229_10153249019143026_5465703991058721750_n
Solder connecting wires to the OLED display

Get the wires soldered onto the OLED screen.  Leave them longer and cut down once you know the exact length.

11933402_10153249019163026_3105385150618679157_n
Solder connecting wires to the GPS

Solder the connecting wires to the GPS.  For this project only connections are required for TX, RX, GND and VIN.

11218827_10153249019173026_3259902437873557164_n
Getting everything connected

Ok, so here is where things start getting real.  I had put this circuit together so many times on a breadboard I could probably prototype it with my eyes closed.  Snipping, stripping, and soldering the wires is more permanent.  Especially because the wires had to be snipped short so they didn’t take much extra space.  I used 24 AWG wire.  Probably would have been easier to use something smaller.

11219218_10153249019328026_7734359061484879678_n
All components connected.

Got everything connected.  Just need to connect the on/off switch.  Push the components together gently as to not break the solder joints and put it in the Altoids Smalls tin.

11863354_10153249019423026_1897668160935103236_n
In the tin and connecting the on/off toggle switch

Stuffed it in the tin and connected the on/off toggle switch.  Still haven’t turned it on yet.

11903887_10153249074173026_354590419543481965_n
Powered on for the first time

It turned on when I flipped the switch. All of the components were new and had not been powered on before this.

11221293_10153249074178026_3748953819625709031_n

Tin closes

Everything fits in the tin and the lid closes.  Watch the video above to see exactly how things are placed in the Altoids Smalls tin.  It will fit.

Getting Started

The best place to start is by getting an Ultimate GPS from Adafruit and follow this guide to get the libraries/samples installed and get it connected to an Arduino Uno.

Starting development with the Uno is recommended because deploying the code and using the serial window is easier.  When ready you can easily deploy the code you wrote for the Uno to the Pro Trinket using the Aduino IDE.  There’s a few things to know if developing on the Uno with plans to deploy to the 3V Trinket:

  • Pins #2 and #7 are not available on the Pro Trinket
  • The Uno has 32 KB of program space and the Pro Trinket has 28 KB.  Both have 2 KB RAM.
  • Use the 3.3V power output on the UNO because that is what you are getting from the 3V Pro Trinket.  There is also a 5V Pro Trinket but I’ve never worked with it.

After the GPS is hooked up, take a look at the “parsing” example in the GPS library example.  The source code below is based on that example.

Get the OLED working by following the information provided here.  The OLED libraries do a lot of graphics, but the code sample below only prints text.

If you’ve got the GPS and OLED examples running, then you should be able to use the paste the code below into new sketch and deploy it.

Source Code


/*************************************************************

Shawn Cruise
@ShawnCruise
shawncruise.com
January 24, 2016

Sample code that can be used to create the Arduino Pocket GPS
found at https://www.youtube.com/watch?v=Ikcvef2ENr0

Based on the GPS Parser and SSD1306_128x32_SPI example code.  See original comments below.

Circuit:
PIN 3 - RX on GPS
PIN 4 - TX on GPS
PIN 9 - DATA on OLED
PIN 10 - CLK on OLED
PIN 11 - DC on OLED
PIN 12 - CS on OLED
PIN 13 - RESET on OLED //don't use 13 if you want to use the LED for something
GND - GND on OLED and GPS
3V - VIN on OLED and GPS

**************************************************************/


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


/*********************************************************************
This is an example for our Monochrome OLEDs based on SSD1306 drivers

Pick one up today in the adafruit shop!
------> http://www.adafruit.com/category/63_98

This example is for a 128x32 size display using SPI to communicate
4 or 5 pins are required to interface

Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!

Written by Limor Fried/Ladyada  for Adafruit Industries.
BSD license, check license.txt for more information
All text above, and the splash screen must be included in any redistribution
*********************************************************************/

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

#define GPS_RX 3
#define GPS_TX 4
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13 //don't use 13 if you want to use the LED for something
#define GPSECHO  true

Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
SoftwareSerial mySerial(GPS_TX, GPS_RX); // tx,rx
Adafruit_GPS GPS(&mySerial);
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy
uint32_t timer = millis();
short tzOffset = 0; // setting to subtract from GMT for current time zome

void setup()
{
	display.begin(SSD1306_SWITCHCAPVCC);
	display.display();
	delay(2000);
	GPS.begin(9600);
	GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
	GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
	GPS.sendCommand(PGCMD_ANTENNA);
	useInterrupt(true);
	delay(1000);
}

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

// prints the compass heading based on the angle reported by the GPS 
void printHeading()
{
	char dir[3];
	dir[0] = ' ';
	dir[1] = ' ';
	dir[2] = ' ';

	if (GPS.angle > 337.25)
	{
		dir[0] = 'N';
	}
	else if (GPS.angle <= 22.5)
	{
		dir[0] = 'N';
	}
	else if (GPS.angle <= 67.5)
	{
		dir[0] = 'N';
		dir[1] = 'E';
	}
	else if (GPS.angle <= 112.5)
	{
		dir[0] = 'E';
	}
	else if (GPS.angle <= 157.5)
	{
		dir[0] = 'S';
		dir[1] = 'E';
	}
	else if (GPS.angle <= 202.5)
	{
		dir[0] = 'S';
	}
	else if (GPS.angle <= 247.5)
	{
		dir[0] = 'S';
		dir[1] = 'W';
	}
	else if (GPS.angle <= 292.5)
	{
		dir[0] = 'W';
	}
	else if (GPS.angle <= 337.5)
	{
		dir[0] = 'N';
		dir[1] = 'W';
	}
	display.print(dir[0]);
	display.print(dir[1]);
	display.print(dir[2]);
}

void printDateTime()
{
	short hr = GPS.hour - tzOffset;
	// check if the hr is now less than 0 i.e. before midnight after TX adjustment
	// fine if GMT - offset, need to add code to handle GMT + offset 
	if (hr < 0)
	{
		hr += 24;
	}
	if (hr  < 10)
	{
		display.print('0');
	}
	display.print(hr, DEC);
	display.print(':');
	if ((short)GPS.minute < 10)
	{
		display.print('0');
	}
	display.print(GPS.minute, DEC);
	display.print(':');
	if ((short)GPS.seconds < 10)
	{
		display.print('0');
	}
	display.print(GPS.seconds, DEC);
}

void updateDisplay()
{
	display.clearDisplay();
	display.setTextColor(WHITE);
	display.setCursor(0, 0);
	// the GPS will output a time without a sat fix.  
	// it often gets the sat time long before the sat location fix
	display.setTextSize(2);
	printDateTime();
	display.setTextSize(0);
	display.setCursor(110, 0);  // num sats appear in small text to the right of time
	display.print((int)GPS.satellites);
	// display the location info only if there is a sat fix
	if (GPS.fix)
	{
		display.setCursor(110, 8);
		display.print(GPS.HDOP, 1);  // HDOP appear in small text under num sats.
		// normally need a println here but the hdop value hits the end of the line and forces a new line 
		// coordinates  
		display.print(GPS.latitudeDegrees, 5); display.print(','); display.println(GPS.longitudeDegrees, 5);
		//elevation
		display.print(GPS.altitude, 0); display.print("m ");
		// only display the heading if moving otherwise it is bouncing around due to normal GPS error
		// must cast to int because speed is float value in knots and decimal values are changeing when not moving
		if ((int)GPS.speed>0)
		{
			printHeading();
		}
		else
		{
			display.print("-- ");
		}
		// speed is in knots, convert to km/h
		display.print((int)(GPS.speed*1.852)); display.println(" km/h");
	}
	// refresh the display
	display.display();
}

void loop()
{
	// if a new sentence is received
	if (GPS.newNMEAreceived()) {
		//if the GPS object can't parse it, return and wait for the next one
		if (!GPS.parse(GPS.lastNMEA()))
			return;
	}

	// if timer or mills wrap around, reset the timer
	if (timer > millis())  timer = millis();

	// update the display every second
	if (millis() - timer > 1000) {
		timer = millis(); // reset the timer

		updateDisplay();
	}
}
Advertisements