Measuring speed of a motorbike wheel

Hi everyone,

I'm working on a small project, among sensing other stuff, i'm trying to get the speed of a motorcycle, based on the number of turns of the front wheel. To do this, i've managed to put my hands on a small bicycle speedometer, chopped the magnet / sensor and connected it to an arduino mega (i believe it's a hall sensor, but who knows, the thing is heavily packed in plastic, with only 2 wires coming out) The arduino 5+v goes to the sensor, and the input goes to pin 18(wich should be an interrupt if i'm not wrong) and is tied to ground via a 3.9k. Everytime the magnet reaches the sensor, i'm getting an interrupt. So far so good. I'll try to add more detail: /Not necessarily useful stuff Refreshing the speed in the screen every 500 ms sounds great to me, so i'm counting how many times the interrupt gets triggered in that amount of time. I've measured the wheel circumference, and it's 1.88 meters. So, let's say i count 3 turns in 500ms, then this should be something like: 3 turns * 1.88 mts = 5.64 mts If in 500 ms the wheel does 3 turns, then in 3600000(1hour) it would be...21600 turns... based on that, i can assume speed. / End of blablah Well, the problem actually is that the wheel circumference is too big, and the speedometer "resolution" is really poor, it jumps from 0 to 13 to 27 to 40, and so on... I know adding more magnets would give me more accuracy, but i had to debounce the sensor, because it was giving me two readings every time the magnet was passing by. From some basic(random) calculations, i've used 50 ms as debouncing. If the last pulse was read at least 50 ms ago, it's counted, if not, i discard it (do nothing). Adding more magnets means less time between pulses, wich could be impossible to debounce. Searching different methods, i've found an optic mouse-like sensor. I'm thinking on using the brake disc to count revolutions, using the small refrigeration holes. But i'll have to deal with interference from the sun, cars headlights, road water. rain, and lots more... I've seen rotary encoders, but i'm not sure i can mount that, and price here in my country is a bit too high. Can it be mounted as the old bike generators? (a little wheel spinning with the main wheel) Maybe using one of those little electric engines from toy cars? can it be measured somewhat proportional to the number of turns? Is there a better method of doing this? It doesn't need to be 1km accurate, but the more, the merrier :D I can add more detail, but might be even more confusing. Any idea helps!

Thanks in advance!

Hi Markosec,

If you count the number of revolutions in 500ms, your error can be as large as 1.88 * 2 = 3.76 meters per second. That’s 13.5 kilometers per hour!

Why don’t you just measure the time of each revolution instead?

Thanks JavaMan, your idea is just what i need :) Doing all the other stuff, i ended up confused and trying to do it the wrong way. :roll_eyes: I'll change the code and post results :D

Again, thanks!!

Hi again,
I am trying to implement that solution, but there must be something (again) i’m missing.
First of all, the code:
In the setup:

  attachInterrupt(5, contar, RISING);

The interrupt routine:

void contar()
  //Count only if pulses are at least 40 ms from each other...
  if ( (millis() - ultimo )  > 40)
    tiempos[puntero] = millis() - ultimo; //Save the difference in an array
    puntero++;                            //advance the array pointer
    if ( puntero == 4 )                   //don't fall from the array!
      puntero = 0;
    m_totales += 1.88;                    //count total distance
    ultimo = millis();                    //remember when this pulse happened

Then, the processing loop (a loop inside the main loop, because of the menu):

