My digital speedometer project

I figured I might as well join the forum and share some info about the speedometer project I've been working on over the last few months.

As of now it's running on a Seeeduino V328 with 2 stacked protoshields and a 20-digit 2-line LCD. Vehicle movement is sensed via an ABS sensor on the left front wheel, which provides 54 pulses per wheel revolution. This allows the displayed speed to be valid down to 1 MPH. There is also a 5+1 digit odometer, a 3+1 digit trip odometer, and a power-fail circuit that saves the odometer data into EEPROM when a power failure is detected. The power fail circuit uses a MAX690 IC to trigger an interrupt when the incoming power supply voltage falls below 10 volts. An electrolytic capacitor keeps the circuit alive just long enough for the data to get written to the EEPROM...almost. I recently discovered that the last byte to get written isn't quite making it. (Time for a bigger capacitor)

Vehicle speed is displayed on the LCD in a 2-line block font, which was inspired by the MPGuino, and uses code that I adapted from an example posted by whosawhatsis.

I've been working on a menu to allow different options. The first is calibration, which can be used to dial in the accuracy while in use. The calibration value is also saved in EEPROM when exiting the calibration function. The second menu item brings up a distance meter that reads in feet. When I discovered that the ABS sensor sends a pulse for every 1.86 inches of travel, I thought "Why not?"

Eventually I plan to make a PCB for a DIP Atmega328 and all the additional circuitry, once I decide that I've tweaked the design enough. I also have a backlit 20-digit 4-line LCD that I intend to use, with a 3-line block font for the speed.

Here's an early picture with the odometer in its infancy:

Here's a more recent one with updated odometer. No, I wasn't really driving that fast! :smiley: I was feeding a fast square wave into the circuit to run a test.

One more showing the "footometer" mode (it rolls over at 100000 feet):

I like it. :slight_smile: Any chance you'll share your sketch?

Thanks!
I couldn't figure out another way to post the sketch other than cut it up into chunks, so here goes...

