Measure RPM - Optical Tachometer




Introduction: Measure RPM - Optical Tachometer

About: Greetings ! I'm Tanay, a hobbyist interested in making robots and sharing stuff. I hope that my instructables help you in solving your problems Happy Tinkering ! Note : Sorry for the inconvenience but I won...

This Instructable will show you how to make a Portable Digital Optical Tachometer using an Arduino Uno.

Instead of a slotted sensor , it has a reflection based sensor. So :

1. You don't have to worry about the thickness of the rotor

2. The number of blades won't change your readings

3. It can also read the RPM of drum style rotors which slotted sensor can't

What is a tachometer ?

A tachometer is a device used to measure the RPM or Revolutions Per Minute of any rotating body. Tachometers can be contact based or non-contact ones. The non-contact or contact-less optical tachometers usually use laser or Infrared beam to monitor the rotation of any body. This is done by calculating time taken for one rotation.

  • It can measure RPM over 20k
  • Sensor range extends upto 7~8 cm
  • Displays Maximum RPM when left Idle
  • Automatically toggles modes from "Idle" to "reading"
  • Can be adjusted to match the ambient lighting conditions
  • It is comparatively cheap and easy to build
  • Can work without an LCD
  • Programmable and supports customization
  • Connect an SD card to the Arduino to keep logs
  • Step 1: Part List :

    • Arduino
    • Resistors - 33k , 270 ohm , 10k potentiometer
    • LED - blue
    • IR LED and Photodiode
    • 16 x 2 LCD
    • 74HC595 shift Register
    • Ribbon cable ( 3 wire )
    • Perfboard and headers
    Tools and Hardware
    • Soldering Iron
    • Solder
    • Pins
    • Screws
    • Motors and DC fan

    Step 2: Build the Sensor

    For the sensor you'll need an IR LED and a Photodiode.

    1. Start by sanding the LED and photodiode to make it flat ( do not sand it too much or you'll destroy it ).

    2. Then fold a strip of paper sheet as shown. Make two such Structures so that the LED and Photodiode fit tightly into it. Joint these together by glue and paint them black.

    3. Insert your LED and Photodiode in them in such a way that the positive ( longer ) lead of the LED is right above the shorter lead of the photodiode.

    4. Glue them into the cover using superglue and solder the positive ( longer ) lead of the LED to the shorter lead of the photodiode.

    5. Solder the 3 wire ribbon cable to the remaining leads

    In my case :

    1. Orange wire --> LED's positive pin and photodiode's shorter lead

    2. Yellow wire --> photodiode's longer lead

    3. Green Wire --> LED's ground pin

    You're ready to make the board >>

    Step 3: Making the Sensor Board

    Take a small piece of Perfboard and place the components according to the schematics.

    The resistor values may vary depending on what kind of photodiode are you using.

    The potentiometer helps in reducing or increasing the sensitivity of the sensor.

    Finally solder the sensor wires as shown and solder 3 headers.

    The headers ( in order ) are shown on the left side of the schematic.

    make a cuboidal paper tube whose length is equal to the sensor wires.

    Step 4: The 3-pin LCD

    This method uses a 8-bit shift register 74HC595 with a 16 x 2 LCD. Normally this LCD uses 6 pins but using a shift register reduces the pin requirement by 3.

    The full instruction guide and the library can be downloaded from THIS WEBSITE !

    ## Recent Update : The library provided on the website has a lot of errors and conflicts. I've uploaded my version of enhanced ShiftLCD library. I recommend you to use the one attached below.

    The only thing that I've changed is :

    instead of going for (2, 4, 3) configuration I've used (8, 10, 9)

    So be sure to change the pin mapping accordingly

    Step 5: Make the Box

    You can use any type of case for this but I've used a piece of cardboard to make enclosure.

    Cut the cardboard as shown and cut appropriate sized slits for the USB port , power jack and the sensor board.

    Mount the Arduino on the platform using screws.

    Attach the sensor and push it through the hole.

    Connect the LCD to Arduino as shown.

    Close the box and paint.

    Step 6: Finishing Touch

    Make a small ( 5mm ) hole to fix the status LED. Solder a 270 ohm resistor to the LED and insert it into pin 12 on Arduino.

    Fold the cardboard along the lines to complete the enclosure. Keep the folds in place by using pins.

    Cover the sensor with a cubical paper tube to give additional mechanical strength.

    Place the LCD module over the box.

    Your device is ready for calibration and programming.

    Step 7: Program

    ShiftLCD lcd(8 ,10 , 9);    // DEFINE LCD PINS
    unsigned long int rpm, maxRPM;  //  DEFINE RPM AND MAXIMUM RPM
    unsigned long time;         //  DEFINE TIME TAKEN TO COVER ONE REVOLUTION
    int ledPin = 12;           //   STATUS LED
    long prevtime = 0;       //  STORE IDLE TIME TO TOGGLE MENU
     void setup()
         Serial.begin(9600);   // GET VALUES USING SERIAL MONITOR
         lcd.begin(16, 2);     // INITIATE LCD
         REV = 0;      //  START ALL THE VARIABLES FROM 0
         rpm = 0;
         time = 0;
         pinMode(ledPin, OUTPUT);
         pinMode(3, OUTPUT);           
         pinMode(4, OUTPUT);
         digitalWrite(3, HIGH);             //  VCC PIN FOR SENSOR
         digitalWrite(4, LOW);              // GND PIN FOR SENSOR
         lcd.print("TACHOMETER");           //   STARTUP TEXT
         lcd.setCursor(0, 1);
         lcd.print("- ELECTRO18");          //  THAT'S ME
     void loop()
      long currtime = millis();                 // GET CURRENT TIME
      long idletime = currtime - prevtime;        //  CALCULATE IDLE TIME
        if(REV >= 5 )                  //  IT WILL UPDATE AFETR EVERY 5 READINGS
         if(flag==0)                     //  CLEAR THE LCD TO AVOID ANY GARBAGE TEXT
           lcd.print("SENSOR MEASURING");
           flag=1;                          //   AFTER FLAG = 1 , THE LOOP WILL NOT EXECUTE AGAIN
         rpm = 30*1000/(millis() - time)*REV;       //  CALCULATE  RPM USING REVOLUTIONS AND ELAPSED TIME
         if(rpm > maxRPM)
         maxRPM = rpm;                             //  GET THE MAX RPM THROUGHOUT THE RUN
         time = millis();                            
         REV = 0;
         int x= rpm;                                //  CALCULATE NUMBER OF DIGITS IN RPM
           x = x/10;
         if(RPMlen!=prevRPM)                             // IF THE RPM FALLS TO A LOWER NUMBER WITH LESS DIGITS , THE LCD WILL GET CLEARED
           prevRPM = RPMlen;
           lcd.print("SENSOR MEASURING");
         lcd.setCursor(0, 1);
         lcd.print(rpm,DEC);                        //  PRINT RPM IN DECIMAL SYSTEM
         prevtime = currtime;                        // RESET IDLETIME
       if(idletime > 5000 )                      //  IF THERE ARE NO READING FOR 5 SEC , THE SCREEN WILL SHOW MAX RPM
         if(flag==1)                            // CLEAR THE LCD
         lcd.print("MAXIMUM RPM");
         lcd.setCursor(0, 1);
         lcd.print(maxRPM,DEC);                     // DISPLAY MAX RPM
         lcd.print("   RPM");
         lcd.print("IDLE STATE");
         lcd.setCursor(0, 1);
         lcd.print("READY TO MEASURE");
         prevtime = currtime;
       REV++;                                         // INCREASE REVOLUTIONS
       if (led == LOW)
         led = HIGH;                                 //  TOGGLE STATUS LED
         led = LOW;
       digitalWrite(ledPin, led);
    //////////////////////////////////////////////////////////////  END OF THE PROGRAM  ///////////////////////////////////////////////////////////////////////

    Step 8: Explanation and Calculation

    This program basically monitors the IR sensor's value constantly and with the highest priority using Interrupts.

    The Arduino Uno has 3 interrupts and the Interrupt 0 is pin 2 on the arduino.

    attachInterrupt(0, RPMCount, RISING);

    This line attaches an interrupt to pin 2 on arduino in "RISING" mode. This means that whenever the sensor goes from LOW to HIGH , the function RPMCount(); is invoked.

    This means that in one revolution , the function will be called twice ( REV++ ). Therefore actualREV = REV/ 2.

    rpm = 30*1000/(millis() - time)*REV;

    To calculate the actual RPM, we need the time taken for one revolution. And (millis() - time) is the time taken for one full revolutions.

    In this case , let t be the time taken for one full revolution , so the total number of revolutions RPM in 60sec ( 60*1000 millisecond ) is :

    rpm = 60*1000 / t * actualREV => rpm = 60*1000 / (millis() - time ) * REV/2

    OR rpm = 30*1000 / (millis() - time) * REV;

    Step 9: Testing and Troubleshooting

    Testing :

    1. Take a DC fan and stick a white tape to one of it's blades. Place the sensor 2~7 cm from the blades

    2. The readings will appear on the LCD

    3. If the sensor gets no readings for 5 sec then it will automatically display the idle screen

    4. The Idle screen will display the maximum RPM reached in that particular run.


    1. If the status LED is not blinking, try to adjust the potentiometer until the sensor is able to get readings

    2. Ambient light may sometimes interfere with the sensor. Decreasing the sensitivity would eliminate the chance of getting false readings.

    3. Check the polarity of the photodiode properly.

    4. If everything fails , check your sensor manually by using :

    Serial.println( digitalRead(2) ) ;

    if your sensor doesn't show " 1 " when any object is placed in front of it then try increasing the value of 33k resistor.

    Step 10: Conclusion

    Though there are many optical tachometers available in the market, this device is comparatively cheap and works quite well. I've tested it above 20000 RPM and it works every time ! Being open source and programmable , there arise infinite possibilities of customizing this project.

    Feel free to ask anything about this project. Suggestions , queries , corrections and "grammatical errors" are welcome !

    Happy Tinkering :)

    5 People Made This Project!


    • Tiny Home Contest

      Tiny Home Contest
    • Metalworking Contest

      Metalworking Contest
    • Creative Misuse Contest

      Creative Misuse Contest

    245 Discussions


    Question 8 weeks ago

    HI there.

    Well, I have a different question in mind. I want to build hydrogen generation that works with RPM, so I need it to be active from 1500RPM and stay active when the rpm is constant and when it increases but shuts off when the RPM drops. That said it must also work with the RPM to generate a given PWM out to the mosfet to increase the amount of gas generation for the car because the fuel prices keep climbing in South African and I want to give a try. but I am very new to this Arduino.


    Question 2 months ago

    Can i have the output on my laptop ?

    bruh,i want to get rpm data through obd interface on my motorcycle but my vehicle is it possible for you to make it using inbuilt pulse or external sensor???

    I must have missed something because I am baffled by something that you stated in step 8 - Explanation and calculation where you stated the following:

    "This line attaches an interrupt to pin 2 on arduino in "RISING" mode. This means that whenever the sensor goes from LOW to HIGH , the function RPMCount(); is invoked.

    This means that in one revolution , the function will be called twice ( REV++ ). Therefore actualREV = REV/ 2"

    What confuses me is why there would be TWO rising edge interrupts for each revolution, if there is only one reflective area on the unit being measured. As the IR Led emitter hits the leading edge of the reflective surface, it will cause the IR Photo Diode detector to become activated and cause the voltage to rise to slightly less than the VCC voltage, which in turn will trigger the interrupt of Int0 to fire and invole the RPMCount ISR as the RISING edge is detected. That voltage level will remain at the active level until the IR LED emitter is no longer over the reflective surface and exciting the IR Photo Diode and the signal level will FALL to 0 but because the Int0 interrupt only reacts to the RISING edge, the RPMCount ISR will NOT be invoked. It would seem to me that the RMPCount would only be invoked two times per revolution if the Interrupt mode was set to activate on the condition of "Logic Change"

    Thus i believe that your RPM value is off by a factor of 2, because you divide the time in half to account for the "2 interrupts" that are not actually 2, but are rather only one.

    6 replies

    one thing i realized is that the rev/2 probably originated from measuring a 2 blade rotor. the only reason I can make this assumption is because my IR sensor is tripping the interrupt twice for each blade. for IR depending on the sensitivity set on your photodiode, reflective materials can be almost any surface. Much like how you can change the channel on your TV even when aiming at the wall in the other direction. Just remember to take into account the number of blades if your RPMs way higher than usual (like by a factor of 2 lol)

    ps - Also, you guys rock! I couldn't have finished this without you guys.

    "one thing i realized is that the rev/2 probably originated from measuring a 2 blade rotor."

    You can see in the video that there was one piece of white paper, which worked as a mirror and generated reflected signal.

    So there is ONE blade rotor.

    Thank you for your comment. I also stumped here.

    I agree, that It will trigger the interrupt twice, but one of them will change the signal from low to high, another time - from high to low.

    As far as RISING is used here => the counter should increase once per revolution.

    Hello, absolutely correct! I kept wondering why he assumed it counts twice. and therefore he needs to divide by two. The only case would have been that he uses CHANGE rather than RISING or FALLING.

    I am trying to do the same for a motor with encoder and optocoupler, it seems that the optocoupler does mis count on low speeds! it basically counts more than once (interrupt is called 2~3 times) while the rotor blade is just leaving the optocoupler IR Led and receiver (i.e while going out)!

    Yet to try to figure this out! I already have a debounce for ~20 milliseconds and using a 65ohm resistor for the IR LED.

    I agree dividing by two put reading off by a factor of 2. There will still be some error because we can assume the first time it interrupts was not a full revolution and should be subtracted off but the time would need to be updated to account for this offset as well and millis() can't be called within RMPcount because it is an interrupt itself.

    Thanks for the feedback. Having been a professional software developer for 35+ year and an electronics engineer for 10+ years before that it just didn't add up. As for subtracting 1 from the count, it might just be better to set the inital count to -1 and then the first interrupt will set the counter to zero and then all calculations will be based upon all complete revolutions, irrespective to how much of the first revolution was completed when the interrupt occurred.

    I'm a newbie with electronics, so my questions might be copletely out of place, but here they go:

    1) why two RISING events in one rev?

    2) why connecting the GND of the sensor to an I/O pin instead of the arduino's GND?


    2 replies

    Hey there !

    1. When the rotor will complete one revolution, it will trigger the interrupt twice i.e. first while leaving the sensor and second while returning back to the initial point after one complete revolution. This will invoke the function twice.

    2. I've used one of the I/O pins as the GND pin because I needed the sensor to be as compact as possible. Using three pins in a row is pretty convenient. You could attach the sensor GND to the Arduino GND as well, that will work just fine.

    I hope it helps :)

    "When the rotor will complete one revolution, it will trigger the interrupt twice i.e. first while leaving the sensor and second while returning back to the initial point after one complete revolution. This will invoke the function twice."

    I agree, that It will trigger the interrupt twice, but one of them will change the signal from low to high, another time - from high to low.

    You are using RISING => the counter should increase once per revolution.


    Question 5 months ago

    sir how can i use my laptop for output reading instead of using lcd

    and also please tell me about what is the modification required to arduino program

    I am trying to make this project with some slight modifications, more of a stroboscope. Instead of having a sensor to record the readings, the LED would blink and change blink speeds with an encoder. I'm looking to avoid the delay command, so I'm going off of the BlinkWithoutDelay example and I am having some trouble. Any help is appreciated. Here is my code:

    unsigned long timerStart,

    timerEnd = 12048UL; // interval between blinks, uS

    uint16_t strobe = 1000; // length of strobe pulse, 1 mS

    byte led = 13; // onboard led

    #include <Wire.h>

    #include <LCD.h>

    #include <LiquidCrystal_I2C.h>

    #include <Encoder.h>

    Encoder myEnc(14,15);

    LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7);

    void setup() {

    pinMode(led,OUTPUT); //make led pin OUTPUT



    lcd.begin(16, 2);



    lcd.print("RPM = ");


    lcd.print("Temp (F) = ");


    lcd.print("Bearing Life = ");


    long oldPosition = -999;

    void loop() {

    long newPosition =;

    if (newPosition != oldPosition) {

    oldPosition = newPosition;


    if(micros() - timerStart < strobe)

    digitalWrite(led,HIGH); //turn led on




    else digitalWrite(led,LOW); // turn led off

    if(micros() - timerStart >= timerEnd)

    timerStart += timerEnd; // restart timer


    I have built one of these tachometers and it appears to be working, however at a speed measurement of around 1000 rpm the readings fluctuate/jump between 3 or four different readings. This seems to be an error because the disc that I am measuring has high inertia and cannot change speed rapidly.

    Do you have any suggestions as to what may be causing this?

    Kind regards, and thanks.


    I was very interested in this project, but I have a problem downloading the "arduino1.6.12" program! I can not download from the Internet to a computer, which is on both my PC and my laptop. Could you send this progtam to my email so I can do this tachometer? If necessary, save it to a server where I can download this program. To be able to program the electronics bord for the tachometer. My Email

    Thanks for this great project, although there was a lot of time passed for this project to comment, but I think it is easier to use I2C connection instead of shift register to minimize connections (to I2C LCD) and components?

    I'm new in arduino, this my question, coz I'm confused

    for explanation rpm = 60*1000 / t * actualREV => rpm = 60*1000 / (millis() - time ) * REV/2

    OR rpm = 30*1000 / (millis() - time) * REV;

    what the meaning of actualREV and REV?

    and for line 57


    reading what?

    say thanks before