Pump-It-Up Game
Hyunuk Kim, hk237
Joon Young Kwak, jk252
Table of Contents
High Level Design: Rationale
| Calculations |
Hardware/Software Trade-offs |
Standard Relations |
Intellectual Properties/Trademarks
Program/Hardware Design: Software
| Hardware |
Attempts that did not work
Intellectual Property Considerations
Appendices:
Appendix A: Codes
| Appendix
B: Budget | Appendix C: Task
Distribution | Appendix D: References |
Appendix E: Pictures
Our Project is a game that generates
arrows which player needs to stroke corresponding keyboard button while background
music is playing.
Computer-based Dancing games such
as DDR and Pump-It-Up are very popular nowadays because they are equipped with
both interactive user interface of computer games and dynamics of dance and
music. Our version of the game also provides these yet is more affordable in
terms of cost and space.
We implemented the game using
Mega32 MCU and STK500 development board, LCD for display, TV-Audio type speaker
for music, and PS/2 type keyboard for user input.
High level design
The
game is already commercially available. In fact, it is very popular among wide
range of ages. We transplanted the game to Mega32 based platform, bringing down
cost and changing user interface to more compact and affordable form.
We
concentrated on limiting our project small and simple. However, the game
requires several independent tasks running at the same time, such as input
processing, sound generation, and display. These tasks need to be concurrent while
not interfering with each other. Therefore, we sought ways to distribute the
tasks evenly and assign tasks to independently running processes, so that the
game runs smoothly with strong concurrency.
We
originally planned to use TV as display interface. But we replaced TV with LCD
to save cycles spent on TV display and also saved significant amount of timing
budget spent for proper TV display. LCD also consumes less energy and is easier
to control. Also using timer¡¯s PMW capability made the music to have no noise
or discontinuity problem. Processing ps/2 input using external interrupt made
asynchronous sampling of ps/2 input possible. Moreover, all of these processes
are handled by one MCU.
Sound:
The
minimum unit of music that our project provides is a note. It is generated by
PMW in timer0, and it can change per one frame of LCD display, which is 100 milliseconds
in minimum. We converted the frequency into counts per refresh rate based on
the minimum refresh rate by using the following procedure (simplified). Because
we¡¯re free with the constraints by TV display, our maximum frequency is the
same as the
fmax = 1/16e-6; %
16MHz
rate
= fmax/period of notes
Calculate
frequency generated by calculated counts
Compare
with desired frequency, use frequencies with small errors.
And in
doing so, we found that the 18 notes that we planned to use in our project have
almost no error: at max 0.03% of error from the desired frequency. The matlab
code used for error calculation and its result is included in Appendix
A.
PS/2:
PS/2
has 11 consecutive bits transmitted with clock. However, though clock itself is
uniform, PS/2 doesn¡¯t generate clock signal when the line is idle. In order to
handle PS/2 input properly, we verify the length between two consecutive clocks
so that we don¡¯t misinterpret a packet. Since each clock has a period of about 70
micron (standard PS/2 Keyboard in the lab observed by oscilloscope), we run a
timer that verifies if the period is much larger than the standard (about x3 of
typical clock period length, 200 micron), and discard PS/2 input data if clock
fails.
One
packet of keyboard input takes about 11x70=770 micron. When we run the game, we
empty key-stroke record at the minimum of 100 milliseconds. Therefore,
theoretically, we can process at most 129 keystrokes, calculated as below
We use mono single-tone sound
using TV-audio type speaker. However, we could significantly improve the
quality of music by using another MCU that generates higher quality sound.
However, in order to keep our project simple, we limit our project to one MCU,
trading off the quality of music and hardware budget.
Replacing TV display with LCD
gave an advantage by making timing budget more flexible and also making display
control easier. However, since we used TEXT LCD, we had to sacrifice the
quality of graphics. We also planned to use graphic LCD. However, it is beyond
the maximum budget of this project.
PS/2 standard and Keyboard:
We use standard PS/2 keyboard
to get user input. The PS/2 interface has 6 PINS: 1 Vdd, 1 Gnd, 1 Clock, 1
Data, and 2 N/C. When there is active transmission, clock is generated and data
can be read on the falling edge of the clock. There are 11 bits in one packet
of data, which consists of 1 starting bit, 8 data bits, 1 parity bit, and 1
ending bit. For keyboard, one key generates two types of scan codes: On-Press
and On-Release scan code. In this project, only On-Press scan code is our
interest, therefore we discard On-Release part of the scan code.
The keyboard operates on 5V
Vdd. It drives about 60mA of currents typically (Keyboard specification, back
of PS/2 Keyboard we used). Since one PIN of STK500 can¡¯t handle such an amount
of current, we built another power supply for keyboard.
Intellectual Properties and Trade Marks
Pump-It-Up¢â, the game, is
created by Andamiro, Inc. The game is already commercialized. Therefore, we do
not intend to produce the project commercially without the company¡¯s
permission.
The Setup
Overall Software Process Design
Sequential Process
-
The
main loop contains only initialization and idle infinite loop because all
processes are triggered by interrupt.
-Initialization:
This
process initializes LCD, PORTS, and few variables. It also enables some
interrupts.
-Selection:
This
is the process where user selects game mode: Single, VS, and Dual, and song to
be played. Keyboard input taken by interrupt routine is processed to make
choice. Once user starts the game, main game, [Next_Step] is executed. At this
point, TIMER1 is enabled.
-Next_Step:
This
process is the actual game. It reads song data file from FLASH memory. The LCD
is refreshed per every call. This function is called by TIMER1 interrupt when
TIMER1 counted delay period specified by song data file. This function actually
draws steps and arrows on LCD, change the frequency in TIMER0 PMW so that the
sound changes, and also records score. When the song is done, indicated by 0xff
in data stream, it exits and executes [Score_Display].
-Score_Display:
This
process simply displays the score and records the high score. Then the program
is initialized again and game starts over from [Selection].
External Interrupt Process
-External Interrupt1:
This
interrupt is called on every falling edge of clock by PS/2 keyboard. The PS/2
keyboard generates clock only when a button is pressed, held, or released. When
an interrupt is called, the ISR reads data simultaneously. The ISR maintains a
counter that counts the number of bits in one packet which consists of 11 bits.
8 data bits are stored in a queue and sent to [KeyInput]. TIMER2 tracks the
time between two clocks. If the clock fails, i.e. takes too long, timer2 will
clear the queue and re-initialize the packet counter.
-KeyInput:
The
8 bit data sent from External Interrupt 1 ISR is matched with scan code list
and enables corresponding bit in input array.
-Data Structure and Variables
All
songs, their names, and other static information are stored in FLASH memory and
read by pointer to the address to avoid using SRAM. Only when the data needs to
be processed, it is assigned to temporary variables in SRAM.
Each
keyboard button is assigned a place in key stroke array for easier data
process. The variable is cleared per every frame to make sure keystroke in
previous frame is completely cleared. Also in doing so, the On-Release scan
code became unnecessary.
The
play data is made of 4 characters per frame.
The
first character is note to be played, i.e. 0: Do, 17: Fa, one octave above.
The
third character is player 1 play information.
The
fourth character is player 2 play information.
The
play information is:
0:
no keystroke required
1~5:
one key stroke required
6-15:
two key stroke required
For
example, data {7,5,1,7} yields:
In
order to match scrolled step information and note, duration and steps are
shifted by 2.
-Scoring System
One
correct key stroke counts as one point. So two-button combination gives at most
2 points. There is no penalty for wrong stroke as in the original game. Total
required key stroke is stored, and score is divided by total strokes required
becomes the final score.
-Interrupt Control
Certain
interrupts are not always enabled.
-TIMER0,
the sound generator, is enabled only when [START] is true: when the game is
actually running.
-TIMER2
and EXT INT1 is always enable since keyboard input comes asynchronously and
always needs to be processed.
-TIMER1,
the game controller, is enabled only when the game actually executes and is
disabled after one game is done.
-Sound Generation
Frequency vs Notes
Sound
is generated by timer0¡¯s PMW function by making the frequency to one that
matches with actual frequency of a note as in the table above. Timer0 is
enabled only when the game is started to prevent unnecessary noise in idle mode.
Overall Hardware Design
PortA
PortA
is connected to LED in STK500 for testing purposes, mainly testing the scan
code from keyboard.
PortB
PortB.3
is the PMW of timer0, which is used in generating music. It is connected to
TV-Audio type of speaker to generate sound. It should be connected in parallel
with a 10K resistor.
Ref:
PortC
PortC
is connected to LCD, 4x20 DMW 20434 in our project, for display. The connection
is the same as the LCD used in ECE476 Labs.
Ref:
lcd.c,
test code for LCD control
PortD
PortD
is connected the PS/2 Keyboard. PortD.3 is clock, which is Ext Int1 in Mega32.
PortD.7 is data, but it doesn¡¯t have to be port 7.
Refs:
[Wireless
Keyboard] lab group homepage
Power Supply for Keyboard
PS/2
Keyboard requires 5V power supply and generally drains about 100mA of currents.
(60mA according to data information on the back of keyboard in the lab). It is
beyond the amount that a PIN can drive and it would actually drain the power in
STK500 if connected directly to a Vdd in a Port. So we had to build a separate
power supply for the Keyboard. It is done by using a 9V battery connected to a
voltage regulator, LM340T5, a 5V regulator which can be found in the lab.
Though voltage output from a battery is not always stable, it is always greater
than 5V, so we have no problem with power in the keyboard.
Ref:
Note: If you plan to make one of these, make sure to have the right directions
in your connection. In Top view, the Middle pin is ground, the Left is Original
Voltage Input, and the Right is output voltage, at max 5V.
-
Using
the mat. We originally planned to use a dancing mat that came with pump-it-up
game. It has PS/2 interface and is connected to keyboard slot in computer.
However, it requires certain initialization process which we were unable to
discover, since the company doesn¡¯t have datasheet, the part is obsolete, and
we couldn¡¯t run the initialization software in the lab since it doesn¡¯t run on
windows2000 machine.
-
UART
connection of PS/2 Keyboard using PS/2 to Serial converter. We thought if we
use null-modem connection using serial to PS2 converter, we might be able to
convert the PS/2 signal to RS232 connection and process it using UART. Attempt
failed. However, we were able to build our project based on hyper-terminal
input and UART interrupt.
We
do not use TV display which limits the number of cycles available to other
processes. Also the project doesn¡¯t contain any blocking process. The game
itself is controlled by timer and tasks are distributed so that it is processed
within the absolute minimum time available for the process. Therefore, the project
runs at the right speed that we expected without any hesitation. Also external
asynchronous input such as that of keyboard is processed by External Interrupt,
which has highest priority among the interrupts that we use. Therefore, our project
achieves almost perfect concurrency.
As
shown from earlier notes calculation, at most we have 0.03% maximum error in
frequency. The error is generally even lower. The error is absolutely not noticeable
to normal human ears, and we verified it in our experiment. Therefore, we conclude
that sound would not disturb the user.
Unlike
the original plan to use dancing mat, PS/2 keyboard interface would not harm
any normal user physically. Also the LCD display does not radiate as much UV
radiation as TV. Therefore it is even better for the user¡¯s eyes. Volume is
adjustable so that the user can control it to the level that the music doesn¡¯t
cause distraction.
We
don¡¯t generate any RF signal or significant amount of radiation, therefore we
expect that the project would not disturb or interfere with other electric
devices.
Our
project is a standalone machine and does not require PC presence. It could be made portable as well. Therefore,
it can be more readily accessible by the people in many places.
The
PS/2 keyboard scan process, sound generation code, data structure in flash
memory with pointers could be re-used in other applications.
We
expected to build the machine based on TV display and Mat. So we cannot
directly compare the goals that we set before we start the project.
However,
we achieved very strong concurrency that we can¡¯t expect from TV based display.
Also we didn¡¯t expect much dynamics from LCD display. However, the LCD display
had much more capability of handling fast changing display than we expected.
We
expected to have another MCU to process PS/2 input. However, we actually
achieved it only by using one MCU with external interrupt. Also we didn¡¯t
expect to process many keyboard inputs. However, the keyboard actually
correctly processed 4 simultaneous key strokes without missing any of these.
The
music that we generated has much better quality than we expected. We expected
low-quality music so that we were planning to switch the sound to simply two
notes with correct-incorrect user input. But the notes that we generated
produced good sound for about 1.5 Octaves without noise or discontinuity with
the length of notes frequently changing even when there existed extensive
external interrupt.
We
use flash memory extensively for storing music. When we need to use the memory,
we load it into SRAM for speed. As a result, we could put large amount of game
data with good performance speed.
Possible Improvements
Since
our project reached our goal, to implement the machine using simple design, we could
improve the quality of the game by using more than one MCU and assigning
different tasks to other MCU¡¯s, such as making the sound to be MP3 quality with
stereo type sound.
We
also could use color LCD for better display, or even graphical LCD. However, we
couldn¡¯t do it because of our budget limit. We would not recommend returning to
TV display, considering the pros and cons of TV and LCD display.
We
could easily implement more than 3-button combinations, however, it seems to be
redundant.
Intellectual property
considerations
-Sound: We re-used the code and design for generating sound in Video Generation
with AVR in ECE476 Course Website,
-LCD display:
lcdtest.c,
a sample file for LCD control
In
controlling the LCD, we re-used library files that have been used throughout
the ECE476 labs.
-PS/2 Keyboard Input:
We
could obtain the basic ideas of PS/2 input processing from [wireless
keyboard] Project Group from 2003.
-Pump-It-Up:
For
more information about the pump-it-up game,
Though
we used pump-it-up, we only imported the rules of the game and our project
doesn¡¯t have direct relationship with the product. However, since
commercialized version of pump exists, it¡¯s unlikely that we will patent our
project. However, our project is distinguished from original product since
though the rules are the same, the game itself is far different in the way that
it interacts with user, its portability, and its low-power consumption with
standalone feature.
Ethical
considerations
Refering to the IEEE
Code of Ethics,
1.
to
accept responsibility in making engineering decisions consistent with the
safety, health and welfare of the public, and to disclose promptly factors that
might endanger the public or the environment:
- Our project doesn¡¯t generate
harmful effect to any person, since it is not designed to or generates
significant heat, radiation, or other forms of pollusion.
2.
to
avoid real or perceived conflicts of interest whenever possible, and to
disclose them to affected parties when they do exist:
-We do not commercialize our
project. Therefore there exists no conflict of interest with other parties.
3.
to
be honest and realistic in stating claims or estimates based on available data:
-We are honest with all the
observations, calculations, and derivations used in the project and there is no
hidden or modified information of the project.
4.
to
reject bribery in all its forms:
-There is no bribery regarding
our project since there is no monetary interest involved.
5.
to
improve the understanding of technology, its appropriate application, and
potential consequences:
-While working on the project,
we discovered more information and obtained greater understandings about
standards and protocols we used in our project, such as PS/2 protocol.
6.
to
maintain and improve our technical competence and to undertake technological
tasks for others only if qualified by training or experience, or after full
disclosure of pertinent limitations:
-This question seems to be
irrelevant to the project.
7.
to
seek, accept, and offer honest criticism of technical work, to acknowledge and
correct errors, and to credit properly the contributions of others:
-We are willing to seek,
accept, and offer any positive criticism, for it actually helped us in this
project.
8.
to
treat fairly all persons regardless of such factors as race, religion, gender,
disability, age, or national origin:
-Sure.
9.
to
avoid injuring others, their property, reputation, or employment by false or
malicious action:
-Ok.
10.
to
assist colleagues and co-workers in their professional development and to
support them in following this code of ethics:
-We must.
Appendices
%Generate counts for notes C0
to F1 in units of 10 milliseconds
fmax = 1/16e-6;
notes=[
16.35 %C0 do
17.32 %C#0/Db0
18.35 %D0 re
19.45 %D#0/Eb0
20.60 %E0 mi
21.83 %F0 fa
23.12 %F#0/Gb0
24.50 %G0 sol
25.96 %G#0/Ab0
27.50 %A0 ra
29.14 %A#0/Bb0
30.9 %B0 si
32.70 %C1 do
34.65 %C#1/Db1
36.71 %D1 re
38.89 %D#1/Eb1
41.20 %E1 mi
43.65 %F1 fa
];
r = (fmax./notes);
for i=1:length(r)
fprintf(1,'%6.1f%6.1f%10.4f\n',notes(i),round(r(i)),(r(i)-round(r(i)))/r(i));
end
Result
16.43823.0
-0.0001
17.33609.0 -0.0001
18.43406.0 -0.0000
19.43213.0 0.0001
20.63034.0 -0.0000
21.82863.0 0.0000
23.12703.0 0.0001
24.52551.0 0.0000
26.02408.0 -0.0002
27.52273.0 -0.0001
29.12145.0 -0.0001
30.92023.0 -0.0002
32.71911.0 0.0002
34.61804.0 -0.0001
36.71703.0 -0.0003
38.91607.0 0.0001
41.21517.0 -0.0000
43.61432.0 -0.0001
pump_t.c
void initialize();
void songSelect(char);
void selection();
void keyinput(char);
void next_step();
void step_clear();
void score_display();
#define LCDwidth 20
#asm
.equ __lcd_port=0x15
#endasm
#include <lcd.h>
#include <Mega32.h>
#include <stdio.h>
#include <stdlib.h>
#include <delay.h>
#include <string.h>
unsigned char p1arr[6],
p2arr[6]; // button step LCD
lines
unsigned char onerow[20];
unsigned char
got_step[2]={0,0};
unsigned char p1step[5],
p2step[5]; // button step record and counter
unsigned char
miss_step[2]={0,0};
unsigned char song; //
song number from database
unsigned char start; // song started/stopped
unsigned char pressed=1;
unsigned char songmax=6; // maximum number
of songs
// for keyboard input, use
interrupt 1
unsigned int line;
unsigned int next_delay;
unsigned char duration=0;
unsigned int time_count=0;
unsigned char sound;
unsigned char countIn;
unsigned char dataIn;
unsigned int score1=0;
unsigned int total1=0;
unsigned int score2=0;
unsigned int total2=0;
unsigned int ratio_u1=0;
unsigned int ratio_d1=0;
unsigned int ratio_u2=0;
unsigned int ratio_d2=0;
unsigned int dual_ratio_u=0;
unsigned int dual_ratio_d=0;
unsigned char mode=0; // 0=1p
1=2p comp 2=2p double
eeprom int
highu[3][7]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
eeprom int
highd[3][7]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
// we may increase the length
of these for sensitivity. but its not scaled to this thing.
char p1[4]; // p1 data holder
char p2[4]; // p2 data holder
char i=0;
/* song is made of 4 chars *
Length
bit0 1 is sound
bit1 is duration (delay_ms or
timer when switched)
bit2 is p1 arrow
bit3 is p2 arrow
*/
/* proto types will have 25
length */
const flash int songindex[7]={0,72,220,340,660,788,1020};
char
*song_names[13]={"TSTAR
\0","E and C
\0","B-FLY
\0","WYIY Maggie \0","Happy Bday \0","Nabiya
\0","Jingle
char
*mode_string[3]={"Single\0","Versus\0","Double\0"};
const flash char
songlist[1452]={
20,5,1,3,
20,5,1,3,
0,5,3,1,
0,5,3,1,
7,5,5,2,
7,5,5,2,
9,10,3,3,
9,5,2,2,
7,5,2,2,
5,5,1,4,
5,5,1,4,
4,5,2,2,
4,5,2,2,
2,10,1,1,
2,10,0,0,
0,10,0,0,
20,10,0,0,
20,20,0xff,0xff,
//83-66+1=18 *4=72
20,10,1,3,
20,5,3,1,
0,5,5,2,
4,10,1,3,
7,5,3,1,
0,5,5,2,
4,5,4,4,
7,5,4,4,
5,5,2,3,
5,5,1,5,
9,5,5,1,
12,5,5,1,
7,5,2,3,
7,5,4,5,
11,3,6,1,
14,3,6,1,
0,5,3,3,
0,5,2,2,
4,3,1,6,
7,3,1,6,
0,5,1,3,
0,5,2,2,
4,3,4,1,
7,3,4,1,
5,5,2,2,
5,5,1,3,
9,3,3,4,
12,3,3,4,
7,5,4,2,
7,5,2,5,
11,15,6,1,
14,15,0,1,
12,15,1,0,
0,5,0,0,
0,5,0,0,
20,5,0,0,
20,5,0xff,0xff,
//121-85+1=37 *4 =
148+72=220
20,5,1,4,
20,5,3,2,
7,10,3,2,
4,5,2,3,
4,5,4,1,
5,10,4,1,
2,3,1,5,
2,3,2,4,
0,3,3,3,
2,3,4,2,
4,3,5,1,
5,3,5,1,
7,10,5,1,
7,5,5,5,
7,5,3,3,
7,10,3,3,
4,5,2,2,
4,5,4,4,
5,10,4,4,
2,3,1,5,
2,3,3,3,
0,3,5,1,
4,3,5,1,
7,3,2,4,
7,3,2,4,
4,10,2,5,
4,10,0,0,
4,10,0,0,
20,10,0,0,
20,10,0xff,0xff,
//152-123+1=30 *4
=120+220=340
20,5,10,5,
20,5,10,5,
12,4,9,4,
12,2,2,2,
9,5,3,5,
7,3,2,2,
5,3,3,1,
7,10,2,1,
5,3,1,2,
5,5,14,4,
2,3,5,2,
5,5,4,1,
2,4,5,5,
0,2,5,5,
5,5,6,6,
5,4,10,10,
9,2,10,10,
12,10,7,7,
12,10,0,0,
7,5,10,10,
20,5,10,10,
12,4,5,5,
12,2,4,4,
9,5,3,3,
7,3,2,7,
5,3,5,2,
7,10,4,5,
5,3,3,2,
5,5,2,5,
2,3,4,2,
5,5,2,1,
2,4,1,2,
0,2,10,3,
5,5,11,4,
8,4,12,5,
12,2,11,4,
7,10,12,10,
8,10,0,0,
5,5,10,11,
20,5,5,12,
5,4,5,12,
14,2,5,12,
14,5,2,10,
14,4,5,12,
10,2,5,12,
10,10,2,5,
14,3,10,4,
12,5,13,4,
8,3,1,3,
12,5,2,2,
8,4,5,1,
7,2,5,1,
12,3,5,1,
12,3,6,6,
12,3,7,5,
10,3,3,3,
16,10,10,4,
14,10,0,0,
12,5,10,4,
20,5,10,4,
12,4,3,3,
12,2,2,2,
8,5,1,1,
7,4,9,9,
5,2,10,11,
7,10,10,11,
5,3,1,4,
5,5,5,3,
2,3,1,4,
5,5,10,13,
2,4,11,12,
0,2,12,5,
5,6,15,4,
8,4,15,3,
12,2,14,5,
7,15,5,5,
8,10,0,0,
5,10,0,0,
20,10,0,0,
20,10,0xff,0xff,
//233-154+1=80
*4=320+340=660
20,3,1,5,
20,3,1,5,
2,5,3,3,
2,5,1,5,
4,3,5,1,
2,3,5,1,
7,10,4,2,
7,3,3,1,
6,3,3,1,
2,5,4,2,
2,5,3,1,
4,3,5,3,
2,3,5,3,
9,10,10,6,
9,3,5,3,
7,3,5,3,
2,5,11,4,
2,5,5,3,
14,5,2,1,
11,5,4,2,
7,10,3,3,
6,3,10,15,
4,3,10,15,
12,5,4,2,
12,5,3,3,
11,3,4,2,
7,3,4,2,
9,10,5,10,
9,10,0,0,
7,10,0,0,
20,10,0,0,
20,10,0xff,0xff,
//266-235+1=32 *4=128 +
640 = 788
20,3,1,5,
20,3,3,3,
12,3,3,3,
9,3,2,4,
9,3,4,2,
10,5,4,2,
7,3,1,3,
7,3,2,2,
5,3,3,1,
7,3,4,5,
9,3,5,4,
10,3,5,4,
12,5,5,4,
12,3,5,4,
12,3,3,2,
12,3,3,2,
9,3,3,2,
9,3,4,5,
10,3,2,1,
7,5,2,1,
7,3,2,1,
5,3,1,3,
9,3,3,4,
9,3,5,6,
12,3,5,6,
12,3,3,2,
9,5,3,2,
9,3,8,10,
9,3,2,4,
7,3,2,4,
7,3,2,4,
7,3,2,4,
7,3,3,2,
9,5,4,3,
10,3,3,2,
10,3,3,2,
9,3,3,2,
9,3,3,2,
9,3,3,2,
9,3,4,4,
9,5,5,5,
10,3,5,1,
12,3,3,3,
12,5,3,3,
9,3,4,2,
9,3,2,4,
10,5,2,4,
7,3,1,1,
7,3,3,3,
5,3,5,6,
9,3,5,6,
12,3,3,3,
12,3,3,3,
9,5,10,11,
9,10,0,0,
9,10,0,0,
20,10,0,0,
20,10,0xff,0xff,
//325-268+1=58 *4=232 +
788 = 1020
20,3,1,5,
20,3,3,2,
0,3,2,3,
9,3,1,2,
7,7,5,1,
5,3,0,0,
0,3,5,1,
20,3,4,5,
0,3,3,4,
9,3,2,2,
7,7,1,5,
5,3,0,0,
2,3,2,1,
20,3,3,6,
2,3,5,5,
10,3,4,4,
9,7,2,2,
7,3,0,0,
4,3,5,7,
20,3,5,7,
12,3,3,6,
12,3,2,6,
10,5,3,3,
7,3,2,1,
9,3,0,0,
5,3,1,1,
20,3,5,3,
0,3,3,2,
9,3,2,1,
7,7,1,5,
5,3,0,0,
0,3,7,5,
20,3,6,1,
0,3,7,2,
9,3,6,3,
7,7,4,10,
5,3,0,0,
2,3,4,1,
20,3,6,2,
2,3,5,3,
10,3,3,1,
9,4,8,10,
7,2,8,10,
12,3,8,10,
12,3,8,10,
12,3,1,2,
12,3,3,3,
14,3,2,1,
12,3,1,2,
10,7,2,3,
7,3,0,0,
5,3,9,8,
20,3,9,8,
9,5,9,8,
9,3,9,8,
9,3,9,8,
9,5,9,8,
9,3,9,8,
9,3,10,11,
9,4,1,1,
12,2,2,2,
5,7,3,3,
7,3,0,0,
9,3,11,14,
20,3,11,14,
10,4,11,14,
10,2,11,14,
10,3,11,14,
10,3,10,13,
10,3,10,13,
9,3,10,13,
9,3,10,13,
9,3,2,3,
9,3,2,3,
7,3,5,4,
7,5,1,3,
5,3,10,5,
7,3,0,0,
12,3,9,8,
20,3,9,8,
9,5,9,8,
9,3,9,8,
9,3,9,8,
9,5,9,8,
9,3,9,8,
9,3,10,9,
9,4,11,3,
12,2,1,4,
5,7,3,5,
7,3,0,0,
9,3,13,14,
20,3,13,14,
10,4,13,14,
10,2,13,14,
10,3,13,14,
10,3,12,12,
10,3,12,12,
9,3,12,12,
9,3,1,5,
9,3,1,5,
12,3,3,2,
12,3,1,3,
10,7,5,1,
7,3,0,0,
5,10,0,0,
20,10,0,0,
20,10,0,0,
20,10,0xff,0xff
};
flash char *song_ptr=songlist;
flash char notes[]={61, 58,
54, 51, 49, 46, 43, 41, 39, 36, 34, 32, 31, 29, 27, 26, 24, 23};
interrupt [EXT_INT1] void
external_int1(void){
// falling edge of a
clock. Detect corresponding data bit.
// 1st-start, next 8-code.
3 more bits. (no need to store parity, etc)
countIn++; //count how many clock pulses
we have seen
/* countIn=1, start bit
countIn=2 to 9, data bits 0 to 7
countIn=10, parity bit
countIn=11, stop bit */
if (countIn==1) //seeing
start bit
{
dataIn=0; // initialize new
data
//set
up timer0 to check for no clock transition in 200uSec (100uSec by osciloscope)
//since
it must be continuous clock
TCNT2=0; //reset timer0
TCCR2=0x0B; //put timer0 into compare match mode, prescaler
of 64
TIMSK=(TIMSK&0x7F)|0x80;//make
bit7=1 to enable compare match interrupt timer2
TIFR=TIFR&0x7F; //make
bit1=0 to clear the timer2 comp match flag (just in case its set)
}
else if
((countIn>1)&&(countIn<10)) //data bits being sent
{
TCNT2=0;
//reset timer0 since saw a clock signal
TIFR=TIFR&0x7F; //make
bit1=0 to clear the timer2 comp match flag (just in case its set)
dataIn=dataIn>>1; //shift
data right by 1
if
(PIND.7) dataIn=dataIn|0x80;
//shift
in a received bit of 1. else 0 (automatic)
}
else if (countIn==10) //parity
bit being sent
{
TCNT2=0;
//reset
timer0 since saw a clock signal
TIFR=TIFR&0x7F; //set
bit1=0 to clear the timer0 comp match flag
//
we won't check parity
}
else if (countIn==11) {
//
a set of data is received.
if
(~dataIn==0b11110000) dataIn=0;
keyinput(dataIn);
//
later, do manipulation of buttonpad here
}
// check LED light and
reconfigure stuff.
// now need to process
release scan code
GIFR=0;
}
/* Timer 1 compare-match A ISR
counted 1 ms, Timer1 counter
cleared (no need to reinitialize TCNT1) */
interrupt [TIM1_COMPA] void
cmpA_match(void) {
if
(time_count<next_delay) time_count++;
else {
time_count=0; // counted duration
next_step();
}
}
/* Timer 2 Compare Match for
Keyboard Control: Catches clock failure */
interrupt [TIM2_COMP]
timer2_comp(void)
{
TIMSK=TIMSK&0x7F; //set bit1=0 to turn off timer0 comp
match interrupt
TCCR2=0x00;
//turn off timer0
TIFR=TIFR&0x7F; //set bit1=0 to clear timer0
comp match flag (just in case its set)
countIn=0;
//reset counters
dataIn=0;
}
/* Matches keyboard input with
scan code */
void keyinput(char newpress) {
/*char p1step[5],
p2step[5];*/
switch (newpress) {
/*
p1 buttons (5) */
/*
do check through LED */
//
currently done to keyboard buttons
//
to track press time, p1/p2step will be set to a certain non-zero number.
//
it will be decreased over time.
//
scoring will be done with this thing.
//
previous press won't count towards score.
//
combo buttons can be handled this way.
case (0b00010101): //q
p1step[0]=pressed;
break;
case (0b00011010): //z
p1step[1]=pressed;
break;
case (0b00011011): //s
p1step[2]=pressed;
break;
case (0b00100100): //e
p1step[3]=pressed;
break;
case (0b00100001): //c
p1step[4]=pressed;
break;
/* p2 buttons (5) */
case (0b01101100): //7
p2step[0]=pressed;
break;
case (0b01101001): //1
p2step[1]=pressed;
break;
case (0b01110011): //5
p2step[2]=pressed;
break;
case (0b01111101): //9
p2step[3]=pressed;
break;
case (0b01111010): //3
p2step[4]=pressed;
break;
default:
PORTA=0xff;
// LED
}
}
/* Song Selection & mode
setup with [start] processing */
void songSelect(char choice) {
/* song list rotation */
/* add display to line 3
and line 4*/
if (choice=='a' &&
(song>0)) song--;
if (choice=='b' &&
(song<songmax)) song++;
if (choice=='c') {
line=songindex[song];
start=1;
}
if (choice=='d' &&
(mode>0)) mode--;
if (choice=='e' &&
(mode<songmax)) mode++;
lcd_gotoxy(6,2);
lcd_puts(song_names[song]);
lcd_gotoxy(6,3);
lcd_puts(mode_string[mode]);
}
/* Song and Mode Selection */
void selection() {
TIMSK = 0x00;
//
turn off timer1
lcd_clear();
while (!start) {
p1step[2]=0;
p1step[3]=0;
p1step[4]=0;
lcd_gotoxy(0,0);
lcd_putsf("Select a
Song ");
delay_ms(1000);
if ( p1step[1] )
songSelect('a');
if ( p1step[4] )
songSelect('b');
if ( p1step[0] )
songSelect('d');
if ( p1step[3] )
songSelect('e');
p1step[0]=0;
p1step[1]=0;
p1step[3]=0;
p1step[4]=0;
lcd_gotoxy(0,1);
lcd_putsf("Push Mid
to Confirm");
delay_ms(1000);
if ( p1step[1] )
songSelect('a');
if ( p1step[4] )
songSelect('b');
if ( p1step[0] )
songSelect('d');
if ( p1step[3] )
songSelect('e');
p1step[0]=0;
p1step[1]=0;
p1step[3]=0;
p1step[4]=0;
lcd_gotoxy(0,2);
lcd_putsf("Song:");
lcd_gotoxy(6,2);
lcd_puts(song_names[song]);
lcd_gotoxy(0,3);
lcd_putsf("Mode:");
lcd_gotoxy(6,3);
lcd_puts(mode_string[mode]);
delay_ms(1000);
if ( p1step[1] )
songSelect('a');
if ( p1step[4] )
songSelect('b');
if ( p1step[0] )
songSelect('d');
if ( p1step[3] )
songSelect('e');
p1step[0]=0;
p1step[1]=0;
p1step[3]=0;
p1step[4]=0;
lcd_gotoxy(0,0);
lcd_putsf("
");
delay_ms(1000);
if ( p1step[1] )
songSelect('a');
if ( p1step[4] )
songSelect('b');
if ( p1step[0] )
songSelect('d');
if ( p1step[3] )
songSelect('e');
p1step[0]=0;
p1step[1]=0;
p1step[3]=0;
p1step[4]=0;
lcd_gotoxy(0,1);
lcd_putsf("
");
delay_ms(1000);
if ( p1step[1] )
songSelect('a');
if ( p1step[4] )
songSelect('b');
if ( p1step[0] )
songSelect('d');
if ( p1step[3] )
songSelect('e');
p1step[0]=0;
p1step[1]=0;
p1step[3]=0;
p1step[4]=0;
if ( p1step[2] ) {
songSelect('c');
lcd_clear();
delay_ms(1000);
TIMSK
= 0x10;
//
turn on timer 1 compare match ISR
TCNT1
= 0; //
initialize timer 1 counter
TCCR1B
= 0b00001011; // turn on clear-on-match and setup count time to 1ms
OCR1A
= 249; //
this is clk * 64, 250 count to count =1 ms=
time_count=0;
next_delay=0;
score1=0;
total1=0;
score2=0;
total2=0;
ratio_u1=0;
ratio_d1=0;
ratio_u2=0;
ratio_d2=0;
dual_ratio_u=0;
dual_ratio_d=0;
step_clear();
lcd_gotoxy(0,1);
lcd_putsf("> <\0");
if
(mode) {
lcd_gotoxy(13,1);
lcd_putsf(">");
lcd_gotoxy(19,1);
lcd_putsf("<");
}
}
}
}
/* Actual Game, called by
Timer1 */
void next_step() {
/* game play */
TCCR0 = 0;
if (start) {
sound=*(&song_ptr[0]+line+0);
duration=*(&song_ptr[0]+line+1);
p1[3]=*(&song_ptr[0]+line+2);
p2[3]=*(&song_ptr[0]+line+3);
// terminator
if
(p1[3]==0xff) start=0;
/*
sound */
if
(sound<18) {
if (notes[sound]>0) TCCR0
= 0b00011100; //not a rest
}
OCR0 = notes[sound];
lcd_gotoxy(0,0);
lcd_putsf("
");
i=1;
while
(i<4) {
/*
step
chars:
0-no
button
1-left
top
2-left
bottom
3-middle
4-right
top
5-right
bottom
extended
step chars (2 buttons):
6-LT+LB
7-RT+RB
8-LT+RT
9-LB+RB
10-mid+LT
11-mid+LB
12-mid+RT
13-mid+RB
14-LT+RB
15-LB+RT
no
3-buttons considered.*/
/*
p1 process */
switch
(p1[i]) {
case
(1):
sprintf(p1arr,"1oooo%c",0x00);
break;
case
(2):
sprintf(p1arr,"o1ooo%c",0x00);
break;
case
(3):
sprintf(p1arr,"oo1oo%c",0x00);
break;
case
(4):
sprintf(p1arr,"ooo1o%c",0x00);
break;
case
(5):
sprintf(p1arr,"oooo1%c",0x00);
break;
//
combos are to be added
case
(6):
sprintf(p1arr,"11ooo%c",0x00);
break;
case
(7):
sprintf(p1arr,"ooo11%c",0x00);
break;
case
(8):
sprintf(p1arr,"1oo1o%c",0x00);
break;
case
(9):
sprintf(p1arr,"o1oo1%c",0x00);
break;
case
(10):
sprintf(p1arr,"1o1oo%c",0x00);
break;
case
(11):
sprintf(p1arr,"o11oo%c",0x00);
break;
case
(12):
sprintf(p1arr,"oo11o%c",0x00);
break;
case
(13):
sprintf(p1arr,"oo1o1%c",0x00);
break;
case
(14):
sprintf(p1arr,"1ooo1%c",0x00);
break;
case
(15):
sprintf(p1arr,"o1o1o%c",0x00);
break;
default:
// 0x00, no press
sprintf(p1arr,"ooooo%c",0x00);
}
/*
p2 process, only iff p2 on*/
if
(mode) {
switch
(p2[i]) {
case
(1):
sprintf(p2arr,"1oooo%c",0x00);
break;
case
(2):
sprintf(p2arr,"o1ooo%c",0x00);
break;
case
(3):
sprintf(p2arr,"oo1oo%c",0x00);
break;
case
(4):
sprintf(p2arr,"ooo1o%c",0x00);
break;
case
(5):
sprintf(p2arr,"oooo1%c",0x00);
break;
//
combos are to be added
case
(6):
sprintf(p1arr,"11ooo%c",0x00);
break;
case
(7):
sprintf(p1arr,"ooo11%c",0x00);
break;
case
(8):
sprintf(p1arr,"1oo1o%c",0x00);
break;
case
(9):
sprintf(p1arr,"o1oo1%c",0x00);
break;
case
(10):
sprintf(p1arr,"1o1oo%c",0x00);
break;
case
(11):
sprintf(p1arr,"o11oo%c",0x00);
break;
case
(12):
sprintf(p1arr,"oo11o%c",0x00);
break;
case
(13):
sprintf(p1arr,"oo1o1%c",0x00);
break;
case
(14):
sprintf(p1arr,"1ooo1%c",0x00);
break;
case
(15):
sprintf(p1arr,"o1o1o%c",0x00);
break;
default:
// 0x00, no press
sprintf(p2arr,"ooooo%c",0x00);
}
lcd_gotoxy(14,i);
lcd_puts(p2arr);
}
lcd_gotoxy(1,i);
lcd_puts(p1arr);
i++;
}
line=line+4;
//
at this point, doing line0, top line, work on p1[0], p2[0]
//
rough draft is drawn (non-press done)
//
left top p1: p1[3]=1,6,8,10,14
if
((p1[0]==1) || (p1[0]==6) || (p1[0]==8) || (p1[0]==10) || (p1[0]==14)) {
lcd_gotoxy(1,0);
if
(p1step[0]>0) {
lcd_putsf("G");
score1++;
}
else lcd_putsf("B");
}
//
left bot p1: p1[3]=2, 6, 9, 11, 15
if
((p1[0]==2) || (p1[0]==6) || (p1[0]==9) || (p1[0]==11) || (p1[0]==15)) {
lcd_gotoxy(2,0);
if
(p1step[1]>0) {
lcd_putsf("G");
score1++;
}
else
lcd_putsf("B");
}
//
mid p1: p1[3]=3, 10, 11, 12, 13
if
((p1[0]==3) || (p1[0]==10) || (p1[0]==11) || (p1[0]==12) || (p1[0]==13)) {
lcd_gotoxy(3,0);
if
(p1step[2]>0){
lcd_putsf("G");
score1++;
}
else
lcd_putsf("B");
}
//
right top p1: p1[3]=4, 7, 8, 12, 15
if
((p1[0]==4) || (p1[0]==7) || (p1[0]==8) || (p1[0]==12) || (p1[0]==15)) {
lcd_gotoxy(4,0);
if
(p1step[3]>0) {
lcd_putsf("G");
score1++;
}
else
lcd_putsf("B");
}
//
right bot p1: p1[3]=5, 7, 9, 13, 14
if
((p1[0]==5) || (p1[0]==7) || (p1[0]==9) || (p1[0]==13) || (p1[0]==14)) {
lcd_gotoxy(5,0);
if
(p1step[4]>0) {
lcd_putsf("G");
score1++;
}
else
lcd_putsf("B");
}
if
(p1[0]>0 && p1[0]<6) total1++;
else
if (p1[0]>5) total1=total1+2;
if
(mode) {
//
left top p2: p2[3]=1,6,8,10,14
if
((p2[0]==1) || (p2[0]==6) || (p2[0]==8) || (p2[0]==10) || (p2[0]==14)) {
lcd_gotoxy(14,0);
if
(p2step[0]) {
lcd_putsf("G");
score2++;
}
else
lcd_putsf("B");
}
//
left bot p2: p2[3]=2, 6, 9, 11, 15
if
((p2[0]==2) || (p2[0]==6) || (p2[0]==9) || (p2[0]==11) || (p2[0]==15)) {
lcd_gotoxy(15,0);
if
(p2step[1]) {
lcd_putsf("G");
score2++;
}
else
lcd_putsf("B");
}
//
mid p2: p2[3]=3, 10, 11, 12, 13
if
((p2[0]==3) || (p2[0]==10) || (p2[0]==11) || (p2[0]==12) || (p2[0]==13)) {
lcd_gotoxy(16,0);
if
(p2step[2]) {
lcd_putsf("G");
score2++;
}
else
lcd_putsf("B");
}
//
right top p2: p2[3]=4, 7, 8, 12, 15
if
((p2[0]==4) || (p2[0]==7) || (p2[0]==8) || (p2[0]==12) || (p2[0]==15)) {
lcd_gotoxy(17,0);
if
(p2step[3]) {
lcd_putsf("G");
score2++;
}
else lcd_putsf("B");
}
//
right bot p2: p2[3]=5, 7, 9, 13, 14
if
((p2[0]==5) || (p2[0]==7) || (p2[0]==9) || (p2[0]==13) || (p2[0]==14)) {
lcd_gotoxy(18,0);
if
(p2step[4]) {
lcd_putsf("G");
score2++;
}
else lcd_putsf("B");
}
if
(p2[0]>0 && p2[0]<6) total2++;
else
if (p2[0]>5) total2=total2+2;
}
step_clear();
/*
scroll effect for arrows */
p1[0]=p1[1];
p1[1]=p1[2];
p1[2]=p1[3];
p2[0]=p2[1];
p2[1]=p2[2];
p2[2]=p2[3];
/*
delay control */
next_delay=((int)duration)*100;
}
//
start is false.
else
{
score_display();
initialize();
}
}
/* one game is played. Display
scores */
void score_display() {
TCCR0 = 0;
lcd_clear();
ratio_u1=score1*100/total1;
ratio_d1=(int)((double)score1*1000.0/(double)total1-10*ratio_u1);
ratio_u2=score2*100/total2;
ratio_d2=(int)((double)score2*1000.0/(double)total2-10*ratio_u2);
dual_ratio_u=(score1+score2)*100/(total1+total2);
dual_ratio_d=(int)((double)(score1+score2)*1000.0/(double)(total1+total2)-10*dual_ratio_u);
if (mode<2) { //
display 1p score for single/versus mode
lcd_gotoxy(0,0);
sprintf(onerow,"1P:%2u.%1u
percent\0",ratio_u1,ratio_d1);
lcd_puts(onerow);
if
(score1 >= score2) {
if
( (ratio_u1>highu[mode][song]) || ( (ratio_d1>=highd[mode][song])
&& (ratio_u1==highu[mode][song]) ) )
{
highu[mode][song]=ratio_u1;
highd[mode][song]=ratio_d1;
lcd_gotoxy(0,3);
lcd_putsf("1P
New High Score!");
}
}
//
new HS, 1p single mode
}
if (mode==1) { // display
2p score only for versus mode
lcd_gotoxy(0,1);
sprintf(onerow,"2P:%2u.%1u
percent\0",ratio_u2,ratio_d2);
lcd_puts(onerow);
lcd_gotoxy(0,2);
if
(score1>=score2) lcd_putsf("P1 wins");
else {
lcd_putsf("P2
wins");
if
( (ratio_u2>highu[mode][song]) || ( (ratio_d2>=highd[mode][song])
&& (ratio_u2==highu[mode][song]) ) )
{
highu[mode][song]=ratio_u2;
highd[mode][song]=ratio_d2;
lcd_gotoxy(0,3);
lcd_putsf("2P
New High Score!");
}
}
}
if (mode==2) { // display dual mode score for double mode
lcd_gotoxy(0,0);
sprintf(onerow,"Dual:%2u.%1u
percent\0",dual_ratio_u,dual_ratio_d);
lcd_puts(onerow);
if
( (dual_ratio_u>highu[mode][song]) || ( (dual_ratio_d>=highd[mode][song])
&& (dual_ratio_u==highu[mode][song]) ) )
{
highu[mode][song]=dual_ratio_u;
highd[mode][song]=dual_ratio_d;
lcd_gotoxy(0,3);
lcd_putsf("DBL
New High Score!");
}
}
delay_ms(2000);
lcd_clear();
lcd_gotoxy(0,3);
lcd_putsf("Restarting...");
delay_ms(1500);
}
void main(void) {
initialize();
while(1);
} //dummy main
/* erases key stroke records
*/
void step_clear(){
p1step[0]=0;
p1step[1]=0;
p1step[2]=0;
p1step[3]=0;
p1step[4]=0;
p2step[0]=0;
p2step[1]=0;
p2step[2]=0;
p2step[3]=0;
p2step[4]=0;
}
void initialize() {
lcd_init(LCDwidth);
lcd_clear();
sprintf(miss_step,"B\0");
sprintf(got_step,"G\0");
mode=0;
song=0;
start=0;
sound = 0;
//use
OC0 (pin B.3) for music
DDRB.3
= 1;
TCCR2
= 0;
OCR2 = 50; //Timer0 outoput
compare when 50 (decimal).1/(16Mhz/64)*25=100uSec.
DDRA=0xff;
PORTA=0xff;
DDRD=0x00; // PORTD is input for
keyboard(don't need to have output)
/* p1 p2 init */
step_clear();
line=0;
sprintf(p1arr,"ooooo%c",0x00);
sprintf(p2arr," %c",0x00);
/* make timer1 to decrease
step*/
/* Timer 1 setup,
CompA_Match ISR called on every 1 millisec */
// make external interrupt
on every falling edge of clock. (about 70 us per clockfall)
p1[0]=0;
p1[1]=0;
p1[2]=0;
p1[3]=0;
p2[0]=0;
p2[1]=0;
p2[2]=0;
p2[3]=0;
TIMSK = 0x00; // turn off timer 1 compare match ISR
MCUCR=0x08; // int1 triggered on falling
edge
GICR=0x80; // int1 enabled
GIFR=0; //
flag off
countIn=0; // counter reset
dataIn=0x00; // empty data Input
#asm ("sei");
selection();
}
Appendix B: Budget Listing
Parts |
Quantity |
Price ($) |
Mega32 MCU |
1 |
8 |
STK500 Development BD |
1 |
0 |
LCD (4x20, DMW 20434) –Optrex, AllCorps.com |
1 |
21 |
10K resistor |
1 |
0 |
LM340T5 V Regulator |
1 |
0 |
PS/2 Keyboard |
1 |
0 |
TV-Audio type Speaker |
1 |
0 (TV) |
9V battery |
1 |
0 |
Wires/Clippers |
- |
0 |
Total |
- |
29 |
Appendix C: Task Distribution
Hyunuk Kim, hk237:
Coding, Data Structure, General Design
Software Debugging
Hardware Setup and Testing
Hardware Trouble-shooting
Joon Young Kwak, jk252:
Sound setup, Note calculation in Matlab
Data Generation for songs and games
Budget Calculation
Appendix D: References:
lcd.c,
test code for LCD control
[Wireless
Keyboard] lab group homepage
Pump-it-up
Web:
Data sheets:
Vendor sites:
Ethics:
Appendix E: Pictures
Clock and Data signal on PS/2
Port.
The captured signal is scan code for keyboard button
¡®s¡¯. The horizontal scale is 250 micron per square, and the vertical scale is
5V for both channels. Notice the 11 bits data packet on the falling edge
contains 8 bit data 00011011 as the On-Press scan code for ¡®s¡¯.
Game Sequence 1, the
initialized screen before starting the game.
Game Sequence 2, the game.
Game Sequence 3, the scores.
Sample MPEG video image for
one entire game