ESP8266 - IR Slotted Optical Speed Sensor
This tutorial instructs you how to use the ESP8266 NodeMCU with an IR slotted optical speed sensor, so the board can know each time something moves through the narrow gap of the sensor. With that one signal, the NodeMCU can count things or tell how fast a motor turns. In detail, we will learn:
- How the light gate inside the sensor (also sold as an opto interrupter or photo interrupter) gives a HIGH or LOW signal
- How to wire the sensor to the 3.3V pin and pin D5 (GPIO14) of the NodeMCU, and which pins to keep free
- How to write ESP8266 code that counts every object that blocks the slot
- How to write ESP8266 code that measures motor speed in RPM with an encoder disc and an interrupt function marked with IRAM_ATTR
We suggest:
- Utilizing a rotary encoder if a person turns a knob by hand and the code must know if it goes left or right.
- Utilizing an infrared obstacle sensor if the thing you want to find is in front of the sensor, a few centimeters away, and can not go through a small slot.

Hardware Preparation
Or you can buy the following kits:
| 1 | × | DIYables Sensor Kit (18 sensors/displays) |
Additionally, some of these links are for products from our own brand, DIYables .
Overview of IR Slotted Optical Speed Sensor
Picture a tiny door frame made of black plastic. An invisible beam goes across the frame. When anything solid walks through the frame, the beam is cut, and the sensor tells the ESP8266 right away. That is all this module does, but it does it fast and with a very clean signal. This is why makers use it as a speed sensor, a pulse counter and an end-stop.
Inside the U-Shaped Body

The module is built around three parts:
- An infrared LED in one arm. It sends out IR light all the time.
- An NPN phototransistor in the other arm. It sits 5.9 mm away and faces the LED.
- A Schmitt trigger on the small board. It turns the phototransistor signal into a sharp digital level.
The OUT pin follows the beam:
- Beam reaches the phototransistor (the slot is empty): OUT is HIGH.
- Beam is cut by a solid object (a card, a finger, a tooth of an encoder disc): OUT is LOW.
Thanks to the Schmitt trigger, the edges have no jitter. Your ESP8266 code can use the value as it is, with no debounce step.
Some tips before you test:
- Use something you can not see through. Clear plastic may let IR light pass, and the sensor may not notice it.
- A few modules sold by other makers have the reverse logic (LOW when empty). In that case, swap LOW and HIGH in the counting code. The RPM code counts one edge for each slot, so it still gives the right speed.
Specifications
| IR Slotted Optical Speed Sensor | |
|---|---|
| Sensor type | Slot-type photoelectric (IR LED + NPN phototransistor) |
| Slot width | 5.9 mm |
| Operating voltage | 3.3V to 5V DC |
| Output | Digital (TTL), LOW when blocked, HIGH when clear |
| Signal conditioning | Built-in Schmitt trigger |
| Mounting | M3 screw holes at both ends |
| Pins | 3 (VCC, GND, OUT) |
The two M3 holes let you screw the module to a robot chassis or a motor bracket, so it stays in the same place while a disc spins next to it. Typical jobs for it: an RPM meter for a small motor, wheel speed feedback for a robot car, a counter for items on a moving belt, a distance meter that counts wheel turns, and a limit or position switch with no contact.
The IR Slotted Optical Speed Sensor Pinout

- VCC pin: must be connected to the 3V3 pin of the NodeMCU (not to 5V or VIN)
- GND pin: must be connected to a G (GND) pin of the NodeMCU (0V)
- OUT pin: the digital output; it must be connected to an input pin of the NodeMCU (D5 in this tutorial)
※ NOTE THAT:
The arrangement of pins on a module may differ from one manufacturer to another, and two batches of the same product can also be different. It is essential to always refer to the labels printed on the module when using it. Take a close look!
Why 3.3V and not 5V? The OUT pin goes as high as the voltage on VCC. The ESP8266 is a 3.3V chip. If the sensor gets 5V, OUT would also give 5V to a NodeMCU pin. When VCC comes from the 3V3 pin, OUT stays between 0V and 3.3V, which is safe for the board.
Encoder Disc: From Pulses to RPM
An encoder disc is a flat wheel with a ring of holes near its edge. You press it on the motor shaft or the wheel, and you place the sensor so the edge of the disc runs through the slot. As the disc turns, holes and teeth pass the beam one after another. Each tooth makes OUT go LOW once.
Most TT gear motor kits come with a 20-slot disc. Here is how the ESP8266 turns pulses into speed:
- Count the pulses for 1 second.
- Multiply by 60 to get pulses per minute.
- Divide by 20 (pulses in one turn) to get turns per minute, which is RPM.
Wiring Diagram
Use three jumper wires: sensor VCC to the NodeMCU 3V3 pin, sensor GND to a G pin, and sensor OUT to D5, as you can see in the image below.

