This post contains affiliate links, marked with *. As an Amazon Associate I earn from qualifying purchases, at no extra cost to you. It helps keep the tutorials free.
📋 Quick Summary
An HC-SR04 ultrasonic sensor mounted on an SG90 servo becomes a simple radar. The servo sweeps from 15 to 165 degrees. At every degree the Arduino Uno measures the distance and sends angle and distance to your computer over the serial connection (the data link through the USB cable). An optional Processing sketch draws the readings as a radar screen. Processing is a free program for drawing graphics, and a sketch is the name for a program in both Processing and the Arduino IDE (the program you write and upload Arduino code with). A buzzer can be added as an optional distance alarm.
How the Radar Works
The radar has three core parts (servo, ultrasonic sensor, Arduino Uno) and two optional add-ons (Processing radar screen, buzzer).
The SG90 servo motor rotates the HC-SR04 back and forth. If you have not worked with servos before, my Arduino servo motor control guide covers wiring and control.
At each degree, the HC-SR04 ultrasonic sensor fires a sound pulse and listens for the echo to find the distance to the nearest object. For a closer look at the sensor, see my Arduino ultrasonic distance sensor tutorial.
The Arduino Uno tells the servo where to point, triggers the sensor, calculates the distance, and sends angle and distance over serial to your computer. If you are new to Arduino programming, my Arduino programming basics guide covers setup(), loop(), variables and the Serial Monitor (the window in the Arduino IDE that shows the text the board sends).
A Processing sketch on the computer can draw the data as a radar screen. It is optional: the Serial Monitor shows the same readings as text. A piezo buzzer (a small buzzer whose ceramic disc vibrates to make sound) can sound an alert when an object comes within a set distance.
The Principle of Ultrasonic Sensing
An ultrasonic sensor sends out a high-frequency sound pulse at 40 kHz (well above human hearing) and listens for the echo that bounces back from nearby objects. The time between the pulse and the echo tells you how far away the object is. Bats use the same principle to navigate in the dark. Engineers call it the time-of-flight method.

How the HC-SR04 Measurement Cycle Works
The HC-SR04 has two cylindrical transducers on the front (a transducer converts an electrical signal into sound, or sound into an electrical signal). One is the transmitter, which sends the pulse, and the other is the receiver, which picks up the echo. A single measurement cycle goes through four stages.
Transmission: The Arduino sets the TRIG pin HIGH (5 V) for 10 microseconds. In response, the HC-SR04 fires a burst of eight 40 kHz sound waves that spread outward in a cone about 15 degrees wide.
Reflection: When the sound wave hits an object, it bounces back toward the sensor.
Reception: The receiver transducer picks up the returning echo, and the HC-SR04 holds its ECHO pin HIGH for a duration equal to the round-trip travel time of the sound wave.
Distance calculation: The Arduino reads the ECHO pin duration with pulseIn() and applies the formula Distance = (Time × Speed of Sound) / 2. Sound travels at roughly 0.034 cm per microsecond (340 m/s). You divide by 2 because the sound goes to the object and back, covering the distance twice. In code: distance = duration * 0.034 / 2. My Arduino ultrasonic distance sensor tutorial uses the more exact 0.0343 cm per microsecond (343 m/s at 20 °C). The difference is under 1 %, about 2 mm at 25 cm.
Why the Servo Creates a Radar Sweep
A stationary ultrasonic sensor measures distance in one direction only. Mounted on a servo that sweeps back and forth, it delivers a distance reading at every angle. The Arduino pairs each measurement with the servo angle and sends both over serial. Plotted together, these angle-distance pairs give a top-down, radar-style view of whatever is in front of the sensor.

