The HC-SR04 measures distance by sending out a short burst of 40 kHz sound and timing the echo. It reads from 2 to 400 cm and needs no library, just two pins and one line of maths. It wires straight to a 5V Arduino. On a 3.3V ESP32 the Echo pin needs two resistors or a level converter first. This guide covers both boards, working code, what upsets the readings and two projects to build next.
In this guide
How the HC-SR04 measures distance
The silver can marked T is the speaker and the one marked R is the microphone. You send a 10 microsecond HIGH pulse to Trig. The module then fires eight cycles of 40 kHz sound and holds Echo HIGH until the echo comes back. The length of that Echo pulse is the time the sound took to reach the target and return.
Sound covers 1 cm and back in about 58 microseconds, so the maths is one line: distance in cm = Echo time in microseconds / 58. A wall 1 m away gives an Echo pulse of about 5,800 microseconds. If nothing comes back, the module gives up after about 38 ms.
| Pin | What it does | Connect to |
|---|---|---|
| VCC | 5V supply, about 15 mA while measuring | 5V on an Arduino. VIN or 5V on an ESP32 board running from USB |
| Trig | Starts a measurement on a HIGH pulse of at least 10 microseconds | Any digital output. 3.3V outputs trigger it too |
| Echo | Goes HIGH at 5V for the round trip time | A digital input. On 3.3V boards, through a divider or level converter |
| GND | Ground | GND on your board |
With the two silver cans facing you and the pins pointing down, the order is VCC, Trig, Echo, GND from left to right. The names are printed on the front and the back of the board.
Wiring to a 5V Arduino
A Nano, Uno or Mega runs at 5V, the same as the sensor, so four jumper wires are all you need. Female-to-male Dupont wires push onto the sensor's pins and plug into a breadboard or the board's headers.

| HC-SR04 | Arduino Nano or Uno |
|---|---|
| VCC | 5V |
| Trig | D9 |
| Echo | D10 |
| GND | GND |
D9 and D10 are only a starting point. Any two digital pins work if you change the pin numbers in the sketch. New to breadboards? Our breadboard basics guide shows how the rows connect.
First test sketch
This sketch needs no library. It fires one ping every 100 ms and prints the distance in centimetres.
// Uno / Nano: Trig to D9, Echo to D10.
const int trigPin = 9, echoPin = 10;
void setup() {
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH); // fire
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// give up after 30 ms, not 1 s
long us = pulseIn(echoPin, HIGH, 30000);
if (us == 0 || us > 23500) {
Serial.println("out of range");
} else {
Serial.print(us / 58.0, 1);
Serial.println(" cm");
}
delay(100); // 60 ms min
}
Open Serial Monitor at 9600 baud and point the sensor at a wall about 1 m away. You should see numbers close to 100 cm that change by only a few millimetres. Move your hand in front of it and the number follows your hand.
Always give pulseIn a timeout. Without the third number, pulseIn() waits up to one second for an echo that never comes, and your whole sketch freezes for that second. With 30,000 microseconds it gives up after 30 ms and returns 0. Treat 0 as "no echo", never as 0 cm. The 23,500 microsecond check is 400 cm, the end of the sensor's range.
Using it with an ESP32
ESP32 pins run at 3.3V and are not 5V tolerant. The HC-SR04 still needs 5V to measure properly, and its Echo pin answers at that same 5V. Wired straight to a GPIO, Echo pushes 5V into a 3.3V pin on every ping. It may even seem to work for a while, but that is outside the chip's limits. Trig is the easy one: a 3.3V output is enough to start a measurement, so it connects directly.
Power the sensor from the 5V side of the board. On our 30-pin ESP32 board that is the VIN pin, which carries about 5V when the board runs from USB. Then pick one of these two ways to bring Echo down to 3.3V.
| Divider | Converter | |
|---|---|---|
| Parts | One 1k and one 2k resistor | One 4-channel BSS138 board |
| Echo at the ESP32 | About 3.3V | 3.3V, set by the LV supply |
| Best for | One sensor on a breadboard | Neat builds, several sensors, or 5V modules beside the sensor |
| Extra wiring | Two resistors on the breadboard | 5V, 3.3V and GND to the converter |
Fix 1: a resistor divider
Run Echo through a 1k resistor to your input pin, then fit a 2k resistor from that same pin to GND. The divider passes two thirds of the voltage: 5V x 2k / (1k + 2k) = 3.33V. That sits at the top of a 3.3V pin's range, which is what you want for a clean HIGH.

