Add an Arduino-based Optical Tachometer to a CNC Router




Introduction: Add an Arduino-based Optical Tachometer to a CNC Router

About: 3D printing, CNC and Raspberry Pi/Arduino hobbyist

Build an optical RPM indicator for your CNC router with an Arduino Nano, an IR LED/IR Photodiode sensor and an OLED display for less than $30. I was inspired by eletro18's Measure RPM - Optical Tachometer Instructable and wanted to add a tachometer to my CNC router. I simplified the sensor circuit, designed a custom 3D-printed bracket for my Sienci CNC router. Then I wrote an Arduino sketch to display both a digital and analog dial on an OLED display

A few simple parts and a couple of hours of your time, and you can add a digital and analog RPM display to your CNC router.

Here's the parts list available for 2-day shipping. You can probably source the parts for less if you're willing to wait longer.

Parts List:

$6.99 Arduino Nano

$5.99 IR LED/IR Photodiode (5 pairs)

$7.99 OLED display 0.96 yellow/blue I2C

$4.99 Jumper wires

$1.00 30 inches (75 cm) 3-conductor-stranded wire. Can be purchased from your local home supply store (Home Depot, Lowes) in the buy-by-the-foot section

$0.05 220 ohm resistor ($6.99 if you want 750 assorted resistors)

$0.50 Heat shrink tubing ($5.99 if you want a complete assortment)

3D Printed brackets

Arduino IDE (free)

Note: I initially added a .01μF capacitor after I secured all the wires and notices some erratic RPM values when the CNC was moving. The capacitor worked fine for lower RPMs < 20K but it smoothed out the signal too much for anything higher. I tracked the noise down to powering the Nano and display directly from the CNC shield. A separate supply works for all RPM. I left the steps in for now, but you should use a separate USB power source.

Step 1: Print the 3D Bracket

Print the 3D bracket to hold the IR LED and IR Photodiodes. The 3D files are here and on Thingiverse.

For the Sienci Mill, the angle mount is used to mount the sensor to the aluminum angle bars, but the flat mount may be better for your project.

Step 2: Optionally 3D Print the OLED Display Holder and Electronic Enclosure

I choose to attach the OLED to an angled display holder that I screwed on the top of a Sienci Electronics Enclosure.

Here are the links to the 3D printed parts that I used.

Sienci Electronics Enclosure 3D Part

0.96" OLED Display Mounting Bracket

The enclosure was a nice place to mount the OLED display bracket and it holds the Arduino Nano nicely, plus it fits on the back of the Sienci Mill. I drilled a couple of holes on the top of the enclosure to attach the OLED bracket.

I also drilled a couple of holes in the bottom to run a small zip tie through to firmly attach the wire harness

Step 3: Build the IR Sensor Wire Assembly

The 3-conductor wire will be used to wire up the sensor. One wire will be a common ground for both the IR LED and the IR Photodiode, with each of the other two going to their respective component.

Step 4: Add a Current Limiting Resistor for the IR LED

The IR LED requires a current limiting resistor. The easiest way, is to incorporate the resistor into the wire assembly.

Bend the tips of each into a U-shape and interlock them. Crimp with a pair of pliers and then solder them together.

Step 5: Splice Jumper Wires

You can splice jumper wires to connect them on Arduino header pins.

Cut a piece of heat shrink tubing and slide over the wire before connecting them.

Slide the heat shrink tubing back over the connection (or entire Resistor) and shrink the tubing by using a heat gun or running a flame quickly over the tube until it shrinks. If using a flame, keep it moving quickly or it can start to melt.

Step 6: Determine the IR LED and Photodiode Leads

Both the IR LED and the IR Photodiode look similar, each having a long (anode or positive) lead and a short (cathode or negative) lead.

Step 7: Insert Diodes Into Holder

Take the IR LED (clear diode) and insert it into one of the LED holder holes. Rotate the LED so that the long lead is on the outside. In the photo, you can see the clear LED in the top hole with its long lead at the very top.

Take the IR photodiode (dark diode) and insert it in the other hole. Rotate the photodiode so that its long lead is in the center.

As shown in the photo, the short lead of the LED and the long lead of the photodiode will both be in the center. These two leads will be spliced to a common wire back to the arduino. (See the tech notes at the end if you want more details)

Take a small piece of 1.75 filament and insert it behind the diodes. This will lock the diodes in place and prevent them from rotating or coming out.

