Module 9: Sound & Audio
Level: 🟡 Intermediate
Board: Arduino Uno
Prerequisites: Module 4
Estimated time: 50–70 minutes
Goal: Generate sound output and respond to audio input.
What You'll Learn
Sound adds a whole new dimension to your projects, from simple beep alerts to playing melodies, from clap-detection to voice-level monitoring. In this module, you'll understand the difference between active and passive buzzers, play music with the tone() function, read audio input from a microphone module, and use a transistor to drive a speaker loud enough to actually hear across a room.
9.1 Passive vs. Active Buzzers
Active Buzzer
An active buzzer has a built-in oscillator. Apply voltage, it beeps. Remove voltage, it stops. One fixed tone.
- How to drive:
digitalWrite(pin, HIGH)to beep,LOWto stop - Sound: Fixed frequency (usually ~2.7 kHz)
- Identification: Often has a sticker on top, sealed case. Polarity matters (+ and − markings).
Passive Buzzer
A passive buzzer has no oscillator. You must provide the oscillation: a square wave at the frequency you want to hear. This means you control the pitch.
- How to drive:
tone(pin, frequency)to play a note,noTone(pin)to stop - Sound: Any frequency you generate (typically 31 Hz – 65535 Hz for
tone()) - Identification: Often has an exposed PCB on the bottom. No polarity (but breakout boards may have marked pins).
Quick Test
Not sure which type you have? Connect it to 5V and GND.
- If it beeps immediately → active
- If it's silent → passive (needs a signal)
For this course: We use passive buzzers because they can play melodies. Active buzzers are useful for simple on/off alerts in projects where you just need an audible warning.
Wiring a Passive Buzzer
Simple connection for basic use:
Arduino digital pin → Passive buzzer → GND
A 100Ω resistor in series is optional but limits current and slightly reduces volume. For louder output, you'll use a transistor (section 9.5).
9.2 tone() and noTone(): Making Sound
The tone() Function
tone(pin, frequency); // Play continuously
tone(pin, frequency, duration); // Play for specified milliseconds
- pin: Any digital pin
- frequency: In Hz. Middle C is 262 Hz. Range: 31–65535 Hz.
- duration (optional): Milliseconds. If omitted, plays until
noTone()is called.
The noTone() Function
noTone(pin); // Stop the tone on this pin
Important: Only one
tone()can play at a time on the Uno. Callingtone()on a second pin automatically stops the first. The Uno has only one hardware timer available fortone(). For polyphonic sound, you need a different approach (direct timer manipulation or an external sound chip).
Basic Beep Patterns
/*
* Module 9: Basic sound patterns
* Demonstrates different uses of tone()
*/
const int BUZZER_PIN = 5;
void setup() {
// No pinMode needed — tone() handles it
}
void loop() {
// Simple beep
tone(BUZZER_PIN, 1000, 200); // 1 kHz for 200ms
delay(500);
// Rising pitch
for (int freq = 200; freq <= 2000; freq += 50) {
tone(BUZZER_PIN, freq, 30);
delay(30);
}
delay(300);
// Falling pitch
for (int freq = 2000; freq >= 200; freq -= 50) {
tone(BUZZER_PIN, freq, 30);
delay(30);
}
delay(300);
// Alert pattern (two-tone siren)
for (int i = 0; i < 3; i++) {
tone(BUZZER_PIN, 800, 150);
delay(200);
tone(BUZZER_PIN, 1200, 150);
delay(200);
}
delay(1000);
}
9.3 Playing Melodies: Note Frequencies and Timing
Musical Note Frequencies
Each musical note has a specific frequency. Here's a reference table for one octave:
| Note | Frequency (Hz) | Note | Frequency (Hz) |
|---|---|---|---|
| C4 | 262 | C5 | 523 |
| D4 | 294 | D5 | 587 |
| E4 | 330 | E5 | 659 |
| F4 | 349 | F5 | 698 |
| G4 | 392 | G5 | 784 |
| A4 | 440 | A5 | 880 |
| B4 | 494 | B5 | 988 |
To go up an octave, double the frequency. To go down, halve it.
Defining Notes as Constants
Create a header with all note frequencies for clean code:
// Note frequencies (Hz) — pitches.h
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_D5 587
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_G5 784
#define NOTE_A5 880
#define NOTE_B5 988
#define NOTE_C6 1047
#define REST 0
Playing a Melody: Data-Driven Approach
Store the melody as arrays of notes and durations. This makes it easy to change tunes without rewriting logic.
/*
* Module 9: Melody Player
* Plays "Ode to Joy" using arrays for notes and durations.
*/
const int BUZZER_PIN = 5;
// Ode to Joy (simplified)
int melody[] = {
NOTE_E4, NOTE_E4, NOTE_F4, NOTE_G4,
NOTE_G4, NOTE_F4, NOTE_E4, NOTE_D4,
NOTE_C4, NOTE_C4, NOTE_D4, NOTE_E4,
NOTE_E4, NOTE_D4, NOTE_D4, REST
};
// Duration: 4 = quarter note, 8 = eighth note, 2 = half note
int durations[] = {
4, 4, 4, 4,
4, 4, 4, 4,
4, 4, 4, 4,
4, 8, 2, 4
};
const int TEMPO = 144; // BPM
int noteCount = sizeof(melody) / sizeof(melody[0]);
void playMelody() {
// Quarter note duration in ms = 60000 / BPM
int quarterNote = 60000 / TEMPO;
for (int i = 0; i < noteCount; i++) {
// Calculate note duration from the fraction
int noteDuration = quarterNote * 4 / durations[i];
if (melody[i] == REST) {
noTone(BUZZER_PIN);
} else {
tone(BUZZER_PIN, melody[i], noteDuration * 0.9);
}
// Wait for note duration + small gap between notes
delay(noteDuration);
noTone(BUZZER_PIN);
}
}
void setup() {
playMelody();
}
void loop() {
// Play once. Remove this comment to loop: playMelody(); delay(2000);
}
Timing and Musical Feel
The gap between notes (called "staccato" vs "legato") dramatically affects how the melody sounds:
noteDuration * 0.9in thetone()call means the note plays for 90% of its duration, leaving a 10% silence gap. This creates a clean, separated feel.- For smoother (legato) playing, use
noteDuration * 0.95or higher. - For sharper (staccato) playing, use
noteDuration * 0.7.
9.4 Sound Input: Microphone Modules
Types of Microphone Modules
Analog output modules (e.g., MAX4466, KY-037 analog pin):
- Output a voltage proportional to sound level
- Connect to an analog pin
- Good for: sound level detection, volume meters
Digital output modules (e.g., KY-037 digital pin, KY-038):
- Output HIGH when sound exceeds a threshold (adjustable potentiometer)
- Connect to a digital pin
- Good for: clap detection, sound-trigger events
Reading Sound Level (Analog)
Sound is an AC signal centered around a DC bias (typically VCC/2 = ~2.5V for a 5V module). To measure loudness, you sample rapidly and find the peak-to-peak amplitude:
/*
* Module 9: Sound Level Meter
*
* Circuit:
* - Microphone module OUT → A0
* - VCC → 3.3V or 5V (check your module)
* - GND → GND
*
* Board: Arduino Uno
*/
const int MIC_PIN = A0;
const int SAMPLE_WINDOW = 50; // Sample window in milliseconds
void setup() {
Serial.begin(9600);
}
int measureSoundLevel() {
unsigned long startTime = millis();
int signalMax = 0;
int signalMin = 1023;
// Collect samples for SAMPLE_WINDOW milliseconds
while (millis() - startTime < SAMPLE_WINDOW) {
int sample = analogRead(MIC_PIN);
if (sample > signalMax) signalMax = sample;
if (sample < signalMin) signalMin = sample;
}
// Peak-to-peak amplitude
return signalMax - signalMin;
}
void loop() {
int level = measureSoundLevel();
// Map to a 0–10 visual bar
int barLength = map(level, 0, 600, 0, 10);
barLength = constrain(barLength, 0, 10);
Serial.print("Level: ");
Serial.print(level);
Serial.print(" ");
for (int i = 0; i < barLength; i++) {
Serial.print("#");
}
Serial.println();
}
Clap Detection (Digital or Analog)
/*
* Module 9: Clap Detection
* Detects a clap (sudden loud sound) and toggles an LED.
* Uses analog microphone for sensitivity control.
*/
const int MIC_PIN = A0;
const int LED_PIN = 8;
const int CLAP_THRESHOLD = 200; // Adjust based on your environment
const unsigned long DEBOUNCE_MS = 300; // Ignore sounds within 300ms of a clap
int ledState = LOW;
unsigned long lastClap = 0;
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int level = measureSoundLevel(); // From above
if (level > CLAP_THRESHOLD && (millis() - lastClap > DEBOUNCE_MS)) {
lastClap = millis();
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
Serial.print("CLAP detected! Level: ");
Serial.println(level);
}
}
int measureSoundLevel() {
unsigned long startTime = millis();
int signalMax = 0;
int signalMin = 1023;
while (millis() - startTime < 50) {
int sample = analogRead(MIC_PIN);
if (sample > signalMax) signalMax = sample;
if (sample < signalMin) signalMin = sample;
}
return signalMax - signalMin;
}
Double-Clap Detection
For more reliable detection (avoid false triggers from random noise), require two claps within a time window:
unsigned long firstClap = 0;
bool waitingForSecond = false;
const unsigned long DOUBLE_CLAP_WINDOW = 700; // Max time between two claps
void loop() {
int level = measureSoundLevel();
if (level > CLAP_THRESHOLD && (millis() - lastClap > DEBOUNCE_MS)) {
lastClap = millis();
if (waitingForSecond && (millis() - firstClap < DOUBLE_CLAP_WINDOW)) {
// Second clap within window — toggle!
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
Serial.println("DOUBLE CLAP — toggled!");
waitingForSecond = false;
} else {
// First clap — wait for second
firstClap = millis();
waitingForSecond = true;
Serial.println("First clap detected...");
}
}
// Timeout — reset if second clap doesn't come
if (waitingForSecond && (millis() - firstClap > DOUBLE_CLAP_WINDOW)) {
waitingForSecond = false;
}
}
9.5 Driving a Speaker with a Transistor
Why You Can't Just Connect a Speaker
An Arduino pin can source a maximum of 40 mA. A small 8Ω speaker at 5V would try to draw:
I = V / R = 5V / 8Ω = 625 mA
That's 15× the pin's limit. The pin would be damaged.
The Solution: NPN Transistor as a Switch
A transistor lets a small current (from the Arduino) control a large current (to the speaker) from a separate supply.
Components:
- NPN transistor (2N2222 or BC547)
- 1kΩ resistor (base resistor)
- 8Ω or 16Ω speaker
- Flyback diode (1N4007) across the speaker (optional but good practice for inductive loads)
Wiring
Arduino PWM pin → 1kΩ resistor → Transistor Base
Transistor Collector → Speaker → VCC (5V from external supply)
Transistor Emitter → GND
The Arduino's tone() signal goes through the base resistor to the transistor. The transistor switches the speaker on and off at the tone frequency, driven by the external power supply.
Code
The code is identical to the passive buzzer code: tone() and noTone(). The transistor is transparent to the software; it just amplifies the current.
const int SPEAKER_PIN = 5; // Same as before — transistor on this pin
void setup() {
tone(SPEAKER_PIN, 440, 1000); // Play A4 for 1 second
}
Volume Control with PWM
You can't easily control volume with tone() alone (it generates a fixed-amplitude square wave). But you can add a potentiometer as a voltage divider between the Arduino pin and the transistor base to attenuate the signal. Or use a digital potentiometer for code-controlled volume.
A simpler approach: analogWrite() with varying duty cycle before the transistor, though this changes the waveform rather than true volume.
9.6 Sound Design for Projects
Alert Sounds
Different alert patterns convey different urgencies:
void alertWarning() {
// Two gentle beeps
tone(BUZZER_PIN, 800, 100);
delay(200);
tone(BUZZER_PIN, 800, 100);
delay(500);
}
void alertUrgent() {
// Rapid three-tone ascending
tone(BUZZER_PIN, 800, 80);
delay(100);
tone(BUZZER_PIN, 1000, 80);
delay(100);
tone(BUZZER_PIN, 1200, 80);
delay(300);
}
void alertCritical() {
// Continuous alternating siren
for (int i = 0; i < 5; i++) {
tone(BUZZER_PIN, 1500, 100);
delay(120);
tone(BUZZER_PIN, 800, 100);
delay(120);
}
}
void alertSuccess() {
// Happy ascending arpeggio
tone(BUZZER_PIN, NOTE_C5, 100);
delay(120);
tone(BUZZER_PIN, NOTE_E5, 100);
delay(120);
tone(BUZZER_PIN, NOTE_G5, 150);
delay(200);
}
User Experience Considerations
- Always provide a mute option. Nobody wants to debug a project that beeps every second with no way to silence it.
- Keep alerts short. A 100ms beep is informative. A 5-second siren is annoying.
- Use different pitches for different events. High pitch = attention/warning. Low pitch = confirmation/acknowledgment.
- Don't play melodies in
loop()without timing. The melody blocks everything while it plays if you usedelay(). Usemillis()for non-blocking melody playback.
Module Project: Melody Doorbell
Objective
Build a PIR-triggered melody player that plays a configurable doorbell tune when someone approaches. Includes a transistor-driven speaker for audible volume and a button to cycle between different melodies.
Components Needed
| Component | Quantity | Notes |
|---|---|---|
| Arduino Uno | 1 | |
| USB-B cable | 1 | |
| Breadboard | 1 | |
| PIR sensor (HC-SR501) | 1 | |
| Passive buzzer or 8Ω speaker | 1 | |
| NPN transistor (2N2222) | 1 | For speaker; skip if using buzzer directly |
| 1kΩ Resistor | 1 | Transistor base resistor |
| Pushbutton | 1 | Melody selector |
| LED | 1 | Motion indicator |
| 220Ω Resistor | 1 | For LED |
| Jumper wires | 8+ |
The Code
/*
* Module 9 Project: Melody Doorbell
*
* PIR sensor detects motion and plays a melody through
* a transistor-driven speaker. Button cycles melodies.
*
* Circuit:
* - PIR OUT → pin 3
* - Speaker: pin 5 → 1kΩ → 2N2222 base, collector → speaker → 5V, emitter → GND
* (Or for passive buzzer directly: pin 5 → buzzer → GND)
* - Button → pin 7 (INPUT_PULLUP)
* - LED → 220Ω → pin 8
*
* Board: Arduino Uno
*/
// --- Pin definitions ---
const int PIR_PIN = 3;
const int SPEAKER_PIN = 5;
const int BUTTON_PIN = 7;
const int LED_PIN = 8;
// --- Note definitions ---
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_D5 587
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_G5 784
#define REST 0
// --- Melody 1: Westminster Chimes (classic doorbell) ---
const int melody1[] = { NOTE_E4, NOTE_C4, NOTE_D4, NOTE_G4, REST, NOTE_G4, NOTE_D4, NOTE_E4, NOTE_C4 };
const int durations1[] = { 4, 4, 4, 2, 8, 4, 4, 4, 2 };
const int melody1Length = 9;
// --- Melody 2: Simple ding-dong ---
const int melody2[] = { NOTE_E5, NOTE_C5 };
const int durations2[] = { 4, 2 };
const int melody2Length = 2;
// --- Melody 3: Cheerful jingle ---
const int melody3[] = { NOTE_C5, NOTE_E5, NOTE_G5, NOTE_E5, NOTE_C5, REST, NOTE_G5, NOTE_E5, NOTE_C5 };
const int durations3[] = { 8, 8, 4, 8, 8, 8, 4, 8, 2 };
const int melody3Length = 9;
// --- State ---
int currentMelody = 0;
const int NUM_MELODIES = 3;
const char* melodyNames[] = {"Westminster", "Ding-Dong", "Jingle"};
bool playing = false;
bool pirReady = false;
unsigned long lastMotion = 0;
const unsigned long COOLDOWN = 5000; // Don't retrigger for 5 seconds
const unsigned long WARMUP_TIME = 60000;
// --- Debounce ---
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
void playNote(int frequency, int durationMs) {
if (frequency == REST) {
noTone(SPEAKER_PIN);
} else {
tone(SPEAKER_PIN, frequency, durationMs * 0.9);
}
delay(durationMs);
noTone(SPEAKER_PIN);
}
void playCurrentMelody() {
const int* notes;
const int* durs;
int length;
int tempo = 120;
int quarterNote = 60000 / tempo;
switch (currentMelody) {
case 0: notes = melody1; durs = durations1; length = melody1Length; break;
case 1: notes = melody2; durs = durations2; length = melody2Length; break;
case 2: notes = melody3; durs = durations3; length = melody3Length; break;
}
playing = true;
digitalWrite(LED_PIN, HIGH);
for (int i = 0; i < length; i++) {
int noteDuration = quarterNote * 4 / durs[i];
playNote(notes[i], noteDuration);
}
digitalWrite(LED_PIN, LOW);
playing = false;
}
void setup() {
Serial.begin(9600);
pinMode(PIR_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
Serial.println("Melody Doorbell — Module 9");
Serial.println("Warming up PIR sensor (60s)...");
Serial.print("Current melody: ");
Serial.println(melodyNames[currentMelody]);
// Play a startup confirmation sound
tone(SPEAKER_PIN, NOTE_C5, 100);
delay(150);
tone(SPEAKER_PIN, NOTE_E5, 100);
delay(150);
tone(SPEAKER_PIN, NOTE_G5, 200);
delay(250);
noTone(SPEAKER_PIN);
}
void loop() {
unsigned long now = millis();
// --- PIR warm-up ---
if (!pirReady) {
if (now >= WARMUP_TIME) {
pirReady = true;
Serial.println("PIR ready — doorbell active");
alertSuccess();
} else {
// Blink LED during warmup
digitalWrite(LED_PIN, (now / 500) % 2);
delay(100);
return;
}
}
// --- Button: cycle melodies ---
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = now;
}
if ((now - lastDebounceTime) > 50) {
static int buttonState = HIGH;
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW && !playing) {
currentMelody = (currentMelody + 1) % NUM_MELODIES;
Serial.print("Melody: ");
Serial.println(melodyNames[currentMelody]);
// Preview: play a short snippet
const int* notes;
switch (currentMelody) {
case 0: notes = melody1; break;
case 1: notes = melody2; break;
case 2: notes = melody3; break;
}
tone(SPEAKER_PIN, notes[0], 200);
delay(250);
noTone(SPEAKER_PIN);
}
}
}
lastButtonState = reading;
// --- PIR: detect motion and play ---
if (!playing && digitalRead(PIR_PIN) == HIGH) {
if (now - lastMotion > COOLDOWN) {
lastMotion = now;
Serial.println("Motion detected — playing doorbell");
playCurrentMelody();
}
}
}
void alertSuccess() {
tone(SPEAKER_PIN, NOTE_C5, 100);
delay(120);
tone(SPEAKER_PIN, NOTE_E5, 100);
delay(120);
tone(SPEAKER_PIN, NOTE_G5, 150);
delay(200);
noTone(SPEAKER_PIN);
}
Circuit Diagram (SVG)
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 850 450" font-family="monospace" font-size="11">
<!-- Arduino -->
<rect x="30" y="80" width="140" height="300" fill="#1a7a8a" stroke="#333" stroke-width="2" rx="8"/>
<text x="100" y="110" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Arduino Uno</text>
<!-- Pins -->
<rect x="170" y="140" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="150" text-anchor="middle" fill="white" font-size="8">5V</text>
<rect x="170" y="160" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="170" text-anchor="middle" fill="white" font-size="8">GND</text>
<rect x="170" y="200" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="210" text-anchor="middle" fill="white" font-size="8">D3</text>
<rect x="170" y="240" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="250" text-anchor="middle" fill="white" font-size="8">D5</text>
<rect x="170" y="280" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="290" text-anchor="middle" fill="white" font-size="8">D7</text>
<rect x="170" y="320" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="330" text-anchor="middle" fill="white" font-size="8">D8</text>
<!-- === PIR SENSOR === -->
<rect x="350" y="100" width="100" height="55" fill="#886644" stroke="#333" stroke-width="2" rx="6"/>
<circle cx="400" cy="122" r="15" fill="none" stroke="white" stroke-width="1.5"/>
<text x="400" y="148" text-anchor="middle" fill="white" font-size="9">PIR HC-SR501</text>
<line x1="192" y1="147" x2="350" y2="115" stroke="red" stroke-width="1.5"/>
<line x1="192" y1="207" x2="350" y2="128" stroke="green" stroke-width="1.5"/>
<text x="270" y="162" font-size="8" fill="green">OUT (D3)</text>
<line x1="192" y1="167" x2="350" y2="143" stroke="blue" stroke-width="1.5"/>
<!-- === SPEAKER + TRANSISTOR === -->
<text x="550" y="195" text-anchor="middle" font-size="10" font-weight="bold" fill="#333">Speaker Circuit</text>
<!-- Signal from D5 -->
<line x1="192" y1="247" x2="380" y2="247" stroke="orange" stroke-width="2"/>
<text x="285" y="242" font-size="8" fill="orange">Signal (D5)</text>
<!-- Base resistor -->
<rect x="380" y="240" width="45" height="14" fill="none" stroke="#333" stroke-width="1.5" rx="2"/>
<text x="402" y="250" text-anchor="middle" font-size="8">1kΩ</text>
<!-- Transistor -->
<circle cx="470" cy="250" r="18" fill="none" stroke="#333" stroke-width="2"/>
<text x="470" y="254" text-anchor="middle" font-size="9" font-weight="bold">NPN</text>
<text x="470" y="275" text-anchor="middle" font-size="7" fill="#666">2N2222</text>
<line x1="425" y1="247" x2="452" y2="247" stroke="#333" stroke-width="1.5"/>
<!-- Collector to speaker -->
<line x1="470" y1="232" x2="470" y2="210" stroke="#333" stroke-width="1.5"/>
<!-- Speaker -->
<rect x="448" y="190" width="44" height="20" fill="none" stroke="#333" stroke-width="2" rx="4"/>
<text x="470" y="204" text-anchor="middle" font-size="8">🔊</text>
<text x="520" y="204" font-size="8" fill="#666">8Ω</text>
<!-- Speaker to VCC -->
<line x1="470" y1="190" x2="470" y2="170" stroke="red" stroke-width="1.5"/>
<line x1="470" y1="170" x2="380" y2="170" stroke="red" stroke-width="1.5"/>
<line x1="380" y1="170" x2="380" y2="147" stroke="red" stroke-width="1"/>
<line x1="380" y1="147" x2="192" y2="147" stroke="red" stroke-width="1"/>
<text x="430" y="165" font-size="8" fill="red">5V</text>
<!-- Emitter to GND -->
<line x1="470" y1="268" x2="470" y2="300" stroke="blue" stroke-width="1.5"/>
<line x1="470" y1="300" x2="350" y2="300" stroke="blue" stroke-width="1.5"/>
<line x1="350" y1="300" x2="350" y2="167" stroke="blue" stroke-width="1"/>
<text x="470" y="315" text-anchor="middle" font-size="8" fill="blue">GND</text>
<!-- === BUTTON === -->
<rect x="350" y="340" width="70" height="30" fill="none" stroke="#333" stroke-width="2" rx="4"/>
<text x="385" y="360" text-anchor="middle" font-size="9">Button</text>
<line x1="192" y1="287" x2="350" y2="350" stroke="purple" stroke-width="1.5"/>
<text x="270" y="315" font-size="8" fill="purple">D7</text>
<line x1="420" y1="358" x2="470" y2="358" stroke="blue" stroke-width="1.5"/>
<line x1="470" y1="358" x2="470" y2="300" stroke="blue" stroke-width="1"/>
<!-- === LED === -->
<line x1="192" y1="327" x2="280" y2="370" stroke="#cc0000" stroke-width="1.5"/>
<rect x="280" y="365" width="40" height="12" fill="none" stroke="#333" stroke-width="1" rx="2"/>
<text x="300" y="374" text-anchor="middle" font-size="7">220Ω</text>
<polygon points="325,365 325,377 340,371" fill="none" stroke="red" stroke-width="1.5"/>
<line x1="340" y1="371" x2="360" y2="371" stroke="blue" stroke-width="1"/>
<text x="270" y="365" font-size="8" fill="#cc0000">D8</text>
<!-- Melody list -->
<rect x="600" y="100" width="200" height="100" fill="#f8f8f8" stroke="#ccc" stroke-width="1" rx="4"/>
<text x="700" y="120" text-anchor="middle" font-size="10" font-weight="bold">Melodies</text>
<text x="610" y="140" font-size="9">1. Westminster Chimes</text>
<text x="610" y="156" font-size="9">2. Ding-Dong</text>
<text x="610" y="172" font-size="9">3. Cheerful Jingle</text>
<text x="610" y="192" font-size="8" fill="#666">Button cycles through melodies</text>
</svg>
Circuit Schema (JSON)
{
"module": 9,
"project": "Melody Doorbell",
"board": "Arduino Uno",
"schematic": {
"components": [
{
"id": "U1",
"type": "arduino_uno",
"pins_used": {
"5V": "net_vcc", "GND": "net_gnd",
"D3": "net_pir_out", "D5": "net_speaker_signal",
"D7": "net_button", "D8": "net_led"
}
},
{
"id": "PIR1",
"type": "pir_sensor",
"model": "HC-SR501",
"current_draw": "65mA",
"warmup_time": "60s",
"pins": { "vcc": "net_vcc", "out": "net_pir_out", "gnd": "net_gnd" }
},
{
"id": "Q1",
"type": "npn_transistor",
"model": "2N2222",
"purpose": "Speaker current amplifier",
"pins": { "base": "net_base", "collector": "net_collector", "emitter": "net_gnd" }
},
{
"id": "R_BASE",
"type": "resistor", "value": "1000", "unit": "ohm",
"purpose": "Transistor base current limiter",
"pins": { "pin1": "net_speaker_signal", "pin2": "net_base" }
},
{
"id": "SPK1",
"type": "speaker",
"impedance": "8ohm",
"pins": { "positive": "net_vcc", "negative": "net_collector" }
},
{
"id": "SW1",
"type": "pushbutton",
"purpose": "Melody selector",
"pins": { "pinA": "net_button", "pinB": "net_gnd" }
},
{
"id": "R_LED",
"type": "resistor", "value": "220", "unit": "ohm",
"pins": { "pin1": "net_led", "pin2": "net_led_anode" }
},
{
"id": "LED1",
"type": "led", "color": "green",
"purpose": "Motion indicator",
"pins": { "anode": "net_led_anode", "cathode": "net_gnd" }
}
],
"nets": [
{ "name": "net_vcc", "nodes": ["U1.5V", "PIR1.vcc", "SPK1.positive"] },
{ "name": "net_gnd", "nodes": ["U1.GND", "PIR1.gnd", "Q1.emitter", "SW1.pinB", "LED1.cathode"] },
{ "name": "net_pir_out", "nodes": ["U1.D3", "PIR1.out"] },
{ "name": "net_speaker_signal", "nodes": ["U1.D5", "R_BASE.pin1"] },
{ "name": "net_base", "nodes": ["R_BASE.pin2", "Q1.base"] },
{ "name": "net_collector", "nodes": ["Q1.collector", "SPK1.negative"] },
{ "name": "net_button", "nodes": ["U1.D7", "SW1.pinA"] },
{ "name": "net_led", "nodes": ["U1.D8", "R_LED.pin1"] },
{ "name": "net_led_anode", "nodes": ["R_LED.pin2", "LED1.anode"] }
],
"power": {
"source": "USB",
"total_current_ma": 185,
"breakdown": "Arduino(50) + PIR(65) + Speaker peak(50) + LED(20)"
}
},
"code": { "filename": "module09_melody_doorbell.ino", "language": "cpp" },
"validation": {
"expected_behavior": "PIR detects motion → melody plays through speaker. Button cycles between 3 melodies. LED lights during playback. 5-second cooldown between triggers.",
"common_mistakes": [
"No sound: passive buzzer connected to wrong pin, or transistor wired incorrectly (check B/C/E pinout from datasheet)",
"Very quiet: using buzzer without transistor — expected for direct pin drive",
"Melody plays constantly: PIR sensitivity too high — adjust potentiometer, or PIR still warming up",
"Button doesn't change melody: melody is blocking — button only works between melodies",
"Garbled notes: another call to tone() before noTone() on the previous note"
]
}
}
Experiments to Try
Experiment 1: Non-Blocking Melody Playback
Rewrite the melody player using millis() so it doesn't block. This lets the button and PIR remain responsive during playback. Hint: use a state variable to track which note you're on, and advance when enough time has passed.
Experiment 2: Sound-Reactive LED Bar
Connect 8 LEDs via a shift register (from Module 8) and drive them based on the microphone's sound level, creating a visual VU meter.
Experiment 3: Custom Doorbell Tunes
Transcribe your favorite short melody into the note/duration array format. Use online sheet music converted to note names, then map to the frequency constants.
Self-Check: Module 9
Before moving to Module 10, make sure you can:
- Explain the difference between active and passive buzzers
- Use
tone()andnoTone()to generate specific frequencies - Store and play melodies using note/duration arrays
- Read sound levels from a microphone module
- Implement clap detection with debouncing
- Wire a transistor to drive a speaker from an Arduino pin
- Design appropriate alert sounds for different events
- Build the melody doorbell project with PIR trigger and melody selection
Key Terms Glossary
| Term | Definition |
|---|---|
| Active buzzer | Contains internal oscillator. Apply voltage to beep at a fixed frequency. |
| Passive buzzer | No internal oscillator. You provide the frequency via tone(). |
| tone() | Arduino function that generates a square wave at a specified frequency |
| noTone() | Stops the tone output on a pin |
| Frequency (Hz) | Cycles per second. Determines pitch; higher = higher pitch. |
| BPM (Beats Per Minute) | Tempo. Determines how fast a melody plays. |
| Quarter note | Standard beat unit. Duration = 60000ms / BPM |
| Peak-to-peak | The difference between the highest and lowest values in a signal, used for sound level. |
| NPN transistor | A three-pin semiconductor that lets a small base current control a larger collector current |
| Base resistor | Limits current flowing into the transistor's base from the Arduino pin |
| Collector | The transistor pin connected to the load (speaker) |
| Emitter | The transistor pin connected to ground |
| Double-clap detection | Requiring two sound events within a time window for reliable triggering |
| PROGMEM | Stores constant data in flash memory, saving RAM |
Previous: ← Module 8: Displays & Visual Feedback Next: Module 10: Motors & Movement →