An ultrasonic sensor is one of the most versatile components in the Arduino ecosystem, allowing projects to perceive distance without physical contact. Whether you are building an obstacle-avoiding robot, a smart parking system, or a water-level monitor, the HC-SR04 (or its waterproof sibling, the JSN-SR04T) is the go-to choice for hobbyists and professionals alike. Understanding the logic behind the code is just as important as wiring the hardware, because the accuracy of your measurements depends entirely on how you handle the timing pulses and mathematical conversions inside the sketch That's the part that actually makes a difference..
Understanding the Hardware Logic
Before writing a single line of code, it helps to visualize what the sensor actually does. The module contains two cylindrical transducers: one acts as a speaker (Trigger) emitting a 40 kHz sound wave, and the other acts as a microphone (Echo) listening for the reflection. Practically speaking, when you send a short 10-microsecond pulse to the Trig pin, the sensor emits an 8-cycle sonic burst. The Echo pin immediately goes HIGH. Worth adding: it stays HIGH until the sound wave bounces off an object and returns. The duration of that HIGH signal—measured in microseconds—is the raw data you need But it adds up..
Quick note before moving on.
The speed of sound in air at 20°C is approximately 343 meters per second, or 0.0343 centimeters per microsecond. Because the sound travels to the object and back, the total distance covered is double the actual range.
Distance (cm) = (Duration (µs) × 0.0343) / 2
Alternatively, using the reciprocal for integer math optimization:
Distance (cm) = Duration (µs) / 58.2
Keeping this physics in mind prevents the common "off-by-factor-of-two" error that plagues many beginners Less friction, more output..
Basic Wiring Setup
Standard wiring for the HC-SR04 connects VCC to 5V, GND to Ground, Trig to a digital output pin (e.g., Pin 9), and Echo to a digital input pin (e.And g. On top of that, , Pin 10). *Note: The Echo pin outputs a 5V signal. On the flip side, if you are using a 3. 3V logic board like an ESP32, Arduino Due, or Raspberry Pi Pico, you must use a voltage divider (two resistors) on the Echo line to protect the microcontroller's GPIO.
The "Hello World" Sketch: Using pulseIn()
The most common way to read the sensor uses the built-in Arduino function pulseIn(). This function pauses the program execution and waits for the pin to go HIGH, starts a timer, waits for it to go LOW, and returns the duration in microseconds. It is blocking code, meaning your Arduino cannot do anything else while waiting for the echo.
No fluff here — just what actually works.
/*
Basic Ultrasonic Distance Measurement
Sensor: HC-SR04
Author: Arduino Educational Series
*/
const int TRIG_PIN = 9;
const int ECHO_PIN = 10;
// Variables for calculation
long duration;
float distanceCm;
float distanceInch;
void setup() {
// Initialize serial communication for debugging
Serial.begin(9600);
// Configure pin modes
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
// 1. Clear the trigger pin (ensure clean LOW state)
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
// 2. Send 10µs HIGH pulse to trigger the sensor
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 3. Read the Echo pin: returns pulse length in microseconds
// pulseIn() has a default timeout of 1 second (1,000,000 µs)
duration = pulseIn(ECHO_PIN, HIGH);
// 4. That's why calculate distance
// Speed of sound = 343 m/s = 0. 0343 cm/µs
// Divide by 2 because sound travels there AND back
distanceCm = duration * 0.0343 / 2;
distanceInch = distanceCm * 0.
// 5. Print results to Serial Monitor
Serial.That's why print("Distance: ");
Serial. print(distanceCm);
Serial.Also, print(" cm | ");
Serial. print(distanceInch);
Serial.
// Small delay to stabilize readings (sensor max rate ~20Hz)
delay(100);
}
Code Breakdown: Critical Timing Details
delayMicroseconds(2)before Trigger: The datasheet recommends a short LOW period before the trigger pulse to ensure the internal circuitry resets cleanly.delayMicroseconds(10)for Trigger: The HC-SR04 requires a minimum 10µs HIGH pulse on the Trig pin to initiate the 8-cycle sonic burst. Shorter pulses may fail to trigger; longer pulses waste time.pulseIn(ECHO_PIN, HIGH): This is the workhorse. It measures the width of the returning pulse. If no object is detected (or it is out of range), the pin never goes HIGH, andpulseIneventually times out after 1 second (default), returning0. Always check forduration == 0in production code to handle "out of range" scenarios.
Advanced Approach: Non-Blocking Code with Interrupts
The pulseIn() method is easy but "blocking." If you are driving motors, reading other sensors, or updating a display, a 1-second timeout freezes your entire robot. For serious projects, you must use External Interrupts and Timer registers. This allows the Arduino to perform other tasks while the sensor measures in the background.
Some disagree here. Fair enough.
Here is a solid, non-blocking implementation using Pin Change Interrupts (compatible with standard Uno/Nano pins).
/*
Non-Blocking Ultrasonic Sensor using Interrupts
Compatible with Arduino Uno, Nano, Mega (adjust PCINT vectors for Mega)
*/
const byte TRIG_PIN = 9;
const byte ECHO_PIN = 10;
volatile unsigned long pulseStart = 0;
volatile unsigned long pulseEnd = 0;
volatile bool newDataReady = false;
float distanceCm = 0;
unsigned long lastTriggerTime = 0;
const unsigned long TRIGGER_INTERVAL = 60; // ms (approx 16Hz max safe rate)
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
digitalWrite(TRIG_PIN, LOW);
// --- Configure Pin Change Interrupt for Echo Pin (Pin 10 = PCINT2) ---
// 1. Enable Pin Change Interrupt 0 (covers pins 8-13 / PCINT0-7)
PCICR |= (1 << PCIE0);
// 2. Enable specific pin mask for Pin 10 (PCINT2)
PCMSK0 |= (1 << PCINT2);
sei(); // Enable global interrupts
}
// Interrupt Service Routine (ISR) for Pin Change Interrupt 0
ISR(PCINT0_vect) {
// Check current state of Echo Pin (Port B, Bit 2 for Pin 10)
// Using direct port register reading is faster than digitalRead()
if (PINB & (1 << PB2)) {