Lecture slides, class recording, and project specs for this module are available below.
You can find additional resources for this module (and others) in the Course Wiki.
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 will be played. 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 speaker, an LED, and then to GND.
Make sure the + side of the speaker is oriented correctly; it'll be facing the digital pin.
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.
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 the lecture video).
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 connect.
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.");
}
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 becuase 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 because the #include was placed after the line with the function call.
Download each of these files 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.
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_##
An alternative to Pitches.h with a simpler set of pitches.
You only need to use one of the pitch files.
Here is an example of a song function. You can copy the whole Despacito() function into your sketch, or you can save the file into your sketch folder and #include "Despacito.h" to access the function from within your sketch.
Try this 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) {
Despacito();
}
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 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. You can copy-paste it into the Arduino IDE and use it as your starting point.
You're always welceme 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).
// Load our predefined pitches into the sketch:
#include "pitches.h"
//#include "basicpitches.h" //Optional; simpler pitches
// Define pins
#define speaker [your speaker pin]
#define pot [pot pin]
//#define displayLED1 [pin]
//#define displayLED2 [pin]
//#define etc.
// You can modify this to change note length:
int NOTE_DUR = 137;
/* Function Declarations
Note: We *define* what they do later,
but we *declare* them before setup() and loop()
so the compiler knows they exist.
*/
void play(int, int); //"a function that returns nothing (void)
// and accepts two int arguments"
void song1();
void song2();
void song3();
//include / paste Despacito() function?
// Setup function; runs once when you press reset or power on the board
void setup() {
// Configure pinModes
// Play a startup beep? (optional)
// Blink LED_BUILTIN at startup? (also optional)
// Turn on the Serial interface for debugging? (optional, but useful!)
}
// Loop function; runs over and over again forever
void loop() {
// Read the potentiometer
// Print debug value(s) to Serial? (optional, but useful!)
// Determine what song is selected (hint: if, else if, else)
// Display the song selection on our LED(s)
// Play the song by executing its function: song#();
}
// Play function definition
void play(int note, int duration) {
tone(speaker, note);
delay(duration * NOTE_DUR);
noTone(speaker);
delay(duration * NOTE_DUR);
}
// Song function definitions
void song1() {
// Write a song using multiple calls to play()
}
void song2() {
// Write another song using multiple calls to play()
}
void song3() {
// Write a third song using multiple calls to play()
}