Components and Bill of Materials
| Component | Purpose | Required? | Notes |
|---|---|---|---|
| Arduino Uno | Main microcontroller board | Yes | Any Uno-compatible board works |
| HC-SR04 ultrasonic sensor | Measures distance with ultrasonic pulses | Yes | Range 2 cm to 400 cm (datasheet); readings past 200 to 300 cm get less reliable |
| SG90 servo motor | Sweeps the sensor across a 150-degree arc | Yes | Standard micro servo |
| Breadboard | Solderless prototyping board | Yes | Half-size or full-size |
| Jumper wires (male-male and male-female) | Connecting all components | Yes | At least 10 wires |
| USB cable (Type A to B) | Uploads the code and carries the serial data | Yes | Standard Arduino USB cable |
| Piezo buzzer | Audible alert for close objects | Optional | Passive buzzer recommended, such as the KY-006 module in the wiring diagram (it plays the 2000 Hz tone from the code; an active buzzer has its own fixed pitch) |
| 3D printer and filament (PLA or ABS, two common 3D printing plastics) | Custom sensor and servo mounts | Optional | Tape or hot glue also works |
| Computer with Arduino IDE | Writing and uploading the Arduino code | Yes | Download Arduino IDE |
| Computer with Processing | Radar screen on the computer | Optional | Free download at processing.org |
Designing and 3D Printing Custom Mounts (Optional)
A printed sensor bracket holds the HC-SR04 straighter and steadier than tape, and the build looks cleaner. Without a 3D printer, use tape, hot glue or zip ties to hold the sensor on the servo horn (the plastic arm on the servo shaft). I designed three printed parts: the sensor bracket, the servo base and a flat 70 × 70 mm base plate. In the video I fixed the servo base onto a wooden board and made the holes in the board myself. Any CAD program (computer-aided design software such as Fusion 360 or Tinkercad) works for your own version.
Sensor bracket: Design it to hold the HC-SR04 snugly. Leave the front open so that nothing blocks the sound path of either transducer. The back needs a slot or screw hole to attach it to the servo horn.
Servo base: Design it to hold the servo body firmly. Make it wide and flat enough to sit on a table or bolt to a platform, and keep the servo horn accessible at the top so the sensor can rotate freely. For 3D printing tips and design ideas, see my introduction to 3D printing and 3D design.
The STL files (the standard file format for 3D printing) for the sensor bracket, the servo base and the base plate are in the OmArTronics shop.


Assembling the Hardware
Step 1: Mount the sensor. Secure the HC-SR04 in the printed sensor bracket, or tape it to the servo horn. Check that nothing covers either transducer.
Step 2: Mount the servo. Place the servo in the printed servo base. Without a printed servo base, fix the servo body to the table with adhesive putty or double-sided tape.
Step 3: Attach the sensor to the servo. Connect the sensor bracket to the servo horn with the small screw included with the servo, or use a snap-fit design (parts that click together without screws). Check that the sensor rotates smoothly when the servo moves.
Step 4: Connect the wiring. Follow the tables in the next section and connect everything to the Arduino through the breadboard with jumper wires.
Wiring Diagram and Pin Connections
Wire the core build with the first table; the buzzer has its own table below. In the tables, VCC is the power input of a part and GND is ground (0 V).
Core Radar Wiring
| Component | Component pin | Arduino pin | Notes |
|---|---|---|---|
| SG90 servo | Signal (Orange/Yellow) | Pin 8 | Control signal (the Servo library, ready-made servo code that ships with the Arduino IDE, can drive a servo from any digital pin) |
| SG90 servo | VCC (Red) | 5 V | Servo power |
| SG90 servo | GND (Brown/Black) | GND | Common ground |
| HC-SR04 | VCC | 5 V | Sensor power |
| HC-SR04 | GND | GND | Common ground |
| HC-SR04 | TRIG | Pin 9 | Trigger pulse output |
| HC-SR04 | ECHO | Pin 10 | Echo pulse input |
Note: an SG90 draws about 100 mA when it moves freely and up to about 650 mA when it stalls (is blocked and cannot turn). A USB port supplies 500 mA. Here the servo only turns the sensor bracket, so for a desk test it can run from the Arduino 5 V pin. In my Arduino password door lock the servo pushes a latch that can block it, so there it always gets its own supply. If the Arduino resets while the servo moves, power the servo from an external 5 V supply rated 1 A or more and connect that supply’s GND to the Arduino GND.
Optional Buzzer Wiring
| Component | Component pin | Arduino pin | Notes |
|---|---|---|---|
| Piezo buzzer | Positive (+), or S on a KY-006 module | Pin 12 | Signal wire for the tone output |
| Piezo buzzer | Negative (−) | GND | Common ground |
Important: All GND connections (servo, sensor, buzzer) must go to the same Arduino GND. Separate ground paths cause erratic sensor readings and servo jitter (small uncontrolled twitches). Use the breadboard power rails (the long + and − strips along the edge of the breadboard) to distribute 5 V and GND.


