The arduino tone function generates a 50% duty cycle square wave on a digital pin, primarily used for driving piezo buzzers and basic audio alerts. On the Arduino Uno R3 and Nano V3 (ATmega328P), it operates between 31 Hz and 65,535 Hz. However, because it fundamentally hijacks hardware timers to maintain frequency accuracy, it causes silent failures and compiler errors if you mix it with analogWrite(), servo libraries, or IR receivers.
Before wiring up a speaker, you must understand which timers are being consumed. Below is the definitive mapping for the ATmega328P, detailing exactly how the tone() function interacts with your other peripherals.
ATmega328P Timer Mapping and PWM Conflicts
The tone() function defaults to using Timer 2 on the ATmega328P. If Timer 2 is already in use (e.g., by the IRremote library), it cascades to Timer 1, and finally Timer 0. This cascading behavior is the root cause of most runtime bugs in embedded audio projects.
| Digital Pin | Associated Timer | PWM Conflict (analogWrite) |
System Impact if Hijacked |
|---|---|---|---|
| Pin 3 | Timer 2 | Yes (Disables PWM on 3 & 11) | Standard tone() target. Safe to use. |
| Pin 11 | Timer 2 | Yes (Disables PWM on 3 & 11) | Standard tone() target. Safe to use. |
| Pin 9 | Timer 1 | Yes (Disables PWM on 9 & 10) | Breaks Servo.h library if used simultaneously. |
| Pin 10 | Timer 1 | Yes (Disables PWM on 9 & 10) | Breaks Servo.h library if used simultaneously. |
| Pin 5 | Timer 0 | Yes (Disables PWM on 5 & 6) | Critical: Breaks millis(), delay(), and Serial. |
| Pin 6 | Timer 0 | Yes (Breaks PWM on 5 & 6) | Critical: Breaks millis(), delay(), and Serial. |
Source: Nick Gammon's ATmega Timer and Interrupt Guide
tone() on Pins 5 or 6 unless you are writing a bare-metal sketch that does not rely on millis() or delay(). Timer 0 is the system clock timer; overriding it will freeze your time-based logic.
Hardware Build: Parts List and Pin Mapping
Not all audio transducers are created equal. The tone() function outputs a 5V square wave with limited current sourcing (~20mA per pin). This is sufficient for a piezo buzzer, but will instantly brownout your microcontroller or fry the GPIO pin if you connect an 8-ohm electromagnetic speaker directly.
Component Spec Sheet: Piezo vs. Electromagnetic
| Feature | Piezo Buzzer (e.g., AST1240MLTRQ) | Electromagnetic Speaker (e.g., CUI CMS-2850) |
|---|---|---|
| Drive Type | Voltage-driven (capacitive load) | Current-driven (inductive load) |
| Impedance | High (~10kΩ at resonance) | Low (8Ω - 16Ω DC resistance) |
| Current Draw | < 30mA | 100mA - 500mA+ |
tone() Compatibility |
Direct drive via digital pin | Requires N-Channel MOSFET (e.g., 2N7000) driver |
Exact Parts List
- Microcontroller: Arduino Uno R3 or Nano V3 (ATmega328P)
- Transducer: 5V Piezo Buzzer (AST1240MLTRQ or generic 12mm active/passive piezo)
- Current Limiting: 220Ω through-hole resistor (prevents inductive kickback spikes from resetting the MCU)
- Wiring: 22 AWG solid core jumper wires, solderless breadboard
Pin Mapping Table
| Component Pin | Arduino Uno R3 Pin | Notes |
|---|---|---|
| Piezo Positive (+) | D8 (via 220Ω resistor) | Avoids Timer 2 (pins 3/11) to leave PWM free for LEDs |
| Piezo Negative (-) | GND | Must share common ground with MCU |
Complete Compilable Code with Error Handling
The following sketch targets the Arduino Uno R3 / Nano V3. It creates a serial-controlled frequency generator. Because embedded C++ lacks traditional try/catch exception handling, error handling is implemented via strict bounds checking and serial state validation. This prevents the MCU from hanging if a user inputs an out-of-bounds frequency or non-numeric characters.
/*
* Serial-Controlled Tone Generator
* Target: Arduino Uno R3 / Nano V3 (ATmega328P)
* Avoids Timer 0 (Pins 5/6) and Timer 2 (Pins 3/11) to preserve
* millis() and standard analogWrite() functionality.
*/
// --- PIN DEFINITIONS ---
#define BUZZER_PIN 8 // Digital Pin 8 (Uses Timer 1, safe for basic tone)
#define LED_PIN 13 // Built-in LED for visual feedback
// --- SYSTEM CONSTANTS ---
const unsigned long MIN_FREQ = 31; // ATmega328P hardware limit for tone()
const unsigned long MAX_FREQ = 65535; // 16-bit timer overflow limit
const unsigned int DEFAULT_DURATION = 500; // ms
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
Serial.println(F("Tone Generator Ready."));
Serial.print(F("Enter frequency ("));
Serial.print(MIN_FREQ);
Serial.print(F(" - "));
Serial.print(MAX_FREQ);
Serial.println(F(" Hz). Send '0' to stop."));
}
void loop() {
if (Serial.available() > 0) {
// Read incoming string and trim whitespace
String input = Serial.readStringUntil('\n');
input.trim();
// Error Handling: Check for empty input
if (input.length() == 0) {
return;
}
// Error Handling: Validate numeric characters only
for (unsigned int i = 0; i < input.length(); i++) {
if (!isDigit(input[i])) {
Serial.print(F("ERROR: Invalid character detected: '"));
Serial.print(input[i]);
Serial.println(F("'. Numeric input only."));
noTone(BUZZER_PIN); // Fail-safe: silence buzzer on bad input
digitalWrite(LED_PIN, LOW);
return;
}
}
unsigned long targetFreq = input.toInt();
// Error Handling: Bounds checking against hardware limits
if (targetFreq == 0) {
noTone(BUZZER_PIN);
digitalWrite(LED_PIN, LOW);
Serial.println(F("Tone stopped."));
}
else if (targetFreq < MIN_FREQ || targetFreq > MAX_FREQ) {
Serial.print(F("ERROR: Frequency out of bounds. Must be between "));
Serial.print(MIN_FREQ);
Serial.print(F(" and "));
Serial.print(MAX_FREQ);
Serial.println(F(" Hz."));
}
else {
// Execute valid tone
Serial.print(F("Playing: "));
Serial.print(targetFreq);
Serial.println(F(" Hz"));
digitalWrite(LED_PIN, HIGH);
tone(BUZZER_PIN, targetFreq, DEFAULT_DURATION);
// Non-blocking delay alternative could be used here,
// but delay() is acceptable for this simple serial tool.
delay(DEFAULT_DURATION + 50);
digitalWrite(LED_PIN, LOW);
}
}
}
Debugging: Timer Clashes and the __vector_7 Error
When working with the Arduino official tone() documentation, the function seems simple. But when you integrate it into a larger project with IR sensors or motor controllers, it often fails silently or throws cryptic compiler errors.
The First Three Things to Check When It Fails
- Timer 2 Clash with
analogWrite(): If you calltone()and then attempt to fade an LED on Pin 3 or 11 usinganalogWrite(), the PWM will fail.tone()takes exclusive ownership of Timer 2. You must callnoTone(pin)before re-enabling PWM on those pins. - Missing
noTone()Before Switching Pins: If you calltone(pinA, freq)and then immediately calltone(pinB, freq)without stopping the first, the internal timer interrupt gets confused, often resulting in a continuous low-frequency hum or total silence on both pins. - Piezo Polarity and Kickback: Piezos are capacitive. When the square wave drops from HIGH to LOW, the collapsing electric field can generate a reverse voltage spike. If your MCU is randomly resetting when the tone stops, you are missing the 220Ω series resistor or a flyback diode across the piezo terminals.
Resolving the multiple definition of '__vector_7' Error
If you compile your sketch and see this exact error string in the IDE console:
multiple definition of `__vector_7'
collect2.exe: error: ld returned 1 exit status
Ranked Causes and Fixes:
- Cause: You are using a library that also relies on Timer 2's overflow interrupt (ISR). The most common culprit is the
IRremotelibrary or an older version of theServolibrary.__vector_7is the specific interrupt vector for Timer 2 Overflow on the ATmega328P. - Fix: You cannot use
tone()and standardIRremotesimultaneously on an Uno. You must either switch to an Arduino Mega (which has 6 hardware timers), use theIRremotelibrary's alternate pin configurations that map to Timer 1, or bit-bang your audio output using a custom non-blocking delay loop instead oftone().
Extending and Simplifying the Audio Build
Once you have the basic tone() function working, you will quickly notice its primary limitation: the audio is thin, tinny, and relatively quiet. This is because a 50% duty cycle square wave is mathematically composed of the fundamental frequency plus infinite odd harmonics (Fourier series). The piezo only resonates efficiently at its specific mechanical frequency (usually 2kHz - 4kHz), filtering out the rest as heat.
How to Extend the Build (Louder, Richer Audio)
To get significantly more volume without adding an external amplifier IC, use the toneAC library. Instead of driving one pin against ground, toneAC drives two pins (Pins 9 and 10 on the Uno) out of phase. This creates a differential voltage swing of 10V peak-to-peak across the piezo, resulting in roughly 4x the acoustic volume and allowing you to bypass the current-limiting resistor entirely.
How to Simplify the Build (Basic Alerts)
If you only need a simple 'beep' and want to avoid timer conflicts entirely, strip away the tone() function and use direct port manipulation or a simple blocking loop. While this lacks the precise frequency accuracy of hardware timers, it frees up all ISRs for your RF or IR libraries:
// Simplified bit-bang beep (approx 1kHz)
for(int i=0; i<500; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delayMicroseconds(500);
digitalWrite(BUZZER_PIN, LOW);
delayMicroseconds(500);
}
For projects requiring actual audio playback (WAV files, MP3s, or polyphonic chords), abandon the tone() function entirely. Upgrade to an I2S digital-to-analog converter like the MAX98357A paired with an ESP32, which utilizes dedicated I2S hardware peripherals and DMA buffers, leaving your CPU timers completely untouched.






