Saturday, October 18, 2014

Old GPS receiver and Arduino = speedometer

   I made a speedometer with GPS coordinates and clock using: an old GPS receiver, Arduino board, alphanumeric LCD with 20 columns and 4 rows.
   To find offered datas from GPS receiver, I followed the information provided in article tronixstuffial – Arduino and EM406A GPS:
   After I study article named NMEA data and I selectet usefull datas for speed:
   I use TinyGPS++ library made by Mikal Hart and I extract speed:
TinyGPSCustom zdop(gps, "GPVTG", 7); // $GPVTG sentence, 7th element 
   I designed this schematic for tests:

   I write more sketches but last is 1.6.5 version:
// source: http://arduiniana.org/libraries/tinygpsplus/
// for see your position: http://www.gps-coordinates.net/
// for new article made by niq_ro: http://nicuflorica.blogspot.com/
// & http://arduiniq.blogspot.com/
#include <TinyGPS++.h>
#include <SoftwareSerial.h>

/*
   This sample code demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/
static const int RXPin = 4, TXPin = 3;
static const uint32_t GPSBaud = 4800;

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

#include <LiquidCrystal.h>
// folosesc libraria pentru afisaje LCD simple
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
// indic modul de legare, vezi mai jos:
/*                                    -------------------
                                      |  LCD  | Arduino |
                                      -------------------
 LCD RS pin to digital pin 7          |  RS   |   D7    |
 LCD Enable pin to digital pin 6      |  E    |   D8    |
 LCD D4 pin to digital pin 5          |  D4   |   D9    |
 LCD D5 pin to digital pin 4          |  D5   |   D10   |
 LCD D6 pin to digital pin 3          |  D6   |   D11   |
 LCD D7 pin to digital pin 2          |  D7   |   D12   |
 LCD R/W pin to ground                |  R/W  |   GND   |
                                      -------------------
niq_ro adapted this sketch for see data on 2004 LCD
*/

/* A set of custom made large numbers for a 16x2 LCD using the 
 LiquidCrystal library. Works with displays compatible with the 
 Hitachi HD44780 driver. Made by Michael Pilcher, 2/9/2010
*/ 

int x = 0;
// the 8 arrays that form each segment of the custom numbers
byte LT[8] = 
{
  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] =
{
  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100
};
byte UMB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte LMB[8] =
{
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};


// for speed in knots (noduri) 
//TinyGPSCustom sdop(gps, "GPVTG", 5); // $GPVTG sentence, 5th element
// for speed in kilometers per hour
TinyGPSCustom zdop(gps, "GPVTG", 7); // $GPVTG sentence, 7th element 

void setup()
{
  Serial.begin(115200);
  ss.begin(GPSBaud);

  Serial.println(F("FullExample.ino"));
  Serial.println(F("An extensive example of many interesting TinyGPS++ features"));
  Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println(F("by Mikal Hart"));
  Serial.println();
  Serial.println(F("Sats HDOP Latitude   Longitude   Fix  Date       Time     Date Alt    Course Speed Card  Distance Course Card  Chars Sentences Checksum"));
  Serial.println(F("          (deg)      (deg)       Age                      Age  (m)    --- from GPS ----  ---- to London  ----  RX    RX        Fail"));
  Serial.println(F("---------------------------------------------------------------------------------------------------------------------------------------"));


lcd.begin(20, 4); // set up the LCD's number of columns and rows: 

// assignes each segment a write number
  lcd.createChar(8,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,UMB);
  lcd.createChar(7,LMB);

 lcd.clear(); // clear the screen
 lcd.setCursor(1, 0); // put cursor at colon x and row y
 lcd.print("GPS data - 7.2014"); // print a text
 lcd.setCursor(0, 1); // put cursor at colon x and row y
 lcd.print("ver 1.6.5 by niq_ro"); // print a text
 lcd.setCursor(1, 2); // put cursor at colon x and row y
 lcd.print("Craiova - Romania"); // print a text
 lcd.setCursor(0, 3); // put cursor at colon x and row y
 lcd.print("(TinyGPS++ library)"); // print a text
 
 delay (2000);
 lcd.clear(); // clear the screen
 
/* 
// testare la inceput mod afisare viteza
lcd.setCursor(2,0);
 lcd.print("niq_ro testeaza");
 lcd.setCursor(1,1);
 lcd.print("afisarea vitezei:"); 
for (int q=0; q<120; q=q++)
 {
 vitezamare(q); 
 lcd.setCursor(12,3); 
 lcd.print("km/h");
 delay(200);
 lcd.setCursor(0,2);
 lcd.print("           ");
 lcd.setCursor(0,3);
 lcd.print("           ");
}
*/
 lcd.clear(); // clear the screen
 delay(500);
}