I went through several iterations of designs before settling on this one. Having the diodes stick out a bit greatly improved the tolerance when aligning it with the collet nut.

Step 8: Fuse the Locking Filament to the Holder

You'll want to trim the locking piece of filament to just a little longer than the width of the holder.

Heat a nail for a few seconds in vise or holding it with pliers.

Step 9: Press the Filament Ends Against the Heated Nail Head

Keep your finger on the opposite end of the filament and press to melt and fuse the locking pin in the holder.

Step 10: Finished Diode Holder

Flush and neat

Step 11: Attach the Wiring Harness to the Diodes

Trim the wire to length for your application. For the Sienci Mill, you'll need about 30 inches (~75cm) in total (wire + jumpers) and have slack for the router to move.

Bend the wire and lead tips into a U-shape to interlock them and make soldering easier.

Take some thin heat-shrink tubing and trim two short pieces and two slightly longer pieces. Slip the shorter pieces over the outside diode leads. Slip the longer pieces over the two center leads.

Having two different lengths offsets the splice joints and offsets the thicker joints from each other so that the diameter of the wiring is reduced. It also prevents any shorts between the different wire splices

Cut three pieces of slightly larger diameter heat-shrink tubing and place them over each of the three wires in the wiring harness.

It's important to make sure that there's a little gap between the ends of the heat shrink tubing on the wires and the splice point. The wires will get hot, and if the heat shrink tubing is too close, they'll begin to shrink at the end, potentially making them too small to slide over the joint.

Step 12: Ensure the Wire With the Resistor Is Attached to the Long Lead of the IR LED

The current limiting resistor (220 ohm) built into the wiring harness, needs to be connected to the long (anode) lead of the clear IR LED. The wire connecting the two common leads will be connected to ground, so you may want to use a black or bare wire for that connection.

Solder the connections to make them permanent.

Step 13: Shrink the Heat-shrink Tubing

After the joints have been soldered, use a match or lighter to shrink the tubing on the diode leads first. First move the heat-shrink tubing on the wires as far away from the heat as possible.

Keep the flame moving quickly as it shrinks and rotate to get all sides equally. Don't linger or the tubing will melt instead of shrink.

After the diode leads have been shrunk, slide the slightly larger heat-shrink tubing from the wires, over the joints and repeat the shrinking.

Step 14: Prepare the Mounting Block

Depending on your application, choose the mounting block that suits your application. For the Since Mill, select the angle mounting block.

Take a M2 nut and a M2 screw. Screw the nut just barely on to the end of the screw.

Turn the mounting block over and test fit the M2 nut into the hole.

Remove and heat the nut slightly with a match or flame and then quickly insert it into the back of the mounting block.

Unscrew the screw, leaving the nut embedded in the plastic mounting block. For some added strength, apply a drop of super glue to the edge of the nut to securely attach the nut to the block.

Step 15: Ensure the M2 Screw Is the Proper Length

Make sure that the screw is not too long or the sensor will not tighten against the mounting block. For the angle mounting block, ensure that the M2 screw is 9mm or a bit shorter.

Step 16: Attach the Mounting Block to the CNC Router

For the Sienci Mill, attach the angle mounting block to the bottom of the inside of the Z Rail with a couple of drops of super glue.

Step 17: Attach the Sensor to the Mounting Block

Place the adjustable arm into the mounting block

Insert the M2 screw with a washer through the slot in the adjustable mounting arm and screw it into the nut.

Slide the adjustable arm until the LED and Photodiodes are even with the router collet nut

Tighten the screw

Step 18: Add Reflective Tape to One Side of Collet Nut

Use a small strip of aluminum tape (used for furnace ducts) and attach it to one facet of the collet nut. This reflective tape will allow the IR optical sensor to pick up a single revolution of the spindle.

Step 19: Ensure the Reflective Tape Does Not Go Over the Edge to Adjacent Facets

The tape has to be on one side of the collet nut only. The tape is thin and light enough that it does not interfere with the wrench to change end mills or affect spindle balance.

Step 20: Run the Sensor Wire Along the Inside of the Z Rail.

Using strips of the aluminum duct tape, attach the wire to the inside of the Z Rail. It's best to run the tape near the edge of the angle rail to clear the lead screw nut assembly.

Step 21: Attach the Sensor to the Arduino Nano

Connect the wires to the Arduino as follows:

  • IR LED (with integrated resistor) -> Pin D3
  • IR Photodiode -> Pin D2
  • Common wire -> Pin GND

