- The ESP32 tone problem
- How does Arduino’s tone work?
- Playing tones on ESP32
- Let’s make stuff
- Next Lesson
On Arduino, the
tone() function generates a square wave of a specified frequency on a pin and is used to “play” tones on piezo buzzers or speakers; however, it is famously unsupported on the ESP32. In this lesson, we will provide some context about this problem and then show you how to play tones on the ESP32 using the LEDC PWM library, which we also used in our ESP32 LED Fade lesson.
The ESP32’s Arduino library called arduino-esp32 attempts to mimic and/or directly replicate the functionality from core Arduino; however, as we saw in our ESP32’s LED Fade lesson, this is not always possible and key functions, like
analogWrite, are different.
analogWrite is not supported on arduino-esp32 so too is
tone() unavailable. Recall that on Arduino,
tone()generates a square wave of a specified frequency (with fixed 50% duty cycle) on a pin and is used to “play” tones on piezo buzzers or speakers. In our Intro to Arduino series, for example, we used
tone() to create a piano.
However, if you attempt to compile code with
tone() using the ESP32, you will receive a compiler error like this:
'tone' was not declared in this scope. Thus, even basic tone examples built into the Arduino IDE like
Examples -> Digital -> toneMelody fail, as shown below.
Figure. Example of how even basic tone examples, like toneMelody.ino, which ships as a built-in example with the Arduino IDE, fails with a ESP32 board selected.
What can we do about this? And why isn’t tone supported anyway? Let’s dive in below.
Recall that Arduino’s tone provides three key methods:
Both tone methods drive a PWM waveform on the provided pin with the given frequency using timer interrupts. The second version adds in a
duration parameter, which allows you to specify how long (in milliseconds) to play the tone for. Either way, you can call
noTone(pin) to stop outputting the PWM waveform and turn off the tone.
This tone API is simple and well-understood. It’s implemented in core Arduino, including for AVR-based microcontrollers—ArduinoCore-avr (Tone.cpp)—and SAMD-based microcontrollers— ArduinoCore-samd (Tone.cpp). When using Arduino, we expect
tone() to be available!
To generate the PWM waveforms and to track play tone duration, the tone library uses hardware timers (aka timer interrupts). However, these hardware timers and the functionality therein differs widely depending on microcontroller chip. The Atmel AVR microcontrollers like the ATmega328 used on the Arduino Uno and ATmega32u4 used on the Arduino Leonardo handle them one way while the Atmel SAMD21 microcontrollers handle them another. Even for just AVR-based microcontrollers, there is a lot of nuance and differences—see the
#ifdef in Tone.cpp for ArduinoCore-avr.
Most relevantly for us, Expressif decided not to implement
tone() into arduino-esp32. While we’re not sure why, what can we do about it?
Fear not, things are not as dire as they seem. As Thomas Countz points out on GitHub Issue #1720, the LEDC PWM library—which we used in our ESP32 LED Fade lesson—actually has tone related methods, including:
note_t is defined as the following in esp32-hal-ledc.h:
To use either
ledcWriteNote, we can follow a similar approach to what we did for fading an LED. First, let’s build our circuit.
Our circuit is as simple as they come. Simply attach your piezo buzzer to a GPIO pin. In this case, we’re using GPIO Pin 26. In our courses, we often use the TDK PS1240 piezo buzzers (about $0.46 Mouser or $1.35 at Adafruit). These buzzers work with both 3V and 5V square waves. Their resonant frequency (loudest tone) is 4kHz but you can drive them with a far larger range (we’ve tested from 32Hz up to 10Khz, at which point the sound is ear piercing). As non-polarized devices, they can be connected in either orientation (like resistors).
Figure. Circuit diagram to hook up PS1240 piezo buzzer with the ESP32. We’ve wired the buzzer to GPIO Pin 26. Image made in Fritzing and PowerPoint.
Now, let’s write the code.
First, we need to “attach” our piezo buzzer pin to one of the 16 PWM channels available on the ESP32 using the
ledcAttachPin function. In this case, we’ll use Pin 26 and PWM channel 0. Recall that the ESP32 has 16 PWM channels (0-15) and each can be configured independently to drive different PWM waveforms. In software, we “attach” pins to these PWM channels to receive the waveform.
Great, now we’ve attached Pin 26 on PWM channel 0.
Now, we can simply play a note using
ledcWriteNote or raw frequency using
ledcWriteTone. For example, the code below loops through playing middle C using
ledcWriteNote and then the frequency 800 Hz using
ledcWriteTone with 500ms pauses in between.
Now, those with a keen eye may have noticed that there are no functions that take in a
duration parameter. Yes, this is a small problem. Yes, we will address this!
Let’s get making!
In the activities below, we are going to first play a scale and various raw frequencies before introducing the Tone32.hpp class, which helps abstract some complexity. Generally, we try to embed mp4 videos directly into our lessons. To more easily control playback and sound, we are going to be using YouTube embeds here. So, make sure you have your sound on (and possibly wear headphones, to limit annoyance to others around you).
Using the same circuit as before, let’s write code to play a simple C major scale based on Thomas Countz’s comment on GitHub Issue 1720. While we could use an array to step through notes, let’s keep things super simple and just write out each note directly. The full code is:
And a video demo below:
OK, now let’s make a slightly more complicated version that reads in an analog input and translates this value into an output frequency. For our demo, we are using a potentiometer. But, of course, any analog input would work!
We need to slightly modify our circuit by adding a potentiometer—in this case, a 10K potentiometer.
Figure. Circuit diagram of Huzzah32 with piezo buzzer and potentiometer. Image made in Fritzing and PowerPoint.
Now, let’s write code to take in the analog input and use this to set the frequency of the PWM output waveform.
Here’s a video demo.
The examples above demonstrated how to use
ledcWriteNote from esp32-hal-ledc.c to drive specific PWM frequencies on output pins—these square waves manifest as sound with the piezo buzzers.
However, these methods aren’t as easy to use as the Arduino
tone() library and don’t support play durations. To address these limitations, we created Tone32.hpp. Tone32.hpp is part of the Makeability Lab Arduino Library. Follow the instructions here for installation and use.
There are a few key differences with:
First, we use an object-oriented approach. To create a Tone32 object, simply call
Tone32 tone32(pin, pwmChannel), which creates a Tone32 object with the given output pin and PWM channel.
tone()uses timer interrupts to track play durations—and automatically stops playing after a duration has expired—we use a “polling” approach. So, you must call
loop(). This is essential if you are using the duration parameters. Note: I encourage others to adapt Tone32 to use timer interrupts but polling is fine for our purposes (as long as you consistently call update() with limited time between calls)
tone(), you play tones via either
playTone, both of which are overloaded functions with
Here are the key Tone32 methods:
There are also some other helpful functions like:
Let’s try building some stuff with Tone32.hpp!
To demonstrate using durations, we wrote AnalogInputTone32.ino, which walks up and down the C scale using an analog input (we used a potentiometer). When you “land” on a new note, we play the note’s frequency for 500 ms. We will use the same piezo buzzer + potentiometer circuit as before but with new code.
Here’s the whole code:
Here’s a video demonstration. Notice how we’re displaying the
duration remaining for each tone on the OLED—this is to help highlight the duration functionality of the Tone32.hpp library.
Finally, we’ve included a bonus simple ball bounce demo using the Tone32.hpp library, again highlighting the play
duration functionality. Here, we play a brief tone when the ball bounces off the floor or ceiling.
Video. A video demonstrating BallBounceTone32WithOLED.ino. Make sure your sound is on.
Tone was Not Declared in this Scope, arduino-esp32 GitHub Issue #1720
Arduino Tone for ESP32, Thomas Countz
In the next lesson, we will learn about and use the ESP32’s built-in capacitive touch sensing module.