void loop()
{
  // partea de ecran LCD 20x4
  afisareecran();


  // scot viteza 
 int viteza = atoi (zdop.value());
 //int viteza = 123;
 //int viteza = 456;
 //int viteza = 789;
 //int viteza = 92;
 //int viteza = 6;
 //int viteza = 0;
 vitezamare(viteza); 
 lcd.setCursor(12,3); 
 lcd.print("km/h");
  
  
  
  static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002;

  printInt(gps.satellites.value(), gps.satellites.isValid(), 5);
  printInt(gps.hdop.value(), gps.hdop.isValid(), 5);
  printFloat(gps.location.lat(), gps.location.isValid(), 11, 6);
  printFloat(gps.location.lng(), gps.location.isValid(), 12, 6);
  printInt(gps.location.age(), gps.location.isValid(), 5);
  printDateTime(gps.date, gps.time);
  printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2);
  printFloat(gps.course.deg(), gps.course.isValid(), 7, 2);
  printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2);
  printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.value()) : "*** ", 6);

  unsigned long distanceKmToLondon =
    (unsigned long)TinyGPSPlus::distanceBetween(
      gps.location.lat(),
      gps.location.lng(),
      LONDON_LAT, 
      LONDON_LON) / 1000;
  printInt(distanceKmToLondon, gps.location.isValid(), 9);

  double courseToLondon =
    TinyGPSPlus::courseTo(
      gps.location.lat(),
      gps.location.lng(),
      LONDON_LAT, 
      LONDON_LON);

  printFloat(courseToLondon, gps.location.isValid(), 7, 2);

  const char *cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon);

  printStr(gps.location.isValid() ? cardinalToLondon : "*** ", 6);

  printInt(gps.charsProcessed(), true, 6);
  printInt(gps.sentencesWithFix(), true, 10);
  printInt(gps.failedChecksum(), true, 9);

  // speed in km/h
  Serial.print(" ");
  Serial.print(zdop.value());
  int vitesa1 = atoi (zdop.value());
  Serial.print("/");
  Serial.print(vitesa1);
  Serial.println();
  
  smartDelay(1000);

  if (millis() > 5000 && gps.charsProcessed() < 10)
    Serial.println(F("No GPS data received: check wiring"));
}

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

static void printFloat(float val, bool valid, int len, int prec)
{
  if (!valid)
  {
    while (len-- > 1)
      Serial.print('*');
    Serial.print(' ');
  }
  else
  {
    Serial.print(val, prec);
    int vi = abs((int)val);
    int flen = prec + (val < 0.0 ? 2 : 1); // . and -
    flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i=flen; i<len; ++i)
      Serial.print(' ');
  }
  smartDelay(0);
}

static void printInt(unsigned long val, bool valid, int len)
{
  char sz[32] = "*****************";
  if (valid)
    sprintf(sz, "%ld", val);
  sz[len] = 0;
  for (int i=strlen(sz); i<len; ++i)
    sz[i] = ' ';
  if (len > 0) 
    sz[len-1] = ' ';
  Serial.print(sz);
  smartDelay(0);
}

static void printDateTime(TinyGPSDate &d, TinyGPSTime &t)
{
  if (!d.isValid())
  {
    Serial.print(F("********** "));
  }
  else
  {

    char sz[32];
   sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
//    sprintf(sz, "%02d/%02d/%02d ", gps.date.month(), gps.date.day(), gps.date.year());
    Serial.print(sz);
  }
  
  if (!t.isValid())
  {
    Serial.print(F("******** "));
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
//  sprintf(sz, "%02d:%02d:%02d ", gps.time.hour(), gps.time.minute(), gps.time.second());
    Serial.print(sz);

  }

  printInt(d.age(), d.isValid(), 5);
  smartDelay(0);
}

static void printStr(const char *str, int len)
{
  int slen = strlen(str);
  for (int i=0; i<len; ++i)
    Serial.print(i<slen ? str[i] : ' ');
  smartDelay(0);
}

