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
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(500000);
|
|
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");
|
|
//}
|
|
|