Jump to content

DIY Speedometer


Recommended Posts

At the time of this writing, this isn't exactly complete, but should give enough info for anyone looking into building their own speedometer. I've put up the basics & some ideas on improvements. Likely at some point I'll build one for my custom-frame (called "Phoenix" or "Fenix", as it was born from the "ashes" of the custom-battery project for the Firewheel, which so far has been an epic failure every step of the way :D).

Last night, while d***ing around with the Firewheel-mainboard, I for some reason picked a 7-segment display block in my hand from my parts bins. I also have a bunch of 7-segment and led/dot matrix/led bar-drivers (MAX7219, LM3194, TLC5940...). I haven't actually even tried any of them earlier, so I thought, what the hell, let's see if I can drive some 7-segments with these.

I started with a MAX7219 and an Arduino Nano. It's fairly straightforward, has capability to drive up to 8 separate 7-segment blocks (and can be chained with other MAX7219's to drive even more) and has programmatic control for brightness. 

Within a couple of hours, I had not only a working display, but also a simple speedometer, that works similarly as the bike computer speedos (calculating time between signals, in bike computers they're got from a magnetic switch, like a reed-switch or hall-sensor). 


Instead of an actual reed-switch, I simply used a micro-switch with a small capacitor in parallel (to kill off any switch vibrations). 

The software is very basic, and I actually used a ready-made library for the MAX7219 (LedControl), available from Github:  https://github.com/wayoda/LedControl


#include "LedControl.h"

LedControl lc=LedControl(12,11,10,1);

const unsigned long TIRECIRCUMFERENCE_MM = 1300;
volatile unsigned long lastTick = 0;
volatile unsigned int timeBetweenTicks = 0;

float rpm = 0;
float travelPerMin_m = 0;
float speed = 0;
float averagedSpeed = 0;

void setup() {
  lc.shutdown(0, false);

  //4 digits
  lc.setScanLimit(0, 4);
  // Set the brightness to a medium value
  lc.setIntensity(0, 8);

  //Signal input, using input-pullup, because the switch pulls the signal to ground
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), countSpeed, FALLING);

void countSpeed()
  if(lastTick > 0)
    long val = millis() - lastTick;
    //"Poor man's low pass", just check that "enough" time has passed, so it's not just switch vibration
    if(val > 75)
      timeBetweenTicks = val;

  lastTick = millis();

void loop() 

  if(timeBetweenTicks > 0)

    //Start lowering speed value if no signal is got for a while
    if((millis() - lastTick) < 4000)
      //Rounds per minute
      rpm = 60000.0 / timeBetweenTicks;
      //Travel per minute in meters
      travelPerMin_m = rpm * (TIRECIRCUMFERENCE_MM / 1000.0);
      //Speed as km/h
      speed = (travelPerMin_m * 60) / 1000.0;
      speed = speed / 1.5;

    //Move "gradually" towards target speed
    averagedSpeed = 0.8 * speed + 0.2 * averagedSpeed;
    //Update LCD value
    if(speed / 10 >= 1)
      lc.setDigit(0, 3, (int)(averagedSpeed / 10) % 10, false);
    lc.setDigit(0, 2, (int)(averagedSpeed) % 10, true);
    lc.setDigit(0, 1, (int)(averagedSpeed * 10) % 10, false);
    lc.setDigit(0, 0, (int)(averagedSpeed * 100) % 10, false);

The "important" bits for building a basic speedometer are of course reading the signal from the sensor, and calculating the speed based on that. There's a constant value called TIRECIRCUMFERENCE_MM, that holds the circumference of the tire (the outer part touching the road) in millimeters. For testing, I just used the value 1300 (1300mm = 1.3 meters), which is pretty close to the circumference of a "typical" 16-inch tire.

Each time the signal is received (in the above code, it is assumed that the switch pulls the input into ground, if the switch pulls the input to +5V, use INPUT, not INPUT_PULLUP, and use something like a 10k resistor between the switch and the input to limit the current). I've chosen that the interrupt handler is run on falling signal (attachInterrupt), so when the switch goes down it pulls the input to ground and the signal falls, if it pulls it up to +5V, use RISING.