/*
Speedometer / Odometer Arduino Sketch
by ElectricWater

Acknowledgements:
dfowler / uchobby.com for Timer2 code
MPGuino for Large Font inspiration
whosawhatsis / Arduino forum for Large Font code

This sketch detects a square wave input using external interrupt 0.
The signal's frequency is proportional to the vehicle's speed.
The sketch also detects internal Timer2's overflow.
The two are used to calculate the vehicle's speed, and the square wave count is also
used to report the distance travelled.

Interrupt 0 (Digital pin 2) is connected to the vehicle's speed sensor.
An interrupt service routine triggered by a rising edge on Interrupt 0 increments
a counter (SensorCount).

Timer2 in the AVR is reconfigured to run at 125 KHz.
An interrupt service routine triggered by Timer2's overflow increments a counter
(TimerCount) by 8 and tests to see if the counter has reached a predetermined constant
value (CalValue - used for calibration). If so, 3 things happen:
 - The value of SensorCount is copied intothe variable "Freq" for use in the main loop
 - The "Trigger" flag gets set
 - SensorCount and TimerCount get reset
TimerCount reaches CalValue at a very consistent rate, which is used to calculate the vehicle speed.
When CalValue is properly calibrated the value of Freq is equal to the actual speed in Miles per Hour.

The Timer2 ISR aslo causes digital pin 4 to toggle between high and low for testing purposes.

Note: The reason for incrementing TimerCount by 8 is that the sketch when first written
originally configured Timer2 to run at 2 MHz, which is 8 times faster than its current 250 KHz.
Slowing down Timer2 to 1/8 its original speed and having the ISR add 8 instead of 1
allows the AVR to spend less time servicing interrupts. This change resulted in no
noticeable degradation in speedometer function.

The main loop tests to see if the Trigger flag is set. If so, several things happen:
 - The variable "Speed" is set to the average of the 3 most recent cycles of "Freq"
 - Speed then gets printed on the LCD in large 2-line block font.
 - Freq is used to update the odometer
 - Odometer information is printed to the LCD...as long as we're not in a menu
 
The main odometer uses the variables TotalClicks, TotalTenths, and TotalMiles.
The trip odometer uses the variables DistanceClicks, DistanceTenths, and DistanceMiles.

TotalClicks is increased by the value of Freq each time Trigger is set, which means that it is
a running total of the speed sensor's pulses. CalDist is a calibration value that when adjusted
correctly is equal to the number of sensor pulses per 1/10 mile. Therefore, when TotalClicks
reaches CalDist the vehicle has travelled 1/10 mile, so TotalTenths is incremented and CalDist
is subtracted from TotalClicks. When TotalTenths reaches 10, TotalMiles is incremented and
TotalTenths is reset to zero. When TotalMiles reaches 100000 it gets rolled over back to zero.

The trip odometer then goes through the same process, except that DistanceMiles rolls over at 1000.

A trip odometer reset button is provided via a pushbutton switch connected to digital pin 6 (Button1).
Digital pin 6 has its internal pull-up resistor turned on and the pushbutton drives the pin low when pressed.
When a low is detected it resets DistanceClicks, DistanceTenths, and DistanceMiles to zero.

Interrupt 1 (Digital pin 3) is connected to a power-fail signal that goes low when the unregulated DC voltage
drops below ~10 volts. When ISR1 is called it copies all of the odometer data into EEPROM for use the next
time the circuit is powered up. Upon power-up the EEPROM is read back into the odometer variables.

An options menu is beginning to be implemented. Menu navigation is controlled by the 4
pushbutton switches connected to digital pin 6 and analog pins 0-2 (digital pins 14-16).
The A0 button (Button2) is used to enter the menu mode, and also to exit menu mode.
A1 (Button3) and A2 (Button4) is used to flip through the menus.
D6 (Button1) is used to select a menu option.
Once inside a menu option, Button3 and Button4 is used to change the data.
Button1  is used as an "OK" or "Enter" button, while Button2 is used as an "Exit" or "Escape" button.

The first menu option is to adjust the calibration value (CalDist) for the speed sensor count. (CalValue
is calculated from CalDist). When the CalDist is changed and "OK-ed" it will be written into EEPROM,
which is read again on power-up.

The second menu option is to display a temporary distance meter that reads in feet.

MenuMode is an integer variable that will change depending on which menu the user is in.
MenuMode = 0, no menu
MenuMode = 1, calibrate speedometer
MenuMode = 2, foot distance display mode


-----The circuit connections-----

 Speed sensor input to Digital pin 2 (interrupt 0)
 Power fail input to Digital pin 3 (interrupt 1)
 Timer2 test output is on Digital pin 4

LCD hookups:
 * LCD Vss pin (pin 1) to ground
 * LCD Vcc pin (pin 2) to +5V
 * LCD VO pin (pin 3) to ground
 * LCD RS pin (pin 4) to Digital pin 7
 * LCD R/W pin (pin 5) to Digital pin 5
 * LCD Enable pin (pin 6) to Digital pin 8
 * LCD D4 pin (pin 11) to Digital pin 9
 * LCD D5 pin (pin 12) to Digital pin 10
 * LCD D6 pin (pin 13) to Digital pin 11
 * LCD D7 pin (pin 14) to Digital pin 12
 
 Pushbutton switches:
 Button1 to Digital pin 6
 Button2 to Analog pin 0 (Digital pin 14)
 Button3 to Analog pin 1 (Digital pin 15)
 Button4 to Analog pin 2 (Digital pin 16)
 
 Physical protoshield layout of the pushbuttons:
   ----  ----
  |Btn4||Btn3|
   ----  ----
           ----
          |Btn2|
           ----
           ----
          |Btn1|
           ----

*/

// include the EEPROM library code:
#include <EEPROM.h>

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 5, 8, 9, 10, 11, 12);

// build 2-line digit font data array
// Digits are 3 characters wide.
byte bignums[10][2][3] = {
// Define which characters to use for each number. 255 is a solid block; 254 is a space  
// The format is { {TopLeft, TopMiddle, TopRight}, {BottomLeft, BottomMiddle, BottomRight} }
 { {255, 0, 255}, {255, 1, 255} },        // data to display "0"
 { {0, 255, 254}, {1, 255, 1} },          // data to display "1"
 { {2, 2, 255}, {255, 1, 1} },            // data to display "2"
 { {0, 2, 255}, {1, 1, 255} },            // data to display "3"
 { {255, 1, 255}, {254, 254, 255} },      // data to display "4"
 { {255, 2, 2}, {1, 1, 255} },            // data to display "5"
 { {255, 2, 2}, {255, 1, 255} },          // data to display "6"
 { {0, 0, 255}, {254, 255, 254} },        // data to display "7"
 { {255, 2, 255}, {255, 1, 255} },        // data to display "8"
 { {255, 2, 255}, {254, 254, 255} }       // data to display "9"
};

