/********************************************************************************** Cartridge Reader for Arduino Mega2560 This project represents a community-driven effort to provide an easy to build and easy to modify cartridge dumper. Date: 2024-03-02 Version: 13.2 SD lib: https://github.com/greiman/SdFat LCD lib: https://github.com/olikraus/u8g2 Neopixel lib: https://github.com/adafruit/Adafruit_NeoPixel Rotary Enc lib: https://github.com/mathertel/RotaryEncoder SI5351 lib: https://github.com/etherkit/Si5351Arduino RTC lib: https://github.com/adafruit/RTClib Frequency lib: https://github.com/PaulStoffregen/FreqCount Compiled with Arduino IDE 2.2.1 Thanks to: 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-, Intellivision, Virtual Boy, WSV, PCW, ARC, Atari 2600/5200/7800, ODY2, Fairchild, MSX, Pokemon Mini, C64, Vectrex modules Tamanegi_taro - PCE and Satellaview modules splash5 - GBSmart, Wonderswan, NGP and Super A'can modules partlyhuman - Casio Loopy module hkz & themanbehindthecurtain - N64 flashram commands Andrew Brown & Peter Den Hartog - N64 controller protocol libdragon - N64 controller checksum functions Angus Gratton - CRC32 Snes9x - SuperFX sram fix insidegadgets - GBCartRead RobinTheHood - GameboyAdvanceRomDumper Gens-gs - Megadrive checksum fceux - iNes header And a special Thank You to all coders and contributors on Github and the Arduino forum: jiyunomegami, splash5, Kreeblah, ramapcsx2, PsyK0p4T, Dakkaron, majorpbx, Pickle, sdhizumi, Uzlopak, sakman55, Tombo89, scrap-a, borti4938, vogelfreiheit, CaitSith2, Modman, philenotfound, karimhadjsalem, nsx0r, ducky92, niklasweber, Lesserkuma, BacteriaMage, vpelletier, Ancyker, mattiacci, RWeick, joshman196, partlyhuman, ButThouMust, hxlnt, breyell, qufb And to nocash for figuring out the secrets of the SFC Nintendo Power cartridge. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **********************************************************************************/ #include "OSCR.h" /****************************************** Libraries *****************************************/ // SD Card SdFs sd; FsFile myFile; #ifdef ENABLE_GLOBAL_LOG FsFile myLog; bool dont_log = false; #endif // AVR Eeprom #include // forward declarations for "T" (for non Arduino IDE) template int EEPROM_writeAnything(int ee, const T& value); template int EEPROM_readAnything(int ee, T& value); // Graphic SPI LCD #ifdef ENABLE_LCD #include U8G2_ST7567_OS12864_F_4W_HW_SPI display(U8G2_R2, /* cs=*/12, /* dc=*/11, /* reset=*/10); #endif // Rotary Encoder #ifdef ENABLE_ROTARY #include #define PIN_IN1 18 #define PIN_IN2 19 #ifdef rotate_counter_clockwise RotaryEncoder encoder(PIN_IN2, PIN_IN1, RotaryEncoder::LatchMode::FOUR3); #else RotaryEncoder encoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::FOUR3); #endif int rotaryPos = 0; #endif // Choose RGB LED type #ifdef ENABLE_NEOPIXEL // Neopixel #include Adafruit_NeoPixel pixels(3, 13, NEO_GRB + NEO_KHZ800); #endif typedef enum COLOR_T { blue_color, red_color, purple_color, green_color, turquoise_color, yellow_color, white_color, } color_t; // Graphic I2C OLED #ifdef ENABLE_OLED #include U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, /* reset=*/U8X8_PIN_NONE); #endif // Adafruit Clock Generator #include Si5351 clockgen; bool i2c_found; // RTC Library #ifdef ENABLE_RTC #define _RTC_H #include "RTClib.h" #endif // Clockgen Calibration #ifdef OPTION_CLOCKGEN_CALIBRATION #include "FreqCount.h" #endif void _print_FatalError(void) __attribute__((noreturn)); void print_FatalError(const __FlashStringHelper* errorMessage) __attribute__((noreturn)); void print_FatalError(byte errorMessage) __attribute__((noreturn)); /****************************************** End of inclusions and forward declarations *****************************************/ template int EEPROM_writeAnything(int ee, const T& value) { const byte* p = (const byte*)(const void*)&value; unsigned int i; for (i = 0; i < sizeof(value); i++) EEPROM.write(ee++, *p++); return i; } template int EEPROM_readAnything(int ee, T& value) { byte* p = (byte*)(void*)&value; unsigned int i; for (i = 0; i < sizeof(value); i++) *p++ = EEPROM.read(ee++); return i; } /****************************************** Common Strings *****************************************/ #define press_button_STR 0 #define sd_error_STR 1 #define reset_STR 2 #define did_not_verify_STR 3 #define _bytes_STR 4 #define error_STR 5 #define create_file_STR 6 #define open_file_STR 7 #define file_too_big_STR 8 #define done_STR 9 #define saving_to_STR 10 #define verifying_STR 11 #define flashing_file_STR 12 #define press_to_change_STR 13 #define right_to_select_STR 14 #define rotate_to_change_STR 15 #define press_to_select_STR 16 // This arrays holds the most often uses strings constexpr char string_press_button0[] PROGMEM = "Press Button..."; constexpr char string_sd_error1[] PROGMEM = "SD Error"; constexpr char string_did_not_verify3[] PROGMEM = "did not verify"; constexpr char string_bytes4[] PROGMEM = " bytes "; constexpr char string_error5[] PROGMEM = "Error: "; constexpr char string_create_file6[] PROGMEM = "Can't create file"; constexpr char string_open_file7[] PROGMEM = "Can't open file"; constexpr char string_file_too_big8[] PROGMEM = "File too big"; constexpr char string_done9[] PROGMEM = "Done"; constexpr char string_saving_to10[] PROGMEM = "Saving to "; constexpr char string_verifying11[] PROGMEM = "Verifying..."; constexpr char string_flashing_file12[] PROGMEM = "Flashing file "; constexpr char string_press_to_change13[] PROGMEM = "Press left to Change"; constexpr char string_right_to_select14[] PROGMEM = "and right to Select"; constexpr char string_rotate_to_change15[] PROGMEM = "Rotate to Change"; constexpr char string_press_to_select16[] PROGMEM = "Press to Select"; static const char* const string_table[] PROGMEM = { string_press_button0, string_sd_error1, FSTRING_RESET, string_did_not_verify3, string_bytes4, string_error5, string_create_file6, string_open_file7, string_file_too_big8, string_done9, string_saving_to10, string_verifying11, string_flashing_file12, string_press_to_change13, string_right_to_select14, string_rotate_to_change15, string_press_to_select16 }; void print_STR(byte string_number, boolean newline) { char string_buffer[22]; strcpy_P(string_buffer, (char*)pgm_read_word(&(string_table[string_number]))); if (newline) println_Msg(string_buffer); else print_Msg(string_buffer); } /****************************************** Defines *****************************************/ // optimization-safe nop delay #define NOP __asm__ __volatile__("nop\n\t") // Button timing #define debounce 20 // ms debounce period to prevent flickering when pressing or releasing the button #define DCgap 250 // max ms between clicks for a double click event #define holdTime 2000 // ms hold period: how long to wait for press+hold event #define longHoldTime 5000 // ms long hold period: how long to wait for press+hold event /****************************************** Variables *****************************************/ #ifdef ENABLE_ROTARY // Button debounce boolean buttonState = HIGH; // the current reading from the input pin boolean lastButtonState = HIGH; // the previous reading from the input pin unsigned long lastDebounceTime = 0; // the last time the output pin was toggled unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers #endif #ifdef ENABLE_OLED // Button 1 boolean buttonVal1 = HIGH; // value read from button boolean buttonLast1 = HIGH; // buffered value of the button's previous state boolean DCwaiting1 = false; // whether we're waiting for a double click (down) boolean DConUp1 = false; // whether to register a double click on next release, or whether to wait and click boolean singleOK1 = true; // whether it's OK to do a single click long downTime1 = -1; // time the button was pressed down long upTime1 = -1; // time the button was released boolean ignoreUp1 = false; // whether to ignore the button release because the click+hold was triggered boolean waitForUp1 = false; // when held, whether to wait for the up event boolean holdEventPast1 = false; // whether or not the hold event happened already boolean longholdEventPast1 = false; // whether or not the long hold event happened already // Button 2 boolean buttonVal2 = HIGH; // value read from button boolean buttonLast2 = HIGH; // buffered value of the button's previous state boolean DCwaiting2 = false; // whether we're waiting for a double click (down) boolean DConUp2 = false; // whether to register a double click on next release, or whether to wait and click boolean singleOK2 = true; // whether it's OK to do a single click long downTime2 = -1; // time the button was pressed down long upTime2 = -1; // time the button was released boolean ignoreUp2 = false; // whether to ignore the button release because the click+hold was triggered boolean waitForUp2 = false; // when held, whether to wait for the up event boolean holdEventPast2 = false; // whether or not the hold event happened already boolean longholdEventPast2 = false; // whether or not the long hold event happened already #endif #ifdef ENABLE_SERIAL // For incoming serial data int incomingByte; #endif // Variables for the menu int choice = 0; // Temporary array that holds the menu option read out of progmem char menuOptions[7][20]; boolean ignoreError = 0; // File browser #define FILENAME_LENGTH 100 #define FILEPATH_LENGTH 132 #define FILEOPTS_LENGTH 20 char fileName[FILENAME_LENGTH]; char filePath[FILEPATH_LENGTH]; byte currPage; byte lastPage; byte numPages; boolean root = 0; boolean filebrowse = 0; // Common // 21 chars for SNES ROM name, one char for termination char romName[22]; unsigned long sramSize = 0; int romType = 0; byte saveType; word romSize = 0; word numBanks = 128; char checksumStr[9]; bool errorLvl = 0; byte romVersion = 0; char cartID[5]; unsigned long cartSize; unsigned int flashid; char flashid_str[5]; char vendorID[5]; unsigned long fileSize; unsigned long sramBase; unsigned long flashBanks; bool flashX16Mode; bool flashSwitchLastBits; // Variable to count errors unsigned long writeErrors; // Operation mode CORES mode = CORE_MAX; //remember folder number to create a new folder for every game int foldern; // 4 chars for console type, 4 chars for SAVE/ROM, 21 chars for ROM name, 4 chars for folder number, 3 chars for slashes, one char for termination, one char savety char folder[38]; // Array that holds the data byte sdBuffer[512]; // soft reset Arduino: jumps to 0 // using the watchdog timer would be more elegant but some Mega2560 bootloaders are buggy with it void (*resetArduino)(void) __attribute__((noreturn)) = 0; // Progressbar void draw_progressbar(uint32_t processedsize, uint32_t totalsize); // used by MD and NES modules byte eepbit[8]; byte eeptemp; // Array to hold iNES header byte iNES_HEADER[16]; //ID 0-3 //ROM_size 4 //VROM_size 5 //ROM_type 6 //ROM_type2 7 //ROM_type3 8 //Upper_ROM_VROM_size 9 //RAM_size 10 //VRAM_size 11 //TV_system 12 //VS_hardware 13 //reserved 14, 15 //****************************************** // CRC32 //****************************************** // CRC32 lookup table // 256 entries constexpr uint32_t crc_32_tab[] PROGMEM = { /* CRC polynomial 0xedb88320 */ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; // Defined as a macros, as compiler disregards inlining requests and these are // performance-critical functions. #define UPDATE_CRC(crc, ch) \ do { \ uint8_t idx = ((crc) ^ (ch)) & 0xff; \ uint32_t tab_value = pgm_read_dword(crc_32_tab + idx); \ (crc) = tab_value ^ ((crc) >> 8); \ } while (0) uint32_t updateCRC(const byte* buffer, size_t length, uint32_t crc) { for (size_t c = 0; c < length; c++) { UPDATE_CRC(crc, buffer[c]); } return crc; } uint32_t calculateCRC(const byte* buffer, size_t length) { uint32_t crc = 0xFFFFFFFF; crc = updateCRC(buffer, length, crc); return ~crc; } uint32_t calculateCRC(FsFile& infile) { uint32_t byte_count; uint32_t crc = 0xFFFFFFFF; while ((byte_count = infile.read(sdBuffer, sizeof(sdBuffer))) != 0) { crc = updateCRC(sdBuffer, byte_count, crc); } return ~crc; } // Calculate rom's CRC32 from SD uint32_t calculateCRC(char* fileName, char* folder, unsigned long offset) { FsFile infile; uint32_t result; sd.chdir(folder); if (infile.open(fileName, O_READ)) { infile.seek(offset); result = calculateCRC(infile); infile.close(); return result; } else { display_Clear(); print_Msg(F("File ")); //print_Msg(folder); //print_Msg(F("/")); //print_Msg(fileName); print_FatalError(F(" not found")); return 0; } } /****************************************** CRC Functions for Atari, Fairchild, Ody2, Arc, etc. modules *****************************************/ #if (defined(ENABLE_ODY2) || defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_MSX) || defined(ENABLE_POKE) || defined(ENABLE_2600) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64) || defined(ENABLE_VECTREX) || defined(ENABLE_NES)) void printCRC(char* checkFile, uint32_t* crcCopy, unsigned long offset) { uint32_t crc = calculateCRC(checkFile, folder, offset); char tempCRC[9]; if (crcCopy != NULL) { *crcCopy = crc; } sprintf(tempCRC, "%08lX", crc); print_Msg(F("CRC: ")); println_Msg(tempCRC); display_Update(); } #endif //****************************************** // Functions for CRC32 database //****************************************** //Skip line void skip_line(FsFile* readfile) { int i = 0; char str_buf; while (readfile->available()) { //Read 1 byte from file str_buf = readfile->read(); //if end of file or newline found, execute command if (str_buf == '\r') { readfile->read(); //dispose \n because \r\n break; } i++; } //End while } //Get line from file void get_line(char* str_buf, FsFile* readfile, uint8_t maxi) { int read_len; // Status LED on statusLED(true); read_len = readfile->read(str_buf, maxi - 1); for (int i = 0; i < read_len; i++) { //if end of file or newline found, execute command if (str_buf[i] == '\r') { str_buf[i] = 0; readfile->seekCur(i - read_len + 2); // +2 to skip over \n because \r\n return; } } str_buf[maxi - 1] = 0; // EOL was not found, keep looking (slower) while (readfile->available()) { if (readfile->read() == '\r') { readfile->read(); // read \n because \r\n break; } } } void rewind_line(FsFile& readfile, byte count = 1) { uint32_t position = readfile.curPosition(); // To seek one line back, this code must step over the first newline it finds // in order to exit the current line and enter the end of the previous one. // Convert from how-many-lines-back into how-many-newlines-to-look-for // by incrementing it by 1. count++; for (byte count_newline = 0; count_newline < count; count_newline++) { // Go to the strictly previous '\n', or file start. while (position) { // Seek back first (keeping position updated)... position--; readfile.seekCur(-1); // ...and check current byte second. // Note: this code assumed all files use ASCII with DOS-style newlines // so \n is encountered first when seeking backwards. if (readfile.peek() == '\n') break; } } // If not at file start, the current character is the '\n' just before the // desired line, so advance by one. if (position) readfile.seekCur(1); } // Calculate CRC32 if needed and compare it to CRC read from database boolean compareCRC(const char* database, uint32_t crc32sum, boolean renamerom, int offset) { char crcStr[9]; print_Msg(F("CRC32... ")); display_Update(); if (crc32sum == 0) { //go to root sd.chdir(); // Calculate CRC32 sprintf(crcStr, "%08lX", calculateCRC(fileName, folder, offset)); } else { // Convert precalculated crc to string sprintf(crcStr, "%08lX", ~crc32sum); } // Print checksum print_Msg(crcStr); display_Update(); //Search for CRC32 in file char gamename[96]; char crc_search[9]; //go to root sd.chdir(); if (myFile.open(database, O_READ)) { //Search for same CRC in list while (myFile.available()) { //Read 2 lines (game name and CRC) get_line(gamename, &myFile, sizeof(gamename)); get_line(crc_search, &myFile, sizeof(crc_search)); skip_line(&myFile); //Skip every 3rd line //if checksum search successful, rename the file and end search if (strcmp(crc_search, crcStr) == 0) { #ifdef ENABLE_NES if ((mode == CORE_NES) && (offset != 0)) { // Rewind to iNES Header myFile.seekCur(-36); char iNES_STR[33]; // Read iNES header get_line(iNES_STR, &myFile, 33); // Convert "4E4553" to (0x4E, 0x45, 0x53) unsigned int iNES_BUF; for (byte j = 0; j < 16; j++) { sscanf(iNES_STR + j * 2, "%2X", &iNES_BUF); iNES_HEADER[j] = iNES_BUF; } //Skip CRLF myFile.seekCur(4); } #endif // ENABLE_NES // Close the file: myFile.close(); //Write iNES header #ifdef ENABLE_NES if ((mode == CORE_NES) && (offset != 0)) { // Write iNES header sd.chdir(folder); if (!myFile.open(fileName, O_RDWR)) { print_FatalError(sd_error_STR); } for (byte z = 0; z < 16; z++) { myFile.write(iNES_HEADER[z]); } myFile.close(); } #endif // ENABLE_NES print_Msg(F(" -> ")); display_Update(); if (renamerom) { println_Msg(gamename); // Rename file to database name sd.chdir(folder); delay(100); if (myFile.open(fileName, O_READ)) { myFile.rename(gamename); // Close the file: myFile.close(); } } else { println_Msg(FS(FSTRING_OK)); } return 1; break; } } if (strcmp(crc_search, crcStr) != 0) { print_Error(F(" -> Not found")); return 0; } } else { println_Msg(F(" -> Error")); print_Error(F("Database missing")); return 0; } return 0; } //****************************************** // Math Functions //****************************************** #if (defined(ENABLE_NES) || defined(ENABLE_MSX) || defined(ENABLE_GBX)) int int_pow(int base, int exp) { // Power for int int result = 1; while (exp) { if (exp & 1) result *= base; exp /= 2; base *= base; } return result; } #endif // move file pointer to first game line with matching letter. If no match is found the last database entry is selected void seek_first_letter_in_database(FsFile& database, byte myLetter) { char gamename_str[3]; #ifdef ENABLE_GLOBAL_LOG // Disable log to prevent unnecessary logging println_Log(F("Select Mapping from List")); dont_log = true; #endif database.rewind(); // Skip ahead to selected starting letter if ((myLetter > 0) && (myLetter <= 26)) { myLetter += 'A' - 1; do { // Read current name get_line(gamename_str, &database, 2); // Skip data line skip_line(&database); // Skip empty line skip_line(&database); } while (database.available() && gamename_str[0] != myLetter); rewind_line(database, 3); } #ifdef ENABLE_GLOBAL_LOG // Enable log again dont_log = false; #endif } #if (defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_VECTREX)) // read single digit data line as byte void readDataLineSingleDigit(FsFile& database, void* byteData) { // Read rom size (*(byte*)byteData) = database.read() - 48; // Skip rest of line database.seekCur(2); } #endif #if (defined(ENABLE_ODY2) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64)) struct database_entry_mapper_size { byte gameMapper; byte gameSize; }; // read database entry with mapper and size digits void readDataLineMapperSize(FsFile& database, void* entry) { struct database_entry_mapper_size* castEntry = (database_entry_mapper_size*)entry; // Read mapper castEntry->gameMapper = database.read() - 48; // if next char is not a semicolon expect an additional digit char temp = database.read(); if(temp != ',') { castEntry->gameMapper = (castEntry->gameMapper * 10) + (temp - 48); // Skip over semicolon database.seekCur(1); } // Read rom size castEntry->gameSize = database.read() - 48; // Skip rest of line database.seekCur(2); } #endif // navigate through the database file using OSSC input buttons. Requires function pointer readData for reading device specific data line from database // printDataLine - optional callback for printing device specific data informations about the currently browsed game // setRomName - callback function to set rom name if game is selected // returns true if a game was selected, false otherwise boolean checkCartSelection(FsFile& database, void (*readData)(FsFile&, void*), void* data, void (*printDataLine)(void*) = NULL, void (*setRomName)(const char* input) = NULL) { char gamename[128]; uint8_t fastScrolling = 1; // Display database while (database.available()) { #ifdef ENABLE_GLOBAL_LOG // Disable log to prevent unnecessary logging dont_log = true; #endif display_Clear(); get_line(gamename, &database, sizeof(gamename)); readData(database, data); skip_line(&database); println_Msg(F("Select your cartridge")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(gamename); if(printDataLine) { printDataLine(data); } println_Msg(FS(FSTRING_EMPTY)); #if defined(ENABLE_OLED) print_STR(press_to_change_STR, 0); if (fastScrolling > 1) println_Msg(F(" (fast)")); else println_Msg(""); print_STR(right_to_select_STR, 1); #elif defined(ENABLE_LCD) print_STR(rotate_to_change_STR, 0); if (fastScrolling > 1) println_Msg(F(" (fast)")); else println_Msg(""); print_STR(press_to_select_STR, 1); #elif defined(SERIAL_MONITOR) println_Msg(F("U/D to Change")); println_Msg(F("Space to Select")); #endif display_Update(); #ifdef ENABLE_GLOBAL_LOG // Enable log again dont_log = false; #endif uint8_t b = 0; while (1) { // Check button input b = checkButton(); // Next if (b == 1) { // 1: Next record if (fastScrolling > 1) { for (uint8_t skipped = 0; skipped < fastScrolling * 3; skipped++) { skip_line(&database); } } break; } // Previous else if (b == 2) { // 2: Previous record if (fastScrolling > 1) rewind_line(database, fastScrolling * 3 + 3); else rewind_line(database, 6); break; } // Selection else if (b == 3) { if(setRomName) { setRomName(gamename); } database.close(); return true; } else if (b == 4) { // 4: Toggle Fast Scrolling if (fastScrolling == 1) fastScrolling = 30; else fastScrolling = 1; continue; } } } return false; } #if (defined(ENABLE_ODY2) || defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_MSX) || defined(ENABLE_POKE) || defined(ENABLE_2600) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64) || defined(ENABLE_VECTREX) || defined(ENABLE_NES)) #if (defined(ENABLE_OLED) || defined(ENABLE_LCD)) || defined(ENABLE_GBX) void printInstructions() { println_Msg(FS(FSTRING_EMPTY)); #if defined(ENABLE_OLED) print_STR(press_to_change_STR, 1); print_STR(right_to_select_STR, 1); #elif defined(ENABLE_LCD) print_STR(rotate_to_change_STR, 1); print_STR(press_to_select_STR, 1); #elif defined(SERIAL_MONITOR) println_Msg(F("U/D to Change")); println_Msg(F("Space to Select")); #endif display_Update(); } int navigateMenu(int min, int max, void (*printSelection)(int)) { uint8_t b = 0; int i = min; // Check Button Status #if defined(ENABLE_OLED) buttonVal1 = (PIND & (1 << 7)); // PD7 #elif defined(ENABLE_LCD) boolean buttonVal1 = (PING & (1 << 2)); //PG2 #endif if (buttonVal1 == LOW) { // Button Pressed while (1) { // Scroll Mapper List #if defined(ENABLE_OLED) buttonVal1 = (PIND & (1 << 7)); // PD7 #elif defined(ENABLE_LCD) buttonVal1 = (PING & (1 << 2)); //PG2 #endif if (buttonVal1 == HIGH) { // Button Released // Correct Overshoot if (i == min) i = max; else i--; break; } printSelection(i); display_Update(); if (i == max) i = min; else i++; delay(250); } } b = 0; printSelection(i); printInstructions(); while (1) { b = checkButton(); if (b == 2) { // Previous Mapper (doubleclick) if (i == min) i = max; else i--; // Only update display after input because of slow LCD library printSelection(i); printInstructions(); } if (b == 1) { // Next Mapper (press) if (i == max) i = min; else i++; // Only update display after input because of slow LCD library printSelection(i); printInstructions(); } if (b == 3) { // Long Press - Execute (hold) return i; } } } #endif #endif void starting_letter__subDraw(byte selection, byte line) { display.setDrawColor(0); for (uint8_t i = 0; i < 4; i++) display.drawLine(0, 10 + i * 16, 128, 10 + i * 16); display.setDrawColor(1); display.drawLine(4 + selection * 16, 10 + line * 16, 9 + selection * 16, 10 + line * 16); display_Update(); } byte starting_letter() { #ifdef ENABLE_GLOBAL_LOG // Disable log to prevent unnecessary logging dont_log = true; #endif #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) byte selection = 0; byte line = 0; display_Clear(); println_Msg(F("[#] [A] [B] [C] [D] [E] [F]")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("[G] [H] [ I ] [J] [K] [L] [M]")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("[N] [O] [P] [Q] [R] [S] [T]")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("[U] [V] [W] [X] [Y] [Z] [?]")); // Draw selection line display.setDrawColor(1); display.drawLine(4 + selection * 16, 10 + line * 16, 9 + selection * 16, 10 + line * 16); display_Update(); while (1) { uint8_t b = checkButton(); if (b == 2) { // Previous if ((selection == 0) && (line > 0)) { line--; selection = 6; } else if ((selection == 0) && (line == 0)) { line = 3; selection = 6; } else if (selection > 0) { selection--; } starting_letter__subDraw(selection, line); } else if (b == 1) { // Next if ((selection == 6) && (line < 3)) { line++; selection = 0; } else if ((selection == 6) && (line == 3)) { line = 0; selection = 0; } else if (selection < 6) { selection++; } starting_letter__subDraw(selection, line); } else if (b == 3) { // Long Press - Execute if ((selection + line * 7) != 27) { display_Clear(); println_Msg(F("Please wait...")); display_Update(); } break; } } return (selection + line * 7); #elif defined(SERIAL_MONITOR) Serial.println(F("Enter first letter: ")); while (Serial.available() == 0) { } // Read the incoming byte: byte incomingByte = Serial.read(); return incomingByte; #endif #ifdef ENABLE_GLOBAL_LOG // Enable log again dont_log = false; #endif } void print_MissingModule(void) { display_Clear(); println_Msg(F("Please enable module")); print_FatalError(F("in Config.h.")); } /****************************************** Main menu *****************************************/ constexpr char modeItem1[] PROGMEM = "Game Boy"; constexpr char modeItem2[] PROGMEM = "NES/Famicom"; constexpr char modeItem3[] PROGMEM = "Super Nintendo/SFC"; constexpr char modeItem4[] PROGMEM = "Nintendo 64 (3V)"; constexpr char modeItem5[] PROGMEM = "Mega Drive/Genesis"; constexpr char modeItem6[] PROGMEM = "SMS/GG/MIII/SG-1000"; constexpr char modeItem7[] PROGMEM = "PC Engine/TG16"; constexpr char modeItem8[] PROGMEM = "WonderSwan (3V)"; constexpr char modeItem9[] PROGMEM = "NeoGeo Pocket (3V)"; constexpr char modeItem10[] PROGMEM = "Intellivision"; constexpr char modeItem11[] PROGMEM = "Colecovision"; constexpr char modeItem12[] PROGMEM = "Virtual Boy"; constexpr char modeItem13[] PROGMEM = "Watara Supervision (3V)"; constexpr char modeItem14[] PROGMEM = "Pocket Challenge W"; constexpr char modeItem15[] PROGMEM = "Atari 2600"; constexpr char modeItem16[] PROGMEM = "Magnavox Odyssey 2"; constexpr char modeItem17[] PROGMEM = "Arcadia 2001"; constexpr char modeItem18[] PROGMEM = "Fairchild Channel F"; constexpr char modeItem19[] PROGMEM = "Super A'can"; constexpr char modeItem20[] PROGMEM = "MSX"; constexpr char modeItem21[] PROGMEM = "Pokemon Mini (3V)"; constexpr char modeItem22[] PROGMEM = "Casio Loopy"; constexpr char modeItem23[] PROGMEM = "Commodore 64"; constexpr char modeItem24[] PROGMEM = "Atari 5200"; constexpr char modeItem25[] PROGMEM = "Atari 7800"; constexpr char modeItem26[] PROGMEM = "Vectrex"; constexpr char modeItem27[] PROGMEM = "Flashrom Programmer"; constexpr char modeItem28[] PROGMEM = "Self Test (3V)"; constexpr char modeItem29[] PROGMEM = "About"; static const char* const modeOptions[] PROGMEM = { #ifdef ENABLE_GBX modeItem1, #endif #ifdef ENABLE_NES modeItem2, #endif #ifdef ENABLE_SNES modeItem3, #endif #ifdef ENABLE_N64 modeItem4, #endif #ifdef ENABLE_MD modeItem5, #endif #ifdef ENABLE_SMS modeItem6, #endif #ifdef ENABLE_PCE modeItem7, #endif #ifdef ENABLE_WS modeItem8, #endif #ifdef ENABLE_NGP modeItem9, #endif #ifdef ENABLE_INTV modeItem10, #endif #ifdef ENABLE_COLV modeItem11, #endif #ifdef ENABLE_VBOY modeItem12, #endif #ifdef ENABLE_WSV modeItem13, #endif #ifdef ENABLE_PCW modeItem14, #endif #ifdef ENABLE_2600 modeItem15, #endif #ifdef ENABLE_ODY2 modeItem16, #endif #ifdef ENABLE_ARC modeItem17, #endif #ifdef ENABLE_FAIRCHILD modeItem18, #endif #ifdef ENABLE_SUPRACAN modeItem19, #endif #ifdef ENABLE_MSX modeItem20, #endif #ifdef ENABLE_POKE modeItem21, #endif #ifdef ENABLE_LOOPY modeItem22, #endif #ifdef ENABLE_C64 modeItem23, #endif #ifdef ENABLE_5200 modeItem24, #endif #ifdef ENABLE_7800 modeItem25, #endif #ifdef ENABLE_VECTREX modeItem26, #endif #ifdef ENABLE_FLASH modeItem27, #endif #ifdef ENABLE_SELFTEST modeItem28, #endif modeItem29, FSTRING_RESET }; uint8_t pageMenu(const __FlashStringHelper* question, const char* const* menuStrings, uint8_t entryCount, uint8_t default_choice = 0) { // Create menu uint8_t modeMenu; uint8_t num_answers; uint8_t option_offset; // Menu spans across multiple pages currPage = 1; lastPage = 1; numPages = (entryCount / 7) + ( (entryCount % 7) != 0 ); do { option_offset = (currPage - 1) * 7; num_answers = ( (entryCount < (option_offset + 7)) ? entryCount - option_offset : 7); // Copy menuOptions out of progmem convertPgm(menuStrings + option_offset, num_answers); modeMenu = question_box(question, menuOptions, num_answers, default_choice) + option_offset; } while (numPages != 0); // Reset page number currPage = 1; return modeMenu; } // All included slots void mainMenu() { // wait for user choice to come back from the question box menu switch (pageMenu(F("OPEN SOURCE CART READER"), modeOptions, SYSTEM_MENU_TOTAL)) { #ifdef ENABLE_GBX case SYSTEM_MENU_GBX: return gbxMenu(); #endif #ifdef ENABLE_NES case SYSTEM_MENU_NES: mode = CORE_NES; display_Clear(); display_Update(); setup_NES(); getMapping(); checkStatus_NES(); return nesMenu(); #endif #ifdef ENABLE_SNES case SYSTEM_MENU_SNES: return snsMenu(); #endif #ifdef ENABLE_N64 case SYSTEM_MENU_N64: return n64Menu(); #endif #ifdef ENABLE_MD case SYSTEM_MENU_MD: return mdMenu(); #endif #ifdef ENABLE_SMS case SYSTEM_MENU_SMS: return smsMenu(); #endif #ifdef ENABLE_PCE case SYSTEM_MENU_PCE: return pcsMenu(); #endif #ifdef ENABLE_WS case SYSTEM_MENU_WS: display_Clear(); display_Update(); setup_WS(); mode = CORE_WS; return wsMenu(); #endif #ifdef ENABLE_NGP case SYSTEM_MENU_NGP: display_Clear(); display_Update(); setup_NGP(); mode = CORE_NGP; break; #endif #ifdef ENABLE_INTV case SYSTEM_MENU_INTV: setup_INTV(); return intvMenu(); #endif #ifdef ENABLE_COLV case SYSTEM_MENU_COLV: setup_COL(); return colMenu(); break; #endif #ifdef ENABLE_VBOY case SYSTEM_MENU_VBOY: setup_VBOY(); vboyMenu(); break; #endif #ifdef ENABLE_WSV case SYSTEM_MENU_WSV: setup_WSV(); return wsvMenu(); break; #endif #ifdef ENABLE_PCW case SYSTEM_MENU_PCW: setup_PCW(); return pcwMenu(); break; #endif #ifdef ENABLE_2600 case SYSTEM_MENU_2600: setup_2600(); return a2600Menu(); break; #endif #ifdef ENABLE_ODY2 case SYSTEM_MENU_ODY2: setup_ODY2(); return ody2Menu(); break; #endif #ifdef ENABLE_ARC case SYSTEM_MENU_ARC: setup_ARC(); return arcMenu(); break; #endif #ifdef ENABLE_FAIRCHILD case SYSTEM_MENU_FAIRCHILD: setup_FAIRCHILD(); return fairchildMenu(); break; #endif #ifdef ENABLE_SUPRACAN case SYSTEM_MENU_SUPRACAN: return setup_SuprAcan(); break; #endif #ifdef ENABLE_MSX case SYSTEM_MENU_MSX: setup_MSX(); return msxMenu(); break; #endif #ifdef ENABLE_POKE case SYSTEM_MENU_POKE: setup_POKE(); return pokeMenu(); break; #endif #ifdef ENABLE_LOOPY case SYSTEM_MENU_LOOPY: setup_LOOPY(); return loopyMenu(); break; #endif #ifdef ENABLE_C64 case SYSTEM_MENU_C64: setup_C64(); return c64Menu(); break; #endif #ifdef ENABLE_5200 case SYSTEM_MENU_5200: setup_5200(); return a5200Menu(); break; #endif #ifdef ENABLE_7800 case SYSTEM_MENU_7800: setup_7800(); return a7800Menu(); break; #endif #ifdef ENABLE_VECTREX case SYSTEM_MENU_VECTREX: setup_VECTREX(); return vectrexMenu(); break; #endif #ifdef ENABLE_FLASH case SYSTEM_MENU_FLASH: # ifdef ENABLE_VSELECT setup_FlashVoltage(); # endif return flashMenu(); break; #endif #ifdef ENABLE_SELFTEST case SYSTEM_MENU_SELFTEST: return selfTest(); break; #endif case SYSTEM_MENU_ABOUT: return aboutScreen(); break; case SYSTEM_MENU_RESET: return resetArduino(); break; default: return print_MissingModule(); // does not return } } /****************************************** Self Test *****************************************/ #ifdef ENABLE_SELFTEST // Check if given pin number is one of pins 2-9, 14-17, 22-37, 42-49, 54-69 bool isPin_2t9_14t17_22t37_42t49_54t69(byte pinNumber) { return ((2 <= pinNumber) && (pinNumber <= 9)) || ((14 <= pinNumber) && (pinNumber <= 17)) || ((22 <= pinNumber) && (pinNumber <= 37)) || ((42 <= pinNumber) && (pinNumber <= 49)) || ((54 <= pinNumber) && (pinNumber <= 69)); } void selfTest() { #ifdef ENABLE_VSELECT // Set Automatic Voltage Selection to 3V setVoltage(VOLTS_SET_3V3); #endif display_Clear(); println_Msg(F("Self Test")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("Remove all Cartridges")); println_Msg(F("before continuing!!!")); println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); display_Clear(); #if defined(HW3) println_Msg(F("Self Test")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("Turn the EEP switch on.")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); display_Clear(); #endif // Test if pin 7 is held high by 1K resistor pinMode(7, INPUT); println_Msg(F("Testing 1K resistor ")); display_Update(); if (!digitalRead(7)) { setColor_RGB(255, 0, 0); errorLvl = 1; println_Msg(F("Error")); println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); //wait(); //resetArduino(); } println_Msg(F("Testing short to GND")); display_Update(); // Set pins 2-9, 14-17, 22-37, 42-49, 54-69 to input and activate internal pull-up resistors for (byte pinNumber = 2; pinNumber <= 69; pinNumber++) { if (isPin_2t9_14t17_22t37_42t49_54t69(pinNumber)) { pinMode(pinNumber, INPUT_PULLUP); } } // Tests pins 2-9, 14-17, 22-37, 42-49, 54-69 for short to GND for (byte pinNumber = 2; pinNumber <= 69; pinNumber++) { if (isPin_2t9_14t17_22t37_42t49_54t69(pinNumber)) { if (!digitalRead(pinNumber)) { setColor_RGB(255, 0, 0); errorLvl = 1; print_Msg(F("Error: Pin ")); if ((54 <= pinNumber) && (pinNumber <= 69)) { print_Msg(F("A")); println_Msg(pinNumber - 54); } else { print_Msg(F("D")); println_Msg(pinNumber); } println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); resetArduino(); } } } println_Msg(F("Testing short between pins")); display_Update(); // Test for short between pins 2-9, 14-17, 22-37, 42-49, 54-69 for (byte pinNumber = 2; pinNumber <= 69; pinNumber++) { if (isPin_2t9_14t17_22t37_42t49_54t69(pinNumber)) { pinMode(pinNumber, OUTPUT); digitalWrite(pinNumber, LOW); for (byte pinNumber2 = 2; pinNumber2 <= 69; pinNumber2++) { if (isPin_2t9_14t17_22t37_42t49_54t69(pinNumber2) && (pinNumber != pinNumber2)) { pinMode(pinNumber2, INPUT_PULLUP); if (!digitalRead(pinNumber2)) { setColor_RGB(255, 0, 0); errorLvl = 1; print_Msg(F("Error: Pin ")); if ((54 <= pinNumber) && (pinNumber <= 69)) { print_Msg(F("A")); print_Msg(pinNumber - 54); } else { print_Msg(F("D")); print_Msg(pinNumber); } print_Msg(F(" + ")); if ((54 <= pinNumber2) && (pinNumber2 <= 69)) { print_Msg(F("A")); println_Msg(pinNumber2 - 54); } else { print_Msg(F("D")); println_Msg(pinNumber2); } println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); resetArduino(); } } } pinMode(pinNumber, INPUT_PULLUP); } } println_Msg(F("Testing Clock Generator")); initializeClockOffset(); if (!i2c_found) { setColor_RGB(255, 0, 0); errorLvl = 1; println_Msg(F("Error: Clock Generator")); println_Msg(F("not found")); println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); resetArduino(); } println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); resetArduino(); } #endif /****************************************** About Screen *****************************************/ // Info Screen void aboutScreen() { display_Clear(); println_Msg(F("Cartridge Reader")); println_Msg(F("github.com/sanni")); print_Msg(F("2024 FW ")); println_Msg(FS(FSTRING_VERSION)); println_Msg(FS(FSTRING_EMPTY)); println_Msg(FS(FSTRING_EMPTY)); println_Msg(FS(FSTRING_EMPTY)); println_Msg(FS(FSTRING_EMPTY)); // Prints string out of the common strings array either with or without newline print_STR(press_button_STR, 1); display_Update(); while (1) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) // get input button uint8_t b = checkButton(); // if the cart readers input button is pressed shortly if (b == 1) { resetArduino(); } // if the cart readers input button is pressed long if (b == 3) { resetArduino(); } // if the button is pressed super long if (b == 4) { display_Clear(); println_Msg(F("Resetting folder...")); display_Update(); delay(2000); foldern = 0; EEPROM_writeAnything(0, foldern); resetArduino(); } #elif defined(ENABLE_SERIAL) wait_serial(); resetArduino(); #endif } } /****************************************** Progressbar *****************************************/ void draw_progressbar(uint32_t processed, uint32_t total) { uint8_t current, i; static uint8_t previous; uint8_t steps = 20; //Find progressbar length and draw if processed size is not 0 if (processed == 0) { previous = 0; print_Msg(F("[")); display_Update(); return; } // Progress bar current = (processed >= total) ? steps : processed / (total / steps); //Draw "*" if needed if (current > previous) { for (i = previous; i < current; i++) { // steps are 20, so 20 - 1 = 19. if (i == (19)) { //If end of progress bar, finish progress bar by drawing "]" println_Msg(F("]")); } else { print_Msg(F("*")); } } //update previous "*" status previous = current; //Update display display_Update(); } } /****************************************** RTC Module *****************************************/ #ifdef ENABLE_RTC #if defined(DS3231) RTC_DS3231 rtc; #elif defined(DS1307) RTC_DS1307 rtc; #endif // Start Time void RTCStart() { // Start RTC if (!rtc.begin()) { abort(); } // RTC_DS1307 does not have lostPower() #if defined(DS3231) // Set RTC Date/Time of Sketch Build if it lost battery power // After initial setup it would have lost battery power ;) if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } #endif } // Set Date/Time Callback Funtion // Callback for file timestamps void dateTime(uint16_t* date, uint16_t* time) { DateTime now = rtc.now(); // Return date using FAT_DATE macro to format fields *date = FAT_DATE(now.year(), now.month(), now.day()); // Return time using FAT_TIME macro to format fields *time = FAT_TIME(now.hour(), now.minute(), now.second()); } /****************************************** RTC Time Stamp Setup Call in any other script *****************************************/ // Format a Date/Time stamp String RTCStamp() { // Set a format char dtstamp[] = "DDMMMYYYY hh:mm:ssAP"; // Get current Date/Time DateTime now = rtc.now(); // Convert it to a string and caps lock it String dts = now.toString(dtstamp); dts.toUpperCase(); // Print results return dts; } #endif /****************************************** Clockgen Calibration *****************************************/ #ifdef OPTION_CLOCKGEN_CALIBRATION int32_t cal_factor = 0; int32_t old_cal = 0; int32_t cal_offset = 100; void clkcal() { // Adafruit Clock Generator // last number is the clock correction factor which is custom for each clock generator cal_factor = readClockOffset(); display_Clear(); print_Msg(F("Read correction: ")); println_Msg(String(cal_factor)); display_Update(); delay(500); if (cal_factor > INT32_MIN) { i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, cal_factor); } else { i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); cal_factor = 0; } if (!i2c_found) { display_Clear(); print_FatalError(F("Clock Generator not found")); } //clockgen.set_correction(cal_factor, SI5351_PLL_INPUT_XO); clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLA); clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLB); //clockgen.pll_reset(SI5351_PLLA); //clockgen.pll_reset(SI5351_PLLB); clockgen.set_freq(400000000ULL, SI5351_CLK0); clockgen.set_freq(100000000ULL, SI5351_CLK1); clockgen.set_freq(307200000ULL, SI5351_CLK2); clockgen.output_enable(SI5351_CLK1, 1); clockgen.output_enable(SI5351_CLK2, 1); clockgen.output_enable(SI5351_CLK0, 1); // Frequency Counter delay(500); FreqCount.begin(1000); while (1) { if (old_cal != cal_factor) { display_Clear(); println_Msg(F("Adjusting...")); display_Update(); clockgen.set_correction(cal_factor, SI5351_PLL_INPUT_XO); clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLA); clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLB); clockgen.pll_reset(SI5351_PLLA); clockgen.pll_reset(SI5351_PLLB); clockgen.set_freq(400000000ULL, SI5351_CLK0); clockgen.set_freq(100000000ULL, SI5351_CLK1); clockgen.set_freq(307200000ULL, SI5351_CLK2); old_cal = cal_factor; delay(500); } else { clockgen.update_status(); while (clockgen.dev_status.SYS_INIT == 1) { } if (FreqCount.available()) { float count = FreqCount.read(); display_Clear(); println_Msg(F("Clock Calibration")); print_Msg(F("Freq: ")); print_Msg(count); println_Msg(F("Hz")); print_Msg(F("Correction:")); println_Msg(String(cal_factor)); print_Msg(F("Step:")); print_right(cal_offset); println_Msg(FS(FSTRING_EMPTY)); #ifdef ENABLE_BUTTON2 println_Msg(F("(Hold button to save)")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("Decrease Increase")); #else #ifdef ENABLE_ROTARY println_Msg(F("Rotate to adjust Frequency")); println_Msg(F("Press to change step width")); println_Msg(F("Hold to save")); #else println_Msg(F("Click/dbl to adjust")); #endif #endif display_Update(); } #ifdef ENABLE_BUTTON2 // get input button uint8_t a = checkButton1(); uint8_t b = checkButton2(); // if the cart readers input button is pressed shortly if (a == 1) { old_cal = cal_factor; cal_factor -= cal_offset; } if (b == 1) { old_cal = cal_factor; cal_factor += cal_offset; } // if the cart readers input buttons is double clicked if (a == 2) { cal_offset /= 10ULL; if (cal_offset < 1) { cal_offset = 100000000ULL; } } if (b == 2) { cal_offset *= 10ULL; if (cal_offset > 100000000ULL) { cal_offset = 1; } } // if the cart readers input button is pressed long if (a == 3) { savetofile(); } if (b == 3) { savetofile(); } #else //Handle inputs for either rotary encoder or single button interface. uint8_t a = checkButton(); if (a == 1) { //clockwise rotation or single click old_cal = cal_factor; cal_factor += cal_offset; } if (a == 2) { //counterclockwise rotation or double click old_cal = cal_factor; cal_factor -= cal_offset; } if (a == 3) { //button short hold cal_offset *= 10ULL; if (cal_offset > 100000000ULL) { cal_offset = 1; } } if (a == 4) { //button long hold savetofile(); } #endif } } } void print_right(int32_t number) { int32_t abs_number = number; if (abs_number < 0) abs_number *= -1; else print_Msg(FS(FSTRING_SPACE)); if (abs_number == 0) abs_number = 1; while (abs_number < 100000000ULL) { print_Msg(FS(FSTRING_SPACE)); abs_number *= 10ULL; } println_Msg(number); } void savetofile() { display_Clear(); println_Msg(F("Saving...")); println_Msg(String(cal_factor)); display_Update(); delay(2000); if (!myFile.open("/snes_clk.txt", O_WRITE | O_CREAT | O_TRUNC)) { print_FatalError(sd_error_STR); } // Write calibration factor to file myFile.print(cal_factor); // Close the file: myFile.close(); print_STR(done_STR, 1); display_Update(); delay(1000); resetArduino(); } #endif #if defined(OPTION_CLOCKGEN_CALIBRATION) || defined(OPTION_CLOCKGEN_USE_CALIBRATION) int32_t atoi32_signed(const char* input_string) { if (input_string == NULL) { return 0; } int int_sign = 1; int i = 0; if (input_string[0] == '-') { int_sign = -1; i = 1; } int32_t return_val = 0; while (input_string[i] != '\0') { if (input_string[i] >= '0' && input_string[i] <= '9') { return_val = (return_val * 10) + (input_string[i] - '0'); } else if (input_string[i] != '\0') { return 0; } i++; } return_val = return_val * int_sign; return return_val; } int32_t readClockOffset() { FsFile clock_file; char* clock_buf; int16_t i; int32_t clock_offset; if (!clock_file.open("/snes_clk.txt", O_READ)) { return INT32_MIN; } clock_buf = (char*)malloc(12 * sizeof(char)); i = clock_file.read(clock_buf, 11); clock_file.close(); if (i == -1) { free(clock_buf); return INT32_MIN; } else if ((i == 11) && (clock_buf[0] != '-')) { free(clock_buf); return INT32_MIN; } else { clock_buf[i] = 0; } for (i = 0; i < 12; i++) { if (clock_buf[i] != '-' && clock_buf[i] < '0' && clock_buf[i] > '9') { if (i == 0) { free(clock_buf); return INT32_MIN; } else if ((i == 1) && (clock_buf[0] == '-')) { free(clock_buf); return INT32_MIN; } else { clock_buf[i] = 0; } } } clock_offset = atoi32_signed(clock_buf); free(clock_buf); return clock_offset; } #endif int32_t initializeClockOffset() { #ifdef OPTION_CLOCKGEN_USE_CALIBRATION FsFile clock_file; const char zero_char_arr[] = { '0' }; int32_t clock_offset = readClockOffset(); if (clock_offset > INT32_MIN) { i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, clock_offset); } else { i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); if (clock_file.open("/snes_clk.txt", O_WRITE | O_CREAT | O_TRUNC)) { clock_file.write(zero_char_arr, 1); clock_file.close(); } } return clock_offset; #else // last number is the clock correction factor which is custom for each clock generator i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); return 0; #endif } /****************************************** Setup *****************************************/ void setup() { // Set Button Pin PG2 to Input DDRG &= ~(1 << 2); # if defined(HW5) && !defined(ENABLE_VSELECT) /** * HW5 has status LED connected to PD7 * Set LED Pin PD7 to Output **/ DDRD |= (1 << 7); PORTD |= (1 << 7); # elif defined(ENABLE_VSELECT) /** * VSELECT uses pin PD7 * Set LED Pin PD7 to Output **/ DDRD |= (1 << 7); # else /* !defined(HW5) && !defined(HW5) */ /** * HW1-3 have button connected to PD7 * Set pin PD7 to input for button **/ DDRD &= ~(1 << 7); # endif /* HW5 &| ENABLE_VSELECT */ // Set power to low to protect carts setVoltage(VOLTS_SET_3V3); # if defined(ENABLE_3V3FIX) // Set clock high during setup setClockScale(CLKSCALE_16MHZ); delay(10); # endif /* ENABLE_3V3FIX */ # if !defined(ENABLE_SERIAL) && defined(ENABLE_UPDATER) ClockedSerial.begin(UPD_BAUD); printVersionToSerial(); ClockedSerial.flush(); # endif /* ENABLE_UPDATER */ // Read current folder number out of the EEPROM EEPROM_readAnything(0, foldern); if (foldern < 0) foldern = 0; # ifdef ENABLE_LCD display.begin(); display.setContrast(40); display.setFont(u8g2_font_haxrcorp4089_tr); # endif /* ENABLE_LCD */ # ifdef ENABLE_NEOPIXEL pixels.begin(); setColor_RGB(0, 0, 100); // Set TX0 LED Pin(PE1) to Output for status indication during flashing for HW4 # if !(defined(ENABLE_SERIAL) || defined(HW5)) DDRE |= (1 << 1); # endif /* ENABLE_SERIAL */ # else /* !ENABLE_NEOPIXEL */ # ifndef ENABLE_LCD # ifdef ENABLE_CA_LED // Turn LED off digitalWrite(12, 1); digitalWrite(11, 1); digitalWrite(10, 1); # endif /* ENABLE_CA_LED */ // Configure 4 Pin RGB LED pins as output DDRB |= (1 << DDB6); // Red LED (pin 12) DDRB |= (1 << DDB5); // Green LED (pin 11) DDRB |= (1 << DDB4); // Blue LED (pin 10) # endif /* ENABLE_LCD */ # endif /* ENABLE_NEOPIXEL */ # ifdef ENABLE_RTC // Start RTC RTCStart(); // Set Date/Time Callback Funtion SdFile::dateTimeCallback(dateTime); # endif /* ENABLE_RTC */ # ifdef ENABLE_OLED display.begin(); //isplay.setContrast(40); display.setFont(u8g2_font_haxrcorp4089_tr); # endif /* ENABLE_OLED */ # ifdef ENABLE_SERIAL // Serial Begin Serial.begin(9600); Serial.println(FS(FSTRING_EMPTY)); Serial.println(F("Cartridge Reader")); Serial.println(F("2024 github.com/sanni")); // LED Error setColor_RGB(0, 0, 255); # endif /* ENABLE_SERIAL */ // Init SD card if (!sd.begin(SS)) { display_Clear(); # ifdef ENABLE_VSELECT print_STR(sd_error_STR, 1); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("Press button to enable 5V for")); println_Msg(F(" updating firmware...")); display_Update(); wait(); display_Clear(); setVoltage(VOLTS_SET_5V); // Set voltage high for flashing println_Msg(F(" ======== UPDATE MODE ======== ")); println_Msg(F("Waiting for update...")); println_Msg(FS(FSTRING_EMPTY)); println_Msg(F("Press button to cancel/restart.")); display_Update(); wait(); resetArduino(); # else /* !ENABLE_VSELECT */ print_FatalError(sd_error_STR); # endif /* ENABLE_VSELECT */ } # if defined(ENABLE_CONFIG) configInit(); # if defined(ENABLE_GLOBAL_LOG) loggingEnabled = !!configGetLong(F("oscr.logging"), 1); # endif /*ENABLE_CONFIG*/ // Change LCD background if config specified # ifdef ENABLE_NEOPIXEL setColor_RGB(0, 0, 100); # endif /* ENABLE_NEOPIXEL */ # endif /* ENABLE_CONFIG */ # ifdef ENABLE_GLOBAL_LOG if (!myLog.open("OSCR_LOG.txt", O_RDWR | O_CREAT | O_APPEND)) { print_FatalError(sd_error_STR); } println_Msg(FS(FSTRING_EMPTY)); # if defined(HW1) print_Msg(F("OSCR HW1")); # elif defined(HW2) print_Msg(F("OSCR HW2")); # elif defined(HW3) print_Msg(F("OSCR HW3")); # elif defined(HW4) print_Msg(F("OSCR HW4")); # elif defined(HW5) print_Msg(F("OSCR HW5")); # elif defined(SERIAL_MONITOR) print_Msg(F("OSCR Serial")); # endif /* HWn */ print_Msg(FS(FSTRING_SPACE)); println_Msg(FS(FSTRING_VERSION)); # endif /* ENABLE_GLOBAL_LOG */ // Turn status LED on statusLED(true); # if defined(ENABLE_3V3FIX) setClockScale(CLKSCALE_8MHZ); // Set clock back to low after setup # endif /* ENABLE_3V3FIX */ // Start menu system mainMenu(); } /****************************************** Common I/O Functions *****************************************/ // Switch data pins to write void dataOut() { DDRC = 0xFF; } // Switch data pins to read void dataIn() { // Set to Input and activate pull-up resistors DDRC = 0x00; // Pullups PORTC = 0xFF; } /****************************************** Helper Functions *****************************************/ // Set RGB color void setColor_RGB(byte r, byte g, byte b) { #if defined(ENABLE_NEOPIXEL) # if defined(ENABLE_3V3FIX) if (clock == CS_8MHZ) return; # endif // Dim Neopixel LEDs if (r >= 100) r = 100; if (g >= 100) g = 100; if (b >= 100) b = 100; pixels.clear(); # if defined(ENABLE_CONFIG) uint8_t lcdConfColor = configGetLong(F("lcd.confColor")); if (lcdConfColor > 0) { uint8_t lcdRed = configGetLong(F("lcd.red")); uint8_t lcdGreen = configGetLong(F("lcd.green")); uint8_t lcdBlue = configGetLong(F("lcd.blue")); pixels.setPixelColor(0, pixels.Color(lcdGreen, lcdRed, lcdBlue)); } else { pixels.setPixelColor(0, pixels.Color(OPTION_LCD_BG_COLOR)); } # else /* !ENABLE_CONFIG */ pixels.setPixelColor(0, pixels.Color(OPTION_LCD_BG_COLOR)); # endif /* ENABLE_CONFIG */ pixels.setPixelColor(1, pixels.Color(g, r, b)); pixels.setPixelColor(2, pixels.Color(g, r, b)); pixels.show(); #elif defined(ENABLE_CA_LED) // Set color of analog 4 Pin common anode RGB LED analogWrite(12, 255 - r); analogWrite(11, 255 - g); analogWrite(10, 255 - b); #else // Set color of analog 4 Pin common cathode RGB LED analogWrite(12, r); analogWrite(11, g); analogWrite(10, b); #endif } // Extract ASCII printable characters from input, collapsing underscores and spaces. // Use when extracting titles from cartridges, to build a rom title. byte buildRomName(char* output, const byte* input, byte length) { byte input_char; byte output_len = 0; for (unsigned int i = 0; i < length; i++) { input_char = input[i]; if (isprint(input_char) && input_char != '<' && input_char != '>' && input_char != ':' && input_char != '"' && input_char != '/' && input_char != '\\' && input_char != '|' && input_char != '?' && input_char != '*') { output[output_len++] = input_char; } else { if (output_len == 0 || output[output_len - 1] != '_') { output[output_len++] = '_'; } } } while ( output_len && (output[output_len - 1] == '_' || output[output_len - 1] == ' ')) { output_len--; } output[output_len] = 0; return output_len; } // Converts a progmem array into a ram array void convertPgm(const char* const pgmOptions[], byte numArrays) { for (int i = 0; i < numArrays; i++) { strlcpy_P(menuOptions[i], (char*)pgm_read_word(&(pgmOptions[i])), 20); } } void _print_Error(void) { errorLvl = 1; setColor_RGB(255, 0, 0); display_Update(); } void print_Error(const __FlashStringHelper* errorMessage) { println_Msg(errorMessage); _print_Error(); } void print_Error(byte errorMessage) { print_STR(errorMessage, 1); _print_Error(); } void _print_FatalError(void) { println_Msg(FS(FSTRING_EMPTY)); print_STR(press_button_STR, 1); display_Update(); wait(); resetArduino(); } void print_FatalError(const __FlashStringHelper* errorMessage) { print_Error(errorMessage); _print_FatalError(); } void print_FatalError(byte errorMessage) { print_Error(errorMessage); _print_FatalError(); } void wait() { // Switch status LED off statusLED(false); #if defined(ENABLE_LCD) wait_btn(); #elif defined(ENABLE_OLED) wait_btn(); #elif defined(ENABLE_SERIAL) wait_serial(); #endif } #ifdef ENABLE_GLOBAL_LOG // Copies the last part of the current log file to the dump folder void save_log() { // Last found position uint64_t lastPosition = 0; // Go to first line of log myLog.rewind(); // Find location of OSCR string to determine start of current log char tempStr[5]; while (myLog.available()) { // Read first 4 chars of line tempStr[0] = myLog.read(); // Check if it's an empty line if (tempStr[0] == '\r') { // skip \n myLog.read(); } else { // Read more lines tempStr[1] = myLog.read(); tempStr[2] = myLog.read(); tempStr[3] = myLog.read(); tempStr[4] = '\0'; char str_buf; // Skip rest of line while (myLog.available()) { str_buf = myLog.read(); //break out of loop if CRLF is found if (str_buf == '\r') { myLog.read(); //dispose \n because \r\n break; } } // If string is OSCR remember position in file and test if it's the lastest log entry if (strncmp(tempStr, "OSCR", 4) == 0) { // Check if current position is newer as old position if (myLog.position() > lastPosition) { lastPosition = myLog.position(); } } } } // Go to position of last log entry myLog.seek(lastPosition - 16); // Copy log from there to dump dir sd.chdir(folder); strcpy(fileName, romName); strcat(fileName, ".txt"); if (!myFile.open(fileName, O_RDWR | O_CREAT)) { print_FatalError(sd_error_STR); } while (myLog.available()) { if (myLog.available() >= 512) { for (word i = 0; i < 512; i++) { sdBuffer[i] = myLog.read(); } myFile.write(sdBuffer, 512); } else { int i = 0; for (; i < myLog.available(); i++) { sdBuffer[i] = myLog.read(); } myFile.write(sdBuffer, i); } } // Close the file: myFile.close(); } #endif #ifdef ENABLE_GLOBAL_LOG void println_Log(const __FlashStringHelper* string) { myLog.println(string); } #endif void print_Msg(const __FlashStringHelper* string) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(string); #endif #ifdef ENABLE_SERIAL Serial.print(string); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(string); #endif } void print_Msg(const char myString[]) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) // test for word wrap if ((display.tx + strlen(myString) * 6) > 128) { unsigned int strPos = 0; // Print until end of display while (display.tx < 122) { display.print(myString[strPos]); strPos++; } // Newline display.setCursor(0, display.ty + 8); // Print until end of display and ignore remaining characters while ((strPos < strlen(myString)) && (display.tx < 122)) { display.print(myString[strPos]); strPos++; } } else { display.print(myString); } #endif #ifdef ENABLE_SERIAL Serial.print(myString); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(myString); #endif } void print_Msg(long unsigned int message) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message); #endif #ifdef ENABLE_SERIAL Serial.print(message); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(message); #endif } void print_Msg(byte message, int outputFormat) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message, outputFormat); #endif #ifdef ENABLE_SERIAL Serial.print(message, outputFormat); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(message, outputFormat); #endif } void print_Msg(word message, int outputFormat) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message, outputFormat); #endif #ifdef ENABLE_SERIAL Serial.print(message, outputFormat); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(message, outputFormat); #endif } void print_Msg(int message, int outputFormat) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message, outputFormat); #endif #ifdef ENABLE_SERIAL Serial.print(message, outputFormat); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(message, outputFormat); #endif } void print_Msg(long unsigned int message, int outputFormat) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message, outputFormat); #endif #ifdef ENABLE_SERIAL Serial.print(message, outputFormat); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(message, outputFormat); #endif } void print_Msg(String string) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(string); #endif #ifdef ENABLE_SERIAL Serial.print(string); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.print(string); #endif } void print_Msg_PaddedHexByte(byte message) { if (message < 16) print_Msg(0, HEX); print_Msg(message, HEX); } void print_Msg_PaddedHex16(word message) { print_Msg_PaddedHexByte((message >> 8) & 0xFF); print_Msg_PaddedHexByte((message >> 0) & 0xFF); } void print_Msg_PaddedHex32(unsigned long message) { print_Msg_PaddedHexByte((message >> 24) & 0xFF); print_Msg_PaddedHexByte((message >> 16) & 0xFF); print_Msg_PaddedHexByte((message >> 8) & 0xFF); print_Msg_PaddedHexByte((message >> 0) & 0xFF); } void println_Msg(String string) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(string); display.setCursor(0, display.ty + 8); #endif #ifdef ENABLE_SERIAL Serial.println(string); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.println(string); #endif } void println_Msg(byte message, int outputFormat) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message, outputFormat); display.setCursor(0, display.ty + 8); #endif #ifdef ENABLE_SERIAL Serial.println(message, outputFormat); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.println(message, outputFormat); #endif } void println_Msg(const char myString[]) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) // test for word wrap if ((display.tx + strlen(myString) * 6) > 128) { unsigned int strPos = 0; // Print until end of display while ((display.tx < 122) && (myString[strPos] != '\0')) { display.print(myString[strPos]); strPos++; } // Newline display.setCursor(0, display.ty + 8); // Print until end of display and ignore remaining characters while ((strPos < strlen(myString)) && (display.tx < 122) && (myString[strPos] != '\0')) { display.print(myString[strPos]); strPos++; } } else { display.print(myString); } display.setCursor(0, display.ty + 8); #endif #ifdef ENABLE_SERIAL Serial.println(myString); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.println(myString); #endif } void println_Msg(const __FlashStringHelper* string) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(string); display.setCursor(0, display.ty + 8); #endif #ifdef ENABLE_SERIAL Serial.println(string); #endif #ifdef ENABLE_GLOBAL_LOG char myBuffer[15]; strlcpy_P(myBuffer, (char*)string, 15); if ((strncmp(myBuffer, "Press Button...", 14) != 0) && (strncmp(myBuffer, "Select file", 10) != 0)) { if (!dont_log && loggingEnabled) myLog.println(string); } #endif } void println_Msg(long unsigned int message) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.print(message); display.setCursor(0, display.ty + 8); #endif #ifdef ENABLE_SERIAL Serial.println(message); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.println(message); #endif } void display_Update() { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.updateDisplay(); #endif #ifdef ENABLE_SERIAL delay(100); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.flush(); #endif } void display_Clear() { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.clearDisplay(); display.setCursor(0, 8); #endif #ifdef ENABLE_GLOBAL_LOG if (!dont_log && loggingEnabled) myLog.println(FS(FSTRING_EMPTY)); #endif } void display_Clear_Slow() { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) display.setDrawColor(0); for (byte y = 0; y < 64; y++) { display.drawLine(0, y, 128, y); } display.setDrawColor(1); display.setCursor(0, 8); #endif } /****************************************** RGB LED *****************************************/ void rgbLed(byte Color) { switch (Color) { case blue_color: setColor_RGB(0, 0, 255); break; case red_color: setColor_RGB(255, 0, 0); break; case purple_color: setColor_RGB(255, 0, 255); break; case green_color: setColor_RGB(0, 255, 0); break; case turquoise_color: setColor_RGB(0, 255, 255); break; case yellow_color: setColor_RGB(255, 255, 0); break; case white_color: setColor_RGB(255, 255, 255); break; } } void blinkLED() { #if defined(ENABLE_VSELECT) // Nothing #elif defined(HW5) PORTD ^= (1 << 7); #elif defined(ENABLE_OLED) PORTB ^= (1 << 4); #elif defined(ENABLE_LCD) PORTE ^= (1 << 1); #elif defined(ENABLE_SERIAL) PORTB ^= (1 << 4); PORTB ^= (1 << 7); #endif } #if defined(HW5) && !defined(ENABLE_VSELECT) void statusLED(boolean on) { if (!on) PORTD |= (1 << 7); else PORTD &= ~(1 << 7); } #else void statusLED(boolean on __attribute__((unused))) { } #endif /****************************************** Menu system *****************************************/ unsigned char question_box(const __FlashStringHelper* question, char answers[7][20], uint8_t num_answers, uint8_t default_choice) { #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) return questionBox_Display(question, answers, num_answers, default_choice); #endif #ifdef ENABLE_SERIAL return questionBox_Serial(question, answers, num_answers, default_choice); #endif } #if defined(ENABLE_SERIAL) // Serial Monitor byte questionBox_Serial(const __FlashStringHelper* question, char answers[7][20], uint8_t num_answers, uint8_t default_choice) { // Print menu to serial monitor Serial.println(FS(FSTRING_EMPTY)); for (byte i = 0; i < num_answers; i++) { Serial.print(i); Serial.print(F(")")); Serial.println(answers[i]); } // Wait for user input Serial.println(FS(FSTRING_EMPTY)); Serial.println(F("Please browse pages with 'u'(up) and 'd'(down)")); Serial.println(F("and enter a selection by typing a number(0-6): _ ")); Serial.println(FS(FSTRING_EMPTY)); while (Serial.available() == 0) { } // Read the incoming byte: incomingByte = Serial.read() - 48; // Page up (u) if (incomingByte == 69) { if (currPage > 1) { lastPage = currPage; currPage--; } else { root = 1; } } // Page down (d) else if (incomingByte == 52) { if (numPages > currPage) { lastPage = currPage; currPage++; } } // Execute choice else if ((incomingByte >= 0) && (incomingByte < 7)) { numPages = 0; } // Print the received byte for validation e.g. in case of a different keyboard mapping //Serial.println(incomingByte); //Serial.println(FS(FSTRING_EMPTY)); return incomingByte; } #endif // OLED & LCD #if (defined(ENABLE_LCD) || defined(ENABLE_OLED)) // Display a question box with selectable answers. Make sure default choice is in (0, num_answers] unsigned char questionBox_Display(const __FlashStringHelper* question, char answers[7][20], uint8_t num_answers, uint8_t default_choice) { //clear the screen display.clearDisplay(); display.updateDisplay(); display.setCursor(0, 8); display.setDrawColor(1); // change the rgb led to the start menu color rgbLed(default_choice); // print menu display.println(question); display.setCursor(0, display.ty + 8); for (unsigned char i = 0; i < num_answers; i++) { // Add space for the selection dot display.print(" "); // Print menu item display.println(answers[i]); display.setCursor(0, display.ty + 8); } display.updateDisplay(); // start with the default choice choice = default_choice; // draw selection box display.drawBox(1, 8 * choice + 11, 3, 3); display.updateDisplay(); unsigned long idleTime = millis(); byte currentColor = 0; // wait until user makes his choice while (1) { // Attract Mode if (millis() - idleTime > 300000) { if ((millis() - idleTime) % 4000 == 0) { if (currentColor < 7) { currentColor++; if (currentColor == 1) { currentColor = 2; // skip red as that signifies an error to the user } } else { currentColor = 0; } } rgbLed(currentColor); } /* Check Button/rotary encoder 1 click/clockwise rotation 2 doubleClick/counter clockwise rotation 3 hold/press 4 longHold */ uint8_t b = checkButton(); // if button is pressed twice or rotary encoder turned left/counter clockwise if (b == 2) { idleTime = millis(); // remove selection box display.setDrawColor(0); display.drawBox(1, 8 * choice + 11, 3, 3); display.setDrawColor(1); display.updateDisplay(); // If cursor on top list entry if (choice == 0) { // On 2nd, 3rd, ... page go back one page if (currPage > 1) { lastPage = currPage; currPage--; break; } // In file browser go to root dir else if ((filebrowse == 1) && (root != 1)) { root = 1; break; } // Else go to bottom of list as a shortcut else { choice = num_answers - 1; } } // If not top entry go up/back one entry else { choice--; } // draw selection box display.drawBox(1, 8 * choice + 11, 3, 3); display.updateDisplay(); // change RGB led to the color of the current menu option rgbLed(choice); } // go one down in the menu if the Cart Readers button is clicked shortly if (b == 1) { idleTime = millis(); // remove selection box display.setDrawColor(0); display.drawBox(1, 8 * choice + 11, 3, 3); display.setDrawColor(1); display.updateDisplay(); if ((choice == num_answers - 1) && (numPages > currPage)) { lastPage = currPage; currPage++; break; } else choice = (choice + 1) % num_answers; // draw selection box display.drawBox(1, 8 * choice + 11, 3, 3); display.updateDisplay(); // change RGB led to the color of the current menu option rgbLed(choice); } // if the Cart Readers button is hold continiously leave the menu // so the currently highlighted action can be executed if (b == 3) { idleTime = millis(); // All done numPages = 0; break; } checkUpdater(); } // pass on user choice setColor_RGB(0, 0, 0); #ifdef ENABLE_GLOBAL_LOG println_Msg(FS(FSTRING_EMPTY)); print_Msg(F("[+] ")); println_Msg(answers[choice]); #endif return choice; } #endif #if !defined(ENABLE_SERIAL) && defined(ENABLE_UPDATER) void checkUpdater() { if (ClockedSerial.available() > 0) { String cmd = ClockedSerial.readStringUntil('\n'); cmd.trim(); if (cmd == "VERCHK") { // VERCHK: Gets OSCR version and features delay(500); printVersionToSerial(); } else if (cmd == "GETCLOCK") { // GETCLOCK: Gets the MEGA's current clock speed. # if defined(ENABLE_3V3FIX) ClockedSerial.print(F("Clock is running at ")); ClockedSerial.print((clock == CS_16MHZ) ? 16UL : 8UL); ClockedSerial.println(F("MHz")); # else /* !ENABLE_3V3FIX */ ClockedSerial.println(FS(FSTRING_MODULE_NOT_ENABLED)); # endif /* ENABLE_3V3FIX */ } else if (cmd.substring(1, 8) == "ETVOLTS") { // (G/S)ETVOLTS: Get and set the voltage. # if defined(ENABLE_VSELECT) if (cmd != "GETVOLTS") { switch(cmd.substring(9, 10).toInt()) { case 3: setVoltage(VOLTS_SET_3V3); break; case 5: setVoltage(VOLTS_SET_5V); break; } } ClockedSerial.print(F("Voltage is set to ")); ClockedSerial.print((voltage == VOLTS_SET_5V) ? 5 : 3.3); ClockedSerial.println(F("V")); # else /* !ENABLE_VSELECT */ ClockedSerial.println(FS(FSTRING_MODULE_NOT_ENABLED)); # endif /* ENABLE_VSELECT */ } // RTC commands else if (cmd.substring(1, 7) == "ETTIME") { // (G/S)ETTIME: Get and set the date/time. # if defined(ENABLE_RTC) if (cmd != "GETTIME") { ClockedSerial.println(F("Setting Time...")); rtc.adjust(DateTime(cmd.substring(8).toInt())); } ClockedSerial.print(F("Current Time: ")); ClockedSerial.println(RTCStamp()); # else /* !ENABLE_RTC */ ClockedSerial.println(FS(FSTRING_MODULE_NOT_ENABLED)); # endif /* ENABLE_RTC */ } else { ClockedSerial.print(FS(FSTRING_OSCR)); ClockedSerial.println(F(": Unknown Command")); } } } #else void checkUpdater() {} #endif /****************************************** User Control *****************************************/ // Using Serial Monitor #if defined(ENABLE_SERIAL) uint8_t checkButton() { while (Serial.available() == 0) { } incomingByte = Serial.read() - 48; //Next if (incomingByte == 52) { return 1; } //Previous else if (incomingByte == 69) { return 2; } //Selection else if (incomingByte == 240) { return 3; } return 0; } void wait_serial() { if (errorLvl) { errorLvl = 0; } while (Serial.available() == 0) { } incomingByte = Serial.read() - 48; /* if ((incomingByte == 53) && (fileName[0] != '\0')) { // Open file on sd card sd.chdir(folder); if (myFile.open(fileName, O_READ)) { // Get rom size from file fileSize = myFile.fileSize(); // Send filesize char tempStr[16]; sprintf(tempStr, "%d", fileSize); Serial.write(tempStr); // Wait for ok while (Serial.available() == 0) { } // Send file for (unsigned long currByte = 0; currByte < fileSize; currByte++) { // Blink led if (currByte % 1024 == 0) blinkLED(); Serial.write(myFile.read()); } // Close the file: myFile.close(); } else { print_FatalError(open_file_STR); } }*/ } #endif // Using one or two push buttons (HW1/HW2/HW3) #if defined(ENABLE_OLED) // Read button state uint8_t checkButton() { #ifdef ENABLE_BUTTON2 byte eventButton2 = checkButton2(); if ((eventButton2 > 0) && (eventButton2 < 2)) return 3; else if (eventButton2 > 2) return 4; #endif return (checkButton1()); } // Read button 1 uint8_t checkButton1() { uint8_t event = 0; // Read the state of the button (PD7) buttonVal1 = (PIND & (1 << 7)); // Button pressed down if (buttonVal1 == LOW && buttonLast1 == HIGH && (millis() - upTime1) > debounce) { downTime1 = millis(); ignoreUp1 = false; waitForUp1 = false; singleOK1 = true; holdEventPast1 = false; longholdEventPast1 = false; if ((millis() - upTime1) < DCgap && DConUp1 == false && DCwaiting1 == true) DConUp1 = true; else DConUp1 = false; DCwaiting1 = false; } // Button released else if (buttonVal1 == HIGH && buttonLast1 == LOW && (millis() - downTime1) > debounce) { if (!ignoreUp1) { upTime1 = millis(); if (DConUp1 == false) DCwaiting1 = true; else { event = 2; DConUp1 = false; DCwaiting1 = false; singleOK1 = false; } } } // Test for normal click event: DCgap expired if (buttonVal1 == HIGH && (millis() - upTime1) >= DCgap && DCwaiting1 == true && DConUp1 == false && singleOK1 == true) { event = 1; DCwaiting1 = false; } // Test for hold if (buttonVal1 == LOW && (millis() - downTime1) >= holdTime) { // Trigger "normal" hold if (!holdEventPast1) { event = 3; waitForUp1 = true; ignoreUp1 = true; DConUp1 = false; DCwaiting1 = false; //downTime1 = millis(); holdEventPast1 = true; } // Trigger "long" hold if ((millis() - downTime1) >= longHoldTime) { if (!longholdEventPast1) { event = 4; longholdEventPast1 = true; } } } buttonLast1 = buttonVal1; return event; } // Read button 2 uint8_t checkButton2() { uint8_t event = 0; // Read the state of the button (PG2) buttonVal2 = (PING & (1 << 2)); // Button pressed down if (buttonVal2 == LOW && buttonLast2 == HIGH && (millis() - upTime2) > debounce) { downTime2 = millis(); ignoreUp2 = false; waitForUp2 = false; singleOK2 = true; holdEventPast2 = false; longholdEventPast2 = false; if ((millis() - upTime2) < DCgap && DConUp2 == false && DCwaiting2 == true) DConUp2 = true; else DConUp2 = false; DCwaiting2 = false; } // Button released else if (buttonVal2 == HIGH && buttonLast2 == LOW && (millis() - downTime2) > debounce) { if (!ignoreUp2) { upTime2 = millis(); if (DConUp2 == false) DCwaiting2 = true; else { event = 2; DConUp2 = false; DCwaiting2 = false; singleOK2 = false; } } } // Test for normal click event: DCgap expired if (buttonVal2 == HIGH && (millis() - upTime2) >= DCgap && DCwaiting2 == true && DConUp2 == false && singleOK2 == true) { event = 1; DCwaiting2 = false; } // Test for hold if (buttonVal2 == LOW && (millis() - downTime2) >= holdTime) { // Trigger "normal" hold if (!holdEventPast2) { event = 3; waitForUp2 = true; ignoreUp2 = true; DConUp2 = false; DCwaiting2 = false; //downTime2 = millis(); holdEventPast2 = true; } // Trigger "long" hold if ((millis() - downTime2) >= longHoldTime) { if (!longholdEventPast2) { event = 4; longholdEventPast2 = true; } } } buttonLast2 = buttonVal2; return event; } // Wait for user to push button void wait_btn() { // Change led to green if (errorLvl == 0) rgbLed(green_color); while (1) { // get input button uint8_t b = checkButton(); // if the cart readers input button is pressed shortly if (b == 1) { errorLvl = 0; break; } // if the cart readers input button is pressed long if (b == 3) { if (errorLvl) { errorLvl = 0; } break; } checkUpdater(); } } #endif // Using rotary encoder (HW4/HW5) #if (defined(ENABLE_LCD) && defined(ENABLE_ROTARY)) // Read encoder state uint8_t checkButton() { // Read rotary encoder encoder.tick(); int newPos = encoder.getPosition(); // Read button boolean reading = (PING & (1 << PING2)) >> PING2; // Check if rotary encoder has changed if (rotaryPos != newPos) { int rotaryDir = (int)encoder.getDirection(); rotaryPos = newPos; if (rotaryDir == 1) { return 1; } else if (rotaryDir == -1) { return 2; } } else if (reading != buttonState) { if (reading != lastButtonState) { lastDebounceTime = millis(); lastButtonState = reading; } else if ((millis() - lastDebounceTime) > debounceDelay) { buttonState = reading; // Button was pressed down if (buttonState == 0) { setColor_RGB(0, 0, 0); unsigned long pushTime = millis(); // Wait until button was let go again while ((PING & (1 << PING2)) >> PING2 == 0) { // Signal long press delay reached if ((millis() - pushTime) > 2000) { rgbLed(green_color); } } // 2 second long press if ((millis() - pushTime) > 2000) { return 4; } // normal press else { return 3; } } } } return 0; } // Wait for user to push button void wait_btn() { // Change led to green if (errorLvl == 0) rgbLed(green_color); while (1) { // get input button uint8_t b = checkButton(); // if the cart readers input button is pressed shortly if (b == 1) { errorLvl = 0; break; } // if the cart readers input button is pressed long if (b == 3) { if (errorLvl) { errorLvl = 0; } break; } checkUpdater(); } } // Wait for user to rotate knob void wait_encoder() { // Change led to green if (errorLvl == 0) rgbLed(green_color); while (1) { // Get rotary encoder encoder.tick(); int newPos = encoder.getPosition(); if (rotaryPos != newPos) { rotaryPos = newPos; errorLvl = 0; break; } } } #endif /****************************************** Filebrowser Module *****************************************/ void fileBrowser(const __FlashStringHelper* browserTitle) { char fileNames[7][FILENAME_LENGTH]; int currFile; FsFile myDir; div_t page_layout; filebrowse = 1; // Root filePath[0] = '/'; filePath[1] = '\0'; // Temporary char array for filename char nameStr[FILENAME_LENGTH]; browserstart: // Print title println_Msg(browserTitle); // Set currFile back to 0 currFile = 0; currPage = 1; lastPage = 1; // Open filepath directory if (!myDir.open(filePath)) { display_Clear(); print_FatalError(sd_error_STR); } // Count files in directory while (myFile.openNext(&myDir, O_READ)) { if (!myFile.isHidden() && (myFile.isDir() || myFile.isFile())) { currFile++; } myFile.close(); } myDir.close(); page_layout = div(currFile, 7); numPages = page_layout.quot + 1; // Fill the array "answers" with 7 options to choose from in the file browser char answers[7][20]; page: // If there are less than 7 entries, set count to that number so no empty options appear byte count = currPage == numPages ? page_layout.rem : 7; // Open filepath directory if (!myDir.open(filePath)) { display_Clear(); print_FatalError(sd_error_STR); } int countFile = 0; byte i = 0; // Cycle through all files while ((myFile.openNext(&myDir, O_READ)) && (i < 8)) { // Get name of file myFile.getName(nameStr, FILENAME_LENGTH); // Ignore if hidden if (myFile.isHidden()) { } // Directory else if (myFile.isDir()) { if (countFile == ((currPage - 1) * 7 + i)) { snprintf(fileNames[i], FILENAME_LENGTH, "%s%s", "/", nameStr); i++; } countFile++; } // File else if (myFile.isFile()) { if (countFile == ((currPage - 1) * 7 + i)) { snprintf(fileNames[i], FILENAME_LENGTH, "%s", nameStr); i++; } countFile++; } myFile.close(); } myDir.close(); for (byte i = 0; i < 8; i++) { // Copy short string into fileOptions snprintf(answers[i], FILEOPTS_LENGTH, "%s", fileNames[i]); } // Create menu with title and 1-7 options to choose from unsigned char answer = question_box(browserTitle, answers, count, 0); // Check if the page has been switched if (currPage != lastPage) { lastPage = currPage; goto page; } // Check if we are supposed to go back to the root dir if (root) { // Change working dir to root filePath[0] = '/'; filePath[1] = '\0'; sd.chdir("/"); // Start again root = 0; goto browserstart; } // wait for user choice to come back from the question box menu switch (answer) { case 0: strncpy(fileName, fileNames[0], FILENAME_LENGTH - 1); break; case 1: strncpy(fileName, fileNames[1], FILENAME_LENGTH - 1); break; case 2: strncpy(fileName, fileNames[2], FILENAME_LENGTH - 1); break; case 3: strncpy(fileName, fileNames[3], FILENAME_LENGTH - 1); break; case 4: strncpy(fileName, fileNames[4], FILENAME_LENGTH - 1); break; case 5: strncpy(fileName, fileNames[5], FILENAME_LENGTH - 1); break; case 6: strncpy(fileName, fileNames[6], FILENAME_LENGTH - 1); break; //case 7: // File import //break; } // Add directory to our filepath if we just entered a new directory if (fileName[0] == '/') { // add dirname to path strcat(filePath, fileName); // Remove / from dir name char* dirName = fileName + 1; // Change working dir sd.chdir(dirName); // Start browser in new directory again goto browserstart; } else { // Afer everything is done change SD working directory back to root sd.chdir("/"); } filebrowse = 0; } /****************************************** Main loop *****************************************/ void loop() { switch (mode) { #ifdef ENABLE_N64 case CORE_N64_CART: return n64CartMenu(); case CORE_N64_CONTROLLER: return n64ControllerMenu(); #endif #ifdef ENABLE_SNES case CORE_SNES: return snesMenu(); #endif #ifdef ENABLE_SFM case CORE_SFM: return sfmMenu(); # ifdef ENABLE_FLASH case CORE_SFM_FLASH: return sfmFlashMenu(); # endif case CORE_SFM_GAME: return sfmGameOptions(); #endif #ifdef ENABLE_GBX case CORE_GB: return gbMenu(); case CORE_GBA: return gbaMenu(); case CORE_GBM: return gbmMenu(); case CORE_GB_GBSMART: return gbSmartMenu(); case CORE_GB_GBSMART_FLASH: return gbSmartFlashMenu(); case CORE_GB_GBSMART_GAME: return gbSmartGameOptions(); #endif #ifdef ENABLE_FLASH case CORE_FLASH8: return flashromMenu8(); # ifdef ENABLE_FLASH16 case CORE_FLASH16: return flashromMenu16(); case CORE_EPROM: return epromMenu(); # endif #endif #ifdef ENABLE_MD case CORE_MD_CART: return mdCartMenu(); case CORE_SEGA_CD: return segaCDMenu(); #endif #ifdef ENABLE_PCE case CORE_PCE: return pceMenu(); #endif #ifdef ENABLE_SV case CORE_SV: return svMenu(); #endif #ifdef ENABLE_NES case CORE_NES: return nesMenu(); #endif #ifdef ENABLE_SMS case CORE_SMS: return smsMenu(); #endif #ifdef ENABLE_WS case CORE_WS: return wsMenu(); #endif #ifdef ENABLE_NGP case CORE_NGP: return ngpMenu(); #endif #ifdef ENABLE_INTV case CORE_INTV: return intvMenu(); #endif #ifdef ENABLE_COLV case CORE_COL: return colMenu(); #endif #ifdef ENABLE_VBOY case CORE_VBOY: return vboyMenu(); #endif #ifdef ENABLE_WSV case CORE_WSV: return wsvMenu(); #endif #ifdef ENABLE_PCW case CORE_PCW: return pcwMenu(); #endif #ifdef ENABLE_ODY2 case CORE_ODY2: return ody2Menu(); #endif #ifdef ENABLE_ARC case CORE_ARC: return arcMenu(); #endif #ifdef ENABLE_FAIRCHILD case CORE_FAIRCHILD: return fairchildMenu(); #endif #ifdef ENABLE_SUPRACAN case CORE_SUPRACAN: return suprAcanMenu(); #endif #ifdef ENABLE_MSX case CORE_MSX: return msxMenu(); #endif #ifdef ENABLE_POKE case CORE_POKE: return pokeMenu(); #endif #ifdef ENABLE_LOOPY case CORE_LOOPY: return loopyMenu(); #endif #ifdef ENABLE_C64 case CORE_C64: return c64Menu(); #endif #ifdef ENABLE_2600 case CORE_2600: return a2600Menu(); #endif #ifdef ENABLE_5200 case CORE_5200: return a5200Menu(); #endif #ifdef ENABLE_7800 case CORE_7800: return a7800Menu(); #endif #ifdef ENABLE_VECTREX case CORE_VECTREX: return vectrexMenu(); #endif #ifdef ENABLE_ST case CORE_ST: return stMenu(); #endif #ifdef ENABLE_GPC case CORE_GPC: return gpcMenu(); #endif case CORE_MAX: return resetArduino(); } } //****************************************** // End of File //******************************************