Uniostar
Published © GPL3+

Arduino GPS Clock With Menu

Interested in standalone GPS devices? Checkout this quick teardown on how I made my GPS Device with an OLED display!

IntermediateFull instructions provided2 hours58
Arduino GPS Clock With Menu

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Jumper wires (generic)
Jumper wires (generic)
×1
OLED Display 128x64 0.96 inch, I2C Interface
DIYables OLED Display 128x64 0.96 inch, I2C Interface
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×1
Neo-6m GPS Sensor
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Back Cover

Cad file.

Sketchfab still processing.

Main Box

Cad file.

Sketchfab still processing.

Schematics

Schematic

Code

Main Code File

Arduino
#include "U8glib.h"
#include <TinyGPS++.h>
#include <SoftwareSerial.h>

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);

const unsigned char epd_bitmap_Aerospace_qr_code [] PROGMEM = {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 
	0xc0, 0x00, 0xfc, 0xfc, 0xfc, 0x3c, 0x00, 0x0c, 0xc0, 0x00, 0xfc, 0xfc, 0xfc, 0x3c, 0x00, 0x0c, 
	0xcf, 0xfc, 0xf3, 0xf3, 0xf0, 0x3c, 0xff, 0xcc, 0xcf, 0xfc, 0xf3, 0xf3, 0xf0, 0x3c, 0xff, 0xcc, 
	0xcc, 0x0c, 0xcf, 0x3f, 0x3c, 0x0c, 0xc0, 0xcc, 0xcc, 0x0c, 0xcf, 0x3f, 0x3c, 0x0c, 0xc0, 0xcc, 
	0xcc, 0x0c, 0xf3, 0xc3, 0xfc, 0x0c, 0xc0, 0xcc, 0xcc, 0x0c, 0xf3, 0xc3, 0xfc, 0x0c, 0xc0, 0xcc, 
	0xcc, 0x0c, 0xf3, 0xc3, 0xcf, 0x3c, 0xc0, 0xcc, 0xcc, 0x0c, 0xf3, 0xc3, 0xcf, 0x3c, 0xc0, 0xcc, 
	0xcf, 0xfc, 0xfc, 0x0c, 0x03, 0x3c, 0xff, 0xcc, 0xcf, 0xfc, 0xfc, 0x0c, 0x03, 0x3c, 0xff, 0xcc, 
	0xc0, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x0c, 0xc0, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x0c, 
	0xff, 0xff, 0xcf, 0x3f, 0xc3, 0x3f, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0x3f, 0xc3, 0x3f, 0xff, 0xfc, 
	0xc0, 0xc0, 0x0f, 0xcf, 0xff, 0x30, 0xfc, 0xfc, 0xc0, 0xc0, 0x0f, 0xcf, 0xff, 0x30, 0xfc, 0xfc, 
	0xcf, 0x33, 0xf3, 0x03, 0xfc, 0xcc, 0xf3, 0xcc, 0xcf, 0x33, 0xf3, 0x03, 0xfc, 0xcc, 0xf3, 0xcc, 
	0xcf, 0xf0, 0xfc, 0x0c, 0xcc, 0xfc, 0x0c, 0x0c, 0xcf, 0xf0, 0xfc, 0x0c, 0xcc, 0xfc, 0x0c, 0x0c, 
	0xff, 0xff, 0x30, 0xc0, 0xf3, 0x3c, 0xcf, 0x3c, 0xff, 0xff, 0x30, 0xc0, 0xf3, 0x3c, 0xcf, 0x3c, 
	0xcf, 0xf0, 0xcc, 0x3c, 0xcf, 0xfc, 0xf3, 0x0c, 0xcf, 0xf0, 0xcc, 0x3c, 0xcf, 0xfc, 0xf3, 0x0c, 
	0xc3, 0xf3, 0x3c, 0x3c, 0xcf, 0x00, 0xf3, 0xcc, 0xc3, 0xf3, 0x3c, 0x3c, 0xcf, 0x00, 0xf3, 0xcc, 
	0xc3, 0x00, 0xcf, 0xf3, 0xcf, 0xc3, 0xf3, 0x0c, 0xc3, 0x00, 0xcf, 0xf3, 0xcf, 0xc3, 0xf3, 0x0c, 
	0xcc, 0x3f, 0x3f, 0x3f, 0x30, 0xf0, 0x33, 0x3c, 0xcc, 0x3f, 0x3f, 0x3f, 0x30, 0xf0, 0x33, 0x3c, 
	0xf3, 0x3c, 0xc3, 0xcf, 0x3f, 0xc0, 0x33, 0x0c, 0xf3, 0x3c, 0xc3, 0xcf, 0x3f, 0xc0, 0x33, 0x0c, 
	0xf0, 0xcf, 0xcc, 0x03, 0x00, 0xc0, 0xf0, 0xcc, 0xf0, 0xcf, 0xcc, 0x03, 0x00, 0xc0, 0xf0, 0xcc, 
	0xcf, 0x30, 0x3f, 0x0c, 0xc3, 0xf0, 0x3f, 0x0c, 0xcf, 0x30, 0x3f, 0x0c, 0xc3, 0xf0, 0x3f, 0x0c, 
	0xf0, 0x3f, 0x3c, 0xc0, 0xc3, 0xcc, 0xc3, 0x3c, 0xf0, 0x3f, 0x3c, 0xc0, 0xc3, 0xcc, 0xc3, 0x3c, 
	0xcc, 0x0c, 0x0c, 0x3c, 0xc3, 0x00, 0x0f, 0xfc, 0xcc, 0x0c, 0x0c, 0x3c, 0xc3, 0x00, 0x0f, 0xfc, 
	0xff, 0xff, 0xc0, 0x3c, 0x0c, 0x0f, 0xcc, 0x0c, 0xff, 0xff, 0xc0, 0x3c, 0x0c, 0x0f, 0xcc, 0x0c, 
	0xc0, 0x00, 0xc0, 0xf3, 0xcc, 0x0c, 0xc3, 0x0c, 0xc0, 0x00, 0xc0, 0xf3, 0xcc, 0x0c, 0xc3, 0x0c, 
	0xcf, 0xfc, 0xcc, 0x3f, 0xf3, 0x0f, 0xc3, 0xcc, 0xcf, 0xfc, 0xcc, 0x3f, 0xf3, 0x0f, 0xc3, 0xcc, 
	0xcc, 0x0c, 0xcf, 0xcf, 0x03, 0xc0, 0x0f, 0xcc, 0xcc, 0x0c, 0xcf, 0xcf, 0x03, 0xc0, 0x0f, 0xcc, 
	0xcc, 0x0c, 0xfc, 0x03, 0x0f, 0xff, 0x0c, 0x0c, 0xcc, 0x0c, 0xfc, 0x03, 0x0f, 0xff, 0x0c, 0x0c, 
	0xcc, 0x0c, 0xcc, 0x0c, 0xf0, 0x0f, 0x03, 0xcc, 0xcc, 0x0c, 0xcc, 0x0c, 0xf0, 0x0f, 0x03, 0xcc, 
	0xcf, 0xfc, 0xcf, 0xc0, 0x33, 0x0c, 0x0f, 0x3c, 0xcf, 0xfc, 0xcf, 0xc0, 0x33, 0x0c, 0x0f, 0x3c, 
	0xc0, 0x00, 0xc3, 0x3c, 0xcf, 0x0c, 0x0f, 0x0c, 0xc0, 0x00, 0xc3, 0x3c, 0xcf, 0x0c, 0x0f, 0x0c, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc
};

