ESP32 S3 UNO - IR Slotted Optical Speed Sensor

This ESP32 S3 Uno IR slotted optical speed sensor tutorial shows you how to wire the U-shaped sensor to the ESP32 S3 Uno form board and read it with Arduino code. The module is also called a photo interrupter or opto interrupter. You will count objects that pass through the slot, and then turn it into a simple tachometer that shows motor speed in RPM.

In this tutorial, you will:

  1. Learn how a photo interrupter detects an object between its two arms
  2. Wire the IR slotted optical speed sensor to the ESP32 S3 Uno
  3. Count objects in the slot with digitalRead()
  4. Measure motor speed (RPM) with an encoder disc and an interrupt
ESP32 S3 Uno IR slotted optical speed sensor

Hardware Preparation

1×ESP32 S3 Uno-form Board
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×Optionally, Breadboard

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

Before you wire anything, it helps to know what is inside this little U-shaped block. Once you see how simple it is, the code will make sense right away.

The module has two arms with a 5.9 mm gap between them. One arm holds an infrared LED, and the other holds an NPN phototransistor. They look at each other across the gap. So the sensor answers just one question for the ESP32 S3 Uno: is the gap empty, or is something in it?

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)

You will find this sensor in many projects. It works as a tachometer when you add an encoder disc to a motor. It can count objects or pulses, and it can measure distance by counting wheel turns. Robots use it for wheel speed feedback, and machines use it to find a limit or home position.

Pinout

The module has only three pins, so wiring is quick.

IR slotted optical speed sensor pinout

VCC pin

Connect it to the 3.3V pin of the ESP32 S3 Uno. The module works from 3.3V to 5V, but 3.3V keeps the output safe for the board.

GND pin

Connect it to GND (0V).

OUT pin

This is the digital output. Connect it to an input pin of the ESP32 S3 Uno.

The pin order can be different from one batch to the next. Always check the labels printed on your module before you connect it.

How It Works

The idea is simple: light either reaches the receiver, or it does not.

How the IR slotted optical speed sensor works

The IR LED shines across the slot all the time. When the slot is empty, the light hits the phototransistor and the OUT pin is HIGH. When something opaque is in the slot (a card, a finger or a tooth of an encoder disc), the light is blocked and the OUT pin goes LOW.

A Schmitt trigger on the module turns the raw light level into a clean 0 or 1. The signal does not bounce, so you do not need software debounce.

Two things to watch for

Clear plastic or glass can let IR light pass, so the sensor may not see it. Use opaque objects. Also, some modules from other makers work the other way round. If your sensor says "blocked" when the slot is empty, swap LOW and HIGH in the first sketch. The RPM sketch counts one edge per slot, so it works with both types.

How To Program For IR Slotted Optical Speed Sensor

There are two ways to read this sensor. Polling is easy and good for slow objects. An interrupt is the right tool when a disc spins fast.

Check if the slot is blocked (polling)

  1. Set the pin connected to OUT as an input:
pinMode(SENSOR_PIN, INPUT);
  1. Read the pin. LOW means blocked, HIGH means clear:
int state = digitalRead(SENSOR_PIN);
  1. Compare it with the last reading, so you only react when the state changes:
if (state != lastState) { // the slot just became blocked or clear }

Count fast pulses (interrupt)

  1. Create a counter. Make it volatile, because the interrupt changes it:
volatile unsigned long pulseCount = 0;
  1. Write a short function that adds one to the counter. On the ESP32 S3 Uno, add IRAM_ATTR so the function sits in fast RAM:
void IRAM_ATTR countPulse() { pulseCount++; }
  1. Call that function each time the signal falls from HIGH to LOW (a tooth enters the slot):
attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), countPulse, FALLING);
  1. Once per second, copy and reset the counter with interrupts paused, then convert pulses to RPM:
noInterrupts(); unsigned long pulses = pulseCount; pulseCount = 0; interrupts(); float rpm = pulses * 60.0 / SLOTS_PER_TURN;

Where does the formula come from? A 20-slot disc makes 20 pulses in one full turn. Multiply the pulses in 1 second by 60 to get pulses per minute. Then divide by 20 to get turns per minute, which is RPM.

