SIMON Project Overview
Below is a detailed description of my project. I first examine and explain the hardware setup I chose, and then give an overall description of the software setup which discusses overall program organization, interrupt structure, subroutines, and timing. Then I go more in depth to look at the two main blocks of my program, the sections which make up the code implementing the two different modes of play. Finally, I offer some concluding remarks on my project, including what I would have done differently if I had to do it over and other general comments.
Appendix B contains a schematic of the project hardware, which this section discusses. For this project, as mentioned earlier, I used the AT90S4414 microprocessor. I felt this chip best suited my design goals because of the number of I/O ports--the project employs all four I/O ports on the 4414--and the presence of two timers. The program does make use of code from EE 476 Lab 3, the one-octave synthesizer, to play the tones associated with each button. However, that program was written for the AT90S1200, which was not suitable for this project due to the fact that it only has two I/O ports and one 8-bit timer, as well as a reduced instruction set.
This project also makes use of some external hardware. The display used is a standard 16x1 LCD, and is employed in 4-bit mode, no cursor, no blink, address increment, no scrolling, and 5x7 font. The speaker is also externally connected. There are also four external LEDs corresponding to each of the four colors in the original SIMON game--red, yellow, green, and blue. Finally, there are four external pushbuttons, which are unfortunately packaged together so that I couldn't separate them and do the same layout as the original game, which had the four buttons in a circular arrangement. The external components are connected to the development board via a protoboard which has slots for wires and pins. The wires to the board connect to the ports through female-to-female headers which are placed over the port pins. The one exception to this is the speaker; its jack is connected to an adapter which is plugged directly into the header on Port A.
Some of the wiring of familiar components may be slightly different than in previous labs; in particular, the LCD takes inputs from Port C rather than Port D, and it takes its power source from pin D7. The change to Port C came about largely because of the order in which I developed my project. I first wrote the majority of my code to work with the pushbuttons and LEDs on the development board. The standard setup for those components is to wire the LEDs to Port B and the switches to Port D, so I followed that convention for the first two weeks of development. Therefore, I decided that it would be much easier to add the LCD to Port C, since adding external pushbuttons and LEDs only required me to wire them so that they were consistent with the way the components on the development board were wired. For the buttons, this required me to connect the common lead on the pushbuttons to ground and set the pullups on their port pins, so that a read of those pins would yield a 1 unless one was pressed. For the LEDs, I had to wire their positive leads to a constant Vcc, and their negative leads to port pins which I would clear to turn them on. Switching the LCD to Port C was then a simple matter of modifying all of the references to Port D within the existing LCD code. I also changed the LCD power pin to Port D, pin 7, because Port A is modified by the Timer0 overflow interrupt service routine--specifically, the only pin that is ever set on the entire port is pin 6, the output for the speaker. Rather than modify the ISR, which worked perfectly well with the code I wrote for the switches and LEDs on the development board, I chose to change the LCD connection.
The overall external hardware wiring is summarized below (see the schematic for a pictorial representation):
Component Pin Connection ---------------------------------------- LCD 1 Port D gnd 2 +5 volts--Port D7 (under program control for LCD power-up/power down sequence) 3 10k trimpot wiper (the ends of the trimpot go to ground and +5) 4 Port C6 5 Port C5 6 Port C4 7-10 no connection 11 Port C0 12 Port C1 13 Port C2 14 Port C3 Speaker input Port A6 ground Port A gnd Yellow LED pos Port B0 (LED 0) neg Port B4 Red LED pos Port B1 (LED 1) neg Port B5 Green LED pos Port B2 (LED 2) neg Port B6 Blue LED pos Port B3 (LED 3) neg Port B7 Pushbuttons S0 Port D0 S1 Port D1 S2 Port D2 S3 Port D3 common Port D gnd
Note: For the LED connections, pos and neg indicate the positive and negative terminals, respectively, for a positive bias across the LED. For the pushbutton connections, Sx refers to the connection exclusively to switch x, and common is the common connection between all four buttons.
Appendix A contains the program listing for the assembly language used in the implementation of this project. This section focuses on the overall organization of the code, covering its execution in general from beginning to end in a sequential fashion.
The program begins with several special definitions, since almost all of the 4414's 32 registers are required at some point in the program. Low-order registers r1-r8 are used mostly for temporary storage, and are rarely manipulated, and high-order registers r16-r27 are used for operations which require more operations to be performed on their value. The comments in the program provide a short description of each register's function, but the following list examines each one's use in slightly more detail, so that the remainder of this overview can use the defined names and assume that the reader knows what the registers are:
There are also a number of special defines for constants, selected ones of which will be discussed in terms of their names only, not their values:
The program's lone data segment (.dseg) contains an array in RAM called tones, which holds a sequence of up to 100 tones. This is used in Mode 1 to store the sequence intended to be replicated by the user, so that it can be easily compared against user input. The actual use of the array--how values are stored and read from it--is discussed in the Mode 1 section.
The program only uses two interrupts, the Timer0 overflow and Timer1 overflow interrupts. The Timer0 interrupt is used in both modes for tone playback. Each time a Timer0 interrupt is taken, it toggles the output bit to the speaker unless the value in reload is 0x00, in which case no tone is meant to be played and a logical 0 is output to the speaker. The frequency with which these interrupts occur depends on the value of the reload register; a higher reload value corresponds to a higher frequency and thus a higher tone output to the speaker. It also sets the T bit just before it returns so that it can be used in the LCD setup code, since that code has several spin loops which only break when they detect a set T bit.
The Timer1 interrupt also has two functions, depending on the mode of play. In Mode 1, it is set to run with a prescaler of 1 (4 MHz), and is used to provide pseudorandom values during level 2 play if more than one tones are added to the current sequence (based on the number held in rndnum). In Mode 2, it is set to run with a prescaler of 8 (.5 MHz), and is used to provide the intervals that the computer waits for a user to replicate a tone before declaring the game over.
The program also makes use of several subroutines. Most of them are LCD subroutines written by Charles Ott and modified by Bruce Land. I also wrote my own subroutines to output an entire string to the LCD, and one to wait for roughly two seconds so that I could display a message for a certain period of time before changing to the next one in a sequence. The subroutines are briefly summarized here:
I would now like to discuss basic program flow outside of the two main parts of the program, the execution of Mode 1 and Mode 2. The program starts, at reset, by running through the LCD power-up and setup sequence, which consists of powering down the LCD, waiting 1.5 seconds, powering up the LCD, waiting another 1.5 seconds, and then calling the lcdinit and lcdclr subroutines. Once that is complete, it outputs the opening sequence "SIMON/EE 476 Project/by Mike Geiger", where the slashes designate changes between strings. It does so by successive calls to the outstr subroutine for each string, and then to the wait2s subroutine to keep the string displayed for roughly 2 seconds.
Then the program sets up the I/O ports and registers. Ports A and B are set to be all outputs, and the first seven bits of Port D are set to an input (Port D7 is the output to LCD power). The pullups on Port D are also turned on, and the LEDs cleared. Timer0 is cleared and turned on, and the Timer0 and Timer1 overflow interrupts are enabled. The toggle register is set to hold 0x40, and the following registers are cleared: outreg, numtones, count, wcount, and tcount. Also, the Z register is loaded with the address of the tones array, and then saved to r30save and r31save.
The program then enters the mode select sequence. It first outputs the string sequence "Select mode/S1: Memory mode/S2: React mode" and then returns the display to the "Select mode string". At this point, it enters a polling loop which polls pushbuttons S0 and S1 until one of them is pressed. At this point, the program jumps to the appropriate mode, depending on the button press.
Since the modes are covered in detail in the sections below, this section will conclude by discussing the end of game sequence. The program, once sent to the "error" label, outputs the string "Game over" and also sets a reload value of 10, which corresponds to a tone of approximately 127 Hz. It plays this tone over the span of 255 Timer0 overflow interrupts, or 1.004 seconds. Once that is complete, the program outputs the sequence "Play again?/S1: Y, S2: N" and displays that last string until the user presses one of the two specified buttons. The buttons are tested by a polling loop. If S1 is pressed, the program clears the LEDs and waits for the switch to be released before returning to the mode select sequence. If S2 is pressed, the game displays "Goodbye" and spins endlessly.
Mode 1 is the "memory mode" of play, the mode in which the user is required to repeatedly replicate an increasingly complex sequence output by the game. This section examines its execution in detail, from the program's "mode1" label on. At that point in the program, Timer1 is set to run as fast as possible (4 MHz) and rndnum is loaded with a 1. The program then waits for switch S1 to be released before proceeding with the level select sequence, which consists of the strings "Select level/S1: L1, S2: L2". A polling loop checks S1 and S2 until the user presses one of the two, and then sets the level register to the appropriate value. It then displays the string "Get ready ... " and waits for approximately two seconds before starting the actual game. This time is to allow the user ample time to release the switch used to select the level, rather than debouncing the press and ensuring that the button was released. Once the wait is over, the LCD displays "Mode 1" and then the Z register is restored to the address of the tones array in RAM.
At this point, the program is at the "mode1b" label, to which it will return often, as long as the user is able to successfully replicate sequences. There is a .25-second pause first, so that there is a short pause between the last user button press of each sequence and the start of a new computer-generated sequence. Then the Y register is set to point to the start of the tones array, because in successive iterations through the Mode 1 loop, Z will be incremented so that it points past the last tone in the array and into a spot where the next tone will be stored.
The next tone is then chosen by taking the value in rand and performing an AND operation with 0x03, so that the value is effectively converted to two bits. Depending on what that value is, one of the four available tones is chosen and its reload value is stored in the tones array. numtones is also incremented to indicate this addition, and the reload value is then cleared so that no tones will be played accidentally. The program then decrements the rndnum register, which is set to 1 before the loop's first iteration, as described above, but may hold other values after multiple iterations. rndnum is tested, and new tones are generated by getting pseudo-random values from TCNT1L (while rndnum is decremented) until rndnum equals 0. If in level 1, rndnum will always be 1 entering this stage of the program, and thus only one iteration of the tone-generating loop will be taken and one new tone will be added to the sequence. When that loop is complete, the tcount register is cleared to indicate that no tones in the sequence have been played back at this point.
The program then enters a playback loop in which it plays all tones currently stored in the tones array; this is the sequence which the user must replicate. reload is loaded with the next reload value in RAM (to which the Y register now points), and count is loaded with the corresponding count value. The appropriate LED is also cleared. The program then spins until count is 0--an interval of .25 seconds, since that's how the Timer0 interrupt structure is set up. There is then a pause of .25 seconds of silence--a similar spin loop with a reload value of 0--before the program proceeds. It increments tcount and tests it to see if it is equal to numtones; when this condition is satisfied, the entire sequence has been played. If more tones remains, the program returns to the top of the playback loop. Otherwise, it clears tcount and reload, sets the Y register to once again point to the beginning of the tones array, and enters a loop which tests the user's ability to repeat the sequence.
The program polls until it receives a button press, and then checks the reload value corresponding to that press against the appropriate value in the RAM array. The value from RAM has been loaded to reload, while the reload value for the user button press is in rtemp1. If the two are equal, the program plays the tone; otherwise, it jumps to the "error" label. This sequence is repeated until the tones array is exhausted, and if the user has correctly repeated all tones, the program gets the next random value from TCNT0 after the last button press. Since human interaction introduces a completely unpredictable element to the program--I have no idea of knowing how fast each user is going to press buttons--this value is as random as possible. If the game is on level 2, the value from TCNT0 is also moved to the rndnum register, where it is shifted by two bits and ANDed with 0x03 to produce a 2-bit value indicating the number of new tones to add to the sequence. If the game is on level 1, rndnum is simply set to 1. The program then returns to the "mode1" label.
Mode 2 is the "react mode" of play, in which the user is required to repeat each tone the computer plays as fast as possible. This section is similar to the Mode 1 section above in that it examines the execution of this mode in great detail, starting at the "mode2" label of the program. At this point, the program goes through a level select very similar to the one described at the start of the Mode 1 section. However, this time, there are three levels of difficulty, and they are indicated by loading maxnum and minnum with the appropriate values at the very beginning of the mode.
Once the level is selected, Timer1 is set to a prescale of 8 (.5 MHz) and then the "Get ready ... " string is displayed for 2 seconds. The program then outputs the "Mode 2" string to the LCD, and begins game play. As in Mode 1, it selects a tone based on the value of (rand AND 0x03), and plays that tone. However, it only plays one tone at a time, and then enters a polling loop which waits for a user button press. The amount of time it waits is based on the current value of the waitval register, which starts at the value in maxnum, and goes as low as the value in minnum. Before the polling loop starts, the value of waitval is moved to wcount, which is decremented every time Timer1 overflows by the Timer1 overflow interrupt. So, within the polling loop, if wcount reaches 0 or the user presses the wrong button, the program jumps to the "error" label. If the user correctly repeats the tone, waitval is decremented as long as it remains above the value in minnum, and then the program returns to the game play loop.
In conclusion, I definitely enjoyed this project. I think implementing this game was a challenging experience that worked out well, but there are definitely some changes I would make if I had to do it all over again. For one thing, Mode 2 would be harder. Rather than having the program exit after one missed button press, I would program it to continuously output new tones, faster and faster, and only exit once the user input a wrong tone. So the game would start slowly, outputting a tone perhaps once every 2 seconds, and soon it would be outputting a tone every 200 milliseconds, and the game would end once the user was unable to keep up any longer.
I also would have improved my randomness slightly for Mode 1, level 2. Rather than simply drawing four "random" values in succession off of TCNT1L, I would only read TCNT1L (or TCNT0) once--on a button press--and create four random tones from that value by taking the four 2-bit quantities contained in that 8-bit number--bits 7-6, 5-4, 3-2, and 1-0--and storing them for use as the new additions to the sequence. Overall, though, I think my project works well and does what I intended it to do. It may not be as attractive physically as the original SIMON (it's hard to compete with that package when you're doing your wiring on a breadboard), but I think it works just as well and goes beyond the original in terms of functionality.