const unsigned char bitmap_icon_dashboard[] PROGMEM = {
  0x00, 0x00, 0x1f, 0xf0, 0x13, 0x50, 0x1b, 0xb0, 0x11, 0x50, 0x1f, 0xf0, 0x03, 0x80, 0x01, 0x00,
  0x00, 0x00, 0x09, 0x20, 0x49, 0x24, 0x20, 0x08, 0x00, 0x01, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00
};

const unsigned char bitmap_icon_gps_speed[] PROGMEM = {
  0x00, 0x00, 0x03, 0xf0, 0x00, 0x08, 0x01, 0xe4, 0x00, 0x12, 0x00, 0xca, 0x06, 0x2a, 0x07, 0x2a,
  0x07, 0x8a, 0x07, 0xc2, 0x07, 0xc0, 0x0a, 0x00, 0x1f, 0x00, 0x20, 0x80, 0x7f, 0xc0, 0x00, 0x00
};

const unsigned char bitmap_icon_knob_over_oled[] PROGMEM = {
  0x07, 0xe0, 0x18, 0x18, 0x21, 0x24, 0x50, 0x02, 0x48, 0x0a, 0x84, 0x01, 0x83, 0x81, 0xa2, 0x45,
  0x82, 0x41, 0x81, 0x81, 0xa0, 0x05, 0x40, 0x02, 0x4b, 0xd2, 0x23, 0xc4, 0x18, 0x18, 0x07, 0xe0
};