- Other values work if they keep the same 1 to 2 ratio, for example 10k and 20k. Our 600-piece resistor kit includes 1k, 2k, 10k and 20k.
- Do not use 1k and 1k. Equal resistors halve the signal to 2.5V, which some boards read as an unreliable HIGH.
- Check the order. The 1k sits between Echo and the pin. The 2k goes from the pin to GND. Swapped, the pin sees only 1.67V.
Not sure which resistor is which? Our resistor guide covers the colour bands.
Fix 2: a logic level converter
A 4-channel logic level converter does the same job with a proper 5V side and 3.3V side, and has three spare channels for other 5V parts.

| Converter pin | Connect to |
|---|---|
| HV | 5V: the ESP32's VIN pin, the same wire as the sensor's VCC |
| LV | ESP32 3V3 |
| GND (both sides) | ESP32 GND and the sensor's GND |
| HV1 | Sensor Echo |
| LV1 | ESP32 D18 |
Both HV and LV must be powered or no signal gets through. The converter has 10k pull-ups on each channel, which is plenty fast for an Echo pulse that lasts at least 116 microseconds.
ESP32 sketch with temperature correction
// 30-pin ESP32: Trig to D19, Echo to D18 through a divider or converter.
const int trigPin = 19;
const int echoPin = 18;
float airTempC = 15.0; // set this to the air temperature where it runs
float readCm() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
unsigned long us = pulseIn(echoPin, HIGH, 30000);
if (us == 0) return -1; // no echo
float speed = 331.3 + 0.606 * airTempC; // metres per second
return us * speed / 20000.0; // there and back, in cm
}
void setup() {
Serial.begin(115200);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
float cm = readCm();
if (cm < 0) {
Serial.println("no echo");
} else {
Serial.printf("%.1f cm\n", cm);
}
delay(100); // keep at least 60 ms between pings
}
Open Serial Monitor at 115200 baud. The result should match the Arduino test: a steady reading that follows your hand. First time with this board? Our ESP32 getting-started guide covers the board package and the upload.
Other ESP32 boards. The wiring is the same on any ESP32: VCC to the 5V or VIN pin, and Echo through the divider or converter. On an ESP32-C3 SuperMini, keep Trig and Echo off GPIO2, GPIO8 and GPIO9, which the board reads at power-up. Our C3 SuperMini setup guide has the rest.
Home Assistant with ESPHome
ESPHome has a built-in ultrasonic platform, so a tank or bin sensor needs only this in your device's YAML. The divider or converter on Echo is still needed.
sensor:
- platform: ultrasonic
trigger_pin: GPIO19
echo_pin: GPIO18
name: "Tank distance"
update_interval: 5s
timeout: 3m
ESPHome reports the distance in metres. The default timeout is 2 m, so raise it for anything deeper. Setting it far past the real distance lets missed echoes show up as false long readings.
Speed of sound and temperature
The divide-by-58 rule assumes sound travels at about 345 m/s, which is true at roughly 23°C. Sound slows down in cold air by about 0.6 m/s for every degree. In a garage in a Canterbury winter, the sensor reads long.
| Air | Sound speed | 200 cm reads |
|---|---|---|
| 0°C | 331.3 m/s | 208 cm |
| 10°C | 337.4 m/s | 204 cm |
| 20°C | 343.4 m/s | 201 cm |
| 30°C | 349.5 m/s | 197 cm |
The last column is what the divide-by-58 sketch prints when the target is really 200 cm away. For a hand-wave detector this does not matter. For a tank gauge or a measuring tool, use the ESP32 sketch above and set airTempC. With a temperature sensor such as a DS18B20 on the same board, you can update it live. Our DS18B20 guide shows the wiring.
Range, beam and what it can see
The 2 to 400 cm figure is for a large, flat, hard target facing the sensor square on. The beam spreads in a cone of about 15 degrees, and the sensor reports the first echo that comes back from anything inside it. A real room is less tidy than that.
| Target | What you will see |
|---|---|
| Wall, door or cardboard box, square on | Full range and steady readings |
| Flat surface at a steep angle | Often no echo at all. The sound bounces away like light off a mirror |
| Person, clothing, curtains, foam, a cat | Soft surfaces soak up sound. Expect a shorter range and jumpy numbers |
| Thin pole, chair legs, wire fence | Easy to miss, or the wall behind is seen instead |
| Still water, from above | Reflects well when the sensor points straight down |
| Anything closer than 2 cm | No reading |
- Mount it clear of edges. A box edge or a robot chassis just inside the cone gives a constant short reading.
- Aim at the flat part of the target. On a car, that is the number plate, not the curved bumper corner.
- Average a few pings when the target is soft or moving. Taking the middle of five readings throws out the odd stray value.
Known issues with HC-SR04 modules
The HC-SR04 has been made by many factories for over a decade, and buyers run into the same handful of quirks. Here is what to expect and what to do about each.
Readings of 0, or "out of range"
With a timeout set, 0 means no echo came back in time. The cause is nearly always the target: too far, too soft, at a steep angle, or closer than 2 cm. The rest of the time it is wiring: Echo on a different pin from the one in the sketch, or Trig and Echo swapped. Point the sensor at a wall 50 cm away to tell the two apart.
Stuck on one value, or stuck on no echo
Some modules hold Echo HIGH when no echo returns, for example when pointed at open sky or into foam. They then ignore new Trig pulses until the power is cycled. The sketch shows the same number or "no echo" over and over, even with a wall in front. Unplug the sensor's VCC wire for a second and plug it back in. If one sensor does this repeatedly with a clear target in front of it, message us.
Ghost short readings
Leave at least 60 ms between pings. Fire faster and the late echo from the previous ping arrives during the next one, which shows up as a false short distance. With two or more sensors on one project, fire them one at a time, 60 ms apart, so one sensor never hears the other's ping.
3.3V versions exist
Versions sold as HC-SR04P, or marked 3V to 5.5V, run from 3.3V and look almost identical. Ours is the classic 5V module. Run it from 5V: on 3.3V it gives short, noisy or missing readings. If you have a sensor from somewhere else, check its listing or markings before you power it from 3V3.
Readings jump when motors run
Motors and servos on the same 5V supply cause dips and noise that upset the sensor's receiver. Give motors their own supply, join the grounds and keep the sensor wires short. If the whole project runs from a 12V battery or plug pack, our guide to powering an Arduino or ESP32 from 12V shows how to step it down to a clean 5V.
Projects: parking sensor and tank level
Garage parking sensor
Mount the sensor on the garage wall at number plate height, facing the car. Three LEDs show how close the car is: green above 100 cm, amber from 40 to 100 cm and red under 40 cm. Each LED needs its own 220Ω resistor to GND. Our LED wiring guide shows which leg goes where. Measure where you want the car to stop and change the numbers to suit.
// Nano: sensor on D9/D10 as above. LEDs on D3 (green), D4 (amber), D5 (red).
const int trigPin = 9, echoPin = 10;
const int green = 3, amber = 4, red = 5;
void setup() {
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(green, OUTPUT);
pinMode(amber, OUTPUT);
pinMode(red, OUTPUT);
}
void loop() {
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long us = pulseIn(echoPin, HIGH, 30000);
float cm = us / 58.0;
bool seen = (us > 0);
digitalWrite(green, seen && cm > 100);
digitalWrite(amber, seen && cm > 40 && cm <= 100);
digitalWrite(red, seen && cm <= 40);
delay(100);
}
Expected result: with nothing in front, all three LEDs stay off. Walk towards the sensor with a flat board and they change from green to amber to red.
Tank or bin level
Mount the sensor above the contents, pointing straight down. The distance it reads is the empty space, so the level is what is left:
// Measure these two distances once, in cm, from the sensor.
const float emptyCm = 180.0; // sensor to the bottom of an empty tank
const float fullCm = 30.0; // sensor to the surface when full
float levelPercent(float cm) {
float p = (emptyCm - cm) * 100.0 / (emptyCm - fullCm);
return constrain(p, 0, 100);
}
Multiply the percentage by the tank's capacity for litres. Set fullCm to at least 25 cm so the full level stays well clear of the sensor.
Keep it dry. The HC-SR04's board and transducers are open to the air. Inside a rainwater tank, condensation soon settles on them, and a damp sensor gives wrong readings or fails. It suits a dry grain bin, a hopper, a covered trough or a bench test with a bucket. For a sealed water tank, use a waterproof ultrasonic sensor with a sealed transducer instead. Those cannot read closer than about 20 to 25 cm, so mount one high enough to stay clear of the full level.
Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
| Always 0 or "out of range" | No echo returns. Check the Trig and Echo pin numbers, then test against a wall 50 cm away. |
| Sketch pauses for a second at a time |
pulseIn() has no timeout. Add 30000 as the third value. |
| Same number every time, even when you move it | The module's Echo is stuck. Unplug VCC for a second. Message us if it keeps happening. |
| Odd short readings now and then | Pings are too close together, or another sensor is pinging. Leave 60 ms between pings and fire sensors in turn. |
| Readings jump around by several centimetres | The target is soft, small or angled. Aim at a flat surface and take the middle of five readings. |
| Readings are a few percent long in winter | Cold air slows sound. Set airTempC in the ESP32 sketch. |
| Works on Arduino, nothing on ESP32 | VCC is on 3V3, or the divider resistors are swapped. Power VCC from VIN or 5V and check that the 1k is next to Echo. |
| ESP32 reads nothing through the converter | HV or LV is not powered. Both need a supply, and both GNDs go to ground. |
| Constant short reading, nothing in front | The beam is catching the mounting edge or a nearby wire. Move the sensor forward of the edge. |
Frequently asked questions
Can I run the HC-SR04 from a 3.3V pin?
Not the classic 5V module we sell. It measures badly or not at all on 3.3V. Power it from 5V and bring only the Echo signal down to 3.3V.
Do I need a library?
No. The sketches here use pulseIn(), which is built in. The NewPing library in the Arduino Library Manager tidies up timeouts and averaging, and is worth a look once you have several sensors.
How accurate is it?
The module resolves about 3 mm. How close it gets to the true distance depends on the target and the air. Set the temperature, aim at a flat surface square on, then check a few readings against a tape measure. Soft or angled targets are less accurate.
How many readings per second can it take?
Up to about 16, with the 60 ms gap. Ten a second suits most projects and gives each echo time to fade.
Parts used in this guide
HC-SR04 ultrasonic distance sensor$3.49View sensor
Nano V3 USB-C, Soldered or UnsolderedFrom $5.99View board
ESP32-WROOM-32 30-pin board, USB-C$11.99View board
4-channel logic level converterFrom $2.59View converter
600-piece resistor kit, includes 1k and 2k$8.99View kit
400-point breadboard$3.99View breadboard
Dupont jumper wires, male to female, 40 pack$3.89View wires
Still stuck?
Message us with your board, your wiring and what the Serial Monitor shows, and we'll help you.
