DIY digital speedometer using PIC microcontroller

October 4th, 2007 by Jeff Leave a reply »

DIY digital speedometer

Digital speedometer schematic available here (PDF, 34KB).

C source code available here under GPL (ZIP, 22KB).

I like knowing how fast my car is going, but the stock analog speedometer only goes up to 140kph. Plus, I suspect its accuracy isn’t all that great, it doesn’t register speeds below 15kph, and there’s no way to calibrate it. I looked at digital speedometers online, but most of them were backlit LCD displays (almost illegible in the sunlight atop my dashboard, and an eyestrain even when you can read them). The ones that weren’t were just too expensive for what should be a rather simple piece of electronics. I wanted a three digit 7-segment LED display, like my buddy Takahashi has in his Lancer EVO 6.

I looked at frequency counter chips, figuring I could use BCD decoder and a trio of 7 segment driver chips to display everything, but they were outdated and the amount of external timing circuitry I’d need was a bit ridiculous. I looked around at the local electronics supply store, and stumbled across some cheap programmable microchips. At first I thought it would be overkill, but considering a simple microcontroller is only $2 or $3 and reduces the number of parts significantly, it actually turns out to be the best option. Plus, you can add extra features like in-software calibration, 1/4 mile “drag strip” times, storing top speed, display in kph or mph, and whatever else you can think of. The drawback is the initial investment of a PIC programmer so that you can use your computer to burn your programs to the chip. You can now get an official PICKit 2 programmer for about $35 plus shipping, and it can be used on a variety of microchips all the way up through DSP microchips. If you’re into hobbyist electronics, there’s really no reason you shouldn’t get one. Cheap, easy to program, and powerful enough for most stuff.

The cheapest microcontroller I could find was the PIC16F648A, which is an 8 bit microcontroller with 16 input/output pins and 3 hardware timers, more than enough for this project. It has 256 bytes of RAM, 256 bytes of EEPROM permanent storage, and programming storage space for about four thousand instructions, and best of all, it doesn’t require any external oscillator hardware. PIC microcontrollers have a very minimal instruction set, meaning that any operation more complex than addition or subtraction requires a lot of instructions, but since this chip defaults to 1 million instructions per second that’s not usually a problem, assuming you have a compiler capable of generating those sequences. As well, almost every input and output is reprogrammable to different hardware functions such as voltage comparison, interrupt triggering, edge counting, timer capture, or even serial communication. It’s really quite a powerful little package, especially if you compare it to what was available just 5 years ago.

Your basic 7 segment LED has 10 pins. Two of those are the same, connected to either the anode or the cathode of all the LEDs, and the other 8 are the other end of each of the 8 LEDs. Since 24 LEDs is too many to control with 16 pins, I multiplexed them; that is, the 8 individual pins are all connected through resistors to 8 of the microcontroller pins, and I use 3 other pins to select which of the three displays to light up (via transistors since the PIC pins can’t source that much current). If you switch between the displays fast enough, the human eye can’t even tell they’re being switched on and off.

Obviously the whole thing runs off 5 volts, so to step down the 12-14 volts from a car battery you need a voltage regulator (I used an LM7805 because it was what I had on hand). A couple capacitors also help, as cars tend to have lots of voltage transients. If I wanted to be a bit safer I could have also used a zener diode for overvoltage protection, but the power supply area of the circuit board was already a bit tight for space and the LM7805 and associated components are cheap enough to replace if something bad happens.

Here’s a quick parts list of what I used:

1 – PIC16F648A microcontroller
1 – 18 pin socket (not needed if you’re just breadboarding it)
3 – 7-segment displays, common cathode
8 – LED resistors (I used 120 ohm, but you should design with the max LED current in mind.)
3 – NPN transistors, anything should work
3 – 1k resistors (to use with the base of each transistor)
1 – LM7805 5 volt voltage regulator
2 – 22uF 50V capacitors (maybe overkill, but if you have noisy wiring you will need to adjust this)
1 – button (for software control)
2 – 10nF capacitor (button debounce and speedo input filtering)
1 – 100k resistor (button pullup)
a board to put it all on, and cables to string it together. You should be able to get all this stuff for under $20.

If you’re hooking this directly to a reed switch with no external driving circuitry (like a stock ECU), you will have to add another pullup resistor.

