Concept
For this assignment, I worked together with Ling to create an instrument using the ultrasonic sensor. He came up with the idea to use the distance sensor, and the way we executed it reminded me of a theremin because there’s no physical contact needed to play the instrument. We also used a red button to turn the instrument on and off, as well as a green button that controls the tempo of the piezo buzzer (based on how many times you click on it in 5 seconds).
Schematic Diagram
Code
const int trigPin = 9;
const int echoPin = 10;
const int buzzerPin = 8;
const int buttonPin = 4; // system ON/OFF button
const int tempoButtonPin = 2; // tempo setting button
//sensor variables
float duration, distance;
bool systemOn = false; // system on or off state
// tempo variables
bool tempoSettingActive = false;
int tempoPressCount = 0;
unsigned long tempoStartTime = 0;
unsigned long tempoInterval = 500;
// for edge detection on tempo button (to count presses cleanly)
int lastTempoButtonState = HIGH;
// for edge detection on system button (cleaner)
int lastSystemButtonState = HIGH;
// for controlling when next beep happens
unsigned long lastBeatTime = 0;
void setup() {
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(buzzerPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP); // system ON/OFF button
pinMode(tempoButtonPin, INPUT_PULLUP); // tempo button
Serial.begin(9600);
}
void loop() {
// system on/off state
int systemButtonState = digitalRead(buttonPin);
// detects a press: HIGH -> LOW
if (systemButtonState == LOW && lastSystemButtonState == HIGH) {
systemOn = !systemOn; // Toggle system state
Serial.print("System is now ");
Serial.println(systemOn ? "ON" : "OFF");
delay(200); // basic debounce
}
lastSystemButtonState = systemButtonState;
// if system is OFF: make sure buzzer is silent and skip rest
if (!systemOn) {
noTone(buzzerPin);
delay(50);
return;
}
// tempo button code
int tempoButtonState = digitalRead(tempoButtonPin);
// detects a press
if (tempoButtonState == LOW && lastTempoButtonState == HIGH) {
if (!tempoSettingActive) {
//first press (starts 5 second capture window)
tempoSettingActive = true;
tempoStartTime = millis();
tempoPressCount = 1; // Count this first press
Serial.println("Tempo setting started: press multiple times within 5 seconds.");
} else {
// additional presses inside active window (5 secs)
tempoPressCount++;
}
delay(40); // debounce for tempo button
}
lastTempoButtonState = tempoButtonState;
// if we are in tempo mode, check whether 5 seconds passed
if (tempoSettingActive && (millis() - tempoStartTime >= 5000)) {
tempoSettingActive = false;
if (tempoPressCount > 0) {
// tempo interval = 1000 ms / (number of presses in 5 secs)
tempoInterval = 1000UL / tempoPressCount;
// avoids unrealistically fast intervals
if (tempoInterval < 50) {
tempoInterval = 50;
}
Serial.print("Tempo set: ");
Serial.print(tempoPressCount);
Serial.print(" presses in 5s -> interval ");
Serial.print(tempoInterval);
Serial.println(" ms between beeps.");
} else {
Serial.println("No tempo detected.");
}
}
// ultrasonic sensor distance measurement
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration * 0.0343) / 2.0; // in cm
Serial.print("Distance: ");
Serial.println(distance);
//buzzer pitch and tempo
// checks if distance is valid and in a usable range
if (distance > 0 && distance < 200) {
float d = distance;
// limit distances for stable mapping
if (d < 5) d = 5;
if (d > 50) d = 50;
// maps distance to frequency (closer = higher pitch)
int frequency = map((int)d, 5, 50, 2000, 200);
// uses tempoInterval to define how often we "tick" the sound (creates short beeps at each interval using current frequency)
unsigned long now = millis();
if (now - lastBeatTime >= tempoInterval) {
lastBeatTime = now;
// plays a short beep (half of the interval duration)
unsigned long beepDuration = tempoInterval / 2;
if (beepDuration < 20) {
beepDuration = 20; // minimum hearable blip
}
tone(buzzerPin, frequency, beepDuration);
}
} else {
// if bad/no reading, silence the buzzer between ticks
noTone(buzzerPin);
}
delay(50); //delay to reduce noise
}
Favorite Code
//buzzer pitch and tempo
// checks if distance is valid and in a usable range
if (distance > 0 && distance < 200) {
float d = distance;
// limit distances for stable mapping
if (d < 5) d = 5;
if (d > 50) d = 50;
// maps distance to frequency (closer = higher pitch)
int frequency = map((int)d, 5, 50, 2000, 200);
This is a pretty simple part of the code, but I loved how the beeping sounds turned out. It’s so satisfying to see how my hand movements directly control the sound, and it made me realize how significant even a small part of the code could be.
Video Demo
Reflection and Future Improvements
Mapping the distance to control the sound was pretty simple. The challenging part was controlling the tempo, we had to figure out the values to divide in order for it to have a clear change in the sound of the piezo buzzer after clicking the green button. Overall, this was a really cool project and I hope to continue to improve and expand my knowledge in physical computing!
