diff --git a/Cart_Reader/Cart_Reader.ino b/Cart_Reader/Cart_Reader.ino index 5e5b985..9233968 100644 --- a/Cart_Reader/Cart_Reader.ino +++ b/Cart_Reader/Cart_Reader.ino @@ -4,8 +4,8 @@ This project represents a community-driven effort to provide an easy to build and easy to modify cartridge dumper. - Date: 19.08.2022 - Version: 9.5 + Date: 21.08.2022 + Version: 9.6 SD lib: https://github.com/greiman/SdFat OLED lib: https://github.com/adafruit/Adafruit_SSD1306 @@ -25,7 +25,7 @@ MichlK - ROM Reader for Super Nintendo Jeff Saltzman - 4-Way Button Wayne and Layne - Video Game Shield menu - skaman - Cart ROM READER SNES ENHANCED, Famicom Cart Dumper, Coleco- and Intellivision modules + skaman - Cart ROM READER SNES ENHANCED, Famicom Cart Dumper, Coleco-, Intellivision, Virtual Boy, WSV modules Tamanegi_taro - PCE and Satellaview modules splash5 - GBSmart, Wonderswan and NGP modules hkz & themanbehindthecurtain - N64 flashram commands @@ -60,7 +60,7 @@ **********************************************************************************/ -char ver[5] = "9.5"; +char ver[5] = "9.6"; //****************************************** // !!! CHOOSE HARDWARE VERSION !!! @@ -97,6 +97,7 @@ char ver[5] = "9.5"; // #define enable_INTV // #define enable_COLV // #define enable_VBOY +// #define enable_WSV //****************************************** // HW CONFIGS @@ -305,6 +306,7 @@ bool i2c_found; #define mode_INTV 23 #define mode_COL 24 #define mode_VBOY 25 +#define mode_WSV 26 // optimization-safe nop delay #define NOP __asm__ __volatile__ ("nop\n\t") @@ -683,7 +685,8 @@ boolean compareCRC(char* database, char* crcString, boolean renamerom, int offse } } else { - println_Msg(F(" -> database file not found")); + println_Msg(F(" -> Error")); + println_Msg(F("Database missing")); return 0; } #else @@ -807,19 +810,20 @@ static const char modeItem9[] PROGMEM = "NeoGeo Pocket"; static const char modeItem10[] PROGMEM = "Intellvision"; static const char modeItem11[] PROGMEM = "Colecovision"; static const char modeItem12[] PROGMEM = "Virtual Boy"; -static const char modeItem13[] PROGMEM = "Flashrom Programmer"; -static const char modeItem14[] PROGMEM = "About"; -static const char* const modeOptions[] PROGMEM = {modeItem1, modeItem2, modeItem3, modeItem4, modeItem5, modeItem6, modeItem7, modeItem8, modeItem9, modeItem10, modeItem11, modeItem12, modeItem13, modeItem14}; +static const char modeItem13[] PROGMEM = "Watara Supervision"; +static const char modeItem14[] PROGMEM = "Flashrom Programmer"; +static const char modeItem15[] PROGMEM = "About"; +static const char* const modeOptions[] PROGMEM = {modeItem1, modeItem2, modeItem3, modeItem4, modeItem5, modeItem6, modeItem7, modeItem8, modeItem9, modeItem10, modeItem11, modeItem12, modeItem13, modeItem14, modeItem15}; // All included slots void mainMenu() { - // create menu with title and 13 options to choose from + // create menu with title and 15 options to choose from unsigned char modeMenu; // Main menu spans across two pages currPage = 1; lastPage = 1; - numPages = 2; + numPages = 3; while (1) { if (currPage == 1) { @@ -832,6 +836,11 @@ void mainMenu() { convertPgm(modeOptions, 7, 7); modeMenu = question_box(F("OPEN SOURCE CART READER"), menuOptions, 7, 0); } + if (currPage == 3) { + // Copy menuOptions out of progmem + convertPgm(modeOptions, 14, 1); + modeMenu = question_box(F("OPEN SOURCE CART READER"), menuOptions, 1, 0); + } if (numPages == 0) { // Execute choice modeMenu = (currPage - 1) * 7 + modeMenu; @@ -941,8 +950,15 @@ void mainMenu() { break; #endif -#ifdef enable_FLASH +#ifdef enable_WSV case 12: + setup_WSV(); + wsvMenu(); + break; +#endif + +#ifdef enable_FLASH + case 13: #ifdef enable_FLASH16 flashMenu(); #else @@ -951,7 +967,7 @@ void mainMenu() { break; #endif - case 13: + case 14: aboutScreen(); break; @@ -997,8 +1013,9 @@ static const char* const consolesOptions[] PROGMEM = {consolesItem1, consolesIte static const char handheldsItem1[] PROGMEM = "Virtual Boy"; static const char handheldsItem2[] PROGMEM = "WonderSwan"; static const char handheldsItem3[] PROGMEM = "NeoGeo Pocket"; -static const char handheldsItem4[] PROGMEM = "Reset"; -static const char* const handheldsOptions[] PROGMEM = {handheldsItem1, handheldsItem2, handheldsItem3, handheldsItem4}; +static const char handheldsItem4[] PROGMEM = "Watara Supervision"; +static const char handheldsItem5[] PROGMEM = "Reset"; +static const char* const handheldsOptions[] PROGMEM = {handheldsItem1, handheldsItem2, handheldsItem3, handheldsItem4, handheldsItem5}; // All included slots void mainMenu() { @@ -1160,21 +1177,19 @@ void consoleMenu() { // Everything that needs an adapter void handheldMenu() { - // create menu with title and 4 options to choose from + // create menu with title and 5 options to choose from unsigned char handheldsMenu; // Copy menuOptions out of progmem - convertPgm(handheldsOptions, 4); - handheldsMenu = question_box(F("Choose Adapter"), menuOptions, 4, 0); + convertPgm(handheldsOptions, 5); + handheldsMenu = question_box(F("Choose Adapter"), menuOptions, 5, 0); // wait for user choice to come back from the question box menu switch (handheldsMenu) { #ifdef enable_VBOY case 0: - mode = mode_VBOY; - display_Clear(); - display_Update(); setup_VBOY(); + vboyMenu(); break; #endif @@ -1196,7 +1211,14 @@ void handheldMenu() { break; #endif +#ifdef enable_WSV case 3: + setup_WSV(); + wsvMenu(); + break; +#endif + + case 4: resetArduino(); break; @@ -3372,6 +3394,11 @@ void loop() { else if (mode == mode_VBOY) { vboyMenu(); } +#endif +#ifdef enable_WSV + else if (mode == mode_WSV) { + wsvMenu(); + } #endif else { display_Clear(); diff --git a/Cart_Reader/VBOY.ino b/Cart_Reader/VBOY.ino new file mode 100644 index 0000000..d532ef5 --- /dev/null +++ b/Cart_Reader/VBOY.ino @@ -0,0 +1,534 @@ +//****************************************** +// VIRTUALBOY MODULE +//****************************************** +#ifdef enable_VBOY +// Nintendo VirtualBoy +// Cartridge Pinout +// 60P 2.00mm pitch connector +// +// TOP SIDE BOTTOM SIDE +// +-------+ +// GND -| 1 2 |- GND +// /WE0 (SRAM WRITE ENABLE) -| 3 4 |- nc +// nc -| 5 6 |- /CS1 (SRAM ENABLE) +// CS2 (SRAM) -| 7 8 |- VCC(+5V) +// nc -| 9 10 |- A23 +// A19 -| 11 12 |- A22 +// A18 -| 13 14 |- A21 +// A8 -| 15 16 |- A20 +// A7 -| 17 18 |- A9 +// A6 -| 19 20 |- A10 +// A5 -| 21 22 |- A11 +// A4 -| 23 24 |- A12 +// A3 -| 25 26 |- A13 +// A2 -| 27 28 |- A14 +// A1 -| 29 30 |- A15 +// /CE (ROM) -| 31 32 |- A16 +// GND -| 33 34 |- A17 +// /OE -| 35 36 |- VCC(+5V) +// D8 -| 37 38 |- D7 +// D0 -| 39 40 |- D15 +// D9 -| 41 42 |- D6 +// D1 -| 43 44 |- D14 +// D10 -| 45 46 |- D5 +// D2 -| 47 48 |- D13 +// D11 -| 49 50 |- D4 +// D3 -| 51 52 |- D12 +// VCC(+5V) -| 53 54 |- VCC(+5V) +// nc -| 55 56 |- nc +// nc -| 57 58 |- nc +// GND -| 59 60 |- GND +// +-------+ +// + +// CONTROL PINS: +// CS2(SRAM) - (PH0) - VBOY PIN 7 - SNES RST +// /CE(ROM) - (PH3) - VBOY PIN 31 - SNES /CS +// /CS1(SRAM ENABLE) - (PH4) - VBOY PIN 6 - SNES /IRQ +// /WE0(SRAM WRITE ENABLE) - (PH5) - VBOY PIN 3 - SNES /WR +// /OE - (PH6) - VBOY PIN 35 - SNES /RD + +// NOT CONNECTED: +// CLK(PH1) - N/C + +//****************************************** +// SETUP +//****************************************** + +void setup_VBOY() +{ + // Set Address Pins to Output + //A0-A7 + DDRF = 0xFF; + //A8-A15 + DDRK = 0xFF; + //A16-A23 + DDRL = 0xFF; + + // Set Control Pins to Output + // CS2(PH0) ---(PH1) /CE(PH3) /CS1(PH4) /WE0(PH5) /OE(PH6) + DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6); + + // Set TIME(PJ0) to Output (UNUSED) + DDRJ |= (1 << 0); + + // Set Pins (D0-D15) to Input + DDRC = 0x00; + DDRA = 0x00; + + // Setting Control Pins to HIGH + // ---(PH1) /CE(PH3) /CS1(PH4) /WE0(PH5) /OE(PH6) + PORTH |= (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6); + // Set CS2(PH0) to LOW + PORTH &= ~(1 << 0); + + // Set Unused Pins HIGH + PORTJ |= (1 << 0); // TIME(PJ0) + + getCartInfo_VB(); + + mode = mode_VBOY; +} + +//****************************************** +// MENU +//****************************************** + +// Base Menu +static const char vboyMenuItem1[] PROGMEM = "Read ROM"; +static const char vboyMenuItem2[] PROGMEM = "Read SRAM"; +static const char vboyMenuItem3[] PROGMEM = "Write SRAM"; +static const char vboyMenuItem4[] PROGMEM = "Reset"; +static const char* const menuOptionsVBOY[] PROGMEM = {vboyMenuItem1, vboyMenuItem2, vboyMenuItem3, vboyMenuItem4}; + +void vboyMenu() +{ + convertPgm(menuOptionsVBOY, 4); + uint8_t mainMenu = question_box(F("VIRTUALBOY MENU"), menuOptions, 4, 0); + + switch (mainMenu) + { + case 0: + // Read ROM + sd.chdir("/"); + readROM_VB(); + sd.chdir("/"); + break; + + case 1: + // Read SRAM + if (sramSize) { + sd.chdir("/"); + display_Clear(); + println_Msg(F("Reading SRAM...")); + display_Update(); + readSRAM_VB(); + sd.chdir("/"); + } + else { + print_Error(F("Cart has no SRAM"), false); + } +#if (defined(enable_OLED) || defined(enable_LCD)) + // Wait for user input + println_Msg(F("Press Button...")); + display_Update(); + wait(); +#endif + break; + + case 2: + // Write SRAM + if (sramSize) { + // Change working dir to root + sd.chdir("/"); + fileBrowser(F("Select SRM file")); + display_Clear(); + writeSRAM_VB(); + writeErrors = verifySRAM_VB(); + if (writeErrors == 0) { + println_Msg(F("SRAM verified OK")); + display_Update(); + } + else { + print_Msg(F("Error: ")); + print_Msg(writeErrors); + println_Msg(F(" bytes ")); + print_Error(F("did not verify."), false); + } + } + else { + print_Error(F("Cart has no SRAM"), false); + } +#if (defined(enable_OLED) || defined(enable_LCD)) + // Wait for user input + println_Msg(F("Press Button...")); + display_Update(); + wait(); +#endif + break; + + case 3: + // reset + resetArduino(); + break; + } +} + +//****************************************** +// LOW LEVEL FUNCTIONS +//****************************************** + +void writeByte_VB(unsigned long myAddress, byte myData) { + PORTF = myAddress & 0xFF; + PORTK = (myAddress >> 8) & 0xFF; + PORTL = (myAddress >> 16) & 0xFF; + + __asm__("nop\n\t"); + + PORTA = myData; + + // Set CS2(PH0), /CE(PH3), /OE(PH6) to HIGH + PORTH |= (1 << 0) | (1 << 3) | (1 << 6); + // Set /CS1(PH4), /WE0(PH5) to LOW + PORTH &= ~(1 << 4) & ~(1 << 5); + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); + + // Set CS2(PH0), /CS1(PH4), /WE0(PH5) to HIGH + PORTH |= (1 << 0) | (1 << 4) | (1 << 5); + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); +} + +word readWord_VB(unsigned long myAddress) { + PORTF = myAddress & 0xFF; + PORTK = (myAddress >> 8) & 0xFF; + PORTL = (myAddress >> 16) & 0xFF; + + __asm__("nop\n\t"); + + // Set CS2(PH0), /CS1(PH4), /WE0(PH5) to HIGH + PORTH |= (1 << 0) | (1 << 4) | (1 << 5); + // Set /CE(PH3), /OE(PH6) to LOW + PORTH &= ~(1 << 3) & ~(1 << 6); + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); + + word tempWord = ( ( PINA & 0xFF ) << 8 ) | ( PINC & 0xFF ); + + // Set /CE(PH3), /OE(PH6) to HIGH + PORTH |= (1 << 3) | (1 << 6); + // Setting CS2(PH0) LOW + PORTH &= ~(1 << 0); + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); + + return tempWord; +} + +byte readByte_VB(unsigned long myAddress) { // SRAM BYTE + PORTF = myAddress & 0xFF; + PORTK = (myAddress >> 8) & 0xFF; + PORTL = (myAddress >> 16) & 0xFF; + + __asm__("nop\n\t"); + + // Set CS2(PH0), /CE(PH3), /WE0(PH5) to HIGH + PORTH |= (1 << 0) | (1 << 3) | (1 << 5); + // Set /CS1(PH4), /OE(PH6) to LOW + PORTH &= ~(1 << 4) & ~(1 << 6); + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); + + byte tempByte = PINA; + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); + + // Set /CS1(PH4), /OE(PH6) to HIGH + PORTH |= (1 << 3) | (1 << 6); + // Setting CS2(PH0) LOW + PORTH &= ~(1 << 0); + + __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); + + return tempByte; +} + +// Switch data pins to write +void dataOut_VB() { + DDRC = 0xFF; + DDRA = 0xFF; +} + +// Switch data pins to read +void dataIn_VB() { + DDRC = 0x00; + DDRA = 0x00; +} + +//****************************************** +// CART INFO +//****************************************** + +void getCartInfo_VB() { + // Set control + dataIn_VB(); + + cartSize = 0; + for (unsigned long address = 0x80000; address <= 0x400000; address *= 2) { + // Get Serial + word vbSerial = readWord_VB((address - 0x204) / 2); // Cart Serial + + switch (vbSerial) + { + case 0x4D54: // MT = Mario's Tennis + case 0x4832: // H2 = Panic Bomber/Tobidase! Panibomb + case 0x5350: // SP = Space Invaders + case 0x5353: // SS = Space Squash + case 0x5452: // TR = V-Tetris + cartSize = 0x80000; // 512KB + break; + + case 0x494D: // IM = Insmouse no Yakata + case 0x4A42: // JB = Jack Bros. + case 0x4D43: // MC = Mario Clash + case 0x5245: // RE = Red Alarm + case 0x4833: // H3 = Vertical Force + case 0x5642: // VB = Virtual Bowling + case 0x5646: // VF = Virtual Fishing + case 0x4A56: // JV = Virtual Lab + case 0x5650: // VP = Virtual League Baseball/Virtual Pro Yakyuu '95 + cartSize = 0x100000; // 1MB + break; + + case 0x5042: // PB = 3-D Tetris + case 0x4750: // GP = Galactic Pinball + case 0x5344: // SD = SD Gundam Dimension War + case 0x5442: // TB = Teleroboxer + cartSize = 0x100000; // 1MB + sramSize = 0x2000; // 8KB + break; + + case 0x5647: // VG = Golf/T&E Virtual Golf + case 0x4E46: // NF = Nester's Funky Bowling + case 0x5745: // WE = Waterworld + cartSize = 0x200000; // 2MB + break; + + case 0x5743: // WC = Virtual Boy Wario Land + cartSize = 0x200000; // 2MB + sramSize = 0x2000; // 8KB + break; + + case 0x4644: // FD = Hyper Fighting + cartSize = 0x400000; // 4MB + sramSize = 0x2000; // 8KB + break; + } + + if (cartSize) + break; + } + + // Get name + for (byte c = 0; c < 20; c += 2) { + // split word + word myWord = readWord_VB((cartSize - 0x220 + c) / 2); + byte loByte = myWord & 0xFF; + byte hiByte = myWord >> 8; + + // write to buffer + sdBuffer[c] = hiByte; + sdBuffer[c + 1] = loByte; + } + byte myLength = 0; + for (unsigned int i = 0; i < 20; i++) { + if (((char(sdBuffer[i]) >= 48 && char(sdBuffer[i]) <= 57) || (char(sdBuffer[i]) >= 65 && char(sdBuffer[i]) <= 122)) && myLength < 15) { + romName[myLength] = char(sdBuffer[i]); + myLength++; + } + } + + display_Clear(); + println_Msg(F("Cart Info")); + println_Msg(F(" ")); + print_Msg(F("Name: ")); + println_Msg(romName); + print_Msg(F("Size: ")); + print_Msg(cartSize * 8 / 1024 / 1024 ); + println_Msg(F(" MBit")); + print_Msg(F("Sram: ")); + if (sramSize > 0) { + print_Msg(sramSize * 8 / 1024); + println_Msg(F(" KBit")); + } + else + println_Msg(F("None")); + println_Msg(F(" ")); + +#if (defined(enable_OLED) || defined(enable_LCD)) + // Wait for user input + println_Msg(F("Press Button...")); + display_Update(); + wait(); +#endif +} + +//****************************************** +// READ CODE +//****************************************** + +void readROM_VB() { + dataIn_VB(); + + strcpy(fileName, romName); + strcat(fileName, ".vb"); + + EEPROM_readAnything(10, foldern); + sprintf(folder, "VBOY/ROM/%s/%d", romName, foldern); + sd.mkdir(folder, true); + sd.chdir(folder); + + display_Clear(); + print_Msg(F("Saving to ")); + print_Msg(folder); + println_Msg(F("/...")); + display_Update(); + + foldern = foldern + 1; + EEPROM_writeAnything(10, foldern); + + if (!myFile.open(fileName, O_RDWR | O_CREAT)) { + print_Error(F("SD Error"), true); + } + + word d = 0; + // HYPER FIGHTING FIX + // VIRTUAL BOY ADDRESSING IS TOP DOWN + // ONLY FOR HYPER FIGHTING PLUGIN WITH ALL ADDRESS LINES CONNECTED + // NORMAL PLUGIN DOES NOT HAVE THE UPPER ADDRESS LINES CONNECTED + if (cartSize == 0x400000) { + unsigned long romData = 0x1000000 - (cartSize / 2); + for (unsigned long currBuffer = romData; currBuffer < 0x1000000; currBuffer += 256) { + for (int currWord = 0; currWord < 256; currWord++) { + word myWord = readWord_VB(currBuffer + currWord); + // Split word into two bytes + sdBuffer[d] = (( myWord >> 8 ) & 0xFF); + sdBuffer[d + 1] = (myWord & 0xFF); + d += 2; + } + myFile.write(sdBuffer, 512); + d = 0; + } + } + else { + for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 256) { + for (int currWord = 0; currWord < 256; currWord++) { + word myWord = readWord_VB(currBuffer + currWord); + // Split word into two bytes + sdBuffer[d] = (( myWord >> 8 ) & 0xFF); + sdBuffer[d + 1] = (myWord & 0xFF); + d += 2; + } + myFile.write(sdBuffer, 512); + d = 0; + } + } + myFile.close(); + + // Compare CRC32 to database and rename ROM if found + // Arguments: database name, precalculated crc string or 0 to calculate, rename rom or not, starting offset + compareCRC("vb.txt", 0, 1, 0); + +#if (defined(enable_OLED) || defined(enable_LCD)) + // Wait for user input + println_Msg(F("")); + println_Msg(F("Press Button...")); + display_Update(); + wait(); +#endif +} + +//****************************************** +// SRAM +//****************************************** + +void writeSRAM_VB() { + dataOut_VB(); + + sprintf(filePath, "%s/%s", filePath, fileName); + println_Msg(F("Writing...")); + println_Msg(filePath); + display_Update(); + + if (myFile.open(filePath, O_READ)) { + for (unsigned long currByte = 0; currByte < sramSize; currByte++) { + // writeWord_VB(currByte, ((myFile.read() << 8 ) & 0xFF)); + writeByte_VB(currByte, (myFile.read())); + } + myFile.close(); + println_Msg(F("Done")); + display_Update(); + } + else { + print_Error(F("SD Error"), true); + } + dataIn_VB(); +} + +void readSRAM_VB() { + dataIn_VB(); + + strcpy(fileName, romName); + strcat(fileName, ".srm"); + + EEPROM_readAnything(10, foldern); + sprintf(folder, "VBOY/SAVE/%s/%d", romName, foldern); + sd.mkdir(folder, true); + sd.chdir(folder); + + foldern = foldern + 1; + EEPROM_writeAnything(10, foldern); + + if (!myFile.open(fileName, O_RDWR | O_CREAT)) { + print_Error(F("SD Error"), true); + } + for (unsigned long currBuffer = 0; currBuffer < sramSize; currBuffer += 512) { + for (int currByte = 0; currByte < 512; currByte++) { + byte myByte = readByte_VB(currBuffer + currByte); + sdBuffer[currByte] = myByte; + } + myFile.write(sdBuffer, 512); + } + myFile.close(); + print_Msg(F("Saved to ")); + print_Msg(folder); + println_Msg(F("/")); + display_Update(); +} + +unsigned long verifySRAM_VB() { + dataIn_VB(); + writeErrors = 0; + + if (myFile.open(filePath, O_READ)) { + for (unsigned long currBuffer = 0; currBuffer < sramSize; currBuffer += 512) { + for (int currByte = 0; currByte < 512; currByte++) { + byte myByte = readByte_VB(currBuffer + currByte); + sdBuffer[currByte] = myByte; + } + for (int i = 0; i < 512; i++) { + if (myFile.read() != sdBuffer[i]) { + writeErrors++; + } + } + } + myFile.close(); + } + else { + print_Error(F("SD Error"), true); + } + + return writeErrors; +} +#endif diff --git a/Cart_Reader/WS.ino b/Cart_Reader/WS.ino index 5d61e61..99e76f5 100644 --- a/Cart_Reader/WS.ino +++ b/Cart_Reader/WS.ino @@ -1,6 +1,7 @@ //****************************************** // WS MODULE //****************************************** +#ifdef enable_WS // Cartridge pinout // 48P 1.25mm pitch connector // C1, C48 : GND @@ -22,8 +23,6 @@ // C46 : INT (for RTC alarm interrupt) // C47 : CLK (384KHz on WS) -#ifdef enable_WS - #ifdef ws_adapter_v2 #define WS_CLK_BIT 5 // USE PE5 as CLK #else diff --git a/Cart_Reader/WSV.ino b/Cart_Reader/WSV.ino new file mode 100644 index 0000000..e39aba3 --- /dev/null +++ b/Cart_Reader/WSV.ino @@ -0,0 +1,562 @@ +//****************************************** +// WSV MODULE +//****************************************** +#ifdef enable_WSV +// Watara Supervision +// Cartridge Pinout +// 40P 2.5mm pitch connector +// +// BACK LABEL +// SIDE SIDE +// +-------+ +// /RD -| 1 40 |- +5V +// A0 -| 2 39 |- nc +// A1 -| 3 38 |- nc +// A2 -| 4 37 |- nc +// A3 -| 5 36 |- nc +// A4 -| 6 35 |- /WR +// A5 -| 7 34 |- D0 +// A6 -| 8 33 |- D1 +// A7 -| 9 32 |- D2 +// A8 -| 10 31 |- D3 +// A9 -| 11 30 |- D4 +// A10 -| 12 29 |- D5 +// A11 -| 13 28 |- D6 +// A12 -| 14 27 |- D7 +// A13 -| 15 26 |- nc +// A14 -| 16 25 |- nc +// A15 -| 17 24 |- L1 +// A16 -| 18 23 |- L2 +// L3 -| 19 22 |- GND +// L0 -| 20 21 |- PWR GND +// +-------+ +// +// L3 - L0 are the Link Port's I/O - only the 'MAGNUM' variant +// routed these to the cartridge slot as additional banking bits. +// +// CONTROL PINS: +// /WR - (PH5) +// /RD - (PH6) + +word WSV[] = {32, 64, 512}; +byte wsvlo = 0; // Lowest Entry +byte wsvhi = 2; // Highest Entry + +byte wsvsize; +byte newwsvsize; + +// EEPROM MAPPING +// 08 ROM SIZE + +//****************************************** +// SETUP +//****************************************** + +void setup_WSV() +{ + // Set Address Pins to Output + //A0-A7 + DDRF = 0xFF; + //A8-A15 + DDRK = 0xFF; + //BA0-BA7 (BA5-BA7 UNUSED) + DDRL = 0xFF; + //PA0-PA7 (UNUSED) + DDRA = 0xFF; + + // Set Data Pins (D0-D7) to Input + // D0 - D7 + DDRC = 0x00; + + // Set Control Pins to Output + // WR(PH5) RD(PH6) + // DDRH |= (1 << 5) | (1 << 6); + // ---(PH0) ---(PH1) ---(PH3) ---(PH4) /WR(PH5) /RD(PH6) + DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6); + + // Switch WR(PH5) to HIGH + // PORTH |= (1 << 5); + // Switch RD(PH6) to LOW + // PORTH &= ~(1 << 6); + // Setting Control Pins to HIGH + // ---(PH0) ---(PH1) ---(PH3) ---(PH4) /WR(PH5) + PORTH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5); + // Switch RD(PH6) to LOW + PORTH &= ~(1 << 6); + + // Set Unused Pins HIGH + PORTL = 0xE0; + PORTA = 0xFF; + PORTJ |= (1 << 0); // TIME(PJ0) + + checkStatus_WSV(); + strcpy(romName, "SUPERVISION"); + + mode = mode_WSV; +} + +//****************************************** +// MENU +//****************************************** + +// Base Menu +static const char wsvMenuItem1[] PROGMEM = "Select Cart"; +static const char wsvMenuItem2[] PROGMEM = "Read ROM"; +static const char wsvMenuItem3[] PROGMEM = "Set Size"; +static const char wsvMenuItem4[] PROGMEM = "Reset"; +static const char* const menuOptionsSV[] PROGMEM = {wsvMenuItem1, wsvMenuItem2, wsvMenuItem3, wsvMenuItem4}; + +void wsvMenu() +{ + convertPgm(menuOptionsSV, 4); + uint8_t mainMenu = question_box(F("SUPERVISION MENU"), menuOptions, 4, 0); + + switch (mainMenu) + { + case 0: + // Select Cart + setCart_WSV(); + setup_WSV(); + break; + + case 1: + // Read Rom + sd.chdir("/"); + readROM_WSV(); + sd.chdir("/"); + break; + + case 2: + setROMSize_WSV(); + break; + + case 3: + // reset + resetArduino(); + break; + } +} + +//****************************************** +// LOW LEVEL FUNCTIONS +//****************************************** + +// WRITE +void controlOut_WSV() { + // Switch RD(PH6) to HIGH + PORTH |= (1 << 6); + // Switch WR(PH5) to LOW + PORTH &= ~(1 << 5); +} + +// READ +void controlIn_WSV() { + // Switch WR(PH5) to HIGH + PORTH |= (1 << 5); + // Switch RD(PH6) to LOW + PORTH &= ~(1 << 6); +} + +void dataIn_WSV() +{ + DDRC = 0x00; +} + +void dataOut_WSV() +{ + DDRC = 0xFF; +} + +uint8_t readByte_WSV(uint32_t addr) +{ + PORTF = addr & 0xFF; + PORTK = (addr >> 8) & 0xFF; + PORTL = (addr >> 16) & 0xFF; + + // Wait for data bus + // 6 x 62.5ns = 375ns + NOP; NOP; NOP; NOP; NOP; NOP; + + uint8_t ret = PINC; + NOP; + + return ret; +} + +//****************************************** +// READ CODE +//****************************************** + +void readROM_WSV() +{ + strcpy(fileName, romName); + strcat(fileName, ".sv"); + + // create a new folder for storing rom file + EEPROM_readAnything(0, foldern); + //sprintf(folder, "WSV/ROM/%s/%d", romName, foldern); + sprintf(folder, "WSV/ROM/%d", foldern); + sd.mkdir(folder, true); + sd.chdir(folder); + + display_Clear(); + print_Msg(F("Saving to ")); + print_Msg(folder); + println_Msg(F("/...")); + display_Update(); + + // open file on sdcard + if (!myFile.open(fileName, O_RDWR | O_CREAT)) + print_Error(F("Can't create file on SD"), true); + + // write new folder number back to EEPROM + foldern++; + EEPROM_writeAnything(0, foldern); + + // start reading rom + dataIn_WSV(); + controlIn_WSV(); + + romSize = WSV[wsvsize]; + + uint32_t romStart = 0; + if (romSize < 64) + romStart = 0x8000; + uint32_t romEnd = (uint32_t)romSize * 0x400; + for (uint32_t addr = 0; addr < romEnd; addr += 512) + { + for (uint16_t w = 0; w < 512; w++) + sdBuffer[w] = readByte_WSV(romStart + addr + w); + myFile.write(sdBuffer, 512); + } + myFile.close(); + + // Compare CRC32 to database and rename ROM if found + // Arguments: database name, precalculated crc string or 0 to calculate, rename rom or not, starting offset + compareCRC("wsv.txt", 0, 1, 0); + + println_Msg(F("")); + println_Msg(F("Press Button...")); + display_Update(); + wait(); +} + +//****************************************** +// ROM SIZE +//****************************************** + +void setROMSize_WSV() +{ +#if (defined(enable_OLED) || defined(enable_LCD)) + display_Clear(); + if (wsvlo == wsvhi) + newwsvsize = wsvlo; + else { + int b = 0; + int i = wsvlo; + + display_Clear(); + print_Msg(F("ROM Size: ")); + println_Msg(WSV[i]); + println_Msg(F("")); +#if defined(enable_OLED) + println_Msg(F("Press left to Change")); + println_Msg(F("and right to Select")); +#elif defined(enable_LCD) + println_Msg(F("Rotate to Change")); + println_Msg(F("Press to Select")); +#endif + display_Update(); + + while (1) { + b = checkButton(); + if (b == 2) { // Previous (doubleclick) + if (i == wsvlo) + i = wsvhi; + else + i--; + + // Only update display after input because of slow LCD library + display_Clear(); + print_Msg(F("ROM Size: ")); + println_Msg(WSV[i]); + println_Msg(F("")); +#if defined(enable_OLED) + println_Msg(F("Press left to Change")); + println_Msg(F("and right to Select")); +#elif defined(enable_LCD) + println_Msg(F("Rotate to Change")); + println_Msg(F("Press to Select")); +#endif + display_Update(); + } + if (b == 1) { // Next (press) + if (i == wsvhi) + i = wsvlo; + else + i++; + + // Only update display after input because of slow LCD library + display_Clear(); + print_Msg(F("ROM Size: ")); + println_Msg(WSV[i]); + println_Msg(F("")); +#if defined(enable_OLED) + println_Msg(F("Press left to Change")); + println_Msg(F("and right to Select")); +#elif defined(enable_LCD) + println_Msg(F("Rotate to Change")); + println_Msg(F("Press to Select")); +#endif + display_Update(); + } + if (b == 3) { // Long Press - Execute (hold) + newwsvsize = i; + break; + } + } + display.setCursor(0, 56); // Display selection at bottom + } + print_Msg(F("ROM SIZE ")); + print_Msg(WSV[newwsvsize]); + println_Msg(F("K")); + display_Update(); + delay(1000); +#else + if (wsvlo == wsvhi) + newwsvsize = wsvlo; + else { +setrom: + String sizeROM; + for (int i = 0; i < (wsvhi - wsvlo + 1); i++) { + Serial.print(F("Select ROM Size: ")); + Serial.print(i); + Serial.print(F(" = ")); + Serial.print(WSV[i + wsvlo]); + Serial.println(F("K")); + } + Serial.print(F("Enter ROM Size: ")); + while (Serial.available() == 0) {} + sizeROM = Serial.readStringUntil('\n'); + Serial.println(sizeROM); + newwsvsize = sizeROM.toInt() + wsvlo; + if (newwsvsize > wsvhi) { + Serial.println(F("SIZE NOT SUPPORTED")); + Serial.println(F("")); + goto setrom; + } + } + Serial.print(F("ROM Size = ")); + Serial.print(WSV[newwsvsize]); + Serial.println(F("K")); +#endif + EEPROM_writeAnything(8, newwsvsize); + wsvsize = newwsvsize; +} + +void checkStatus_WSV() +{ + EEPROM_readAnything(8, wsvsize); + if (wsvsize > 2) { + wsvsize = 1; // default 64K + EEPROM_writeAnything(8, wsvsize); + } + +#if (defined(enable_OLED) || defined(enable_LCD)) + display_Clear(); + println_Msg(F("WATARA SUPERVISION")); + println_Msg(F("CURRENT SETTINGS")); + println_Msg(F("")); + print_Msg(F("ROM SIZE: ")); + print_Msg(WSV[wsvsize]); + println_Msg(F("K")); + display_Update(); + wait(); +#else + Serial.print(F("CURRENT ROM SIZE: ")); + Serial.print(WSV[wsvsize]); + Serial.println(F("K")); + Serial.println(F("")); +#endif +} + +//****************************************** +// CART SELECT CODE +//****************************************** +void setCart_WSV() { + char gamename[100]; + char tempStr2[2]; + char crc_search[9]; + + //go to root + sd.chdir(); + + // Select starting letter + byte myLetter = starting_letter(); + + // Open database + if (myFile.open("wsv.txt", O_READ)) { + // Skip ahead to selected starting letter + if ((myLetter > 0) && (myLetter <= 26)) { + while (myFile.available()) { + // Read current name + get_line(gamename, &myFile, 96); + + // Compare selected letter with first letter of current name until match + while (gamename[0] != 64 + myLetter) { + skip_line(&myFile); + skip_line(&myFile); + get_line(gamename, &myFile, 96); + } + break; + } + + // Rewind one line + for (byte count_newline = 0; count_newline < 2; count_newline++) { + while (1) { + if (myFile.curPosition() == 0) { + break; + } + else if (myFile.peek() == '\n') { + myFile.seekSet(myFile.curPosition() - 1); + break; + } + else { + myFile.seekSet(myFile.curPosition() - 1); + } + } + } + if (myFile.curPosition() != 0) + myFile.seekSet(myFile.curPosition() + 2); + } + + // Display database + while (myFile.available()) { + display_Clear(); + + // Read game name +#if defined(enable_OLED) + get_line(gamename, &myFile, 42); +#else + get_line(gamename, &myFile, 96); +#endif + + // Read CRC32 checksum + sprintf(checksumStr, "%c", myFile.read()); + for (byte i = 0; i < 7; i++) { + sprintf(tempStr2, "%c", myFile.read()); + strcat(checksumStr, tempStr2); + } + + // Skip over semicolon + myFile.seekSet(myFile.curPosition() + 1); + + // Read CRC32 of first 512 bytes + sprintf(crc_search, "%c", myFile.read()); + for (byte i = 0; i < 7; i++) { + sprintf(tempStr2, "%c", myFile.read()); + strcat(crc_search, tempStr2); + } + + // Skip over semicolon + myFile.seekSet(myFile.curPosition() + 1); + + // Read rom size + // Read the next ascii character and subtract 48 to convert to decimal + cartSize = myFile.read() - 48; + + // Remove leading 0 for single digit cart sizes + if (cartSize != 0) { + cartSize = cartSize * 10 + myFile.read() - 48; + } + else { + cartSize = myFile.read() - 48; + } + + // Skip rest of line + myFile.seekSet(myFile.curPosition() + 2); + + // Skip every 3rd line + skip_line(&myFile); + + println_Msg(F("Select your cartridge")); + println_Msg(F("")); + println_Msg(gamename); + print_Msg(F("Size: ")); + if (cartSize == 51) + print_Msg(F("512")); + else + print_Msg(cartSize); + println_Msg(F("KB")); + println_Msg(F("")); +#if defined(enable_OLED) + println_Msg(F("Press left to Change")); + println_Msg(F("and right to Select")); +#elif defined(enable_LCD) + println_Msg(F("Rotate to Change")); + println_Msg(F("Press to Select")); +#elif defined(SERIAL_MONITOR) + println_Msg(F("U/D to Change")); + println_Msg(F("Space to Select")); +#endif + display_Update(); + + int b = 0; + while (1) { + // Check button input + b = checkButton(); + + // Next + if (b == 1) { + break; + } + + // Previous + else if (b == 2) { + for (byte count_newline = 0; count_newline < 7; count_newline++) { + while (1) { + if (myFile.curPosition() == 0) { + break; + } + else if (myFile.peek() == '\n') { + myFile.seekSet(myFile.curPosition() - 1); + break; + } + else { + myFile.seekSet(myFile.curPosition() - 1); + } + } + } + if (myFile.curPosition() != 0) + myFile.seekSet(myFile.curPosition() + 2); + break; + } + + // Selection + else if (b == 3) { + //word WSV[] = {32,64,512}; + switch (cartSize) { + case 32: + wsvsize = 0; + break; + + case 64: + wsvsize = 1; + break; + + case 51: + wsvsize = 2; + break; + } + EEPROM_writeAnything(8, wsvsize); + myFile.close(); + break; + } + } + } + } + else { + print_Error(F("Database file not found"), true); + } +} +#endif