Time for some music! In this tutorial we create sounds using a piezo speaker and our Arduino Uno.
Last time we looked into PWM which allowed us to change the duty cycle of a fixed frequency signal. For making different sounds we need a signal with adjustable frequency. There is a handy procedure called tone
available, which allows us to create tones with a specific frequency. The technique used is somewhat similar to PWM, but the Arduino implementation does not use the PWM peripheral directly to generate sound. The signal is generated by toggling a pin with the help of a hardware timer. An advantage opposed to directly using the PWM hardware is, that we can use any pin and not just PWM capable pins. However, we won't be able to use PWM on pins 3
and 11
at the same time, as the Arduino needs to use the same hardware timer for this.
Enough theory, let's get started. I chose a small music sample, that we are going to recreate with the Arduino and a piezo speaker. For granted the result won't sound as perfect and harmonic as a piano, but it's fun to play with sounds and maybe you recognize the piece of music I chose.
There is not much to say about the circuit. Just connect the piezo speaker to pin 8
of the Arduino. Make sure you picked a piezo speaker and not a buzzer. A buzzer only makes one sound with a fixed frequency. It has an internal circuit for generating this tone and can be operated with a fixed voltage using a simple digital output. This is however not the goal of this tutorial.
The Arduino is able to drive the piezo speaker directly, for real speakers or a big piezo speaker you need an amplifier. But to be honest to you, a small piezo speaker is annoying enough. You can connect a 220 Ω resistor in series to reduce the loudness.
In our code we will need the following new functions:
tone(pin, frequency)
pin
: Number of the pinfrequency
: Frequency of the tone to generatenoTone(pin)
pin
: Number of the pinUsing the tone
procedure, we can generate a tone with a given frequency. Using noTone
we can silence the speaker again. There is also a version of the tone
function to which you can pass a duration for the tone as a third argument. The problem is, that this won't block but instead measure the duration using the hardware timer. If you want to produce multiple tones right after each other this will cause the first tone to be interrupted by the second one before it was fully played. As a result you will only hear some clicking noises. To control the duration of tones, we will just use the delay
procedure.
Now we will have to write code to generate each individual note in the music sample. For this we need to translate the notes into a frequency. To make our lives easier we can define variables for each note and assign the corresponding frequency as value.
const int F = 349;
const int G = 392;
const int A = 440;
const int BB = 466;
const int C = 523;
You might notice that I used the keyword const
in the variable declaration. A variable declared as const
cannot be changed at runtime - it's a constant.
The full code is still a bit lengthy. Feel free to just copy it. I implemented everything in the setup
procedure, as I want to play the tune only once. Even though we don't need any code in the loop
procedure, this procedure still cannot be simply omitted.
const int F = 349;
const int G = 392;
const int A = 440;
const int BB = 466;
const int C = 523;
void setup() {
tone(8, A);
delay(250);
tone(8, A);
delay(250);
tone(8, BB);
delay(250);
tone(8, C);
delay(250);
tone(8, C);
delay(250);
tone(8, BB);
delay(250);
tone(8, A);
delay(250);
tone(8, G);
delay(250);
tone(8, F);
delay(250);
tone(8, F);
delay(250);
tone(8, G);
delay(250);
tone(8, A);
delay(250);
tone(8, A);
delay(375);
tone(8, G);
delay(125);
tone(8, G);
delay(500);
tone(8, A);
delay(250);
tone(8, A);
delay(250);
tone(8, BB);
delay(250);
tone(8, C);
delay(250);
tone(8, C);
delay(250);
tone(8, BB);
delay(250);
tone(8, A);
delay(250);
tone(8, G);
delay(250);
tone(8, F);
delay(250);
tone(8, F);
delay(250);
tone(8, G);
delay(250);
tone(8, A);
delay(250);
tone(8, G);
delay(375);
tone(8, F);
delay(125);
tone(8, F);
delay(500);
noTone(8);
}
void loop() {
}
Once the code is uploaded to the Arduino, the melody will start to play. The result might be good enough to recognize the tune, but it is definitely not great. We are missing breaks to be able to differentiate between the individual notes played after each other. This will be our next step. And by the way, the tune is the beginning of Beethoven's Ode to Joy, which is also known as the European Hymn.
To separate the individual tones, we need to add a call to noTone
and a small delay, after each tone to produce some silence. To do so for every single tone would be a pretty tedious task, however.
This is why I want to teach you, how to write your own functions. It is not hard at all. To declare your own function just put down the return type, a function name and the list of arguments with their types. We can use the arguments to pass values we need in the function implementation.
It's probably the best if I simply show you the code:
void playNote(int freq, int duration) {
tone(8, freq);
delay(duration-25);
noTone(8);
delay(25);
}
The argument freq
is used to set the frequency, and we use duration
to define the length of our delay. To separate the individual tones I subtracted 25 ms from the total length and used this 25 ms for a short period of silence directly after the tone. This gives us distinguishable tones, even if the same note is played twice. One important detail when defining your own functions, is to add them before the functions that they are used in. The compiler interprets the file from top to bottom. Thus, if you add the function to the end of the file you won't be able to use it.
Using the newly defined function, we can implement the generation of our tune in a nice and clean way:
const int F = 349;
const int G = 392;
const int A = 440;
const int BB = 466;
const int C = 523;
void playNote(int freq, int duration) {
tone(8, freq);
delay(duration-25);
noTone(8);
delay(25);
}
void setup() {
playNote(A, 250);
playNote(A, 250);
playNote(BB, 250);
playNote(C, 250);
playNote(C, 250);
playNote(BB, 250);
playNote(A, 250);
playNote(G, 250);
playNote(F, 250);
playNote(F, 250);
playNote(G, 250);
playNote(A, 250);
playNote(A, 250);
playNote(G, 250);
playNote(G, 500);
playNote(A, 250);
playNote(A, 250);
playNote(BB, 250);
playNote(C, 250);
playNote(C, 250);
playNote(BB, 250);
playNote(A, 250);
playNote(G, 250);
playNote(F, 250);
playNote(F, 250);
playNote(G, 250);
playNote(A, 250);
playNote(G, 250);
playNote(F, 250);
playNote(F, 500);
}
void loop() {
}
Our final result is a big improvement to what we achieved before. Truth be told, the sound of a piezo speaker is not amazing, but you are able to recognize the tune clearly.