int CalDist;                              // Calibration value for the odometer
                                          // Increase to increment slower, decrease to increment faster
                                          // Equal to the number of speed sensor pulses per 1/10 mile

int CalValue;                             // Calibration value for the speedometer
                                          // Gets calculated from CalDist
                                          // Increase to read faster, decrease to read slower
                                          
/*   ---Time and distance math---

  Timer2 runs at 250 KHz and counts from 0 to 255 before overflow.
  Each overflow triggers an interrupt adding +8 to TimerCount. This happens 976.5625 times per second.
  Therefore, TimerCount increases by an average of 7812.5 per second. We'll call these increases in value "counts".
  We'll call one pulse from the speed sensor 1 click.
  
  CalValue "counts" = 1 mile/hour 
  
  Formulas for relating CalValue to CalDist are as follows:
  
                                   7812.5 counts       1 click       1 hour   3600 sec      1 mile       2812500
  CalDist (clicks per 1/10 mile) = ------------- x --------------- x ------ x -------- x ------------ = --------
                                       1 sec       CalValue counts   1 mile    1 hour    10 1/10 mile   CalValue
  
                      7812.5 counts   3600 sec   1 hour      1 mile      1 1/10 mile    2812500
  CalValue (counts) = ------------- x -------- x ------ x ------------ x ----------- = ---------
                          1 sec        1 hour    1 mile   10 1/10 mile     CalDist      CalDist   
  
*/
const byte SqWavePin = 4;                 // Square wave genereated from digital pin 4
const byte Button1 = 6;                   // Pushbutton switch D6, connected to ground
const byte Button2 = 14;                  // Pushbutton switch A0, connected to ground
const byte Button3 = 15;                  // Pushbutton switch A1, connected to ground
const byte Button4 = 16;                  // Pushbutton switch A2, connected to ground

byte MenuMode = 0;                        // MenuMode 0 means not in a menu

boolean MenuModeActive = false;           // Only goes true when a menu item has been selected

unsigned long ButtonsOffTime;             // How long since last button was released

boolean Button1State;                     // State of Button1 from last cycle
boolean Button2State;                     // State of Button2 from last cycle
boolean Button3State;                     // State of Button3 from last cycle
boolean Button4State;                     // State of Button4 from last cycle
boolean ButtonPressed = false;            // Becomes true when a button press is detected
boolean ButtonPressedPrev = false;        // Previous value of ButtonPressed

int CalDistPrev;                          // Previous value of CalDist during calibration

int SensorCount = 0;                      // Speed Sensor pulse count
int TimerCount = 0;                       // Timer2 overflow count
int Freq;                                 // Copy of SensorCount, and is the actual vehicle speed
boolean Trigger = false;                  // When it becomes true it triggers the print cycle
boolean SqWaveOut = false;                // Used to toggle output state of pin 4
int FreqPrev1 = 0;                        // Previous value of Freq, used for averaging speed
int FreqPrev2 = 0;                        // Another previous value of Freq, used for averaging speed
int Speed;                                // Average of all 3 Freq variables, and is what is displayed on LCD

int DistanceMiles;                        // Cumulative distance travelled in miles
int DistanceTenths;                       // Cumulative distance travelled, tenths digit
int DistanceClicks;                       // Cumulative speed sensor pulses received, reduced by CalDist each 1/10 mile
unsigned long TotalMiles;                 // Total distance travelled in miles
int TotalTenths;                          // Total distance travelled, tenths digit
int TotalClicks;                          // Total speed sensor pulses received, reduced by CalDist each 1/10 mile

long DistanceFeet;                        // Cumulative distance travelled in feet, temporary display option
unsigned int DistanceFeetTenths;          // Cumulative distance travelled in feet, tenths digit, temporary display option
int FeetScaleFactor;                      // Scale Factor used to calculate feet from speed sensor pulses


ISR(TIMER2_OVF_vect) {                    // This is the Interrupt Service Routine, called when Timer2 overflows

  TimerCount +=8;                         // Increment TimerCount by 8
  if (TimerCount > CalValue)  {           // Check to see if it's time to display the count
    Freq = SensorCount;                   // Copies the interrupt 0 count into variable "Freq"
    Trigger = true;                       // sets "Trigger" to let the main loop know it's time to print data
    SensorCount = 0;                      // Reset the interrupt 0 count
    TimerCount -= CalValue;               // Reset Timer2 count, but keep the excess for next time around
  }
  SqWaveOut = !SqWaveOut;                 // Toggle SqWaveOut to its opposite state
  digitalWrite(SqWavePin, SqWaveOut);     // Set pin 4 to the new state of SqWaveOut

}