Step 22: Attach Jumper Wires to the OLED Display

Pull off a 4-wire set of jumper cables

Plug the wires into the 4 pins for the I2C interface:

  • VCC
  • GND
  • SCL
  • SDA

Step 23: Attach the OLED Display to the Arduino

Attach the jumper wires to the following pins. Note: These wire do not all attach to adjacent pins, nor in the same order.

  • VCC -> Pin 5V
  • GND -> Pin GND
  • SCL -> Pin A5
  • SDA -> Pin A4

Step 24: Attach the OLED Display to Its Holder

Using the brackets you printed earlier, attach the OLED display to its holder

Then attach the display to the CNC frame.

Step 25: Prepare the Arduino IDE for Loading the Arduino Sketch

A program for an Arduino is called a sketch. The Integrated Development Environment (IDE) for Arduinos is free and must be used to load the program to detect the sensor and display the RPM.

If you don't already have it, here's a link to download the Arduino IDE. Choose the downloadable version 1.8.5 or above.

Step 26: Add the Required OLED Libraries

To run the OLED display, you'll need a couple of additional libraries, the Adafruit_SSD1306 library and the Adafruit-GFX-Library. Both libraries are free and available through the links provided. Follow the Adafruit tutorial on how to install the libraries for your computer.

Once the libraries are installed, they're available for any Arduino sketch you create.

The Wire.h and Math.h libraries are standard and are automatically included in your IDE installation.

Step 27: Connect the Arduino to Your Computer

Using a standard USB cable, connect the Arduino Nano to your computer with the Arduino IDE.

  1. Launch the IDE
  2. From the Tools menu, select Board | Arduino Nano
  3. From the Tools menu, select Port |

Now you are ready to load the sketch, compile it and upload it to the Nano

Step 28: Download the Arduino Sketch

The Arduino Sketch code is attached and is also available on my GitHub page where any future improvements will be posted.

Download the OpticalTachometerOledDisplay.ino file and place it into a work directory with the same name (minus the .ino).

From the Arduino IDE, choose File | Open...

Navigate to your work directory

Open the OpticalTachometerOledDisplay.ino.ino file.

Step 29: Compile the Sketch

Click the 'Check' button or choose Sketch | Verify/Compile from the menu to compile the sketch.

You should see the compile area in the bottom, with a status bar. In a few seconds the message "Done Compiling" and some statistic on how much memory the sketch takes up will be displayed. Don't worry about the "Low Memory Available" message, it does not affect anything. Most of the memory is used by the GFX library needed to draw the fonts on the OLED display and not the actual sketch itself.

If you see some errors, they are most likely the result of missing libraries, or a configuration issues. Double check that the libraries have been copied into the correct directory for the IDE.

If that doesn't fix the problem check the instructions on how to install a library and try again.

Step 30: Upload to the Nano

Press the 'Arrow' button or choose Sketch | Upload from the menu to compile and upload the sketch.

You'll see the same 'Compiling..' message, followed by an 'Uploading..' message and finally a 'Done Uploading' message. The Arduino starts running the program as soon as the Upload is complete or as soon as power is applied afterwards.

At this point, the OLED display should come alive with a RPM: 0 display with the dial at zero.

If you've put the router back together, you can turn on the switch and see the display read out the RPM as you adjust the speed.


Step 31: Use a Dedicated Power Source

NOTE: This was the source of the the signal noise that caused the erratic RPM displays. I'm investigating putting some filter caps on the power jumpers, but for now you'll need to power it via a separate USB cable.


You can run the display connected to your computer with the USB cable, but eventually you'll want a dedicated power source.

You have a couple of options, you can get a standard USB wall charger and run the Arduino from it.

Or you can run the Arduino directly from your CNC router electronics. The Arduino/OLED display only draws 0.04 amps, so it's not going to overload your existing electronics.

If you have Arduino/CNC Router Shield electronics (like the Sienci Mill), then you can use a couple of unused pins to tap into the needed 5 volts of power.

On the upper left side of the CNC router shield, you can see that there are a couple of unused pins labeled 5V/GND. Attach a pair of jumper cables to these two pins.

Step 32: Connect the Arduino to the Power Jumpers

This one's easy, but not as nicely labeled.

On the Arduino Nano, there are a set of 6 pins at the end of the board. They aren't labeled, but I've included the pin out diagram and you can see that the two outside pins that are closest to the indicator LEDs are labeled GND and 5V on the diagram.

