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.

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.

Quick Test

Not sure which type you have? Connect it to 5V and GND.

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

The noTone() Function

noTone(pin);  // Stop the tone on this pin

Important: Only one tone() can play at a time on the Uno. Calling tone() on a second pin automatically stops the first. The Uno has only one hardware timer available for tone(). 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:


9.4 Sound Input: Microphone Modules

Types of Microphone Modules

Analog output modules (e.g., MAX4466, KY-037 analog pin):

Digital output modules (e.g., KY-037 digital pin, KY-038):

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:

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

  1. Always provide a mute option. Nobody wants to debug a project that beeps every second with no way to silence it.
  2. Keep alerts short. A 100ms beep is informative. A 5-second siren is annoying.
  3. Use different pitches for different events. High pitch = attention/warning. Low pitch = confirmation/acknowledgment.
  4. Don't play melodies in loop() without timing. The melody blocks everything while it plays if you use delay(). Use millis() 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:


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 →