void setup()  {
  
// turn on DigitalPin 13 LED to signal that the custom characters have been loaded into the LCD  
  pinMode(13, OUTPUT);
  loadchars();
  digitalWrite(13, HIGH);

  pinMode(SqWavePin, OUTPUT);                  // Output on pin 4 will be oscillating
  pinMode(Button1, INPUT);                     // Set Button1 pin as input
  pinMode(Button2, INPUT);                     // Set Button2 pin as input
  pinMode(Button3, INPUT);                     // Set Button3 pin as input
  pinMode(Button4, INPUT);                     // Set Button4 pin as input
  digitalWrite(Button1, HIGH);                 // Turn on Button1's internal pull-up resistor
  digitalWrite(Button2, HIGH);                 // Turn on Button2's internal pull-up resistor
  digitalWrite(Button3, HIGH);                 // Turn on Button3's internal pull-up resistor
  digitalWrite(Button4, HIGH);                 // Turn on Button4's internal pull-up resistor
  
  CalDist = (int(EEPROM.read(101)) * 256) + (int(EEPROM.read(102)));  // read CalDist out of EEPROM
  CalValue = int((2812500.0 / CalDist) + .5);               // calculate CalValue from CalDist

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

  // read total mileage out of EEPROM
  TotalMiles = (EEPROM.read(5) * 65536) + (EEPROM.read(6) * 256) + (EEPROM.read(7));
  TotalTenths = EEPROM.read(8);
  TotalClicks = (EEPROM.read(9) * 256) + (EEPROM.read(10));
  
  // read Distance out of EEPROM
  DistanceMiles = (EEPROM.read(0) * 256) + (EEPROM.read(1));
  DistanceTenths = EEPROM.read(2);
  DistanceClicks = (EEPROM.read(3) * 256) + (EEPROM.read(4));
  
// print the speed in big font
    lcd.setCursor(0, 0);                  // set the cursor to (13,0)
    Freq = Freq % 1000;                   // drop any digits above 999
    printbigchar(int(Freq / 100),0);      // print the speed hundreds
    Freq = Freq % 100;                    // drop any digits above 99
    printbigchar(int(Freq / 10),1);       // print the speed tens
    Freq = Freq % 10;                     // drop any digits above 9
    printbigchar(Freq,2);                 // print the speed ones
  // write Distance to LCD
  printmileage();
  
  attachInterrupt(0, AddSensorCount, RISING);  // Interrupt 0 is on digital pin 2
  attachInterrupt(1, EEPROMwrite, FALLING);    // Interrupt 1 is on digital pin 3

  //  -------------------------------------------------------------------------------------
  //  This bit of code is adapted from an article by dfowler at uchobby.com
  //  http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/
  
  //Timer2 Settings: Timer Prescaler / 64, mode 0
  //Timmer clock = 16 MHz / 64 = 250 KHz or 0.5us
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 0<<CS21 | 0<<CS20;      // Set Timer2 frequency to 250 KHz
                                             // Used to be 010 for 2 MHz clock

  //Timer2 Overflow Interrupt Enable   
  TIMSK2 = 1<<TOIE2;
  
  
  //  -------------------------------------------------------------------------------------
  
  ButtonsOffTime = millis();                 // Set ButtonStateTime to current time
  
}
void loop()  { 

  if (Trigger)  {                         // Print data if the "Trigger" is set
// calculate average speed for printing
    Speed = int((Freq + FreqPrev1 + FreqPrev2) / 3);

// print the speed in big font
    Speed = Speed % 1000;                 // drop any digits above 999
    printbigchar(int(Speed / 100), 0);    // print the speed hundreds
    Speed = Speed % 100;                  // drop any digits above 99
    printbigchar(int(Speed / 10), 1);     // print the speed tens
    Speed = Speed % 10;                   // drop any digits above 9
    printbigchar(Speed, 2);               // print the speed ones
    
// Main odometer update
    TotalClicks += Freq;                  // Add Freq to TotalClicks
    while (TotalClicks >= CalDist)  {     // Check if TotalClicks has reached CalDist (1/10 mile travelled)
      TotalTenths ++;                     // increment TotalTenths and
      TotalClicks -=CalDist;              // reduce TotalClicks by CalDist
    }
    while (TotalTenths >= 10)  {          // Check if TotalTenths has reached 10
      TotalMiles ++;                      // if so, increment TotalMiles and
      TotalTenths -= 10;                  // decrease TotalTenths by 10
    }
    while (TotalMiles > 99999)  {         // Check if TotalMiles has reached 100000
      TotalMiles -= 100000;               // if so, decrease TotalMiles by 100000 (rollover odometer)
    }
    
// Trip odometer update
    DistanceClicks += Freq;               // Add Freq to DistanceClicks
    while (DistanceClicks >= CalDist)  {  // Check if DistanceClicks has reached CalDist (1/10 mile travelled)
      DistanceTenths ++;                  // if so, increment DistanceTenths and
      DistanceClicks -=CalDist;           // reduce DistanceClicks by CalDist
    }
    while (DistanceTenths >= 10)  {       // Check if DistanceTenths has reached 10
      DistanceMiles ++;                   // if so, increment DistanceMiles and
      DistanceTenths -= 10;               // decrease DistanceTenths by 10
    }
    while (DistanceMiles > 999)  {        // Check if DistanceMiles has reached 1000
      DistanceMiles -= 1000;              // if so, decrease DistanceMiles by 1000 (rollover odometer)
    }
    
    if (MenuMode == 0)  {                 // if we're not in a menu
      printmileage();                     // then print the odometer
    }
    
// foot distance display mode
// DistanceFeetTenths is value is 100 times the actual value. This to allow for
// much greater precision while avoiding the use of a float variable.
    if ((MenuMode == 2) && MenuModeActive)  {          // are we in foot distance display mode? If so...
      DistanceFeetTenths += (Freq * FeetScaleFactor);  // Update DistanceFeetTenths
      while (DistanceFeetTenths >= 1000)  {            // Has the tenths digit overflowed (exceeded 10.00)?
        DistanceFeet ++;                               // If so, increment DistanceFeet by 1,
        DistanceFeetTenths -= 1000;                    // and decrease tenths digit by 1000 (represending 10.00)
      }
      while (DistanceFeet > 99999)  {                  // Has DistanceFeet reached 100000?
        DistanceFeet -= 100000;                        // If so, roll it over (drop it by 100000 feet)
      }
      printfeet();                                     // print the distance in feet
    }
    
// copy speed values for use next cycle
    FreqPrev2 = FreqPrev1;
    FreqPrev1 = Freq;
    
    Trigger = false;                      // Reset "Trigger"...until TimerCount reaches CalValue again
  }
  
// Read the pushbutton switches for user input
  Button1State = digitalRead(Button1);    // Read the state of Button1
  Button2State = digitalRead(Button2);    // Read the state of Button2
  Button3State = digitalRead(Button3);    // Read the state of Button3
  Button4State = digitalRead(Button4);    // Read the state of Button4
  
  // ButtonPressed is true if any buttons are pressed, otherwise it's false
  ButtonPressed = !(Button1State && Button2State && Button3State && Button4State);
  
  // If a button was just pressed for the first time then reset the ButtonsOffTime timer
  if ((ButtonPressed == false) && (ButtonPressedPrev == true))  {
    ButtonsOffTime = millis();
  }
  // test to see if it's been at over 50 mS since last button was released
  // and that a button is being pressed for the first time around
  if (((millis() - ButtonsOffTime) > 50) && (ButtonPressed == true) && (ButtonPressedPrev == false))  {
    
    switch (MenuMode)  {                              // which menu is currently active?
      
      case 1:  {                                      // MenuMode 1 is the Calibration screen
        
        if (Button1State == LOW) {                    // test for Button1 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            MenuModeActive = false;                   // Set MenuModeActive to false
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("  CAL   ");                    // overwrite total mileage with "CAL" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite CalDist with spaces
            //write CalDist to EEPROM:
            EEPROM.write(101, byte((CalDist & 65280)/256));
            EEPROM.write(102, byte((CalDist & 255)));
          }
          else  {
            MenuModeActive = true;                    // Set MenuModeActive to true
            CalDistPrev = CalDist;                    // copy CalDist to CalDistPrev
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("  CAL:  ");                    // overwrite total mileage with "CAL:" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite trip mileage with spaces
            lcd.setCursor(15, 1);                     // set the cursor to (13,1)
            lcd.print(CalDist);                       // print CalDist
          }
        }
        
        if (Button2State == LOW)  {                   // test for Button2 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            MenuModeActive = false;                   // Set MenuModeActive to false
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("  CAL   ");                    // overwrite total mileage with "CAL" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite CalDist with spaces
            CalDist = CalDistPrev;                    // set CalDist back to CalDistPrev (toss changes)
            CalValue = int((2812500.0 / CalDist) + .5); // recalculate CalValue from CalDist
          }
          else  {
            MenuMode = 0;                             // Set MenuMode to 0
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("       ");                     // overwrite "CAL" with spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite CalDist with spaces
            printmileage();                           // display the odometer
          }
        }
        
        if (Button3State == LOW)  {                   // test for Button3 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            CalDist ++;                               // Increment CalDist
            CalValue = int((2812500.0 / CalDist) + .5); // recalculate CalValue from CalDist
            lcd.setCursor(15, 1);                     // set the cursor to (13,0)
            lcd.print(CalDist);                       // print CalDist
          }
          else  {
            MenuMode = 2;                             // Go to the previous menu
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print(" FEET  ");                     // overwrite menu item with "FEET:" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite lower menu area with spaces
          }
        }
      
        if (Button4State == LOW)  {                   // test for Button4 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            CalDist --;                               // decrement CalDist
            CalValue = int((2812500.0 / CalDist) + .5); // recalculate CalValue from CalDist
            lcd.setCursor(15, 1);                     // set the cursor to (13,0)
            lcd.print(CalDist);                       // print CalDist
          }
          else  {
            MenuMode = 2;                             // Go to the previous menu
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print(" FEET  ");                     // overwrite menu item with "FEET:" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite lower menu area with spaces
          }
        }
        
        break;
      }
      
      case 2:  {                                      // MenuMode 2 is the Foot distance meter
        
        if (Button1State == LOW)  {                   // test for Button1 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            DistanceFeet = 0;                         // Reset DistanceFeet to 0
            DistanceFeetTenths = 0;                   // Reset DistanceFeetTenths to 0
            printfeet();                              // Print the distance in feet
          }
          else  {
            MenuModeActive = true;                    // Set MenuModeActive to true (enter this mode)
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print(" FEET: ");                     // overwrite total mileage with "FEET:" & spaces
            DistanceFeet = 0;                         // Reset DistanceFeet to 0
            DistanceFeetTenths = 0;                   // Reset DistanceFeetTenths to 0
            FeetScaleFactor = 528000 / CalDist;       // Calculate FeetScaleFactor from CalValue
            printfeet();                              // Print the distance in feet
          }
        } 
        
        if (Button2State == LOW)  {                   // test for Button2 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            MenuModeActive = false;                   // Set MenuModeActive to true (exit this mode)
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print(" FEET  ");                     // overwrite total mileage with "FEET:" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite feet distance with spaces
          }
          else  {
            MenuMode = 0;                             // Set MenuMode to 0
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("       ");                     // overwrite "CAL" with spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite CalDist with spaces
            printmileage();                           // display the odometer
          }
        }
        
        if (Button3State == LOW)  {                   // test for Button3 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            // do nothing
          }
          else  {
            MenuMode = 1;                             // Go to the previous menu
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("  CAL  ");                     // overwrite menu item with "CAL" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite lower menu area with spaces
          }
        }
      
        if (Button4State == LOW)  {                   // test for Button4 being pressed
          if (MenuModeActive)  {                      // test to see if a menu mode has been selected
            // do nothing
          }
          else  {
            MenuMode = 1;                             // Go to the previous menu
            lcd.setCursor(13, 0);                     // set the cursor to (13,0)
            lcd.print("  CAL  ");                     // overwrite menu item with "CAL" & spaces
            lcd.setCursor(13, 1);                     // set the cursor to (13,1)
            lcd.print("       ");                     // overwrite lower menu area with spaces
          }
        }
        
        break;
      }
      default:  {                                   // MenuMode 0 is the default screen
        if ((Button1State) == LOW)  {               // test for Button1 being pressed
          DistanceMiles = 0;                        // if so, reset distance miles to zero
          DistanceTenths = 0;                       // also, reset the tenths to zero
          DistanceClicks = 0;                       // finally, reset the clicks to zero
          printmileage();                           // display the odometer
          break;
        }

        if ((Button2State) == LOW)  {               // test for Button2 being pressed
          MenuMode = 1;                             // Set MenuMode to 1
          CalDistPrev = CalDist;                    // copy CalDist to CalDistPrev
          lcd.setCursor(13, 0);                     // set the cursor to (13,0)
          lcd.print("  CAL  ");                     // overwrite total mileage with "CAL" & spaces
          lcd.setCursor(13, 1);                     // set the cursor to (13,1)
          lcd.print("       ");                     // overwrite trip mileage with spaces
          break;
        }
    
      }
    }
  }
  