The red notes correct both diagrams: the servo’s red wire belongs on 5 V, not on the 9 V battery. The SG90 is rated for 4.8 to 6 V, and 9 V overheats and damages it. The 9 V battery itself stays on VIN, the Arduino input for an external supply, which accepts 7 to 12 V. The second note marks the KY-006 middle pin: the module only needs S and −, so leave the middle pin unconnected.
Arduino Radar Code: Core Sketch
The sketch below is the core of the radar. It sweeps the servo through its arc and back, measures the distance at each degree and sends every reading to the computer over serial.
/**
* Arduino Ultrasonic Radar — Core Sketch
* Author: Omar Draidrya
* Date: 2024/05/09
* This code controls a servo motor and measures distance
* using an HC-SR04 ultrasonic sensor.
*/
#include <Servo.h>
Servo myServo;
const int trigPin = 9;
const int echoPin = 10;
long duration;
int distance;
void setup() {
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
Serial.begin(9600);
myServo.attach(8);
}
void loop() {
// Forward sweep: 15 to 165 degrees
for (int i = 15; i <= 165; i++) {
myServo.write(i);
delay(30);
distance = calculateDistance();
Serial.print(i);
Serial.print(",");
Serial.print(distance);
Serial.print(".");
}
// Return sweep: 165 back to 15 degrees
for (int i = 165; i > 15; i--) {
myServo.write(i);
delay(30);
distance = calculateDistance();
Serial.print(i);
Serial.print(",");
Serial.print(distance);
Serial.print(".");
}
}
int calculateDistance() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration * 0.034 / 2;
return distance;
}
How the Code Works
Servo sweep logic: Two for loops (a for loop repeats a block of code a set number of times) handle the sweep. One goes from 15 to 165 degrees, the other returns from 165 back to 15. The range is 15 to 165 instead of 0 to 180 because many cheap servos do not reach the full 0 to 180 degrees and buzz or jitter at their mechanical end stops. The 30 ms delay between each degree gives the servo time to settle before the next measurement.
Distance measurement with pulseIn(): The calculateDistance() function triggers the sensor and calls pulseIn(echoPin, HIGH). That function waits (the sketch does nothing else meanwhile) until the ECHO pin goes HIGH and returns how long it stayed HIGH in microseconds: the round-trip travel time of the sound. The duration is converted to centimeters with the formula from the measurement cycle above.
Serial Output and Testing
Serial output format: Each reading is sent as angle,distance. For example, 90,25. means the sensor is at 90 degrees and sees an object at 25 cm. The period is the delimiter (the character that marks where one reading ends and the next starts). Units such as “cm” or a different delimiter break the parsing in Processing, so change the Arduino output and the Processing parser (the code that splits the text into angle and distance) together.
Testing with the Serial Monitor: Before setting up Processing, upload the sketch and open the Serial Monitor (Tools > Serial Monitor) at 9600 baud. Baud is the transfer speed: Serial.begin(9600) sets it in the sketch, and the Serial Monitor must use the same value. You should see a continuous stream of angle-distance pairs like 15,40.16,38.17,35. scrolling across the screen. If you only see zeros or very large numbers, check the wiring and the Troubleshooting section below.
Optional Upgrade: Adding a Buzzer for Obstacle Alerts
Once the core radar works, you can add a piezo buzzer that sounds when an object comes too close, like a parking sensor for your desk.
/**
* Arduino Ultrasonic Radar — Buzzer Upgrade
* Author: Omar Draidrya
* Date: 2024/05/09
* Adds a buzzer alert when an obstacle is within threshold distance.
*/
#include <Servo.h>
Servo myServo;
const int trigPin = 9;
const int echoPin = 10;
const int buzzerPin = 12;
const int alertThreshold = 10; // cm — change this value to adjust sensitivity
long duration;
int distance;
void setup() {
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(buzzerPin, OUTPUT);
Serial.begin(9600);
myServo.attach(8);
}
void loop() {
for (int i = 15; i <= 165; i++) {
myServo.write(i);
delay(30);
distance = calculateDistance();
Serial.print(i);
Serial.print(",");
Serial.print(distance);
Serial.print(".");
if (distance <= alertThreshold) {
tone(buzzerPin, 2000);
} else {
noTone(buzzerPin);
}
}
for (int i = 165; i > 15; i--) {
myServo.write(i);
delay(30);
distance = calculateDistance();
Serial.print(i);
Serial.print(",");
Serial.print(distance);
Serial.print(".");
if (distance <= alertThreshold) {
tone(buzzerPin, 2000);
} else {
noTone(buzzerPin);
}
}
}
int calculateDistance() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration * 0.034 / 2;
return distance;
}
How the buzzer works: After each distance measurement, the code checks whether the reading is at or below alertThreshold (10 cm by default). If it is, tone(buzzerPin, 2000) drives the buzzer at 2000 Hz; otherwise noTone() switches it off. Change alertThreshold to the distance you want: 20 cm for earlier warnings, or 5 cm if only very close objects should trigger it. In the video I used the default of 10 cm.
Optional Upgrade: Real-Time Radar Visualization with Processing
The Processing sketch below reads the serial data from the Arduino and draws the radar screen in real time.
What the Processing Sketch Does
The Processing code opens the serial connection to the Arduino at 9600 baud and collects incoming characters until it reaches the period delimiter (bufferUntil(‘.’)). It splits each reading at the comma into angle and distance and draws the result. Each reading appears as a line inside arc rings that mark the distance ranges. Blue marks free space up to the detected object, and orange marks the area from the object to the outer ring. Without an object within maxDistance, the whole line stays blue. A text overlay shows the current angle and measured distance.

