From bbe37d9839e83395ddf692740c3412109398bdbe Mon Sep 17 00:00:00 2001 From: LuigiBlood Date: Fri, 9 Feb 2024 22:06:44 +0100 Subject: [PATCH] SNES: Add Game Processor RAM Cassette Read/Write support --- Cart_Reader/Cart_Reader.ino | 6 + Cart_Reader/Config.h | 7 + Cart_Reader/GPC.ino | 421 ++++++++++++++++++++++++++++++++++++ Cart_Reader/SNES.ino | 32 ++- 4 files changed, 455 insertions(+), 11 deletions(-) create mode 100644 Cart_Reader/GPC.ino diff --git a/Cart_Reader/Cart_Reader.ino b/Cart_Reader/Cart_Reader.ino index 6a7cd90..11b4940 100644 --- a/Cart_Reader/Cart_Reader.ino +++ b/Cart_Reader/Cart_Reader.ino @@ -258,6 +258,7 @@ void print_STR(byte string_number, boolean newline) { #define mode_7800 38 #define mode_VECTREX 39 #define mode_ST 40 +#define mode_GPC 41 // optimization-safe nop delay #define NOP __asm__ __volatile__("nop\n\t") @@ -3598,6 +3599,11 @@ void loop() { stMenu(); } #endif +#ifdef enable_GPC + else if (mode == mode_GPC) { + gpcMenu(); + } +#endif #ifdef enable_NES else if (mode == mode_NES) { nesMenu(); diff --git a/Cart_Reader/Config.h b/Cart_Reader/Config.h index 31ac524..68080a9 100644 --- a/Cart_Reader/Config.h +++ b/Cart_Reader/Config.h @@ -230,6 +230,13 @@ /****/ +/* [ Super Famicom Game Processor RAM Cassette -------------------- ] +*/ + +//#define enable_GPC + +/****/ + /* [ Super Nintendo ----------------------------------------------- ] */ diff --git a/Cart_Reader/GPC.ino b/Cart_Reader/GPC.ino new file mode 100644 index 0000000..5f7d560 --- /dev/null +++ b/Cart_Reader/GPC.ino @@ -0,0 +1,421 @@ +//****************************************** +// SNES Game Processor RAM Cassette code by LuigiBlood +// Revision 1.0.0 February 2024 +//****************************************** +#ifdef enable_GPC + +/****************************************** + Game Processor RAM Cassette +******************************************/ +/****************************************** + Prototype Declarations + *****************************************/ +/* Hoping that sanni will use this progressbar function */ +extern void draw_progressbar(uint32_t processedsize, uint32_t totalsize); + +//void gpcMenu(); +void readRAM_GPC(); +//void setup_GPC(); +void writeRAM_GPC(void); + +/****************************************** + Variables + *****************************************/ +//No global variables + +/****************************************** + Menu +*****************************************/ +// GPC flash menu items +static const char gpcFlashMenuItem1[] PROGMEM = "Read RAM"; +static const char gpcFlashMenuItem2[] PROGMEM = "Write RAM"; +static const char gpcFlashMenuItem3[] PROGMEM = "Back"; +static const char* const menuOptionsGPCFlash[] PROGMEM = { gpcFlashMenuItem1, gpcFlashMenuItem2, gpcFlashMenuItem3 }; + + +void gpcMenu() { + // create menu with title and 3 options to choose from + unsigned char mainMenu; + // Copy menuOptions out of progmem + convertPgm(menuOptionsGPCFlash, 3); + mainMenu = question_box(F("Game Processor RAM"), menuOptions, 3, 0); + + // wait for user choice to come back from the question box menu + switch (mainMenu) { + // Read ram + case 0: + // Change working dir to root + sd.chdir("/"); + readRAM_GPC(); + break; + + // Write ram + case 1: + // Change working dir to root + sd.chdir("/"); + writeRAM_GPC(); + unsigned long wrErrors; + wrErrors = verifyRAM_GPC(); + if (wrErrors == 0) { + println_Msg(F("Verified OK")); + display_Update(); + } else { + print_STR(error_STR, 0); + print_Msg(wrErrors); + print_STR(_bytes_STR, 1); + print_Error(did_not_verify_STR); + } + wait(); + break; + + // Reset + case 2: + resetArduino(); + break; + } +} + +/****************************************** + Setup + *****************************************/ +void setup_GPC() { + // Request 5V + setVoltage(VOLTS_SET_5V); + + // Set cicrstPin(PG1) to Output + DDRG |= (1 << 1); + // Output a high signal until we're ready to start + PORTG |= (1 << 1); + // Set cichstPin(PG0) to Input + DDRG &= ~(1 << 0); + + // Adafruit Clock Generator + i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); + if (i2c_found) { + clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLA); + clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLB); + clockgen.set_freq(2147727200ULL, SI5351_CLK0); + clockgen.set_freq(307200000ULL, SI5351_CLK2); + clockgen.output_enable(SI5351_CLK0, 1); + clockgen.output_enable(SI5351_CLK1, 0); + clockgen.output_enable(SI5351_CLK2, 1); + } +#ifdef clockgen_installed + else { + display_Clear(); + print_FatalError(F("Clock Generator not found")); + } +#endif + + // Set Address Pins to Output + //A0-A7 + DDRF = 0xFF; + //A8-A15 + DDRK = 0xFF; + //BA0-BA7 + DDRL = 0xFF; + //PA0-PA7 + DDRA = 0xFF; + + // Set Control Pins to Output RST(PH0) CS(PH3) WR(PH5) RD(PH6) + DDRH |= (1 << 0) | (1 << 3) | (1 << 5) | (1 << 6); + // Switch RST(PH0) and WR(PH5) to HIGH + PORTH |= (1 << 0) | (1 << 5); + // Switch CS(PH3) and RD(PH6) to LOW + PORTH &= ~((1 << 3) | (1 << 6)); + + // Set Refresh(PE5) to Output + DDRE |= (1 << 5); + // Switch Refresh(PE5) to LOW (needed for SA-1) + PORTE &= ~(1 << 5); + + // Set CPU Clock(PH1) to Output + DDRH |= (1 << 1); + //PORTH &= ~(1 << 1); + + // Set IRQ(PH4) to Input + DDRH &= ~(1 << 4); + // Activate Internal Pullup Resistors + //PORTH |= (1 << 4); + + // Set expand(PG5) to output + DDRG |= (1 << 5); + // Output High + PORTG |= (1 << 5); + + // Set Data Pins (D0-D7) to Input + DDRC = 0x00; + // Enable Internal Pullups + //PORTC = 0xFF; + + // Unused pins + // Set wram(PE4) to Output + DDRE |= (1 << 4); + //PORTE &= ~(1 << 4); + // Set pawr(PJ1) to Output + DDRJ |= (1 << 1); + //PORTJ &= ~(1 << 1); + // Set pard(PJ0) to Output + DDRJ |= (1 << 0); + //PORTJ &= ~(1 << 0); + + // Start CIC by outputting a low signal to cicrstPin(PG1) + PORTG &= ~(1 << 1); + + // Wait for CIC reset + delay(1000); +} + +/****************************************** + Low level functions + *****************************************/ +// Write one byte of data to a location specified by bank and address, 00:0000 +void writeBank_GPC(byte myBank, word myAddress, byte myData) { + PORTL = myBank; + PORTF = myAddress & 0xFF; + PORTK = (myAddress >> 8) & 0xFF; + PORTC = myData; + + // Arduino running at 16Mhz -> one nop = 62.5ns + // Wait till output is stable + __asm__("nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t"); + + // Switch WR(PH5) to LOW + PORTH &= ~(1 << 5); + + // Leave WR low for at least 60ns + __asm__("nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t"); + + // Switch WR(PH5) to HIGH + PORTH |= (1 << 5); + + // Leave WR high for at least 50ns + __asm__("nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t"); +} + +// Read one byte of data from a location specified by bank and address, 00:0000 +byte readBank_GPC(byte myBank, word myAddress) { + PORTL = myBank; + PORTF = myAddress & 0xFF; + PORTK = (myAddress >> 8) & 0xFF; + + // Arduino running at 16Mhz -> one nop = 62.5ns -> 1000ns total + __asm__("nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t"); + + // Read + byte tempByte = PINC; + return tempByte; +} + +/****************************************** + Game Processor RAM Cassette functions + *****************************************/ +// Read RAM cassette to SD card +void readRAM_GPC() { + // Set control + dataIn(); + controlIn_SNES(); + + // Get name, add extension and convert to char array for sd lib + strcpy(fileName, "GPC4M.sfc"); + + // create a new folder for the save file + EEPROM_readAnything(0, foldern); + sprintf(folder, "SNES/ROM/%s/%d", "GPC4M", foldern); + sd.mkdir(folder, true); + sd.chdir(folder); + + //clear the screen + display_Clear(); + print_STR(saving_to_STR, 0); + print_Msg(folder); + println_Msg(F("/...")); + display_Update(); + + // write new folder number back to eeprom + foldern = foldern + 1; + EEPROM_writeAnything(0, foldern); + + //open file on sd card + if (!myFile.open(fileName, O_RDWR | O_CREAT)) { + print_FatalError(create_file_STR); + } + + // Read Banks + for (int currBank = 0xC0; currBank < 0xC8; currBank++) { + // Dump the bytes to SD 512B at a time + for (long currByte = 0; currByte < 65536; currByte += 512) { + draw_progressbar((currBank - 0xC0) * 0x10000 + currByte, 0x80000); + for (int c = 0; c < 512; c++) { + sdBuffer[c] = readBank_GPC(currBank, currByte + c); + } + myFile.write(sdBuffer, 512); + } + } + draw_progressbar(0x80000, 0x80000); //Finish drawing progress bar + + // Close the file: + myFile.close(); + println_Msg(F("Read ram completed")); + display_Update(); + wait(); +} + +void writeRAM_GPC(void) { + //Display file Browser and wait user to select a file. Size must be 512KB. + filePath[0] = '\0'; + sd.chdir("/"); + fileBrowser(F("Select SFC file")); + // Create filepath + sprintf(filePath, "%s/%s", filePath, fileName); + display_Clear(); + + //open file on sd card + if (myFile.open(filePath, O_READ)) { + + fileSize = myFile.fileSize(); + if (fileSize != 0x80000) { + println_Msg(F("File must be 512KB")); + display_Update(); + myFile.close(); + wait(); + return; + } + + //Disable ram cassette write protection + dataOut(); + controlOut_SNES(); + for (int countProtect = 0; countProtect < 15; countProtect++) { + writeBank_GPC(0x20, 0x6000, 0x00); + } + + //Write ram + dataOut(); + controlOut_SNES(); + println_Msg(F("Writing ram...")); + display_Update(); + for (int currBank = 0xC0; currBank < 0xC8; currBank++) { + //startAddr = 0x0000 + for (long currByte = 0x0000; currByte < 0x10000; currByte += 512) { + myFile.read(sdBuffer, 512); + for (unsigned long c = 0; c < 512; c++) { + //startBank = 0x10; CS low + writeBank_GPC(currBank, currByte + c, sdBuffer[c]); + } + } + draw_progressbar(((currBank - 0xC0) * 0x10000), 0x80000); + } + + //reenable write protection + dataIn(); + controlIn_SNES(); + byte keepByte = readBank_GPC(0x20, 0x6000); + delay(100); + dataOut(); + controlOut_SNES(); + writeBank_GPC(0x20, 0x6000, keepByte); + + draw_progressbar(0x80000, 0x80000); + delay(100); + // Set pins to input + dataIn(); + + // Close the file: + myFile.close(); + println_Msg(""); + println_Msg(F("RAM writing finished")); + display_Update(); + } else { + print_Error(F("File doesnt exist")); + } +} + +// Check if the RAM was written without any error +unsigned long verifyRAM_GPC() { + //open file on sd card + if (myFile.open(filePath, O_READ)) { + // Variable for errors + writeErrors = 0; + + // Set control + controlIn_SNES(); + + //startBank = 0xC0; endBank = 0xC7; CS low + for (byte currBank = 0xC0; currBank < 0xC8; currBank++) { + //startAddr = 0x0000 + for (long currByte = 0x0000; currByte < 0x10000; currByte += 512) { + //fill sdBuffer + myFile.read(sdBuffer, 512); + for (unsigned long c = 0; c < 512; c++) { + if ((readBank_GPC(currBank, currByte + c)) != sdBuffer[c]) { + writeErrors++; + } + } + } + } + // Close the file: + myFile.close(); + return writeErrors; + } else { + print_Error(F("Can't open file")); + return 1; + } +} + +#endif + +//****************************************** +// End of File +//****************************************** \ No newline at end of file diff --git a/Cart_Reader/SNES.ino b/Cart_Reader/SNES.ino index 5e69126..d0cf457 100644 --- a/Cart_Reader/SNES.ino +++ b/Cart_Reader/SNES.ino @@ -33,14 +33,15 @@ static const char snsMenuItem1[] PROGMEM = "SNES/SFC cartridge"; static const char snsMenuItem2[] PROGMEM = "SF Memory Cassette"; static const char snsMenuItem3[] PROGMEM = "Satellaview BS-X"; static const char snsMenuItem4[] PROGMEM = "Sufami Turbo"; -static const char snsMenuItem5[] PROGMEM = "Flash repro"; +static const char snsMenuItem5[] PROGMEM = "Game Processor RAM"; +static const char snsMenuItem6[] PROGMEM = "Flash repro"; #ifdef clockgen_calibration -static const char snsMenuItem6[] PROGMEM = "Calibrate Clock"; -//static const char snsMenuItem7[] PROGMEM = "Reset"; (stored in common strings array) -static const char* const menuOptionsSNS[] PROGMEM = { snsMenuItem1, snsMenuItem2, snsMenuItem3, snsMenuItem4, snsMenuItem5, snsMenuItem6, string_reset2 }; +static const char snsMenuItem7[] PROGMEM = "Calibrate Clock"; +//static const char snsMenuItem8[] PROGMEM = "Reset"; (stored in common strings array) +static const char* const menuOptionsSNS[] PROGMEM = { snsMenuItem1, snsMenuItem2, snsMenuItem3, snsMenuItem4, snsMenuItem5, snsMenuItem6, snsMenuItem7, string_reset2 }; #else //static const char snsMenuItem6[] PROGMEM = "Reset"; (stored in common strings array) -static const char* const menuOptionsSNS[] PROGMEM = { snsMenuItem1, snsMenuItem2, snsMenuItem3, snsMenuItem4, snsMenuItem5, string_reset2 }; +static const char* const menuOptionsSNS[] PROGMEM = { snsMenuItem1, snsMenuItem2, snsMenuItem3, snsMenuItem4, snsMenuItem5, snsMenuItem6, string_reset2 }; #endif // SNES menu items @@ -137,11 +138,11 @@ void snsMenu() { unsigned char snsCart; // Copy menuOptions out of progmem #ifdef clockgen_calibration + convertPgm(menuOptionsSNS, 8); + snsCart = question_box(F("Select Cart Type"), menuOptions, 8, 0); +#else convertPgm(menuOptionsSNS, 7); snsCart = question_box(F("Select Cart Type"), menuOptions, 7, 0); -#else - convertPgm(menuOptionsSNS, 6); - snsCart = question_box(F("Select Cart Type"), menuOptions, 6, 0); #endif // wait for user choice to come back from the question box menu @@ -180,19 +181,28 @@ void snsMenu() { break; #endif -#ifdef enable_FLASH +#ifdef enable_GPC case 4: + display_Clear(); + display_Update(); + setup_GPC(); + mode = mode_GPC; + break; +#endif + +#ifdef enable_FLASH + case 5: setup_FlashVoltage(); reproMenu(); break; #endif - case 5: + case 6: #ifdef clockgen_calibration clkcal(); break; - case 6: + case 7: #endif resetArduino(); break;