ESP32 S3 Uno Pinout

The image below shows the pinout of the ESP32 S3 Uno form board. Use it to find the D2 header pin and its GPIO number before you wire the sensor.

ESP32 S3 Uno pinout diagram

Wiring Diagram

You need just three wires. Power the sensor from 3.3V, connect GND to GND, and connect OUT to D2.

The wiring diagram between ESP32 S3 Uno IR slotted optical speed sensor

This image is created using Fritzing. Click to enlarge image

Speed Sensor Pin ESP32 S3 Uno Pin
VCC 3.3V
GND GND
OUT D2 (GPIO18)

Any ESP32 S3 Uno GPIO can run an interrupt, and D2 is a safe, normal pin. The RPM sketch below uses that interrupt.

WARNING

ESP32 S3 Uno pins are NOT 5V tolerant. If you power the sensor from 5V, its OUT pin also gives 5V, which can damage GPIO18. Power the module from 3.3V (it works fine there), or add a voltage divider or level shifter on the OUT line if you must use 5V.

ESP32 S3 Uno Code - Detect Object in the Slot

This sketch reads the OUT pin over and over. It prints a line only when the state changes, and it adds one to a counter every time the slot gets blocked.

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-ir-slotted-optical-speed-sensor */ const int SENSOR_PIN = 18; // ESP32 S3 Uno pin (D2 = GPIO18) 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

  1. New to the ESP32 S3 Uno? Follow ESP32 S3 Uno - Getting Started first.
  2. Wire it up as shown in the diagram.
  3. Connect the board to your computer with a USB Type-C cable.
  4. Open Arduino IDE, choose the ESP32S3 Dev Module board and the correct COM port.
  5. Copy the code above and paste it into the Arduino IDE.
  6. Upload the sketch with the Upload button.
  7. Open the Serial Monitor and set it to 9600 baud.
  8. Test it by sliding a piece of card (or your finger) into the slot and pulling it out a few times.
  9. Check the output on the Serial Monitor:
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev 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
ESP32S3 Dev Module on COM15
2
  1. Tip: If nothing prints, look at the small LED on the module (if it has one). It should change when you block the slot. If it does, check the OUT wire to D2.

Every pass gives two lines: one when the card goes in, and one when it comes out. While nothing moves, nothing is printed.

ESP32 S3 Uno Code - Measure Motor Speed (RPM)

Polling inside loop() works for slow objects, but a spinning disc is much faster and loop() could miss teeth. This sketch counts pulses with an interrupt on the FALLING edge, so each tooth gives exactly one count. Every second it prints the pulse count and the speed in RPM.

SLOTS_PER_TURN is set to 20, which matches the common encoder disc sold with TT gear motors. If your disc has a different number of slots, change this value.

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-ir-slotted-optical-speed-sensor */ const int SENSOR_PIN = 18; // ESP32 S3 Uno pin (D2 = GPIO18) 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() { // IRAM_ATTR keeps the interrupt function in fast RAM on the ESP32 S3 Uno 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(); noInterrupts(); // read and reset the counter safely unsigned long pulses = pulseCount; pulseCount = 0; interrupts(); 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"); } }

The only ESP32 S3 Uno change in this sketch is IRAM_ATTR on the interrupt function. It places the function in internal RAM, so it runs quickly and safely even while the chip reads its flash memory.

Set Up the Encoder Disc

The disc turns rotation into pulses, so it must sit in the right place.

  1. Fix the encoder disc on the motor shaft or the wheel.
  2. Place the sensor so the teeth of the disc pass through the slot.
  3. Make sure the disc does not rub against the sensor arms.
  4. Spin the disc by hand, or power the motor. The motor needs its own wiring; see the ESP32 S3 Uno - DC Motor tutorial for that part.

Detailed Instructions

  1. Keep the wiring the same as before.
  2. Copy the RPM code above and paste it into the Arduino IDE.
  3. Upload the sketch with the Upload button.
  4. Open the Serial Monitor at 9600 baud.
  5. Spin the disc, then let it stop.
  6. Read the speed on the Serial Monitor. In this example the motor runs at about 300 RPM:
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev 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
ESP32S3 Dev Module on COM15
2
  1. Tip: Check one line by hand. 100 pulses × 60 ÷ 20 = 300 RPM.