The easiest way would just be to hook up all the 8 LED leads to the same 8 bit port (PORTA or PORTB), but because we’re using some ports of A and some of B to do other things, we can’t be so lazy. So we’ll spread those out and use a software bitmask to determine which ports to light up. Thankfully, it doesn’t really matter that much what order they go in, because you can define it in software. The speedometer input has to be on a specific pin if we want to automatically log the timer when we get a pulse. And one of the pins cannot be used as an output, so we’ll set that one to be our button input (and when we’re putting our board together we’ll use it as a reset pin). Aside from that, we can stick the three selector bits wherever we want them, and still have the two serial port pins left for CAN interfacing if we want it, and another for supply voltage sensing (so we can determine whether the headlights are on or off and dim the display accordingly). The parts for these last two options aren’t included in the list above, because I haven’t added them yet.

Once the hardware is all set up, you need some software to run it, and that’s the tough part. The first order of business is reading the chip manuals thoroughly, and then finding yourself a C compiler, because programming in assembly is time consuming and rife with pitfalls. I started with cc5x but the free version only does up to 16 bit math, and it has a slew of other problems (“Can’t generate code” is the chief among them, requiring you to manually simplify your code and manually define intermediate variables for calculations). But I tried out SourceBoost and the free version has far fewer limitations. The code should first set up all the interrupts and config registers, define the BCD and seven segment driver code, set up the timers, and tell the main loop to show something useful. It’s best to stay simple; the first version of my code simply did a lamp test (all on, all off) and then displayed a rising sequence of numbers (which was actually just a spare timer I wasn’t using). This makes sure your lamps all work and your wiring is correct. Then you can start adding in code to test that the pulses are being received and triggering properly without bounce, probably using a counter.

Update 1:
It seems one of the LED 7-segment displays I bought has a bad lamp (D, the one on the bottom). Of course the test lamp feature caught this easily, but only after I soldered everything to a board. Note to self: in the future, use sockets instead of soldering 7 segment displays directly to a board.

Update 2:

It turned out the lamp wasn’t bad at all. It was just a short from overzealous soldering. Unfortunately I didn’t catch this until AFTER I had ripped out the display element, trashing it in the process. Oh well, there goes 200 yen. But the good news is that it works now, and I’ve worked out the software bugs I’ve been able to find.

Update 3:

I finally wired everything into the car. My wiring was fine, but I had lots of noise in the input signal, causing a small interrupt storm that sent the microcontroller into fits. A small capacitor (10nF) between the signal and ground wires at the speedometer fixed that problem. Driving around, the speedometer was just fine below 10kph, but above that there was a lot of jitter in the time between pulses. I’m tweaking the algorithm to average more and more pulses at higher speeds, but since memory is limited this is an interesting problem.

Update 4:

I’ve had a chance to run the speedometer in my car for several months, and it’s given me a chance to test it out. It works pretty well, is a lot more visible than my stock speedometer, and turns a few heads to boot. (“You built that YOURSELF??”)

Since several people have asked me to release the source, I will do so. The source is released under GPLv3. You are free to take the code and use it, modify it, and improve it. If you fix any bugs let me know so I can fix them on my speedometer too! Also if you build your own DIY speedometer and put it in a car or bike or whatever, I’d love to see it. Please ask me before using it in a commercial product; I’ll probably say OK but I would like to see the end result! The source does have known bugs which I haven’t gotten around to fixing; these are listed at the top of the source file in the comments. The biggest one is that quarter mile calibration still doesn’t work properly; with snow and ice on the ground I wasn’t too motivated to fix them. Now that spring has arrived, I can do so.

As the GPL works, if you redistribute assembled code including hardware with the code installed, you must distribute the source along with it. You got it for free, so you can’t complain.

The test modes included in my source code all let you test your wiring easily; simply set MODE_DEFAULT to MODE_TEST in the code, recompile and flash, and the tests will run when you apply power. The first test should display a constantly increasing number which wraps at 255. This makes sure your lamps are wired correctly and set up in software correctly. Press the button to go to the second test. The second test shows how long the button has been held down; a count of 30 is about one second. Use this to make sure your button works properly and isn’t bouncing. After about 15 seconds the pulse counter mode is activated. This will show how many speedometer input pulses have been received. This is handy to verify that noise isn’t triggering as pulses, and the pulses are registering properly. If all three of these tests work, then your wiring is correct and it should be possible to calibrate the speedometer (drive 50 and enter calibration mode) and display your speed.

Update 5

The schematic I’d posted before was using the wrong parts in the wrong configuration. I’m not sure how I screwed this up, but it’s fixed now. Thanks, Velson.

