Summary of Bicycle Speedometer and Odometer Reference: Pulse Qualification, Wheel Calibration and Interval Handling
This article outlines building a bicycle speedometer using an Arduino as a pulse counter. Key steps include debouncing mechanical sensor signals to prevent false counts, keeping interrupt service routines brief, and calibrating distance via the roll-out method for accurate wheel circumference. The author compares two speed calculation methods: fixed time-window counting versus interval-between-pulses analysis, noting specific failure modes for each like zero-reading at low speeds or freezing when stopped. Proper handling of stopped-wheel states and odometer range limits are also emphasized.
Parts used in Bicycle Speedometer:
- Arduino board (UNO Rev3 or similar)
- Wheel sensor (mechanical type producing one signal per revolution)
- Display unit
- Magnet and wiring assembly
- Digital input pins (specifically interrupt-capable pins 2 and 3 on UNO)
Treat a bicycle speedometer as a qualified pulse counter. Debounce each wheel revolution before it is counted, keep the counting routine short, calibrate wheel circumference from an actual measurement, and decide up front whether speed comes from a fixed time window or from the interval between pulses.
What the electronics have to do
A wheel speedometer is a pulse counter with a display attached. A sensor at the wheel produces a signal once per revolution, the firmware turns those events into distance, and a readout shows the result. The hard part isn’t the wiring. It’s deciding what counts as a valid event, how you convert events into distance, and what the display should do when nothing is moving. Those three decisions drive everything else.
Qualifying a pulse
Mechanical sensors don’t produce clean edges. The official Arduino Debounce example notes that contact bounce can otherwise turn one physical transition into multiple state changes, and it qualifies a changed switch state using elapsed-time checking before accepting it. A wheel sensor deserves the same treatment: qualify the event, then count it exactly once. The Arduino attachInterrupt() reference also recommends keeping interrupt service routines short, which means the routine should set a flag or bump a counter rather than doing arithmetic or display work inside the interrupt.
Choosing the counting pin
The attachInterrupt() reference lists pins 2 and 3 as the interrupt-capable pins on the UNO Rev3, and it notes that pin mappings vary across boards. It recommends digitalPinToInterrupt(pin) so the code follows whichever board is fitted instead of hard-coding a number. If your board isn’t an UNO, check its own documentation rather than assuming the same pins apply.
Calibration is the biggest error source
Distance per revolution depends on the true rolling circumference, not the number printed on the tyre. A practical approach is the roll-out method: mark the tyre and the ground, roll the wheel exactly one revolution under normal load, and measure the distance travelled. That measured value is the only thing that converts counted revolutions into believable distance, and it should be repeated if you change tyres or pressure.
Speed from a window or from intervals
There are two common ways to turn events into a speed figure, and they fail differently. Choosing between them is a design decision, not a fact about any particular sensor.
| Method | What it produces | Where it suits | Failure to guard |
|---|---|---|---|
| Fixed time-window counting | number of pulses inside a fixed interval | steady riding and simple readouts | coarse at low speed; must show zero when stopped |
| Interval between pulses | elapsed time from one event to the next | sensitivity at low speed | no new interval when stopped; needs a timeout |
| Polling a digital input | sampled input state over time | when interrupt-capable pins are scarce | can miss short pulses between samples |
Evaluating a design, in order
- Confirm the interrupt pin. Check the actual board’s interrupt-capable pins and use digitalPinToInterrupt() rather than assuming UNO numbering.
- Qualify every transition. Apply elapsed-time debounce before a pulse is accepted, following the pattern in the Debounce example.
- Keep the ISR short. Set a flag or increment a counter inside the interrupt; do the maths and the drawing outside it.
- Calibrate by measurement. Establish the rolling circumference with the roll-out method and re-check after any tyre change.
- Pick window or interval based on the speed range you care about, and accept that each has a blind spot.
- Define stopped-wheel behaviour. Decide explicitly that no pulses in a window means zero, not the last value held.
- Plan the odometer’s range so a long ride can’t silently wrap the distance variable.
Reader questions
Do I need an interrupt at all?
Not necessarily. Polling works if pulses are long compared with the sample interval, but it can miss short ones. Interrupts catch brief events, which is why the reference keeps their routines short.
Is debounce really needed on a wheel sensor?
Yes, if the sensor is mechanical. Bounce turns one revolution into several counts, which inflates both distance and speed. The Debounce example exists precisely because of that behaviour.
Why does speed read zero at very low speed?
A fixed-window counter can finish a window with no pulses counted, so it reports zero even while creeping. An interval-based approach handles that range better, but it needs a timeout so it doesn’t freeze once you stop.
How accurate will my distance figure be?
It hinges on the measured circumference, the load and the sensor position. Treat the number as an estimate that improves with careful calibration rather than a certified measurement, and re-check the circumference after any tyre or pressure change.
The same qualified-event reasoning shows up in input work like the rotary-encoder menu reference, and rotational pulse counting also underlies the anemometer pulse-sensing reference.
Documentation
- Arduino attachInterrupt() reference (official reference-en source)
- Arduino Debounce example (official arduino-examples source)
Documentation note: the sources used here cover interrupt usage and switch debounce only. No bench or road testing was done, and sensor placement, magnet retention, code behaviour and actual distance or speed accuracy are not verified. Keep magnets and wiring clear of brakes and moving parts; this is an evaluation reference, not a vehicle or safety-critical instrument.
- How do I ensure a single wheel revolution is counted only once?
Apply elapsed-time checking to qualify the event before accepting it, effectively debouncing the signal. - Can I use any digital pin for the wheel sensor interrupt?
No, you must use interrupt-capable pins like 2 and 3 on an UNO Rev3 and should use digitalPinToInterrupt() for portability. - What is the best way to determine the correct distance per revolution?
Use the roll-out method by marking the tyre and ground, rolling the wheel one revolution under normal load, and measuring the distance. - Does the speed read zero at very low speeds?
A fixed time-window counter may report zero if no pulses occur within the window, whereas interval-based approaches handle low speeds better but require a timeout. - Why is debounce necessary for a wheel sensor?
Mechanical sensors suffer from contact bounce which can turn one physical transition into multiple state changes, inflating distance and speed readings. - Should I perform calculations inside the interrupt service routine?
No, keep the routine short by setting a flag or bumping a counter, and perform all arithmetic and display work outside the interrupt. - How should the system behave when the bike is stopped?
You must explicitly define that no pulses detected in a window means zero speed, rather than holding the last value. - Do I need interrupts if polling is available?
Polling works if pulses are long compared to the sample interval, but interrupts are recommended to catch brief events that polling might miss.