Do you want to know how far a wheel has moved? Multiply the number of turns by the wheel circumference (π × wheel diameter).

※ NOTE THAT:

  • Keep the interrupt function short, and never call Serial.print() inside it.
  • Every variable the interrupt changes must be volatile.
  • To sense an object at a distance instead of inside a slot, try the obstacle avoidance sensor. For a knob that also gives turn direction, try the rotary encoder.

Video Tutorial

Watch the video below to see this ESP32 S3 Uno project step by step.

Function References

FAQ

Can I power the IR slotted optical speed sensor from 5V on the ESP32 S3 Uno?

The module itself runs on 5V, but then its OUT pin also outputs 5V. ESP32 S3 Uno pins are 3.3V only and are not 5V tolerant. Power the sensor from 3.3V, or put a voltage divider on the OUT line if you need 5V.

Which ESP32 S3 Uno pin should I use for the OUT signal?

This tutorial uses D2 (GPIO18). On the ESP32 S3 Uno every GPIO can trigger an interrupt, so D3, D4, D5 or D7 also work. Avoid D6 (GPIO3) and D9 (GPIO46), because they are boot strapping pins.

Why does the ESP32 S3 Uno interrupt function use IRAM_ATTR?

IRAM_ATTR stores the function in internal RAM. Without it, an interrupt that fires while the chip reads flash can crash the board. The UNO R4 does not need this keyword, but on the ESP32 S3 Uno it is good practice for every interrupt function.

How fast a motor can the ESP32 S3 Uno measure?

The interrupt can count many thousands of pulses per second, so the board is rarely the limit. The sensor response time and the number of disc slots set the real limit. For very fast motors, use a disc with fewer slots.

My RPM value jumps around. How can I make it smoother?

A 1-second window with a 20-slot disc gives a resolution of 3 RPM. Use a longer time window, or average the last few readings. Also make sure the disc is centered and does not wobble in the slot.

What if my project needs more pins than the Uno headers give?

The ESP32 S3 Uno has two extra rows of holes with more GPIOs: GPIO15 and GPIO16 (I/O, PWM, analog), GPIO45 and GPIO35 to GPIO42 (I/O, PWM), and GPIO47/GPIO48 (output only, PWM). Solder pin headers to use them and write the GPIO number in code. A second speed sensor fits well on GPIO35 to GPIO42 or GPIO15/16; do not use GPIO47/48, because they are output only. Be careful: GPIO47 and GPIO48 may run at 1.8V instead of 3.3V on some modules (especially ones with "V" in the name, like R8V or R16V), so 3.3V devices can be damaged or act strangely. Also, GPIO0, GPIO3 (D6), GPIO45 and GPIO46 (D9) are boot strapping pins, and a wrong connection can stop the board from booting or uploading code. Use the other extra pins first.

Troubleshooting

Problem Possible Cause Solution
No COM port in Arduino IDE USB driver missing or board not in upload mode Install the CP210x or CH340 driver, use a data USB cable, or hold BOOT while you press RESET
Nothing prints on the Serial Monitor Wrong baud rate or wrong board selected Set the Serial Monitor to 9600 baud and choose ESP32S3 Dev Module
State never changes Loose wire or wrong pin order on the module Check VCC, GND and OUT against the labels on your module and use D2 (GPIO18)
Blocked and clear are swapped The module has inverted output Swap LOW and HIGH in the first sketch
Object is not detected Object is transparent or too thin Use an opaque card or object that fully covers the slot
RPM reads 0 while the disc spins Disc teeth do not pass the slot or wiring is wrong Move the sensor so the teeth cross the gap and check the OUT wire
RPM value is too high or too low SLOTS_PER_TURN does not match the disc Count the slots on your disc and update SLOTS_PER_TURN
Board resets or GPIO stops working Sensor powered from 5V sends 5V into the pin Power the sensor from 3.3V or add a voltage divider on OUT

※ 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!