ButtonPressedPrev = ButtonPressed;        // store ButtonPressed in ButtonPressedPrev for test on next loop
  
}


void AddSensorCount()  {                  // This is the subroutine that is called when interrupt 0 goes high
  SensorCount++;                          // Increment SensorCount by 1
}


void EEPROMwrite()  {                     // This is the subroutine that is called when interrupt 1 goes low
// store mileage variables in EEPROM
    EEPROM.write(0, byte((DistanceMiles & 65280)/256));
    EEPROM.write(1, byte((DistanceMiles & 255)));
    EEPROM.write(2, byte(DistanceTenths));
    EEPROM.write(3, byte((DistanceClicks) & 65280)/256);
    EEPROM.write(4, byte((DistanceClicks) & 255));
    EEPROM.write(5, byte((TotalMiles & 16711680)/65536));
    EEPROM.write(6, byte((TotalMiles & 65280)/256));
    EEPROM.write(7, byte((TotalMiles & 255)));
    EEPROM.write(8, byte(TotalTenths));
    EEPROM.write(9, byte((TotalClicks) & 65280)/256);
    EEPROM.write(10, byte((TotalClicks) & 255));
}

  
void loadchars() {                        // This subroutine programs the custom character data into the LCD
  lcd.command(64);
// Custom character 0
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  
// Custom character 1
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  
// Custom character 2
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  lcd.write(byte(B11111));
  
// Custom character 3
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B01110));
  lcd.write(byte(B01110));
  lcd.write(byte(B01110));
  
