Originally published on ShillehTek IoT & Beyond.
Project Overview
Arduino Nano + IR reflective sensor tachometer: Use an IR sensor to detect pulses from a spinning shaft, count them with a hardware interrupt, and display accurate RPM on an SSD1306 I2C OLED.
This contactless tachometer helps verify whether a motor, fan, or spindle is actually running at the speed your code expects.
- Time: ~45 minutes
- Skill level: Intermediate
- What you will build: A contactless RPM meter with an OLED readout and an interrupt-driven pulse counter.
Pulses in, RPM out, with no contact to the shaft.
Parts List
From ShillehTek
- Arduino Nano V3.0 Pre-Soldered - the microcontroller running the interrupt counter and OLED updates
- SSD1306 0.96" I2C OLED - displays live RPM
- Uno Starter Kit - includes the IR obstacle/reflective sensor module used as the pickup
- N20 Gear Motor + TB6612 Driver - a test motor and driver to measure
- 400-Point Breadboard - for prototyping the circuit
- Dupont Jumper Wires - to connect the Nano, sensor, and OLED
External
- A strip of white tape (or a hole/notch) on the shaft or disc to create pulses
Note: RPM = (pulses per second ÷ pulses per revolution) × 60. One reflective stripe equals one pulse per revolution. The original build used a 3-blade fan, so it divided by 3.
Step-by-Step Guide
Step 1 - Wire the sensor and OLED
Goal: Connect the IR sensor to an interrupt-capable pin and connect the SSD1306 OLED over I2C.
What to do: Wire the IR module as VCC → 5V, GND → GND, OUT → D2 (interrupt-capable). Wire the OLED as SDA → A4 and SCL → A5. Aim the IR emitter/receiver pair at the shaft from about 5 to 15 mm away, then adjust the module potentiometer until the module LED flickers cleanly as the stripe passes.
IR OUT on D2 so the interrupt can count pulses reliably.
Expected result: A clean digital pulse per revolution (or per blade/stripe) on the sensor output.
Step 2 - Count pulses with an interrupt
Goal: Count every pulse even while the display is updating.
What to do: Use a hardware interrupt so counting happens immediately when the sensor edge arrives. The interrupt service routine increments a counter in the background. In the main loop, read the counter once per second, convert the count to RPM, and reset the counter for the next measurement window.
Expected result: Pulse counting that keeps up at higher shaft speeds where polling would miss transitions.
Step 3 - Upload the sketch
Goal: Compute RPM once per second and show it on the OLED.
What to do: Upload the code below. Set PULSES_PER_REV to match your stripe count (or blade count) so the RPM calculation is correct.
Code:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
const int SENSOR = 2;
const float PULSES_PER_REV = 1.0; // stripes/blades per rotation
volatile unsigned long pulses = 0;
unsigned long lastCalc = 0;
void countPulse() { pulses++; }
void setup() {
pinMode(SENSOR, INPUT);
attachInterrupt(digitalPinToInterrupt(SENSOR), countPulse, FALLING);
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
oled.setTextColor(SSD1306_WHITE);
lastCalc = millis();
}
void loop() {
if (millis() - lastCalc >= 1000) {
noInterrupts();
unsigned long p = pulses; pulses = 0;
interrupts();
lastCalc = millis();
float rpm = (p / PULSES_PER_REV) * 60.0;
oled.clearDisplay();
oled.setTextSize(1); oled.setCursor(0, 0); oled.print("Tachometer");
oled.setTextSize(3); oled.setCursor(0, 22); oled.print((int)rpm);
oled.setTextSize(1); oled.setCursor(0, 52); oled.print("RPM");
oled.display();
}
}
Expected result: Live RPM updates about once per second on the OLED.
Step 4 - Test it on a real motor
Goal: Measure actual motor speed at different drive levels.
What to do: Drive an N20 gear motor with the TB6612 at a few PWM levels and read the tachometer output. At full duty with a 12 V supply, a rated 100 RPM gearmotor should land near its rating. You can also observe how nonlinear PWM-to-speed can be, which is useful when moving toward closed-loop speed control.
Expected result: A measured speed curve for your motor rather than an estimate.
Conclusion
You built an Arduino tachometer that uses an IR reflective sensor, a hardware interrupt counter, and an SSD1306 OLED to turn shaft pulses into a stable RPM number.
Want the exact parts used in this build? Grab them from ShillehTek.com. If you want help customizing this project or building something for your product, check out our IoT consulting services.
Credits: All photos and images in this tutorial are credited to Mirko Pavleski on Hackster.io. The original guide by Mirko Pavleski served as the reference for this ShillehTek version.


Top comments (0)