Lesson 2: A simple piano
Table of Contents
- Making sound with Arduino
- Making a simple siren
- Making a simple piano
- Additional references
- Next Lesson
OK, we made it through our first digital input lesson. Now, let’s do something fun with this newfound knowledge!
In this lesson, we are going to make a simple five-key piano with tactile buttons wired with internal pull-up resistors and a piezo buzzer. Will it be fun? Yes! Will it produce hi-fidelity music? No!
As a sneak preview, try out our circuit+code in the Tinkercad simulator. Click “Start Simulation” and then click on the buttons to “play the piano” (yes, the “notes” will sound somewhat abrasive to our ears—more on that below). You can even click on the ‘Code’ button, modify the code, and rerun the simulation.
If the Tinkercad simulator does not load, click here to view the simulation on the Tinkercad page.
We are going to build input circuits using the microcontroller’s own internal pull-up resistors, so our material list includes only four things (well, and wires of course!):
|Breadboard||Arduino Uno, Leonardo, or similar||Five Tactile Buttons||Piezo Buzzer|
Making sound with Arduino
Sound waves are vibrations in air pressure. The speed of these vibrations (in cycles per second or Hertz) determines the pitch. The higher the vibration frequency, the higher the pitch.
If you’re not familiar with sound waveforms and the differences between sinusoidal, triangle, and square waves—or even if you are—I recommend this short, interactive guide by Josh Comueau called “Let’s Learn About Waveforms.” Make sure to have your sound on. The website defaults to muted volume, so press the letter ‘m’ to unmute once the website loads (or use the interactive sound volume widget).
To play ear-pleasing, high-frequency waveforms with a microcontroller, you need a digital-to-analog converter (DAC). Some microcontrollers, like the Arduino Due, have DACs built in. The Arduino Uno and Leonardo microcontrollers do not.
Instead, these microcontrollers produce square voltage waves. Indeed, when we use
analogWrite, the Arduino produces a square wave of a fixed frequency—490Hz on most PWM pins but the Uno can produce double that (980Hz) on Pins 5 and 6 (see docs). When you write an 8-bit value (0-255) to
analogWrite, this changes the duty cycle of the waveform, not the frequency. And this fixed frequency waveform is not helpful for generating pitch-controllable tones. Well, it’s fine if you just want to produce a tone at 490Hz or 980Hz but what about other frequencies?
The Arduino tone library
Brett Hagman created the tone library to address this problem, which is now part of the core Arduino library. While tone cannot generate sinusoidal waves like a DAC, it does produce square waves at specific frequencies, which can be used to actuate speakers and piezo buzzers.
Using tone is easy. You simply call
tone(pin, frequency) with the pin number and a frequency (the minimum frequency is 31Hz) and a square wave with the given frequency is generated on the pin. The library also offers a convenience method that enables you to specify how long to play a tone in milliseconds:
tone(pin, frequency, duration).
Note that the tone call returns immediately—almost like an asynchronous call in a multi-threaded program. However, the Arduino Uno is a single-core chip with no multi-threading support. So, how does this work?
Answer: via timer interrupts.
The ATmega328 supports a variety of interrupts, including interrupts to detect when a voltage value on a pin changes as well as time. The timer interrupts, for example, are used by the millis() and micros() functions to count precisely. They are also used to generate square waves at specific frequencies, which is what tone() does.
The ATmega328 has three timers:
- Timer0: 8-bit timer used by millis() and micros().
- Timer1: 16-bit timer used by the Servo() library
- Timer2: An-8 bit timer used by the Tone() library
If you want to take a (super) deep dive into how tone works, see the source code here and accompanying notes. If you want to learn more about interrupts on the Arduino, see Nick Gammon’s blog post and this Adafruit Learn series.
There are two primary types of buzzers: magnetic and piezo. A magnetic buzzer operates similarly to a traditional speaker: a current driven through a coil of wire produces a magnetic field, which dynamically moves a magnetic disk resulting in a sound wave. In contrast, a piezo buzzer is driven by voltage rather than current and is constructed out of piezoelectric material. This material mechanically deforms in response to applied voltages, which can be used to generate sounds.
There are two types of piezo buzzers: active and passive. Active buzzers use internal oscillators to generate tones, so only need a steady DC voltage. In contrast, passive buzzers require a voltage waveform—the waveform frequency will corresopndingly vibrate the piezoelectric material to make sound. This is the waveform that our Arduino will produce via tone.
If you’re taking one of our courses, we purchase passive piezo buzzers—typically from places like Adafruit ($1.35) or Mouser ($0.51).
Playing multiple tones simultaneously
Can you use the Tone() library to generate chords?
No, the default tone library does not support generating square waves composed of multiple frequencies (in other words, chords). However, Brett Hagman, the author of the original Arduino tone function, wrote a more advanced tone library to generate multiple simultaneous tones. This is also described in the Arduino Cookbook (Section 9.3 Generating More than One Simultaneous Tone). Another discussion here.
Making a simple siren
Alright, let’s build some stuff!
We’ll start by constructing a simple piezo buzzer siren before making our piano. It’s always a good idea to modularize and build things in small steps—especially when using new components. So, our simple siren will give us familiarity with hooking up and using a piezo buzzer before we add in the additional circuit and code complexity of buttons required by the piano.
Making the siren circuit
The circuit is quite simple: simply connect one leg of the piezo buzzer to Pin 9 and the other leg to GND. The piezo buzzer will work in either direction (try it if you don’t believe us!).
You can play with the Tinkercad simulation here.
Should I use an in-series resistor?
There is some debate about whether you should use a small in-series resistor with a passive piezo buzzer (link1, link2) similar to an LED circuit. I never have. I’ve always directly wired my piezo buzzer to the Arduino pins like the wiring diagram above (and here).
It never hurts to add a resistor for testing. You could start with a 100Ω or 220Ω resistor (or try a variable resistor in series).
Writing the siren code
We’re going to flash the Arduino’s built-in LED on and off (we can never outrun Blink) and play two alternating sounds. We encourage you to try writing this code first before looking at our step-by-step guide.
You’ve built up all the skills you need to do this! Just remember,
tone(pin, frequency). Oh, and yah, feel free to use
delay() calls for this simple prototype (hey, sometimes
delay() is the best solution!)
Step 1: Declare our constants
Step 2: Initialize our pins in setup()
Step 3: Implement the siren logic in loop()
The loop alternates between playing a 392Hz square wave and a 262Hz square wave—each for
SOUND_DURATION_MS. Feel free to use other frequencies, I chose 392 and 262 because they correspond to the piano keys
C4 (Middle C) and
G4. We’ll also flash on/off an LED with the same rhythm. Very siren like!
Step 4: Compile, upload, and run!
We did it! Now, compile, upload and run your program!
To stop this (admittedly) annoying program, load up a fresh, empty sketch (Ctrl-N or CMD-N in the Arduino IDE) and immediately upload it to your board. An empty sketch will compile, upload, and run just fine (and do nothing, which is, at this point, what you so desperately want!).
Our SimpleSiren code on GitHub
You can access our SimpleSiren code in our GitHub repo. It’s also displayed below:
This source code is on GitHub.
There are lots of fun examples online of using Arduino + piezo buzzers to play lo-fi versions of popular theme songs (like the Imperial March from Star Wars). Feel free to keep playing with the piezo buzzer before building your piano.
Making a simple piano
Alright, let’s make that piano.
Making the piano circuit
To limit the use of unnecessary components, we’re going to hook up our buttons with the ATmega’s internal pull-up resistors. So, the default state will be
HIGH for each button (and then
LOW when pressed). We’ll write code to support both pull-down and pull-resistor designs, however (ahhh, the magic of code to help manage hardware messiness!).
This is the first time we’ve breadboarded so many components, so try to keep your wiring and layout clean. I always reserve using black wire for connections to GND and red wire for connections to Vcc. In fact, if you sneak back a look to any of our wiring diagrams, you should observe this convention. :)
Note that we do not need to wire anything directly to Vcc here (and you can tell this at a glance with my wiring because no red wires!).
You can play with this circuit and the underlying Arduino program on Tinkercad
Here are two images of our physical wiring. Click and open the images in a new tab to zoom. Obviously, many other functionally equivalent wirings are possible. We are also using a mixture of jumper wires (which you have in your kits) and manually cut solid-core wires (which you do not). Generally, we strive to make clean, elegant circuits—but doubly so for when we’re teaching!
|Simple Piano Wiring View 1||Simple Piano Wiring View 2|
Writing the piano code
The code is fairly straightforward.
Step 1: Declare our note frequencies
First, let’s declare the waveform frequencies of our notes.
Step 2: Declare our pin constants
Our piano has five buttons, so we need five input pins. We also need an output pin for our piezo buzzer, of course. And we’ll hookup the built-in LED on the Arduino to turn on whenever we sense a button press (just for fun and to help us debug in case something goes wrong).
Finally, we’ll add in a boolean constant
_buttonsAreActiveLow that simply let’s us handle pull-up vs. pull-down logic in software. The boolean defaults to
true because we assume a pull-up resistor configuration. Switch this to
false if you decide to design your button circuits with pull-down resistors.
Step 3: Setup our I/O pins in setup()
In setup(), we simply initialize our five inputs and two outputs as inputs and outputs accordingly. Note the
INPUT_PULLUP flag for each button input.
Step 4: Write core piano logic in loop()
Because tone() can only play one frequency at a time (darn, no rockin’ chords), we setup a large conditional block checking for each individual button press. If a button is pressed, we play the corresponding note.
To more easily handle both pull-up and pull-down circuit configurations, we wrote a convenience function called
isButtonPressed(int btnPin), which abstracts the “isPressed” logic. (Though one might criticize the use of a global variable—tis common for these rapid prototypes, I’m afraid. You could pass the global var as a parameter into
isButtonPressed if this makes you feel better about code modularity).
Step 5: Compile, upload, and run the code
Now, compile, upload, and run your code. Let’s hear it Beethoven!
Here’s a workbench video of us playing our piano. Make sure your sound is on (or not) to hear our beautiful music! :-D
The sound and video stream seem a bit out of sync here, but you get the idea.
Our SimplePiano code on GitHub
You can access our SimplePiano code in our GitHub repo. It’s also displayed below:
This source code is on GitHub.
For your prototyping journals, select one of the following extensions to the piano keyboard (or come up with your own) and document it.
- Extend your keyboard to support a full octave. If your kit only has five buttons, what else could you use for input (hint: it’s fun and easy to make lo-fi button input out of everyday materials)
- Try adding in LEDs for each key, which light up and then fade after each corresponding key press (hint: use your LEDFader class from a previous exercise)
- Try supporting chords—that is, multiple simultaneous tones—using Brett Hagman’s tone library
- Lab 5: Tone Output Using An Arduino
- Arduino sketch for high frequency precision sine wave tone sound synthesis
In the next lesson, we’ll introduce the problem of “contact bouncing” and talk about solutions.
Previous: Using buttons Next: Debouncing