// Custom character 4
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B01110));
  lcd.write(byte(B01110));
  lcd.write(byte(B01110));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  
// Custom character 5
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  
// Custom character 6
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  
// Custom character 7
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
  lcd.write(byte(B00000));
 
  lcd.home();
}


void printmileage()  {
  lcd.setCursor(13, 0);                               // set the cursor to (13,0)
  lcd.print(int((TotalMiles % 100000) / 10000));      // print the total miles ten-thousands digit
  lcd.print(int((TotalMiles % 10000) / 1000));        // print the total miles thousands digit
  lcd.print(int((TotalMiles % 1000) / 100));          // print the total miles hundreds digit
  lcd.print(int((TotalMiles % 100) / 10));            // print the total miles tens digit
  lcd.print(TotalMiles % 10);                         // print the total miles ones digit
  lcd.print(".");                                     // print the decimal point
  lcd.print(TotalTenths);                             // print the total miles tenths digit
  lcd.setCursor(14, 1);                               // set the cursor to (15,0)
  lcd.print(int((DistanceMiles % 1000) / 100));       // print the distance miles hundreds digit
  lcd.print(int((DistanceMiles % 100) / 10));         // print the distance miles hundreds digit
  lcd.print(DistanceMiles % 10);                      // print the distance miles hundreds digit
  lcd.print(".");                                     // print the decimal point
  lcd.print(DistanceTenths);                          // print the distance miles tenths digit
}


