When building an infrared detector Arduino project, the sheer number of sensor modules on the market can lead to frustrating mismatches. A raw phototransistor will blind itself in sunlight, a standard TV remote receiver won't trigger on static objects, and a Time-of-Flight sensor might blow your per-unit budget on a multi-node conveyor belt. The secret to a reliable IR build lies in matching the sensor's modulation scheme and output type to your specific physical environment.
This guide walks through building a robust, interrupt-driven proximity and intrusion alarm using the industry-standard KY-032 module, while providing a concrete decision framework to ensure you are actually using the right part for your specific application.
The Sensor Decision Tree: Which IR Module Should You Pick?
Before wiring anything, we need to terminate the "which sensor is best" debate. Different IR detectors solve fundamentally different physics problems. Use this decision path to select your hardware.
| Application Need | Recommended Sensor | Technical Reason | Typical Cost |
|---|---|---|---|
| General proximity, obstacle avoidance, or intrusion alarm | KY-032 (4-pin module) | Features a 38kHz modulated IR LED and an LM393 comparator. Rejects ambient sunlight. Provides both digital and analog outputs. | ~$2.00 |
| Decoding TV remotes or IR control signals | VS1838B / TSOP38238 | Optimized for continuous burst decoding with automatic gain control (AGC). Fails at static object detection. | ~$1.00 |
| Exact distance measurement (mm precision) regardless of object color | VL53L1X Time-of-Flight | Uses a VCSEL laser and SPAD array. Measures photon flight time, completely bypassing the reflectivity issues of standard IR. | ~$12.00 |
| Detecting black/dark objects on a conveyor line | Sharp GP2Y0A21 (Analog IR) | High-power emitter and position-sensitive detector (PSD) calculate distance via triangulation rather than reflectivity intensity. | ~$9.00 |
Parts List & Hardware Spec Sheet
This build targets the Arduino Nano V3 (ATmega328P, 16MHz, 5V logic). The Nano is chosen over the Uno for its breadboard-friendly footprint and identical ATmega328P silicon, making it ideal for embedding into a final project enclosure.
| Component | Exact Variant / Model | Key Specifications |
|---|---|---|
| Microcontroller | Arduino Nano V3 (ATmega328P) | 5V logic, 16MHz clock, 32KB Flash, INT0/INT1 on D2/D3 |
| IR Sensor | KY-032 Obstacle Avoidance Module | 940nm IR LED, 38kHz modulation, LM393 comparator, 2-40cm range |
| Audio Alert | 5V Active Buzzer | Internal oscillator, draws ~30mA, requires digital HIGH to trigger |
| Current Limiting | 10kΩ Resistor (1/4W) | Used as an external pull-up on the digital line for long wire runs |
Pin Mapping Table
The KY-032 has four pins. While many tutorials ignore the analog pin (A0), reading it allows you to create a crude "distance gradient" rather than a simple binary tripwire.
| KY-032 Pin | Arduino Nano Pin | Function & Notes |
|---|---|---|
| VCC | 5V | Requires stable 5V. Do not power from the 3V3 pin. |
| GND | GND | Common ground reference. |
| OUT (DO) | D2 | Digital output. Active LOW when object detected. D2 supports hardware interrupts (INT0). |
| A0 (AO) | A0 | Analog output. Voltage drops as object gets closer. Requires analogRead(). |
Buzzer Wiring: Buzzer Positive to Nano D8, Buzzer Negative to Nano GND.
Step-by-Step Build & Compilable Code
- Mount the Nano: Press the Arduino Nano V3 across the center trench of a standard 830-point breadboard.
- Wire Power Rails: Connect Nano 5V to the red rail and Nano GND to the blue rail.
- Connect the KY-032: Route VCC and GND to the power rails. Connect the OUT pin to Nano D2, and the A0 pin to Nano A0.
- Add the Pull-up (Optional but recommended): If your jumper wires exceed 15cm, insert a 10kΩ resistor between Nano D2 and the 5V rail to prevent EMI-induced phantom triggers.
- Wire the Buzzer: Connect the buzzer's positive (usually marked with a + or longer leg) to D8, and negative to GND.
- Tune the Trimpots: Before uploading code, use a small Phillips screwdriver to adjust the two blue potentiometers on the KY-032. Turn the Frequency pot fully counter-clockwise, then slowly clockwise while pointing it at a wall until the onboard status LED flickers. Adjust the Sensitivity pot to set your desired trip distance (usually 2-10cm).
Complete Compilable Code
This sketch uses a hardware interrupt for the digital pin to ensure zero missed triggers, even if the main loop is busy. It also includes a runtime hardware check to detect disconnected sensors. For more on interrupt mechanics, consult the Arduino attachInterrupt() reference.
// Target Board: Arduino Nano V3 (ATmega328P, 16MHz, 5V Logic)
// Project: KY-032 IR Proximity & Intrusion Alarm
#define IR_DIGITAL_PIN 2 // Must be an interrupt-capable pin (D2 or D3 on Nano)
#define IR_ANALOG_PIN A0 // Analog pin for crude distance estimation
#define BUZZER_PIN 8 // Active buzzer output
#define FLOATING_THRESHOLD 1010 // ADC value indicating a disconnected/floating analog pin
volatile bool objectDetected = false;
unsigned long lastTriggerTime = 0;
const unsigned long DEBOUNCE_MS = 250;
const unsigned long ALARM_DURATION_MS = 500;
// Interrupt Service Routine (ISR) - keep it as short as possible
void irTripInterrupt() {
objectDetected = true;
}
void setup() {
Serial.begin(115200);
pinMode(IR_DIGITAL_PIN, INPUT); // KY-032 has onboard pull-up
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// Runtime Hardware Verification
// If the analog pin reads near max (1023), the sensor is likely unpowered or disconnected
int initialCheck = analogRead(IR_ANALOG_PIN);
if (initialCheck > FLOATING_THRESHOLD) {
Serial.println("[ERR] IR_DETECT: Sensor disconnected or floating on A0. Check VCC/GND wiring.");
// Blink LED to indicate hardware fault
pinMode(LED_BUILTIN, OUTPUT);
while(true) { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); delay(100); }
}
Serial.println("IR Detector initialized. Monitoring...");
// Attach interrupt on FALLING edge (KY-032 OUT goes LOW when object is detected)
attachInterrupt(digitalPinToInterrupt(IR_DIGITAL_PIN), irTripInterrupt, FALLING);
}
void loop() {
unsigned long currentMillis = millis();
if (objectDetected) {
// Debounce check
if (currentMillis - lastTriggerTime > DEBOUNCE_MS) {
lastTriggerTime = currentMillis;
// Read analog value to estimate proximity (lower number = closer object)
int proximity = analogRead(IR_ANALOG_PIN);
Serial.print("ALARM TRIGGERED | Analog Proximity Value: ");
Serial.println(proximity);
// Sound the active buzzer
digitalWrite(BUZZER_PIN, HIGH);
delay(ALARM_DURATION_MS);
digitalWrite(BUZZER_PIN, LOW);
}
objectDetected = false; // Reset flag
}
// Non-blocking background task example: periodic sensor health check
static unsigned long lastHealthCheck = 0;
if (currentMillis - lastHealthCheck > 5000) {
lastHealthCheck = currentMillis;
int healthRead = analogRead(IR_ANALOG_PIN);
if (healthRead > FLOATING_THRESHOLD) {
Serial.println("[WARN] IR_DETECT: Analog line floating. Check sensor power.");
}
}
}
Debugging: First Three Things to Check When It Fails
IR sensors are notoriously finicky because they interact with invisible light and analog physics. If your alarm isn't triggering, or is triggering constantly, follow this ranked troubleshooting path.
1. The Compile Error: error: 'digitalPinToInterrupt' was not declared in this scope
If the IDE fails to compile with this exact string, the issue is entirely in your software environment, not your wiring.
- Cause A (Most Likely): You have the wrong board selected in the IDE (e.g., selecting an ESP32 or ATTiny core that handles interrupts differently) or you are using an Arduino IDE version older than 1.0.6. Fix: Go to Tools > Board and select Arduino Nano.
- Cause B: If using PlatformIO, you forgot to include the core API. Fix: Add
#include <Arduino.h>at the very top of your sketch. - Cause C: Typo in the macro. Ensure it is exactly
digitalPinToInterrupt(case-sensitive).
2. Phantom Triggers (Alarm sounds when nothing is there)
This is almost always caused by ambient IR flooding. The sun, incandescent bulbs, and CFLs emit massive amounts of 940nm infrared light. While the KY-032 uses 38kHz modulation to reject DC light, a direct sunbeam can saturate the receiver's photodiode, blinding the LM393 comparator and forcing the output LOW.
- Fix: Add a physical shroud (a piece of heat-shrink tubing or a 3D-printed hood) over the receiver dome to limit its field of view. Recalibrate the sensitivity trimpot after adding the shroud.
3. Sensor Never Triggers (Digital pin stays HIGH)
If the Serial monitor shows no output and the buzzer is silent, the sensor isn't seeing its own reflection.
- Check the Trimpots: The factory calibration on cheap KY-032 modules is often completely misaligned. Point the sensor at a white piece of paper 5cm away. Slowly turn the Sensitivity potentiometer until the red LED on the module turns on, then back it off just a fraction.
- Check Object Color: IR reflects beautifully off white and light colors, but black objects (especially matte black plastic or rubber) absorb 940nm light. If you are trying to detect a black object, you must reduce the distance to under 3cm or switch to the Sharp GP2Y0A21 mentioned in the decision tree.
Extending and Simplifying the Build
Depending on your final deployment environment, you may need to strip this project down to its bare essentials or scale it up into a networked IoT device.
How to Simplify (The "Polling-Only" Approach)
If you are porting this to a microcontroller with limited memory or strict interrupt restrictions (like certain ATTiny chips), you can delete the attachInterrupt() logic entirely. Replace the ISR with a simple blocking digitalRead(IR_DIGITAL_PIN) inside the loop(). You will lose the ability to catch fast-moving objects (like a falling coin on a conveyor), but it will save SRAM and simplify the codebase for basic room-occupancy detection.
How to Extend (Networked MQTT Intrusion System)
To turn this into a smart-home integrated security sensor, swap the Arduino Nano for an ESP32 DevKit V1. Note on Logic Levels: The KY-032 outputs 5V on its digital pin. The ESP32 GPIOs are strictly 3.3V tolerant. You must use a simple voltage divider (e.g., 2.2kΩ to GND, 3.3kΩ to the signal line) to step the 5V OUT down to ~3.0V before feeding it into an ESP32 GPIO. For a deeper dive into level shifting, see the Adafruit IR Sensor Guide.
Once level-shifted, integrate the PubSubClient library to publish a JSON payload ({"sensor":"hallway_ir", "state":"tripped", "analog_val": 412}) to an MQTT broker like Mosquitto, allowing Home Assistant to trigger complex automations, log timestamps, and send push notifications to your phone without relying on a local buzzer.