while( !myTouch.dataAvailable())  //Repeat until there is a touch on the screen, anywhere...
    if ( ( millis() - ultima_vez) > 500 )  //refresh every 500 ms
      //Check if nothing changed since last time...
      igual = true;   //turn on flag...
      for (i = 0; i<4 ; i++)
        if ( verificacion[i] != tiempos[i] )
          igual = false;                //Something has changed
      if (igual)                        //If nothing has changed
        for (i = 0; i<4 ; i++)          //empty everything
          tiempos[i] = 0; 
      promedio = 0;                     //clean average time..
      if (!igual)                       //Only calculate average if there is something to calculate
        for (i = 0 ; i<4 ; i++)
          promedio = promedio + tiempos[i];  
          if ( tiempos[i] != 0)         //sum up every non-zero item

      if ( promedio != 0)
        promedio = promedio / contador;
        //           1 hour   wheel   average     km
        promedio = 3600000UL * 1.88 /  promedio / 1000 ;
      myGLCD.printNumI(promedio, 10 , 140,3,'0');  //Write the calculated speed
      for (i = 0 ; i<4 ; i++)                      //Additionally 
      {                                            //write every measured time
      for (i = 0 ; i<4 ; i++)                      //copy everything for the next turn
        verificacion[i] = tiempos[i];           
      ultima_vez = millis();                       //remember time for the next 500ms

I’ve placed some comments, but the general idea, is to show 4 positions of timings. The four positions in the display, should be the four positions of the array “tiempos”. The array “verificacion” it’s a general idea to check for changes (when i brake to 0 the bike, it shouldn’t be waiting for another wheel turn)
The lcd library is taken from Electronics - Henning Karlsen, and seems to be working fine.
All of this, to get an average speed every 500 ms, counting the average of the array “tiempos”.
promedio = 3600000UL * 1.88 /  promedio / 1000 ; is an approach to measure speed, 1.88 is the wheel perimeter ,and i’m trying to show it in KM/H
The behaviour, is that only in the first turns, the positions are filled correctly, but then the first 3 positions are never updated, and the last one begins to show random numbers (i guess are random, because it changes without noticeable pattern, and even some negative numbers appear).
I’ve tried not clearing the array “tiempos”, but didn’t change behaviour at all.
Any idea about what could be wrong? Anything helps!

  1. Please post the code where you declare the arrays and other variables used in the ISR, so we can see what types they have, and whether they are declared 'volatile'.

  2. It's better to call millis() just once at the start of the ISR rather than 3 times, like this:

void contar()
  //Count only if pulses are at least 40 ms from each other...
  unsigned long now = millis();
  if ( (now - ultimo )  > 40)
    tiempos[puntero] = now - ultimo; //Save the difference in an array
    puntero++;                            //advance the array pointer
    if ( puntero == 4 )                   //don't fall from the array!
      puntero = 0;
    m_totales += 1.88;                    //count total distance
    ultimo = now;                    //remember when this pulse happened

Oh, sorry, i was trying to simplify, since the original code contains more stuff outside the part shown. The variables inside the loop are:

  long int cuantos  = 0;
  long velocidad  = 0;
  long contador = 0;
  long ultima_vez = 0;
  unsigned long  promedio = 0;
  boolean igual = true;
  unsigned long verificacion[3];
  int i = 0;

And the ISR variables are declared like:

volatile unsigned long ultimo = 0;
volatile unsigned long tiempos[3];
volatile int  puntero = 0; 
volatile float m_totales   = 0;

I read somewhere (tutorial maybe) that ISR variables should be volatile, not sure why, but i did it, just in case. The ISR routine is not too long? Not sure if that's ok.... Also, could it be that an interrupt calls just when the arduino is writing to the LCD, and some random lines appear? Once in a while this happens, a few green or white lines are drawed, but behaviour remains the same.... Another thing i don't understand, is that if i should enable the internal pull-up resistors or not, in this case it's enabled:

  pinMode(18, INPUT);  //Velocimetro  (PIN 18 para el interrupt)
  pinMode(18,HIGH);   // Internal pull up resistor(?)

The (presumed) hall sensor goes to arduinos +5v and pin 18, wich should be interrupt 5, and the pin 18 input is tied to ground via a resistor (3.9 Kohms ) Any suspects? :~

If a input pin has a pull-up resistor enabled the voltage at that pin sits around 5V or HIGH unless and until it is pulled LOW by whatever is attached to the pin. You have your ISR set to detect a rising edge, i.e. when the pin goes from LOW to HIGH. Theoretically, this should only mean a delay as the input pin goes from HIGH to LOW then back to HIGH, but only if the sensor attached to the pin is reliably pulling that pin LOW each revolution of the wheel.

Depending on how what’s attached to pin 18 behaves, you may want to try changing the ISR to be falling or disable the pull-up resistor.

You have declared your arrays with 3 elements each, but they should be declared with 4 elements each instead. You will be overrunning them when you store the 4th element, which probably accounts for the problem.

Regarding the correct wiring of the sensor, if it is a Hall sensor then it should have 3 terminals, not 2. Do you have a datasheet for it? If it has only 2 terminals, maybe it is a reed switch.

Hi again Markosec,

You’ve made progress, but it still seems more complicated than it needs to be. 4 magnets? arrays? interrupts? Maybe it would help if you saw an example of a simpler way. One measurement per rotation. See if you can work through the logic in loop() on the sketch below. The code is tested and works. You can ignore the lines having to do with the lcd display if you want, and you will need to change the rollout to 1880 and convert from miles per hour to kilometers per hour.

// Written by Tom Fangrow, July 15, 2012
// reed switch on pin 4, LED on pin 13

#include <LiquidCrystal.h>

LiquidCrystal lcd(3, 5, 9, 10, 11, 12);
unsigned long newTime, oldTime=0;
float newPeriod, oldPeriod=0.0, velocity;
float rollout = 2085.0;      // tire circumference in mm 

void setup() {
  pinMode(4, INPUT_PULLUP);  // other side of switch to ground
  pinMode(13, OUTPUT);
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("Bike Computer");              // welcome screen
  lcd.setCursor(0, 1);
  lcd.print("version 1.0");

void loop() {
  if(digitalRead(4) == LOW) {              // switch is closed
    newTime = millis();                    // note the time
    digitalWrite(13, HIGH);                // turn on LED
    newPeriod = float(newTime-oldTime);    // rotation period
    velocity = rollout*60*60/(newPeriod*1609.34);     // mph
    oldTime = newTime;                     // update oldTime
    oldPeriod = newPeriod;                 // update oldPeriod
    delay(60);           // allow time for switch to open again
  else {                                   // switch is open
    digitalWrite(13, LOW);                 // turn off LED
    if(millis()-oldTime > 3000) {          // if bike stopped
      velocity = 0.0;                      // zero out display
      oldTime = millis();    // this will refresh every 3 seconds

void updateScreen() {
  lcd.setCursor(0, 0);                    // first line
  lcd.print("Current Speed");
  lcd.setCursor(0, 1);                    // second ine
  lcd.print(" mph   ");     // extra spaces erase previous data

hi everyone, thanks for the examples and guidance, i've finally managed to make it work! :) I started from scratch, with another approach as suggested. But being a noob, a loose wire apparently fried up my mega, sending current through an output pin , it's now unable to communicate with a pc, so i can't upload new sketches, it keeps being detected as an unknown device. =( (tried different usb cables and computers) I'm now trying to design a pcb to protect the inputs and outputs via optocouplers (and placing wires really really away from the arduino)while a new mega arrives. Thanks to everyone for the support, i'll open a new post (with pictures!) when i get the new board, to show what's done ;)

Sorry to hear about your mega. Looking forward to the pictures of your project.

Another consideration - when you measured the diameter/circumference of the wheel, that was unloaded. When riding the bike, there's a load on it which will compress the wheel locally, and change the effective circumference. To check for this, I would put the wheel down on the ground, mark the spot, take it through exactly 5 or 10 revolutions, and measure the distance. Sit on it while you're doing the rolling. This will act as a good check if your circumference is correct (although I don't think you'll get 13% errors from this issue, more like 2-4%.)

Also, instead of replacing the whole Arduino Mega, you may be able to buy a chip that you can drop into the board and get it working again. Or if the problem is on the board and not the chip, try to track it down and replace the part that is fried.

Finally, as an arduino newbie, I don't know this for sure, but it sounds like a regular arduino uno would be more than sufficient for your application.

Regards, Martin

Hi aeronaut!
Thanks for the wheel tip, i’ve measured it while standing on the air, so, yes i think i’m getting a small error. I’ll measure again with your method, and check results. :slight_smile:
I’m a newbie to electronics, not only arduino, so replacing the Mega’s chip is totally out of my league. Plus, the value of the chip in my country, makes it cheaper to replace the whole board :~
I woul’d ve started with an arduino uno, but i didn’t tell you about the “other” stuff :slight_smile:
The final idea is to fully replace my motorbike’s panel, this includes speedometer, partial and total odometer(is that a word?), tach, fuel indicator, hi-low lights indicator and turn lights. In addition to this, seeing other motorbikes useful stuff, i’m adding those ideas to my project: Remote engine start, smart choke control (warming up), pc communication, and (in a million years) adaptable bodywork (move the plastics according to bike speed).
I wanted to use a nice and big display, and i found one of the itead’s display shields with touch and SD card slot (3.2") to be big enough (for now, it’s a bit slow).
All of this seems easier with a Mega, wich has more in/out pins and interrupts available.
I know it’s a lot of work, and probably totally useless, but hey, it will be fun!
Any idea is always welcome!