Ultrasonic Distance & PIR Motion Sensors
By the end of this lesson you will be able to measure distance in centimetres with sound, detect a person walking into a room, and combine distance readings into a car parking assistant that beeps faster as you get closer. Both sensors are cheap, common in kits and fun to use.
Bats and dolphins find their way using echoes. The ultrasonic sensor does the same thing. The PIR sensor works more like your skin feeling the warmth of someone standing nearby.
Part 1: The HC-SR04 ultrasonic sensor
The HC-SR04 has two round "eyes". One is a tiny speaker that sends a burst of ultrasound (40 kHz, far too high for humans to hear). The other is a microphone that listens for the echo. The time the echo takes tells you the distance.
| Pin | Job |
|---|---|
| VCC | 5 V supply |
| Trig | Input: a 10 microsecond HIGH pulse starts a measurement |
| Echo | Output: goes HIGH for as long as the sound took to go out and come back |
| GND | Ground |
It typically measures from about 2 cm to about 4 m. Soft surfaces like cushions and curtains absorb sound and give poor readings; walls and boxes work well.
The maths
Sound travels at about 343 metres per second in air at 20 °C. That is 0.0343 cm per microsecond. The echo time covers the journey there and back, so we divide by 2:
distance in cm = echo time in microseconds × 0.0343 ÷ 2
For example, an echo of 1166 µs means 1166 × 0.0343 ÷ 2 ≈ 20 cm. Sound is faster in hot air, so on a 40 °C afternoon in Nagpur, readings are off by a few percent. For hobby projects, that is fine.
Step 1: Check the conversion
float echoToCm(unsigned long echoMicros) {
return echoMicros * 0.0343 / 2.0;
}
void setup() {
Serial.begin(9600);
while (!Serial && millis() < 3000) { }
const unsigned long echoes[] = {583, 1166, 2915};
for (int i = 0; i < 3; i++) {
Serial.print(echoes[i]);
Serial.print(" us -> ");
Serial.print(echoToCm(echoes[i]), 1);
Serial.println(" cm");
}
}
void loop() {
}583 us -> 10.0 cm 1166 us -> 20.0 cm 2915 us -> 50.0 cm
The serial output uses us for microseconds because it is plain text; µs is the proper symbol.
Step 2: Wire and measure
| Part | Pin | Connects to |
|---|---|---|
| HC-SR04 | VCC | Arduino 5V |
| HC-SR04 | Trig | Arduino D9 |
| HC-SR04 | Echo | Arduino D10 |
| HC-SR04 | GND | Arduino GND |
pulseIn(pin, HIGH, timeout) waits for the pin to go HIGH, measures how long it stays HIGH in microseconds, and returns that time. If nothing happens before the timeout, it returns 0. A 30,000 µs timeout covers about 5 m, beyond the sensor's range.
const int TRIG_PIN = 9;
const int ECHO_PIN = 10;
float readDistanceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH); // 10 us pulse starts a measurement
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long echo = pulseIn(ECHO_PIN, HIGH, 30000UL);
if (echo == 0) return -1; // no echo: out of range
return echo * 0.0343 / 2.0;
}
void setup() {
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
Serial.begin(9600);
}
void loop() {
float cm = readDistanceCm();
if (cm < 0) {
Serial.println("out of range");
} else {
Serial.print("distance_cm:");
Serial.println(cm, 1);
}
delay(100); // let old echoes die away
}Move your hand towards the sensor. You should see something like:
distance_cm:48.7 distance_cm:31.2 distance_cm:15.9 distance_cm:6.4
These are example values from a moving hand. Check the sensor with a ruler: hold a book 20 cm away and the reading should be close to 20.
Part 2: The PIR motion sensor
A PIR (passive infrared) sensor detects changes in infrared heat across its view. People and animals are warmer than walls, so when someone walks past, the pattern changes and the output goes HIGH. "Passive" means it sends nothing out; it only watches.
The common HC-SR501 module has a white dome (a lens that widens its view), three pins and two orange knobs.
| Feature | Details |
|---|---|
| Pins | VCC (5 V is fine), OUT, GND |
| Output | About 3.3 V when motion is detected, so it is safe for 3.3 V and 5 V boards |
| Sensitivity knob | Sets how far it detects, up to several metres |
| Time-delay knob | Sets how long OUT stays HIGH after motion, from a few seconds to minutes |
| Trigger jumper | "Repeat" mode keeps OUT HIGH while motion continues; "single" mode does not |
| Warm-up | Give it about a minute after power-on before trusting it |
Turn both knobs fully anticlockwise to start with (shortest delay, lowest range), then adjust.
Step 3: A motion alarm
| Part | Pin | Connects to |
|---|---|---|
| HC-SR501 | VCC | Arduino 5V |
| HC-SR501 | OUT | Arduino D7 |
| HC-SR501 | GND | Arduino GND |
const int PIR_PIN = 7;
int lastState = LOW;
void setup() {
pinMode(PIR_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
Serial.println("Warming up...");
delay(30000); // simple warm-up; delay is fine in setup
Serial.println("Ready");
}
void loop() {
int state = digitalRead(PIR_PIN);
if (state != lastState) {
digitalWrite(LED_BUILTIN, state == HIGH ? HIGH : LOW);
Serial.println(state == HIGH ? "Motion detected" : "Motion ended");
lastState = state;
}
}Walk past the sensor once:
Warming up... Ready Motion detected Motion ended
The time between "detected" and "ended" depends on the time-delay knob. This is the same edge detection you used for buttons: print only when the state changes.
Part 3: A parking assistant
Car parking sensors beep faster as you get closer to the wall. The decision is a simple function from distance to beep gap, which we can test with fixed values:
// Returns the gap between beeps in ms: 0 = continuous tone, -1 = silent
long beepGapMs(float cm) {
if (cm < 0) return -1; // out of range
if (cm < 10) return 0; // stop now!
if (cm < 30) return 150;
if (cm < 60) return 400;
return -1; // far away: quiet
}
void setup() {
Serial.begin(9600);
while (!Serial && millis() < 3000) { }
const float tests[] = {120.0, 45.0, 20.0, 8.0, -1.0};
for (int i = 0; i < 5; i++) {
Serial.print(tests[i], 1);
Serial.print(" cm -> gap ");
Serial.println(beepGapMs(tests[i]));
}
}
void loop() {
}120.0 cm -> gap -1 45.0 cm -> gap 400 20.0 cm -> gap 150 8.0 cm -> gap 0 -1.0 cm -> gap -1
In the next lesson you will connect a buzzer and use tone() with these gaps to make the real sound.
Common mistakes
- No timeout in
pulseIn(). Without one, it can wait about a second when there is no echo, freezing your sketch. - Measuring too fast. Leave at least 60 to 100 ms between HC-SR04 readings so old echoes do not confuse the new one.
- Pointing the PIR at a window, heater or AC vent. Moving warm air and sunlight cause false alarms.
- Testing the PIR before it warms up. It may trigger randomly for the first minute.
- Connecting a 5 V Echo pin straight to a 3.3 V board. Use a divider.
Key points
- The HC-SR04 measures echo time; distance in cm = microseconds × 0.0343 ÷ 2.
- Trigger with a 10 µs pulse, measure with
pulseIn(echo, HIGH, timeout); 0 means no echo. - Divide the 5 V Echo signal down for ESP32 and Raspberry Pi pins.
- A PIR's OUT pin goes HIGH when warm bodies move; adjust range and hold time with its knobs and let it warm up.
- Put decisions such as beep speed in small functions and test them with fixed values.
Exercise
Make a "smart dustbin lid" decision: if the distance is under 25 cm and the lid is closed, print open lid; if the distance is 40 cm or more and the lid is open, print close lid. Test with the series 80, 30, 20, 15, 35, 45, 50. Why are there two different distances?
Show solution
bool lidOpen = false;
void check(float cm) {
if (!lidOpen && cm < 25) {
lidOpen = true;
Serial.print(cm, 0);
Serial.println(" cm: open lid");
} else if (lidOpen && cm >= 40) {
lidOpen = false;
Serial.print(cm, 0);
Serial.println(" cm: close lid");
}
}
void setup() {
Serial.begin(9600);
while (!Serial && millis() < 3000) { }
const float series[] = {80, 30, 20, 15, 35, 45, 50};
for (int i = 0; i < 7; i++) check(series[i]);
}
void loop() {
}20 cm: open lid 45 cm: close lid
The gap between 25 and 40 cm is hysteresis: a hand hovering around 30 cm does not make the lid flap open and shut.
Quick Quiz
1. The HC-SR04 reports an echo of 583 µs. About how far is the object?
583 × 0.0343 ÷ 2 ≈ 10 cm. The division by 2 is because the sound travels there and back.
2. What does pulseIn() return if no echo arrives before the timeout?
It returns 0, so check for 0 before converting to distance.
3. What does a PIR sensor detect?
Passive infrared sensors react to moving warm bodies.
4. Why should an HC-SR04 Echo pin go through a divider on an ESP32?
A 1 kΩ and 2 kΩ divider brings 5 V down to about 3.3 V.
Learn IoT & Arduino with a mentor
Free lessons get you started. Our IoT & Embedded Systems training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
