/********************************************* Wall of Pong May 2, 2007 Author: Adrian Wong, Bhavin Rokad Class: ECE 476, Cornell University **********************************************/ #include // ATmega32 #include // Standard I/O #include // Strings #include // Delay // Timing Variables bit tick_1ms; unsigned char tick_30ms; unsigned int tick_1s; // Stepper Motor Variables char half_step[8] = {0b1010, 0b1000, 0b1001, 0b0001, 0b0101, 0b0100, 0b0110, 0b0010}; char full_step[8] = {0b1000, 0b0001, 0b0100, 0b0010, 0b1000, 0b0001, 0b0100, 0b0010}; char x_step; char y_step; #define NONE 0 #define UP 1 #define DOWN 2 #define LEFT 1 #define RIGHT 2 char x_dir; char y_dir; unsigned char motor_speed = 200; // Motor speed // Coordinate System Variables signed int x_cord; // X axis step counter signed int y_cord; // Y axis step counter signed int left_border; // X coordinate of left border signed int right_border; // X coordinate of right border signed int top_border; // Y coordinate of top border signed int bottom_border; // Y coordinate of bottom border signed int x_center; // X coordinate of center signed int y_center; // Y coordinate of center // Calibration Variables unsigned char calibrate_state; // Calibration state // Pong Paddle Variables unsigned char sensor_number; // Top, middle, and bottom unsigned char ADC_current; // Current LDR value #define THRESHOLD 100 // USART Buffers unsigned char rx_char; // Current received character unsigned char rx_index; // Index into rx_buffer unsigned char rx_buffer[16]; // Buffer of received characters bit rx_ready; // Completion of received string unsigned char tx_char; // Current transmit character unsigned char tx_index; // Index into tx_buffer unsigned char tx_buffer[256]; // Buffer of characters to transmit bit tx_ready; // Completion of transmitted string // Interrupt Timer 1, Compare Match A - 1 ms interrupts interrupt [TIM1_COMPA] void TIMER1_COMPA(void) { tick_1ms = 1; } // Interrupt: Timer 2, Compare Match - Stepper motor control interrupt [TIM2_COMP] void TIMER2_COMP (void) { switch (x_dir) { case LEFT: x_step = (x_step + 1) % 8; // Move to next highest step. --x_cord; // Move x-coordinate toward the left. break; case RIGHT: if (x_step == 0) // If at last step, x_step = 7; // Wrap to the highest step. else --x_step; // Move to next lowest step. ++x_cord; // Move x-coordinate toward the right. break; case NONE: break; } switch (y_dir) { case DOWN: y_step = (y_step + 1) % 8; // Move to next highest step. --y_cord; // Move y-coordinate toward the bottom. break; case UP: if (y_step == 0) // If at last step, y_step = 7; // Wrap to the highest step. else --y_step; // Move to next lowest step. ++y_cord; // Move y-coordinate toward the top. break; case NONE: break; } // Combine x-axis and y-axis stepper motor commands at PORTC. PORTC = (half_step[y_step] << 4) | half_step[x_step]; } // Interrupt for USART, Receive Complete interrupt [USART_RXC] void USART_RX (void) { rx_char = UDR; // Get character from USART RX buffer. UDR = rx_char; // Echo character to USART TX buffer. if (rx_char != '\r') // If Enter key has not been pressed, { rx_buffer[rx_index] = rx_char; // Store character into software RX buffer. rx_index++; // Increment RX buffer index. } else // If Enter key has been pressed, { putchar('\n'); // Print new line at the terminal. rx_buffer[rx_index] = 0; // Terminate the string. rx_ready = 1; // Ready to be serviced by UI. UCSRB.7 = 0; // Disable RXC interrupt. } } // Interrupt for USART, (TX) Data Register Empty interrupt [USART_DRE] void USART_TX (void) { tx_char = tx_buffer[tx_index]; // Get character from software TX buffer. tx_index++; // Increment TX buffer index. if (tx_char == 0) // If string has terminated, { UCSRB.5 = 0; // Disable DRE interurpt. tx_ready = 1; // Transmission complete. } else { UDR = tx_char; // Transmit character. } } // Start receiving on the USART void start_rx(void) { rx_ready = 0; // RX input not ready. rx_index = 0; // Reset RX buffer index. UCSRB.7 = 1; // Enable RXC interrupt. } // Start transmitting on the USART void start_tx(void) { tx_ready = 0; // Transmission not complete. tx_index = 0; // Reset TX buffer index. UCSRB.5 = 1; // Enable DRE interrupt. } // Remotely operate the gimbal void remote_control(void) { unsigned char command; sscanf(rx_buffer, "%c", &command); // Process input string into characters. switch(command) // Standard WSAD movement keys { case 'w': y_dir = UP; strcpyf(tx_buffer, "COMMAND UP\r\n"); break; case 's': y_dir = DOWN; strcpyf(tx_buffer, "COMMAND DOWN\r\n"); break; case 'a': x_dir = LEFT; strcpyf(tx_buffer, "COMMAND LEFT\r\n"); break; case 'd': x_dir = RIGHT; strcpyf(tx_buffer, "COMMAND RIGHT\r\n"); break; case 'c': x_dir = NONE; y_dir = NONE; strcpyf(tx_buffer, "COMMAND CLEAR\r\n"); break; } start_tx(); // Transmit data. } // Initalization routine void initialize (void) { /*** IO Port Initialization ***/ // Port A Pin Assignments // Analog to Digital Converter Inputs // --- PADDLE SENSOR WIRE // PA0 Left Top White // PA1 Left Middle Black // PA2 Left Bottom Red // PA3 // PA4 Right Top White // PA5 Right Middle Green // PA6 Right Bottom Red DDRA = 0x00; PORTA = 0x00; // Port B Pin Assignments // PB2 AIN0 (Analog Comparator +) // PB3 AIN1 (Analog Comparator -) DDRB = 0x00; // Port C Pin Assignments // Stepper Motor Outputs // --- AXIS MOTOR LEAD MCU LEAD // PC0 X Brown Yellow // PC1 X Red Orange // PC2 X Orange Black // PC3 X White Brown // PC4 Y Brown Yellow // PC5 Y Red Orange // PC6 Y Orange Black // PC7 Y White Brown DDRC = 0xFF; PORTC = 0xAA; // Port D Pin Assignments // Assorted Features // PD0 RXD (Serial Communications - Debug Mode) // PD1 TXD (Serial Communications - Debug Mode) // PD2 INT0 (External Interrupt 0 - Unused) // PD3 INT1 (External Interrupt 1 - Fan speed monitor) // PD4 // PD5 // PD6 Laser // PD7 LED DDRD = 0xFF; PORTD = 0xFF; PORTD.6 = 0; // Laser on /*** Analog to Digital Converter Initialization ***/ ADMUX = (0 << 7) | (1 << 6) // Set voltage reference to AVcc. | (1 << 5) // Left adjust result. | (0 << 4); // Mux to ADC0. ADCSRA = (1 << 7) | (1 << 6) // ADC enable, start conversion. | (1 << 2) | (1 << 1) | (1 << 0); // Set ADC prescaler to 125kHz (clkIO/128). /*** Timer 1 Initialization ***/ // Timer 1 will generate interrupts every 1 millisecond. TCCR1A = 0; // Set mode of operation to CTC, OCR1A. TCCR1B = (1 << 3) // Set mode of operation to CTC, OCR1A. | (0 << 2) | (1 << 1) | (1 << 0); // Set prescaler to 250 kHz (clkIO/64). OCR1AH = 0; // Set compare register A to trigger at 1 ms. OCR1AL = 249; // Set compare register A to trigger at 1 ms. TIMSK |= (1 << 4); // Enable Compare A Match interrupt. /*** Timer 2 Initalization ***/ // Timer 2 will generate the stepper motor outputs. TCCR2 = (1 << 3) | (0 << 6) // Set mode of operation to CTC, OCR2. | (0 << 5) | (0 << 4) // Disconnect OC2. | (1 << 2) | (1 << 1) | (1 << 0); // Set prescaler to 15.625 kHz (clk/1024). TCNT2 = 0; // Start stepper timerat zero phase. OCR2 = motor_speed; // This sets the speed of stepper motor. TIMSK |= (1 << 7); // Enable Timer 2 Compare Match Interrupt. /*** USART Initalization ***/ UCSRB = (1 << 7) | (1 << 5) // Enable interrupts for RXE and DRE. | (1 << 4) | (1 << 3); // Enable RX and TX. UBRRL = 103; // Set to 9600 baud. /*** Application Initalization ***/ tick_1ms = 0; // Start 1 ms tick at 0. tick_30ms = 0; // Start 30 ms tick at 0. tick_1s = 0; // Start 1 s tick at 0. x_step = 0; // Start stepper at phase 0. y_step = 0; // Start stepper at phase 0. x_dir = NONE; // Do not move motor in x-axis. y_dir = NONE; // Do not move motor in y-axis. x_cord = 0; // Assume laser starts at center. y_cord = 0; // Assume laser starts at center. x_center= 0; // Assume center is at center. y_center= 0; // Assume center is at center. sensor_number = 0; // Start with top sensor of paddle. #asm sei // Enable interrupts. #endasm } // Sample paddle sensors void sample_paddles(void) { if (x_dir == LEFT) { switch (sensor_number) // Switch through sensors. { case 0: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (0 << 0); // Mux to ADC0. sensor_number = 1; break; case 1: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 0); // Mux to ADC1. sensor_number = 2; break; case 2: default: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 1) | (0 << 0); // Mux to ADC2. sensor_number = 0; break; } } else if (x_dir == RIGHT) { switch (sensor_number) // Switch through sensors. { case 0: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 2); // Mux to ADC4. sensor_number = 1; break; case 1: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 2) | (1 << 0); // Mux to ADC5 sensor_number = 2; break; case 2: default: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 2) | (1 << 1); // Mux to ADC6 sensor_number = 0; break; } } else if (x_dir == NONE) { switch (sensor_number) // Switch through sensors. { case 0: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (0 << 0); // Mux to ADC0. sensor_number = 1; break; case 1: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 0); // Mux to ADC1. sensor_number = 2; break; case 2: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 1) | (0 << 0); // Mux to ADC2. sensor_number = 3; break; case 3: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 2); // Mux to ADC4. sensor_number = 4; break; case 4: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 2) | (1 << 0); // Mux to ADC5 sensor_number = 5; break; case 5: ADMUX = (0 << 7) | (1 << 6) // AVCC Voltage Reference | (1 << 5) // Left adjust result | (1 << 2) | (1 << 1); // Mux to ADC6 sensor_number = 0; break; } } ADCSR.6 = 1; // Start next conversion. } // Center the pong ball void center_dot(void) { bit not_xcenter = 1; bit not_ycenter = 1; PORTD.6 = 1; // Laser off. while (not_xcenter | not_ycenter) { if (x_cord > x_center) // If right of center, x_dir = LEFT; // Move left. else if (x_cord < x_center) // If left of center, x_dir = RIGHT; // Move right. else // Otherwise, { x_dir = NONE; // Do not move. not_xcenter = 0; // Centered in the x-axis. } if (y_cord > y_center) // If above center, y_dir = DOWN; // Move down. else if (y_cord < y_center) // If below center, y_dir = UP; // Move up. else // Otherwise, { y_dir = NONE; // Do not move. not_ycenter = 0; // Centered in the y-axis. } } PORTD.6 = 0; // Laser on. delay_ms(1000); // Pause for one second. } // Calibrate Playing Field void calibrate (void) { calibrate_state = 0; // Start in uncalibrated state. while (calibrate_state != 5) { if (ADCSR.6 == 0) // Sampling completed. { ADC_current = ADCH; // Store current ADC value. if (ADC_current < THRESHOLD) // Paddle hit! { switch (calibrate_state) { case 0: // Waiting for either paddle to trigger. x_dir = LEFT; // Move to left boundary. calibrate_state = 1; break; case 1: // Left paddle triggered. left_border = x_cord; // Store left boundary. x_dir = RIGHT; // Move to right boundary. calibrate_state = 2; break; case 2: // Right paddle triggered. right_border = x_cord; // Store right boundary. x_center = (left_border + right_border) / 2; center_dot(); // Return to center. y_dir = UP; // Move to top of field. calibrate_state = 3; break; case 3: // Left paddle triggered (top). top_border = y_cord; // Store top boundary. center_dot(); // Return to center. y_dir = DOWN; // Move to bottom boundary. calibrate_state = 4; break; case 4: // Right paddle triggered (bottom). bottom_border = y_cord; // Store bottom boundary. y_center = (top_border + bottom_border) / 2; center_dot(); // Return to center. calibrate_state = 5; break; case 5: // Calibration completed. break; } } sample_paddles(); // Switch to next paddle sensor. } } } // Start the game! void start_game(void) { // TODO: Randomize the direction. x_dir = LEFT; y_dir = UP; } // Test for boundary collisions void boundary_test(void) { if (x_cord < left_border) { // TODO: Left player lost. center_dot(); // Return to center. start_game(); // Start playing the game! } if (x_cord > right_border) { // TODO: Right player lost. center_dot(); // Return to center. start_game(); // Start playing the game! } if (y_cord < bottom_border) { y_dir = UP; // Reflect ball off bottom border. } if (y_cord > top_border) { y_dir = DOWN; // Reflect ball off top border. } } // Debug function to verify paddle sensor operation void adc_readout(void) { if (ADCSR.6 == 0) // Sampling completed. { ADC_current = ADCH; // Store current ADC value. sample_paddles(); // Switch to next paddle sensor. while (tx_ready == 0) {} sprintf(tx_buffer,"Paddle %u\tSensor %u\tValue %u\n\r",x_dir,sensor_number,ADC_current); start_tx(); } } // Main Execution Loop void main (void) { initialize(); // Initialize the microcontroller. calibrate(); // Calibrate playing field. start_game(); // Start playing the game! while(1) { // Start main control loop. if (ADCSR.6 == 0) // Sampling completed. { ADC_current = ADCH; // Store current ADC value. if (ADC_current < THRESHOLD) // Paddle hit! { if (x_dir == RIGHT) x_dir = LEFT; // Reverse x-axis direction. else if (x_dir == LEFT) x_dir = RIGHT; // Reverse x-axis direction. } sample_paddles(); // Switch to next paddle sensor. } boundary_test(); // Test for boundary collision. if (rx_ready) // If remote UI has input pending, { remote_control(); // Update remote UI (Hyperterm). start_rx(); // Start RX again. } } }