Music Notes and Frequency: Generate Do-Re-Mi with PWM on Arduino
A musical note is a sound that repeats at a particular frequency. A4, the A above middle C, is commonly tuned to 440 Hz. To play that pitch with a microcontroller, make an output pin switch on and off 440 times per second and connect it to a suitable sounder.
This practical example uses an Arduino Uno and a passive piezo buzzer to play a C major scale: Do, Re, Mi, Fa, Sol, La, Ti, Do. Along the way, we will connect note names, frequencies, timer output, and PWM.
1) Notes, pitch, and frequency
Frequency is measured in hertz (Hz), or cycles per second. A low frequency sounds like a low pitch; a high frequency sounds like a high pitch. For example, a 440 Hz tone vibrates 440 times per second and is the familiar tuning reference A4.
Musical pitch is divided into semitones. In the standard 12-tone equal temperament system, each semitone is the same frequency ratio above the previous one:
semitone ratio = 2^(1/12) ≈ 1.05946
After twelve semitones, the frequency doubles, which is one octave. For example, A3 is 220 Hz, A4 is 440 Hz, and A5 is 880 Hz.
To calculate a note from its distance in semitones from A4:
f = 440 × 2^(n / 12)
n is the signed number of semitones above or below A4. C4 is nine semitones below A4, so its frequency is about 261.63 Hz. For simple buzzer melodies, frequencies are usually rounded to the nearest whole hertz.
2) Do-Re-Mi and note frequencies
Do-Re-Mi are solfège syllables: they label scale steps. In movable Do, Do is the first note of whichever major key you are playing. In C major, Do is C, so the scale uses the white keys from C to the next C.
| Solfège in C major | Note | Frequency (approx.) |
|---|---|---|
| Do | C4 | 262 Hz |
| Re | D4 | 294 Hz |
| Mi | E4 | 330 Hz |
| Fa | F4 | 349 Hz |
| Sol | G4 | 392 Hz |
| La | A4 | 440 Hz |
| Ti (Si) | B4 | 494 Hz |
| Do | C5 | 523 Hz |
These are equal-tempered frequencies rounded for an Arduino sketch. The exact pitch depends on tuning reference and temperament; A4 = 440 Hz is the common modern reference, not a law of nature.
A note’s frequency sets its pitch. How long it plays sets its duration. Loudness depends on the sounder, its drive, and the signal waveform; changing frequency does not control volume.
3) How PWM makes a tone
PWM (Pulse Width Modulation) is a repeating digital signal with a chosen frequency and duty cycle. A 50% duty cycle spends half of each cycle HIGH and half LOW:
One cycle at 50% duty:
HIGH ┌─────┐ ┌─────┐
│ │ │ │
LOW ──┘ └─────┘ └──
<---- one period ---->
The pitch is set by how often the waveform repeats. For a 440 Hz A4, its period is:
period = 1 / frequency = 1 / 440 ≈ 0.00227 seconds = 2.27 ms
For a simple piezo buzzer, a square wave at the note frequency is enough to produce a recognizable pitch. In this use, the timer output toggles at the audio frequency. This is different from PWM audio synthesis, where a much faster PWM carrier is filtered to reconstruct a varying audio waveform. A square-wave buzzer melody is simple, but it cannot reproduce rich instrument timbres or recorded music.
4) Parts and wiring
You need:
- Arduino Uno R3 (or a compatible 5 V Arduino board)
- Passive piezo buzzer / piezo sounder (not an active buzzer)
- Breadboard and jumper wires
- Optional 100–220 Ω series resistor for a small piezo element
Connect the buzzer’s positive lead to D8 through the resistor, and its other lead to GND:
Arduino D8 ──[100–220 Ω]── (+) passive piezo (-) ── GND
The resistor is a conservative series part for a small piezo element. Do not connect a low-impedance 4 Ω or 8 Ω speaker directly to an Arduino GPIO pin; use a suitable transistor/audio driver and follow its current and voltage limits. An active buzzer has its own oscillator and usually makes one fixed pitch when powered, so it is not suitable for playing different notes with this method.
5) Complete Arduino example: play Do-Re-Mi
The Arduino Uno R3 uses an ATmega328P at 16 MHz. The Arduino tone() function configures a timer to generate a square wave on the selected pin. The frequency argument is in hertz and the optional duration is in milliseconds.
const byte BUZZER_PIN = 8;
// C major, octave 4 to octave 5 (rounded to nearest Hz)
const unsigned int notes[] = {
262, // C4 - Do
294, // D4 - Re
330, // E4 - Mi
349, // F4 - Fa
392, // G4 - Sol
440, // A4 - La
494, // B4 - Ti / Si
523 // C5 - Do
};
const char* names[] = {
"Do", "Re", "Mi", "Fa", "Sol", "La", "Ti", "Do"
};
const unsigned int noteDurationMs = 400;
const unsigned int gapMs = 80;
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
for (byte i = 0; i < 8; i++) {
Serial.print(names[i]);
Serial.print(" - ");
Serial.print(notes[i]);
Serial.println(" Hz");
tone(BUZZER_PIN, notes[i], noteDurationMs);
delay(noteDurationMs + gapMs);
noTone(BUZZER_PIN);
}
delay(1000); // pause before repeating the scale
}
Upload the sketch, then open Serial Monitor at 9600 baud. You should hear the scale and see each solfège name with its frequency.
What the key lines do
tone(BUZZER_PIN, notes[i], noteDurationMs)starts a square wave at the selected note frequency.- The duration argument tells Arduino to stop that tone after the specified number of milliseconds;
tone()itself does not wait. delay(noteDurationMs + gapMs)leaves a short silence between notes and gives the note time to finish.noTone(BUZZER_PIN)explicitly stops the output before the next note.
This basic melody uses delay(), so the program cannot do other work during the scale. In an interactive product, track the note start time with millis() and advance the melody without blocking.
6) Change pitch, octave, and rhythm
Change octave
Multiply a note frequency by two to move up one octave, or divide by two to move down one octave. For example:
A3 = 220 Hz
A4 = 440 Hz
A5 = 880 Hz
Change note duration
A frequency of 440 Hz always gives A4. Play it for 200 ms for a short note, or 800 ms for a longer one. To create rhythm, give each note its own duration, such as quarter, half, and whole notes. At 120 beats per minute, one beat is 500 ms.
Play a rest
A rest is silence, not a low-frequency note. Call noTone() and wait for the rest duration:
noTone(BUZZER_PIN);
delay(250); // quarter-second rest
Sharps and flats
A sharp raises a note by one semitone; a flat lowers it by one semitone. For instance, C-sharp4 is approximately 277 Hz and D-flat4 is the same equal-tempered pitch. A lookup table is often simplest for a fixed melody; use the equal-temperament formula when you need to calculate arbitrary notes.
7) Is tone() really PWM?
It is related to PWM because both use a timer to create a repeating digital waveform. But Arduino’s tone() is the clearest choice for playing a single-frequency square-wave note. It is not the same as analogWrite():
analogWrite()typically generates PWM at a board-specific fixed frequency and changes duty cycle.tone(pin, frequency)changes the repetition rate to set pitch, usually with about a 50% duty cycle.- A piezo turns the changing electrical signal into vibration and sound; a low-pass filter is not needed for this simple buzzer tone.
On classic Arduino boards, tone() uses a hardware timer. On the Uno and many non-Mega boards, it interferes with PWM output on pins 3 and 11 while a tone is active. Pin D8 is used here as the sound output; avoid relying on PWM from those pins at the same time. Timer behavior can vary on other Arduino boards, so check the board-specific documentation.
For actual audio waveforms, samples, or volume control, a faster PWM carrier plus a low-pass filter or a DAC is needed. See the site’s PWM audio synthesis guide for that different technique.
8) Troubleshooting
- No sound: confirm the buzzer is passive, the leads contact the breadboard, and Arduino GND is connected to the buzzer.
- Only one fixed beep: you may have an active buzzer, which contains its own oscillator.
- Pitch seems wrong: check the note table and ensure the frequency value is an integer in hertz; buzzer resonance can make some pitches sound louder than others.
- Very quiet output: small piezo sounders vary in efficiency and orientation. Do not solve this by connecting a low-impedance speaker directly to the GPIO; use an appropriate driver circuit.
- Other PWM output changed:
tone()uses a timer. On Uno-class boards it conflicts withanalogWrite()on pins 3 and 11 while playing.
In short
Musical notes correspond to frequencies: A4 is commonly 440 Hz, and each equal-tempered semitone is a factor of about 1.059 apart. A timer-driven square wave at a note’s frequency can play simple melodies through a passive piezo. On Arduino Uno, tone() handles the timer setup, while noTone() stops the sound. This is a practical use of PWM-like timer output for pitches, distinct from filtered PWM used to approximate full audio.