mirror of
https://github.com/sanni/cartreader.git
synced 2024-11-24 05:29:17 +01:00
13243cdba4
IMPROVEMENTS: Added read/write support for all Serial EEPROM cart types (Acclaim Type 1, Acclaim Type 2, Capcom, Codemasters, EA, and Sega). Capcom EEPROM code is untested but should work the same as Sega EEPROM code. Fixed SRAM code to handle all SRAM types. Added identification of SRAM carts that have bad headers. Added support for Super Street Fighter 2. WARNINGS: Serial EEPROM writes take time to complete. Serial EEPROM support was tested using Serial Output only. Support using the OLED Display might require some timing adjustment as display_Update() on Serial translates to delay(100). ADDITIONAL NOTES: I've included a save file for NBA Jam T.E. Rev 0. NBA Jam T.E. Rev 0 has faulty code that will erase the EEPROM when the cart is played on the console. The solution proposed by the publisher was to play the cart with two different sets of initials (XXX, NBA) to initialize the EEPROM. The included save file was created after doing this special initialization. My tests of the EEPROM show that it will still erase after the special initialization. I did some additional tests and found a potential solution. Write the included save file to the cart. Play the cart on the console as normal. Create new saves as needed. When done playing, HOLD THE RESET BUTTON DOWN BEFORE TURNING THE CONSOLE OFF. YMMV.
1384 lines
42 KiB
C++
1384 lines
42 KiB
C++
/**********************************************************************************
|
|
Cartridge Reader for Arduino Mega2560
|
|
|
|
Author: sanni
|
|
Date: 25-08-2019
|
|
Version: 3.2
|
|
|
|
SD lib: https://github.com/greiman/SdFat
|
|
LCD lib: https://github.com/adafruit/Adafruit_SSD1306
|
|
Clockgen: https://github.com/etherkit/Si5351Arduino
|
|
RGB Tools lib: https://github.com/joushx/Arduino-RGB-Tools
|
|
|
|
Compiled with Arduino 1.8.9
|
|
|
|
Thanks to:
|
|
MichlK - ROM-Reader for Super Nintendo
|
|
Jeff Saltzman - 4-Way Button
|
|
Wayne and Layne - Video-Game-Shield menu
|
|
skaman - SNES enhancements, SA1 sram support,GB flash fix and MD improvements
|
|
nocash - Nintendo Power and GBA Eeprom commands and lots of other info
|
|
crazynation - N64 bus timing
|
|
hkz/themanbehindthecurtain - N64 flashram commands
|
|
jago85 - help with N64 stuff
|
|
Andrew Brown/Peter Den Hartog - N64 controller protocol
|
|
bryc - mempak
|
|
Shaun Taylor - N64 controller CRC functions
|
|
Angus Gratton - CRC32
|
|
Tamanegi_taro - SA1 fix, PCE and Satellaview support
|
|
Snes9x - SuperFX sram fix
|
|
zzattack - multigame pcb fix
|
|
Pickle - SDD1 fix
|
|
insidegadgets - GBCartRead
|
|
RobinTheHood - GameboyAdvanceRomDumper
|
|
YamaArashi - GBA flashrom bank switch command
|
|
infinest - GB Memory Binary Maker
|
|
moldov - SF Memory Binary Maker
|
|
vogelfreiheit - N64 flashram fix
|
|
|
|
**********************************************************************************/
|
|
char ver[5] = "3.2";
|
|
|
|
/******************************************
|
|
Define Starting Point
|
|
******************************************/
|
|
// mainMenu, n64Menu, snsMenu, gbxMenu, segaMenu, flashMenu, pceMenu
|
|
#define startMenu mainMenu
|
|
|
|
/******************************************
|
|
Define Output
|
|
******************************************/
|
|
// To use the Serial Monitor change
|
|
// enable_OLED to 0 and enable_Serial to 1
|
|
#define enable_OLED 1
|
|
#define enable_Serial 0
|
|
|
|
/******************************************
|
|
Define Input
|
|
******************************************/
|
|
// If you are using the old version with only one button add // in front of the next line
|
|
#define enable_Button2
|
|
|
|
/******************************************
|
|
Define SD Speed
|
|
******************************************/
|
|
// Change to half speed if you get an sd error or it hangs when writing
|
|
#define sdSpeed SPI_FULL_SPEED
|
|
//#define sdSpeed SPI_HALF_SPEED
|
|
|
|
/******************************************
|
|
Options
|
|
******************************************/
|
|
// If set to 1 then the crc32 checksum will be calculated after reading a N64 rom
|
|
boolean n64crc = 1;
|
|
// Enable 16bit flash adapter menu
|
|
//#define enable_flash16
|
|
|
|
/******************************************
|
|
Libraries
|
|
*****************************************/
|
|
// Basic Libs
|
|
#include <SPI.h>
|
|
#include <Wire.h>
|
|
#include <avr/pgmspace.h>
|
|
|
|
// AVR Eeprom
|
|
#include <EEPROM.h>
|
|
#include "EEPROMAnything.h"
|
|
|
|
// Graphic I2C LCD
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#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 4 // Reset pin # (or -1 if sharing Arduino reset pin)
|
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
|
|
// Adafruit Clock Generator
|
|
#include <si5351.h>
|
|
Si5351 clockgen;
|
|
|
|
// RGB LED
|
|
#include <RGBTools.h>
|
|
|
|
// Set pins of red, green and blue
|
|
RGBTools rgb(12, 11, 10);
|
|
|
|
typedef enum COLOR_T {
|
|
blue_color,
|
|
red_color,
|
|
purple_color,
|
|
green_color,
|
|
turquoise_color,
|
|
yellow_color,
|
|
white_color,
|
|
} color_t;
|
|
|
|
// SD Card (Pin 50 = MISO, Pin 51 = MOSI, Pin 52 = SCK, Pin 53 = SS)
|
|
#include <SdFat.h>
|
|
#define chipSelectPin 53
|
|
SdFat sd;
|
|
SdFile myFile;
|
|
|
|
/******************************************
|
|
Defines
|
|
*****************************************/
|
|
// Mode menu
|
|
#define mode_N64_Cart 0
|
|
#define mode_N64_Controller 1
|
|
#define mode_SNES 2
|
|
#define mode_SFM 3
|
|
#define mode_SFM_Flash 4
|
|
#define mode_SFM_Game 5
|
|
#define mode_GB 6
|
|
#define mode_FLASH8 7
|
|
#define mode_FLASH16 8
|
|
#define mode_GBA 9
|
|
#define mode_GBM 10
|
|
#define mode_MD 11
|
|
#define mode_EPROM 12
|
|
#define mode_PCE 13
|
|
#define mode_SV 14
|
|
|
|
/******************************************
|
|
Variables
|
|
*****************************************/
|
|
// Button timing
|
|
static int debounce = 20; // ms debounce period to prevent flickering when pressing or releasing the button
|
|
static int DCgap = 250; // max ms between clicks for a double click event
|
|
static int holdTime = 2000; // ms hold period: how long to wait for press+hold event
|
|
static int longHoldTime = 5000; // ms long hold period: how long to wait for press+hold event
|
|
// Variables for 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
|
|
// Variables for 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
|
|
|
|
// For incoming serial data
|
|
int incomingByte;
|
|
|
|
// 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
|
|
char fileName[26];
|
|
char filePath[50];
|
|
byte currPage;
|
|
byte lastPage;
|
|
byte numPages;
|
|
boolean root = 0;
|
|
boolean filebrowse = 0;
|
|
char fileOptions[30][20];
|
|
|
|
// Common
|
|
char romName[17];
|
|
unsigned long sramSize = 0;
|
|
int romType = 0;
|
|
byte saveType;
|
|
word romSize = 0;
|
|
byte numBanks = 128;
|
|
char checksumStr[5];
|
|
bool errorLvl = 0;
|
|
byte romVersion = 0;
|
|
char cartID[5];
|
|
unsigned long cartSize;
|
|
char flashid[5];
|
|
char vendorID[5];
|
|
unsigned long fileSize;
|
|
unsigned long sramBase;
|
|
|
|
// Variable to count errors
|
|
unsigned long writeErrors;
|
|
|
|
// Operation mode
|
|
byte mode;
|
|
|
|
//remember folder number to create a new folder for every save
|
|
int foldern;
|
|
char folder[36];
|
|
|
|
// Array that holds the data
|
|
byte sdBuffer[512];
|
|
|
|
//******************************************
|
|
// Bitmaps
|
|
//******************************************
|
|
static const unsigned char PROGMEM icon [] = {
|
|
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
|
|
0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF,
|
|
0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x00,
|
|
0x0F, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xF8, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xF8,
|
|
0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xF8, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0x00,
|
|
0x00, 0xFF, 0x80, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0x80, 0x0F, 0x00, 0x0F, 0xFF,
|
|
0xFF, 0xFF, 0xF0, 0x0F, 0x80, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x80, 0x0F, 0x00,
|
|
0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x80, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x80,
|
|
0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0x0F, 0xF0, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0x0F, 0xF0, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, 0xF0, 0xF0, 0x0F, 0xFF,
|
|
0xF0, 0x00, 0xFF, 0xFF, 0x00, 0xF0, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, 0xF0, 0xF0,
|
|
0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0x00, 0xF0, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00,
|
|
0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF,
|
|
0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0,
|
|
0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
|
|
0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F,
|
|
0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0,
|
|
0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x00,
|
|
0xFF, 0xF0, 0x00, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0,
|
|
0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x1F, 0xF0, 0xFF, 0xF0,
|
|
0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x1F, 0xF0, 0xFF, 0xF0, 0xFF, 0x0F, 0xFF, 0x0F, 0xF0, 0x1F, 0xF0,
|
|
0xFF, 0xF0, 0xFF, 0x0F, 0xFF, 0x0F, 0xF0, 0x1F, 0xF0, 0xFF, 0xF0, 0xFF, 0x0F, 0xFF, 0x0F, 0xF0,
|
|
0x01, 0xF0, 0xFF, 0xF0, 0xFF, 0x0F, 0xFF, 0x0F, 0x80, 0x01, 0xF0, 0xFF, 0xF0, 0xFF, 0x0F, 0xFF,
|
|
0x0F, 0x80, 0x01, 0xF0, 0xFF, 0xF0, 0xFF, 0x0F, 0xFF, 0x0F, 0x80, 0x01, 0xF0, 0xFF, 0xF0, 0xFF,
|
|
0x0F, 0xFF, 0x0F, 0x80, 0x01, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0x01, 0xF0, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0x01, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0x01,
|
|
0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
|
|
0x80, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x0F, 0x80, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x01, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80
|
|
};
|
|
|
|
static const unsigned char PROGMEM sig [] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0xF0, 0x80, 0x40, 0x30, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x90, 0xCC, 0x4E, 0x10, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x90, 0x5C, 0x7B, 0x19, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xB8, 0x56, 0x31, 0x09, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0xA8, 0x72, 0x31, 0x0F, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xAC, 0x23, 0x21, 0x86, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xE7, 0xA1, 0x00, 0x80, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
//For PC Engine
|
|
extern void pcsMenu(void);
|
|
extern void pceMenu(void);
|
|
|
|
/******************************************
|
|
Menu
|
|
*****************************************/
|
|
// Main menu
|
|
static const char modeItem1[] PROGMEM = "Nintendo 64";
|
|
static const char modeItem2[] PROGMEM = "Super Nintendo";
|
|
static const char modeItem3[] PROGMEM = "Game Boy";
|
|
static const char modeItem4[] PROGMEM = "Mega Drive";
|
|
static const char modeItem5[] PROGMEM = "Flashrom Programmer";
|
|
static const char modeItem6[] PROGMEM = "PC Engine/TG16";
|
|
static const char modeItem7[] PROGMEM = "About";
|
|
static const char* const modeOptions[] PROGMEM = {modeItem1, modeItem2, modeItem3, modeItem4, modeItem5, modeItem6, modeItem7};
|
|
|
|
void aboutScreen() {
|
|
display_Clear();
|
|
// Draw the Logo
|
|
display.drawBitmap(0, 0, sig, 128, 64, 1);
|
|
println_Msg(F("Cartridge Reader"));
|
|
println_Msg(F("github.com/sanni"));
|
|
print_Msg(F("2019 Version "));
|
|
println_Msg(ver);
|
|
println_Msg(F(""));
|
|
println_Msg(F(""));
|
|
println_Msg(F(""));
|
|
println_Msg(F(""));
|
|
println_Msg(F("Press Button"));
|
|
display_Update();
|
|
|
|
while (1) {
|
|
if (enable_OLED) {
|
|
// get input button
|
|
int b = checkButton();
|
|
|
|
// if the cart readers input button is pressed shortly
|
|
if (b == 1) {
|
|
asm volatile (" jmp 0");
|
|
}
|
|
|
|
// if the cart readers input button is pressed long
|
|
if (b == 3) {
|
|
asm volatile (" jmp 0");
|
|
}
|
|
|
|
// 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(10, foldern);
|
|
asm volatile (" jmp 0");
|
|
}
|
|
}
|
|
if (enable_Serial) {
|
|
wait_serial();
|
|
asm volatile (" jmp 0");
|
|
}
|
|
rgb.setColor(random(0, 255), random(0, 255), random(0, 255));
|
|
delay(random(50, 100));
|
|
}
|
|
}
|
|
|
|
void mainMenu() {
|
|
// create menu with title and 6 options to choose from
|
|
unsigned char modeMenu;
|
|
// Copy menuOptions out of progmem
|
|
convertPgm(modeOptions, 7);
|
|
modeMenu = question_box("Cartridge Reader", menuOptions, 7, 0);
|
|
|
|
// wait for user choice to come back from the question box menu
|
|
switch (modeMenu)
|
|
{
|
|
case 0:
|
|
n64Menu();
|
|
break;
|
|
|
|
case 1:
|
|
snsMenu();
|
|
break;
|
|
|
|
case 2:
|
|
gbxMenu();
|
|
break;
|
|
|
|
case 3:
|
|
segaMenu();
|
|
break;
|
|
|
|
case 4:
|
|
flashMenu();
|
|
break;
|
|
case 5:
|
|
pcsMenu();
|
|
break;
|
|
case 6:
|
|
aboutScreen();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************
|
|
Setup
|
|
*****************************************/
|
|
void setup() {
|
|
// Set Button Pins(PD7, PG2) to Input
|
|
DDRD &= ~(1 << 7);
|
|
DDRG &= ~(1 << 2);
|
|
// Activate Internal Pullup Resistors
|
|
//PORTD |= (1 << 7);
|
|
//PORTG |= (1 << 2);
|
|
|
|
// Read current folder number out of eeprom
|
|
EEPROM_readAnything(10, foldern);
|
|
|
|
if (enable_OLED) {
|
|
// GLCD
|
|
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
|
|
display.setTextSize(1);
|
|
display.setTextColor(WHITE);
|
|
|
|
// Clear the screen buffer.
|
|
display_Clear();
|
|
delay(100);
|
|
|
|
// Draw line
|
|
display.drawLine(0, 32, 127, 32, WHITE);
|
|
display_Update();
|
|
delay(100);
|
|
|
|
// Initialize LED
|
|
rgb.setColor(0, 0, 0);
|
|
|
|
// Clear the screen.
|
|
display_Clear();
|
|
display_Update();
|
|
delay(25);
|
|
|
|
// Draw the Logo
|
|
display.drawBitmap(28, 0, icon, 72, 64, 1);
|
|
for (int s = 1; s < 64; s += 2) {
|
|
// Draw Scanlines
|
|
display.drawLine(0, s, 127, s, BLACK);
|
|
}
|
|
display_Update();
|
|
delay(50);
|
|
|
|
// Clear the screen.
|
|
display_Clear();
|
|
display_Update();
|
|
delay(25);
|
|
|
|
// Draw the Logo
|
|
display.drawBitmap(28, 0, icon, 72, 64, 1);
|
|
for (int s = 1; s < 64; s += 2) {
|
|
// Draw Scanlines
|
|
display.drawLine(0, s, 127, s, BLACK);
|
|
}
|
|
display.setCursor(100, 55);
|
|
display.println(ver);
|
|
display_Update();
|
|
delay(200);
|
|
}
|
|
|
|
if (enable_Serial) {
|
|
// Serial Begin
|
|
Serial.begin(9600);
|
|
Serial.println(F("Cartridge Reader"));
|
|
Serial.println(F("2019 sanni"));
|
|
Serial.println("");
|
|
// LED Error
|
|
rgb.setColor(0, 0, 255);
|
|
}
|
|
else {
|
|
// LED Off
|
|
rgb.setColor(0, 0, 0);
|
|
}
|
|
|
|
// Init SD card
|
|
if (!sd.begin(chipSelectPin, sdSpeed)) {
|
|
display_Clear();
|
|
print_Error(F("SD Error"), true);
|
|
}
|
|
|
|
if (enable_Serial) {
|
|
// 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()));
|
|
}
|
|
|
|
startMenu();
|
|
}
|
|
|
|
/******************************************
|
|
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
|
|
*****************************************/
|
|
// Converts a progmem array into a ram array
|
|
void convertPgm(const char* const pgmOptions[], byte numArrays) {
|
|
for (int i = 0; i < numArrays; i++) {
|
|
strcpy_P(menuOptions[i], (char*)pgm_read_word(&(pgmOptions[i])));
|
|
}
|
|
}
|
|
|
|
void print_Error(const __FlashStringHelper *errorMessage, boolean forceReset) {
|
|
errorLvl = 1;
|
|
rgb.setColor(255, 0, 0);
|
|
println_Msg(errorMessage);
|
|
display_Update();
|
|
|
|
if (forceReset) {
|
|
if (enable_Serial) {
|
|
println_Msg(F("Fatal Error, please reset"));
|
|
while (1);
|
|
}
|
|
else {
|
|
println_Msg(F(""));
|
|
println_Msg(F("Press Button..."));
|
|
display_Update();
|
|
wait();
|
|
if (ignoreError == 0) {
|
|
asm volatile (" jmp 0");
|
|
}
|
|
else {
|
|
ignoreError = 0;
|
|
display_Clear();
|
|
println_Msg(F(""));
|
|
println_Msg(F(""));
|
|
println_Msg(F(""));
|
|
println_Msg(F(" Error Overwrite"));
|
|
display_Update();
|
|
delay(2000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wait() {
|
|
if (enable_OLED) {
|
|
wait_btn();
|
|
}
|
|
if (enable_Serial) {
|
|
wait_serial();
|
|
}
|
|
}
|
|
|
|
void print_Msg(const __FlashStringHelper *string) {
|
|
if (enable_OLED)
|
|
display.print(string);
|
|
if (enable_Serial)
|
|
Serial.print(string);
|
|
}
|
|
|
|
void print_Msg(const char string[]) {
|
|
if (enable_OLED)
|
|
display.print(string);
|
|
if (enable_Serial)
|
|
Serial.print(string);
|
|
}
|
|
|
|
void print_Msg(long unsigned int message) {
|
|
if (enable_OLED)
|
|
display.print(message);
|
|
if (enable_Serial)
|
|
Serial.print(message);
|
|
}
|
|
|
|
void print_Msg(byte message, int outputFormat) {
|
|
if (enable_OLED)
|
|
display.print(message, outputFormat);
|
|
if (enable_Serial)
|
|
Serial.print(message, outputFormat);
|
|
}
|
|
|
|
void print_Msg(String string) {
|
|
if (enable_OLED)
|
|
display.print(string);
|
|
if (enable_Serial)
|
|
Serial.print(string);
|
|
}
|
|
|
|
void println_Msg(String string) {
|
|
if (enable_OLED)
|
|
display.println(string);
|
|
if (enable_Serial)
|
|
Serial.println(string);
|
|
}
|
|
|
|
void println_Msg(byte message, int outputFormat) {
|
|
if (enable_OLED)
|
|
display.println(message, outputFormat);
|
|
if (enable_Serial)
|
|
Serial.println(message, outputFormat);
|
|
}
|
|
|
|
void println_Msg(const char message[]) {
|
|
if (enable_OLED)
|
|
display.println(message);
|
|
if (enable_Serial)
|
|
Serial.println(message);
|
|
}
|
|
|
|
void println_Msg(const __FlashStringHelper *string) {
|
|
if (enable_OLED)
|
|
display.println(string);
|
|
if (enable_Serial)
|
|
Serial.println(string);
|
|
}
|
|
|
|
void println_Msg(long unsigned int message) {
|
|
if (enable_OLED)
|
|
display.println(message);
|
|
if (enable_Serial)
|
|
Serial.println(message);
|
|
}
|
|
|
|
void display_Update() {
|
|
if (enable_OLED)
|
|
display.display();
|
|
if (enable_Serial)
|
|
delay(100);
|
|
}
|
|
|
|
void display_Clear() {
|
|
if (enable_OLED) {
|
|
display.clearDisplay();
|
|
display.setCursor(0, 0);
|
|
}
|
|
}
|
|
|
|
unsigned char question_box(const char* question, char answers[7][20], int num_answers, int default_choice) {
|
|
if (enable_OLED) {
|
|
return questionBox_OLED(question, answers, num_answers, default_choice);
|
|
}
|
|
else if (enable_Serial) {
|
|
return questionBox_Serial(question, answers, num_answers, default_choice);
|
|
}
|
|
}
|
|
|
|
/******************************************
|
|
Serial Out
|
|
*****************************************/
|
|
void wait_serial() {
|
|
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)
|
|
PORTB ^= (1 << 4);
|
|
Serial.write(myFile.read());
|
|
}
|
|
// Close the file:
|
|
myFile.close();
|
|
}
|
|
else {
|
|
print_Error(F("Can't open file"), true);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
byte questionBox_Serial(const char* question, char answers[7][20], int num_answers, int default_choice) {
|
|
// Print menu to serial monitor
|
|
//Serial.println(question);
|
|
for (byte i = 0; i < num_answers; i++) {
|
|
Serial.print(i);
|
|
Serial.print(F(")"));
|
|
Serial.println(answers[i]);
|
|
}
|
|
// Wait for user input
|
|
Serial.println("");
|
|
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): _ "));
|
|
while (Serial.available() == 0) {
|
|
}
|
|
|
|
// Read the incoming byte:
|
|
incomingByte = Serial.read() - 48;
|
|
|
|
/* Import file (i)
|
|
if (incomingByte == 57) {
|
|
if (filebrowse == 1) {
|
|
// Make sure we have an import directory
|
|
sd.mkdir("IMPORT", true);
|
|
|
|
// Create and open file on sd card
|
|
EEPROM_readAnything(10, foldern);
|
|
sprintf(fileName, "IMPORT/%d.bin", foldern);
|
|
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
|
|
print_Error(F("Can't create file on SD"), true);
|
|
}
|
|
|
|
// Read file from serial
|
|
fileSize = 0;
|
|
while (Serial.available() > 0) {
|
|
myFile.write(Serial.read());
|
|
fileSize++;
|
|
// Blink led
|
|
PORTB ^= (1 << 4);
|
|
}
|
|
|
|
// Close the file:
|
|
myFile.close();
|
|
|
|
// Write new folder number back to eeprom
|
|
foldern = foldern + 1;
|
|
EEPROM_writeAnything(10, foldern);
|
|
|
|
print_Msg("Imported ");
|
|
print_Msg(fileSize);
|
|
print_Msg(" bytes to file ");
|
|
println_Msg(fileName);
|
|
return 7;
|
|
}
|
|
}*/
|
|
|
|
// Page up (u)
|
|
if (incomingByte == 69) {
|
|
if (filebrowse == 1) {
|
|
if (currPage > 1) {
|
|
lastPage = currPage;
|
|
currPage--;
|
|
}
|
|
else {
|
|
root = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Page down (d)
|
|
else if (incomingByte == 52) {
|
|
if ((numPages > currPage) && (filebrowse == 1)) {
|
|
lastPage = currPage;
|
|
currPage++;
|
|
}
|
|
}
|
|
|
|
// Print the received byte for validation e.g. in case of a different keyboard mapping
|
|
//Serial.println(incomingByte);
|
|
//Serial.println("");
|
|
return incomingByte;
|
|
}
|
|
|
|
/******************************************
|
|
RGB LED
|
|
*****************************************/
|
|
void rgbLed(byte Color) {
|
|
switch (Color) {
|
|
case blue_color:
|
|
rgb.setColor(0, 0, 255);
|
|
break;
|
|
case red_color:
|
|
rgb.setColor(255, 0, 0);
|
|
break;
|
|
case purple_color:
|
|
rgb.setColor(255, 0, 255);
|
|
break;
|
|
case green_color:
|
|
rgb.setColor(0, 255, 0);
|
|
break;
|
|
case turquoise_color:
|
|
rgb.setColor(0, 255, 255);
|
|
break;
|
|
case yellow_color:
|
|
rgb.setColor(255, 255, 0);
|
|
break;
|
|
case white_color:
|
|
rgb.setColor(255, 255, 255);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************
|
|
OLED Menu Module
|
|
*****************************************/
|
|
// Read button state
|
|
int checkButton() {
|
|
#ifdef enable_Button2
|
|
if (checkButton2() != 0)
|
|
return 3;
|
|
else
|
|
return (checkButton1());
|
|
#else
|
|
return (checkButton1());
|
|
#endif
|
|
}
|
|
|
|
// Read button 1
|
|
int checkButton1() {
|
|
int 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 (not 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 (not holdEventPast1) {
|
|
event = 3;
|
|
waitForUp1 = true;
|
|
ignoreUp1 = true;
|
|
DConUp1 = false;
|
|
DCwaiting1 = false;
|
|
//downTime1 = millis();
|
|
holdEventPast1 = true;
|
|
}
|
|
// Trigger "long" hold
|
|
if ((millis() - downTime1) >= longHoldTime) {
|
|
if (not longholdEventPast1) {
|
|
event = 4;
|
|
longholdEventPast1 = true;
|
|
}
|
|
}
|
|
}
|
|
buttonLast1 = buttonVal1;
|
|
return event;
|
|
}
|
|
|
|
// Read button 2
|
|
int checkButton2() {
|
|
int event = 0;
|
|
// Read the state of the button (PD7)
|
|
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 (not 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 (not holdEventPast2) {
|
|
event = 3;
|
|
waitForUp2 = true;
|
|
ignoreUp2 = true;
|
|
DConUp2 = false;
|
|
DCwaiting2 = false;
|
|
//downTime2 = millis();
|
|
holdEventPast2 = true;
|
|
}
|
|
// Trigger "long" hold
|
|
if ((millis() - downTime2) >= longHoldTime) {
|
|
if (not 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
|
|
int b = checkButton();
|
|
|
|
// Send some clock pulses to the Eeprom in case it locked up
|
|
if ((mode == mode_N64_Cart) && ((saveType == 5) || (saveType == 6))) {
|
|
pulseClock_N64(1);
|
|
}
|
|
// 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) {
|
|
// Debug
|
|
//ignoreError = 1;
|
|
errorLvl = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display a question box with selectable answers. Make sure default choice is in (0, num_answers]
|
|
unsigned char questionBox_OLED(const char* question, char answers[7][20], int num_answers, int default_choice) {
|
|
|
|
//clear the screen
|
|
display.clearDisplay();
|
|
display.display();
|
|
display.setCursor(0, 0);
|
|
|
|
// change the rgb led to the start menu color
|
|
rgbLed(default_choice);
|
|
|
|
// print menu
|
|
display.println(question);
|
|
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.display();
|
|
|
|
// start with the default choice
|
|
choice = default_choice;
|
|
|
|
// draw selection box
|
|
display.drawPixel(0, 8 * choice + 12, WHITE);
|
|
display.display();
|
|
|
|
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++;
|
|
}
|
|
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.drawPixel(0, 8 * choice + 12, BLACK);
|
|
display.display();
|
|
|
|
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.drawPixel(0, 8 * choice + 12, WHITE);
|
|
display.display();
|
|
|
|
// 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.drawPixel(0, 8 * choice + 12, BLACK);
|
|
display.display();
|
|
|
|
if ((choice == num_answers - 1 ) && (numPages > currPage) && (filebrowse == 1)) {
|
|
lastPage = currPage;
|
|
currPage++;
|
|
break;
|
|
}
|
|
else
|
|
choice = (choice + 1) % num_answers;
|
|
|
|
// draw selection box
|
|
display.drawPixel(0, 8 * choice + 12, WHITE);
|
|
display.display();
|
|
|
|
// 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
|
|
rgb.setColor(0, 0, 0);
|
|
return choice;
|
|
}
|
|
|
|
/******************************************
|
|
Filebrowser Module
|
|
*****************************************/
|
|
void fileBrowser(const char* browserTitle) {
|
|
char fileNames[30][26];
|
|
int currFile;
|
|
filebrowse = 1;
|
|
|
|
// Empty filePath string
|
|
filePath[0] = '\0';
|
|
|
|
// Temporary char array for filename
|
|
char nameStr[26];
|
|
|
|
browserstart:
|
|
|
|
// Print title
|
|
println_Msg(browserTitle);
|
|
|
|
// Set currFile back to 0
|
|
currFile = 0;
|
|
currPage = 1;
|
|
lastPage = 1;
|
|
|
|
// Read in File as long as there are files
|
|
while (myFile.openNext(sd.vwd(), O_READ)) {
|
|
|
|
// Get name of file
|
|
myFile.getName(nameStr, 27);
|
|
|
|
// Ignore if hidden
|
|
if (myFile.isHidden()) {
|
|
}
|
|
// Indicate a directory.
|
|
else if (myFile.isDir()) {
|
|
// Copy full dirname into fileNames
|
|
sprintf(fileNames[currFile], "%s%s", "/", nameStr);
|
|
// Truncate to 19 letters for LCD
|
|
nameStr[19] = '\0';
|
|
// Copy short string into fileOptions
|
|
sprintf(fileOptions[currFile], "%s%s", "/", nameStr);
|
|
currFile++;
|
|
}
|
|
// It's just a file
|
|
else if (myFile.isFile()) {
|
|
// Copy full filename into fileNames
|
|
sprintf(fileNames[currFile], "%s", nameStr);
|
|
// Truncate to 19 letters for LCD
|
|
nameStr[19] = '\0';
|
|
// Copy short string into fileOptions
|
|
sprintf(fileOptions[currFile], "%s", nameStr);
|
|
currFile++;
|
|
}
|
|
myFile.close();
|
|
}
|
|
|
|
// "Calculate number of needed pages"
|
|
if (currFile < 8)
|
|
numPages = 1;
|
|
else if (currFile < 15)
|
|
numPages = 2;
|
|
else if (currFile < 22)
|
|
numPages = 3;
|
|
else if (currFile < 29)
|
|
numPages = 4;
|
|
else if (currFile < 36)
|
|
numPages = 5;
|
|
|
|
// 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;
|
|
if (currFile < 8)
|
|
count = currFile;
|
|
else if (currPage == 1)
|
|
count = 7;
|
|
else if (currFile < 15)
|
|
count = currFile - 7;
|
|
else if (currPage == 2)
|
|
count = 7;
|
|
else if (currFile < 22)
|
|
count = currFile - 14;
|
|
else if (currPage == 3)
|
|
count = 7;
|
|
else if (currFile < 29)
|
|
count = currFile - 21;
|
|
else {
|
|
display_Clear();
|
|
|
|
println_Msg(F("Too many files"));
|
|
display_Update();
|
|
println_Msg(F(""));
|
|
println_Msg(F("Press Button..."));
|
|
display_Update();
|
|
wait();
|
|
}
|
|
|
|
for (byte i = 0; i < 8; i++ ) {
|
|
// Copy short string into fileOptions
|
|
sprintf( answers[i], "%s", fileOptions[ ((currPage - 1) * 7 + 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) {
|
|
// Empty filePath string
|
|
filePath[0] = '\0';
|
|
// Rewind filesystem
|
|
//sd.vwd()->rewind();
|
|
// Change working dir to root
|
|
sd.chdir("/");
|
|
// Start again
|
|
root = 0;
|
|
goto browserstart;
|
|
}
|
|
|
|
// wait for user choice to come back from the question box menu
|
|
switch (answer)
|
|
{
|
|
case 0:
|
|
strcpy(fileName, fileNames[0 + ((currPage - 1) * 7)]);
|
|
break;
|
|
|
|
case 1:
|
|
strcpy(fileName, fileNames[1 + ((currPage - 1) * 7)]);
|
|
break;
|
|
|
|
case 2:
|
|
strcpy(fileName, fileNames[2 + ((currPage - 1) * 7)]);
|
|
break;
|
|
|
|
case 3:
|
|
strcpy(fileName, fileNames[3 + ((currPage - 1) * 7)]);
|
|
break;
|
|
|
|
case 4:
|
|
strcpy(fileName, fileNames[4 + ((currPage - 1) * 7)]);
|
|
break;
|
|
|
|
case 5:
|
|
strcpy(fileName, fileNames[5 + ((currPage - 1) * 7)]);
|
|
break;
|
|
|
|
case 6:
|
|
strcpy(fileName, fileNames[6 + ((currPage - 1) * 7)]);
|
|
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() {
|
|
if (mode == mode_N64_Controller) {
|
|
n64ControllerMenu();
|
|
}
|
|
else if (mode == mode_N64_Cart) {
|
|
n64CartMenu();
|
|
}
|
|
else if (mode == mode_SNES) {
|
|
snesMenu();
|
|
}
|
|
else if (mode == mode_FLASH8) {
|
|
flashromMenu8();
|
|
}
|
|
else if (mode == mode_FLASH16) {
|
|
flashromMenu16();
|
|
}
|
|
else if (mode == mode_EPROM) {
|
|
epromMenu();
|
|
}
|
|
else if (mode == mode_SFM) {
|
|
sfmMenu();
|
|
}
|
|
else if (mode == mode_GB) {
|
|
gbMenu();
|
|
}
|
|
else if (mode == mode_GBA) {
|
|
gbaMenu();
|
|
}
|
|
else if (mode == mode_SFM_Flash) {
|
|
sfmFlashMenu();
|
|
}
|
|
else if (mode == mode_SFM_Game) {
|
|
sfmGameOptions();
|
|
}
|
|
else if (mode == mode_GBM) {
|
|
gbmMenu();
|
|
}
|
|
else if (mode == mode_MD) {
|
|
mdMenu();
|
|
}
|
|
else if (mode == mode_PCE) {
|
|
pceMenu();
|
|
}
|
|
else if (mode == mode_SV) {
|
|
svMenu();
|
|
}
|
|
else {
|
|
display_Clear();
|
|
println_Msg(F("Menu Error"));
|
|
println_Msg("");
|
|
println_Msg("");
|
|
print_Msg(F("Mode = "));
|
|
print_Msg(mode);
|
|
println_Msg(F(""));
|
|
println_Msg(F("Press Button..."));
|
|
display_Update();
|
|
wait();
|
|
asm volatile (" jmp 0");
|
|
}
|
|
}
|
|
|
|
//******************************************
|
|
// End of File
|
|
//******************************************
|