void printfeet()  {
  lcd.setCursor(13, 0);                               // set the cursor to (13,0)
  lcd.print(" FEET: ");                               // print the FEET units
  lcd.setCursor(13, 1);                               // set the cursor to (13,0)
  lcd.print(int((DistanceFeet % 100000) / 10000));    // print the distance feet ten thousands digit
  lcd.print(int((DistanceFeet % 10000) / 1000));      // print the distance feet thousands digit
  lcd.print(int((DistanceFeet % 1000) / 100));        // print the distance feet hundreds digit
  lcd.print(int((DistanceFeet % 100) / 10));          // print the distance feet tens digit
  lcd.print(DistanceFeet % 10);                       // print the distance feet ones digit
  lcd.print(".");                                     // print the decimal point
  lcd.print(DistanceFeetTenths);                      // print the distance feet tenths digit
}


void printbigchar(byte digit, byte col) { // This subroutine prints the big font characters on the LCD screen
 if (digit > 9) return;                   // anything above 9 gets rejected
 for (int i = 0; i < 2; i++) {            // count i from 0 to 1
   lcd.setCursor(col*4 , i);              // set LCD cursor at correct point
   for (int j = 0; j < 3; j++) {          // count j from 0 to 2
     lcd.write(bignums[digit][i][j]);     // write proper block to LCD from array
   }
   lcd.write(254);                        // write an empty space
 }
 
 lcd.setCursor(col + 4, 0);               // move the cursor to the top line, col + 4
}

