Arduino Speedometer and Tachometer (pictures, instruction & code)

I have created a speedometer for my motorcycle and am still working on a tachometer. My motorcycle speedometer has several functions such as temperature readings and time tracking, but I also created a more universal and simple concept that you can use on any vehicle.

The simple speedometer displays your speed, an odometer and a tripmeter. The tripmeter can be reset by holding a button for 3 seconds or more. Here is a picture of a breadboard setup:

The speed is displayed by large numbers. Odo and tripmeter values are stored in the EEPROM. I have included a memory wear leveller to avoid wearing out the EEPROM.

There are 2 pieces of code, one that you run to prepare the memory positions and to set the odometer and tripmeter values and another piece of code that is actually the speedometer.

PDF file with instructions (including conversion to MPH and miles) and the two programs can be found here. (here is a mirror).

My more advanced speedometer has more functions:

  • speed
  • odometer
  • tripmeter
  • service meter (keeps track of miles since last service of the engine)
  • clock
  • air temperature
  • oil sump temperature
  • cilinder head temperature
  • battery voltage

the main screen will show a warning when something is wrong such as a too low battery voltage.
Here is a youtube video that shows some of the functions.

Code and PDF with instructions can be found here. I'm still working on it and this version has a small bug in the large speed numbers which is fixed in the earlier mentioned stripped version. Will this code later.

Here are some pictures of the hardware.
I have built everything into the original speedometer unit of a Suzuki GS500 motorcycle with the help of some 3D printed parts.

A bit of a wire mess inside

I'm also working on a tachometer that also indicates the gear the motorcycle is in. It is powered by two switec x27 steppers.
Here a youtube video of the prototype
Here a video of the gear indicator

Here some pictures of the hardware

I'm not sharing this code yet as I have not tested it properly yet and I don't have a PDF with some additional information yet either.

I figured I'd share this stuff because somebody might be able to use it as well as I can. Especially the simple speedometer could be nice for people because you can build one very cheaply.

I wrote some functions that I think you will find useful. These are for formatting numbers.

Examples:
showPaddedInt(100, 3) gives you "100" (just like that)
showPaddedInt(99, 3) gives you " 99" (notice the space in front)
showPaddedInt(7, 3) gives you "  7" (notice the two spaces in front)
showPaddedFloat(9.738, 5, 2) gives you " 9.74" (rounding is done for you, too)

void showPaddedInt (int num, int cells) {
  while (cells>6) {
    lcd.print(" ");
    cells--;
  }
  if ((cells>5) && (num>=-9999))                 lcd.print(" ");
  if ((cells>4) && (num>=-999)  && (num<=9999))  lcd.print(" ");
  if ((cells>3) && (num>=-99)   && (num<=999))   lcd.print(" ");
  if ((cells>2) && (num>=-9)    && (num<=99))    lcd.print(" ");
  if ((cells>1) && (num>=0)     && (num<=9))     lcd.print(" ");
  lcd.print(num);
}


void showPaddedLong (long num, int cells) {
  while (cells>11) {
    lcd.print(" ");
    cells--;
  }
  if ((cells>10) && (num>=-999999999L))                     lcd.print(" ");
  if ((cells>9)  && (num>=-99999999L) && (num<=999999999L)) lcd.print(" ");
  if ((cells>8)  && (num>=-9999999L)  && (num<=99999999L))  lcd.print(" ");
  if ((cells>7)  && (num>=-999999L)   && (num<=9999999L))   lcd.print(" ");
  if ((cells>6)  && (num>=-99999L)    && (num<=999999L))    lcd.print(" ");
  if ((cells>5)  && (num>=-9999L)     && (num<=99999L))     lcd.print(" ");
  if ((cells>4)  && (num>=-999L)      && (num<=9999L))      lcd.print(" ");
  if ((cells>3)  && (num>=-99L)       && (num<=999L))       lcd.print(" ");
  if ((cells>2)  && (num>=-9L)        && (num<=99L))        lcd.print(" ");
  if ((cells>1)  && (num>=0L)         && (num<=9L))         lcd.print(" ");
  lcd.print(num);
}

