28 Light and Proximity Sensor Labs
28.1 Learning Objectives
By the end of this chapter, you will be able to:
- Interface light sensors: Configure BH1750 digital lux meter and LDR analog sensors
- Build proximity detection: Use PIR motion sensors and HC-SR04 ultrasonic distance sensors
- Compare touch sensing technologies: Differentiate capacitive and skin-inspired tactile sensor architectures
- Diagnose I2C communication: Scan buses, address multiple sensors, and resolve communication issues
- Apply circuit fundamentals: Use voltage dividers and RC filters in sensor circuits
For Beginners: Light and Proximity Sensors
Light sensors measure how bright it is (like the automatic brightness on your phone screen), PIR motion sensors detect when a person walks by (like the sensor that turns on a porch light), and ultrasonic distance sensors measure how far away an object is by bouncing sound waves off it (like a bat navigating in the dark). These are some of the most commonly used sensors in smart home and security projects.
28.2 Prerequisites
Required Knowledge:
- Motion & Environmental Sensors - I2C basics
- Electronics Basics - Circuit fundamentals
- Sensor Circuits - Signal conditioning
Hardware Requirements:
- ESP32 development board
- BH1750 light sensor or LDR (photoresistor)
- PIR motion sensor (HC-SR501)
- HC-SR04 ultrasonic distance sensor
- Resistors (10kOhm for voltage divider, 4.7kOhm for I2C)
- Breadboard and jumper wires
28.3 Light Sensors
28.3.1 BH1750 (Digital Light Intensity)
The BH1750 is a digital ambient light sensor with spectral response close to the human eye, making it ideal for automatic brightness adjustment.
Specifications:
- Range: 1-65535 lux
- Interface: I2C
- Resolution: 1 lux
- I2C Address: 0x23 (ADDR pin LOW/floating) or 0x5C (ADDR pin HIGH)
- Spectral response close to human eye
- Power: 120uA active, 0.01uA power down
ESP32 Implementation:
#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter;
void setup() {
Serial.begin(115200);
Wire.begin(); // Uses default ESP32 I2C pins: SDA=GPIO21, SCL=GPIO22
if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
Serial.println("BH1750 initialized");
} else {
Serial.println("Error initializing BH1750");
}
}
void loop() {
float lux = lightMeter.readLightLevel();
Serial.print("Light: ");
Serial.print(lux);
Serial.print(" lux");
// Classify light levels (approximate ranges)
String classification;
if (lux < 1) {
classification = "Darkness (moonlight)";
} else if (lux < 50) {
classification = "Very dim (street lighting)";
} else if (lux < 200) {
classification = "Dim (hallway)";
} else if (lux < 500) {
classification = "Normal (home/office)";
} else if (lux < 2000) {
classification = "Bright (retail/overcast)";
} else if (lux < 25000) {
classification = "Very bright (daylight)";
} else {
classification = "Extremely bright (direct sun)";
}
Serial.print(" - ");
Serial.println(classification);
delay(1000);
}
Learning Points: Light Sensing
Lux Reference Values:
| Condition | Lux Level |
|---|---|
| Full moon | 0.1-1 |
| Street lighting | 10-50 |
| Home lighting | 150-300 |
| Office | 300-500 |
| Overcast sky | 1,000-2,000 |
| Full daylight | 10,000-25,000 |
| Direct sunlight | 100,000+ |
Real-World Applications:
- Smart street lights: Dim when natural light is sufficient
- Display backlighting: Adjust screen brightness based on ambient light
- Greenhouse automation: Supplement natural light with artificial lighting
- Energy efficiency: Reduce power when full brightness isnโt needed
28.4 Proximity & Presence Sensors
28.4.1 PIR Motion Sensor (HC-SR501)
PIR (Passive Infrared) sensors detect motion by measuring changes in infrared radiation from warm bodies (humans, animals).
Specifications:
- Detection Range: 3-7 meters (adjustable)
- Detection Angle: 110 degree cone
- Output: Digital HIGH when motion detected
- Hold Time: ~2.5 to 200 seconds (adjustable via potentiometer)
- Power: 5V, ~65ยตA quiescent
- Trigger Modes: Single trigger or repeatable trigger
ESP32 Implementation:
#define PIR_PIN 13 // GPIO13 for PIR sensor
bool motionDetected = false;
unsigned long lastMotionTime = 0;
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println("PIR Motion Sensor Test");
Serial.println("Warming up sensor (30-60 seconds)...");
delay(60000); // PIR needs 30-60s warm-up time
Serial.println("Ready!");
}
void loop() {
int pirState = digitalRead(PIR_PIN);
if (pirState == HIGH) {
if (!motionDetected) {
motionDetected = true;
lastMotionTime = millis();
Serial.println("MOTION DETECTED!");
Serial.print("Time: ");
Serial.println(millis() / 1000);
// Trigger action (turn on light, send alert, etc.)
digitalWrite(LED_BUILTIN, HIGH);
}
} else {
if (motionDetected) {
unsigned long motionDuration = (millis() - lastMotionTime) / 1000;
Serial.print("Motion ended. Duration: ");
Serial.print(motionDuration);
Serial.println(" seconds");
motionDetected = false;
digitalWrite(LED_BUILTIN, LOW);
}
}
delay(100);
}
Learning Points: PIR Sensors
Key Characteristics:
- PIR sensors need a 30-60 second warm-up period after power-on
- Output is digital (HIGH/LOW), making interfacing very simple
- The sensor stays HIGH as long as motion is detected
- Adjustable potentiometers control sensitivity and hold time
Real-World Applications:
- Smart lighting: Turn on lights when someone enters a room
- Security systems: Send alerts when motion detected while away
- Energy saving: Power down devices when no one is present
- Occupancy counting: Track room usage patterns
28.4.2 Ultrasonic Distance Sensor (HC-SR04)
Ultrasonic sensors measure distance by timing the echo return of a 40kHz sound pulse.
Specifications:
- Range: 2cm to 400cm
- Accuracy: +/-3mm
- Measuring angle: 15 degrees
- Trigger pulse: 10us
- Power: 5V, 15mA
The HC-SR04 operates at 5V logic, but the ESP32 GPIO pins are 3.3V. The ECHO pin outputs a 5V HIGH signal that can damage the ESP32. Use a voltage divider (two resistors, e.g., 1kOhm + 2kOhm) on the ECHO line to reduce it to ~3.3V, or use a 3.3V-compatible variant like the HC-SR04P.
ESP32 Implementation:
#define TRIG_PIN 5
#define ECHO_PIN 18
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
float distance = measureDistance();
Serial.print("Distance: ");
Serial.print(distance, 1);
Serial.println(" cm");
// Object detection
if (distance > 0 && distance < 50) {
Serial.println("Object detected nearby!");
}
delay(100);
}
float measureDistance() {
// Send 10us pulse
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Measure echo pulse duration
long duration = pulseIn(ECHO_PIN, HIGH, 30000); // 30ms timeout
// Calculate distance (speed of sound = 343 m/s at 20ยฐC)
// distance = (duration * 0.0343) / 2
float distance = duration * 0.0343 / 2.0;
return distance;
}
Putting Numbers to It
Ultrasonic sensors measure distance using the time-of-flight formula where sound travels to the object and back.
$ d = $
where \(d\) is distance, \(t\) is echo pulse duration in microseconds, and \(v_{\text{sound}}\) is 343 m/s (0.0343 cm/ยตs) at 20ยฐC.
Worked example: The HC-SR04 measures an echo pulse duration of 1,750 ยตs. Calculate the distance:
$ d = = = 30.01 , $
Temperature compensation: At 0ยฐC, speed of sound is 331.3 m/s (3.5% slower), so the same 1,750 ยตs pulse would calculate as 29.0 cm instead of 30.0 cm, introducing 1 cm error per 30 cm distance without compensation.
Try It: Ultrasonic Distance Calculator
Adjust the echo pulse duration and ambient temperature to see how they affect the calculated distance. Notice how temperature changes the speed of sound and therefore the distance measurement.
Learning Points: Ultrasonic Sensors
Key Concepts:
- Time-of-Flight:
distance = (time * speed_of_sound) / 2 - Speed of Sound: 343 m/s at 20ยฐC (0.0343 cm/ยตs)
- Temperature Compensation: Speed varies ~0.6 m/s per ยฐC
- Blind zone: Cannot measure objects closer than 2cm
Real-World Applications:
- Parking sensors: Alert drivers to obstacles
- Robotics: Obstacle avoidance and navigation
- Liquid level sensing: Measure tank fill levels
- People counting: Detect presence at doorways
Temperature Compensation Formula:
float speed_of_sound = 331.3 + (0.606 * temp_celsius); // m/s
float distance_cm = (pulse_duration_us * speed_of_sound) / 20000;
How It Works: HC-SR04 Ultrasonic Distance Measurement
The HC-SR04 uses time-of-flight measurement with these precise steps:
- Send trigger pulse - 10ยตs HIGH signal on TRIG pin activates the sensor
- Sensor emits ultrasound - 8 cycles of 40 kHz sound burst (inaudible to humans)
- Wait for echo - ECHO pin goes HIGH when sound is transmitted
- Measure echo duration - ECHO pin goes LOW when reflected sound returns
- Calculate distance -
distance_cm = (echo_duration_ยตs ร 0.0343) / 2
Why divide by 2? The sound travels to the object AND back, so the total time covers twice the distance.
28.5 Touch and Skin Sensing
Beyond light and proximity, touch sensors represent an emerging area of IoT sensing. Skin-inspired (or โe-skinโ) sensors mimic the multi-layer structure of human skin, using arrays of capacitive or piezoresistive sensing elements (โtaxelsโ) to detect pressure, texture, and even temperature. These sensors are particularly relevant for robotics, prosthetics, and human-computer interaction.
For IoT applications, simpler capacitive touch sensors (like the ESP32โs built-in touch pins) are commonly used for user interfaces, while advanced e-skin arrays remain primarily a research area.
28.6 I2C Sensors and Bus Management
I2C Bus Scanner:
#include <Wire.h>
#define I2C_SDA 21
#define I2C_SCL 22
void setup() {
Serial.begin(115200);
Wire.begin(I2C_SDA, I2C_SCL);
// Scan I2C bus
scanI2C();
}
void scanI2C() {
Serial.println("Scanning I2C bus...");
int devices = 0;
for(byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at 0x");
if (address < 16) Serial.print("0");
Serial.println(address, HEX);
devices++;
}
}
Serial.print("Found ");
Serial.print(devices);
Serial.println(" devices");
}
void loop() {
// Empty loop
}28.7 Circuit Fundamentals for Sensors
28.7.1 Voltage Divider with LDR
For analog light sensors (LDR/photoresistor), use a voltage divider circuit:
VCC (3.3V) ----[LDR]----+----[10kOhm]---- GND
|
ADC Pin
How it works:
- LDR resistance decreases with more light (~1kOhm bright, ~1MOhm dark)
- Voltage at midpoint varies with light level
- ADC reads this varying voltage
28.7.2 RC Low-Pass Filter
Filter high-frequency noise from sensor readings:
Sensor Output ----[10kOhm]----+---- ADC Pin
|
[100nF]
|
GND
Cutoff Frequency: f_c = 1 / (2 * PI * R * C) = 159 Hz
Try It: LDR Voltage Divider Calculator
Adjust the LDR resistance (which changes with light level) to see how the voltage at the ADC pin changes. The fixed resistor is 10kOhm and the supply is 3.3V.
Learning Points: Circuit Fundamentals
Voltage Divider Formula:
V_out = V_in * (R2 / (R1 + R2))
RC Filter Time Constant:
tau = R * C
5*tau = time to reach steady state
Why Use Pull-up Resistors for I2C: I2C uses open-drain outputs that can only pull LOW. Pull-up resistors (4.7k Ohm typical) pull the line HIGH when no device is transmitting.
28.8 Interactive Simulator: Sensor-Controlled Servo
Try it yourself! See how sensors control actuators for automated systems.
What This Simulates: An LDR light sensor controlling a servo motor to automatically adjust window blinds based on sunlight.
How to Use:
- Click Start Simulation
- Click on the LDR sensor to adjust light levels
- Watch the servo motor rotate as light changes
- Observe the Serial Monitor showing light levels and servo angles
28.9 Knowledge Check
Key Takeaway
Light and proximity sensors each serve distinct purposes: BH1750 provides calibrated lux measurements for adaptive lighting, PIR sensors detect human presence via infrared changes (with a 30-60 second warm-up), and ultrasonic sensors measure distance using sound waves (requiring temperature compensation for outdoor use). Understanding voltage dividers and RC filters is essential for interfacing analog sensors reliably.
For Kids: Meet the Sensor Squad!
The Sensor Squad got three new members today!
First up is Lucy the Light Sensor (BH1750). โI can tell you exactly how bright it is,โ Lucy said. โOffice lighting is about 300-500 lux, but direct sunlight is over 100,000 lux! Smart buildings use me to dim the lights when the sun is shining โ saving lots of energy!โ
Next is Pete the PIR Sensor. โI detect warm-blooded creatures!โ Pete announced. โWhen a person walks by, the infrared heat pattern changes, and I send a HIGH signal. But you have to wait about a minute after I power on โ I need my warm-up time!โ
Finally, Ulti the Ultrasonic Sensor (HC-SR04) showed off: โI shout a tiny โBEEPโ too high for humans to hear, then listen for the echo bouncing back. The longer it takes, the farther away the object is! I use the speed of sound โ 343 meters per second at room temperature.โ
Bella the Battery asked, โWhat happens when it is cold outside?โ Ulti admitted: โSound travels slower in cold air, so I need a thermometer friend to help me calculate distance correctly!โ
Common Mistake: PIR False Triggers from Heat Sources
The Problem: Your PIR motion sensor triggers constantly even when no one is present, or fails to detect people walking by.
Why It Happens: PIR sensors detect changes in infrared radiation, not absolute levels. Common causes of false triggers:
- Heating/AC vents: Warm or cold air blowing directly on the PIR causes continuous infrared fluctuations
- Sunlight through windows: Moving shadows or direct sunlight changes IR levels dramatically
- Pets: Dogs and cats are warm-blooded and trigger PIR sensors (not a โfalseโ alarm, but unintended)
- Mounting height: PIR pointed at ground level has a small detection zone; mounted at 2-2.5m gives optimal 5-7m range
Real-World Example: A smart lighting system in an office hallway triggers lights every 30 seconds with no one present. Investigation reveals the PIR sensor is mounted directly below an AC vent. Cold air blowing on the sensor creates IR fluctuations that mimic human motion.
The Fix:
// Add filtering to reduce false triggers
const int PIR_PIN = 13;
const int MIN_TRIGGER_DURATION = 500; // Milliseconds
unsigned long triggerStartTime = 0;
bool confirmedMotion = false;
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
delay(60000); // Wait for PIR warm-up
}
void loop() {
int pirState = digitalRead(PIR_PIN);
if (pirState == HIGH) {
if (triggerStartTime == 0) {
triggerStartTime = millis(); // Start timing
} else if (millis() - triggerStartTime > MIN_TRIGGER_DURATION) {
// Confirmed motion only if PIR stays HIGH for 500ms
if (!confirmedMotion) {
confirmedMotion = true;
Serial.println("CONFIRMED MOTION");
digitalWrite(LED_BUILTIN, HIGH);
}
}
} else {
// PIR went LOW - reset
triggerStartTime = 0;
confirmedMotion = false;
digitalWrite(LED_BUILTIN, LOW);
}
delay(50);
}Prevention Checklist:
Key Insight: PIR sensors are binary (motion / no motion) and donโt provide distance or identity. For more sophisticated detection, combine PIR with ultrasonic (distance) or camera (identity).
28.10 Hands-On Challenges
Beginner Level: Read a Single Light Sensor
Goal: Read the BH1750 light sensor and display lux values.
Steps:
- Connect BH1750: VCCโ3.3V, GNDโGND, SDAโGPIO21, SCLโGPIO22
- Add 4.7kฮฉ pull-up resistors on SDA and SCL (or use module with built-in resistors)
- Install library:
#include <BH1750.h> - Initialize in setup:
lightMeter.begin() - Read in loop:
float lux = lightMeter.readLightLevel()
Expected result: 300-500 lux in office, 10,000+ lux outdoors
Intermediate Level: Build an Automatic Night Light
Goal: Turn on LED when ambient light drops below 50 lux.
Components: BH1750 + LED + 220ฮฉ resistor + ESP32
Challenge:
- Read light every second
- Use hysteresis (turn on at 50 lux, turn off at 80 lux) to prevent flickering
- Add fade-in/fade-out using PWM
Code skeleton:
float lux = lightMeter.readLightLevel();
if (lux < 50 && !ledOn) {
fadeIn(LED_PIN);
ledOn = true;
} else if (lux > 80 && ledOn) {
fadeOut(LED_PIN);
ledOn = false;
}
Advanced Level: Multi-Zone Motion-Activated Lighting
Goal: Use 3 PIR sensors to detect which zone has motion, light only that zoneโs LEDs.
Challenge:
- Wire 3 PIR sensors (front, middle, back)
- Control 3 LED strips independently
- Implement timeout (lights off 30s after last motion)
- Add ambient light override (donโt activate if already bright)
Bonus: Log motion patterns to SD card for occupancy analysis
28.11 Concept Relationships
| Core Concept | Related Concepts | Why It Matters |
|---|---|---|
| Lux Measurement | Human Vision Response, Lighting Standards | BH1750 spectral response matches human eye |
| PIR Motion Detection | Infrared Changes, Warm-Up Period, False Triggers | Detects heat signature changes, not absolute values |
| Ultrasonic ToF | Speed of Sound, Temperature Compensation | Accuracy depends on knowing sound velocity |
| I2C Pull-ups | Open-Drain Logic, Signal Integrity | Required for reliable I2C communication |
28.12 Summary
This chapter covered light and proximity sensor implementation:
- BH1750 provides calibrated lux measurements with spectral response matching human vision
- PIR sensors detect motion via infrared radiation changes with simple digital output
- Ultrasonic HC-SR04 measures distance using time-of-flight with temperature compensation needed
- I2C bus scanning identifies connected sensors and verifies communication
- Voltage dividers convert variable resistance (LDR) to measurable voltage
- RC filters reduce high-frequency noise from analog sensor readings
28.13 See Also
- Temperature Sensor Labs - Temperature sensing fundamentals
- Motion & Environmental Sensors - IMU and barometric sensing
- Sensor Calibration Lab - Hands-on calibration techniques
Common Pitfalls
1. Ambient Light Interference with IR Proximity Sensors
IR proximity sensors (VCNL4040, APDS-9960) can be overwhelmed by strong sunlight or incandescent lamps. In high-ambient-light environments, shield the sensor from direct light or use ultrasonic ranging instead.
2. HC-SR04 Echo Timeout Blocking the Main Loop
If nothing is within the sensorโs 4 m range, the echo pulse may never return. Without a timeout, pulseIn() blocks for up to 1 second. Always use pulseIn(ECHO, HIGH, 30000) with a 30 ms timeout to prevent main loop blocking.
3. LDR Voltage Divider Fixed Resistor Mismatch
A light-dependent resistor requires a fixed reference resistor. If the reference is mismatched to the LDRโs operating range, the voltage swing across the full light range is compressed. Choose the fixed resistor near the geometric mean of the LDRโs bright and dark resistance values.
4. Ultrasonic Sensor Crosstalk in Arrays
When two HC-SR04 sensors cover overlapping areas, sensor Aโs transmitted pulse can trigger sensor Bโs echo detection, causing false distance readings. Trigger sensors sequentially with 50 ms gaps between triggers.
28.14 Whatโs Next?
| Topic | Description |
|---|---|
| Best Practices & Labs | Sensor wiring standards, debugging strategies, and deployment guidelines |
| Calibration Lab (Wokwi) | Hands-on calibration techniques using simulated ESP32 environments |
| Temperature Sensor Labs | Thermistor, DS18B20, and thermocouple implementation on ESP32 |
| Motion & Environmental Sensors | IMU, barometric pressure, and environmental sensor integration |
| Sensor Circuits & Signal Conditioning | Amplification, filtering, and ADC interfacing for analog sensors |