In this project we will learn how to interface GPS module with esp8266 as main controller to show GPS data on OLED display. We will read and display various elements from GPS like Speed, Clock, Date, Location, Altitude, Trip distance, Number of connected satellites, Cardinals (moving direction), etc. We can use this project in cars or other moving vehicles.
In next update I am planning to build a web-server using esp8266 to receive data from GPS in smart phones and generate geographical location on online 2D maps using longitude and latitude.
/* * Project: Mini GPS Display using Ublox neo-6m and ESP8266 * Author: Pranay SS, eTechPath * Website: www.etechpath.com * Tutorial Link:Mini GPS Display using Ublox neo-6m module and ESP8266 nodemcu* Video Link: https://youtu.be/ExPBmiz1cj0 * */ #include <Arduino.h> #include <U8g2lib.h> #ifdef U8X8_HAVE_HW_SPI #include <SPI.h> #endif #ifdef U8X8_HAVE_HW_I2C #include <Wire.h> #endif #define menu D3 #define enter D4 int key = 0; double Home_LAT = 0; double Home_LNG = 0; //sat20x20px logo U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); static const unsigned char u8g_logo_sat[] U8X8_PROGMEM = { 0x00, 0x01, 0x00, 0x80, 0x07, 0x00, 0xc0, 0x06, 0x00, 0x60, 0x30, 0x00, 0x60, 0x78, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0xfe, 0x01, 0x00, 0xff, 0x01, 0x80, 0xff, 0x00, 0xc0, 0x7f, 0x06, 0xc0, 0x3f, 0x06, 0x80, 0x1f, 0x0c, 0x80, 0x4f, 0x06, 0x19, 0xc6, 0x03, 0x1b, 0x80, 0x01, 0x73, 0x00, 0x00, 0x66, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x70, 0x00, 0x00 }; //wave10px logo static const unsigned char u8g2_logo_wave[] U8X8_PROGMEM ={ 0xE0, 0x03, 0x18, 0x00, 0xC4, 0x01, 0x32, 0x00, 0x8A, 0x01, 0x69, 0x00, 0x25, 0x00, 0x95, 0x01, 0x95, 0x01, 0x01, 0x00, }; //sat40x35px logo // The serial connection to the GPS device #include <SoftwareSerial.h> static const int RXPin = D5, TXPin = D6; static const uint32_t GPSBaud = 9600; SoftwareSerial ss(RXPin, TXPin); //GPS Library #include <TinyGPS++.h> TinyGPSPlus gps; //Program variables double Lat; double Long; double Alt; int day, month, year; //int hour, minute, second; int num_sat, gps_speed; String heading; //SETUP void setup() { pinMode(menu, INPUT_PULLUP); pinMode(enter, INPUT_PULLUP); ss.begin(GPSBaud); u8g2.begin(); //PrintingLoadingPage u8g2.firstPage(); do { print_page1(); } while ( u8g2.nextPage() ); delay(5000); }//END SETUP //LOOP void loop() { Get_GPS(); //Get GPS data if (digitalRead(menu) == LOW) key = (key+1); //else if (digitalRead(menu) == LOW) //key = (key-1); if (key<0 or key>3) key = 0; switch (key) { case 0: u8g2.firstPage(); do { print_Clock(); } while ( u8g2.nextPage() ); delay(10); break; case 1: u8g2.firstPage(); do { print_speed(); } while ( u8g2.nextPage() ); delay(10); break; case 2: u8g2.firstPage(); do { print_location(); } while ( u8g2.nextPage() ); delay(10); break; case 3: u8g2.firstPage(); do { print_Trip(); if (digitalRead(enter) == LOW) { Home_LAT = gps.location.lat(); Home_LNG = gps.location.lng(); } else { u8g2.setFont(u8g2_font_courR08_tr); u8g2.setCursor(0, 64); u8g2.print("Press Enter to reset"); } } while ( u8g2.nextPage() ); delay(10); break; } } //end of loop void print_page1() { u8g2.drawXBMP(0, 0, 20, 20, u8g_logo_sat); u8g2.setFont( u8g2_font_crox1cb_tf); //u8g2.setFont(u8g2_font_helvB12_tf); //u8g2.setFont(u8g2_font_timB12_tf); u8g2.setCursor(45, 20); u8g2.print("MINI GPS"); //u8g2.setFont(u8g2_font_7x13B_tf); u8g2.setFont(u8g2_font_nine_by_five_nbp_tf); u8g2.setCursor(55, 35); u8g2.print("by eTechPath"); u8g2.setFont(u8g2_font_nine_by_five_nbp_tf); u8g2.setCursor(0, 60); u8g2.print("Loading"); u8g2.setFont(u8g2_font_glasstown_nbp_tf); u8g2.setCursor(40, 60); u8g2.print(" . . . . . "); } void print_Clock() { u8g2.setFont(u8g2_font_courB08_tn); u8g2.setCursor(105, 64); u8g2.print( num_sat, 5); u8g2.drawXBMP(118, 54, 10, 10, u8g2_logo_wave); u8g2.setFont(u8g2_font_crox1cb_tf); u8g2.setCursor(20, 10); u8g2.print("GPS CLOCK"); u8g2.drawLine(0,12,128,12); u8g2.setFont(u8g2_font_t0_22b_tn); u8g2.setCursor(20, 42); printTime(gps.time); // u8g.print(gps.date); //Get_Date(); u8g2.setFont(u8g2_font_nine_by_five_nbp_tf); u8g2.setCursor(0, 64); printDate(gps.date); } void print_speed() { u8g2.setFont(u8g2_font_crox1cb_tf); u8g2.setCursor(16, 10); u8g2.print("Speedometer"); u8g2.drawLine(0,15,128,15); u8g2.setFont(u8g2_font_t0_22b_tn); u8g2.setCursor(5, 42); u8g2.print(gps_speed , DEC); u8g2.setFont(u8g2_font_glasstown_nbp_tf); u8g2.setCursor(62, 42); u8g2.print("km/h"); u8g2.setFont(u8g2_font_courB08_tn); u8g2.setCursor(105, 64); u8g2.print( num_sat, 5); u8g2.drawXBMP(118, 54, 10, 10, u8g2_logo_wave); u8g2.setFont(u8g2_font_glasstown_nbp_tf); u8g2.setCursor(0,64); u8g2.print("Direction:"); u8g2.setCursor(45,64); u8g2.print( heading); } void print_location() { u8g2.setFont(u8g2_font_crox1cb_tf); u8g2.setCursor(10, 10); u8g2.print("GPS Location"); u8g2.drawLine(0,12,128,12); u8g2.setFont(u8g2_font_nine_by_five_nbp_tf); u8g2.setCursor(5, 28); u8g2.print("Long: "); u8g2.setCursor(40, 28); u8g2.print( Long, 6); u8g2.setCursor(5, 43); u8g2.print("Lat: "); u8g2.setCursor(40, 43); u8g2.print( Lat, 6); u8g2.setCursor(0, 64); u8g2.print("Alt: "); u8g2.setCursor(20, 64); u8g2.print( Alt, 3); u8g2.setFont(u8g2_font_courB08_tn); u8g2.setCursor(105, 64); u8g2.print( num_sat, 5); u8g2.drawXBMP(118, 54, 10, 10, u8g2_logo_wave); } // This custom version of delay() ensures that the gps object // is being "fed". static void smartDelay(unsigned long ms) { unsigned long start = millis(); do { while (ss.available()) gps.encode(ss.read()); } while (millis() - start < ms); } void Get_GPS() { num_sat = gps.satellites.value(); if (gps.location.isValid() == 1) { Lat = gps.location.lat(); Long = gps.location.lng(); Alt = gps.altitude.meters(); gps_speed = gps.speed.kmph(); heading = gps.cardinal(gps.course.value()); } /* if (gps.date.isValid()) { day = gps.date.day(); month = gps.date.month(); year = gps.date.year(); } if (gps.time.isValid()) { hour = gps.time.hour(); minute = gps.time.minute(); second = gps.time.second(); } */ smartDelay(1000); if (millis() > 5000 && gps.charsProcessed() < 10) { // Serial.println(F("No GPS detected: check wiring.")); } } static void printDate(TinyGPSDate &d) { if (!d.isValid()) { u8g2.print(F("******** ")); } else { char sz[32]; sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year()); u8g2.print(sz); } } static void printTime(TinyGPSTime &t) { if (!t.isValid()) { u8g2.print(F("******** ")); } else { char sz[32]; sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second()); u8g2.print(sz); } // printInt(d.age(), d.isValid(), 5); smartDelay(0); } void print_Trip() { unsigned long distanceKm = (unsigned long)TinyGPSPlus::distanceBetween( gps.location.lat(), gps.location.lng(), Home_LAT, Home_LNG ) / 1000.0; u8g2.setFont(u8g2_font_nine_by_five_nbp_tf); u8g2.setCursor(0, 20); u8g2.print("Trip: "); u8g2.setCursor(50, 20); u8g2.print(distanceKm); u8g2.setCursor(90, 20); u8g2.print("Km"); double courseTo = TinyGPSPlus::courseTo( gps.location.lat(), gps.location.lng(), Home_LAT, Home_LNG ); u8g2.setCursor(0, 30); u8g2.print("Course: "); u8g2.setCursor(60, 30); u8g2.print(courseTo); u8g2.setCursor(90, 30); u8g2.print("Km"); String cardinalTo = TinyGPSPlus::cardinal(courseTo); u8g2.setCursor(0, 40); u8g2.print("Cardinal: "); u8g2.setCursor(60, 40); u8g2.print(cardinalTo); }
Circuit Diagram:
Steps:
const char* ssid = "your SSID"; // edit your wifi SSID here const char* password = "your Password"; // edit your wifi password here
//Link: https://blog.etechpath.com
#include <ESP8266WiFi.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#define PRINT_CALLBACK 0
#define DEBUG 0
#define LED_HEARTBEAT 0
#if DEBUG
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#define PRINTS(s) { Serial.print(F(s)); }
#else
#define PRINT(s, v)
#define PRINTS(s)
#endif
#if LED_HEARTBEAT
#define HB_LED D2
#define HB_LED_TIME 500 // in milliseconds
#endif
#define MAX_DEVICES 4
#define CLK_PIN D5 // or SCK
#define DATA_PIN D7 // or MOSI
#define CS_PIN D8 // or SS
// SPI hardware interface
//MD_MAX72XX mx = MD_MAX72XX(CS_PIN, MAX_DEVICES);
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW //edit this as per your LED matrix hardware type
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// WiFi login parameters - network name and password
const char* ssid = "your SSID"; // edit your wifi SSID here
const char* password = "your Password"; // edit your wifi password here
// WiFi Server object and parameters
WiFiServer server(80);
// Global message buffers shared by Wifi and Scrolling functions
const uint8_t MESG_SIZE = 255;
const uint8_t CHAR_SPACING = 1;
const uint8_t SCROLL_DELAY = 75;
char curMessage[MESG_SIZE];
char newMessage[MESG_SIZE];
bool newMessageAvailable = false;
char WebResponse[] = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n";
char WebPage[] =
"<!DOCTYPE html>" \
"<html>" \
"<head>" \
"<title>eTechPath MAX7219 ESP8266</title>" \
"<style>" \
"html, body" \
"{" \
"width: 600px;" \
"height: 400px;" \
"margin: 0px;" \
"border: 0px;" \
"padding: 10px;" \
"background-color: white;" \
"}" \
"#container " \
"{" \
"width: 100%;" \
"height: 100%;" \
"margin-left: 200px;" \
"border: solid 2px;" \
"padding: 10px;" \
"background-color: #b3cbf2;" \
"}" \
"</style>"\
"<script>" \
"strLine = \"\";" \
"function SendText()" \
"{" \
" nocache = \"/&nocache=\" + Math.random() * 1000000;" \
" var request = new XMLHttpRequest();" \
" strLine = \"&MSG=\" + document.getElementById(\"txt_form\").Message.value;" \
" request.open(\"GET\", strLine + nocache, false);" \
" request.send(null);" \
"}" \
"</script>" \
"</head>" \
"<body>" \
"<div id=\"container\">"\
"<H1><b>WiFi MAX7219 LED Matrix Display</b></H1>" \
"<form id=\"txt_form\" name=\"frmText\">" \
"<label>Msg:<input type=\"text\" name=\"Message\" maxlength=\"255\"></label><br><br>" \
"</form>" \
"<br>" \
"<input type=\"submit\" value=\"Send Text\" onclick=\"SendText()\">" \
"<p><b>Visit Us at</b></p>" \
"<a href=\"http://www.eTechPath.com\">www.eTechPath.com</a>" \
"</div>" \
"</body>" \
"</html>";
char *err2Str(wl_status_t code)
{
switch (code)
{
case WL_IDLE_STATUS: return("IDLE"); break; // WiFi is in process of changing between statuses
case WL_NO_SSID_AVAIL: return("NO_SSID_AVAIL"); break; // case configured SSID cannot be reached
case WL_CONNECTED: return("CONNECTED"); break; // successful connection is established
case WL_CONNECT_FAILED: return("CONNECT_FAILED"); break; // password is incorrect
case WL_DISCONNECTED: return("CONNECT_FAILED"); break; // module is not configured in station mode
default: return("??");
}
}
uint8_t htoi(char c)
{
c = toupper(c);
if ((c >= '0') && (c <= '9')) return(c - '0');
if ((c >= 'A') && (c <= 'F')) return(c - 'A' + 0xa);
return(0);
}
boolean getText(char *szMesg, char *psz, uint8_t len)
{
boolean isValid = false; // text received flag
char *pStart, *pEnd; // pointer to start and end of text
// get pointer to the beginning of the text
pStart = strstr(szMesg, "/&MSG=");
if (pStart != NULL)
{
pStart += 6; // skip to start of data
pEnd = strstr(pStart, "/&");
if (pEnd != NULL)
{
while (pStart != pEnd)
{
if ((*pStart == '%') && isdigit(*(pStart+1)))
{
// replace %xx hex code with the ASCII character
char c = 0;
pStart++;
c += (htoi(*pStart++) << 4);
c += htoi(*pStart++);
*psz++ = c;
}
else
*psz++ = *pStart++;
}
*psz = '\0'; // terminate the string
isValid = true;
}
}
return(isValid);
}
void handleWiFi(void)
{
static enum { S_IDLE, S_WAIT_CONN, S_READ, S_EXTRACT, S_RESPONSE, S_DISCONN } state = S_IDLE;
static char szBuf[1024];
static uint16_t idxBuf = 0;
static WiFiClient client;
static uint32_t timeStart;
switch (state)
{
case S_IDLE: // initialise
PRINTS("\nS_IDLE");
idxBuf = 0;
state = S_WAIT_CONN;
break;
case S_WAIT_CONN: // waiting for connection
{
client = server.available();
if (!client) break;
if (!client.connected()) break;
#if DEBUG
char szTxt[20];
sprintf(szTxt, "%03d:%03d:%03d:%03d", client.remoteIP()[0], client.remoteIP()[1], client.remoteIP()[2], client.remoteIP()[3]);
PRINT("\nNew client @ ", szTxt);
#endif
timeStart = millis();
state = S_READ;
}
break;
case S_READ: // get the first line of data
PRINTS("\nS_READ");
while (client.available())
{
char c = client.read();
if ((c == '\r') || (c == '\n'))
{
szBuf[idxBuf] = '\0';
client.flush();
PRINT("\nRecv: ", szBuf);
state = S_EXTRACT;
}
else
szBuf[idxBuf++] = (char)c;
}
if (millis() - timeStart > 1000)
{
PRINTS("\nWait timeout");
state = S_DISCONN;
}
break;
case S_EXTRACT: // extract data
PRINTS("\nS_EXTRACT");
// Extract the string from the message if there is one
newMessageAvailable = getText(szBuf, newMessage, MESG_SIZE);
PRINT("\nNew Msg: ", newMessage);
state = S_RESPONSE;
break;
case S_RESPONSE: // send the response to the client
PRINTS("\nS_RESPONSE");
// Return the response to the client (web page)
client.print(WebResponse);
client.print(WebPage);
state = S_DISCONN;
break;
case S_DISCONN: // disconnect client
PRINTS("\nS_DISCONN");
client.flush();
client.stop();
state = S_IDLE;
break;
default: state = S_IDLE;
}
}
void scrollDataSink(uint8_t dev, MD_MAX72XX::transformType_t t, uint8_t col)
// Callback function for data that is being scrolled off the display
{
#if PRINT_CALLBACK
Serial.print("\n cb ");
Serial.print(dev);
Serial.print(' ');
Serial.print(t);
Serial.print(' ');
Serial.println(col);
#endif
}
uint8_t scrollDataSource(uint8_t dev, MD_MAX72XX::transformType_t t)
// Callback function for data that is required for scrolling into the display
{
static enum { S_IDLE, S_NEXT_CHAR, S_SHOW_CHAR, S_SHOW_SPACE } state = S_IDLE;
static char *p;
static uint16_t curLen, showLen;
static uint8_t cBuf[8];
uint8_t colData = 0;
// finite state machine to control what we do on the callback
switch (state)
{
case S_IDLE: // reset the message pointer and check for new message to load
PRINTS("\nS_IDLE");
p = curMessage; // reset the pointer to start of message
if (newMessageAvailable) // there is a new message waiting
{
strcpy(curMessage, newMessage); // copy it in
newMessageAvailable = false;
}
state = S_NEXT_CHAR;
break;
case S_NEXT_CHAR: // Load the next character from the font table
PRINTS("\nS_NEXT_CHAR");
if (*p == '\0')
state = S_IDLE;
else
{
showLen = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
curLen = 0;
state = S_SHOW_CHAR;
}
break;
case S_SHOW_CHAR: // display the next part of the character
PRINTS("\nS_SHOW_CHAR");
colData = cBuf[curLen++];
if (curLen < showLen)
break;
// set up the inter character spacing
showLen = (*p != '\0' ? CHAR_SPACING : (MAX_DEVICES*COL_SIZE)/2);
curLen = 0;
state = S_SHOW_SPACE;
// fall through
case S_SHOW_SPACE: // display inter-character spacing (blank column)
PRINT("\nS_ICSPACE: ", curLen);
PRINT("/", showLen);
curLen++;
if (curLen == showLen)
state = S_NEXT_CHAR;
break;
default:
state = S_IDLE;
}
return(colData);
}
void scrollText(void)
{
static uint32_t prevTime = 0;
// Is it time to scroll the text?
if (millis() - prevTime >= SCROLL_DELAY)
{
mx.transform(MD_MAX72XX::TSL); // scroll along - the callback will load all the data
prevTime = millis(); // starting point for next time
}
}
void setup()
{
#if DEBUG
Serial.begin(115200);
PRINTS("\n[MD_MAX72XX WiFi Message Display]\nType a message for the scrolling display from your internet browser");
#endif
#if LED_HEARTBEAT
pinMode(HB_LED, OUTPUT);
digitalWrite(HB_LED, LOW);
#endif
// Display initialisation
mx.begin();
mx.setShiftDataInCallback(scrollDataSource);
mx.setShiftDataOutCallback(scrollDataSink);
curMessage[0] = newMessage[0] = '\0';
// Connect to and initialise WiFi network
PRINT("\nConnecting to ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
PRINT("\n", err2Str(WiFi.status()));
delay(500);
}
PRINTS("\nWiFi connected");
// Start the server
server.begin();
PRINTS("\nServer started");
// Set up first message as the IP address
sprintf(curMessage, "%03d:%03d:%03d:%03d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
PRINT("\nAssigned IP ", curMessage);
}
void loop()
{
#if LED_HEARTBEAT
static uint32_t timeLast = 0;
if (millis() - timeLast >= HB_LED_TIME)
{
digitalWrite(HB_LED, digitalRead(HB_LED) == LOW ? HIGH : LOW);
timeLast = millis();
}
#endif
handleWiFi();
scrollText();
}
How to solve mirror image and orientation problems of matrix display if you are using old MD_MAX72xx library.