Built-in project · Arduino Uno R3

Parking sensor on an Arduino Uno

An ultrasonic ranger and a buzzer that beeps faster as the wall gets closer. Drag the slider to move the wall. The whole build, an Arduino Uno and 4 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $36. On Mokxi they're free.

Step by step, with the wiring and the common mistakes: Arduino Ultrasonic Sensor (HC-SR04) Tutorial

The circuit runs here once the page has loaded.

What you'll learn

  • How an ultrasonic ranger measures distance
  • Mapping a distance to a beep rate
  • Thresholds: a stop line and a watch range
  • Waiting between pings so echoes fade
  • Using a helper library for a sensor
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 6

How it works

An ultrasonic ranger sends out a pulse on TRIG (pin 7) and times the echo on ECHO (pin 6). The sketch turns that time into a distance in millimeters. Drag the slider on the ranger to move the wall.

Between 2 meters and 15 cm, the buzzer on pin 9 beeps. The closer the wall, the shorter the gap between beeps, from about 900 ms down to 90 ms.

At 15 cm or less it plays a steady note and lights the red LED on pin 8. Past 2 meters, or with no echo, it stays silent. Serial prints a line each time the distance changes by about a centimeter.

Parts list

6 parts, plus the jumper wires. They retail for about $36; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Buzzer pin 1Arduino Uno R3 pin 9
Resistor, 220 Ω pin 1Arduino Uno R3 pin 8
Ultrasonic ranger pin TRIGArduino Uno R3 pin 7
Ultrasonic ranger pin ECHOArduino Uno R3 pin 6
Ultrasonic ranger pin VCCArduino Uno R3 pin 5V
Ultrasonic ranger pin GNDArduino Uno R3 pin GND
Buzzer pin 2Arduino Uno R3 pin GND
LED, red pin CArduino Uno R3 pin GND
LED, red pin AResistor, 220 Ω pin 2

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Parking sensor: an ultrasonic ranger and a buzzer that beeps faster as
// whatever is in front of it gets closer.
//
//   pin 7   TRIG on the HC-SR04
//   pin 6   ECHO on the HC-SR04       (VCC -> 5 V, GND -> GND)
//   pin 9   the buzzer, the other leg to GND
//   pin 8   the red LED, through 220 ohm to GND
//
// Drag the slider on the ranger to move the wall.
//
// # What the ranger actually does
//
// Ten microseconds of TRIG, and the module holds ECHO high for as long as the
// sound takes to come back. Sound is 343 metres a second, which is 0.343 mm a
// microsecond, and it makes the trip twice, so the distance is the echo in
// microseconds times 343 over 2000. `mokxi_hcsr04.h` does that sum in integers
// and returns 0 for no echo at all.
//
// # The beep
//
// The gap between beeps is the distance, near enough: at two metres it beeps
// about once a second, at twenty centimetres about ten times, and inside the
// stop line it holds one long note and lights the LED. That is what a real
// parking sensor does, and it is a thing you hear rather than read.
//
// # Why it waits sixty milliseconds
//
// The echo of the last ping is still going round the room. The datasheet asks
// for 60 ms between measurements and the sketch takes it; the loop has nothing
// better to do anyway.

#include "mokxi_hcsr04.h"
#include "mokxi_tone.h"

const uint8_t TRIG = 7;
const uint8_t ECHO = 6;
const uint8_t BUZZER = 9;
const uint8_t LED = 8;

Sonar sonar(TRIG, ECHO);

// Millimetres. Inside STOP it is one long note; past WATCH it says nothing.
const uint16_t STOP = 150;
const uint16_t WATCH = 2000;

uint32_t nextBeep = 0;

/** Milliseconds between beeps at a distance: closer is faster. */
static uint16_t gapFor(uint16_t mm) {
  if (mm <= STOP) {
    return 0;
  }
  // Linear from 90 ms at the stop line to 900 ms at the edge of interest,
  // which sounds right even though the ear is not linear.
  uint32_t span = (uint32_t)(WATCH - STOP);
  uint32_t over = (uint32_t)(mm - STOP);
  return (uint16_t)(90ul + (810ul * over) / span);
}

/** One line per change of about a centimetre, so the monitor stays readable. */
static void say(uint16_t mm, uint16_t gap) {
  static int lastCm = -1;
  int cm = (int)(mm / 10u);
  if (cm == lastCm) {
    return;
  }
  lastCm = cm;
  if (mm == 0u) {
    Serial.println("nothing in range");
    return;
  }
  Serial.print(cm);
  Serial.print(" cm");
  if (gap == 0u) {
    Serial.println("  STOP");
  } else {
    Serial.print("  beep every ");
    Serial.print(gap);
    Serial.println(" ms");
  }
}

