HC-SR04 ultrasonic sensor on a servo sweeping next to a small OLED radar display

Intermediate

Build an Ultrasonic Radar Display

SensorsDisplaysRobotics

Sweep an ultrasonic sensor across a wide arc with a servo and watch a real sweeping radar-style display build up on screen, the classic green sweeping line, made from parts that already do exactly this job.

4 real parts included

Total for this kit: 968.00 ฿

Doesn't include any order discount you may get at checkout.

What you'll need

Project Goal

This project turns a single distance sensor into a real sweeping radar display: a servo slowly rotates the sensor across a wide arc while the screen draws a line for whatever it detects at each angle, the same sweeping-line effect from old radar screens, built from three parts that already do this job individually.

What the Controller Does

On a loop, the controller commands the servo to a new angle, waits briefly for it to physically arrive, triggers the ultrasonic sensor, reads back the distance, and draws one new line on the display before moving to the next angle. Sweep, measure, draw, repeat.

What the Servo Does

The servo physically rotates the ultrasonic sensor across roughly a 180 degree arc, one angle step at a time, giving every distance reading a real physical direction to be drawn at.

What the Ultrasonic Sensor Does

It sends a short ultrasonic pulse and times how long the echo takes to return, converting that into a distance measurement, the same working principle as a real sonar system.

What the Display Does

The OLED draws each new distance reading as a line at the angle it was measured, building up a genuine radar-style sweep image one reading at a time.

Software and Library Concepts

Get each part working alone first: sweep the servo through its full range and confirm it physically moves correctly, take a single distance reading and print it, then draw one static test line on the display before combining all three into the sweep loop.

Testing and Troubleshooting

  • The servo jitters or won't hold a stable angle, check its power supply, servos draw more current than a GPIO pin alone should try to provide.
  • Distance readings are wildly inconsistent, the ultrasonic sensor is sensitive to soft or angled surfaces that scatter the pulse rather than reflecting it straight back, test against a flat hard wall first.
  • The display only ever shows part of the sweep, confirm the servo is actually completing its full range of motion and the angle-to-pixel math matches the servo's real range.

Optional Upgrade

Add a second display mode that logs only the closest detected object per sweep instead of drawing the full arc, a simple proximity-alert readout built from the same two sensors.

Wiring

  • HC-SR04 VCC → ESP32-S3 5V
  • HC-SR04 GND → ESP32-S3 GND
  • HC-SR04 TRIG → ESP32-S3 GPIO4 (output)
  • HC-SR04 ECHO → 1kΩ/2kΩ voltage divider → ESP32-S3 GPIO5 (input) — ECHO is 5V logic, the divider protects the 3.3V input pin
  • SG90 red → ESP32-S3 5V
  • SG90 brown/black → ESP32-S3 GND
  • SG90 orange/yellow (signal) → ESP32-S3 GPIO6 (PWM via ESP32Servo)
  • OLED VCC → ESP32-S3 3.3V
  • OLED GND → ESP32-S3 GND
  • OLED SDA → ESP32-S3 GPIO8 (default I2C)
  • OLED SCL → ESP32-S3 GPIO9 (default I2C)

HC-SR04 gets unreliable under ~2cm and the servo needs a short settle delay after each `write()` before the reading is trustworthy, which is why there's a 150ms pause per step. This draws a simplified single-line "sweep" radar (redrawn each angle) rather than a persistence-style trail — genuinely working, but simpler than a full radar-screen effect; feel free to add fading trail dots once the basic version is running.

Wiring Diagram

VCC → 5V GND TRIG → GPIO4 ECHO GPIO5 5V GND GPIO6 VCC → 3.3V GND SDA → GPIO8 SCL → GPIO9 ESP32-S3 HC-SR04 1kΩ/2kΩ voltage divider SG90 red SG90 brown/black SG90 orange/yellow OLED

Starter Code

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Servo.h>

// --- Pin definitions: adjust to match your ESP32-S3 board's silkscreen if different ---
#define TRIG_PIN   4   // HC-SR04 trigger (output)
#define ECHO_PIN   5   // HC-SR04 echo (input, through a 1k/2k voltage divider - ECHO is 5V logic)
#define SERVO_PIN  6   // SG90 signal (PWM-capable pin)
#define OLED_SDA   8   // ESP32-S3 default I2C SDA
#define OLED_SCL   9   // ESP32-S3 default I2C SCL

#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
#define OLED_ADDR     0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Servo radarServo;

const int MAX_DISTANCE_CM = 40;   // radar range shown on screen
const int CENTER_X = 64;
const int CENTER_Y = 63;
const int RADIUS_PX = 60;

long readDistanceCM() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 25000); // 25ms timeout (~4m max)
  if (duration == 0) return -1; // no echo / out of range
  return duration * 0.0343 / 2;
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  radarServo.attach(SERVO_PIN);

  Wire.begin(OLED_SDA, OLED_SCL);
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("OLED init failed");
    while (true) delay(1000);
  }
  display.clearDisplay();
  display.display();
}

void sweepAndDraw(int angle) {
  radarServo.write(angle);
  delay(150); // let the servo settle before reading

  long distance = readDistanceCM();

  display.clearDisplay();

  // Sweep line from center at current angle
  float rad = radians(angle);
  int lineLen = RADIUS_PX;
  if (distance > 0 && distance < MAX_DISTANCE_CM) {
    lineLen = map(distance, 0, MAX_DISTANCE_CM, 0, RADIUS_PX);
  }
  int x = CENTER_X - cos(rad) * lineLen;
  int y = CENTER_Y - sin(rad) * lineLen;
  display.drawLine(CENTER_X, CENTER_Y, x, y, SSD1306_WHITE);

  // Text readout
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Angle: ");
  display.print(angle);
  display.print((char)247); // degree symbol
  display.setCursor(0, 10);
  display.print("Dist: ");
  if (distance > 0) {
    display.print(distance);
    display.print(" cm");
  } else {
    display.print("--");
  }
  display.display();
}

void loop() {
  for (int angle = 0; angle <= 180; angle += 3) {
    sweepAndDraw(angle);
  }
  for (int angle = 180; angle >= 0; angle -= 3) {
    sweepAndDraw(angle);
  }
}

Not sure about a step? Ask us