From 05deb817ace8faeea4622c9d8e0bcf88164d3ec5 Mon Sep 17 00:00:00 2001 From: sanni Date: Sun, 24 Oct 2021 00:41:18 +0200 Subject: [PATCH] V7.0: Add basic support for MKS MINI12864 V3 https://www.aliexpress.com/item/1005003098864693.html --- Cart_Reader/Cart_Reader.ino | 480 ++++++++++++++++++++++++++++++------ Cart_Reader/N64.ino | 15 +- Cart_Reader/NES.ino | 14 +- Cart_Reader/NP.ino | 2 +- Cart_Reader/README.md | 13 +- Cart_Reader/RTC.cpp | 5 + Cart_Reader/RTC.h | 4 + Cart_Reader/SMS.ino | 4 +- Cart_Reader/SNES.ino | 5 +- Cart_Reader/options.h | 38 ++- pinout.ods | Bin 19767 -> 20660 bytes 11 files changed, 483 insertions(+), 97 deletions(-) diff --git a/Cart_Reader/Cart_Reader.ino b/Cart_Reader/Cart_Reader.ino index 5015a1f..9c4d587 100644 --- a/Cart_Reader/Cart_Reader.ino +++ b/Cart_Reader/Cart_Reader.ino @@ -4,15 +4,19 @@ This project represents a community-driven effort to provide an easy to build and easy to modify cartridge dumper. - Date: 14.10.2021 - Version: 6.8 + Date: 24.10.2021 + Version: 7.0 SD lib: https://github.com/greiman/SdFat - LCD lib: https://github.com/adafruit/Adafruit_SSD1306 + OLED lib: https://github.com/adafruit/Adafruit_SSD1306 GFX Lib: https://github.com/adafruit/Adafruit-GFX-Library BusIO: https://github.com/adafruit/Adafruit_BusIO + LCD lib: https://github.com/olikraus/u8g2 RGB Tools lib: https://github.com/joushx/Arduino-RGB-Tools + 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 Compiled with Arduino 1.8.13 @@ -33,14 +37,14 @@ Gens-gs - Megadrive checksum And a special Thank You to all coders and contributors on Github and the Arduino forum: - jiyunomegami, splash5, Kreeblah, ramapcsx2, PsyK0p4T, Dakkaron, Pickle, sdhizumi, - sakman55, Uzlopak, scrap-a, majorpbx, borti4938, Modman, philenotfound, vogelfreiheit + jiyunomegami, splash5, Kreeblah, ramapcsx2, PsyK0p4T, Dakkaron, majorpbx, Pickle, sdhizumi, + Uzlopak, sakman55, scrap-a, borti4938, vogelfreiheit, Modman, philenotfound And to nocash for figuring out the secrets of the SFC Nintendo Power cartridge. **********************************************************************************/ -char ver[5] = "6.8"; +char ver[5] = "7.0"; /****************************************** Libraries @@ -48,18 +52,18 @@ char ver[5] = "6.8"; // Options #include "options.h" -// SD Card -#include "SdFat.h" -SdFs sd; -FsFile myDir; -FsFile myFile; - // Basic Libs #include #include #include #include +// SD Card +#include "SdFat.h" +SdFs sd; +FsFile myDir; +FsFile myFile; + // AVR Eeprom #include // forward declarations for "T" (for non Arduino IDE) @@ -82,24 +86,34 @@ template int EEPROM_readAnything(int ee, T& value) { return i; } -// Graphic I2C LCD -#include -#include -#define SCREEN_WIDTH 128 // OLED display width, in pixels -#define SCREEN_HEIGHT 64 // OLED display height, in pixels -// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) -#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) -Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +// Graphic SPI LCD +#ifdef enable_LCD +#include +U8G2_ST7567_OS12864_F_4W_HW_SPI display(U8G2_R2, /* cs=*/ 12, /* dc=*/ 11, /* reset=*/ 10); +#endif -// Adafruit Clock Generator -#include -Si5351 clockgen; +// Rotary Encoder +#ifdef enable_rotary +#include +#define PIN_IN1 18 +#define PIN_IN2 19 +RotaryEncoder encoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::FOUR3); +int rotaryPos = 0; +#endif -// RGB LED +// Choose RGB LED type +#ifdef enable_neopixel +// Neopixel +#include +Adafruit_NeoPixel pixels(3, 13, NEO_GRB + NEO_KHZ800); +#else +#ifndef enable_LCD +// 4 Pin RGB LED #include - // Set pins of red, green and blue RGBTools rgb(12, 11, 10); +#endif +#endif typedef enum COLOR_T { blue_color, @@ -111,6 +125,21 @@ typedef enum COLOR_T { white_color, } color_t; +// Graphic I2C OLED +#ifdef enable_OLED +#include +#include +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) +#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +#endif + +// Adafruit Clock Generator +#include +Si5351 clockgen; + // RTC Library #ifdef RTC_installed #include "RTC.h" @@ -156,6 +185,14 @@ typedef enum COLOR_T { /****************************************** 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 @@ -181,7 +218,9 @@ boolean ignoreUp2 = false; // whether to ignore the button release because the c 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 -#else +#endif + +#ifdef enable_serial // For incoming serial data int incomingByte; #endif @@ -437,7 +476,9 @@ static const char* const addonsOptions[] PROGMEM = {addonsItem1, addonsItem2, ad void aboutScreen() { display_Clear(); // Draw the Logo +#ifdef enable_OLED display.drawBitmap(0, 0, sig, 128, 64, 1); +#endif println_Msg(F("Cartridge Reader")); println_Msg(F("github.com/sanni")); print_Msg(F("2021 Version ")); @@ -450,7 +491,7 @@ void aboutScreen() { display_Update(); while (1) { -#ifdef enable_OLED +#if defined(enable_OLED) || defined(enable_LCD) // get input button int b = checkButton(); @@ -474,11 +515,12 @@ void aboutScreen() { EEPROM_writeAnything(0, foldern); resetArduino(); } -#else +#endif +#ifdef enable_serial wait_serial(); resetArduino(); #endif - rgb.setColor(random(0, 255), random(0, 255), random(0, 255)); + setColor_RGB(random(0, 255), random(0, 255), random(0, 255)); delay(random(50, 100)); } } @@ -643,6 +685,20 @@ void setup() { // Read current folder number out of eeprom EEPROM_readAnything(0, foldern); +#ifdef enable_LCD + display.begin(); + display.setFont(u8g2_font_haxrcorp4089_tr); +#endif + +#ifdef enable_neopixel + pixels.begin(); + pixels.clear(); + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.setPixelColor(1, pixels.Color(0, 0, 255)); + pixels.setPixelColor(2, pixels.Color(0, 0, 255)); + pixels.show(); +#endif + #ifdef enable_OLED // GLCD display.begin(SSD1306_SWITCHCAPVCC, 0x3C); @@ -661,7 +717,7 @@ void setup() { delay(100); // Initialize LED - rgb.setColor(0, 0, 0); + setColor_RGB(0, 0, 0); // Clear the screen. display_Clear(); @@ -693,31 +749,24 @@ void setup() { display_Update(); delay(200); #endif +#endif -#else +#ifdef enable_serial // Serial Begin Serial.begin(9600); Serial.println(F("Cartridge Reader")); Serial.println(F("2021 sanni")); Serial.println(""); // LED Error - rgb.setColor(0, 0, 255); + setColor_RGB(0, 0, 255); #endif // Init SD card - if (!sd.begin(SdSpiConfig(SS, DEDICATED_SPI))) { + if (!sd.begin(SS)) { display_Clear(); print_Error(F("SD Error"), true); } -#ifndef enable_OLED - // Print SD Info - Serial.print(F("SD Card: ")); - Serial.print(sd.card()->cardSize() * 512E-9); - Serial.print(F("GB FAT")); - Serial.println(int(sd.vol()->fatType())); -#endif - #ifdef RTC_installed // Start RTC RTCStart(); @@ -748,6 +797,19 @@ void dataIn() { /****************************************** Helper Functions *****************************************/ +// Set RGB color +void setColor_RGB(byte r, byte g, byte b) { +#ifdef enable_neopixel + pixels.clear(); + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.setPixelColor(1, pixels.Color(g, r, b)); + pixels.setPixelColor(2, pixels.Color(g, r, b)); + pixels.show(); +#else + rgb.setColor(r, g, b); +#endif +} + // Converts a progmem array into a ram array void convertPgm(const char* const pgmOptions[], byte numArrays) { for (int i = 0; i < numArrays; i++) { @@ -757,12 +819,12 @@ void convertPgm(const char* const pgmOptions[], byte numArrays) { void print_Error(const __FlashStringHelper *errorMessage, boolean forceReset) { errorLvl = 1; - rgb.setColor(255, 0, 0); + setColor_RGB(255, 0, 0); println_Msg(errorMessage); display_Update(); if (forceReset) { -#ifdef enable_OLED +#if defined(enable_OLED) || defined(enable_LCD) println_Msg(F("")); println_Msg(F("Press Button...")); display_Update(); @@ -780,7 +842,8 @@ void print_Error(const __FlashStringHelper *errorMessage, boolean forceReset) { display_Update(); delay(2000); } -#else +#endif +#ifdef enable_serial println_Msg(F("Fatal Error, please reset")); while (1); #endif @@ -788,49 +851,73 @@ void print_Error(const __FlashStringHelper *errorMessage, boolean forceReset) { } void wait() { +#ifdef enable_LCD + wait_encoder(); +#endif #ifdef enable_OLED wait_btn(); -#else +#endif +#ifdef enable_serial wait_serial(); #endif } void print_Msg(const __FlashStringHelper *string) { +#ifdef enable_LCD + display.print(string); +#endif #ifdef enable_OLED display.print(string); -#else +#endif +#ifdef enable_serial Serial.print(string); #endif } void print_Msg(const char string[]) { +#ifdef enable_LCD + display.print(string); +#endif #ifdef enable_OLED display.print(string); -#else +#endif +#ifdef enable_serial Serial.print(string); #endif } void print_Msg(long unsigned int message) { +#ifdef enable_LCD + display.print(message); +#endif #ifdef enable_OLED display.print(message); -#else +#endif +#ifdef enable_serial Serial.print(message); #endif } void print_Msg(byte message, int outputFormat) { +#ifdef enable_LCD + display.print(message, outputFormat); +#endif #ifdef enable_OLED display.print(message, outputFormat); -#else +#endif +#ifdef enable_serial Serial.print(message, outputFormat); #endif } void print_Msg(String string) { +#ifdef enable_LCD + display.print(string); +#endif #ifdef enable_OLED display.print(string); -#else +#endif +#ifdef enable_serial Serial.print(string); #endif } @@ -852,56 +939,88 @@ void print_Msg_PaddedHex32(unsigned long message) { print_Msg_PaddedHexByte((message >> 0) & 0xFF); } - void println_Msg(String string) { +#ifdef enable_LCD + display.println(string); + display.setCursor(0, display.ty + 8); +#endif #ifdef enable_OLED display.println(string); -#else +#endif +#ifdef enable_serial Serial.println(string); #endif } void println_Msg(byte message, int outputFormat) { +#ifdef enable_LCD + display.println(message, outputFormat); + display.setCursor(0, display.ty + 8); +#endif #ifdef enable_OLED display.println(message, outputFormat); -#else +#endif +#ifdef enable_serial Serial.println(message, outputFormat); #endif } void println_Msg(const char message[]) { +#ifdef enable_LCD + display.println(message); + display.setCursor(0, display.ty + 8); +#endif #ifdef enable_OLED display.println(message); -#else +#endif +#ifdef enable_serial Serial.println(message); #endif } void println_Msg(const __FlashStringHelper *string) { +#ifdef enable_LCD + display.println(string); + display.setCursor(0, display.ty + 8); +#endif #ifdef enable_OLED display.println(string); -#else +#endif +#ifdef enable_serial Serial.println(string); #endif } void println_Msg(long unsigned int message) { +#ifdef enable_LCD + display.print(message); + display.setCursor(0, display.ty + 8); +#endif #ifdef enable_OLED display.println(message); -#else +#endif +#ifdef enable_serial Serial.println(message); #endif } void display_Update() { +#ifdef enable_LCD + display.updateDisplay(); +#endif #ifdef enable_OLED display.display(); -#else +#endif +#ifdef enable_serial delay(100); #endif } void display_Clear() { +#ifdef enable_LCD + display.clearDisplay(); + display.setCursor(0, 8); +#endif #ifdef enable_OLED display.clearDisplay(); display.setCursor(0, 0); @@ -909,9 +1028,13 @@ void display_Clear() { } unsigned char question_box(const __FlashStringHelper* question, char answers[7][20], int num_answers, int default_choice) { +#ifdef enable_LCD + return questionBox_LCD(question, answers, num_answers, default_choice); +#endif #ifdef enable_OLED return questionBox_OLED(question, answers, num_answers, default_choice); -#else +#endif +#ifdef enable_serial return questionBox_Serial(question, answers, num_answers, default_choice); #endif } @@ -919,7 +1042,7 @@ unsigned char question_box(const __FlashStringHelper* question, char answers[7][ /****************************************** Serial Out *****************************************/ -#ifndef enable_OLED +#ifdef enable_serial void wait_serial() { while (Serial.available() == 0) { } @@ -1045,29 +1168,248 @@ byte questionBox_Serial(const __FlashStringHelper* question, char answers[7][20] void rgbLed(byte Color) { switch (Color) { case blue_color: - rgb.setColor(0, 0, 255); + setColor_RGB(0, 0, 255); break; case red_color: - rgb.setColor(255, 0, 0); + setColor_RGB(255, 0, 0); break; case purple_color: - rgb.setColor(255, 0, 255); + setColor_RGB(255, 0, 255); break; case green_color: - rgb.setColor(0, 255, 0); + setColor_RGB(0, 255, 0); break; case turquoise_color: - rgb.setColor(0, 255, 255); + setColor_RGB(0, 255, 255); break; case yellow_color: - rgb.setColor(255, 255, 0); + setColor_RGB(255, 255, 0); break; case white_color: - rgb.setColor(255, 255, 255); + setColor_RGB(255, 255, 255); break; } } +/****************************************** + LCD Menu Module +*****************************************/ +#if defined(enable_LCD) && defined(enable_rotary) +// Read encoder state +int 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(); + if (rotaryDir == 1) { + rotaryPos = newPos; + return 1; + } + else if (rotaryDir == -1) { + rotaryPos = newPos; + return 2; + } + else { + return 0; + } + } + // Check if button has changed + else { + if (reading != lastButtonState) { + lastDebounceTime = millis(); + } + if ((millis() - lastDebounceTime) > debounceDelay) { + if (reading != buttonState) { + buttonState = reading; + if (buttonState == 0) { + while ((PING & (1 << PING2)) >> PING2 == 0); + lastButtonState = reading; + return 3; + } + } + else { + lastButtonState = reading; + return 0; + } + } + else { + lastButtonState = reading; + return 0; + } + } +} + +// Wait for user to push button +void wait_encoder() { + // Change led to green + if (errorLvl == 0) + rgbLed(green_color); + + while (1) + { + // Get rotary encoder + encoder.tick(); + int newPos = encoder.getPosition(); + +#ifdef enable_N64 +#ifndef clockgen_installed + // Send some clock pulses to the Eeprom in case it locked up + if ((mode == mode_N64_Cart) && ((saveType == 5) || (saveType == 6))) { + pulseClock_N64(1); + } +#endif +#endif + + if (rotaryPos != newPos) { + rotaryPos = newPos; + errorLvl = 0; + break; + } + } +} +#endif + +#ifdef enable_LCD +// Display a question box with selectable answers. Make sure default choice is in (0, num_answers] +unsigned char questionBox_LCD(const __FlashStringHelper * question, char answers[7][20], int num_answers, int default_choice) { + //clear the screen + display.clearDisplay(); + display.updateDisplay(); + display.setCursor(0, 8); + + // 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.setDrawColor(1); + display.drawPixel(0, 8 * choice + 12); + 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 + 1 click + 2 doubleClick + 3 hold + 4 longHold */ + int b = checkButton(); + + if (b == 2) { + idleTime = millis(); + + // remove selection box + display.setDrawColor(0); + display.drawPixel(0, 8 * choice + 12); + display.updateDisplay(); + + if ((choice == 0) && (filebrowse == 1)) { + if (currPage > 1) { + lastPage = currPage; + currPage--; + break; + } + else { + root = 1; + break; + } + } + else if (choice > 0) { + choice--; + } + else { + choice = num_answers - 1; + } + + // draw selection box + display.setDrawColor(1); + display.drawPixel(0, 8 * choice + 12); + display.updateDisplay(); + + // change RGB led to the color of the current menu option + rgbLed(choice); + } + + // go one down in the menu if the Cart Dumpers button is clicked shortly + + if (b == 1) { + idleTime = millis(); + + // remove selection box + display.setDrawColor(0); + display.drawPixel(0, 8 * choice + 12); + display.updateDisplay(); + + if ((choice == num_answers - 1 ) && (numPages > currPage) && (filebrowse == 1)) { + lastPage = currPage; + currPage++; + break; + } + else + choice = (choice + 1) % num_answers; + + // draw selection box + display.setDrawColor(1); + display.drawPixel(0, 8 * choice + 12); + display.updateDisplay(); + + // change RGB led to the color of the current menu option + rgbLed(choice); + } + + // if the Cart Dumpers button is hold continiously leave the menu + // so the currently highlighted action can be executed + + if (b == 3) { + idleTime = millis(); + break; + } + } + + // pass on user choice + setColor_RGB(0, 0, 0); + return choice; +} +#endif + /****************************************** OLED Menu Module *****************************************/ @@ -1243,7 +1585,7 @@ void wait_btn() { } // Display a question box with selectable answers. Make sure default choice is in (0, num_answers] -unsigned char questionBox_OLED(const __FlashStringHelper* question, char answers[7][20], int num_answers, int default_choice) { +unsigned char questionBox_OLED(const __FlashStringHelper * question, char answers[7][20], int num_answers, int default_choice) { //clear the screen display.clearDisplay(); display.display(); @@ -1365,7 +1707,7 @@ unsigned char questionBox_OLED(const __FlashStringHelper* question, char answers } // pass on user choice - rgb.setColor(0, 0, 0); + setColor_RGB(0, 0, 0); return choice; } #endif @@ -1373,7 +1715,7 @@ unsigned char questionBox_OLED(const __FlashStringHelper* question, char answers /****************************************** Filebrowser Module *****************************************/ -void fileBrowser(const __FlashStringHelper* browserTitle) { +void fileBrowser(const __FlashStringHelper * browserTitle) { char fileNames[30][FILENAME_LENGTH]; int currFile; filebrowse = 1; diff --git a/Cart_Reader/N64.ino b/Cart_Reader/N64.ino index 4782e47..e973e20 100644 --- a/Cart_Reader/N64.ino +++ b/Cart_Reader/N64.ino @@ -182,7 +182,9 @@ void n64ControllerMenu() { case 0: display_Clear(); display_Update(); - controllerTest(); +#ifdef enable_OLED + controllerTest_OLED(); +#endif quit = 1; break; @@ -903,6 +905,7 @@ void get_button() /****************************************** N64 Controller Test *****************************************/ +#ifdef enable_OLED #define CENTER 64 void oledPrint(const char string[], int x, int y) { @@ -926,7 +929,7 @@ void printSTR(String st, int x, int y) oledPrint(buf, x, y); } -void controllerTest() { +void controllerTest_OLED() { // on which screens do we start int startscreen = 1; int mode = 0; @@ -1426,6 +1429,8 @@ void controllerTest() { } } } +#endif + /****************************************** N64 Controller Pak Functions (connected via Controller) @@ -3069,7 +3074,7 @@ redumpsamefolder: else { // Dump was bad or unknown errorLvl = 1; - rgb.setColor(255, 0, 0); + setColor_RGB(255, 0, 0); println_Msg(F("Checksum not found")); println_Msg(F("in N64.txt")); println_Msg(F("")); @@ -3099,7 +3104,7 @@ redumpsamefolder: case 1: // Dump again into new folder display_Clear(); - rgb.setColor(0, 0, 0); + setColor_RGB(0, 0, 0); goto redumpnewfolder; break; @@ -3118,7 +3123,7 @@ redumpsamefolder: display_Clear(); println_Msg(F("Reading Rom...")); display_Update(); - rgb.setColor(0, 0, 0); + setColor_RGB(0, 0, 0); goto redumpsamefolder; break; diff --git a/Cart_Reader/NES.ino b/Cart_Reader/NES.ino index c9c8ed2..c4cb242 100644 --- a/Cart_Reader/NES.ino +++ b/Cart_Reader/NES.ino @@ -1253,10 +1253,11 @@ chooseMapper: errorLvl = 1; display.println("Mapper not supported"); display.display(); - wait_btn(); + wait(); goto chooseMapper; } -#else +#endif +#ifdef enable_serial setmapper: String newmap; mapfound = false; @@ -1356,7 +1357,8 @@ void setPRGSize() { println_Msg(F("K")); display_Update(); delay(1000); -#else +#endif +#ifdef enable_serial if (prglo == prghi) newprgsize = prglo; else { @@ -1429,7 +1431,8 @@ void setCHRSize() { println_Msg(F("K")); display_Update(); delay(1000); -#else +#endif +#ifdef enable_serial if (chrlo == chrhi) newchrsize = chrlo; else { @@ -1549,7 +1552,8 @@ void setRAMSize() { } display_Update(); delay(1000); -#else +#endif +#ifdef enable_serial if (ramlo == ramhi) newramsize = ramlo; else { diff --git a/Cart_Reader/NP.ino b/Cart_Reader/NP.ino index 56392f8..6efaefb 100644 --- a/Cart_Reader/NP.ino +++ b/Cart_Reader/NP.ino @@ -717,7 +717,7 @@ void getCartInfo_SFM() { if (checkcart_SFM() == 0) { // Checksum either corrupt or 0000 errorLvl = 1; - rgb.setColor(255, 0, 0); + setColor_RGB(255, 0, 0); display_Clear(); println_Msg(F("ERROR")); println_Msg(F("Rom header corrupt")); diff --git a/Cart_Reader/README.md b/Cart_Reader/README.md index 29fb9a7..65c76d6 100644 --- a/Cart_Reader/README.md +++ b/Cart_Reader/README.md @@ -57,10 +57,13 @@ To compile and upload the code please have a look at [this wiki article](https:/ Needed libraries(already included in the portable Arduino IDE under Releases) -SD lib: https://github.com/greiman/SdFat -LCD lib: https://github.com/adafruit/Adafruit_SSD1306 +SD lib: https://github.com/greiman/SdFat +OLED lib: https://github.com/adafruit/Adafruit_SSD1306 GFX Lib: https://github.com/adafruit/Adafruit-GFX-Library BusIO: https://github.com/adafruit/Adafruit_BusIO -RGB Tools lib: https://github.com/joushx/Arduino-RGB-Tools -SI5351 lib: https://github.com/etherkit/Si5351Arduino -RTC lib: https://github.com/adafruit/RTClib (If you include an RTC) +LCD lib: https://github.com/olikraus/u8g2 +RGB Tools lib: https://github.com/joushx/Arduino-RGB-Tools +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 \ No newline at end of file diff --git a/Cart_Reader/RTC.cpp b/Cart_Reader/RTC.cpp index da4aee2..61b22a4 100644 --- a/Cart_Reader/RTC.cpp +++ b/Cart_Reader/RTC.cpp @@ -1,3 +1,6 @@ +#include "options.h" +#ifdef RTC_installed + #include "RTC.h" #include "SdFat.h" @@ -49,3 +52,5 @@ String RTCStamp() { // Print results return dts; } + +#endif diff --git a/Cart_Reader/RTC.h b/Cart_Reader/RTC.h index 6e8df7d..6a485c4 100644 --- a/Cart_Reader/RTC.h +++ b/Cart_Reader/RTC.h @@ -1,3 +1,6 @@ +#include "options.h" +#ifdef RTC_installed + // RTC Library #ifndef _RTC_H #define _RTC_H @@ -11,3 +14,4 @@ void dateTime(uint16_t* date, uint16_t* time); String RTCStamp(); #endif +#endif diff --git a/Cart_Reader/SMS.ino b/Cart_Reader/SMS.ino index d18167d..d74cc31 100644 --- a/Cart_Reader/SMS.ino +++ b/Cart_Reader/SMS.ino @@ -319,7 +319,7 @@ void getCartInfo_SMS() { default: cartSize = 48 * 1024UL; // LED Error - rgb.setColor(0, 0, 255); + setColor_RGB(0, 0, 255); break; } @@ -382,7 +382,7 @@ void getCartInfo_SMS() { wait(); #endif // Turn off LED - rgb.setColor(0, 0, 0); + setColor_RGB(0, 0, 0); } // Read rom and save to the SD card diff --git a/Cart_Reader/SNES.ino b/Cart_Reader/SNES.ino index b4c17bd..5c2b25d 100644 --- a/Cart_Reader/SNES.ino +++ b/Cart_Reader/SNES.ino @@ -665,7 +665,7 @@ void getCartInfo_SNES() { // Checksum either corrupt or 0000 manualConfig = 1; errorLvl = 1; - rgb.setColor(255, 0, 0); + setColor_RGB(255, 0, 0); display_Clear(); println_Msg(F("ERROR")); @@ -775,7 +775,8 @@ void getCartInfo_SNES() { println_Msg(F("Press Button...")); display_Update(); wait(); -#else +#endif +#ifdef enable_serial println_Msg(F(" ")); #endif diff --git a/Cart_Reader/options.h b/Cart_Reader/options.h index c793396..140752a 100644 --- a/Cart_Reader/options.h +++ b/Cart_Reader/options.h @@ -1,3 +1,31 @@ +//****************************************** +// CHOOSE HARDWARE VERSION +//****************************************** +//#define HW4 +#define HW3 +//#define HW2 +//#define HW1 +//#define SERIAL + +#if defined(HW4) +#define enable_LCD +#define enable_neopixel +#define enable_rotary +#endif + +#if defined(HW2) || defined(HW3) +#define enable_OLED +#define enable_Button2 +#endif + +#if defined(HW1) +#define enable_OLED +#endif + +#if defined(SERIAL) +#define enable_serial +#endif + //****************************************** // GLOBAL OPTIONS //****************************************** @@ -5,15 +33,9 @@ // flashMenu, nesMenu or smsMenu for single slot Cart Readers #define startMenu mainMenu -// Comment out to change to Serial Output -// be sure to change the Arduino Serial Monitor to no line ending -#define enable_OLED -// Skip OLED start-up animation +// Skip start-up animation // #define fast_start -// Enable the second button -#define enable_Button2 - // Setup RTC if installed. // remove // if you have an RTC installed // #define RTC_installed @@ -46,7 +68,7 @@ // #define clockgen_installed // The CRC for N64 Roms will be calculated during dumping from memory instead of after dumping from SD card, not compatible to all Cart Readers -// #define fastcrc +// #define fastcrc // saves a n64log.txt file with rom info in /N64/ROM // #define savesummarytotxt diff --git a/pinout.ods b/pinout.ods index 4cec67c562b21c5907ab1dc28b2020239d31ea7e..8b1d83296a38dcb56e016b8096923a4bd24d2725 100644 GIT binary patch delta 10363 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 delta 9445 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 Xgd_jk;;)_-S6qzJs~4?OzeoQMT_L9^