void setup() {
  sonar.begin();
  pinMode(LED, OUTPUT);
  mokxiNoTone(BUZZER);
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: parking sensor");
  Serial.println("drag the slider on the ranger to move the wall");
}

void loop() {
  uint16_t mm = sonar.read();

  if (mm == 0u || mm > WATCH) {
    // Nothing worth telling the driver about.
    digitalWrite(LED, LOW);
    mokxiNoTone(BUZZER);
    say(0, 0);
    delay(60);
    return;
  }

  if (mm <= STOP) {
    digitalWrite(LED, HIGH);
    mokxiTone(BUZZER, NOTE_A4, 60);
    say(mm, 0);
    delay(60);
    return;
  }

  digitalWrite(LED, LOW);
  uint16_t gap = gapFor(mm);
  if ((int32_t)(millis() - nextBeep) >= 0) {
    mokxiTone(BUZZER, NOTE_C5, 40);
    nextBeep = millis() + gap;
  }
  say(mm, gap);
  delay(60);
}

Code walkthrough

The key functions of the sketch, and what each one does.

gapFor sketch.ino

Turns a distance into the wait between beeps. At or inside STOP it returns 0. Beyond that the gap grows in a straight line from 90 ms at the stop line to 900 ms at WATCH, which is 2000 mm.

/** Milliseconds between beeps at a distance: closer is faster. */
static uint16_t gapFor(uint16_t mm) {
  if (mm <= STOP) {
    return 0;
  }
  // Linear from 90 ms at the stop line to 900 ms at the edge of interest,
  // which sounds right even though the ear is not linear.
  uint32_t span = (uint32_t)(WATCH - STOP);
  uint32_t over = (uint32_t)(mm - STOP);
  return (uint16_t)(90ul + (810ul * over) / span);
}

say sketch.ino

Prints a status line to Serial, but only when the distance changes by about a centimeter. It remembers the last value, so the monitor does not flood. It says STOP, a beep gap, or nothing in range.

/** One line per change of about a centimetre, so the monitor stays readable. */
static void say(uint16_t mm, uint16_t gap) {
  static int lastCm = -1;
  int cm = (int)(mm / 10u);
  if (cm == lastCm) {
    return;
  }
  lastCm = cm;
  if (mm == 0u) {
    Serial.println("nothing in range");
    return;
  }
  Serial.print(cm);
  Serial.print(" cm");
  if (gap == 0u) {
    Serial.println("  STOP");
  } else {
    Serial.print("  beep every ");
    Serial.print(gap);
    Serial.println(" ms");
  }
}

loop sketch.ino

Reads the distance with sonar.read(). Out of range means silence. At or inside STOP, it sounds a note and lights the LED. Otherwise it beeps when the next beep time arrives. Every pass ends with a 60 ms delay.

void loop() {
  uint16_t mm = sonar.read();

  if (mm == 0u || mm > WATCH) {
    // Nothing worth telling the driver about.
    digitalWrite(LED, LOW);
    mokxiNoTone(BUZZER);
    say(0, 0);
    delay(60);
    return;
  }

  if (mm <= STOP) {
    digitalWrite(LED, HIGH);
    mokxiTone(BUZZER, NOTE_A4, 60);
    say(mm, 0);
    delay(60);
    return;
  }

  digitalWrite(LED, LOW);
  uint16_t gap = gapFor(mm);
  if ((int32_t)(millis() - nextBeep) >= 0) {
    mokxiTone(BUZZER, NOTE_C5, 40);
    nextBeep = millis() + gap;
  }
  say(mm, gap);
  delay(60);
  // ... 1 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The buzzer never beeps. This project
    The wall must be inside 2000 mm. Check TRIG is on pin 7 and ECHO on pin 6, and that the ranger has 5V.
  • STOP triggers too early or too late. This project
    Change STOP, which is in millimeters. It is 150 now, so the LED lights at 15 cm.
  • Distance reads zero, or never changes. Ultrasonic ranger
    Power it from 5 V and share ground. Send a 10 microsecond HIGH pulse on TRIG, then time ECHO with pulseIn. Check TRIG and ECHO are not swapped.
  • Readings jump around or are wrong on a 3.3 V board. Ultrasonic ranger
    ECHO outputs 5 V, so divide it down for 3.3 V boards. Aim at flat, hard surfaces. The range is about 2 cm to 4 m, and soft or angled targets bounce the sound away.
  • The buzzer makes a constant tone, or you cannot change its pitch. Buzzer
    An active buzzer has its own oscillator, so it only beeps on and off. A passive buzzer needs tone() to set pitch. Check which type you have.
  • The buzzer is silent or very quiet. Buzzer
    Check polarity: the plus mark goes to the pin or supply. For loud buzzers, drive them through a transistor instead of straight from a pin.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.
  • A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
    Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.