Connect the jumper from the 5V pin on the CNC shield to the pin nearest to the one labeled VIN (don't connect it to VIN, but to the inside corner pin of the 6 pin group). VIN is for powering the Nano with 7V-12V power.

Connect the jumper from the GND pin on the CNC shield to the pin nearest the TX1 pin.

Now when you turn on the CNC router electronics, the OLED RPM display will come on as well.

Step 33: Technical Notes on the Circuit

The sensor circuit uses an IR LED/IR Photodiode pair.

The IR LED works like any regular LED. The positive lead (the longer or anode) is connected to positive voltage. On an Arduino Nano, it's an output pin set to HIGH. The negative lead (shorter or cathode) is connected to ground to complete the circuit. Since LEDs are sensitive to too much current, a small resistor is placed in series with the LED to limit the amount of current. This resistor can be anywhere in the circuit, but it makes the most sense to place it on the positive side of the circuit, since the negative lead shares a connection to ground with the photodiode.

The IR Photodiode behaves like any other diode (including Light Emitting Diodes LEDs) in that they only conduct electricity in one direction, blocking electricity in the opposite direction. That's why it's important to get the polarity correct for LEDs to work.

The important difference with Photodiodes, is when they detect light, the photodiodes will allow electricity to flow either way. This property is used to make a light detector (in this case Infrared light or IR). The IR Photodiode is connected in an opposite polarity (called reverse bias) with the positive 5V on the Arduino pin connected to the negative lead of the photodiode and the positive lead is connected through a common wire along with the IR LED to ground.

With no IR light, the IR photodiode blocks electricity, allowing the Arduino pin with its internal pull-up resistor be at the HIGH state. When the IR photodiode detects IR light, it allows electricity to flow, grounding the pin and causing the HIGH value on the photodiode pin to drop down towards ground causing a FALLING edge that the Arduino can detect.

This change of state on the Arduino pin is used in the sketch to count the revolutions.

The strip of aluminum tape on the collet nut, reflects the IR light from the always-on IR LED back to the IR photodiode every time it rotates past the sensor.

Step 34: Technical Notes on the Arduino Sketch

The Arduino sketch drives the OLED display and simultaneous reacts to IR LED/IR Photodiode sensor.

The Sketch initializes the OLED display throughout he I2C (Inter-integrated Circuit) protocol. This protocol allows multiple displays/sensors to share a connection and can read or write to a specific connected device with a minimum of wires (4). This connection reduces the number of connections between the Arduino and the OLED display.

It then turns on the IR LED by setting that pin HIGH providing the 5V needed for the LED.

It attaches an interrupt function to a pin that is called when it detects a change that pin's state. In this case the incrementRevolution() function is called whenever a FALLING edge is detected on Pin 2.

An interrupt function does just what it implies, it interrupts anything that is currently being done, executes the function and then resumes the action exactly where it was interrupted. Interrupt functions should be as short as possible, in this case it just adds one to a counter variable. The little Arduino Nano runs at 16Mhz - 16 million cycles per second - plenty fast enough to handle the interrupt of 30,000 RPM, which is only 500 revolutions per second.

The Loop() function is the primary action function for any Arduino sketch. It is continuously called, over and over again as long as the Arduino has power. It gets the current time, checks to see if a specified interval has elapsed (1/4 second = 250 milliseconds). If so, it calls the updateDisplay() function to display the new RPM value.

The loop function will also dim the display after 1 minute and turn off the display after 2 minutes - fully configurable in the code.

The updateDisplay() functions calls the calculateRpm() function. That function takes the count of revolutions the interrupt function has been steadily incrementing and calculates the RPM by determining the rate of revolutions per time interval and extrapolating that to the number of Revolutions per Minute.

It displays the numerical value and uses some High School trig to draw an analog dial and the indicator arm to reflect the same values.

The constants at the top of the sketch can be modified, if you would like a RPM dial with different major and minor values.

The update interval and average interval can also be modified.

Arduino Contest 2017

Participated in the
Arduino Contest 2017

4 People Made This Project!


  • 3D Printed Student Design Challenge

    3D Printed Student Design Challenge
  • Laser Challenge

    Laser Challenge
  • Cookie Speed Challenge

    Cookie Speed Challenge



Question 3 months ago

Hi all, I'm having the same problem that stragenmitsuko had with his screen 2 years ago. I have installed all the latest display libraries and have added this line:-
#define SSD1306_128_64
but still no joy.
Can someone please help.


Answer 3 months ago

Hi all, I've sorted it. Didn't need to add the line but to define it within the SDD1406.h library 😁


Reply 6 days ago

Where were you able to to find the SDD1406.h library. I couldn't find it under the manage libraries.

Taaran Rai
Taaran Rai

Reply 2 months ago

can you please help me with it??


Reply 2 months ago

Hi Taaran,
What would you like help with?

Taaran Rai
Taaran Rai

Reply 2 months ago

Well thanks for your reply but it worked with a edit in adafruite library


Question 6 days ago

Has anyone else had an issue with it reading "0 RPM" and not registering the speed. I checked for continuity and I seem to have it wired up correctly. I checked for continutiy on the wires and they checked out. Any help would be appreciated?

Also is the display supposed to shut off after a few minutes?

These are the LEDS I bought:
Gikfun 5mm 940nm LEDs Infrared Emitter and IR Receiver Diode for Arduino (Pack of 20pcs) EK8443


2 years ago

My display looks odd .
The dial is oval and the RPM letters are partially yellow partially blue .
Almost as if i have a wrong display but I did double check that .

Help much appreciated .


Reply 1 year ago

I have the same problem, is it doable with this kind of display or did you order a new one?


Reply 1 year ago

The display is not the problem .
It's to long ago now I don't remember exactly , but in one of the libraries you have to set the resolution to 128*64 and then it all works as it should .
Its no more then comment / uncomment a certain line .

good luck


Reply 1 year ago

Make sure that you have the correct OLED library defined in the Arduino code.
Try swapping the two display types in the code

//One of the next two defines must be uncommented for the type of OLED display
//SSD1306 is typically the 0.96" OLED
#define OLED_TYPE_SSD1306
//SH1106 is typically a 1.3" OLED
//#define OLED_TYPE_SH1106


//One of the next two defines must be uncommented for the type of OLED display
//SSD1306 is typically the 0.96" OLED
//#define OLED_TYPE_SSD1306
//SH1106 is typically a 1.3" OLED
#define OLED_TYPE_SH1106

As stragenmitsuko pointed out, you might need to make a change in the Adafruit library header file. On a Mac, they are located in /Documents/Arduino/libraries, then the name of the file (Adafruit_SH1106.h or Adafruit_SSD1306.h).
Make sure the correct resolution is uncommented for the 128x64 resolution:
#define SH1106_128_64
// #define SH1106_128_32
// #define SH1106_96_16

Reply 6 days ago

I was able to update it in the libary but when I recomplied and sent it to the controller it didn't fix it. Additionally, if I defining it as the 1106 it says it missing the library.


Reply 1 year ago

Yes that's it , the adafruit library header file .
In my case it was the only modification needed to make it work .


1 year ago

hello, thanks for sharing! i have an arduino UNO left so i would like to use it instead of the nano, in this case the sketch and pin wiring remain the same or what should i modify?
and how long could be the cable from the sensor to the arduino???


Question 1 year ago on Introduction

This is GREAT - Thanks! How would you add a cumulative run time hour meter to this? Something that you would reset manually (like if you were going to track time for changing router brushes)


Question 1 year ago on Step 34

I'm interested in applying this to a router lift table in order to display RPM of a 3.25 hp router motor (see link for the application: Couple of questions before trying: Given the vibration environment and dust/debris of my dust collection port (I have a surrounding box for the router that enables dust collection from a 4" port), is there a nominal length limit (in inches) to connect the IR/Optical sensors to the Ardruino unit? I would like to physically separate the sensors from the display by probably up to 2 feet. With respect to dust/debris, would the sensors be skewed adversely due to the router chips/dust? TIA!


Answer 1 year ago

That's a good question. I'll see if I can test out the maximum distance it can pick up a good reading without any dust or chips. I would expect a large distance with dust and chips will reduce the accuracy of the photodiode to pick the reflections.


Reply 1 year ago

By physical separation, I was referring to the Arduino card. With respect the the photodiode, I would think that it would be no more that 2.5 inches away to reflect from the collet nut. Regarding dust and debris, it would be sucked in, as the router bit is above router, not below, as you show for your CNC device. TIA for investigating!!!!!!!!!


Reply 1 year ago

That's no problem at all. I've set up a display for my Bandsaw and I have a 2 foot cable that connects the sensor to the Arduino.