Introduction: Arduino Odometer for Bike (1.1 MA Total Current, No Display)

Here is a simple, (somewhat) compact, and low power (only about 1.1 mA current draw) odometer that can be installed on a bicycle. Wheel rotations are detected by voltage spikes generated from a magnet (zip-tied to a spoke) moving near a wire coil (mounted on a chain stay). In order to keep this project simple and low-current, there is no display. To see distance traveled, the user must use the Arduino serial monitor.


Arduino (any board should work - even a LilyPad - but these instructions are for the Arduino Micro (with ATmega32U4). Use any non-32U4 board at your own risk)

Op Amp IC (I used the LM 324, but if you use the NTE889M, you can get power draw of the entire odometer system down to about .6 mA)

Battery (anything from 3V - 5V. No higher, since we shall bypass the regulator to avoid the quiescent current of the Arduino's on-board 5V regulator. I used a 1200 mAh 3-cell 3.6V rechargeable NiMH battery pack for an over 1 month battery life! If you want to make it more compact, a 3V 240 mAh CR2032 coin cell battery would still be suitable)

Jumper Wires

Wire Coil (I ripped one out a wire coil from a "wall wart" power supply)

Small Bar Magnet (must be axially magnetized if you follow this instructable for the mounting of the magnet and coil - or feel free to do what you want as long as wheel rotations provide a changing magnetic flux through the mounted wire coil)

Breadboard (Or if you are daring enough, solder it to perfboard)

Enclosure (I used a Wrigley's Spearmint chewing gum tin)


-----------OPTIONAL ITEMS-----------

Barrel Jack and connector (for quick installation and removal)

Power Switch (with such low power consumption, you may find a switch more trouble than it's worth!)


Step 1: The Circuit

I designed the circuit in Fritzing. Build as shown in the .fzz file. I didn't see a barrel jack plug in the Fritzing parts, so I just connected the wires from L1 directly to the barrel jack. Also, disregard the unrouted connections in Breadboard view. The connections are routed, but Fritzing isn't recognising any wires connected to the Arduino in the Breadboard view. I did not have any trouble with this in the Schematic view. If you have any questions, please ask in the comments or message me.

Note that polarity does not matter for L1 and the barrel jack.

Step 2: The Sketch

Below are the Arduino sketches you will need to use the odometer. I also included the .ino files (code is the same as listed below, but you may want to download the .ino files since nuisance HTML tags may appear in the embedded code)

Odometer sketch:

<p>#include <avr/eeprom.h>
#include <avr/sleep.h>
volatile unsigned long totRot, rotSinceClr;
volatile boolean canClear;//determine if it is
                        //"safe" to zero rotSinceClr
                        //since interrupt could be triggered
                        //during program setup
void setup()
  CLKPR = B10000000;
  CLKPR = B00000001;//divide ck by 2
  canClear = false;
  pinMode(2, INPUT_PULLUP);
  totRot = eeprom_read_dword((uint32_t*)0) + 0x80;//round up
  rotSinceClr = eeprom_read_dword((uint32_t*)4);
  attachInterrupt(0, rot, FALLING);//int0 is pin 3 on micro
  attachInterrupt(1, clr, FALLING);//int1 is pin2 on micro
void clr()
    rotSinceClr = 0;
    eeprom_update_dword((uint32_t*)4, 0);
void loop()
  canClear = true;
  digitalWrite(13, LOW);
  if(totRot%(0x100) == 0)
    eeprom_update_dword((uint32_t*)0, totRot);
  if(rotSinceClr%(0x100) == 0)
    eeprom_update_dword((uint32_t*)4, rotSinceClr);
  //thanks to <a href="">>
  digitalWrite(13, HIGH);
  //this is the only place in loop() where
  //interrupts should be called
void rot()

Viewing distance sketch: (use this sketch to view distance traveled on the serial monitor)

<p>#include <avr/eeprom.h>
#define ROT_PER_MI 737//for a 700c wheel
void setup()
  unsigned long totRot = eeprom_read_dword((uint32_t*)0);
  unsigned long rotSinceClr = eeprom_read_dword((uint32_t*)4);
  String toPrint = "totRot: ";
  toPrint += totRot;
  toPrint += "\nrotSinceClr: ";
  toPrint += rotSinceClr;
  Serial.print("total miles: ");
  Serial.print("miles since clr: ");
void loop()

Step 3: Assembly

Once you have constructed the circuit, find an enclosure that can encase it (Note that wire coil L1 will be placed outside of the enclosure). Then zip tie a magnet to a spoke of the rear wheel and zip tie the wire coil to a chain stay. View pictures to see mounting orientation. If you were to draw an imaginary line through the length of the magnet, that line should pass through the centre of the coil when the magnet is nearest to the coil. Sorry if the image quality is too poor for you to see it. Don't hesitate to ask for clarification.

Step 4: Calibration

Finding ROT_PER_MI:

Measure how far the bike travels per wheel rotation. Replace the value "ROT_PER_MI" with the value: ROT_PER_MI=63360*n/d where n is the number of times the wheel rotated and d is the distance traveled by the bike. The value I have in the sketch (737) is for a 700c wheel.

Getting the optimum R2:R1 ratio:

The "sensitivity" of the electromagnetic wheel rotation detector is proportional to (R2/R1). The following procedure describes how to ensure the odometer detects all the wanted rotations, but does not pick up noise.

Make sure the detection is sensitive enough:

Spin the wheel with magnet on it. Every time the magnet passes the wire coil, LED13 on the Arduino should illuminate (The pulses are brief and dim so do this in a dark area). If there are no pulses, most likely there is a mistake or loose wire in the circuit. Recheck the circuit. If there are still no pulses of LED13 when the magnet passes the wire coil, the detector is not sensitive enough. Increase the R2:R1 ratio, that is, increase R2 from 1MΩ or decrease R1 from 10KΩ until the pulses are detected.

...but not too sensitive:

Bump the wire coil and the wires leading to the coil without spinning the wheel. If these unwanted pulses are detected (LED13 pulses), decrease the R2:R1 ratio until you achieve desired results. If the detector is not sensitive enough to pick up rotations and is picking up unwanted noise, check the connections from coil L1 to the rest of the circuit. You may have to solder the connections to minimise noise, as twisted wires can momentarily become disconnected and inject noise. Also, try a stronger magnet or more turns of wire in coil L1.

Step 5: Evil Modifications

The purpose of the op amp IC is to magnify the small voltages induced in the wire coil. The 324 op amp uses .9 mA and the arduino only uses .2 mA when sleeping (in this application, the majority of the time the arduino will be sleeping). Therefore, a significant reduction in current draw could be made if only the magnet could induce high enough voltages in the wire coil. My following suggestion didn't work for me, but if you are daring enough....

Remove the op amp IC and connect the wire coil from GND to the interrupt pin, using diodes as shown to protect your precious arduino from high/low voltage spikes. Spin the wheel and see if the pulses are being detected (watch for led on pin 13 to blink every time the magnet passes the wire coil. Be in a dark area - the blinks are quit brief and dim). If no pulses are detected, you can try stronger magnets (the solution to many of the world's problems), try adding more turns in the wire coil, position the magnet/coil so the magnet gets even closer to the coil, coil the wire around a ferrous core to increase the permeability, or spin the wheel faster. I think hard drive magnets might be strong enough to generate detectable pulses, but I was unable to acquire adequate said hard drive magnets.