An Arduino buzzer: from a beep to a metronome
- 192parts on the bench
- 25boards running now
- 1.00xreal time, on every board
Sound is the fastest way to make an Arduino project feel alive: a beep when a button is pressed, an alarm when a sensor trips, a tune when a game ends. The circuit above is a metronome on an Arduino Uno. It clicks a passive piezo on every beat, flashes a lamp with it, and two buttons move the tempo between 40 and 240 beats per minute. It is running in your browser on the same sketch shown below.
Press the buttons and watch the tempo change in the serial monitor. The readout under the piezo shows the frequency of each click: a higher note on the first beat of every bar of four, the way a real metronome’s bell works.
What you need
- An Arduino Uno
- A passive piezo buzzer
- Two pushbuttons
- One LED and a 220 ohm resistor
- A breadboard and jumper wires
Wiring
Active buzzer or passive piezo?
Two parts are sold as a buzzer, and they are wired and coded differently. An active buzzer has its own oscillator inside. Give it a steady voltage and it makes its one fixed tone; take the voltage away and it stops. digitalWrite(pin, HIGH) is all the code it needs.
A passive piezo has no oscillator. A steady voltage just bends the disc once and it stays silent. To make a note you have to drive it with a square wave at the frequency you want to hear: switch the pin high and low 440 times a second and you get an A. That is more work, and it is why the passive kind can play any note and a tune, where the active kind can only beep.
The metronome uses a passive piezo so the first beat of each bar can be a higher note than the other three. Mokxi has both parts in the parts bin, so you can try each.
How the notes are made
Most tutorials call tone(pin, frequency, duration), which uses a hardware timer to toggle the pin in the background while the rest of the sketch carries on. That works in Mokxi exactly as on a real Uno: it takes timer 2, so PWM on pins 3 and 11 pauses while a note plays, and noTone(pin) stops it early.
This example uses mokxiTone(pin, frequency, milliseconds) from Mokxi’s own header instead, which toggles the pin itself and waits. The one real difference is that it blocks: the sketch pauses for the length of the note. For an 8 millisecond click that does not matter, and it leaves every timer alone. Replace mokxiTone with tone() and it works the same, without the pause.
The tempo is kept as beats per minute. The gap between beats is 60,000 milliseconds divided by the tempo, all in whole numbers: at 120 bpm that is 500 ms. The sketch subtracts the click’s own 8 ms from the gap so each beat lands where it should.
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; // four beats to a bar
waitAndWatch(gap > CLICK_MS ? gap - CLICK_MS : 1);
}Reading the buttons while it waits
A metronome spends almost all its time waiting for the next beat, so that is where the buttons are read. Instead of one long delay(), waitAndWatch() loops in 4 millisecond steps until the gap is over, reading both buttons each time. A tempo change happens on an edge, the moment a button goes from released to pressed, so holding a button down moves the tempo by one step of 4 bpm rather than racing to the limit.
Both buttons use the Uno’s internal pull-ups, so each one is just a wire to its pin and a wire to ground. Pressed reads LOW.
Try it in the editor
Change NOTE_C6 to NOTE_G5 and listen to the difference in the readout. Then change the & 3 to a count of three beats per bar for a waltz, using beat = (beat + 1) % 3.
Swap the passive piezo for the active buzzer from the parts bin and use digitalWrite(PIEZO, HIGH), a short delay and LOW for the click. It still beeps, but every beat is now the same note.
Finally, play a tune: #include "pitches.h", an array of notes and an array of lengths, and a loop that calls tone() for each pair with a delay a little longer than the note. Put the loop in setup() so it plays once at startup.
Common mistakes
Using digitalWrite(HIGH) on a passive piezo and hearing nothing. It needs a square wave, from tone() or a toggling loop.
Using tone() on an active buzzer and getting a buzzy, rough sound. The buzzer already makes its own tone; drive it with a plain HIGH and LOW.
Putting a large resistor in series with a piezo. The pin can drive a small piezo directly, and a resistor mostly makes it quieter.
Using long blocking delays and missing button presses. Read inputs while you wait, as this sketch does, or use millis() timing.
Questions
How do I make a buzzer beep with Arduino?
For an active buzzer, digitalWrite the pin HIGH, delay, then LOW. For a passive piezo, call tone(pin, frequency, duration), in Mokxi as on a real Arduino.
Does Mokxi support tone()?
Yes. tone(), tone() with a duration and noTone() work as on a real board, from a timer interrupt, so the sketch carries on while the note plays. On an Uno the note takes timer 2, as the Arduino core does. Mokxi also has mokxiTone(pin, frequency, ms), which pauses the sketch for the note and uses no timer.
Which pin should the buzzer use?
Any digital pin works for a piezo. This example uses pin 7, leaving the PWM pins free for LEDs and motors.
Keep going
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.