//p2r.c :main program #pragma regalloc- #pragma optsize- //optimize for speed #include #include #include #include #include #define begin { #define end } //screen display //cycles = 63.625 * 16 Note NTSC is 63.55 //but this line duration makes each frame exactly 1/60 sec //which is nice for keeping a realtime clock #define lineTime 1018 #define ScreenTop 30 #define ScreenBot 230 //debouncer of buttons #define DebounceTime 4 //Switch Debounce State machine state names #define NoPush 1 // state of no push #define MaybePush 2 // state of maybe push #define Pushed 3 // state of push #define MaybeNoPush 4 // state of maybe push //playing mode //audio speed #define BasicSpeed 2 #define HardSpeed 1 //arrows speed #define ARROW_SPEED_BASIC 2 #define ARROW_SPEED_HARD 3 //audio, playing notes #define AUDIO_COUNTER 2 //playing mode and animation #define xl 50 //LHS arrow X position #define xr 71 //RHS arrow X position #define time_minute 0 //playing time #define time_second 30 //playing time #define delay 17 //delay of the arrow void initialize(void); void DrawStart(void);//draw start screen void DrawPlaying(void);//draw playing mode screen void DrawResult(void);//draw result screen void debounce(void);//debounce switch in mode 1 void mode0(void); //select song and level void mode1(void); //playing mode void clock(void); //diaplay time //TV char syncON, syncOFF; int LineCount; int time; char screen[1600]; //clock time int minute,sec; char t;//count from 0-59 per second //print buffer char ts[20]; //print screen char parapara[]="PARA PARA"; char revolution[]="REVOLUTION"; char SetSong[]="SONG:"; char song0[]="SONG0"; char song1[]="SONG1"; char song2[]="SONG2"; char level[]="LEVEL:"; char practice[]="ROOKIE"; char basic[]="BASIC"; char hard[]="HARD"; char left[]="LEFT"; char right[]="RIGHT"; char miss[]="MISS! ";//use 3 more space to erase "perfect!" char good[]="GOOD! "; char perfect[]="PERFECT!"; char miss1[]="MISS:"; char good1[]="GOOD:"; char perfect1[]="PERFECT:"; char hitrate[]="HIT RATE:"; char score[]="SCORE"; char statistics[]="STATISTICS"; char mode; //0= beginning, 1=playing, 2=result statistcs bit draw;//0=do not draw screen, 1=draw screen bit clean;//0=do not clean screen, 1=clean screen char k;//clean screen counter char d;//draw screen counter //for mode1 char SelSong; //select song char SelLevel;//select Level //switch debouncer unsigned char previousKey,debounced_flag; unsigned char PushState; unsigned char d_time;//debounce time char button;//debounce result //animation in playing mode unsigned char ypos[5]; //arrows' y axile unsigned char compare_pat; //the arrow should be compared with the sensor input unsigned char L_done, R_done; //compare done unsigned char lpat[5]; //LHS pattern 1.3.7. unsigned char rpat[5]; //RHS hand pattern 1.5.7. unsigned char patflag,patnum; //patflag from 0-4, //patnum from pattern[SelSong][L=0;R=1][0] to pattern[SelSong][L=0;R=1][patsize[SelSong]] char step; //arrow speed //statistics results unsigned int L_good, R_good; unsigned int L_perfect,R_perfect; unsigned int L_miss,R_miss; unsigned int L_hitrate,R_hitrate; unsigned int final_score; #include "font.c" //bitmaps #include "video.c" //video display and effects #include "song.c" //audio #include "handADC.c" //sensors ADC and state machines #include "drawscreen.c" //reset every mode's screen //================================== //This is the sync generator and raster generator. It MUST be entered from //sleep mode to get accurate timing of the sync pulses #pragma warn- interrupt [TIM1_COMPA] void t1_cmpA(void) begin //start the Horizontal sync pulse PORTD = syncON; //update the curent scanline number LineCount ++ ; //begin inverted (Vertical) synch after line 247 if (LineCount==248) begin syncON = 0b00100000; syncOFF = 0; end //back to regular sync after line 250 if (LineCount==251) begin syncON = 0; syncOFF = 0b00100000; end //start new frame after line 262 if (LineCount==263) begin LineCount = 1; end delay_us(2); //adjust to make 5 us pulses //end sync pulse PORTD = syncOFF; if (LineCount=ScreenTop) begin //compute byte index for beginning of the next line //left-shift 4 would be individual lines // <<3 means line-double the pixels //The 0xfff8 truncates the odd line bit //i=(LineCount-ScreenTop)<<3 & 0xfff8; // #asm push r16 lds r12, _LineCount lds r13, _Linecount+1 ldi r16, 30 sub r12, r16 ldi r16,0 sbc r13, r16 lsl r12 rol r13 lsl r12 rol r13 lsl r12 rol r13 mov r16,r12 andi r16,0xf0 mov r12,r16 pop r16 #endasm //load 16 registers with screen info #asm push r14 push r15 push r16 push r17 push r18 push r19 push r26 push r27 ldi r26,low(_screen) ;base address of screen ldi r27,high(_screen) add r26,r12 ;offset into screen (add i) adc r27,r13 ld r4,x+ ;load 16 registers and inc pointer ld r5,x+ ld r6,x+ ld r7,x+ ld r8,x+ ld r9,x+ ld r10,x+ ld r11,x+ ld r12,x+ ld r13,x+ ld r14,x+ ld r15,x+ ld r16,x+ ld r17,x+ ld r18,x+ ld r19,x pop r27 pop r26 #endasm delay_us(4); //adjust to center image on screen //blast 16 bytes to the screen #asm ;but first a macro to make the code shorter ;the macro takes a register number as a parameter ;and dumps its bits serially to portD.6 ;the nop can be eliminated to make the display narrower .macro videobits ;regnum BST @0,7 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,6 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,5 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,4 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,3 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,2 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,1 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 BST @0,0 IN R30,0x12 BLD R30,6 nop OUT 0x12,R30 .endm videobits r4 ;video line -- byte 1 videobits r5 ;byte 2 videobits r6 ;byte 3 videobits r7 ;byte 4 videobits r8 ;byte 5 videobits r9 ;byte 6 videobits r10 ;byte 7 videobits r11 ;byte 8 videobits r12 ;byte 9 videobits r13 ;byte 10 videobits r14 ;byte 11 videobits r15 ;byte 12 videobits r16 ;byte 13 videobits r17 ;byte 14 videobits r18 ;byte 15 videobits r19 ;byte 16 clt ;clear video after the last pixel on the line IN R30,0x12 BLD R30,6 OUT 0x12,R30 pop r19 pop r18 pop r17 pop r16 pop r15 pop r14 #endasm end end #pragma warn+ //================================== // set up the ports and timers void main(void) { initialize(); //The following loop executes once/video line during lines //1-230, then does all of the frame-end processing while(1) { #asm ("sleep"); if (LineCount==231) { audioT--; if (audioT<=0)PlayNote(); //always playing music if (PINC==0xfe) {mode=0;draw=1;clean=1;d=0;}//always detect reset button 0 if (mode==0){ //change to start mode if (draw==1){ DrawStart(); //draw user manu screen note=0; //restart music MusicTime=0; } mode0();//menu select }//end if (mode==0) else if (mode==1){ //change playing mode if (draw==1){ //draw playing mode screen and reset music DrawPlaying(); note=0; MusicTime=0; } clock(); //clock time if ((sec|minute|draw)==0){clean=1;draw=1;mode=2;d=0;} //time up;game over; go to statistic mode mode1(); //animation and sensors signal comparison }//end else if (mode==1) else if (mode==2){ //shcnag to statistic mode if (draw==1){ DrawResult(); //draw screen, restart music and calculate the final score note=0; MusicTime=0; } }//end else if (mode==2) } //line 231 } //while } //main void mode0(void){ //start mode, user manu if (d_time>0) d_time--;//for button debouncer if (d_time==0) debounce();//button debouncer if (debounced_flag==1){//a button is pressed note=0; MusicTime=0; debounced_flag=0; //button is read if (button==0xfd){ //button 1 start game mode=1; draw=1; clean=1; //clean and redraw screen d=0; if (SelLevel==2) step=ARROW_SPEED_HARD;//set arrow speed else step =ARROW_SPEED_BASIC; } if (button==0xfb){ //button 2, select songs, highlight selected option, put the pointer video_putchar(5, 61+10*SelSong, 39); video_highlight(14,61+10*SelSong,46,67+10*SelSong); if (++SelSong>2) SelSong=0; video_putchar(5, 61+10*SelSong, 40); video_highlight(14,61+10*SelSong,46,67+10*SelSong); } if (button==0xf7){ //button 3, select level, highlight selected option video_putchar(60, 61+10*SelLevel, 39); video_highlight(69,61+10*SelLevel,106,67+10*SelLevel); if (++SelLevel>2) SelLevel=0; video_putchar(60, 61+10*SelLevel, 40); video_highlight(69,61+10*SelLevel,106,67+10*SelLevel); } } } void mode1(void){ //playing mode if (draw==0){ //when drawing screen finished handset();//detect sensors, ADC //use a chain to slice works to many frames, use d as the counter if(d==0){//arrows animations video_putarrow(xl,ypos[0],9); video_putarrow(xr,ypos[0],9); ypos[0]-=step; video_putarrow(xl,ypos[0],lpat[0]); video_putarrow(xr,ypos[0],rpat[0]); d++; } else if (d==1){ video_putarrow(xl,ypos[1],9); video_putarrow(xr,ypos[1],9); ypos[1]-=step; video_putarrow(xl,ypos[1],lpat[1]); video_putarrow(xr,ypos[1],rpat[1]); d++; } else if (d==2){ video_putarrow(xl,ypos[2],9); video_putarrow(xr,ypos[2],9); ypos[2]-=step; video_putarrow(xl,ypos[2],lpat[2]); video_putarrow(xr,ypos[2],rpat[2]); d++; } else if (d==3){ video_putarrow(xl,ypos[3],9); video_putarrow(xr,ypos[3],9); ypos[3]-=step; video_putarrow(xl,ypos[3],lpat[3]); video_putarrow(xr,ypos[3],rpat[3]); d++; } else if (d==4){ video_putarrow(xl,ypos[4],9); video_putarrow(xr,ypos[4],9); ypos[4]-=step; video_putarrow(xl,ypos[4],lpat[4]); video_putarrow(xr,ypos[4],rpat[4]); d++; } else if (d==5){ //when arrows reach acreen top, earse them and set a new one part 1 if (ypos[patflag]<=2){ video_putarrow(xl,ypos[patflag],9); video_putarrow(xr,ypos[patflag],9); video_line(50,0,57,0,1); //redraw the line eaten by arrows video_line(71,0,78,0,1); video_line(50,10,57,10,1); video_line(71,10,78,10,1); video_line(50,20,57,20,1); video_line(71,20,78,20,1); } d++; } else if (d==6){//when hands wave, highlight their direction patterns part 1 if (RHU==0) video_putarrow(90,8,1); if ((RHR==0)||(RHL==0)) video_putarrow(100,16,5); if (RHD==0) video_putarrow(90,24,7); if (LHU==0) video_putarrow(30,8,1); if ((LHR==0)||(LHL==0))video_putarrow(20,16,3); if (LHD==0) video_putarrow(30,24,7); d++; } else if (d==7){//when hands wave, highlight their direction patterns part 2 if (LHU!=0){video_inv_putarrow(30,8,1);} if (RHU!=0){video_inv_putarrow(90,8,1);} if (LHD!=0){video_inv_putarrow(30,24,7);} if (RHD!=0){video_inv_putarrow(90,24,7);} if ((LHL!=0)||(LHR!=0)){video_inv_putarrow(20,16,3);} if ((RHL!=0)||(RHR!=0)){video_inv_putarrow(100,16,5);} d++; } else if (d==8){//when arrows reach acreen top, earse them and set a new one part2 if (ypos[patflag]<=2){//arrow disapear ypos[patflag]=92; lpat[patflag]=pattern[SelSong][0][patnum]; rpat[patflag]=pattern[SelSong][1][patnum]; video_putarrow(xl,ypos[patflag],lpat[patflag]); video_putarrow(xr,ypos[patflag],rpat[patflag]); if (++patflag==5) patflag=0; if (++patnum>=patsize[SelSong]) patnum=0; } d++; } else if (d==9){//RHS:compare the hands' waving direction with patterns. if match, count their times if((SelLevel>=1)&&(R_done==0)){ if ((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=16)){//when arrows come to compare range if ((ypos[compare_pat]>=10)&&(ypos[compare_pat]<=13)){//perfectly match->perfect if ((RHU==1)&&(rpat[compare_pat]==1)){ video_putsmalls(88,40,perfect); R_perfect++; R_done=3; } else if((RHD==1)&&(rpat[compare_pat]==7)){ video_putsmalls(88,40,perfect); R_perfect++; R_done=3; } else if(((RHR==1)||(RHL==1))&&(rpat[compare_pat]==5)){ video_putsmalls(88,40,perfect); R_perfect++; R_done=3; } } else if (((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=9))||((ypos[compare_pat]>=14)&&(ypos[compare_pat]<=16))){ if ((RHU==1)&&(rpat[compare_pat]==1)){//not perfectly match->good video_putsmalls(88,40,good); R_good++; R_done=3; } else if((LHD==1)&&(rpat[compare_pat]==7)){ video_putsmalls(88,40,good); R_good++; R_done=3; } else if(((LHR==1)||(RHL==1))&&(rpat[compare_pat]==5)){ video_putsmalls(88,40,good); R_good++; R_done=3; } } } }//end if((SelLevel>=1)&&(R_done==0)) else if((SelLevel==0)&&(R_done==0)){//for practice/rookie level if ((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=16)){ if ((ypos[compare_pat]>=10)&&(ypos[compare_pat]<=13)){ if (((RHU==1)||(RHR==1)||(RHL==1)||(RHD==1))&&((rpat[compare_pat]==1)||(rpat[compare_pat]==7)||(rpat[compare_pat]==5))){ video_putsmalls(88,40,perfect); R_perfect++; R_done=3; } } else if (((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=9))||((ypos[compare_pat]>=14)&&(ypos[compare_pat]<=16))){ if (((RHU==1)||(RHR==1)||(RHL==1)||(RHD==1))&&((rpat[compare_pat]==1)||(rpat[compare_pat]==7)||(rpat[compare_pat]==5))){ video_putsmalls(88,40,good); R_good++; R_done=3; } } } } d++; } else if (d==10){//LHS:the same as RHS if((SelLevel>=1)&&(L_done==0)){ if ((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=16)){ if ((ypos[compare_pat]>=10)&&(ypos[compare_pat]<=13)){ if ((LHU==1)&&(lpat[compare_pat]==1)){ video_putsmalls(4,40,perfect); L_perfect++; L_done=3; } else if((LHD==1)&&(lpat[compare_pat]==7)){ video_putsmalls(4,40,perfect); L_perfect++; L_done=3; } else if(((LHR==1)||(LHL==1))&&(lpat[compare_pat]==3)){ video_putsmalls(4,40,perfect); L_perfect++; L_done=3; } } else if (((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=9))||((ypos[compare_pat]>=14)&&(ypos[compare_pat]<=16))){ if ((LHU==1)&&(lpat[compare_pat]==1)){ video_putsmalls(4,40,good); L_good++; L_done=3; } else if((LHD==1)&&(lpat[compare_pat]==7)){ video_putsmalls(4,40,good); L_good++; L_done=3; } else if(((LHR==1)||(LHL==1))&&(lpat[compare_pat]==3)){ video_putsmalls(4,40,good); L_good++; L_done=3; } } } } else if((SelLevel==0)&&(L_done==0)){ if ((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=16)){ if ((ypos[compare_pat]>=10)&&(ypos[compare_pat]<=13)){ if (((LHU==1)||(LHR==1)||(LHL==1)||(LHD==1))&&((lpat[compare_pat]==1)||(lpat[compare_pat]==7)||(lpat[compare_pat]==3))){ video_putsmalls(4,40,perfect); L_perfect++; L_done=3; } } else if (((ypos[compare_pat]>=7)&&(ypos[compare_pat]<=9))||((ypos[compare_pat]>=14)&&(ypos[compare_pat]<=16))){ if (((LHU==1)||(LHR==1)||(LHL==1)||(LHD==1))&&((lpat[compare_pat]==1)||(lpat[compare_pat]==7)||(lpat[compare_pat]==3))){ video_putsmalls(4,40,good); L_good++; L_done=3; } } } } d++; } else if (d==11){//when match, highlight the word "perfect!" or "good". use a counter to inverse the regions twice if (L_done>1){ L_done--; video_highlight(3,39,36,45); } if (R_done>1){ R_done--; video_highlight(87,39,120,45); } d++; } else if (d==12){//display statistics sprintf(ts,"%02d",L_perfect ); video_putsmalls(28,68,ts); sprintf(ts,"%02d",R_perfect ); video_putsmalls(112,68,ts); sprintf(ts,"%02d",L_good ); video_putsmalls(28,76,ts); sprintf(ts,"%02d",R_good ); video_putsmalls(112,76,ts); sprintf(ts,"%02d",L_miss ); video_putsmalls(28,84,ts); sprintf(ts,"%02d",R_miss ); video_putsmalls(112,84,ts); d++; } else if (d==13){//when arrows and hand waving direction do not match, diaply miss; else, reset the hit flag if (ypos[compare_pat]<=6){ if ((lpat[compare_pat]!=9)&&(L_done==0)){ video_clear(3,39,36,45); video_putsmalls(4,40,miss); L_miss++; } L_done=0; if ((rpat[compare_pat]!=9)&&(R_done==0)){ video_clear(87,39,120,45); video_putsmalls(88,40,miss); R_miss++; } R_done=0; if (++compare_pat==5) compare_pat=0; } d++; }else if (d==14){//delay for arrow. without this, arrows may go to fast. if (d!=delay) d++; else if (d==delay) d=0; d=0; } } } void clock(void)//display time { if (++time>59){ //count 60 for 1 sec; 60Hz time=0; if (--sec<0){ sec=59; minute--; } sprintf(ts,"%d:%02d",minute,sec ); video_putsmalls(100,93,ts); } } void debounce(void){//debounce switch in mode 0 d_time=DebounceTime; switch (PushState){ case NoPush: if (PINC != 0xff){ PushState=MaybePush; previousKey=PINC; } else PushState=NoPush; break; case MaybePush: if (PINC != 0xff){ if(previousKey != PINC){ PushState=NoPush; // than we go back to the no push state previousKey = PINC; // record the last key that we press break; } else { //get key PushState=Pushed; debounced_flag=1; button=PINC; } } else PushState=NoPush; break; case Pushed: if (PINC != 0xff){ if(previousKey != PINC){ PushState=MaybePush; previousKey = PINC; break; } PushState=Pushed; } else PushState=MaybeNoPush; break; case MaybeNoPush: if (PINC != 0xff){ if(previousKey != PINC){ PushState=MaybePush; previousKey = PINC; break; } PushState=Pushed; } else PushState=NoPush; break; }//end of switch } void initialize(void){ //init timer 1 to generate sync OCR1A = lineTime; //One NTSC line TCCR1B = 9; //full speed; clear-on-match TCCR1A = 0x00; //turn off pwm and oc lines TIMSK = 0x10; //enable interrupt T1 cmp //video gen and sound DDRD = 0xf0; //video out //D.5 is sync:1000 ohm + diode to 75 ohm resistor //D.6 is video:330 ohm + diode to 75 ohm resistor //buttons DDRC=0x00; PORTC=0xff; PushState=NoPush; //initialize synch constants LineCount = 1; syncON = 0b00000000; syncOFF = 0b00100000; //init clock t=0; time=0; //user manu start mode draw screen draw=1; clean=1; mode=0; //init musical scale //B.3 is sound and should have a 10k resistor to gnd TCCR0 = 0b00011100; note = 0; musicT = 0; //use OC0 (pin B.3) for music DDRB.3 = 1 ; audioT=AUDIO_COUNTER; d=0; //Draw screen counter //draw edge video_line(0,0,0,99,1); video_line(127,0,127,99,1); video_line(0,0,126,0,1); video_line(0,99,126,99,1); //animation patflag=0; //arrows that will disappear compare_pat=patflag+1; //arrows needed to be compared with sensors inputs patnum=0; //restart pattern //for handset sensor state_y_R=no_state_y_R; state_z_R=no_state_z_R; state_y_L=no_state_y_L; state_z_L=no_state_z_L; axis_flag=0; LHU=0; LHR=0; LHD=0; LHL=0; RHU=0; RHR=0; RHD=0; RHL=0; //enable sleep mode MCUCR = 0b10000000; #asm ("sei"); }