Lecture slides and class recording are available below.
You're an engineer and we want you to build a new product for us: the iPoduino!
The specs we want are simple; it needs to play some different songs (at least 3), have a way for the user to select which song will play next, and visually show the user which song is playing.
Sound: Play music through the speaker using a digital square wave
We will use the tone() and noTone() Arduino functions which generate a square wave designed to drive a piezoelectric speaker (a buzzer)
Interface: Select next song to be played using the potentiometer knob
You need to find a way to detect which region the potentiometer is turned to (hint: if / else if)
Display: LED indicator that shows which song is being played.
You also need an LED music visualizer (that's the LED in series with the speaker).
How the display works is up to you—some ideas:
Make LED brightness change for different songs (hint: analogWrite)?
Blink a certain pattern ?
Light up different LEDs corresponding to different songs?
The circuit for this project has three main sections, all of which are connected to your Arduino. Let's talk about what each of them does.
This is a simple series circuit coming from a digital pin on your Arduino, through a resistor, the piezo buzzer, an LED (the music visualizer), and then to GND.
IMPORTANT: When you connect the buzzer to your breadboard, you will have to insert the pins diagonally across two adjacent rows.
Do not try to bend them; they will break off. If you're not sure what this means, ask an instructor!
Make sure the + side of the speaker is oriented correctly; it'll be facing the digital pin. If the marking has rubbed off the top of your buzzer, you can find another + marking on the back side PCB.
Make sure the LED is oriented correctly too!
If your speaker is really quiet, you can remove the resistor from the speaker circuit and connect the speaker directly to your digital pin. This won't hurt the speaker, but it will be driving your LED with a little more current than it likes. This is technically bad practice but won't hurt the LED enough for us to worry about it, since the speaker has some internal resistance.
You could also build an alternative circuit with two branches coming out of the digital pin: One branch going from the pin to the speaker, and then to GND, and the other branch going from the pin to the resistor, then to the LED, and then to GND. (We draw this circuit on the board at around 1:13:00 in last semester's lecture). This gives your speaker the full current from the digital pin while still safely limiting the current through the LED.
This potentiometer circuit should look familiar; it's the same one we used last week!
Connect one of the outer pins to 5V, the other to GND, and connect the wiper to an analog input pin.
You'll need to read the potentiometer's position with analogRead() just like in project 1, but this time use if-else statements to check if the potentiometer reading is within certain ranges, then execute different code accordingly.
This part's up to you! The only requirement is that it's easy to tell which of your songs is playing from the LEDs you use.
Don't forget to connect a current limiting resistor in series with each LED (the 470Ω work well for this, do you remember their color codes?)
Some ideas for your display:
Vary the LED(s) brightness using analogWrite()?
Blink the LED(s) in a different pattern / at a different speed?
Light up multiple LEDs to correspond to different songs?
analogWrite() only works on PWM pins! Look for digital pins with the ~ next to them on your pinout diagram. If you try to use analogWrite() on a non-PWM digital pin, or an analog input pin, the Arduino won't output a PWM signal.
You'll need to use PWM pins for the speaker output and any LEDs that you want to control the brightness of.
An important note from the Arduino Reference:
Use of the tone() function interferes with PWM output on pins 3 and 11.
This means you can't use analogWrite() on pins 3 or 11 (but digitalWrite() and digitalRead() will still work on them)
Why does this happen? Our microcontrollers use internal timer circuits to generate the periodic signals used by analogWrite() (PWM waves) and for tone() (square waves). However, the microcontroller on the Arduino Nano has a limited number of timers, and the lower level code used by tone() takes over the timer that outputs signals to pins 3 and 11, blocking analogWrite()'s internal code from generating PWM on those pins.
The #include compiler directive is just like telling the compiler to go open up a file, copy all the lines of code that are in that file, and paste them into your program right where the #include statement is. For example, let's do some snack math:
If we have the following in SnackTime.ino:
#include "myMathFunction.h"
void setup() {
int guests = 2;
int snacksPerPerson = 2;
int snacksNeeded = snackMath(snacksPerPerson, guests);
// snacksNeeded = 2 + 2 * 2 = 6
// 2 snacks per guest plus 2 for me
Serial.begin(9600);
Serial.print("You need ");
Serial.print(snacksNeeded);
Serial.println(" snacks.");
}
And another file named myMathFunction.h containing:
int snackMath(int snacks, int people) {
// need snacks for me plus snacks for everyone else:
int result = snacks + snacks * people;
return result; // return the result
/* Note the int before the function name instead of void.
This is a function that returns some data to the caller
(whatever point in our program that the functin has been called).
*/
}
Then the resulting compiled code would be exactly the same as if we pasted the contents of myMathFunction.h right there where the #include statement is:
int snackMath(int snacks, int people) {
// need snacks for me plus snacks for everyone else:
int result = snacks + snacks * people;
return result; // return the result
/* Note the int before the function name instead of void.
This is a function that returns some data to the caller
(whatever point in our program that the functin has been called).
*/
}
void setup() {
int guests = 2;
int snacksPerPerson = 2;
int snacksNeeded = snackMath(snacksPerPerson, guests);
// snacksNeeded = 2 + 2 * 2 = 6
// 2 snacks per guest plus 2 for me
Serial.print("You need ");
Serial.print(snacksNeeded);
Serial.print(" snacks.");
}
And for those astute observers out there, yes, it is bad coding practice to place functions inside of header (.h) files. In the real world, you'd have both a header file and a .c (or .cpp) file. However, for our simplistic needs, simply putting everything in the header is fine (and simpler for beginners to understand 😜). If none of that made sense to you, don't worry; you'll learn all about it if you ever take ECE 209 or any other C programming course.
You can't use anything from an #included file before the #include statement. Likewise, the code inside an included file can't access anything that wasn't already in your main sketch file before the include statement.
For example, the following code wouldn't work:
void setup() {
int guests = 2;
int snacksPerPerson = 2;
int snacksNeeded = snackMath(snacksPerPerson, guests);
// snacksNeeded = 2 + 2 * 2 = 6
// 2 snacks per guest plus 2 for me
Serial.print("You need ");
Serial.print(snacksNeeded);
Serial.print(" snacks.");
}
#include "myMathFunction.h"
The compiler would give us the following error at line 4:
error: 'snackMath' was not declared in this scope
In this situation, the compiler isn't able to execute the snackMath() function because it doesn't yet know that function exists (remember, the compiler evaluates our code line by line from top to bottom). The include statement telling the compiler where the function definition is (in myMathFunction.h) hasn't yet been evaluated when stackMath() is called because the #include was placed after the line with the function call.
Download each of these files (click the link) and place them in the same folder that your sketch .ino is saved in. Any time you #include a file in double quotes (as shown above) the compiler will look for that file in the same location as your sketch file. If you're not sure where the Arduino IDE is saving your sketches, go to File -> Preferences and take note of the Sketchbook location (usually it'll be in your Documents folder on Windows and similar on Mac).
This file contains #define statements for the frequency that corresponds to various notes
After you load the file into your program (using #include "Pitches.h") you'll be able to refer to these frequencies with NOTE_##
Beethoven.h, Empire.h, and Despacito.h
Here are some example song functions. You could copy Elise() function into your sketch, or you can save the file into your sketch folder and #include "Beethoven.h" to access the function from within your sketch. The same for March() within Empire.h and Despacito.
Include Beethoven.h and try it out after you've built your speaker circuit by placing the following in your loop() function and turning the potentiometer up:
if (analogRead(pot) > 512) {
Elise();
}
Here are some songs with labelled notes that you can use for reference if you're stumped on inspiration.
You could also paste the code of the play() function into ChatGPT and ask it to generate a song for you with a prompt something like this:
Given the following play function and using note labels in the format of NOTE_## (e.g. NOTE_C4), generate a C++ code snippet that will play Hot Cross Buns on an Arduino speaker. Assume the note labels have already been defined elsewhere. NOTE_DUR is the duration in milliseconds of each note; alter it to change the tempo if needed.
---
void play(int note, int duration) {
tone(speaker, note);
delay(duration * NOTE_DUR);
noTone(speaker);
delay(duration * NOTE_DUR);
}
(It's okay with us if you use AI here since making the song is a fairly boring process. Just make sure you understand what's going on before you let the AI do all the work for you)
Here's some pseudocode of what you should expect to have in your program. We recommend using this file as your starting point for the project (it's the same pseudocode we covered in lecture).
Nevertheless, you're always welcome to write the program however you please, as long as it meets the minimum requirements we've talked about above (so in other words, you don't have to use this pseudocode if you don't want to).