Project 8 : Ultrasonic Radar using Servo and ESP32
Share
ESP32 Radar Project - Complete Beginner Tutorial
Overview
This project creates a radar system using an ESP32 microcontroller, ultrasonic sensor, servo motor, and a visual display on your computer. The radar sweeps in a 180-degree arc, detecting objects and displaying them on a colorful radar screen - just like in movies!
What You'll Learn
- How to use ultrasonic sensors for distance measurement
- Servo motor control with ESP32
- Serial communication between ESP32 and computer
- Visual programming with Processing IDE
- Real-time data visualization
Components Needed
Hardware:
- ESP32 Development Board - The brain of the project
- HC-SR04 Ultrasonic Sensor - For distance measurement (like a bat's echolocation!)
- SG90 Servo Motor - To rotate the sensor
- Breadboard - For connecting components
- Jumper Wires - Male-to-male and male-to-female
- USB Cable - To connect ESP32 to computer
- Power Supply - 5V (optional, USB can power everything)
Software:
- Arduino IDE - To program the ESP32
- Processing IDE - To create the radar display
- ESP32 Board Package - For Arduino IDE
Part 1: Hardware Setup
Step 1: Wiring Connections
Ultrasonic Sensor (HC-SR04) to ESP32:
HC-SR04 → ESP32
VCC → 3.3V
GND → GND
TRIGGER → GPIO 5
ECHO → GPIO 18
Servo Motor to ESP32:
Servo → ESP32
Red Wire → 5V
Brown Wire → GND
Orange Wire→ GPIO 13
Step 2: Physical Assembly
- Mount the ultrasonic sensor on the servo horn (the plastic piece that comes with servo)
- Secure with hot glue or double-sided tape
- Connect all wires according to the wiring diagram above
- Place on breadboard for stability
Wiring Diagram Description:
Part 2: Software Setup
Step 1: Install Arduino IDE
Step 2: Install Processing IDE
- Download from processing.org
- Install and run it once to set up
Step 3: Install Required Libraries
In Arduino IDE:
- Go to Tools → Manage Libraries
- Search and install: "ESP32Servo"
Part 3: Programming the ESP32
Step 1: Understanding the ESP32 Code
The ESP32 code does three main things:
- Controls the servo - Makes it sweep back and forth
- Reads the ultrasonic sensor - Measures distances
- Sends data to computer - Via serial communication
Step 2: Upload the ESP32 Code
Arduino Ide Code :
-------------------------------------------------------------------------------
#include <ESP32Servo.h>
// Pin definitions
#define TRIGGER_PIN 5 // GPIO pin connected to ultrasonic trigger
#define ECHO_PIN 18 // GPIO pin connected to ultrasonic echo
#define SERVO_PIN 13 // GPIO pin connected to servo signal
// Constants
#define SERVO_DELAY 50 // Delay between servo movements (ms)
#define SCAN_ANGLE_MIN 15 // Minimum sweep angle
#define SCAN_ANGLE_MAX 165 // Maximum sweep angle
#define SCAN_STEP 1 // Degrees to move each step
#define MAX_DISTANCE 200 // Maximum distance to measure (cm)
Servo radarServo;
int currentAngle = SCAN_ANGLE_MIN;
bool scanningForward = true;
unsigned long lastMeasurement = 0;
const unsigned long measurementInterval = 100; // ms between measurements
void setup() {
// Initialize serial communication
Serial.begin(115200);
// Configure ultrasonic sensor pins
pinMode(TRIGGER_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// ESP32 specific - allow allocation of timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
// Initialize servo with ESP32 servo settings
radarServo.setPeriodHertz(50); // Standard 50hz servo
radarServo.attach(SERVO_PIN, 500, 2400); // Default min/max pulse width values
radarServo.write(SCAN_ANGLE_MIN);
delay(2000); // Give time for the servo to reach starting position
Serial.println("ESP32 Radar Starting...");
}
void loop() {
// Move the servo
radarServo.write(currentAngle);
delay(SERVO_DELAY);
// Measure distance at appropriate intervals
unsigned long currentTime = millis();
if (currentTime - lastMeasurement >= measurementInterval) {
lastMeasurement = currentTime;
// Measure distance
int distance = measureDistance();
// Send data to Processing in format: angle,distance
Serial.print(currentAngle);
Serial.print(",");
Serial.println(distance);
}
// Update angle for next measurement
if (scanningForward) {
currentAngle += SCAN_STEP;
if (currentAngle >= SCAN_ANGLE_MAX) {
scanningForward = false;
}
} else {
currentAngle -= SCAN_STEP;
if (currentAngle <= SCAN_ANGLE_MIN) {
scanningForward = true;
}
}
}
int measureDistance() {
// Clear the trigger pin
digitalWrite(TRIGGER_PIN, LOW);
delayMicroseconds(2);
// Set the trigger pin high for 10 microseconds
digitalWrite(TRIGGER_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER_PIN, LOW);
// Read the echo pin, return pulse duration in microseconds
long duration = pulseIn(ECHO_PIN, HIGH, 30000); // Timeout after 30ms
// Calculate the distance
int distance = duration * 0.034 / 2; // Speed of sound wave divided by 2
// Constrain the distance to maximum range
if (distance > MAX_DISTANCE || distance <= 0) {
distance = MAX_DISTANCE;
}
return distance;
}
----------------------------------------------------------------------------------
Processing Code:
----------------------------------------------------------------------------------
import processing.serial.*;
Serial myPort; // Serial port object
String serialData; // Data received from serial port
boolean connected = false;
int dataCount = 0; // Count received data points for debugging
// Display configuration - made larger for kids
int displaySize = 900;
int radarRadius = 400; // Bigger radar for better visibility
int centerX, centerY;
// Radar settings - optimized for short range
int maxDistance = 100; // Reduced to 100cm (1 meter) for short range
float currentAngle = 15;
float previousAngle = currentAngle;
boolean scanningForward = true;
float[] distances = new float[181];
int[] objectAge = new int[181]; // Track how long objects have been detected
// Servo timing simulation
int servoDelay = 50;
int scanStep = 1;
int scanAngleMin = 15;
int scanAngleMax = 165;
long lastMoveTime = 0;
// Enhanced visual settings for kids
color backgroundColor = color(5, 15, 25);
color gridColor = color(0, 80, 120);
color sweepColor = color(100, 255, 100);
color objectColor = color(255, 80, 80);
color textColor = color(200, 255, 200);
color warningColor = color(255, 255, 0);
color dangerColor = color(255, 50, 50);
// Kid-friendly features
boolean showDebug = false; // Start with debug off for kids
StringList debugLog = new StringList();
int maxLogLines = 5;
boolean forceTestObject = false;
boolean playWarningSound = true;
int warningDistance = 30; // Objects closer than 30cm show warning
// Distance zones for kids
int[] distanceZones = {20, 40, 60, 80, 100};
String[] zoneLabels = {"VERY CLOSE!", "Close", "Medium", "Far", "Very Far"};
color[] zoneColors = {
color(255, 0, 0), // Red - Very close
color(255, 100, 0), // Orange - Close
color(255, 255, 0), // Yellow - Medium
color(0, 255, 0), // Green - Far
color(0, 150, 255) // Blue - Very far
};
// IMPORTANT: Change this to match your ESP32's port
String ESP32_PORT = ""; // Leave blank to force manual selection
void setup() {
size(900, 900);
centerX = width / 2;
centerY = height / 2;
smooth();
// Initialize arrays
for (int i = 0; i < distances.length; i++) {
distances[i] = 0;
objectAge[i] = 0;
}
// List available serial ports
println("Available serial ports:");
String[] portList = Serial.list();
for (int i = 0; i < portList.length; i++) {
println(i + ": " + portList[i]);
}
addDebugMessage("Available ports: " + portList.length);
if (ESP32_PORT.isEmpty()) {
println("Press 0-9 keys to select a serial port");
addDebugMessage("Press 0-9 to select a serial port");
} else {
connectToPort(ESP32_PORT);
}
}
void addDebugMessage(String message) {
println(message);
debugLog.append(message);
if (debugLog.size() > maxLogLines) {
debugLog.remove(0);
}
}
void connectToPort(String portName) {
try {
if (myPort != null) {
myPort.stop();
}
println("Connecting to: " + portName);
addDebugMessage("Connecting to: " + portName);
myPort = new Serial(this, portName, 115200);
myPort.bufferUntil('\n');
connected = true;
println("Connected successfully!");
addDebugMessage("Connected successfully!");
dataCount = 0;
} catch (Exception e) {
println("Failed to connect to: " + portName);
addDebugMessage("Failed: " + portName);
connected = false;
}
}
void draw() {
background(backgroundColor);
// Age objects (fade old detections)
for (int i = 0; i < objectAge.length; i++) {
if (objectAge[i] > 0) {
objectAge[i]--;
if (objectAge[i] == 0) {
distances[i] = 0; // Clear old detections
}
}
}
// Draw title for kids
fill(textColor);
textSize(28);
textAlign(CENTER);
text("RADAR DETECTOR", width/2, 40);
textAlign(LEFT);
// Draw connection status
if (!connected) {
fill(dangerColor);
textSize(20);
textAlign(CENTER);
text("NOT CONNECTED - PRESS 0-9 TO SELECT PORT", width/2, 80);
textAlign(LEFT);
// Simulate movement when not connected
if (millis() - lastMoveTime >= servoDelay) {
updateServoPosition();
lastMoveTime = millis();
}
// Add test objects for demonstration
if (forceTestObject) {
distances[90] = 50; // Test object at 90 degrees, 50cm
distances[60] = 25; // Another test object
objectAge[90] = 30;
objectAge[60] = 30;
}
} else {
fill(color(0, 255, 0));
textSize(18);
textAlign(CENTER);
text("CONNECTED ✓ - Objects detected: " + countObjects(), width/2, 80);
textAlign(LEFT);
}
// Draw the enhanced radar
drawRadarGrid();
drawSweepLine();
drawDetectedObjects();
drawInfoPanel();
// Draw warning if objects are too close
drawWarnings();
// Draw debug panel if enabled
if (showDebug) {
drawDebugPanel();
}
}
void updateServoPosition() {
if (scanningForward) {
currentAngle += scanStep;
if (currentAngle >= scanAngleMax) {
scanningForward = false;
}
} else {
currentAngle -= scanStep;
if (currentAngle <= scanAngleMin) {
scanningForward = true;
}
}
}
void drawRadarGrid() {
// Draw colorful distance zones
noFill();
strokeWeight(2);
// Draw zone circles with colors
for (int i = 0; i < distanceZones.length; i++) {
float radius = map(distanceZones[i], 0, maxDistance, 0, radarRadius);
stroke(zoneColors[i], 100);
arc(centerX, centerY, radius * 2, radius * 2, PI, TWO_PI);
// Draw zone labels
fill(zoneColors[i]);
textSize(14);
text(zoneLabels[i] + " (" + distanceZones[i] + "cm)",
centerX + radius + 10, centerY - 5);
}
// Draw angle lines
stroke(gridColor);
strokeWeight(1);
for (int angle = 0; angle <= 180; angle += 30) {
float rad = radians(angle);
float x = centerX + radarRadius * cos(rad);
float y = centerY - radarRadius * sin(rad);
line(centerX, centerY, x, y);
// Draw bigger angle labels
fill(textColor);
textSize(16);
float labelX = x + 20 * cos(rad);
float labelY = y - 20 * sin(rad);
text(angle + "°", labelX, labelY);
}
// Draw the radar base (bigger)
fill(gridColor);
stroke(gridColor);
strokeWeight(2);
arc(centerX, centerY, 80, 80, PI, TWO_PI);
// Add "RADAR" text in the base
fill(textColor);
textSize(12);
textAlign(CENTER);
text("RADAR", centerX, centerY - 5);
textAlign(LEFT);
}
void drawSweepLine() {
// Draw animated sweep line
stroke(sweepColor);
strokeWeight(3);
// Convert angle correctly for display
float displayAngle = currentAngle; // 0 is up, 90 is right, 180 is down
float rad = radians(displayAngle);
float x = centerX + radarRadius * cos(rad);
float y = centerY - radarRadius * sin(rad);
line(centerX, centerY, x, y);
// Draw glowing effect at the end
noStroke();
fill(sweepColor, 200);
ellipse(x, y, 12, 12);
fill(sweepColor, 100);
ellipse(x, y, 20, 20);
// Draw trail effect
for (int i = 1; i <= 10; i++) {
float trailAngle = scanningForward ?
displayAngle - i * 2 :
displayAngle + i * 2;
if (trailAngle >= 0 && trailAngle <= 180) {
float trailRad = radians(trailAngle);
float trailX = centerX + radarRadius * cos(trailRad);
float trailY = centerY - radarRadius * sin(trailRad);
float alpha = map(i, 1, 10, 100, 10);
stroke(sweepColor, alpha);
strokeWeight(2);
line(centerX, centerY, trailX, trailY);
}
}
}
void drawDetectedObjects() {
noStroke();
for (int angle = 0; angle <= 180; angle++) {
if (distances[angle] > 0 && distances[angle] <= maxDistance) {
float distance = distances[angle];
// Calculate screen position
float displayAngle = angle;
float rad = radians(displayAngle);
// Improved distance mapping - linear and accurate
float screenDistance = map(distance, 0, maxDistance, 0, radarRadius);
float x = centerX + screenDistance * cos(rad);
float y = centerY - screenDistance * sin(rad);
// Determine object color based on distance
color objColor = getObjectColor(distance);
// Draw object with size based on age (newer = bigger)
float objSize = map(objectAge[angle], 0, 30, 8, 20);
// Draw glow effect
fill(objColor, 50);
ellipse(x, y, objSize * 2, objSize * 2);
// Draw main object
fill(objColor);
ellipse(x, y, objSize, objSize);
// Draw distance label for close objects
if (distance <= 50) {
fill(255);
textSize(10);
textAlign(CENTER);
text(int(distance) + "cm", x, y - objSize/2 - 5);
textAlign(LEFT);
}
}
}
}
color getObjectColor(float distance) {
// Return color based on distance zones
for (int i = 0; i < distanceZones.length; i++) {
if (distance <= distanceZones[i]) {
return zoneColors[i];
}
}
return zoneColors[zoneColors.length - 1];
}
void drawInfoPanel() {
// Overlay panel on lower part of radar
int panelWidth = 400;
int panelHeight = 100;
int panelX = centerX - panelWidth / 2;
int panelY = centerY + radarRadius - panelHeight - 20 - 200;
// Draw main info panel
fill(0, 100);
stroke(gridColor);
strokeWeight(2);
rect(panelX, panelY, panelWidth, panelHeight, 15);
// Current angle and direction
fill(textColor);
textSize(18);
text("Scanning: " + int(currentAngle) + "° " +
(scanningForward ? "→" : "←"), panelX + 20, panelY + 30);
// Current distance reading
int angleIndex = int(currentAngle);
if (angleIndex >= 0 && angleIndex < distances.length && distances[angleIndex] > 0) {
float currentDistance = distances[angleIndex];
fill(getObjectColor(currentDistance));
text("Distance: " + int(currentDistance) + "cm", panelX + 20, panelY + 55);
// Status message
String status = getDistanceStatus(currentDistance);
text("Status: " + status, panelX + 20, panelY + 80);
} else {
fill(textColor);
text("Distance: No object detected", panelX + 20, panelY + 55);
text("Status: Clear", panelX + 20, panelY + 80);
}
// Object counter
fill(textColor);
textSize(16);
text("Objects in range: " + countObjects(), panelX + 230, panelY + 30);
}
String getDistanceStatus(float distance) {
if (distance <= 20) return "VERY CLOSE!";
if (distance <= 40) return "Close";
if (distance <= 60) return "Medium distance";
if (distance <= 80) return "Far";
return "Very far";
}
void drawWarnings() {
// Check for very close objects
boolean hasCloseObjects = false;
for (int i = 0; i < distances.length; i++) {
if (distances[i] > 0 && distances[i] <= warningDistance) {
hasCloseObjects = true;
break;
}
}
if (hasCloseObjects) {
// Flash warning
int flashRate = millis() / 300;
if (flashRate % 2 == 0) {
fill(dangerColor, 150);
textSize(32);
textAlign(CENTER);
text("⚠️ VERY CLOSE OBJECT! ⚠️", width/2, height/2 - 100);
textAlign(LEFT);
}
}
}
int countObjects() {
int count = 0;
for (int i = 0; i < distances.length; i++) {
if (distances[i] > 0 && distances[i] <= maxDistance) {
count++;
}
}
return count;
}
void drawDebugPanel() {
// Debug panel for troubleshooting
fill(0, 150);
rect(width - 350, 100, 330, 200, 10);
fill(255, 200, 0);
textSize(14);
text("DEBUG MODE", width - 340, 125);
text("Press 'D' to hide", width - 340, 145);
if (serialData != null) {
text("Data: " + serialData, width - 340, 170);
}
text("Angle: " + currentAngle + "°", width - 340, 190);
text("Objects: " + countObjects(), width - 340, 210);
text("Connected: " + connected, width - 340, 230);
// Show recent debug messages
for (int i = 0; i < min(debugLog.size(), 3); i++) {
text(debugLog.get(debugLog.size() - 1 - i), width - 340, 250 + i * 20);
}
}
void serialEvent(Serial port) {
serialData = port.readStringUntil('\n');
if (serialData != null) {
serialData = trim(serialData);
String[] data = split(serialData, ',');
if (data.length == 2) {
try {
int angle = int(data[0]);
float distance = float(data[1]);
// Update scanning direction
if (angle > currentAngle) {
scanningForward = true;
} else if (angle < currentAngle) {
scanningForward = false;
}
previousAngle = currentAngle;
currentAngle = angle;
// Store distance and set object age for persistence
if (angle >= 0 && angle < distances.length) {
distances[angle] = distance;
objectAge[angle] = 30; // Keep object visible for 30 frames
dataCount++;
}
} catch (Exception e) {
addDebugMessage("Parse error: " + e.toString());
}
}
}
}
void keyPressed() {
// Port selection
if (key >= '0' && key <= '9') {
int portIndex = key - '0';
String[] portList = Serial.list();
if (portIndex < portList.length) {
connectToPort(portList[portIndex]);
}
}
// Toggle debug mode
if (key == 'd' || key == 'D') {
showDebug = !showDebug;
}
// Toggle test objects
if (key == 't' || key == 'T') {
forceTestObject = !forceTestObject;
}
// Clear all objects
if (key == 'c' || key == 'C') {
for (int i = 0; i < distances.length; i++) {
distances[i] = 0;
objectAge[i] = 0;
}
addDebugMessage("Cleared all objects");
}
}
----------------------------------------------------------------------------------
- Connect your ESP32 to computer via USB
- Open Arduino IDE
- Select your board: Tools → Board → ESP32 Arduino → "ESP32 Dev Module"
- Select the port: Tools → Port → (choose your ESP32 port)
- Copy and paste the ESP32 code (the one you provided)
- Click Upload (arrow button)
Key Code Explanations:
// This part measures distance
int measureDistance() {
digitalWrite(TRIGGER_PIN, HIGH); // Send ultrasonic pulse
delayMicroseconds(10);
digitalWrite(TRIGGER_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH); // Wait for echo
int distance = duration * 0.034 / 2; // Convert to centimeters
return distance;
}
// This part moves the servo
if (scanningForward) {
currentAngle += SCAN_STEP; // Move forward
} else {
currentAngle -= SCAN_STEP; // Move backward
}
radarServo.write(currentAngle); // Move servo to new position
Part 4: Setting Up the Processing Radar Display
Step 1: Understanding the Processing Code
The Processing code creates a visual radar display that:
- Receives data from ESP32 via serial port
- Draws a radar screen with distance rings and angle lines
- Shows detected objects as colored dots
- Provides kid-friendly interface with warnings and status
Step 2: Run the Processing Code
- Open Processing IDE
- Copy and paste the Processing code (the one you provided)
- Click Run (play button)
Step 3: Connect to ESP32
When the Processing program runs:
- It will show available serial ports
- Press the number key (0-9) corresponding to your ESP32 port
- You should see "CONNECTED ✓" message
- The radar should start showing objects!
Part 5: How It Works
The Radar Process:
- Servo rotates from 15° to 165° and back
- At each angle, ultrasonic sensor measures distance
- ESP32 sends angle and distance data: "90,45" (90 degrees, 45cm)
- Processing receives this data and draws it on screen
- Objects appear as colored dots based on distance
Distance Zones (Kid-Friendly):
- Red: Very Close (0-20cm) - "VERY CLOSE!"
- Orange: Close (20-40cm) - "Close"
- Yellow: Medium (40-60cm) - "Medium"
- Green: Far (60-80cm) - "Far"
- Blue: Very Far (80-100cm) - "Very Far"
Part 6: Testing and Troubleshooting
Testing Your Radar:
- Move your hand in front of the sensor at different distances
- Place objects at various positions
- Watch the radar screen - objects should appear as colored dots
- Test the range - maximum detection is about 100cm (1 meter)
Common Issues and Solutions:
"Port not found" or "Connection failed":
- Check USB cable - Make sure it's a data cable, not power-only
- Check drivers - Install ESP32 drivers if needed
- Try different port - Use different USB port on computer
- Restart Arduino IDE and try again
"Servo not moving smoothly":
- Check power supply - Servo needs enough power (5V recommended)
- Check connections - Ensure all wires are secure
-
Adjust delay - Increase
SERVO_DELAY
in ESP32 code
"No objects detected" or "Incorrect distances":
- Check sensor connections - TRIGGER and ECHO pins
- Sensor orientation - Make sure sensor faces forward
- Remove obstacles - Clear area in front of sensor
- Check sensor mounting - Should be stable on servo
"Processing display not working":
- Select correct port - Press right number key for ESP32 port
- Check baud rate - Should be 115200 in both codes
- Try debug mode - Press 'D' in Processing to see debug info
Useful Processing Controls:
- Press 'D' - Toggle debug mode
- Press 'T' - Add test objects (when not connected)
- Press 'C' - Clear all detected objects
- Press 0-9 - Select serial port
Part 7: Customization Ideas
For Beginners:
- Change colors - Modify the color variables in Processing code
-
Adjust range - Change
maxDistance
to scan further/closer -
Speed control - Modify
SERVO_DELAY
for faster/slower scanning - Add sounds - Use Processing's sound library for beeps
For Advanced Users:
- Add LCD display - Show distance on hardware display
- Multiple sensors - Use multiple ultrasonics for wider coverage
- Object tracking - Remember object positions over time
- Save radar data - Log detections to file
- WiFi connectivity - Send data over network
Part 8: Understanding the Science
How Ultrasonic Sensors Work:
- Send sound wave at 40kHz (too high for humans to hear)
- Wait for echo to return from objects
- Calculate distance using: Distance = (Time × Speed of Sound) ÷ 2
- Speed of sound ≈ 343 meters/second in air
Why Divide by 2?
Sound travels to the object and back, so total distance is double the actual distance to object.
Servo Motor Basics:
- PWM Control - Uses pulse width to set position
- 50Hz frequency - Standard for servo motors
- Pulse width: 1ms = 0°, 1.5ms = 90°, 2ms = 180°
Part 9: Safety and Best Practices
Safety Tips:
- Low voltage - This project uses safe 5V/3.3V levels
- Secure connections - Ensure all wires are properly connected
- Stable mounting - Prevent servo/sensor from falling
- Adult supervision - For younger builders
Best Practices:
- Comment your code - Add explanations for future reference
- Test incrementally - Test each component separately first
- Keep backups - Save working code versions
- Document changes - Note what modifications you make
Conclusion
Congratulations! You've built a working radar system that can detect objects and display them in real-time. This project demonstrates many important concepts:
- Sensor interfacing - Reading real-world data
- Motor control - Precise positioning
- Serial communication - Data transfer between devices
- Data visualization - Converting numbers to graphics
- Real-time systems - Processing continuous data streams
Next Steps:
- Experiment with different objects and distances
- Modify the code to add new features
- Share your project with friends and family
- Learn more about robotics and programming
Additional Resources:
- ESP32 Documentation: docs.espressif.com
- Processing Reference: processing.org/reference
- Arduino Tutorials: arduino.cc/en/Tutorial
Happy building! 🚀