The IR obstacle module bounces infrared off anything in front of it and gives a clean digital LOW when something is within range. It is used in line-following and obstacle-avoiding robots, hand-wave switches and simple object counters.
On the ESP32 DevKit V1 the key point is the supply: the module’s output follows VCC, so running it from 3V3 keeps the signal at a safe level with no divider. The sketch counts each object that breaks the beam.
Wiring
| ESP32 DevKit V1 | IR obstacle avoidance sensor (FC-51) | Wire | Notes |
|---|---|---|---|
| 3V3 | VCC | red | OUT is pulled up to VCC on the module — at 3V3 it can never exceed 3.3 V. |
| GND | GND | black | |
| D18 | OUT | green | LOW = obstacle (the module’s green LED lights). |
3.3–5 V part, 3.3 V board. OUT is pulled up to VCC, so power the module from 3.3 V on ESP32 and Pico (it works fine there) and from 5 V on Uno.
Board quirks
- Power it from 3V3, not VIN. The comparator output is pulled up to VCC, so at 5 V it would put 5 V on the GPIO. The module works fine at 3.3 V, with a slightly shorter range.
- Set the range with the blue potentiometer while watching the green LED, not the serial output. Typical range is 2–15 cm, less on black or shiny surfaces.
- Sunlight and halogen lamps contain lots of infrared and can trigger it constantly — shade the sensor for outdoor or window-side use.
- GPIO18 is a plain GPIO (also VSPI SCK). If your project uses SPI, D27, D32 or D33 work just as well; avoid D2, D12 and D15 because the module holds OUT at a fixed level during reset.
Code
// IR obstacle sensor module (FC-51 style) on an ESP32 DevKit V1.
// VCC → 3V3, GND → GND, OUT → D18 (GPIO18). OUT goes LOW when an obstacle is in range.
constexpr uint8_t OBSTACLE_PIN = 18;
bool lastBlocked = false;
uint32_t count = 0;
void setup() {
Serial.begin(115200);
pinMode(OBSTACLE_PIN, INPUT); // the module has its own pull-up to VCC (3.3 V here)
Serial.println("IR obstacle sensor on GPIO18 — wave a hand in front of it");
}
void loop() {
bool blocked = digitalRead(OBSTACLE_PIN) == LOW;
if (blocked && !lastBlocked) {
count++;
Serial.printf("Obstacle detected (#%lu)\n", (unsigned long)count);
} else if (!blocked && lastBlocked) {
Serial.println("Path clear");
}
lastBlocked = blocked;
delay(10);
}// IR obstacle sensor module (FC-51 style) on an ESP32 DevKit V1.
// VCC → 3V3, GND → GND, OUT → D18 (GPIO18). OUT goes LOW when an obstacle is in range.
constexpr uint8_t OBSTACLE_PIN = 18;
bool lastBlocked = false;
uint32_t count = 0;
void setup() {
Serial.begin(115200);
pinMode(OBSTACLE_PIN, INPUT); // the module has its own pull-up to VCC (3.3 V here)
Serial.println("IR obstacle sensor on GPIO18 — wave a hand in front of it");
}
void loop() {
bool blocked = digitalRead(OBSTACLE_PIN) == LOW;
if (blocked && !lastBlocked) {
count++;
Serial.printf("Obstacle detected (#%lu)\n", (unsigned long)count);
} else if (!blocked && lastBlocked) {
Serial.println("Path clear");
}
lastBlocked = blocked;
delay(10);
}Expected serial output
IR obstacle sensor on GPIO18 — wave a hand in front of it
Obstacle detected (#1)
Path clear
Obstacle detected (#2)
Path clearIR obstacle sensor on GPIO18 — wave a hand in front of it Obstacle detected (#1) Path clear Obstacle detected (#2) Path clear
Common errors
Always “Obstacle detected”
Range pot turned too far, or ambient IR (sunlight, a nearby remote). Turn the pot anticlockwise until the green LED goes out with nothing in front.
Never detects anything
Range pot fully anticlockwise, or the IR LED and photodiode bent so they face each other. Point both straight ahead.
Counts one object several times
Edges of the object flicker across the threshold. Add a 50 ms dead time after each detection, or debounce like a push button.