const unsigned char* bitmap_icons[8] = {
  bitmap_icon_dashboard,
  bitmap_icon_gps_speed,
  bitmap_icon_knob_over_oled,
};

const unsigned char bitmap_scrollbar_background[] PROGMEM = {
  0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
  0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
  0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
  0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00
};

const unsigned char bitmap_item_sel_outline[] PROGMEM = {
  0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0,
  0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0
};

const int NUM_ITEMS = 3;
const int MAX_ITEM_LENGTH = 20;

char menu_items[NUM_ITEMS][MAX_ITEM_LENGTH] = {  // array with item names
  { "Club Code" },
  { "GPS Data" },
  { "Time" }
};

#define BUTTON_UP_PIN 12
#define BUTTON_SELECT_PIN 8
#define BUTTON_DOWN_PIN 4

int button_up_clicked = 0;
int button_select_clicked = 0;
int button_down_clicked = 0;

int item_selected = 0;

int item_sel_previous;
int item_sel_next;

/*
Page Numbers:
0 - Menu
1 - Settings
2 - Data
3 - Time
*/

int current_screen = 0;

float lat;
float lon;
int day;
int mon;
int yr;
int hr;
int minute;
int sec;
int speed;
int altitude;
int progress = 0;
char buffer[32];  // helper buffer to construct a string to be displayed

TinyGPSPlus gps;
SoftwareSerial gpsSerial(10, 11));

void setup() {
  u8g.setColorIndex(1);

  pinMode(BUTTON_UP_PIN, INPUT_PULLUP);
  pinMode(BUTTON_SELECT_PIN, INPUT_PULLUP);
  pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP);

  gpsSerial.begin(9600);

  load();
}

void loop() {
  if (current_screen == 0) {
    if ((digitalRead(BUTTON_UP_PIN) == LOW) && (button_up_clicked == 0)) {
      item_selected = item_selected - 1;
      button_up_clicked = 1;
      if (item_selected < 0) {
        item_selected = NUM_ITEMS - 1;
      }
    } else if ((digitalRead(BUTTON_DOWN_PIN) == LOW) && (button_down_clicked == 0)) {
      item_selected = item_selected + 1;
      button_down_clicked = 1;
      if (item_selected >= NUM_ITEMS) {
        item_selected = 0;
      }
    }

    if ((digitalRead(BUTTON_UP_PIN) == HIGH) && (button_up_clicked == 1)) {
      button_up_clicked = 0;
    }
    if ((digitalRead(BUTTON_DOWN_PIN) == HIGH) && (button_down_clicked == 1)) {
      button_down_clicked = 0;
    }
  }

  if ((digitalRead(BUTTON_SELECT_PIN) == LOW) && (button_select_clicked == 0)) {
    button_select_clicked = 1;

    if (current_screen == 0) {
      current_screen = (item_selected != NUM_ITEMS) ? item_selected + 1 : 1;
    } else {
      current_screen = 0;
    }
  }
  if ((digitalRead(BUTTON_SELECT_PIN) == HIGH) && (button_select_clicked == 1)) {
    button_select_clicked = 0;
  }

  item_sel_previous = item_selected - 1;
  if (item_sel_previous < 0) { item_sel_previous = NUM_ITEMS - 1; }
  item_sel_next = item_selected + 1;
  if (item_sel_next >= NUM_ITEMS) { item_sel_next = 0; }

  //Menu
  if (current_screen == 0) {
    u8g.firstPage();

    do {

      u8g.drawBitmapP(0, 22, 128 / 8, 21, bitmap_item_sel_outline);

      u8g.setFont(u8g_font_7x14);
      u8g.drawStr(25, 15, menu_items[item_sel_previous]);
      u8g.drawBitmapP(4, 2, 16 / 8, 16, bitmap_icons[item_sel_previous]);

      u8g.setFont(u8g_font_7x14B);
      u8g.drawStr(25, 15 + 20 + 2, menu_items[item_selected]);
      u8g.drawBitmapP(4, 24, 16 / 8, 16, bitmap_icons[item_selected]);

      u8g.setFont(u8g_font_7x14);
      u8g.drawStr(25, 15 + 20 + 20 + 2 + 2, menu_items[item_sel_next]);
      u8g.drawBitmapP(4, 46, 16 / 8, 16, bitmap_icons[item_sel_next]);

      u8g.drawBitmapP(128 - 8, 0, 8 / 8, 64, bitmap_scrollbar_background);

      u8g.drawBox(125, ((int)64 / NUM_ITEMS) * item_selected, 3, 64 / NUM_ITEMS);
    } while (u8g.nextPage());

  } else if (current_screen == 1) {
    u8g.firstPage();
    do 
    {
      u8g.drawBitmapP(33, 1, 64/8, 64, epd_bitmap_Aerospace_qr_code);
    } while(u8g.nextPage());
  } else if (current_screen == 2) {
    while (gpsSerial.available() > 0) {
      if (gps.encode(gpsSerial.read())) {
        getLocation();
        getSpeed();
        getAltitude();

        printAll();

        if ((digitalRead(BUTTON_SELECT_PIN) == LOW) && (button_select_clicked == 0)) {
          return;
        }
      }
    }

  } else if (current_screen == 3) {
    while (gpsSerial.available() > 0) {
      if (gps.encode(gpsSerial.read())) {
        getTime();
        getDate();

        printTime();

        if ((digitalRead(BUTTON_SELECT_PIN) == LOW) && (button_select_clicked == 0)) {
          return;
        }
      }
    }
  }
}

