EthicsComputerCourses
Join training +91 90532 10052

Ultrasonic Distance & PIR Motion Sensors

IoT & Arduino · Lesson 19 of 29 Intermediate 7 min read Updated

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.

PinJob
VCC5 V supply
TrigInput: a 10 microsecond HIGH pulse starts a measurement
EchoOutput: goes HIGH for as long as the sound took to go out and come back
GNDGround

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

C++
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() {
}
Output
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

PartPinConnects to
HC-SR04VCCArduino 5V
HC-SR04TrigArduino D9
HC-SR04EchoArduino D10
HC-SR04GNDArduino 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.

C++
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:

Output
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.

FeatureDetails
PinsVCC (5 V is fine), OUT, GND
OutputAbout 3.3 V when motion is detected, so it is safe for 3.3 V and 5 V boards
Sensitivity knobSets how far it detects, up to several metres
Time-delay knobSets 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-upGive 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

PartPinConnects to
HC-SR501VCCArduino 5V
HC-SR501OUTArduino D7
HC-SR501GNDArduino GND
C++
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:

Output
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:

C++
// 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() {
}
Output
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
C++
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() {
}
Output
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?

2. What does pulseIn() return if no echo arrives before the timeout?

3. What does a PIR sensor detect?

4. Why should an HC-SR04 Echo pin go through a divider on an ESP32?

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