EENG 383
Lab 4 - Music Box with interrupts
InLab 4
Some self-guided
activities.
Lab assignment
Use interrupts and a terminal interface to play a song.
Program Requirements
Many of the program requirements in this week's lab are the same
as last weeks.
- A whole note should take 1 second,
- Have a 32nd rest between notes,
- Store at least two octaves worth of notes (half periods of timer 1)
in an array called scale. For example,
my code has the following (partial) declaration:
uint16_t scale[NUM_OCTAVES*12] = {12864,12144,11472,10816,10208,9632,…
Note, I used a #define to set NUM_OCTAVES to 2,
- Use Timer 0 with a 1:256 prescaler to generate the note durations,
- Your program will store several songs. Have a #define for NUM_SONGS
which is the number of different songs.
- Store your songs in the following arrays.
- The notes of the songs are stored in a 2D array.
The first index of the 2D array for the notes is the song.
The second index of the 2D array for the notes are the actual
notes of the song. For example,
my code, with Hot Crossed Buns as the first song, has the
following (partial) declaration for the note array:
uint16_t notes[NUM_SONGS][MAX_SONG_LENGTH] = {{B5, A5, G4, B5, A5,…}, … };
The symbols in the notes array are #defined to indicies in the scale
array which contains the half period of that note. For example,
since the half period of a G4 note is 10208 timer 1 counts (which
is index 4 in the scale array defined above), my code contains the
following #define at the top of the program:
#define G4 4
- The durations for the notes in the songs are stored in a 2D
array. The first index of the 2D array for the durations is the
song. The second index of the 2D array is the actual duration
for each note. For example my code has the following (partial)
declaration:
uint16_t duration[NUM_SONGS][MAX_SONG_LENGTH] = {{QUA, QUA, HAL, QUA,…}, … };
The symbols in the duration array are #define to the number of 1:256
prescaled timer 0 counts each duration required. For example, a quarter
note is 0.25 seconds long, corresponding to 15625 counts on a 1:256
prescaled timer 0. So my code contains the following define:
#define QUA 15625
- Store the number of notes in each song in an array songLength.
Each entry in the array is the number of notes in the song at that
index in the notes and duration array. A define for MAX_SONG_LENGTH
provides before the array definitions will provide an upper bound
on the length of any one song.
- Be careful setting SONG_LENGTH and NUM_SONGS too high - you may
run out of memory. You can see how much memory your program is using
in the Dashboard tab at the bottom main project navigator window.
- Set the speaker pin to logic 0 when you are not playing a note,
- Use the TMR1 ISR to toggle the speaker pin when a
global variable playNote is true. The note frequency played
is determined by a pair of global variable, songNumber and noteIndex.
The TMR1 ISR uses these variables to index the (global) notes array.
- Use the TMR0 ISR to play the notes of the song for the
correct duration when a global variable, playingSong, is true. The
TMR0 ISR should "tell" the TMR1 ISR to play the next note in the sing
by incrementing the noteIndex global variable after each note duration
has expired. Remember that after each note is played, it is followed
by a rest. I used a static local variable in the TMR0 ISR to tell
me if the TMR0 ISR had just finished playing a note or just finished
waiting out the rest between notes. You can set the global playNote
variable to false during the rest to stop the speaker from toggling.
When the TMR0 ISR completed playing the song, it should set the noteIndex
back to 0 and set the global playingSong variable to false.
At start-up your program should present a splash screen - this would
be a great place for some
ASCII art. The splash
screen should also contain connection instruction for the development board
(where to install jumper wires). When you press "?" you should be
greeted with the following menu.
------------------------------
?: Help menu
o: k
Z: Reset processor
z: Clear the terminal
p: play song once
r: Rhythm practice
------------------------------
- ?
Prints out the ever useful help menu.
- z
Clear the terminal using a bunch of new lines.
- Z
Reset the processor so that we can see that splash screen.
- p
When the p key is pressed, set the playingSong global variable
true. Since the song is being completely played in the background, you
should be able to continue to use the terminal while the song is being
played.
- r
This program features tests the users ability to reproduce the note
durations
of a song. To accomplish this, you will need three songs; a test song,
a success song, and a failure song. Start by playing the test song
for the user. I would suggest a simple test song with three notes,
all with half notes. When you finish playing the test song, prompt the user
to press the upper button with the same duration as the test song.
Each button press will play one note of the song while the
button is held down (like inLab03). When the button is released, your
program should increment to the next note (like inLab03).
Your program needs to measure the
number of TMR0 counts between consecutive button presses and store it in
a variable, pressDuration. Do not concern yourself with TMR0 rollovers
when calculating pressDuration. In order to stop the TMR0 ISR from changing
TMR0 on its own, disable the timer 0 interrupt (after the code plays the
test song) by setting the TMR0IE to 0. You should compare pressDuration
against the expected duration of that note (from the duration array).
Since it would be unreasonable to expect the user to be accurate to
1 TMR0 count, create some interval around the expected duration which
represents a valid note duration from the user. In the following
code snippet, I used a #define for DELTA set to about 0.15 seconds.
Note I accounted for the REST delay heard by the user.
The larger DELTA is the more forgiving your program will be of timing
miscues.
if ( (pressDuration < (duration[songNumber][noteIndex-1] + REST - DELTA)) ||
(pressDuration > (duration[songNumber][noteIndex-1] + REST + DELTA)))
success = false;
Your program should stop allowing user input at the first note duration
failure, immediately (or almost immediately) exiting out
of the test mode. If the user fails to reproduce the rhythm of the song,
print a failure message and play a failure song.
If the song is finished as a success, then print a success message and play
a short success song. The output from two runs of my program is shown
below.
Listen to the beat pattern.
Use the upper button to reproduce this pattern.
You lose.
Listen to the beat pattern.
Use the upper button to reproduce this pattern.
Winner!
After alerting the user of the success or failure of their efforts,
re-enable the TMR0 interrupt by setting TMR0IE.
Turn-in
You may work with a single partner (or alone) to complete this lab.
Submit your main.c file on Canvas using the instructions posted
there. You should take note of the Rubric that will be used to evaluate
your assignment. Please form a group before submitting using the
instructions posted on Canvas. You will demonstrate your code at the
beginning of lab.