#include #include #include #include #include #include #include static volatile uint8_t bits = 0; static volatile uint8_t value_in = 0x00; /* Data coming in from Game Boy */ static volatile uint8_t value_out = 0x00; /* Data going out to Game Boy */ static const uint16_t timer_counter_intervals[4] = { 1953, 977, 61, 31 }; static volatile tabby_avr_buffer buffer = { .len = 0, .cur = 0 }; /* * Internal clock source interrupt vector */ ISR(TIM0_COMPB_vect) { value_in >>= 1; if (PORTB & (1 << PORTB2)) { value_in |= 0x80; } if (value_out & 0x80) { PORTB |= (1 << PORTB3); } else { PORTB &= ~(1 << PORTB3); } value_out <<= 1; if (--bits == 0) { uart_putchar(value_in, NULL); if (buffer.cur < buffer.len) { value_out = buffer.data[++buffer.cur]; } else { buffer.len = 0; buffer.cur = 0; } bits = 8; } /* * Strobe the SCK pin */ PORTB |= (1 << PORTB1); PORTB &= ~(1 << PORTB1); } /* * SPI byte receipt interrupt vector */ ISR(SPI_STC_vect) { uart_putchar(SPDR); if (buffer.cur < buffer.len) { SPDR = buffer.data[++buffer.cur]; } else { buffer.len = 0; buffer.cur = 0; } } static void setup_clock_internal(tabby_clock_speed speed) { /* * Configure MISO as output */ DDB3 |= (1 << PORTB3); /* * Configure MOSI as input */ DDB2 &= ~(1 << PORTB2); /* * Configure SCK pin as output */ DDB1 |= (1 << PORTB1); /* * Enable timer interrupt vector */ TIMSK = (1 << TOIE1); /* * Reset timer counter to zero */ TCNT1 = 0; /* * Set timer interval */ OCR1A = timer_counter_intervals[speed]; /* * Set timer clock divider to 1/1 */ TCCR1B = (1 << CS10); } static void setup_clock_external() { /* * Disable internal timer interrupts */ TCCR1B = 0; OCR1A = 0; TIMSK = 0; /* * Configure MISO as output */ DDB3 |= (1 << PORTB3); /* * Configure MOSI as input */ DDB2 &= ~(1 << PORTB2); /* * Set SPI slave mode, and shift in/out most significant bit first */ SPCR &= ~((1 << MSTR) | (1 << DORD)); /* * Enable SPI in Mode 3 with interrupts */ SPCR |= (1 << CPOL) | (1 << CPHA) | (1 << SPIE) | (1 << SPE); /* * Initialize the SPI Data Register and serial buffer */ SPDR = 0; buf.len = 0; buf.cur = 0; } static void snooze() { set_sleep_mode(mode); sleep_enable(); sleep_mode(); sleep_disable(); } int main() { tabby_command state = TABBY_COMMAND_NONE; uint8_t last = 0x00; int received = 0; /* * Best turn on the serial UART! */ uart_init(); /* * By default, the Game Boy link port is configured to monitor for external * clock pulses, so we'll go ahead and do that in this case. */ setup_clock_external(); /* * We do actually want to globally enable interrupts here, so here's that * one assembly instruction to do so. */ sei(); while (1) { uint8_t c = uart_getchar(NULL); received++; switch (state) { case TABBY_COMMAND_NONE: { if (last == 0 && c == TABBY_PACKET_START) { continue; } else if (last == TABBY_PACKET_START) { state = c; } break; } case TABBY_COMMAND_SEND: { if (received == 3 || received == 4) { buffer.len = (buffer.len >> 8) | (c << 8); } else { buffer.data[buffer.cur++] = c; } break; } case TABBY_COMMAND_CLOCK_SOURCE: case TABBY_COMMAND_CLOCK_SPEED: default: goto error_invalid_packet; } last = c; received++; continue; error_invalid_packet: state = TABBY_COMMAND_NONE; last = 0; received = 0; buffer.cur = 0; buffer.len = 0; } return 0; }