static void afisareecran()
{
// lcd.clear(); // clear the screen

// partea de ora
// ora de vara a Romaniei - 
// trebuie sa pun un comutator pentru selectie de iarna si de vara
 int ora = 3 + gps.time.hour();
 if (ora ==24) ora=0;
 if (ora ==25) ora=1;
 if (ora ==26) ora=2;
 lcd.setCursor(12,2); // put cursor at colon 2 and row 2
 if (ora<10) lcd.print(" "); 
 lcd.print(ora); 
 lcd.print(":");
 if (gps.time.minute()<10) lcd.print("0"); 
 lcd.print(gps.time.minute()); 
 lcd.print(":");
 if (gps.time.second()<10) lcd.print("0"); 
 lcd.print(gps.time.second()); 


// parte de coordonate GPS
 lcd.setCursor(0,0); // put cursor at colon 0 and row 0 = left/up
 lcd.print("LAT:");
 lcd.print(gps.location.lat(),6);
 lcd.write(0b11011111);
 lcd.setCursor(0,1); // put cursor at colon 0 and row 1 
 lcd.print("LON:");
 lcd.print(gps.location.lng(),6);
 lcd.write(0b11011111);

// numar sateliti receptionati
 lcd.setCursor(16,0); // put cursor at colon 15 and row 2
 lcd.print(gps.satellites.value());
// if (gps.satellites.value() == 1) lcd.print(" satelit ");
// else
 lcd.print("sat");

// viteza
/*
lcd.setCursor(10,3); // put cursor at colon x and row y
double viteza = gps.speed.kmph(); 
// tests
// double viteza = 0.;
// double viteza = 5;
// double viteza = 14;
// double viteza = 104;
 if (viteza>100.0) lcd.print(viteza);
 else
 if (viteza>10.0) {lcd.print(" "); lcd.print(viteza);}
 else 
 if (viteza<10.0) {lcd.print("  "); lcd.print(viteza);}
// lcd.print(viteza);
// lcd.print(gps.speed.kmph());
 lcd.print("km/h");
*/

/*
// altitudine
  lcd.setCursor(16,0); // put cursor at colon 16 and row 0
  lcd.print("ALT:");
  int cota = gps.altitude.meters();
  lcd.setCursor(15,1); // put cursor at colon 15 and row 1
// cota=5;
//cota=15;
//cota=497;
//cota=2056;
 if (cota>1000) lcd.print(cota);
 else
 if (cota>100) {lcd.print(" "); lcd.print(cota);}
 else
 if (cota>10) {lcd.print("  "); lcd.print(cota);}
 if (cota<10) {lcd.print("   "); lcd.print(cota);}
 lcd.print("m");
*/

/* 
// viteza extrasa custom
 lcd.setCursor(0,3); // put cursor at colon x and row y
 lcd.print("viteza: ");
 int viteza = atoi (zdop.value());
// lcd.print(zdop.value());
 if (viteza>100.0) lcd.print(viteza);
 else
 if (viteza>10.0) {lcd.print(" "); lcd.print(viteza);}
 else 
 if (viteza<10.0) {lcd.print("  "); lcd.print(viteza);}
 lcd.print("km/h");
*/ 
 }  



 
void custom0O()  // uses segments to build the number 0
{ 
  lcd.setCursor(x, 2); 
  lcd.write(8);  
  lcd.write(1); 
  lcd.write(2);
  lcd.setCursor(x, 3); 
  lcd.write(3);  
  lcd.write(4);  
  lcd.write(5);
}

void custom1()
{
  lcd.setCursor(x,2);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x,3);
  lcd.write(4);
  lcd.write(255);
  lcd.write(4);
}

void custom2()
{
  lcd.setCursor(x,2);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 3);
  lcd.write(3);
  lcd.write(7);
  lcd.write(7);
}

void custom3()
{
  lcd.setCursor(x,2);
  lcd.write(6);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 3);
  lcd.write(7);
  lcd.write(7);
  lcd.write(5); 
}

void custom4()
{
  lcd.setCursor(x,2);
  lcd.write(3);
  lcd.write(4);
  lcd.write(2);
  lcd.setCursor(x+2, 3);
  lcd.write(255);
}

void custom5()
{
  lcd.setCursor(x,2);
  lcd.write(255);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 3);
  lcd.write(7);
  lcd.write(7);
  lcd.write(5);
}

void custom6()
{
  lcd.setCursor(x,2);
  lcd.write(8);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 3);
  lcd.write(3);
  lcd.write(7);
  lcd.write(5);
}

