---
title: "Nano Theremin — A 15-Minute Build with My Son"
description: "A hand-waving theremin on an Arduino Nano: ultrasonic sensor, passive buzzer, three jumper wires, and just enough explanation to teach my son what each part does."
pubDate: 2026-04-26T00:00:00.000Z
---
Leo wandered into the lab on a rainy afternoon and asked what I was working on. I had nothing planned — so I grabbed an Arduino Nano, a starter-kit ultrasonic sensor, and a tiny buzzer, and we built a hand-waving theremin in fifteen minutes. Wave your hand above the sensor and the pitch changes: close = high, far = low.

The whole point was the explanation, not the build. Two components, three jumper wires, and a chance to actually show him what each part does instead of just saying “it’s like magic, buddy.”

## The two components

**The HC-SR04 is the ear.** It’s an ultrasonic rangefinder — two little metal cylinders on the front, one a transmitter and one a receiver. The Nano pulses the trigger pin for 10 microseconds; the sensor fires off an ultrasonic chirp; the chirp bounces off your hand; the receiver hears the echo and pulses the echo pin HIGH for as long as the round trip took. Divide that pulse length by 58 and you get the distance in centimeters. That last bit is fun to explain: sound travels at ~343 m/s, and the pulse measures the round trip, so half of (343 m/s × microseconds) gives you a distance that conveniently divides by 58 for centimeters.

**The passive buzzer is the mouth.** A passive buzzer is just a tiny speaker — it doesn’t make any sound on its own. You have to drive it with a square wave at the frequency you want to hear. Send it 440 Hz and it plays an A. The Arduino’s built-in `tone()` function generates the square wave for you. (An _active_ buzzer is the opposite: power it and it beeps at a fixed pitch. We didn’t want that. We wanted a real instrument.)

The whole instrument is: read distance, map distance to frequency, send frequency to the buzzer. Repeat 50 times a second.

## Parts

| Component | Qty | Notes |
| --- | --- | --- |
| Arduino Nano | 1 | Inland Nano 3 (ATmega328P, CH340 USB-serial) |
| HC-SR04 ultrasonic sensor | 1 | Keyes 37-in-1 module #37, 4-pin (VCC/Trig/Echo/GND) |
| Passive buzzer | 1 | Keyes 37-in-1 module #27, 2-pin |
| Breadboard + jumper wires | 1 | Half-size or larger |
| USB-A to USB-C cable | 1 | Data-capable |

## Wiring

| Component | Pin | Arduino Nano |
| --- | --- | --- |
| HC-SR04 | VCC | 5V |
| HC-SR04 | GND | GND |
| HC-SR04 | Trig | D9 |
| HC-SR04 | Echo | D10 |
| Buzzer | + | D8 |
| Buzzer | − | GND |

Both components run off the Nano’s 5V rail. Total draw is around 60 mA — USB power is plenty.

![Breadboard wiring close-up](https://assets.elklabs.net/posts/nano-theremin/1.jpg)

## The sketch

No libraries. About 30 lines.

```cpp
const int TRIG_PIN  = 9;
const int ECHO_PIN  = 10;
const int BUZZ_PIN  = 8;

const int DIST_MIN  = 5;    // cm — closer than this = silence
const int DIST_MAX  = 50;   // cm — farther than this = silence
const int FREQ_LOW  = 150;  // Hz at DIST_MAX
const int FREQ_HIGH = 2000; // Hz at DIST_MIN

void setup() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

void loop() {
  int dist = readDistanceCm();

  if (dist < DIST_MIN || dist > DIST_MAX) {
    noTone(BUZZ_PIN);
  } else {
    int freq = map(dist, DIST_MIN, DIST_MAX, FREQ_HIGH, FREQ_LOW);
    tone(BUZZ_PIN, freq);
  }

  delay(20);
}

int readDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 30000);
  if (duration == 0) return 999; // no echo = out of range
  return duration / 58;
}
```

A few small choices worth pointing out:

-   **`map()` is reversed on purpose.** I wanted _close_ hand = _high_ pitch, which feels right for a theremin. Mapping `DIST_MIN → FREQ_HIGH` and `DIST_MAX → FREQ_LOW` gives that.
-   **5–50 cm window.** Closer than 5 cm and the sensor reads garbage; further than 50 cm and the noise floor takes over. Outside that window, silence.
-   **30 ms pulse-in timeout.** A reflection from 5 m round-trip takes ~30 ms. If we get nothing back in that time, treat it as out of range instead of blocking.
-   **20 ms loop delay.** That’s 50 readings per second — fast enough to feel continuous, slow enough that the buzzer doesn’t get retriggered mid-cycle.

## See it in action

 

## What Leo got out of it

He immediately figured out that you can play a tune by waving your hand in steps, that holding still gives you a steady pitch, and that the cat does not enjoy a 2 kHz tone. He also asked why the buzzer sounded different when he held his hand at an angle — which kicked off a quick detour about how the ultrasonic chirp reflects off the closest surface, so a tilted hand sends most of the echo somewhere other than back at the sensor.

Fifteen minutes, two components, one rainy afternoon. Worth more than it cost.

---

> **First Arduino project?** I wrote a [quickstart guide](/guides/arduino) for getting `arduino-cli` installed and a first sketch flashed — friendly to total beginners, links out to the official docs for everything else.