How to Set Up Processing
Step 1: Download and install Processing from processing.org.
Step 2: Close the Arduino IDE Serial Monitor if it is open. Only one program can use the serial port at a time.
Step 3: Copy the Processing code below into a new sketch and run it. The window is 1920 × 1080 pixels. On a smaller screen, lower size() and radarRadius together, for example size(1280, 720) and radarRadius = 500. The line float x = distance * 20; in drawDetection() converts centimeters to pixels with the scale factor 20 (800 px / 40 cm). After changing radarRadius or maxDistance, write float x = distance * radarRadius / maxDistance; instead.
Step 4: If the radar screen appears but shows no scan line, change the serial port index. In the line String portName = Serial.list()[0];, change the [0] to [1] or [2], depending on which port your Arduino uses (such as COM3 on Windows or /dev/ttyUSB0 on Linux). Print Serial.list() to the Processing console to see all available ports.
Processing Radar Code
/**
* Arduino Radar — Processing Visualization
* Date: 2024/05/09
* Reads serial data from Arduino and draws a radar display.
*/
import processing.serial.*;
Serial communicationPort;
String serialData = "";
float scanAngle = 0;
float scanDistance = 0;
int radarRadius = 800;
int maxDistance = 40; // cm
void setup() {
size(1920, 1080);
smooth();
String portName = Serial.list()[0]; // Adjust index if needed
communicationPort = new Serial(this, portName, 9600);
communicationPort.bufferUntil('.');
background(0);
}
void draw() {
drawRadarBackground();
if (serialData.length() > 0) {
parseSerialData();
drawDetection();
}
displayInfo(scanDistance, scanAngle);
}
void drawRadarBackground() {
pushMatrix();
translate(width / 2, height - 200);
noFill();
stroke(80);
strokeWeight(1);
for (int i = 0; i < 5; i++) {
float r = radarRadius * (i + 1) / 5.0;
arc(0, 0, r * 2, r * 2, PI, TWO_PI);
}
for (int i = 0; i < 180; i += 10) {
float x = radarRadius * cos(radians(i));
float y = radarRadius * sin(radians(i));
line(0, 0, x, -y);
if (i % 30 == 0) {
fill(255);
textSize(16);
text(i + "°", x + 5, -y + 5);
}
}
popMatrix();
}
void parseSerialData() {
String[] tokens = serialData.split(",");
if (tokens.length >= 2) {
scanAngle = float(tokens[0]);
scanDistance = float(tokens[1]);
}
}
void drawDetection() {
float angle = radians(scanAngle);
float distance = scanDistance;
float x = distance * 20;
float fullX = radarRadius * cos(angle);
float fullY = radarRadius * sin(angle);
pushMatrix();
translate(width / 2, height - 200);
strokeWeight(4);
noStroke();
fill(0, 20);
rect(-radarRadius, -radarRadius, radarRadius * 2, radarRadius);
if (distance > 0 && distance <= maxDistance) {
stroke(0, 0, 255);
line(0, 0, x * cos(angle), -x * sin(angle));
stroke(255, 165, 0);
line(x * cos(angle), -x * sin(angle), fullX, -fullY);
} else {
stroke(0, 0, 255);
line(0, 0, fullX, -fullY);
}
popMatrix();
serialData = "";
}
void displayInfo(float distance, float angle) {
fill(0);
noStroke();
rect(10, 10, 260, 80);
fill(255);
textSize(20);
text("Angle: " + nf(angle, 1, 2) + "°", 30, 30);
text("Distance: " + nf(distance, 1, 2) + " cm", 30, 60);
}
void serialEvent(Serial p) {
serialData = p.readStringUntil('.');
serialData = serialData.substring(0, serialData.length() - 1);
}
Calibration, Accuracy, and Limitations
Servo alignment: Most hobby servos are not perfectly calibrated out of the box, so angle 90 may not point straight ahead on your servo. If the radar screen looks rotated or offset, adjust the sensor bracket on the horn or add an offset in code: declare const int angleOffset = 5; and write myServo.write(i + angleOffset);. Use a negative value to correct in the other direction, and keep printing i so the radar screen still shows 15 to 165 degrees.
Ultrasonic cone width: The HC-SR04 emits sound in a cone roughly 15 degrees wide, so it cannot tell two objects apart if they sit close together within that cone. The sensor may also pick up the edge of a large object that is not directly ahead.
Reflective and soft surfaces: Hard flat surfaces like walls and boxes produce strong echoes. Soft fabrics, angled surfaces, and thin objects like chair legs tend to absorb or deflect the pulse. Then no echo reaches the receiver, the reading drops out, and the object does not appear on the radar screen.
Range, Noise, and Sweep Speed
Practical maximum range: The HC-SR04 datasheet says 400 cm, but readings past 200 to 300 cm get less reliable, because the echoes get weaker. The Processing sketch defaults to maxDistance = 40 cm, which suits tabletop demos. To show a wider range, raise maxDistance and update the scale factor as described in setup step 3. If you only raise maxDistance, objects beyond 40 cm are drawn outside the outer ring.
Noisy or jumping readings: Occasional spikes are normal. They come from echoes that bounce off several surfaces before returning (multipath reflections), from electrical noise, and from late echoes of the previous pulse. A simple fix is a median filter: take three readings at each angle and use the middle value, so a single spike is ignored. The sketches in this post send every raw reading without a filter.
Sweep speed versus update rate: The 30 ms delay per degree means a full forward sweep takes at least 4.5 seconds (151 steps, one per degree, 30 ms each). Each measurement adds a few milliseconds, more when no echo returns. If you shorten the delay, the sweep gets faster, but the servo has less time to settle and readings get noisier. If you raise it, measurements stabilize but the radar refreshes more slowly.
Troubleshooting Common Issues
If something is off, work through this checklist before swapping any hardware.
Servo not moving: Check that the signal wire is on pin 8 and that VCC and GND go to the Arduino 5 V and GND. Try the Sweep example sketch from the Arduino Servo library to find out whether the problem is the servo itself or the radar code.
HC-SR04 always reads 0 or about 650 cm: 0 means the ECHO pin never went HIGH, which points to wiring or power. About 650 cm means no echo came back: most modules then hold ECHO HIGH for about 38 ms. Verify that TRIG is on pin 9 and ECHO on pin 10, and that the sensor runs on 5 V, not 3.3 V. Check that the sensor bracket does not block the transducers. Point the sensor at a flat wall about 20 cm away and watch the Serial Monitor.
Noisy or jumping distance values: Check that all grounds share one rail and keep the sensor wires short, since long jumper wires pick up electrical noise. If readings still jump, add a 10 µF electrolytic capacitor (rated 10 V or more) between the sensor’s VCC and GND pins. An electrolytic capacitor is polarized: the longer leg goes to VCC, the side marked with a stripe to GND.
Buzzer always on or never triggers: Check that the buzzer is wired to pin 12 and that alertThreshold in the code has the value you want. If the sensor reads wrong distances (see above), the buzzer logic misbehaves too. Fix the sensor first.
Processing and Serial Troubleshooting
Processing not connecting or blank screen: Close the Arduino Serial Monitor and check the port index (setup steps 2 and 4).
Serial format mismatch: If you change the Serial.print() lines in the Arduino sketch (for example by adding units or a different delimiter), change the parser in the Processing sketch the same way.
Power issues or erratic behavior when the servo moves: On USB power alone, the servo’s stall current can cause voltage drops that reset the Arduino or corrupt the sensor readings. Power the servo from a separate 5 V supply with a common ground to the Arduino (see the note under the wiring table). In the video, I powered the whole circuit from an external 5 V supply.
Frequently Asked Questions
Yes. The HC-SR04 is the most common choice because it is cheap and widely available. Sensors like the US-015 or JSN-SR04T also work if they use the same TRIG/ECHO interface. If the replacement uses a different protocol (such as I2C or UART, which send the distance as digital data instead of a pulse length), you need to modify the Arduino code.
Yes. The code runs unchanged on the Arduino Nano and Mega, which use the same 5 V logic as the Uno (a HIGH signal is 5 V). Check that the pin numbers match your wiring and that the board provides 5 V for the sensor and servo.
Related Resources
- Arduino Ultrasonic Distance Sensor: HC-SR04 Tutorial with Wiring and Code: the sensor on its own, including common pitfalls.
- Arduino Servo Control Guide: SG90, PWM, and PCA9685: also covers the PCA9685 driver (an add-on board that controls several servos at once).
- Building a Line Following Robot with KY-033 Sensors: a beginner robot project to try after the radar.
- DIY 6-DOF Robotic Arm: 3D Print, Wire, and Program: a multi-servo project (6-DOF means six degrees of freedom, six independently moving joints).
Next Step
The obstacle-avoiding robot car puts the same HC-SR04 and servo on a moving platform that navigates on its own.
