The above video shows the final assembly of the speedometer with a 555 timer providing a simulated 60mph for calibration. The odometer reading is read from EEPROM upon startup and displayed on the OLED. Each time the speed = 0mph it will be written back to EEPROM.
Parts List
The Build process
My starting point was a Smith speedometer reading in KPH. I wouldn't be using the internals so don't need to worry about calibration and the larger number range is perfect for my bike engine mini. The 0-150 range is in-keeping with much newer cars although I will never reach those speeds.
I carefully removed the glass and took all of the internals out. Be careful not to loose any of the tiny brass screws!
I cut a piece of cardboard to reduce the window that the OLED would sit behind. It was secured in place using a hot glue gun.
You can also see the original MPH number range.
I carefully masked up the Smiths logo and MPH markings and then sprayed the whole face pictured with VHT matt balck paint to cover up the original MPH number range. The old KPH range will become the new MPH range.
Next job was mounting the stepper motor on some prototype board and attaching the mount to hold it inside the speedo housing.
I soldered the mounting pins of the stepper motor onto the proto board using a temperature controlled soldering iron. I've read that these are very sensitive to heat so be careful if doing the same. I used a dremel to trim the metal bracket pictured above to fit around the OLED. I also used a hot glue gun to attach it to the stepper motor as the mounting holes did not line up. The last picture shows the face attached to the bracket and the proto board being too long obscuring the pins to the OLED. I trimmed those down before installing into the speedo housing.
Here we have the OLED mounted to the face and a lead I made for the OLED to Arduino connection.
Everything installed in the speedo housing and face fitted before the glass went on
The Code
/* Stepper Motor Speedometer and OLED Odometer Luke Hurst Nov 2015 http://retromini.co.uk Credits - Thanks must go to the following from whom I have taken code and modified to my requirements. Kevin Gale - For writing the main stepper motor speedometer sketch - https://github.com/Walterclark1/Stepper_Speedometer Walterclark for the excellent write-up on how to use the base sketch written by Kevin Gale - http://www.hillclimb.org/forum/viewtopic.php?f=16&t=1133&sid=260e267564c31b08855a0e21ea57cbac Guy Carpenter - Switec library author - https://github.com/clearwater/SwitecX25 PJRC for the FreqMeasure library - https://www.pjrc.com/teensy/td_libs_FreqMeasure.html Trewjohn2001 for the Odometer code - http://www.mgexp.com/phorum/read.php?40,2761694 */ //----Libraries to Include-------- #include <EEPROMex.h> #include "SwitecX25.h" #include "FreqMeasure.h" #include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> //----End Libraries-------------- //----Define OLED Display Settings------ #define OLED_RESET 4 Adafruit_SSD1306 display(OLED_RESET); #define NUMFLAKES 10 #define XPOS 0 #define YPOS 1 #define DELTAY 2 //-----End OLED Display Settings-------- //-----Variables----------------------------------------------------------------------------------- const int UpdateInterval = 100; // 100 milliseconds speedo update rate const int UpdateInterval2 = 1000; const double StepsPerDegree = 3.0; // Motor step is 1/3 of a degree of rotation const unsigned int MaxMotorRotation = 315; // 315 max degrees of movement const unsigned int MaxMotorSteps = MaxMotorRotation * StepsPerDegree; const double PulsesPerMile = 4000.0; // Number of input pulses per mile const double SecondsPerHour = 3600.0; const int FeetPerMile = 5280; // Feet per mile conversion factor for Odometer update function const double SpeedoDegreesPerMPH = 180.0 / 120.0; // Speed on face of dial at 180 degrees is 108mph. unsigned long PreviousMillis = 0; // last time we updated the speedo unsigned long PreviousMillis2 = 0; double MinMotorStep; // lowest step that will be used - calculated from update interval unsigned long distsubtotal; // Running total of feet covered for Odometer update function unsigned long lastmillis; // Last time we updated the Odometer unsigned long FeetTravelled; // mph converted into feet travelled for Odometer function double sum=0; int count=0; double avgPulseLength=0; unsigned int motorStep = 0; int noInputCount = 0; float mph=0; const int NeutralPin = 2; //----End Variables--------------------------------------------------------------------------------- //----Define Stepper motor library variables and pin outs------------------------------------------- SwitecX25 Motor(MaxMotorSteps, 4,5,6,7); // Create the motor object with the maximum steps allowed //------READ Stored Odometer Values From EEPROM----- // - Commented out during testing to avoid reading from EEPROM resulting in strange number formatting. int disttenthsm = EEPROM.readInt(0); int distm = EEPROM.readInt(5); int distenm = EEPROM.readInt(10); int disthundredm = EEPROM.readInt(15); int disthousandm = EEPROM.readInt(20); int disttenthousandm = EEPROM.readInt(25); int disthundredthousandm = EEPROM.readInt(30); /* int disttenthsm; int distm; int distenm; int disthundredm; int disthousandm; int disttenthousandm; int disthundredthousandm;*/ //------End EEPROM Read----------------------------- //---------------Start-up code run once------------------------------------------------------------------- void setup(void) { Serial.begin(9600); display.begin(SSD1306_SWITCHCAPVCC); //Intialise OLED pinMode(0, INPUT_PULLUP); // Define digital pin 0 as pulse signal input pinMode(NeutralPin, INPUT_PULLUP); // Define digital pin 2 as Neutral Sense //-----Display SMITHS logo on start-up display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(3); display.setCursor(15,38); display.println("SMITHS"); display.display(); delay(2000); display.clearDisplay(); display.display(); //-----End Logo----------------------- Motor.zero(); //Initialize stepper at 0 location Motor.setPosition(744); Motor.updateBlocking(); delay (1000); Motor.setPosition(0); //0MPH Motor.updateBlocking(); delay (1000); lastmillis = (0); //Reset Odometer update interval to zero MinMotorStep = PulseToStep(2 * (UpdateInterval / 1000.0) * F_CPU); //Force to zero when two intervals have passed with input FreqMeasure.begin(); // Start freqmeasure library } //------End Start-up code---------------------------------------------------------------------------------------------------------- //------Start of Loop-------------------------------------------------------------------------------------------------------------- void loop() { unsigned long currentMillis = millis(); // Update the motor position every UpdateInterval milliseconds if (currentMillis - PreviousMillis >= UpdateInterval) { PreviousMillis = currentMillis; count = 0; sum = 0; // Read all the pulses available so we can average them while (FreqMeasure.available()) { sum += FreqMeasure.read(); count++; } if (count) { // Average all the readings we got over our fixed time interval. This helps // stabilize the speedo at higher speeds. The pulse length gets shorter and // thus harder to measure accurately but we get more pulses to average. // It may be necessary to update the FreqMeasure library to change the buffer // length to hold the full number of pulses per update interval at the highest // speedo values. avgPulseLength = sum / count; motorStep = PulseToStep(avgPulseLength); noInputCount = 0; } else if (++noInputCount == 2) // force speed to zero after two missed intervals motorStep = 0; // Ignore speeds below the the two missed intervals speed so the motor doesn't jump if (motorStep <= MinMotorStep) motorStep = 0; Motor.setPosition(motorStep); } // Always update the motor. It doesn't instantly go to the desired step so even if // we didn't call setPosition the motor may still be moving to position from the last // setPosition call. Motor.update(); //-----------------Update Odometer Counter and Display every second ----------------------------------- unsigned long currentMillis2 = millis(); // Update the motor position every UpdateInterval milliseconds if (currentMillis2 - PreviousMillis2 >= UpdateInterval2) { PreviousMillis2 = currentMillis2; mph = ((motorStep / 3)/SpeedoDegreesPerMPH); // Might be a better way of calculating mph based on input frequency Serial.println(mph); // Used for testing to output speed in serial monitor. // if (millis() - lastmillis >= 1000){ //Uptade every second. FeetTravelled = ((mph * FeetPerMile) / SecondsPerHour); // Convert mph into feet per second for running subtotal count. distsubtotal += FeetTravelled; // add feet traveled to running subtotal // lastmillis = millis(); // Update lasmillis updateodometer(); // Adds distance travelled to odometer // } displayodometer(); // Calls displayodometer function } //---------------------------END ODOMETER------------------------------------------------------------- //---------------Start EEPROM update if vehicle is stopped------------------ if (digitalRead(NeutralPin) == LOW){ //if (mph == 0){ // Write odomoter values to EEPROM when vehicle is at a stop. Consider replacing with power failure sense circuit to avoid excessive eeprom writes. updateeeprom(); } //--------------End EEPROM Update------------------------------------------- } //-------End of Loop-------------------------------------------------------------------------------------------------------- // The FreqMeasure gives us the pulse length in CPU cycles. This formula converts this into a motor step. // Basically we are converting the length of the pulse in CPU cycles into pulses per second and then // converting that into MPH, Once we have MPH that number is converted into degrees and that is then // converted into a number of steps. unsigned int PulseToStep(double pulseLength) { return (unsigned int)((F_CPU * SecondsPerHour * SpeedoDegreesPerMPH * StepsPerDegree) / (PulsesPerMile * pulseLength)); } //-------Start of Functions------------------------------------------------------------------------------------------------- void displayodometer() { display.setTextSize(3); display.setTextColor(WHITE); display.clearDisplay(); //----------------Display ten thousand m units------------------------------------ display.setCursor(5,38); display.println(disttenthousandm); //----------------Display thousand m units------------------------------------ display.setCursor(25,38); display.println(disthousandm); //----------------Display hundreds m units------------------------------------ display.setCursor(45,38); display.println(disthundredm); //----------------Display tens m units---------------------------------------- display.setCursor(65,38); display.println(distenm); //----------------Display miles units--------------------------------------------- display.setCursor(85,38); display.println(distm); //----------------Display tenths m units--------------------------------------------- display.drawLine(104, 36, 104, 62, WHITE); display.setCursor(110,38); display.println(disttenthsm); display.display(); } //-------------------------Update Odomter Counts-------------------------------------------------------- void updateodometer(){ while (distsubtotal >= 528){ ++disttenthsm; distsubtotal = 0; } if (disttenthsm >9){ ++distm; disttenthsm = 0; } if (distm >9){ ++distenm; distm = 0; } if (distenm >9){ ++disthundredm; distenm = 0; } if (disthundredm >9){ ++disthousandm; disthundredm = 0; } if (disthousandm >9){ ++disttenthousandm; disthousandm = 0; } if (disttenthousandm >9){ ++disthundredthousandm; disttenthousandm = 0; } } void updateeeprom(){ // Called by routine in main loop when vehicle at a stop. Update function only writes changes to eeprom if a bit has changed to avoid excessive data writes. EEPROM.updateInt(0,disttenthsm); EEPROM.updateInt(5,distm); EEPROM.updateInt(10,distenm); EEPROM.updateInt(15,disthundredm); EEPROM.updateInt(20,disthousandm); EEPROM.updateInt(25,disttenthousandm); EEPROM.updateInt(30,disthundredthousandm); } Schematics for OLED & Stepper connection to Arduino
21 Comments
Luke
4/1/2016 12:30:09 pm
Sure I can supply a drawing. I will add it to the bottom of this post. I've put it together using Fritzing for my records. It's not pretty but it will give you an idea of what to do.
Reply
Landy
9/6/2016 07:03:32 am
Hi
Reply
Luke Hurst
9/6/2016 07:10:49 am
I'm using the speed sensor on the Yamaha R1 engine that is already attached to the gearbox. It produces a pulse train and I'm simply measuring the frequency with the Arduino and then converting that to a step position to move the speedo needle.
Reply
R
10/10/2016 04:16:44 am
Brilliant! EXACTLY what I've been dreaming of. Except switch the Mini to a VW. Oh snap! p.s. That has to be some of the cleanest, most well documented code I've seen. GR8T job. Thank you for taking the time and energy to make it all so clear :O)
Reply
Luke Hurst
10/10/2016 07:11:57 am
Thanks for the kind words. It makes the effort worthwhile! I'm glad you like the project. Send a link to yours if you have one. I would love to see what others are up to
Reply
Carlos
10/18/2016 04:22:34 am
Wooow very nice work , awesome proyect ! Liked it very much, I also would like to use it in a VW.
Reply
Luke Hurst
10/18/2016 02:24:41 pm
Glad you like it. Feel free to ask questions if you get stuck.
Stefan
10/29/2016 01:32:10 pm
Hello,
Reply
Luke Hurst
10/31/2016 11:42:34 am
Hi Stefan
Reply
r
12/5/2016 05:11:58 pm
nice effort! why haven't you upgraded your fuel gauge in the same manner?
Reply
Luke Hurst
12/8/2016 10:53:45 am
Thanks. I have most of the code works out for fuel gauge already. It's actually a multi-gauge that displays different meters with a button push e.g. battery charge, oil pressure etc...
Reply
Harsha
1/20/2018 02:19:32 pm
hi, nice tutorial, can you give more information about speed pick-up sensor
Reply
Luke Hurst
1/5/2019 07:34:28 am
The speed sensor on my Yamaha R1 engine is a single wire connected to the gear box. It produces a nice square wave signal and the frequency increases as the speed increases.
Reply
vince
4/2/2018 06:40:34 pm
great, great job! i would like to fit in my mini. It is possible to convert unit in KPH instead of miles? tks
Reply
Luke Hurst
1/5/2019 07:36:07 am
It would be simple to convert to kph. The Arduino code doesn't care what unit it is outputting. The code would just need modifying to covert the pulses into a meaningful kph.
Reply
Dee
4/11/2018 03:07:18 pm
Hey man thank you so much for the info!! can you please tell me if the speed sensor is a one wire or two wire for the signal? i see the two purple wires VSS Signal and Neutral signal. Cheers
Reply
Luke Hurst
1/5/2019 07:38:01 am
The speed signal wire is a single wire that outputs a nice square wave signal that increases in frequency as the speed increases. My Yamaha R1 engine has a separate neutral wire to signal when the sequential gearbox is in neutral.
Reply
Phillip Cervantes
2/6/2019 07:27:36 pm
I have been working to convert a speedo from a 53 Hudson Hornet and came across your write up and tried setting it up on my bench. I am using the same hardware. when i compile the code and upload to the nano it hangs on ('struct EEPROMClass' has no member named 'readInt') then I comment out the section as per your notes it hangs on ('disttenthousandm' was not declared in this scope) should i be commenting out all the code for the EEprom?
Reply
Luke Hurst
7/3/2019 08:38:04 am
I have sent you an email.
Reply
Your comment will be posted after it is approved.
Leave a Reply. |
Categories
All
Archives
July 2020
|