You’re going to learn a bit about C structures and C++ classes. The C structure is similar to a method-less Java class. The C++ class is odd.
Getting ready
Recreate the Arduino and piezo speaker connection of last week. 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. To make sure everyone’s breadboard is connected correctly, try out the following program. Both you and all your lab neighbors should hear the speaker.
const int outputPin = fill_this_in ; void setup() { pinMode(outputPin, OUTPUT) ; } void loop() { digitalWrite(outputPin, LOW) ; delay(10) ; digitalWrite(outputPin, HIGH) ; delay(10) ; }
Using variables
Declare an int
variable delayTime
as a local variable of loop()
.
Initialize delayTime
to 10.
Use
delayTime
as the argument to the calls 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.
The need for higher resolution
With delayTime
set to 10, the output freq ency should be
about 50 Hz tone: to G1, that’s about
2.5 octaves below Middle C.
Now set delayTime
to 1, the output frequency should now
be about 500 Hz.
If you open up the
Physics
of Music page from Michigan Tech, you see that the note should
be close to B4, also an octave above Middle C.
You can use your cell phone’s
guitar tuner app to check the accuracy of the note.
Don’t expect much. You are using a buzzer, not a
speaker.
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
delayTime
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 lengths of the high and low pulses.
Modify your program to pass
delayTime*3/2
to the first call
delayTime/2
to the second call of
delayMicroseconds()
.
Does this change the sound?
Some claim that this makes the note “thin” or
“reedy.”
In any case, set both calls back to using delayTime
.
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 repository for arduino/Arduino.
Now search for Tone.cpp in the repository.
This is
the C++ source code for the tone
function.
Let’s take a quick guided tour of Tone.cpp. Note the following:
- The heavy use of
#if
and#define
to support the different processors used in the Arduino project - The setting of
USE_TIMER2
for the ATmega8 chip of the Arduino Uno (~ line 100) - The use of
PROGMEN
to 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, this clearly isn’t 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 outputPin = fill_this_in ; void playTone(int pin, int frequency, int duration) ; /* 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(outputPin, OUTPUT) ; } #define MIDDLEC 261.625 void loop() { playTone(outputPin, (int)MIDDLEC, 750) ; playTone(outputPin, 0, 250) ; playTone(outputPin, (int)(MIDDLEC*5/4), 750) ; playTone(outputPin, 0, 250) ; playTone(outputPin, (int)(MIDDLEC*3/2), 750) ; playTone(outputPin, 0, 250) ; playTone(outputPin, (int)(MIDDLEC*2), 750) ; playTone(outputPin, 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
Your 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
frequency
andduration
parameters 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 entire tone has been played.
- Inside the loop use
delayMicroseconds
anddigitalWrite
to generate one period of the tone. CalldelayMicroseconds
twice: Once for high and once for low.
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 specified pin, a tone of a specified
frequency for a specified 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 variables, one for frequency and the other for duration. We should use a C structure to represent a note.
Defining the structure
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.
Just insert the following, admittedly ugly, definition
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 as a pointer to the struct noteInfo
.
The *
in the prototype indicates that we are passing
the address rather than the contents of the
structure.
Even though you could pass just pass the structure, in C
you almost always pass using a pointer.
That way you avoid copying a large 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(outputPin, &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.
While C insists that you always refer to the type of the second
parameter as struct noteInfo
; C++ is
fine with noteInfo
.
This is related to the esoteric domain of the C++ namespace.
However, the difference isn’t the real problem.
We just haven’t written a playTone
that knows
about noteInfo
yet.
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
.
Do not remove the first playTone
.
While this is a three-liner, it does require the use of
the dereference
operator *
to map a pointer to its contents
followed by the element select operator .
to retrieve the
desired field. It looks a bit like (*pntr).fld
.
Notice the Java also uses the dot for selecting fields (and methods) of
an object.
Because (*pntr).fld
is so ugly,
C allows the use of the arrow operator
pntr->fld
as syntactic sugar for (*pntr).fld
.
Adding class with C++
Start by opening up the Wikipedia article on C++ classes.
C++ does consider the struct
to be an
aggregate class, which really isn’t much of a class.
However, let’s a least use the keyword class
.
Insert the following class definition near the beginning of your
program. Note the the class include two constructors.
class Note { public: Note() { frequency = 0; duration = 1000; } Note(int initFrequency, long initDuration) { frequency = initFrequency; duration = initDuration; } int frequency ; /* frequency in Hz */ long duration ; /* duration in mSec */ } ;
There are still (at least two) things you must do.
- Remove, or comment out, the
struct noteInfo
declaration ofsong
. - Add a third
playTone
that matches the invocation insetup
that now passes anint
andNote*
. It will look a lot like theplayTone
that used the structure.
C++ that your CSCI 181 instructor might allow
In many CSCI courses you are not allowed to use public fields, so let’s write a C++ class that would be a bit more acceptable in your first programming course by using getter and setter methods.
First add private:
in the definition of
the class Node
before the two field definitions.
Try to compile your program. You should get an error message about
private variables.
Next add definitions for the getter methods in
your class definition. Put them in the public
section.
int getFrequency() { return frequency ; } long getDuration() { return duration ; }
You will also need to modify playTone
to use
the methods rather than the fields.
Getting out-of-line
Our two constructors and two methods are both inline, that is,
they are implemented in the definition. However, many C++ programmers
insist on separating the definition from the implementation.
We will do this for the getDuration
method.
First change the three-line implementation of getDuration
to a one-line definition.
long getDuration() ;
Usually, the definitions and implementations occur in different files,
but this time include the implementation a bit later in your
program. Do not put the implementation inside the class
definition.
long Note::getDuration() { return duration ; }
Odd, isn’t it.
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.
But first, you need to 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.