Since I’ve moved back from Japan and have changed cars, I don’t have a working testbed anymore. Feel free to submit patches to the code or modifications to the schematic, and of course if something doesn’t look right please let me know so I can fix it.


  1. vicky says:

    hi jeff!!!
    can you please upload the HEX file of the source code

  2. Charles says:

    Has anyone figured out how to add an odometer/trip functionality ti the code?

  3. Tanvir says:

    Hi Charles, yes Orlin has a project underway at Google code:


  4. Mauro Branco says:

    Hello Jeff!

    I’m trying to compile your code with Proteus and can not resolve errors that are appearing. Would you be so kind as to send me the file in hex?
    I would like to test a prototype in my Selvagem Brasilian Buggy.
    Thank you!

  5. Tanvir says:

    Hi Jeff and all other fans of this speedometer,

    I am using this speedo since I constructed it almost year ago and am happy with it except for some not-so-stable display at lower speeds (see my next comment).

    I have read all previous comments, how people could not calibrate driving at 50 while some wanted to be able to calibrate at some other speed… Here are my thought on finding the calibration factor mathematically:

    What I gathered from comments and also from Jeff’s c-file is that calibration factor is just the number of bigticks between consecutive two pulses at speed of 1Km/hr. If you know the circumference of your driven wheel (1639.3 mm for my car) and the number of pulses it generates for each revolution (pulses per turn – 4.5 for me), then I think the calibration factor can be calculated easily. It is simple mathematics where first we find how much the car travels in duration of one pulse (my car travels 36.4 cm between two pulses). Next we calculate how much time it takes (in micro Seconds) for this distance at speed of 1Km/hr (1311439 micro seconds). Divide the microseconds by 32 to convert time into bigticks (40982) and here you go, your very own calibration factor is ready to be poked in c-source file in place of Jeff’s 48094.

    Cutting the mathematics short, the final formula would be:

    calib_factor =
    circumference of wheel (mm) x 112.5 / pulses per turn

    I hope it helps many enthusiasts like me out there…

  6. Tanvir says:

    Hi Jeff and all other fans of this speedometer,

    I am using this speedo since I constructed it almost year ago and am happy with it except for some not-so-stable display at lower speeds. From the comments and the c-file, it seems that as the speed increases the rolling sum of last 2,4,8,16,32 or 64 pulses is used to calculate the current speed. That means at speeds lower than 10, only last two pulses are used for this calculation. Minor variations cause the speed to continuously change between say 7, 8 ,6, 9, 6 etc. At speeds 10 to 20, the last 4 pulses are averaged and so on. I think the speed display is quite stable at speeds above 40 and very stable at speeds above 80.

    What I am wondering is: How can we change the code so that from speed zero onwards, always last 16 (or say 32) pulses are averaged. The display may become slow to update at lower speeds but the variation will not be there. I don’t mind if speed of my car is not updating quickly at lower speeds but I would love to see it stable enough.

    I hope Jeff can help (though he has not responded since long) or may be someone out there who can understand the c-code and make the changes…OR suggest some other way to improve it.

  7. Steffen Dohrenbusch says:

    I have a Question.
    I figured out PIN5 is Negative
    PIN 14 is Positive (5V)

    What does VCC goes? i thought it would be the 7805 Output,but why would i short it? (button S1 to ground)

    Also: The wire with C4 goes dierectly to the ECU?

    Sorry if i misunderstand something! (i have bad english)

  8. Tanvir says:

    Hi Steffen, You are right, pin5 ground and pin14 positive. And you are right VCC connects with 7805 out. Button S1 would not short it as there is a 100k resistor in between, it will only pull pin4 to low state as long as the button is pressed.
    Yes the wire with C4 can take pulses from ECU also but I have no idea about connections on an ECU (my car is an old carb model).

  9. Steffen Dohrenbusch says:

    Thank you Tanvir!

  10. eri says:

    hi jeff and everyone,
    This is a very nice project, I try to make the circuit,
    first, I made it in proteus software, hex. file I get from Steve and working … (thanks Steve ..)
    I bought some components to assemble the circuit,
    ( I’m use 16F628A, because I did not find 16F648A)
    then I insert hex. file to the chip..
    I use several programs like WinPic, IC-Prog, winpic800 … successfully ..
    then I create a circuit in the breadboard .. after finished I check back the circuit, after all ok, I connect with a voltage of +5 V. ..
    When I connect the voltage, Display does not light up, I check the circuit, there is nothing wrong ..
    I re-program 16F628A, but still does not work ..
    Did I make mistake making the circuit ..?

    to Jeff and all, can help me please?

    maybe, if you or anyone have the hex. file and the revision of the schematic, can send to to the my email..,

    to jeff and all… I am very grateful..

This work by Jeff Hiner is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported.