2.4 Buzzer Beep
The goal of this example is to make some noise. A passive buzzer is a tiny speaker: feed it a square wave and it vibrates at whatever frequency you send it, so a single buzzer can play any note you like.
In this example the NULA MINI plays a short eight-note scale the moment you run the script.
In this documentation you will learn:
- How to connect a passive buzzer to the board.
- How
PWMturns a frequency into a sound. - What
duty_u16()controls, and why 32768 is the number to use. - How to keep a melody in a list and step through it with a loop.
Hardware required:
- 1x Soldered NULA MINI board
- 1x Breadboard
- 1x Passive buzzer
- 3x Jumper wires
- 1x USB-C cable
IO5 and ground. There is nothing to protect it from, because the pin only ever swings between 0 V and 3.3 V.Putting the components together
Follow the five steps below. Each photo is taken from the same position, so you can compare it with the previous one and see exactly what changed.
1. Insert the NULA MINI board on the breadboard
Push the board into one end of the breadboard so that its two rows of pins sit on either side of the centre channel, with the chip facing down.

2. Place the buzzer and bring ground out to the rail
The buzzer goes on the f–j side, well clear of the board, with its two legs in row 14 and row 15.
Look at the top of the case of the buzzer before you push it in. Next to the HWDZ moulding there is a small + inside a circle, and the leg on that side of the case is the positive one. That leg goes into row 15.
Then run a jumper from j30 (GND) across to the blue − rail. That rail is the ground line for the rest of the example.

3. Connect the positive leg to IO5
One jumper from j15 (the row holding the + leg of the buzzer) down to j28 (IO5). This is the wire that will carry the square wave.

4. Connect the other leg to ground
One more jumper, from j14 (the other leg of the buzzer) across to the blue − rail, which is already tied to GND.

The loop is now closed: IO5 → buzzer → GND. Whatever IO5 does, the buzzer feels.
5. Connect the board to your computer

IO5 reaches the + row, and the other row reaches ground.How a passive buzzer makes a note
Sound is nothing more than air being pushed back and forth. Inside the buzzer is a thin disc that moves whenever the voltage across it changes, and moving it quickly enough makes a note you can hear.
That is what PWM does. PWM stands for Pulse Width Modulation: it switches a pin on and off very quickly, and lets us choose both how fast it switches and what fraction of the time it stays on. The number of switches per second is the frequency, measured in hertz, and your ear reads that frequency as pitch:
| Note | Frequency (Hz) |
|---|---|
| C4 | 262 |
| D4 | 294 |
| E4 | 330 |
| F4 | 349 |
| G4 | 392 |
| A4 | 440 |
| B4 | 494 |
| C5 | 523 |
MicroPython has no tone() function. Instead you create a PWM object on the pin and then drive it with two calls:
buzzer = PWM(Pin(BUZZER_PIN))
buzzer.freq(262) # choose the pitch
buzzer.duty_u16(32768) # start the sound
buzzer.duty_u16(0) # stop the sound
freq() sets the pitch. duty_u16() sets what fraction of the time the pin stays on, as a number from 0 (always off) to 65535 (always on). Half of that, 32768, is an even on-off switching, which gives a buzzer its clearest tone. Writing 0 leaves the pin permanently off, which is silence.
PWM object already claims the pin, so unlike a plain Pin you do not set a direction for it. There is no Pin.OUT in this example at all.How the melody is stored
A melody is really two lists that travel together: which note, and how long it lasts. The script keeps them in two lists of the same length:
melodyholds the frequency of each note in hertz.note_durationholds the length of each note, written the way sheet music writes it:4for a quarter note,2for a half note.
A for loop then walks through both lists one position at a time, plays the note and waits out its duration.
for i in range(len(melody)):
len(melody) is the number of notes in the list, and range() counts from 0 up to just below that number. So i takes the value of each position in turn, and we use it to read the matching entry out of both lists. Written this way the loop stays correct even after you add a note of your own. There is no equivalent of the Arduino sizeof() arithmetic to keep in step.When the loop finishes we release the pin:
buzzer.deinit()
deinit() hands the pin back and leaves the buzzer silent for good. Without it the PWM hardware stays claimed even after the program ends, which can leave the buzzer humming after the script has stopped.Code
# PWM stands for Pulse Width Modulation. It switches a pin on and off very quickly, and we can choose both how fast
# it switches and what fraction of the time it stays on. Feeding that fast switching into a buzzer is what makes it
# produce a sound, and the switching speed is what we hear as the pitch.
from machine import Pin, PWM
import time
# This is a variable to which we assign the number of the pin that we connected the buzzer to.
# The NULA board has a pin naming logic as follows: IO5, where 5 is the number that we give to the variable.
BUZZER_PIN = 5
# We will use two lists - one for note frequencies (in Hertz) and one for note durations.
# A frequency is how many times per second the buzzer moves back and forth, and our ears hear it as the pitch of
# the note: the higher the number, the higher the note.
melody = [262, 294, 330, 349, 392, 440, 494, 523] # C4 to C5
note_duration = [4, 4, 4, 4, 4, 4, 4, 2] # Quarter notes (last one is half note)
# Here we create our PWM object, which we named "buzzer". Creating it already claims the pin, so unlike a plain Pin
# we do not set a direction for it.
buzzer = PWM(Pin(BUZZER_PIN))
# duty_u16() sets what fraction of the time the pin stays on, as a number from 0 (always off) to 65535 (always on).
# Half of that, 32768, is the even on-off switching that gives a buzzer its clearest tone.
SOUND_ON = 32768
SOUND_OFF = 0
# Start with the buzzer silent, so it does not sound before the melody begins.
buzzer.duty_u16(SOUND_OFF)
print("Playing melody...")
# A for loop repeats a block of code once for every item it is given. range(len(melody)) counts from 0 up to the
# number of notes in our list, so "i" ends up being the position of each note in turn.
for i in range(len(melody)):
# Musical note lengths are written as fractions: a quarter note is a quarter of a whole note. Here we turn that
# fraction into milliseconds by dividing one second by the number in the list, so a 4 becomes 250 ms and a 2
# becomes 500 ms.
duration = int(1000 / note_duration[i])
# freq() sets how fast the pin switches, which is the pitch of the note, and duty_u16() then starts the sound.
buzzer.freq(melody[i])
buzzer.duty_u16(SOUND_ON)
# Play the note for its full length.
time.sleep_ms(duration)
# Now we stop the sound and wait a little longer before the next note. That extra silence keeps the notes from
# running into each other. Feel free to experiment with the 0.3.
buzzer.duty_u16(SOUND_OFF)
time.sleep_ms(int(duration * 0.3))
# deinit() releases the pin once we are finished with it, leaving the buzzer silent for good.
buzzer.deinit()
print("Melody finished!")
What you should see
Press Run and the buzzer plays a rising scale of eight notes, just under three seconds in all. Then it goes quiet.
0.3 the scale runs faster without any of the notes changing pitch.There is no endless loop in this script, so the melody plays once and the program ends. To hear it again, press Run again.
The Shell shows the same thing in text:
Playing melody...
Melody finished!
IO5 lands in the same row as one of them.Try it yourself: change one of the numbers in melody and run it again. Because the loop counts with len(melody), you can also add notes to both lists and it will pick them up with no other change.
Full example
Check out the full example code on the link below:
2.4_Buzzer_Beep.py
Example that shows how to use PWM to play a simple melody on a passive buzzer connected to IO5.