void printAll() {

  u8g.firstPage();

  do {
    u8g.setFont(u8g_font_6x13r);

    u8g.drawStr(0, 10, "Lat: ");

    u8g.drawStr(0, 25, String(lat, 6).c_str());

    u8g.drawStr(0, 40, "Lon: ");

    u8g.drawStr(0, 55, String(lon, 6).c_str());

    u8g.drawStr(70, 10, "Speed:");

    u8g.drawStr(70, 25, String(speed).c_str());

    u8g.drawStr(70, 40, "Altitude:");

    u8g.drawStr(70, 55, String(altitude).c_str());

  } while (u8g.nextPage());
}

void printTime() {
  u8g.firstPage();

  do
  {
    u8g.setFont(u8g_font_6x13r);

    u8g.drawStr(0, 10, "Time:");

    u8g.drawStr(0, 25, String(hr).c_str());

    u8g.drawStr(20, 25, String(minute).c_str());

    u8g.drawStr(40, 25, String(sec).c_str());

    u8g.drawStr(0, 40, "Date:");

    u8g.drawStr(0, 55, String(mon).c_str());

    u8g.drawStr(20, 55, String(day).c_str());

    u8g.drawStr(40, 55, String(yr % 100).c_str());
  } while (u8g.nextPage());
}

// void printLoading() {
//   u8g.firstPage();

//   do {
//     u8g.setFont(u8g_font_6x13r);
//     u8g.drawStr(5, 25, "GPS Interface 0.2");
//     u8g.drawStr(5, 40, "Connecting . . .");
//   } while (u8g.nextPage());
// }

void load() {
  for (int i = 0; i <= 100; i++) {
    u8g.firstPage();

    do {
      u8g.drawFrame(12, 21, 104, 20);
      u8g.drawBox(14, 23, progress, 16);  // draw the progressbar fill
      u8g.setFont(u8g_font_helvB08r);
      sprintf(buffer, "Progress: %d%%", progress);  // construct a string with the progress variable
      u8g.drawStr(13, 53, buffer);                  // display the string

    } while (u8g.nextPage());

    progress = progress + 1;

    if (progress > 100) {
      progress = 0;
    }
  }
}

void getDate() {
  if (gps.date.isValid()) {
    day = gps.date.day();
    mon = gps.date.month();
    yr = gps.date.year();
  }
}

void getLocation() {
  if (gps.location.isValid()) {
    lat = gps.location.lat();
    lon = gps.location.lng();
  }
}

void getTime() {
  if (gps.time.isValid()) {
    hr = gps.time.hour();
    minute = gps.time.minute();
    sec = gps.time.second();
  }
}

void getSpeed() {
  if (gps.speed.isValid()) {
    speed = gps.speed.mps();
  }
}

void getAltitude() {
  if (gps.altitude.isValid()) {
    altitude = gps.altitude.feet();
  }
}

Credits

Uniostar
10 projects • 8 followers
Electrical Engineering Undergrad Student specialized in low-level programming, IoT projects, and microcontroller electronics.

Comments