The interrupt-handler countSpeed is fairly trivial: After the first tick has been received  (lastTick > 0), it counts the amount of milliseconds passed since lastTick (millis() - lastTick), does a simple filtering by checking that at least 75 milliseconds (0.075 seconds) have passed (if not, just bail out without saving the lastTick-value), and stores the time that has passed between ticks and the time last tick occurred in. 75 milliseconds seemed a "good" value, because even with a smaller 1000mm tire circumference (around 14"?), if running one round per 75ms, it would mean a speed of around 48km/h ;)  So such readings are left out to filter any jitter / switch vibrations in the signal. A better low-pass filter could be used of course, but for this simple project, that seemed sufficient.

The main loop does the actual calculation of speed based on known tire circumference and time between ticks (ie. how long it took for the tire to make one full round). First it checks if a "long" time (4000ms = 4 seconds) has passed since last tick, and if it has, it starts to drop the speed value to "run" it down instead of jumping to 0 immediately (it looks nicer that way ;)). Otherwise, it counts the RPM (rounds per minute) -value, by dividing 60000 with the time between ticks: 60000ms = 1 minute, so if it takes 1000ms for a full round, the tire is turning 60000ms / 1000ms per round = 60 rounds per minute.

Knowing the RPM, the tire circumference is changed into meters by dividing it with 1000 (of course the tire size could be directly stored as meters instead of millimeters, but probably the compiler is smart enough to replace the division with a pre-calculated value anyway, as it doesn't change), and from there, using the RPM and the circumference, it calculates the amount of meters the wheel travels over one minute. And from there, the value is changed into kilometers per hour by calculating travel per 60 minutes, and dividing by 1000 to get kilometers (thousands of meters). The calculation could be simplified a bit by leaving out some intermediate calculations, but the code would be harder to read and I wanted to keep it simple ;).

Depending how accurately you have measured the tire circumference (taking into account how much it presses in with weight on top), you can get nearly error-free speed-reading, far more precise than any GPS or such can provide.

The rest is just using the LedControl-library to write the digits onto the 7-segments, and a simple moving average (the averagedSpeed = 0.8 * speed + 0.2 * averagedSpeed; -part), as it looked "nicer" that way, the speed value "runs" up and down (fast) as the speed changes, instead of just "jumping" from one value to another each time a signal is received. Might need some fine tuning, as I haven't (yet) had the chance to try this with anything else except by pressing the switch repeatedly myself :D

As for display options, 7-segments look nice (IMHO), the first pic shows 4 separate blocks (they're actually placed one pin row apart, so not that wide if placed right next to each other. I also tried a 4-digit block, that has single pins for the segments and separate pins for the cathode of each digit:


Also in the above image I've replaced one of the green-blocks with a red-block, but it's not recommended to mix different colors for the same MAX7219, as they (may) use different amounts of current. If anyone's more interested in building the speedometer for their wheel and using 7-segments, I could do a small write up on the wiring of the MAX7219, but there are loads of tutorials available for it, so I won't bother right now ;)

If you plan on using MAX7219 and 7-segments, make sure you get the common cathode -kind 7-segment blocks!

But of course you could go with something like a small OLED you can get a 0.97" or was it 0.98" oled-display for about 2.75€, it looks really good and only needs two wires for data as it uses I2C:


Nevermind the "hue" around the characters, it's just the camera, it's actually really crisp, has a very good contrast (not surprising, as it's an OLED) & looks excellent in real life. :)  I've stripped down the Adafruit-library, as it took 13kB of program space, to something like <6kB, by removing everything that's not related to plain text drawing, but cannot distribute it, as I've removed the Adafruit-splash screens etc. and the license prohibits distribution if it's removed.

EDIT: Don't worry about the text-size in my photo, I was just trying the library... It can draw it much larger so it's easy to read from further away.

Or something like a TFT-display, or LCD or... ;)

For powering, 9V battery could be used, or the internal battery of the wheel itself with the aid of a step-down / buck-converter or linear regulator (watch those milliamperes and check that the power dissipation keeps within limits with linear regulation!).

With external power and reed/hall-switches, the meter does not need to be electrically connected to the wheel electronics/batteries in any way, making it entirely "passive" device that can't harm your wheel directly in any way.

A couple of ideas for improvement:

  • The speed-value actually only updates (or starts to "run" towards the new value) everytime the switch goes down (ie. magnet passes a reed-switch or hall-sensor), so using more magnets (equally distributed, so 2 magnets opposite to each other, 4 90 degrees apart etc) the value can be updated more often, especially at slow speeds, where the tire turns much slower
    • The code must be changed to reflect this, ie. the wheel has moved only half of the tire circumference per tick, if there are 2 magnets, only 1/4th with 4 magnets etc. Just add a constant with the amount of magnets and divide by that when counting
  • For moving the meter between wheels with different tire-sizes, a simple two-button -interface could be used for setting the tire-size:
    • One button starts the setup, and moves between the digits, selected digit is blinked to show the user which one it is
    • Second button changes the value of selected digit, rolling around after 9  (so 7, 8, 9, 0, 1, 2...)
      • So the user can "scroll" through the digits and input a the tire circumference in millimeters as a 4-digit number
    • If the user gives no input in, say, 5 or 10 seconds, bail out and store the diameter value in EEPROM (so it's remembered between power offs)
  • Instead of a reed-switch or (separate) hall-sensor, the signal could be directly read from a motor hall-sensor
    • Need to take into account that the signal "fires" far more often, so the "filtering" needs to use a smaller value, and need to know the amount of ticks per full round (kind of like you'd have more magnets, again ;))
    • I don't know how much current the hall-sensor signal can pass, so using a resistor or buffering the input with a high-impedance device in-between, like an op-amp or a comparator, might not be a bad idea
    • The Arduino and the wheel need a common ground for this to work, if the Arduino's powered from the wheel-batteries, it's already there, otherwise the grounds need to be connected
  • Since the wheel has (usually, unless idling in place while the magnet is in correct position :P) travelled a known amount each time the signal triggers, it could also be used to track total mileage and current trip of the wheel
    • When saving the value in EEPROM (so it isn't lost when the power is cut), take into account that it has limited number of rewrites
    • To lessen the amount of rewrites, the value could be stored into EEPROM only every, say, 1km and each time the wheel has stopped and hasn't moved for 5 seconds (and of course then only once, and then wait until the wheel starts moving again :P)

Anyway, it's still pretty bare-bones, but after the latest fiasco with the Firewheel shells and lack of space, I think I'll start rebuilding the custom-frame instead, and use this there. Maybe I'll start to grow it more towards a "full-fledged" metering solution (current, voltages, temperature), and it could even work as a telemetry-device for Wheelemetrics (although I'm not sure if I have any working Bluetooth-modules currently...).

Link to comment
Share on other sites

3 minutes ago, Pasi said:

I must say, maybe it is english, but sounds like hebrea to me ☺☺????

Yeah, easier solution is to walk into a store, buy a bike computer, and attach it to the wheel :D  That's what I did earlier with the Firewheel. Beats GPS in precision any day...

Link to comment
Share on other sites

@esaj OMG when do you sleep? Surely with your skill set there must be a better way to move building materials up your hill besides using buckets? Can't you just invent a PHLE ( plasma hyperdrive levitational earthmover) in your spare time? No wonder your mom stops by unannounced. She is worried about you!


Link to comment
Share on other sites

25 minutes ago, Rehab1 said:

@esaj OMG when do you sleep?

When I feel sleepy, usually somewhere between 5AM and 1PM local time ;):P  Actually, I'm on vacation for now (a little over one week left), but it's not unusual for me to get up around 10 or 11AM even normally, as I work most of the time remotely and have pretty flexible hours... I do complain about my work (a lot at times :D), but it has some really nice perks, so I'm not planning on giving up on it, even though the thought does pass my mind occasionally ;)



Surely with your skill set there must be a better way to move building materials up your hill besides using buckets? Can't you just invent a PHLE ( plasma hyperdrive levitational earthmover) in your spare time?

That plasma hyperdrive levitation-thingamabob might be a teeny-weeny little bit outside my skills :D  Sometimes, you just have to brute force it, and not spend too much time thinking about alternate solutions ;)



No wonder your mom stops by unannounced. She is worried about you!

Well, she does that to pretty much everyone... usually with a bad timing :D


8 minutes ago, steve454 said:

@esaj nice place to live looks like.  Looks like you have a lot of trees around.  What is that on the platform, a water heater?  I've never seen anything like it before.

It's a heat pump, similar technology as in a refrigerator, but in larger scale: it "strips" heat from surrounding air and moves it (in winter/colder seasons, during summer it can be run in the opposite, to cool down the inside):  https://en.wikipedia.org/wiki/Heat_pump

A heat pump is a device that provides heat energy from a source of heat to a destination called a "heat sink". Heat pumps are designed to move thermal energy opposite to the direction of spontaneous heat flow by absorbing heat from a cold space and releasing it to a warmer one. A heat pump uses some amount of external power to accomplish the work of transferring energy from the heat source to the heat sink.

While air conditioners and freezers are familiar examples of heat pumps, the term "heat pump" is more general and applies to many HVAC (heating, ventilating, and air conditioning) devices used for space heating or space cooling. When a heat pump is used for heating, it employs the same basic refrigeration-type cycle used by an air conditioner or a refrigerator, but in the opposite direction - releasing heat into the conditioned space rather than the surrounding environment. In this use, heat pumps generally draw heat from the cooler external air or from the ground.[1] In heating mode, heat pumps are three to four times more efficient in their use of electric power than simple electrical resistance heaters. Typically installed cost for a heat pump is about 20 times greater than for resistance heaters.[2][3]

It uses around 1.5-2kW of electrical energy to provide (up to) 6kW of heating energy.

Link to comment
Share on other sites

1 hour ago, esaj said:

, easier solution is to walk into a store, buy a bike computer, and attach it to the wheel

I'm waiting for the one you just stick on the wheel and it connects with bluetooth and you don't have to enter wheel diameter.:D

Link to comment
Share on other sites

24 minutes ago, steve454 said:

I'm waiting for the one you just stick on the wheel and it connects with bluetooth and you don't have to enter wheel diameter.:D

Isn't that called the "app" of the wheel (for those wheels that have it)? ;)  What boggles my mind is how often they've still managed to screw up the speed (Gotway) and distance (King Song) -calculations, although they should know the correct tire circumference and should be able to read very precisely the wheel motion through the hall-sensors... :huh:

Link to comment
Share on other sites

26 minutes ago, esaj said:

What boggles my mind

Is how the Chinese have done this and also defeated the US in the Korean war!  They are smart or we are not so smart.  I know I am not.

34 minutes ago, esaj said:

Isn't that called the "app" of the wheel (for those wheels that have it)? ;)  What boggles my mind is how often they've still managed to screw up the speed (Gotway) and distance (King Song) -calculations, although they should know the correct tire circumference and should be able to read very precisely the wheel motion through the hall-sensors... :huh:

They are trying to make us feel better:lol:

Link to comment
Share on other sites

There was a time American people worship scientists and electric engineers. Now they call them geeks and mad scientists. Movies and TVs and culture of arrogant had killed off the creativity of American.

Link to comment
Share on other sites

5 hours ago, HunkaHunkaBurningLove said:

@esaj you need this on each of your posts.  Maybe stick it in your signature.  :D  Some of us are more BIY (Buy It Yourself) people.



1 hour ago, Pingouin said:

Or if you give out free samples... ^^

I give out instructions for free, I'm not selling anything :P 

Link to comment
Share on other sites


This topic is now archived and is closed to further replies.

  • Create New...