This image is created using Fritzing. Click to enlarge image
See more in ESP8266's pinout and how to supply power to the ESP8266 and other components.
D5 Label or GPIO14?
The silk screen of the NodeMCU shows labels like D1, D2 and D5. The ESP8266 chip itself uses GPIO numbers, and the two do not match. D5 on the board is GPIO14 in the chip. In the code we write D5, and the ESP8266 core for Arduino IDE changes it to GPIO14 for us. If you write just 5, the code uses GPIO5, which is the D1 pin. This is a common mistake, so always keep the "D" in the code.
You can move the OUT wire to another pin if D5 is busy. Use this list to choose:
| NodeMCU label | GPIO | Good for this sensor? |
|---|---|---|
| D1 | GPIO5 | Yes |
| D2 | GPIO4 | Yes |
| D5 | GPIO14 | Yes (used in this tutorial) |
| D6 | GPIO12 | Yes |
| D7 | GPIO13 | Yes |
| D3 | GPIO0 | No, boot pin |
| D4 | GPIO2 | No, boot pin |
| D8 | GPIO15 | No, boot pin |
| D0 | GPIO16 | No interrupt support, so not for the RPM code |
The chip checks D3, D4 and D8 when it starts up. The sensor drives OUT all the time. If the slot is clear, OUT is HIGH, and on D8 that HIGH can stop the board from booting. If a disc tooth happens to sit in the slot, OUT is LOW, and on D3 or D4 that LOW can stop a normal start too. So keep the sensor away from these three pins.
How To Program For IR Slotted Optical Speed Sensor
There is no library to install. Everything comes from the ESP8266 core that you added to Arduino IDE.
Steps for the counting code
- The first step is to give a name to the pin that is wired to OUT. Note the "D" label:
- Next, the pin is set as an input in setup(). The sensor pushes the line HIGH or LOW by itself, so no pull-up is needed:
- Then, the code reads the pin in every pass of loop(). LOW tells the ESP8266 that the beam is cut:
- Finally, the new reading is compared with the one before it (lastState). The count goes up only when the value has changed to LOW. So, a card that stays in the slot for five seconds still counts as one.
Steps for the RPM code
- The first step is to make the pulse counter volatile. The interrupt function changes it in the background, and the volatile keyword tells the compiler to always read the real value:
- Next, the interrupt function is written. On the ESP8266, this function must have the IRAM_ATTR word in front of it. This puts the function in the fast internal RAM of the chip. Without it, the ESP8266 can crash and restart when a pulse comes in:
- Then, the function is linked to D5 in setup(). FALLING means it runs when OUT drops from HIGH to LOW, which is the moment a tooth enters the slot:
- After that, once every second (checked with millis()), the code copies the counter and sets it back to zero. The ESP8266 is a 32-bit chip, so it reads the 32-bit counter in one step. That is why this version does not need the noInterrupts() and interrupts() pair that 8-bit boards use:
- Finally, the speed is worked out with the formula from the encoder disc section:
ESP8266 NodeMCU Code for Counting Objects in the Slot
This code keeps reading D5 in loop(). It adds one to the counter each time the slot becomes blocked, and it prints a line only when the state changes. So the Serial Monitor does not fill up with the same message.
Detailed Instructions
- If this is the first time you use ESP8266, see how to setup environment for ESP8266 on Arduino IDE.
- Wire the components as shown in the diagram.
- Connect the ESP8266 board to your computer using a USB cable.
- Open Arduino IDE on your computer.
- Select the NodeMCU 1.0 (ESP-12E Module) board and the COM port of your NodeMCU.
- Copy the code above and paste it into Arduino IDE.
- Click the Upload button and wait until the upload is done.
- Open the Serial Monitor and set it to 9600 baud.
- Push a strip of card down into the slot, then pull it out. Repeat three times.
- See the result on the Serial Monitor.
Does the first line say "blocked" while nothing is in the slot? Then your module has the reverse logic. Change LOW to HIGH in the if statement and upload again.
ESP8266 NodeMCU Code for Measuring Motor Speed (RPM)
A spinning 20-slot disc can make hundreds of pulses every second. If loop() only reads the pin, some pulses will slip by while the ESP8266 is busy, for example while it prints text. An interrupt solves this. The chip stops for a moment, runs countPulse(), and goes back to its work, so each tooth is counted.
Once per second, the code prints the pulse count and the speed. The SLOTS_PER_TURN constant is 20. If your disc has a different number of holes, count them and change this number.
Detailed Instructions
- Leave the wiring as it is. OUT stays on D5. If you move it, pick D1, D2, D6 or D7 and change SENSOR_PIN in the code.
- Press the encoder disc onto the motor shaft or wheel. Set the height of the sensor so the teeth go through the middle of the slot and never touch the plastic.
- Paste the RPM code into Arduino IDE and click Upload.
- Open the Serial Monitor at 9600 baud.
- Turn the disc with your finger, or run the motor. To run the motor from the NodeMCU, see the ESP8266 - DC Motor tutorial.
- Watch the speed on the Serial Monitor.
The motor in this test spins at close to 300 RPM. Check the line with 101 pulses: 101 x 60 / 20 = 303 RPM. The first line was printed before the motor started, and the last one after it stopped.
※ NOTE THAT:
If the NodeMCU keeps restarting as soon as the disc turns, look at the interrupt function first. On the ESP8266, it must be marked with IRAM_ATTR. Also keep it very short: no Serial.print(), no delay(), just the counter.
Additional Knowledge
Frequently Asked Questions
Can I power the sensor from the VIN pin to make it stronger?
No. VIN carries 5V from the USB cable, and OUT would then send 5V into a 3.3V GPIO. The module works well from 3.3V, so the 3V3 pin is the right choice.
Why do the RPM values move in steps of 3?
The code counts whole pulses for one second. With 20 slots, one extra pulse adds 60 / 20 = 3 RPM. A disc with more slots gives smaller steps. A disc with fewer slots gives bigger steps. Whatever disc you use, make SLOTS_PER_TURN match it.
Why FALLING and not CHANGE?
FALLING runs the function once per tooth, at the moment the tooth enters the slot. CHANGE would also run it when the tooth leaves, so you would get two counts per tooth and twice the real RPM.
Can one NodeMCU read two speed sensors?
Yes. Put the second sensor on another safe pin, such as D6, and give it its own volatile counter and its own IRAM_ATTR function. This is handy for a robot car with two driven wheels.
Slot Sensor, Obstacle Sensor or Rotary Encoder?
The slot sensor wants the target to go through its 5.9 mm gap, always at the same spot. It gives one clean pulse per pass and is the best tool for RPM and counting.
The obstacle sensor looks at open space. It catches light that bounces back from a wall, a hand or a box in front of it, so the target does not need to fit in a slot.
The rotary encoder is a knob. It has two signals (CLK and DT) plus a push button, so it can also tell the turning direction.
It is evident that the slotted optical sensor is the most precise of the three for timing a spinning part, because the beam is cut at the same small place on every turn. It can not tell the direction, though. If you need that, the rotary encoder is the one to pick.
From RPM to Wheel Speed
When the disc sits on a robot wheel, one more step turns RPM into how fast the robot moves:
- Wheel circumference = π x wheel diameter
- Distance in one second = (RPM / 60) x wheel circumference
Example: a wheel with a 60 mm diameter has a circumference of about 188.5 mm. At 300 RPM, it makes 5 turns per second, so the robot rolls about 942 mm, or 0.94 meters, every second.