void showPaddedFloat (float num, int cells, int places=1) {
  if (places<0) places=0; // because lower values are not supported
  if (places>6) places=6; // because higher values are not supported
  boolean neg = false;
  long adj = (long)((num * pow(10.0, places)) + ((num>0.0)?0.5:(-0.5)));
  if (adj<0) {
    neg = true;    
    adj = -adj;
    cells--; // to leave room for the minus sign
  }
  if (places>0) {
    cells -= (places + 1);
  }
  long divisor = 1;
  for (int i=1; i<=places; i++) {
    divisor *= 10;
  }
  long frac = adj % divisor;
  adj /= divisor;
  if ((cells>9) && (adj<=999999999L)) lcd.print(" ");
  if ((cells>8) && (adj<=99999999L)) lcd.print(" ");
  if ((cells>7) && (adj<=9999999L)) lcd.print(" ");
  if ((cells>6) && (adj<=999999L)) lcd.print(" ");
  if ((cells>5) && (adj<=99999L)) lcd.print(" ");
  if ((cells>4) && (adj<=9999L)) lcd.print(" ");
  if ((cells>3) && (adj<=999L)) lcd.print(" ");
  if ((cells>2) && (adj<=99L)) lcd.print(" ");
  if ((cells>1) && (adj<=9L)) lcd.print(" ");
  if (neg) lcd.print("-");
  lcd.print(adj);
  if (places>0) {
    lcd.print(".");
    if ((places>5) && (frac<=99999L)) lcd.print("0");
    if ((places>4) && (frac<=9999L)) lcd.print("0");
    if ((places>3) && (frac<=999L)) lcd.print("0");
    if ((places>2) && (frac<=99L)) lcd.print("0");
    if ((places>1) && (frac<=9L)) lcd.print("0");
    lcd.print(frac);
  }
}

Hi,

nice work!
You did the speed and trip measuring by a hall sensor, as I read in your sketch?
As I´ve seen, the Suzuki GS500 has a diagnostic plug, where you could get all the required data from the bikes ECU. Maybe something for another project :slight_smile:

