#include #include #include #include #include #include #include #include static const uint16_t timer_counter_intervals[4] = { 1953, 977, 61, 31 }; 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 volatile tabby_avr_buffer buffer = { .len = 0, .cur = 0, .read = 0 }; static tabby_clock_source source = TABBY_CLOCK_SOURCE_INTERNAL; static tabby_clock_speed speed = TABBY_CLOCK_SPEED_8192HZ; /* * Internal clock source interrupt vector */ ISR(TIMER0_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.read) { value_out = buffer.data[buffer.cur++]; } else if (buffer.cur == buffer.len) { buffer.len = 0; buffer.cur = 0; buffer.read = 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.read) { SPDR = buffer.data[buffer.cur++]; } else if (buffer.cur == buffer.len) { buffer.len = 0; buffer.cur = 0; buffer.read = 0; } } static void setup_clock_internal(tabby_clock_speed speed) { /* * Configure MISO as output */ DDRB |= (1 << DDB4); /* * Configure MOSI as input */ DDRB &= ~(1 << DDB3); /* * Configure SCK pin as output */ DDRB |= (1 << DDB5); /* * Enable timer interrupt vector */ TIMSK0 = (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; TIMSK0 = 0; /* * Configure MISO as output */ DDRB |= (1 << DDB4); /* * Configure SS as input */ DDRB &= ~(1 << DDB2); /* * 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; buffer.len = 0; buffer.cur = 0; buffer.read = 0; } int main() { tabby_command state = TABBY_COMMAND_NONE; 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); switch (state) { case TABBY_COMMAND_NONE: { state = c; continue; } case TABBY_COMMAND_SEND: { received++; if (received == 1 || received == 2) { buffer.len <<= 8; buffer.len |= c; } else { buffer.data[buffer.read++] = c; if (buffer.read == buffer.len) { state = TABBY_COMMAND_NONE; received = 0; continue; } } break; } case TABBY_COMMAND_CLOCK_SOURCE: { switch (c) { case TABBY_CLOCK_SOURCE_INTERNAL: { source = c; setup_clock_internal(speed); goto reset; } case TABBY_CLOCK_SOURCE_EXTERNAL: { source = c; setup_clock_external(); goto reset; } default: { goto reset; } } } case TABBY_COMMAND_CLOCK_SPEED: { switch (c) { case TABBY_CLOCK_SPEED_8192HZ: case TABBY_CLOCK_SPEED_16384HZ: case TABBY_CLOCK_SPEED_262144HZ: case TABBY_CLOCK_SPEED_524288HZ: { speed = c; if (source == TABBY_CLOCK_SOURCE_INTERNAL) { setup_clock_internal(speed); } goto reset; } default: goto reset; } } default: { goto reset; } } continue; reset: state = TABBY_COMMAND_NONE; received = 0; buffer.len = 0; buffer.cur = 0; buffer.read = 0; } return 0; }