//Main file for morse code tutor //Authors: Jeff Fung + Yen-Khai Lee //NetID: wf23 + yl245 //ADC feeds in amplified output of laser reception //D.5 is sync:1000 ohm + diode to 75 ohm resistor //D.6 is video:330 ohm + diode to 75 ohm resistor #pragma optsize- //optimize for speed #include #include #include #include #include #include #include // video code header file #include // morse code tree header file //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 begin { #define end } #define ScreenTop 30 #define ScreenBot 230 #define display 0 // morse code character FSM definitions #define Idle 0 #define DitDah 1 // read dit/dah #define WaitChar 2 // wait for x ms and transmit to EndChar once time up #define EndChar 3 // send dit/dah combination and move to idle/wait #define Error 4 // ditdah FSM definitions #define Start 0 #define fingerOn 1 #define fingerOff 2 // graphics FSM definitons #define Menu 0 #define Sketch 1 #define Game 2 #define Tutorial 3 #define FTitle 4 #define NTitle 5 #define ditdah_interval 20 // in multiples of frame time (1/60 sec) #define char_interval 30 // time between dit/dah, in multiples of frame #define error_interval 20 // error beeping time #define dit_tone_length 1 // off by 1 #define dah_tone_length 3 // off by 1 #define dit_fingers 1 // number of fingers passing through the laser in a ditdah_interval to signal a dit #define dah_fingers 2 // number of fingers passing through the laser in a ditdah_interval to signal a dah #define pos_incr 4 #define threshold 183 // The threshold value of the ADC conversion when the laser is obstructed char syncON, syncOFF; int LineCount; #pragma regalloc+ unsigned int finger_count= 0; unsigned int ditdah_frames = 0; unsigned int ditdah_interval_frames = 0; unsigned int error_frames = 0; int morse_pos = 2; unsigned int screen_char_xpos = 4; unsigned char screen_char_ypos = 4; MNODE *curr_letter = NULL; // global pointer to the current letter being signaled in the morse code tree //animation char title[] = "LASER:MORSE:TUTOR"; char cont[] = "WAVE:TO:CONTINUE"; char entry; char clear_str[] = {':','\0'}; unsigned int rand_counter =0; unsigned int c_inter_speed = 10; int tone_length = -1; int game_time_frames = 0; // Game state variables char new_char[] = {'A','\0'}; unsigned int morse_fsm_state, ditdah_fsm_state, graphics_fsm_state; // Function prototypes void ditdah_fsm(void); int finger_check(void); void drawIntervalBar(void); void drawDahBar(void); void display_char(MNODE *); //This is the ADC interrupt interrupt [ADC_INT] void adc(void) { } //================================== //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 ADC ADCSRA = ADCSRA | 0x40; //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 //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(2); //tradeoff between centering image and allowing for more processing time //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+ /* **************************************** * FSM for morse code character entry * Clock tick is NTSC frame (or 1/60 of a second) **************************************** */ void morse_fsm(void) begin switch(morse_fsm_state) { case Idle: // idle state is the steady state when the laser is not being obstructed { curr_letter = &root_node; ditdah_fsm_state = Start; TCCR0 = 0; if (finger_check() == 1) { // bottom edge of finger passes through laser morse_fsm_state = DitDah; // indicate that a hand has been waved - 1sttime ditdah_frames = 0; finger_count = 0; video_putg2(2, 91, 1, 1, 0); video_putg2(6, 91, 1, 1, 0); video_putg2(10, 91, 1, 1, 0); video_putg2(14, 91, 1, 1, 0); video_putg2(18, 91, 1, 1, 0); morse_pos = 2; } break; } // end Idle state case DitDah: // the fsm enters the ditdah state when a finger passes through the laser { // start clock for the ditdah_fsm ditdah_frames++; if (ditdah_frames < ditdah_interval) { // call ditdah_fsm to determine if the signal is a dit or dah ditdah_fsm(); drawDahBar(); } else { if (tone_length == -1) { // determine if it was a dit or dah if (finger_count == dit_fingers) { // dit curr_letter = curr_letter->dit; //set tone length for dit tone_length = dit_tone_length; } else if (finger_count >= dah_fingers ) { curr_letter = curr_letter->dah; // set tone length for dah tone_length = dah_tone_length; } else { // error curr_letter = NULL; tone_length = 0; } } else if (tone_length > 0) { // play a tone tone_length--; OCR0 = 80; TCCR0 = 0b00011100; } else { ditdah_fsm_state = Start; ditdah_frames = 0; ditdah_interval_frames = 0; TCCR0 = 0; tone_length = -1; // error display if (curr_letter == NULL) { morse_fsm_state = Error; display_char(&root_node); } else { // if an end-of-word was signaled, jump to the EndChar state if (curr_letter == &nodeEOW) { morse_fsm_state = EndChar; } else { // else jump to the WaitChar to wait for another signal morse_fsm_state = WaitChar; display_char(curr_letter); } } } } break; } // end DitDah state case WaitChar: // the fsm enters the WaitChar state in between dit and dahs { // start wait time ditdah_interval_frames++; if (ditdah_interval_frames < char_interval) { if (finger_check() == 1) { morse_fsm_state = DitDah; morse_pos += pos_incr; ditdah_frames = 0; ditdah_fsm_state = Start; finger_count = 0; } drawIntervalBar(); } else { // at the end of the wait interval, go to EndChar indicating end of signals for a character morse_fsm_state = EndChar; ditdah_interval_frames = 0; } break; } // end WaitChar state case EndChar: { // display the character if (curr_letter != NULL && curr_letter != &root_node && curr_letter != &dah_nodeO09 && curr_letter != &dit_nodeO8 && curr_letter != &dah_nodeU2 ) { //display letter on the screen display_char(curr_letter); entry = curr_letter->ch[0]; OCR0 = 250; TCCR0 = 0b00011100; morse_fsm_state = Idle; } else { // if the character is not valid, go to error state morse_fsm_state = Error; display_char(&root_node); } break; } // end EndChar state case Error: // Error states indicates some error, such as an invalid morse code sequence of the incorrect letter { OCR0 = 239; // error tone error_frames++; if (error_frames < error_interval) { TCCR0 = 0b00011100; // output sound } else { TCCR0 = 0; error_frames = 0; morse_fsm_state = Idle; video_putg2(2, 91, 1, 1, 0); video_putg2(6, 91, 1, 1, 0); video_putg2(10, 91, 1, 1, 0); video_putg2(14, 91, 1, 1, 0); video_putg2(18, 91, 1, 1, 0); morse_pos = 2; } break; } // end Error state } // end switch end // ditdah recognition FSM (incorporate into morse character FSM) // tick at 1 frame void ditdah_fsm(void) begin switch(ditdah_fsm_state) { case Start: { finger_count++; // indicates the number of fingers if (finger_check() == 1) { // if the finger is still passing through the laser ditdah_fsm_state = fingerOn; } else { // if the finger has passed through the laser ditdah_fsm_state = fingerOff; } break; } case fingerOn: { if (finger_check() == 0) { // determine if the finger has passed across the laser ditdah_fsm_state = fingerOff; } break; } case fingerOff: { if (finger_check() == 1) { ditdah_fsm_state = Start; } break; } } end // returns 1 if finger is on laser; else return 0 int finger_check(void) begin if (ADCH > threshold) { PORTC.0 = 0; // turn on LED return 1; } else { PORTC.0 = 1; // turn off LED return 0; } end // displays character from FSM from // current pointer to morse code tree void display_char(MNODE *curr_char) begin video_putsmalls(44,91,curr_char->ch); if (finger_count == 1) { video_putg2(morse_pos, 91, 1, 1, 3); } else { video_putg2(morse_pos, 91, 1, 1, 4); } end void displayClock(void) begin char time1str[10]; itoa((ditdah_frames-ditdah_interval)%ditdah_interval,time1str); video_putsmalls(16,60,time1str); end void drawIntervalBar(void) begin video_hbar(98, 85, 124, 85, 0, 3); // draw new bar video_hbar(98,85,98+(char_interval*26-ditdah_interval_frames*26)/(char_interval),85,1,3); end void drawDahBar(void) begin // clear old bar video_hbar(98,93,124,93,0,3); // draw new bar video_hbar(98,93,98+(ditdah_interval*26-ditdah_frames*26)/(ditdah_interval),93,1,3); end //================================== // set up the ports and timers void main(void) begin // used for game char rand_num = 0; // used to capture ADCH for the current frame int bool_new_char = 1; // should you drop a new character? char y = 4; unsigned int c_inter = 0; int num_correct = 0; int explode_y_pos = -1; char score_str[10]; int temp = 0; 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 //init ports DDRD = 0xf0; //video out and switches DDRA = 0x00; //ADC input DDRC = 0xff; //LED testing DDRB.3 = 1; //beep output PORTC = 0xff; //initial LEDs //D.5 is sync:1000 ohm + diode to 75 ohm resistor //D.6 is video:330 ohm + diode to 75 ohm resistor //initialize synch constants LineCount = 1; syncON = 0b00000000; syncOFF = 0b00100000; //side lines video_line(0,0,0,99,1); video_line(126,0,126,99,1); //top line & bottom lines video_line(0,0,126,0,1); video_line(0,99,126,99,1); //construct morse code tree nodetree(); //enable interrupts #asm ("sei"); //base title screen video_putsmalls(16,16,title); video_putsmalls(16,32,cont); //init ADC ADMUX = 0b11100000; MCUCR = 0b10000000; ADCSRA = 0b10001110; // initialize ADC initially //reset all variables morse_fsm_state = Idle; ditdah_fsm_state = Start; // tone OCR0 = 80; // separate title screen from main while(1) loop - once // past the code never returns to this point while(finger_check() != 1) { #asm ("sleep"); if (LineCount == 231) { drawTitleScreen(); } } // keep waiting until it is safe for erasing $screen$ // i.e. away from vertical sync while(LineCount != 50) { #asm ("sleep"); } clear(); graphics_fsm_state = Menu; drawDisplayBar(); // main graphics state machine while(1){ #asm ("sleep"); // vertical sync - display characters if (LineCount == 231) { morse_fsm(); rand_counter = (unsigned int)rand(); switch(graphics_fsm_state) { case Menu: // display main menu { drawMenu(); if (entry == 'E' || entry == 'T') { clearWOBar(); graphics_fsm_state = Tutorial; } else if (entry == '1') { clearWOBar(); graphics_fsm_state = Sketch; } else if (entry == '2') { clearWOBar(); num_correct=0; graphics_fsm_state = Game; game_time_frames = 0; y = 4; // reset the altitude of falling letter for the next game num_correct = 0; c_inter_speed = 10; } break; } case Sketch: // free typing mode, allows user to practice their morse code on a blank screen { if (morse_fsm_state == EndChar && curr_letter == &nodeEOW) { clearWOBar(); graphics_fsm_state = Menu; } else { // output the current letter onto the screen video_putsmalls(screen_char_xpos,screen_char_ypos,curr_letter->ch); if (morse_fsm_state == EndChar) { screen_char_xpos += 4; if (screen_char_xpos > 120) { screen_char_xpos = 4; screen_char_ypos += 8; } } } break; } case Game: { if (curr_letter == &nodeEOW) { // exit is signaled, display the speed statistics before exiting out clearWOBar(); screen_char_xpos = 370; } else { if (screen_char_xpos < 180) { drawGameT1(); // draw title 1 screen_char_xpos++; } else if (screen_char_xpos < 360 && screen_char_xpos >= 180){ drawGameT2(); // draw title 2 screen_char_xpos++; } else if (screen_char_xpos == 360) { clearWOBar1(); // clear top half of screen screen_char_xpos++; } else if (screen_char_xpos == 361) { clearWOBar2(); // clear second half of screen screen_char_xpos++; } else if (screen_char_xpos == 362) { drawCity1(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 363) { drawCity2(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 364) { drawCity3(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 365) { drawCity4(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 366) { drawCity5(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 367) { drawCity6(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 368) { drawCity7(); // draw city screen_char_xpos++; } else if (screen_char_xpos == 369) { drawCity8(); // draw city screen_char_xpos = 600; } else if (screen_char_xpos == 370) { if (game_time_frames/3600 > 0) { temp = num_correct/(game_time_frames/3600); } else { temp = 0; } sprintf(score_str, "CPM:%d",temp); // characters per minute video_putsmalls(32,16,score_str); if (game_time_frames/3600 > 0) { temp = (num_correct/5)/(game_time_frames/3600); } else { temp = 0; } sprintf(score_str, "WPM:%d",temp); // words per minute video_putsmalls(32,32,score_str); sprintf(score_str,"WAVE:HAND"); video_putsmalls(32,48,score_str); screen_char_xpos++; } else if (screen_char_xpos == 371) { if (morse_fsm_state == EndChar && (curr_letter->ch[0] == 'E' || curr_letter->ch[0] == 'T')) { clearWOBar(); screen_char_xpos++; morse_fsm_state = Idle; } } else if (screen_char_xpos == 372) { drawDisplayBar(); screen_char_xpos=4; graphics_fsm_state = Menu; num_correct = 0; c_inter_speed = 10; y = 4; } else { // start game game_time_frames++; // start game timer sprintf(score_str,"SCORE:"); video_putsmalls(32,83,score_str); sprintf(score_str,"%d",num_correct); video_putsmalls(44,91,score_str); if (temp > 0) { temp--; } else { sprintf(score_str, ":::::::"); video_putsmalls(36,16,score_str); } if (bool_new_char == 1) { if (explode_y_pos > 0) { drawClearBlock(rand_num,explode_y_pos); explode_y_pos = -1; } rand_num = (char)(rand_counter %36); // range from 0 -35 representing numbers A-Z, 0-9 if (rand_num < 0) { rand_num = (rand_num + 36); } if (rand_num < 26) { // display letter new_char[0] = (rand_num+65); // display a character [starts at ascii 65] } else { // display number new_char[0] = rand_num + 22; // display a number [starts at ascii 48] } rand_num = (char)(rand_counter % 116); // range from 0-119epresenting x coord of initial drop pos rand_num = rand_num - (rand_num%4)+4; // display the character on the screen video_putsmalls(rand_num,y,new_char); bool_new_char = 0; } else { // update position as the letter drops //video_putsmalls(rand_num,y,clear_str); if (y < 74) { if (morse_fsm_state == EndChar) { //if the correct character was signalled if (curr_letter->ch[0] == new_char[0]) { bool_new_char =1; video_putsmalls(rand_num,y,clear_str); explode_y_pos = y; drawExplosion(rand_num,y); y = 4; num_correct++; // increase speed every time 5 words are correctly signalled if (num_correct %5 == 0 && num_correct != 0) { c_inter_speed--; temp = 10-c_inter_speed; sprintf(score_str, "LEVEL:%d",temp); video_putsmalls(36,16,score_str); temp =90; } else { } } else { //signal error morse_fsm_state = Error; } } else { video_putsmalls(rand_num,y,clear_str); c_inter = c_inter +1; if (c_inter > c_inter_speed*2) { c_inter= 0; y++; } video_putsmalls(rand_num,y, new_char); } } else { // when the letter reaches the bottom (read map) bool_new_char =1; video_putsmalls(rand_num,y,clear_str); drawExplosion(rand_num,y-1); explode_y_pos = y-1; y = 4; //rand_counter = (rand_counter>>2) ^ TCNT1; video_putsmalls(rand_num+1,y,clear_str); video_putsmalls(rand_num-1,y,clear_str); video_putsmalls(rand_num,y+1,clear_str); video_putsmalls(rand_num,y-1,clear_str); } } } } break; } case Tutorial: { if (curr_letter == &nodeEOW) { clearWOBar(); screen_char_xpos = 4; graphics_fsm_state = Menu; } else { if (screen_char_xpos == 4) { drawTutScreens(1); // draw title 1 screen_char_xpos++; } else if (screen_char_xpos > 4 && screen_char_xpos <180) { screen_char_xpos++; } else if (screen_char_xpos == 180) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 181) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos == 182) { drawTutScreens(2); // draw instructions screen_char_xpos++; } else if (screen_char_xpos > 182 && screen_char_xpos < 480) { screen_char_xpos++; } else if (screen_char_xpos == 480) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 481) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos == 482) { drawTutScreens(3); // draw dit screen_char_xpos = 600; ; } else if (screen_char_xpos == 600) { if (morse_fsm_state == EndChar && curr_letter->ch[0] == 'E') { screen_char_xpos++; } } else if (screen_char_xpos == 601) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 602) { clearWOBar2(); screen_char_xpos++; }else if (screen_char_xpos == 603) { drawTutScreens(4); // draw T screen_char_xpos = 640; } else if (screen_char_xpos == 640) { if (morse_fsm_state == EndChar && curr_letter->ch[0] == 'T') { screen_char_xpos++; } } else if (screen_char_xpos == 641) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 642) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos == 643) { drawTutScreens(5); screen_char_xpos = 800; } else if (screen_char_xpos == 800) { if (morse_fsm_state == EndChar && curr_letter->ch[0] == '0') { screen_char_xpos++; } } else if (screen_char_xpos == 801) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 802) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos == 803) { drawTutScreens(6); screen_char_xpos = 960; } else if (screen_char_xpos == 960) { if (morse_fsm_state == EndChar && curr_letter->ch[0] == 'X') { screen_char_xpos++; } } else if (screen_char_xpos == 961) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 962) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos == 963) { drawTutScreens(7); screen_char_xpos = 964; } else if (screen_char_xpos == 964) { if (morse_fsm_state == EndChar && curr_letter->ch[0] == '3') { screen_char_xpos++; } } else if (screen_char_xpos == 965) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 966) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos == 967) { drawTutScreens(8); screen_char_xpos++; } else if (screen_char_xpos > 967 && screen_char_xpos < 1280) { screen_char_xpos++; } else if (screen_char_xpos == 1280) { clearWOBar1(); screen_char_xpos++; } else if (screen_char_xpos == 1281) { clearWOBar2(); screen_char_xpos++; } else if (screen_char_xpos > 1281 && screen_char_xpos < 1308) { drawTutLetters(screen_char_xpos-1282); if (morse_fsm_state == EndChar && curr_letter->ch[0] == screen_char_xpos-1282+65) { screen_char_xpos++; clearWOBar(); } else { // morse_fsm_state = Error; } } else if (screen_char_xpos >= 1308 && screen_char_xpos < 1318) { drawTutLetters(screen_char_xpos-1282); if (morse_fsm_state == EndChar && curr_letter->ch[0] == screen_char_xpos-1308+48) { screen_char_xpos++; clearWOBar(); } else { // morse_fsm_state = Error; } } else if (screen_char_xpos == 1318) { drawTutScreens(100); screen_char_xpos++; } else { } } break; } } } } end