Assumptions
I’m assuming that every one did some part of Arduino Lesson 10 – Making Sounds last week, because music did fill the air during lab.
Getting ready
Connect your Arduino to a breadboard with two LEDs of the same color connected to pins 10 and 11. The LED connections should be similar to the one shown in Arduino Lesson 6 – Digital Inputs, but with the connection to pins 10 and 11. We have a limited number of 270 Ω resistors, so you may need to use a 330 Ω (orange, orange, brown). Take a look at a resistor color code JavaScript web app from Danny Goodman’s JavaScript Bible to solve the resistor code.
Do not connect a switch!
Verifying the circuit
To make sure everyone’s breadboard is connected correctly, try out the following program. Both you and all your lab neighbors should see the two LED’s blink alternately.
const int outputPin1 = 10 ;
const int outputPin2 = 11 ;
void setup() {
pinMode(outputPin1, OUTPUT) ;
pinMode(outputPin2, OUTPUT) ;
}
void loop() {
digitalWrite(outputPin1, LOW) ;
digitalWrite(outputPin2, HIGH) ;
delay(1000) ;
digitalWrite(outputPin1, HIGH) ;
digitalWrite(outputPin2, LOW) ;
delay(1000) ;
}
Using variables
Declare two int variables delayTime1 and
delayTime2 as local variables of loop().
Initialize both variables to 1000.
Use
delayTime1 as the argument to the first call of
delay() and
delayTime2 as the argument to the second call of
delay().
Compile and run (verify and upload in the Arduino IDE) your program. There should be no change in its behavior.
If you are having trouble remembering how to declare and use local variables, take a look at either Local Variables in Java or a C tutorial on Functions and Global/Local variables.
Faster than the eye
Do four experiments.
- Set both
delayTime1anddelayTime2to 100. - Set both
delayTime1anddelayTime2to 10. - Set
delayTime1to 4 anddelayTime2to 16.
In the last two cases, the LEDs should seem just a little dimmer.
However, you may have trouble seeing this, especially in a room as
bright as RRO 223.
If you can’t see the difference, set
delayTime1 to 1
and delayTime2 to 19.
You might also try switching your LEDs as their brightness may vary
due to manufacturing differences.
Hearing what you can’t see
Set both delayTime1 and delayTime2 to 10.
Although you can’t see the LEDs flashing; if you replace one of them
with a piezo speaker, you should be able to hear it change.
Unplug your Arduino for a minute and replace the LED at pin 10 with a piezo speaker. Some people, such as Brett Hagman of Rogue robotics, warn that you should always connect a resistor in series with the speaker. Most Arduino examples omit the resistor, except to make the speaker a little quieter. Do whatever you wish.
Reconnect the Arduino and see if you hear anything. You should hear a tone around 50 hz. That’s G1 which is about two and a half octaves below middle C.
Change both delayTime1 and delayTime2 to 2.
That should give you a 250 Hz tone: close to B3, the note
just below middle C.
Either use your perfect pitch or your cell phone’s guitar tuner app to check the accuracy of the note. (I use the embarrassingly named gStrings to tune my ukulele.)
The need for higher resolution
Clearly we need a delay with a resolution better than milliseconds to make music. Otherwise, we are stuck with E4 and B4 as our only notes above middle C.
Fortunately, there is an Arduino function
delayMicroseconds with microsecond resolution.
The only problem is that delayMicroseconds is restricted
to delays no more than 16383 (215-1) microseconds.
That’s not
really a problem as long as we avoid the lowest notes of the piano.
Modify your program to use delayMicroseconds rather than
delay. Also, change it to play a note at around 440 Hz,
the ISO specified pitch for A4, the A above middle C.
Now, let’s see. The frequency is 440 Hz. This means the
period (1/frequency)
is about 2272 µs. So let’s set both
set delayTime1 and delayTime2 to the
half-period in µs or 1136.
Again, test it with your tuner app.
Varying the sound a bit
You don’t get much of a sound with our little piezo speakers, but you can get a little variation by changing the times for the high and low outputs.
Modify your program so that the sum of
delayTime1 and delayTime2 is 2272, but
make delayTime1 about four to eight times greater than
delayTime2.
Does this change the sound?
Some claim that this makes the note “thin” or
“reedy.”
Bit banging
Many microcontrollers contain special hardware to support communication
with other devices, such as sensors and other microcontrollers.
If this hardware isn’t available, the programmer will have to
resort to bit
banging, where low-level programming is used to read and write
voltage levels on pins using calls similar to
digitalWrite(9,LOW) or
mPORTDSetBits(BIT_2) or statements similar to
PORTB|=B00010000 or
PORTBSET=0b00010000.
Some programmers consider bit banging to be the programming equivalent
of free climbing and post their bit banging conquests on YouTube. For the ultimate
in this genre, check out
I2C Bit-Banged
without Microcontroller!.
In Tom Igoe’s Play a Melody using the tone() function Arduino tutorial, the tone function is used to generate a square wave that can be sent through a piezo speaker. If you read the reference page for tone, you see the function has some unusual restrictions, such as only allowing one tone to be played at a time.
Use the source. Look!
It’s time to take a look at GitHub. Open up the GitHub page in another browser window. Then search for Arduino using the text box at the top of the page. Click the link for the project arduino/Arduino. This will bring you to the repository for the Arduino platform.
Now search for tone in the repository. You should find
the source code for tone in the file
Tone.cpp. It will be about the fifth file
in the list.
Let’s take a guided tour of Tone.cpp. Note the following:
- The heavy use of
#ifand#defineto support the different processors used in the Arduino project - The setting of
USE_TIMER2for the ATmega8 chip of the Arduino Uno (~ line 100) - The use of
PROGMENto store some arrays (~ line 110) - Initialization of the timer module in
tone(~ line 260) - Setting the toggle count in
tone(~ line 350) - Toggling the bit in an interrupt (~ line 540)
With all that heavy use of timers and interrupts, I don’t think this looks like bit banging.
Starting with the tone() program
Let’s start with a fresh sketch. Create a new Arduino sketch and cut-and-paste the following program for your code.
const int outputPin1 = 10 ;
const int outputPin2 = 11 ;
void playTone(int pin, int frequency, int duraction) ;
/*
pin: input pin for playing note
frequency: frequency of the tone in cycles per second (Hz)
if frequency is 0, play no tone, that is, take a rest
duration: length of the tone (or rest) in milliseconds (msec)
*/
void setup() {
pinMode(outputPin1, OUTPUT) ;
pinMode(outputPin2, OUTPUT) ;
}
#define MIDDLEC 261.625
void loop() {
playTone(outputPin1, (int)MIDDLEC, 750) ;
playTone(outputPin1, 0, 250) ;
playTone(outputPin1, (int)(MIDDLEC*5/4), 750) ;
playTone(outputPin1, 0, 250) ;
playTone(outputPin1, (int)(MIDDLEC*3/2), 750) ;
playTone(outputPin1, 0, 250) ;
playTone(outputPin1, (int)(MIDDLEC*2), 750) ;
playTone(outputPin1, 0, 250) ;
}
// This implementation of playTone() uses tone()
void playTone(int pin, int frequency, int duration) {
if (frequency > 0) {
tone(pin, frequency) ;
delay(duration) ;
noTone(pin) ;
} else {
delay(duration) ;
}
}
The program should play something resembling a major chord, but the resemblance may be very weak with some of our piezos.
A little bit like bit banging
You goal for this part of the lab is to rewrite
playTone so that it does no calls to
tone, but instead uses digitalWrite
and delayMicroseconds
to create the music.
This will not be easy.
Here is one suggestion for completing the task.
- Use the
frequencyanddurationparameters to compute both the period and the remaining duration in microseconds. Because the Arduino Uno is a 16-bit computer, you will need to store the remaining duration in along. - Write a loop that subtracts the period from the remaining duration until the tone has been completely played.
- Inside the loop use
delayMicrosecondsanddigitalWriteto generate one period of the tone.
There are better ways to implement playTone, but
this approach has worked well in previous CSCI 255 offerings.
This isn’t truly a bit banging solution because
delayMicroseconds is implemented with timers
and interrupts.
If you used delay loop functions, like _delay_loop_2,
you could avoid timers.
Giving some structures to your program
So have just you had written a routine
playTone to play on a pin, a tone of a frequency
for a duration.
Again, here’s the C/C++ prototype for that function.
void playTone(int pin, int frequency, int duration) ;
Our representation of a note has an abstraction issue. The note is a real-world entity: We shouldn’t be splitting the note into two arrays, one for frequency and the other for duration. We should use a C structure to represent a note.
Defining the structure
So add the following structure definition at the front of your program,
close to the prototype you have for playTone.
struct noteInfo {
int frequency ; /* frequency in Hz */
long duration ; /* duration in mSec */
} ;
Note the slight resemblance to a Java class definition.
Storing the song
Using the struct noteinfo you can store your
entire song in an array, rather than embedding it in your program.
So just insert the following
right before the call to setup.
#define MIDDLEC 261.625
struct noteInfo song[] = {
{ (int)MIDDLEC, 750},
{ 0, 250},
{(int)(MIDDLEC*5/4), 750},
{0, 250},
{(MIDDLEC*3/2), 750},
{0, 250},
{(int)(MIDDLEC*2), 750},
{0, 250}
} ;
Now add an additional prototype for playTone to receive the
note in a pointer to the Note structure.
void playTone(int pin, struct noteInfo *note) ;
Because Arduino is based on C++ and consequently supports polymorphism, you should still be able to compile you program.
Using your song
Next, modify loop
to call playTone for each note of the song.
We will use one of the ugliest features of C/C++, the sizeof
hack for determining the size of a statically allocated array.
Hold you nose while you replace loop with the following:
void loop() {
int i ;
for (i = 0; i < sizeof(song)/sizeof(song[0]); ++i) {
playTone(outputPin1, &song[i]) ;
}
}
At this point, the compile should fail with an error message
that there is no
implementation of playTone that fits the
playTone(int, noteInfo*) signature.
Adding another playTone
Using your object-oriented programming skills, add the three-line
implementation of
playTone(int, struct noteInfo*) needed to
complete this program.
This is an overload of playTone.
What about simultaneous notes?
If you want decent sounding music with harmonies; you need a better speaker, a small amplifier, and a much more complicated program, such as the one found in the Skill Builder: Advanced Arduino Sound Synthesis Make project. I’d be happy to lend you the parts.
On the other hand, you could take CSCI 333, Data Structures, to learn about priority queues, and then take CSCI 331, Operating Systems, to learn how priority queues are used to schedule tasks and then write a program that schedules the wave changes.