Metronome on an Arduino Uno
The Uno clicks a beat on a piezo and two buttons walk the tempo between 40 and 240 bpm. The whole build, an Arduino Uno and 5 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.
Beginner About 15 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $33. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino Buzzer and Piezo: Beeps, Notes, Metronome
What you'll learn
- Turning beats per minute into milliseconds
- Playing a short tone on a passive piezo
- Reading buttons while waiting, without losing the beat
- Edge detection so one press is one step
- Accenting the first beat of a bar
- Difficulty: Beginner
- Time: About 15 minutes
- Board: Arduino Uno R3
- Parts: 7
How it works
A passive piezo on pin 7 makes a short click on every beat, and a green LED on pin 9 flashes with it. The first beat in each group of four uses a higher note, so you can hear where the bar starts.
Two buttons set the tempo. The one on pin 2 adds 4 beats per minute, and the one on pin 3 takes away 4. It starts at 96 and stays between 40 and 240. The gap between beats is 60000 divided by the tempo, in milliseconds.
Parts list
7 parts, plus the jumper wires. They retail for about $33; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, greenIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 2 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 1 × Piezo speakerIn Starter Kit, Claw Machine Kit, Vending Machine Kit.
Kits that can build it
These 10 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 |
|---|---|
| LED, green pin C | Arduino Uno R3 pin GND |
| Pushbutton (1) pin 2a | Arduino Uno R3 pin GND |
| Pushbutton (2) pin 2a | Arduino Uno R3 pin GND |
| Piezo speaker pin 2 | Arduino Uno R3 pin GND |
| Resistor, 220 Ω pin 1 | Arduino Uno R3 pin 9 |
| Piezo speaker pin 1 | Arduino Uno R3 pin 7 |
| Pushbutton (2) pin 1a | Arduino Uno R3 pin 3 |
| Pushbutton (1) pin 1a | Arduino Uno R3 pin 2 |
| LED, green 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.
// Metronome: a click a beat on the piezo, with the tempo on two buttons.
//
// pin 7 a passive piezo straight to GND
// pin 9 the beat lamp, through 220 ohm to GND
// pin 2 faster, to GND pin 3 slower, to GND
//
// The tempo is kept as beats per minute and the gap between beats is worked
// out from it: 60 000 milliseconds in a minute divided by the tempo. All of it
// is integer arithmetic, because there is no soft-float runtime here: 60 000
// over 120 is 500, and the remainder nobody can hear.
//
// The first beat of every bar of four is a higher note, which is what a real
// metronome's bell does and what makes it usable: you can hear where you are
// in the bar without counting.
//
// The wait between beats is where the buttons are read, so the tempo changes
// on the next beat rather than the next press. The click itself blocks for its
// eight milliseconds, so the gap is shortened by that much and a beat lands
// where it should.
#include "mokxi_tone.h"
const int PIEZO = 7;
const int LAMP = 9;
const int FASTER = 2;
const int SLOWER = 3;
const int MIN_BPM = 40;
const int MAX_BPM = 240;
const unsigned long CLICK_MS = 8;
int bpm = 96;
int beat = 0;
static bool down(int pin) { return digitalRead(pin) == LOW; }
static void say() {
Serial.print("tempo ");
Serial.print(bpm);
Serial.println(" bpm");
}
// Watch the buttons for `ms`, changing the tempo on an edge.
static void waitAndWatch(unsigned long ms) {
static bool wasFaster = false;
static bool wasSlower = false;
unsigned long start = millis();
while (millis() - start < ms) {
bool f = down(FASTER);
bool s = down(SLOWER);
if (f && !wasFaster && bpm < MAX_BPM) {
bpm += 4;
say();
}
if (s && !wasSlower && bpm > MIN_BPM) {
bpm -= 4;
say();
}
wasFaster = f;
wasSlower = s;
delay(4);
}
}
void setup() {
pinMode(LAMP, OUTPUT);
pinMode(PIEZO, OUTPUT);
pinMode(FASTER, INPUT_PULLUP);
pinMode(SLOWER, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: metronome");
Serial.println("one button speeds it up, the other slows it down");
say();
}
void loop() {
unsigned long gap = 60000UL / (unsigned long)bpm;
digitalWrite(LAMP, HIGH);
mokxiTone(PIEZO, beat == 0 ? NOTE_C6 : NOTE_C5, (uint16_t)CLICK_MS);
digitalWrite(LAMP, LOW);
beat = (beat + 1) & 3;
waitAndWatch(gap > CLICK_MS ? gap - CLICK_MS : 1);
}
Code walkthrough
The key functions of the sketch, and what each one does.
down sketch.ino
Returns true when a button pin reads LOW. The buttons use INPUT_PULLUP and connect to ground, so a pressed button reads LOW.
static bool down(int pin) { return digitalRead(pin) == LOW; }say sketch.ino
Prints the current tempo to serial, in beats per minute. It runs at startup and after every tempo change.
static void say() {
Serial.print("tempo ");
Serial.print(bpm);
Serial.println(" bpm");
}waitAndWatch sketch.ino
Waits for the given time, but checks both buttons every 4 ms while it waits. It compares each button to its last state, so one press moves the tempo by 4 only once, within MIN_BPM and MAX_BPM.
// Watch the buttons for `ms`, changing the tempo on an edge.
static void waitAndWatch(unsigned long ms) {
static bool wasFaster = false;
static bool wasSlower = false;
unsigned long start = millis();
while (millis() - start < ms) {
bool f = down(FASTER);
bool s = down(SLOWER);
if (f && !wasFaster && bpm < MAX_BPM) {
bpm += 4;
say();
}
if (s && !wasSlower && bpm > MIN_BPM) {
bpm -= 4;
say();
}
wasFaster = f;
wasSlower = s;
delay(4);
}
}loop sketch.ino
Works out the gap from bpm, lights the lamp and plays a click of CLICK_MS (8 ms) with mokxiTone, then turns the lamp off. Beat 0 plays NOTE_C6 and the others NOTE_C5. It then waits the gap minus the click time, so beats land on time.
void loop() {
unsigned long gap = 60000UL / (unsigned long)bpm;
digitalWrite(LAMP, HIGH);
mokxiTone(PIEZO, beat == 0 ? NOTE_C6 : NOTE_C5, (uint16_t)CLICK_MS);
digitalWrite(LAMP, LOW);
beat = (beat + 1) & 3;
waitAndWatch(gap > CLICK_MS ? gap - CLICK_MS : 1);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The buttons seem to do nothing. This project
Each button connects its pin (2 or 3) to GND. The sketch turns on the pull-up itself, so no resistor is needed. - No click is heard. This project
Check the piezo is on pin 7 with its other leg on GND. It needs to be a passive piezo. - 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. - The LED does not light, or it lights once and then dies. LED
LEDs have polarity. The longer leg (anode) goes toward the positive side. Always add a series resistor, about 220 ohms to 1 k ohm on a 5 V pin. - A blue or white LED stays dark on 3.3 V. LED
Blue and white LEDs drop about 3 V, so 3.3 V leaves almost nothing for the resistor. Use a 5 V supply, or a smaller resistor and a red or green LED. - The input reads random HIGH and LOW when the button is not pressed. Pushbutton
A floating pin picks up noise. Add a 10 k pull-down or pull-up resistor, or use INPUT_PULLUP and wire the button between the pin and GND. Then pressed reads LOW. - The button seems always pressed, or never changes. Pushbutton
A 4-leg tactile switch has legs joined in pairs. Place it across the breadboard center gap so the pairs sit on different rows. Wired along one side, the legs are already connected.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.