void custom7()
{
  lcd.setCursor(x,2);
  lcd.write(1);
  lcd.write(1);
  lcd.write(2);
  lcd.setCursor(x+1, 3);
  lcd.write(8);
}

void custom8()
{
  lcd.setCursor(x,2);
  lcd.write(8);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 3);
  lcd.write(3);
  lcd.write(7);
  lcd.write(5);
}

void custom9()
{
  lcd.setCursor(x,2);
  lcd.write(8);
  lcd.write(6);
  lcd.write(2);
  lcd.setCursor(x, 3);
  lcd.write(7);
  lcd.write(7);
  lcd.write(5);
}

// subrutina de afisare a numerelor
void afisarenumar(int numar)
{
  switch (numar)
  {
    case 0:
    custom0O();
    break;

    case 1:
    custom1();
    break;

    case 2:
    custom2();
    break;

    case 3:
    custom3();
    break;

    case 4:
    custom4();
    break;

    case 5:
    custom5();
    break;

    case 6:
    custom6();
    break;

    case 7:
    custom7();
    break;

    case 8:
    custom8();
    break;

    case 9:
    custom9();
    break;
  }
}

void vitezamare(int viteza)
{
  // sterg zona de afisare a vitezei
 lcd.setCursor(0,2);
 lcd.print("           ");
 lcd.setCursor(0,3);
 lcd.print("           ");
   
if (viteza>=100)
{
  x = 0;
  afisarenumar(int(viteza/100));
  viteza=viteza % 100;

  x = x + 4;
  afisarenumar(viteza/10);
  
  x = x + 4;
  afisarenumar(viteza % 10);
}
else
if (viteza>=10)
{
 lcd.setCursor(0,2);
 lcd.print("   ");
 lcd.setCursor(0,3);
 lcd.print("   ");
  
  x = 4;
  afisarenumar(viteza/10);
  
  x = x + 4;
  afisarenumar(viteza % 10);
}
else 
if (viteza<10)
{
 lcd.setCursor(0,2);
 lcd.print("       ");
 lcd.setCursor(0,3);
 lcd.print("       ");
  x = 8;
  afisarenumar(viteza);
}
}
   I made few pics with speedometer in moving car:
Also, I made few movies:

   Bibliography:


10 comments:

  1. Why cant I get the "Satellites used" , the number showed on LCD always "0", even I use the "Customfield " function of "TinyGPS.
    Any suggestion ? Thanks for your help !!

    ReplyDelete
  2. if GPS receiver is an older model, you need to have great patience.. sometimes 3-5 minutes

    ReplyDelete
  3. Hi Nicu, great blog, nice place to learn some about Arduino, I did this project, work nice thanks so much José

    ReplyDelete
  4. How accurate have you found the Arduino-based speedometer when compared to the car's own speedo? I know that cars are allowed to read slightly higher than the true speed without failing their MOT (though not lower), so I'm quite curious as to whether you find the home-made one to compare well against the car.

    Raymond @ CKS Global Solutions LTD

    ReplyDelete
  5. Nice work, Nicu,
    I appreciate this posting is quite old and understand that you may not respond to this enquiry.
    Would you be kind enough to post your earlier code versions?
    Alternatively, how difficult is it to invoke clock display, as shown in your videos.
    I can see the relevant code (print date time) in ver.1.6.5 but obviously it is not enabled.
    Same for the 'distance to London' snippet.
    Thank you for your blog.

    ReplyDelete
    Replies
    1. sorry, I losted older projects... you have GPS receiver like mine or newest ?

      Delete
  6. Thank You for your reply, Nicu.
    I am using a UBLOX NEO 6 running at 9600 baud.
    Your sketch displays Lat/Log OK. It displays 'BIG Font' Speedometer digits (00), but has not been tested for speedometer functionality as project is still at prototype stage on bench.
    Thanks once again for your time.
    I do have some older Jupiter GPS Modules somewhere, is it worth testing one of those with your sketch?

    ReplyDelete
    Replies
    1. your GPS module is different than GPS module used be me...
      you must identify the fields send by your module, see, for example, https://randomnerdtutorials.com/guide-to-neo-6m-gps-module-with-arduino/

      Delete
  7. Thanks once again, Nicu,
    So, if I utilise UBLOX U Centre application and configure GPS module to send GPVTG sentence, it should work?

    ReplyDelete