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
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Ultrasonic rangerIn Halloween Prop Kit, Robot Car Kit.
- 1 × BuzzerIn Starter Kit, Rocket Flight Computer Kit.
- 1 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
Kits that can build it
These 8 starter kits have every part in the circuit. Each kit's page lists what else it can build.
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 pin | Connects to |
|---|---|
| Buzzer pin 1 | Arduino Uno R3 pin 9 |
| Resistor, 220 Ω pin 1 | Arduino Uno R3 pin 8 |
| Ultrasonic ranger pin TRIG | Arduino Uno R3 pin 7 |
| Ultrasonic ranger pin ECHO | Arduino Uno R3 pin 6 |
| Ultrasonic ranger pin VCC | Arduino Uno R3 pin 5V |
| Ultrasonic ranger pin GND | Arduino Uno R3 pin GND |
| Buzzer pin 2 | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| LED, red pin A | Resistor, 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.
// 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 editorTroubleshooting
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
ChangeSTOP, 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.