Correct, speed is calculated from hall sensor input (it's described in the PDF as well). Old GS500 models like mine do not have a diagnostic plug.

I like your work on both versions of your speedometer & plan to build one to replace the failed one in my 1980 VW Rabbit. I'd like to ask if you've considered using using a power down detection circuit & code similar to power down data saving - Storage - Arduino Forum instead your current method of writing to eeprom "on a schedule with wear leveling"?

Thanks very much for posting your work
Bill

Hi,

Complete newbee to all the arduino forum but i've been dabbling for a little while. I thought I'd drop your code onto a nano that I have and give it a whirl. It worked great which was an excellent start to me towards working out some gauges for a car.

I modified the code to use I2C and managed to get it working with my LCD and backer.

Funny thing is though that after a couple of goes with it, the speed portion just started spuriously printing numbers, in the 300ish range. I cleared the eeprom and tried again setting up the memorysetter and it does the same. Tried it with another arduino, same problem. :frowning: Something is blocking the code as I'm unable to use the button to reset the trip.

Any ideas at all? I've attached the code below, though I can't see other than the LCD that it's any different.

Many thanks in advance, was so happy this was working for me!

Al (code in next post - I've removed the markup to get the character count down...)

#include <LiquidCrystal_I2C.h>
#include<lcd.h>
#include <EEPROM.h>

#define I2C_ADDR    0x3F 
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C  lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

//declaring variables
int hallpin=1; 
int hallstate=0;
int hallcounter=0;

long odometer=0;
int tripmeter=0;
int servicemeter=0;
int rotationcounter=0;

int vspeed=0;
int dvspeed=0; 
int tyrec=1935; 
long deltat=0; 
long timea=0; 
long timec=0;


int b1pin=6;
int b1prevstate=HIGH;
int b1state=HIGH;
long b1timepressed=0;
long b1timereleased=0;
long b1holdtime=0;
int b1waspressed=0;


int speedrr=500; 
const int speedarraysize=5;  
int speedarray[speedarraysize];
int arrayposition=0;


int odoaddress=1;
int tripaddress=odoaddress+8;
int serviceaddress=odoaddress+15;

byte bar1[8] = 
{
        B11100,
        B11110,
        B11110,
        B11110,
        B11110,
        B11110,
        B11110,
        B11100
};
byte bar2[8] =
{
        B00111,
        B01111,
        B01111,
        B01111,
        B01111,
        B01111,
        B01111,
        B00111
};
byte bar3[8] =
{
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111
};
byte bar4[8] =
{
        B11110,
        B11100,
        B00000,
        B00000,
        B00000,
        B00000,
        B11000,
        B11100
};
byte bar5[8] =
{
        B01111,
        B00111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00011,
        B00111
};
byte bar6[8] =
{
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111,
        B11111
};
byte bar7[8] =
{
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B00111,
        B01111
};
byte bar8[8] =
{
        B11111,
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000
};

void setup() {
//declare LCD size
lcd.begin(16, 2);
lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
lcd.setBacklight(HIGH);

attachInterrupt(hallpin, hallfunction, RISING); 

pinMode(b1pin, INPUT_PULLUP);

addressselector();

  lcd.createChar(1,bar1);
  lcd.createChar(2,bar2);
  lcd.createChar(3,bar3);
  lcd.createChar(4,bar4);
  lcd.createChar(5,bar5);
  lcd.createChar(6,bar6);
  lcd.createChar(7,bar7);
  lcd.createChar(8,bar8);
/////////end of bigfont stuff

}

void loop() {
//check the speed
speedcheck();
//check if something is going on with the button
buttoncheck();

lcd.setCursor(10,0);
lcd.print(odometer);

lcd.setCursor(10,1);
lcd.print(tripmeter);

printlargespeed();

}

void hallfunction(){
  vspeed=tyrec*3.6/(millis()-deltat);
  timea=millis();
  deltat=millis();
  rotationcounter=rotationcounter+1;
}

void speedcheck(){

if (millis()-timea > 1000){
  vspeed=0;
  timea=millis();
}

if (millis()-timec >= speedrr/speedarraysize){ 
  speedarray[arrayposition]=vspeed;
  arrayposition=arrayposition+1;

  if (arrayposition == speedarraysize){
    arrayposition=0;
    dvspeed=0;
    
    for (int x=0; x<speedarraysize; x++)
    {
      dvspeed=dvspeed+speedarray[x];
    }
    
    /*
     * The following line calculates the speed in km/h.
     * If you want the speed in MPH change it into this:
     * dvspeed=(dvspeed/speedarraysize)*0.621371192;
     */
    dvspeed=dvspeed/speedarraysize;  
    
  }
  timec=millis();
}

//actions after driving 1km
if (rotationcounter >= 1000000/tyrec){
  odometer=odometer+1;
  memorywrite(odoaddress, odometer); 
  
  tripmeter=tripmeter+1;
  if (tripmeter > 999){
    tripmeter=0;
  }
  memorywrite(tripaddress, tripmeter); 

  //call the wearleveller to reduce EEPROM wear
  wearleveller();
  
  rotationcounter=0;
}
}

void buttoncheck(){
  b1state=digitalRead(b1pin);
   
if (b1state != b1prevstate){
    if (b1state == LOW){ 
    b1timepressed=millis();
    b1waspressed=1;
    }
  

  if (b1state == HIGH){
    b1timereleased=millis();
    b1waspressed=0;
    b1holdtime=b1timereleased-b1timepressed;

    lcd.clear();
  }

  }
  b1prevstate=b1state;


if (b1waspressed==1){
  if (millis()-b1timepressed > 3000){
        tripmeter=0;
        memorywrite(tripaddress, tripmeter); 
        
    b1waspressed=0;
  }
}

  
}

void addressselector(){

  odometer=memoryread(odoaddress);
  tripmeter=memoryread(odoaddress+8);
  servicemeter=memoryread(odoaddress+15);
  
  for (int pos=1; pos<252; pos=pos+25){

    if (memoryread(pos)> odometer){ 
  
      odoaddress=pos;
      odometer=memoryread(pos);

      tripaddress=pos+8;
      tripmeter=memoryread(tripaddress);

      serviceaddress=pos+15;
      servicemeter=memoryread(serviceaddress);  
    } 
  }
}
void wearleveller(){

  odoaddress=odoaddress+25;
  tripaddress=tripaddress+25;
  serviceaddress=serviceaddress+25;
  
  if (odoaddress >= 250){
    odoaddress=1;
    tripaddress=odoaddress+8;
    serviceaddress=odoaddress+15;
  }
  
}
void memorywrite(int address, long value){
 
long val1=value/100000;
value=value-val1*100000;

long val2=value/10000;
value=value-val2*10000;

int val3=value/1000;
value=value-val3*1000;

int val4=value/100;
value=value-val4*100;

int val5=value/10;
int val6=value-val5*10;

   EEPROM.write(address, val1);
   EEPROM.write(address+1, val2);
   EEPROM.write(address+2, val3);
   EEPROM.write(address+3, val4);
   EEPROM.write(address+4, val5);
   EEPROM.write(address+5, val6); 
}
long memoryread(int address){
  
  long val1=EEPROM.read(address);
  long val2=EEPROM.read(address+1);
  int val3=EEPROM.read(address+2);
  int val4=EEPROM.read(address+3);
  int val5=EEPROM.read(address+4);
  int val6=EEPROM.read(address+5);
  long result=val1*100000+val2*10000+val3*1000+val4*100+val5*10+val6;

  return result;
}

void printlargespeed(){
  int c, d, u, number;
  number = dvspeed;
  
  if (number > 99) {
    c = (number - (number % 100)) / 100;
    number = number % 100;
  } else {
    c = 0;
  }  

  if (number > 9) {
    d = (number - (number % 10)) / 10;
    number = number % 10;
  } else {
    d = 0;
  }  
  
  u = number;
  
  if (dvspeed <10){
      lcd.setCursor(0,0);
    lcd.print("      ");
    lcd.setCursor(0,1);
    lcd.print("      ");
       printNumber(u, 6);
  }
  
  if (dvspeed >= 10 && dvspeed < 100){
          lcd.setCursor(0,0);
    lcd.print("   ");
    lcd.setCursor(0,1);
    lcd.print("   ");
  printNumber(d, 3);
  printNumber(u, 6);
  }
  if (dvspeed >= 100){
    lcd.setCursor(0,0);
  printNumber(c, 0);
  printNumber(d, 3);
  printNumber(u, 6);
  }

}

Thank you for this post i would test this speedometer on m'y motorcycle
Is it possible to reload the PDF file please

Can we get a re-post of the original code and instructions please?

The links in the post(s) above no longer work.

Many thanks,

:w!

Hi guys, can you tell me what to change in the code to optain meters instead km on trip value?. Thanks

Any chance of a new link for the code? the links are dead..thanks

This is a great project. For some reason, some of the links, code, and photos aren't available. Here is another tutorial that shows users how to use a hall effect sensor to calculate RPM using an Arduino and magnet attached to a rotating shaft:

Arduino Tachometer - Using a Hall Effect Sensor (A3144) to Measure Rotations from a Fan

This is a photo of the wiring diagram:

Of course, you can use any device with a magnet attached to its rotating shaft. The magnet must be within reach of the hall sensor. Here is a photo of the experimental setup:

Here is a link to the files.