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:

We suggest:

ESP8266 NodeMCU NodeMCU with IR slotted optical speed sensor

Hardware Preparation

1×ESP8266 NodeMCU ESP-12F
1×Alternatively, ESP8266 D1 Mini NodeMCU ESP-12F
1×Alternatively, ESP8266 NodeMCU ESP-12E (Uno-form)
1×Micro USB Cable
1×Alternatively, ESP8266 NodeMCU ESP-12F (SMD-form)
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×IR Slotted Optical Speed Sensor
1×Jumper Wires
1×Breadboard
1×Optionally, Breadboard
1×Recommended: Screw Terminal Expansion Board for ESP8266

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
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

How IR Slotted Optical Speed Sensor Works

The module is built around three parts:

  1. An infrared LED in one arm. It sends out IR light all the time.
  2. An NPN phototransistor in the other arm. It sits 5.9 mm away and faces the LED.
  3. 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

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:

  1. Count the pulses for 1 second.
  2. Multiply by 60 to get pulses per minute.
  3. 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.

The wiring diagram between ESP8266 NodeMCU and NodeMCU IR Slotted Optical Speed Sensor

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:
const int SENSOR_PIN = D5;
  • 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:
pinMode(SENSOR_PIN, INPUT);
  • Then, the code reads the pin in every pass of loop(). LOW tells the ESP8266 that the beam is cut:
int state = digitalRead(SENSOR_PIN);
  • 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:
volatile unsigned long pulseCount = 0;
  • 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:
void IRAM_ATTR countPulse() { pulseCount++; }
  • 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:
attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), countPulse, FALLING);
  • 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:
unsigned long pulses = pulseCount; pulseCount = 0;
  • Finally, the speed is worked out with the formula from the encoder disc section:
float rpm = pulses * 60.0 / SLOTS_PER_TURN;

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.

/* * This ESP8266 NodeMCU code was developed by newbiely.com * * This ESP8266 NodeMCU code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp8266/esp8266-ir-slotted-optical-speed-sensor */ const int SENSOR_PIN = D5; // ESP8266 pin D5 connected to the OUT pin of the speed sensor int lastState; // the previous state of the sensor int objectCount = 0; // how many times the slot was blocked void setup() { Serial.begin(9600); pinMode(SENSOR_PIN, INPUT); lastState = digitalRead(SENSOR_PIN); } void loop() { int state = digitalRead(SENSOR_PIN); if (state != lastState) { // the state has changed if (state == LOW) { // LOW = something is in the slot objectCount++; Serial.print("The slot is blocked - count: "); Serial.println(objectCount); } else { // HIGH = the slot is clear Serial.println("The slot is clear"); } lastState = state; } }

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.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Nodemcu 1.0 (ESP-12E Module)
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Nodemcu 1.0 (ESP-12E Module)' on 'COM15')
New Line
9600 baud
The slot is blocked - count: 1 The slot is clear The slot is blocked - count: 2 The slot is clear The slot is blocked - count: 3 The slot is clear
Ln 11, Col 1
Nodemcu 1.0 (ESP-12E Module) on COM15
2

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.

/* * This ESP8266 NodeMCU code was developed by newbiely.com * * This ESP8266 NodeMCU code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp8266/esp8266-ir-slotted-optical-speed-sensor */ const int SENSOR_PIN = D5; // ESP8266 pin D5 connected to the OUT pin of the speed sensor const int SLOTS_PER_TURN = 20; // number of slots (holes) in the encoder disc volatile unsigned long pulseCount = 0; // changed inside the interrupt unsigned long lastTime = 0; // runs every time a tooth of the disc blocks the slot void IRAM_ATTR countPulse() { pulseCount++; } void setup() { Serial.begin(9600); pinMode(SENSOR_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), countPulse, FALLING); } void loop() { if (millis() - lastTime >= 1000) { // every 1 second lastTime = millis(); unsigned long pulses = pulseCount; // read and reset the counter pulseCount = 0; float rpm = pulses * 60.0 / SLOTS_PER_TURN; // pulses per second -> turns per minute Serial.print("Pulses: "); Serial.print(pulses); Serial.print(" | Speed: "); Serial.print(rpm); Serial.println(" RPM"); } }

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.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Nodemcu 1.0 (ESP-12E Module)
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Nodemcu 1.0 (ESP-12E Module)' on 'COM15')
New Line
9600 baud
Pulses: 0 | Speed: 0.00 RPM Pulses: 98 | Speed: 294.00 RPM Pulses: 101 | Speed: 303.00 RPM Pulses: 100 | Speed: 300.00 RPM Pulses: 99 | Speed: 297.00 RPM Pulses: 0 | Speed: 0.00 RPM
Ln 11, Col 1
Nodemcu 1.0 (ESP-12E Module) on COM15
2

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.

Video Tutorial

Function References

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!