I like the way you used the LCD to display large characters.
Hey the speedometer works on the principle of Doppler's effect? Is it? I've read it somewhere.

Nice project! I'm working on an automotive Arduino-based project so am interested to see what others are doing. (Maybe we need an automotive or telematics forum/thread?)

Question: How are you powering the Arduino? I know that since most cars use a nominally 12v electrical system, theoretically it should be possible just to route this into the Arduino's power plug. However, the "Practical Arduino" book notes that the actual voltage in auto electrical systems can vary much more widely and suggests using a separate power supply with its own regulator. How are you handling this? Thanks...

I know that since most cars use a nominally 12v electrical system, theoretically it should be possible just to route this into the Arduino's power plug. However, the "Practical Arduino" book notes that the actual voltage in auto electrical systems can vary much more widely and suggests using a separate power supply with its own regulator. How are you handling this? Thanks...

An auto battery systems voltage can vary quite a lot between depending on state of charge, etc. I think 16vdc is the max one can expect to see. This is not too much for the arduino external power although the closer to 8 volts the cooler the regulator on the arduino will run.

The real problem with auto voltage is all the noise and load switching going on can cause lots of problems with some electronics. There is also a phenomenon called a load dump where almost 50volt can be spiked onto the auto's 12volt system. Anyway lots of people suggest to utilize extra filters, transient suppression and over voltage protection for sensitive electronic used in autos. The input filter capacitor on the arduino's voltage regulator is probably the most vulnerable component and I forget it's voltage rating.

Lefty

I like the way you used the LCD to display large characters.
Hey the speedometer works on the principle of Doppler's effect? Is it? I've read it somewhere.

Thank you, but I can't take credit for the large characters. I was inspired by this thread:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1245352653

I'm not sure if Doppler's principle would apply to this particular design. Maybe more so if I were measuring my speed with a radar. :sunglasses:

For power I'm running off the vehicle's battery, but I did take some protective measures. There's a dedicated line that connects very close to the battery itself, which from what I understand makes a pretty significant filter. I plan to use a key-on powered relay in the future, but for now I just plug in the PCB when I'm going to drive somewhere. I also have a simple overvoltage/overcurrent protection circuit that I constructed in the power cable:

So far it's put up with almost daily use for about 9 months with no trouble. :slight_smile:

Thankyou for sharing... ther eare a few projects on the go... my personal favourites are http://www.janspace.com/b2evolution/arduino.php/2010/06/26/scooterputer AND http://howiem.net/flatpress/?x=entry:entry090617-154827

I've bookmarked this as I'd love to see how you get on :slight_smile:

my personal favourites are http://www.janspace.com/b2evolution/arduino.php/2010/06/26/scooterputer AND http://howiem.net/flatpress/?x=entry:entry090617-154827

Wow, those are nice! I can see why they're your favorites!

a power-fail circuit that saves the odometer data into EEPROM when a power failure is detected

I find this to be one of the coolest aspects of the project. I don't think I would have ever considered taking it that far. My solution probably would have been to update the EEPROM whenever the vehicle comes to a stop or if a max allowed amount of time passed since the last write. I also would have shifted the memory address of the updates every write for wear evening. Then at start-up search for the data entry with the highest values. Good work.
I have found it to be quite common for microcontroller hobbyists to use multiple lines to print large numbers on alphanumeric LCDs. If you are looking to improve upon that, I have seen others with more natural looking digits consisting of curved and/or tapered edges. I don't know how much more complex it is to implement than the more blocky alternative but it is something you may want to look into.

Prior to the power-fail solution, I had the odometer writing to EEPROM every 1/10 mile, but I was concerned about exceeding the write cycle life. I had the MAX690 IC and associated components at my disposal, so the decision to utilize them was an easy one to make. :slight_smile:

I have a 3-line font that I constructed a while ago that I plan to use once I upgrade to a 4-line display: