Arduino gameboy cart reader software, using https://github.com/insidegadgets/GBCartRead
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

263 lines
7.0 KiB

// AVR-libc documentation:
// http://www.nongnu.org/avr-libc/user-manual/modules.html
// AVR assembly instructions:
// https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_instruction_list.html
// ATmega datasheet:
// http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega328_P%20AVR%20MCU%20with%20picoPower%20Technology%20Data%20Sheet%2040001984A.pdf
/*
* FIXME: The current hardware design is questionable.
*
* The leds. Why does the power (MOSFET) led even have a mosfet to begin
* with? It doesn't seem to be doing much.
* Furthermore, why aren't the RD and WR leds inverted? Both of these pins
* on the cart are active low, not high.
*/
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "utils.h"
#include "pins.h"
#include "serial.h"
#include "cart.h"
#include "proto.h"
void proto_send_u32(const uint32_t val)
{
char *cval = (char *)&val;
serial_putchar(cval[0]);
serial_putchar(cval[1]);
serial_putchar(cval[2]);
serial_putchar(cval[3]);
}
uint32_t proto_read_u32(void)
{
uint32_t val;
char *cval = (char *)&val;
cval[0] = serial_getchar();
cval[1] = serial_getchar();
cval[2] = serial_getchar();
cval[3] = serial_getchar();
return val;
}
struct proto_client_packet proto_read_packet(void)
{
struct proto_client_packet packet;
packet.command = serial_getchar();
packet.address = proto_read_u32();
packet.size = proto_read_u32();
return packet;
}
void client_read_raw(uint32_t address, uint32_t size)
{
if (address > (uint32_t)0xFFFF || address + size > (uint32_t)0xFFFF) {
serial_putchar(PROTO_READER_FAIL);
return;
}
serial_putchar(PROTO_READER_OK);
cart_set_read();
if (address & 0x8000) cart_select_ram();
for (unsigned i = address; i < address + size; i++) {
serial_putchar_inline(cart_read(i));
}
cart_select_rom();
}
void client_write_raw(uint32_t address, uint32_t size)
{
if (address > (uint32_t)0xFFFF || address + size > (uint32_t)0xFFFF) {
serial_putchar(PROTO_READER_FAIL);
return;
}
serial_putchar(PROTO_READER_OK);
cart_set_write();
if (address & 0x8000) cart_select_ram();
for (unsigned i = address; i < address + size; i++) {
cart_write(i, serial_getchar_inline());
}
cart_select_rom();
}
void client_read_rom(struct cart_info *cart, uint32_t address, uint32_t size)
{
// 0x1FF = max bank (MBC5, 9 bits)
if (address > (uint32_t)0x4000 * 0x200 ||
address + size > (uint32_t)0x4000 * 0x200) {
serial_putchar(PROTO_READER_FAIL);
return;
}
unsigned bank = address / (uint32_t)0x4000;
unsigned bank_end = (address + size - 1) / (uint32_t)0x4000;
unsigned offset = 0; // Offset for the first bank we read, which can be bank 0.
if (bank) {
cart_rom_bank(cart, bank);
// Any bank other than 0 starts at 0x4000.
offset = 0x4000;
}
// If we're not reading cross-bank, just do one read.
if (bank == bank_end) return client_read_raw(offset + address % 0x4000, size);
serial_putchar(PROTO_READER_OK);
cart_set_read();
// Read the current bank until the end
for (unsigned i = offset + address % 0x4000; i < offset + 0x4000; i++) {
serial_putchar_inline(cart_read(i));
}
// Read all the entire banks
for (unsigned i = bank + 1; i < bank_end; i++) {
cart_rom_bank(cart, i);
cart_set_read();
for (unsigned i = 0x4000; i < (unsigned)0x4000 + 0x4000; i++) {
serial_putchar_inline(cart_read(i));
}
}
// Read the final bank until the end
cart_rom_bank(cart, bank_end);
cart_set_read();
for (unsigned i = 0x4000; i <= 0x4000 + (size - 1) % 0x4000; i++) {
serial_putchar_inline(cart_read(i));
}
}
void client_read_ram(struct cart_info *cart, uint32_t address, uint32_t size)
{
// 0xF = max bank (MBC5, 4 bits)
if (address > (uint32_t)0x2000 * 0x10 ||
address + size > (uint32_t)0x2000 * 0x10) {
serial_putchar(PROTO_READER_FAIL);
return;
}
unsigned bank = address / (uint32_t)0x2000;
unsigned bank_end = (address + size - 1) / (uint32_t)0x2000;
cart_ram_enable(cart);
cart_ram_bank(cart, bank);
// If we're not reading cross-bank, just do one read.
if (bank == bank_end) return client_read_raw(0xA000 + address % 0x2000, size);
serial_putchar(PROTO_READER_OK);
cart_set_read();
// Read the current bank until the end
for (unsigned i = 0xA000 + address % 0x2000; i < 0xA000 + 0x2000; i++) {
serial_putchar_inline(cart_read(i));
}
// Read all the entire banks
for (unsigned i = bank + 1; i < bank_end; i++) {
cart_ram_bank(cart, i);
cart_set_read();
for (unsigned i = 0xA000; i < 0xA000 + 0x2000; i++) {
serial_putchar_inline(cart_read(i));
}
}
// Read the final bank until the end
cart_ram_bank(cart, bank_end);
cart_set_read();
for (unsigned i = 0xA000; i <= 0xA000 + (size - 1) % 0x2000; i++) {
serial_putchar_inline(cart_read(i));
}
cart_ram_disable(cart);
}
int main(void)
{
struct cart_info cart;
// Configure reset button interrupt
//PCICR = 0;
//PCMSK1 = _BV(PCINT9);
// Initialize the program
serial_init(2000000);
pinmode(PIN_MOSFET, OUTPUT);
writepin(PIN_MOSFET, LOW);
cart_init();
sei();
int checksum = cart_boot(&cart);
if (checksum) {
// Notify the user that the cart couldn't be read properly.
writepin(PIN_READ, LOW);
writepin(PIN_WRITE, LOW);
}
sbi(PCICR, PCIE1);
while (serial_getchar() != PROTO_CLIENT_INIT);
cbi(PCICR, PCIE1);
serial_putchar(checksum ? PROTO_READER_FAIL : PROTO_READER_OK);
// Interpret commands
for (;;) {
serial_drain();
cart_set_standby();
sbi(PCICR, PCIE1);
struct proto_client_packet packet = proto_read_packet();
cbi(PCICR, PCIE1);
switch (packet.command) {
case PROTO_CLIENT_READ_RAW:
client_read_raw(packet.address, packet.size);
break;
case PROTO_CLIENT_READ_ROM:
client_read_rom(&cart, packet.address, packet.size);
break;
case PROTO_CLIENT_READ_RAM:
client_read_ram(&cart, packet.address, packet.size);
break;
case PROTO_CLIENT_WRITE_RAW:
client_write_raw(packet.address, packet.size);
break;
case PROTO_CLIENT_WRITE_ROM:
case PROTO_CLIENT_WRITE_RAM:
default:
serial_putchar(PROTO_READER_FAIL);
}
}
}
void exit(__attribute__((unused)) int status)
{
serial_drain();
//sbi(PCICR, PCIE1);
cart_off();
for (;;);
}
//ISR(PCINT1_vect) {
//// Debounce it
//int pressed = readpin(PIN_SWITCH);
//int debounce = 0x2DAB;
//while (debounce--) {
//if (pressed != readpin(PIN_SWITCH)) return;
//}
//// Check if it was released
//if (pressed) return;
//_delay_ms(10);
//__asm__ volatile ("jmp 0");
//}