Lesson 97: Using tone() Advanced I/O
Learn how tone() generates square-wave sound output using frequency and optional duration control.
Progress indicator
Lesson 97 of 97
Learning Objectives
- Understand exactly what tone() generates on a hardware pin and why a buzzer can play it.
- Learn both tone() syntaxes and when to use frequency-only vs frequency+duration.
- Understand the frequency (Hz) concept using simple real examples.
- Compare tone() and analogWrite() so you choose the correct function in projects.
- Read button-triggered tone code confidently, including debounce and serial debug lines.
- Avoid common tone() mistakes and apply safe best practices for stable sound behavior.
Concept Explanation
What is tone()
tone() is used to generate a square-wave signal on a digital pin. A passive buzzer converts this fast ON/OFF signal into audible sound.
In beginner terms: tone() is your "make sound" command for buzzer-style output.
It does not generate music logic by itself. You choose one frequency (or many over time) to create beeps or simple notes.
tone() Syntax
tone(pin, frequency);starts tone and continues until stopped.tone(pin, frequency, duration);starts tone and auto-stops after duration.
pin is buzzer output pin, frequency is in Hz, and duration is in milliseconds.
Example: tone(15, 1200, 300) means pin 15 plays 1200 Hz for 0.3 second.
How tone() Works
- Your code calls
tone()with pin and frequency settings. - The board starts toggling that pin HIGH/LOW at a fixed speed.
- This periodic toggle is a square wave.
- The buzzer converts that square wave into audible vibration/sound.
- If duration is provided, the board schedules an automatic stop.
- If duration is not provided, sound continues until you stop it or change it.
Frequency Concept (Hz)
Frequency means cycles per second. 1000 Hz means 1000 complete ON/OFF cycles every second.
- Lower frequency -> lower pitch sound.
- Higher frequency -> higher pitch sound.
- Typical beginner testing zone: 500 Hz to 3000 Hz.
Real-life example: confirmation beep can use around 1000-1500 Hz; warning tone can be higher and shorter.
tone() with Duration
Duration lets you create short beeps without writing extra stop logic each time.
This is useful for notification sounds where you want a controlled short pulse.
If you need continuous alarm behavior, you usually use frequency-only tone and stop it with noTone() when needed.
tone() vs analogWrite() (Comparison)
tone(): audio-focused square-wave output, usually for passive buzzer sound.analogWrite(): PWM duty-cycle control, usually for LED brightness and motor style control.tone()expresses pitch with frequency;analogWrite()expresses power ratio with duty cycle.
Quick rule: if goal is beep sound -> use tone(). If goal is brightness/speed level -> use analogWrite().
When to Use tone()
- Button press feedback beep in user interface projects.
- Alarm or warning notification in safety/status flows.
- Simple melody experiments for beginner embedded learning.
- State change alert when LED-only feedback is not enough.
Example Code
This example reads a push button and plays a short 1200 Hz tone when pressed.
const int BUZZER_PIN = 15;
const int BUTTON_PIN = 0;
bool playTone = false;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.begin(115200);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW) {
playTone = true;
tone(BUZZER_PIN, 1200, 300);
Serial.println("Tone started: 1200 Hz for 300 ms");
delay(220); // simple debounce
}
if (playTone) {
playTone = false;
}
delay(40);
}Example Code Explanation
const int BUZZER_PIN = 15;sets the buzzer output pin in one place for easy maintenance.const int BUTTON_PIN = 0;defines the button input pin and keeps pin numbers readable.bool playTone = false;is a state flag used for beginner-friendly flow clarity.- In
setup(),pinMode(BUTTON_PIN, INPUT_PULLUP)enables internal pull-up, so the pin reads HIGH normally and LOW when pressed. Serial.begin(115200)starts serial logging so you can verify runtime behavior.- In
loop(),digitalRead()samples the button state every cycle. - If button state is LOW (pressed), code sets
playTone = trueand triggers tone at 1200 Hz for 300 ms. - Serial message prints each trigger event, useful for debugging repeated presses.
delay(220)acts as simple debounce, reducing multi-trigger noise from one press.- The next
if (playTone)block resets the state to false so logic stays clean for the next cycle. - Final
delay(40)keeps loop timing stable and avoids overly aggressive pin polling.
What Happens Inside
- CPU enters
loop()repeatedly (infinite Arduino runtime cycle). - Digital input circuitry reads button pin level.
- Condition check compares value against LOW press state.
- If true, tone engine receives command with pin/frequency/duration.
- Timer-based pin toggling starts at requested frequency.
- Buzzer receives square wave and produces audible tone.
- Duration timer expires and output stops automatically.
- Serial subsystem prints debug line so student can verify event order.
- Loop waits short debounce/poll delay and repeats for new input.
Common Mistakes with tone()
- Using active buzzer assumptions with passive buzzer wiring (or vice versa).
- Confusing frequency values with PWM brightness values.
- No debounce logic, causing multiple accidental tone triggers from one press.
- Choosing extreme frequency values that are inaudible or unclear on small buzzers.
- Ignoring board-specific behavior and not testing with Serial debug output.
Best Practices for tone()
- Start with safe test frequencies such as 800, 1000, or 1200 Hz.
- Use duration for short beep UX so sound stops predictably.
- Apply debounce when using button-triggered tone logic.
- Use Serial prints while tuning timing and trigger behavior.
- Keep tone logic modular so you can later extend to melody patterns safely.
Try it now
Open the simulator workspace and test how changing frequency and duration affects buzzer behavior.