cartreader/Cart_Reader/GBS.ino
Ancyker 010b7e7525 Firmware Updater support, 3.3V Fix, and more
Lots of changes/additions.

Added:
* Firmware Updater support: Supports the Firmware Updater app (release to follow soon). Enabled by default, can be disabled in the config.
* 3.3V Fix (3V3FIX): Enable if you have stability issues when using 3.3V, works best with VSELECT. Disabled by default, can be enabled in the config.
* `DynamicClockSerial`: Class that extends and modifies HardwareSerial to be compatible with a dynamically changing clock speed. Used through the `ClockedSerial` object/variable.
* `OSCR.cpp` & `OSCR.h`: New files for storing globals. Only contains these new additions for now. More code cleanup to come.

Changed:
* Moved configuration flags to `Config.h` and documented them better.
* Removed `vselect()` function. Now uses `setVoltage()` with the params `VOLTS_SET_3V3` and `VOLTS_SET_5V`.

Known Issues:
* Rarely the LCD backlight turns white when using 3V3FIX. Resetting fixes it. Doesn't affect functionality/usability; it's just weird.
2023-03-29 21:05:01 -04:00

736 lines
18 KiB
C++

//******************************************
// GB SMART MODULE
// Supports 32M cart with LH28F016SUT flash
//******************************************
#ifdef enable_GBX
#define GB_SMART_GAMES_PER_PAGE 6
/******************************************
Menu
*****************************************/
// GB Smart menu items
static const char gbSmartMenuItem1[] PROGMEM = "Game Menu";
static const char gbSmartMenuItem2[] PROGMEM = "Flash Menu";
//static const char gbSmartMenuItem3[] PROGMEM = "Reset"; (stored in common strings array)
static const char *const menuOptionsGBSmart[] PROGMEM = { gbSmartMenuItem1, gbSmartMenuItem2, string_reset2 };
static const char gbSmartFlashMenuItem1[] PROGMEM = "Read Flash";
static const char gbSmartFlashMenuItem2[] PROGMEM = "Write Flash";
static const char gbSmartFlashMenuItem3[] PROGMEM = "Back";
static const char *const menuOptionsGBSmartFlash[] PROGMEM = { gbSmartFlashMenuItem1, gbSmartFlashMenuItem2, gbSmartFlashMenuItem3 };
static const char gbSmartGameMenuItem1[] PROGMEM = "Read Game";
static const char gbSmartGameMenuItem2[] PROGMEM = "Read SRAM";
static const char gbSmartGameMenuItem3[] PROGMEM = "Write SRAM";
static const char gbSmartGameMenuItem4[] PROGMEM = "Switch Game";
//static const char gbSmartGameMenuItem5[] PROGMEM = "Reset"; (stored in common strings array)
static const char *const menuOptionsGBSmartGame[] PROGMEM = { gbSmartGameMenuItem1, gbSmartGameMenuItem2, gbSmartGameMenuItem3, gbSmartGameMenuItem4, string_reset2 };
struct GBSmartGameInfo {
uint8_t start_bank;
uint8_t rom_type;
uint8_t rom_size;
uint8_t sram_size;
char title[16];
};
uint32_t gbSmartSize = 32 * 131072;
uint16_t gbSmartBanks = 256;
uint8_t gbSmartBanksPerFlashChip = 128;
uint8_t gbSmartBanksPerFlashBlock = 4;
uint32_t gbSmartFlashBlockSize = (gbSmartBanksPerFlashBlock << 14);
uint8_t gbSmartRomSizeGB = 0x07;
uint8_t gbSmartSramSizeGB = 0x04;
uint8_t gbSmartFlashSizeGB = 0x06;
byte signature[48];
uint16_t gameMenuStartBank;
// Compare checksum
boolean compare_checksum_GBS() {
println_Msg(F("Calculating Checksum"));
display_Update();
strcpy(fileName, romName);
strcat(fileName, ".GB");
// last used rom folder
EEPROM_readAnything(0, foldern);
sprintf(folder, "GB/ROM/%s/%d", romName, foldern - 1);
char calcsumStr[5];
sprintf(calcsumStr, "%04X", calc_checksum_GB(fileName));
if (strcmp(calcsumStr, checksumStr) == 0) {
print_Msg(F("Result: "));
println_Msg(calcsumStr);
println_Msg(F("Checksum matches"));
display_Update();
return 1;
} else {
print_Msg(F("Result: "));
println_Msg(calcsumStr);
print_Error(F("Checksum Error"));
return 0;
}
}
byte readByte_GBS(word myAddress) {
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Switch CS(PH3) and RD(PH6) to LOW
PORTH &= ~((1 << 3) | (1 << 6));
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Read
byte tempByte = PINC;
// Switch CS(PH3) and RD(PH6) to HIGH
PORTH |= (1 << 3) | (1 << 6);
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
return tempByte;
}
void setup_GBSmart() {
// take from setup_GB
// Set RST(PH0) to Input
DDRH &= ~(1 << 0);
// Activate Internal Pullup Resistors
PORTH |= (1 << 0);
// Set Address Pins to Output
//A0-A7
DDRF = 0xFF;
//A8-A15
DDRK = 0xFF;
// Set Control Pins to Output CS(PH3) WR(PH5) RD(PH6) AUDIOIN(PH4) RESET(PH0)
DDRH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
// Output a high signal on all pins, pins are active low therefore everything is disabled now
PORTH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
// Set Data Pins (D0-D7) to Input
DDRC = 0x00;
delay(400);
gbSmartRemapStartBank(0x00, 0x00, 0x00);
getCartInfo_GB();
for (byte i = 0; i < 0x30; i++)
signature[i] = readByte_GBS(0x0104 + i);
gameMenuStartBank = 0x02;
display_Clear();
display_Update();
}
void gbSmartMenu() {
setVoltage(VOLTS_SET_5V);
uint8_t mainMenu;
// Copy menuOptions out of progmem
convertPgm(menuOptionsGBSmart, 3);
mainMenu = question_box(F("GB Smart"), menuOptions, 3, 0);
// wait for user choice to come back from the question box menu
switch (mainMenu) {
case 0:
{
gbSmartGameMenu();
break;
}
case 1:
{
mode = mode_GB_GBSmart_Flash;
break;
}
default:
{
asm volatile(" jmp 0");
break;
}
}
}
void gbSmartGameOptions() {
uint8_t gameSubMenu;
convertPgm(menuOptionsGBSmartGame, 5);
gameSubMenu = question_box(F("GB Smart Game Menu"), menuOptions, 5, 0);
switch (gameSubMenu) {
case 0: // Read Game
{
display_Clear();
sd.chdir("/");
readROM_GB();
compare_checksum_GBS();
break;
}
case 1: // Read SRAM
{
display_Clear();
sd.chdir("/");
readSRAM_GB();
break;
}
case 2: // Write SRAM
{
display_Clear();
sd.chdir("/");
writeSRAM_GB();
uint32_t wrErrors = verifySRAM_GB();
if (wrErrors == 0) {
println_Msg(F("Verified OK"));
display_Update();
} else {
print_STR(error_STR, 0);
print_Msg(wrErrors);
println_Msg(F(" bytes"));
print_Error(did_not_verify_STR);
}
break;
}
case 3: // Switch Game
{
gameMenuStartBank = 0x02;
gbSmartGameMenu();
break;
}
default:
{
asm volatile(" jmp 0");
break;
}
}
if (gameSubMenu != 3) {
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
}
void gbSmartGameMenu() {
boolean hasMenu;
byte numGames;
struct GBSmartGameInfo gbSmartGames[GB_SMART_GAMES_PER_PAGE];
uint8_t gameSubMenu = 0;
gb_smart_load_more_games:
if (gameMenuStartBank > 0xfe)
gameMenuStartBank = 0x02;
gbSmartGetGames(gbSmartGames, &hasMenu, &numGames);
if (hasMenu) {
char menuOptionsGBSmartGames[7][20];
int i = 0;
for (; i < numGames; i++)
strncpy(menuOptionsGBSmartGames[i], gbSmartGames[i].title, 16);
strncpy(menuOptionsGBSmartGames[i], "...", 16);
gameSubMenu = question_box(F("Select Game"), menuOptionsGBSmartGames, i + 1, 0);
if (gameSubMenu >= i)
goto gb_smart_load_more_games;
} else {
gameSubMenu = 0;
}
// copy romname
strcpy(romName, gbSmartGames[gameSubMenu].title);
// select a game
gbSmartRemapStartBank(gbSmartGames[gameSubMenu].start_bank, gbSmartGames[gameSubMenu].rom_size, gbSmartGames[gameSubMenu].sram_size);
getCartInfo_GB();
showCartInfo_GB();
mode = mode_GB_GBSmart_Game;
}
void gbSmartFlashMenu() {
uint8_t flashSubMenu;
convertPgm(menuOptionsGBSmartFlash, 3);
flashSubMenu = question_box(F("GB Smart Flash Menu"), menuOptions, 3, 0);
switch (flashSubMenu) {
case 0:
{
// read flash
display_Clear();
sd.chdir("/");
EEPROM_readAnything(0, foldern);
sprintf(fileName, "GBS%d.bin", foldern);
sd.mkdir("GB/GBS", true);
sd.chdir("GB/GBS");
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
gbSmartReadFlash();
break;
}
case 1:
{
// write flash
display_Clear();
println_Msg(F("Attention"));
println_Msg(F("This will erase your"));
println_Msg(F("GB Smart Cartridge."));
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
display_Clear();
filePath[0] = '\0';
sd.chdir("/");
fileBrowser(F("Select 4MB file"));
sprintf(filePath, "%s/%s", filePath, fileName);
gbSmartWriteFlash();
break;
}
default:
{
mode = mode_GB_GBSmart;
return;
}
}
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
void gbSmartGetOneGame(struct GBSmartGameInfo *gbSmartGames, byte bank, word base) {
uint8_t myByte, myLength = 0;
word title_address = base + 0x0134;
for (uint8_t j = 0; j < 15; j++) {
myByte = readByte_GBS(title_address++);
if (((myByte >= '0' && myByte <= '9') || (myByte >= 'A' && myByte <= 'z')))
gbSmartGames->title[myLength++] = myByte;
}
gbSmartGames->title[myLength] = 0x00;
gbSmartGames->start_bank = bank;
gbSmartGames->rom_type = readByte_GBS(base + 0x0147);
gbSmartGames->rom_size = readByte_GBS(base + 0x0148);
gbSmartGames->sram_size = readByte_GBS(base + 0x0149);
}
void gbSmartGetGames(struct GBSmartGameInfo *gbSmartGames, boolean *hasMenu, byte *numGames) {
static const byte menu_title[] = { 0x47, 0x42, 0x31, 0x36, 0x4d };
// reset remap setting
gbSmartRemapStartBank(0x00, gbSmartRomSizeGB, gbSmartSramSizeGB);
uint16_t i;
// check if contain menu
*hasMenu = true;
dataIn();
for (i = 0; i < 5; i++) {
if (readByte_GBS(0x0134 + i) != menu_title[i]) {
*hasMenu = false;
break;
}
}
if (*hasMenu) {
for (i = gameMenuStartBank, *numGames = 0; i < gbSmartBanks && *numGames < GB_SMART_GAMES_PER_PAGE;) {
// switch bank
dataOut();
writeByte_GB(0x2100, i);
dataIn();
// read signature
for (uint8_t j = 0x00; j < 0x30; j++) {
if (readByte_GBS(0x4104 + j) != signature[j]) {
i += 0x02;
continue;
}
}
gbSmartGetOneGame(&gbSmartGames[*numGames], i, 0x4000);
i += (2 << gbSmartGames[(*numGames)++].rom_size);
}
gameMenuStartBank = i;
} else {
dataIn();
gbSmartGetOneGame(&gbSmartGames[0], 0, 0);
*numGames = 1;
gameMenuStartBank = 0xfe;
}
}
void gbSmartReadFlash() {
print_Msg(F("Saving as GB/GBS/"));
print_Msg(fileName);
println_Msg(F("..."));
display_Update();
if (!myFile.open(fileName, O_RDWR | O_CREAT))
print_FatalError(create_file_STR);
// reset flash to read array state
for (uint16_t i = 0x00; i < gbSmartBanks; i += gbSmartBanksPerFlashChip)
gbSmartResetFlash(i);
// remaps mmc to full access
gbSmartRemapStartBank(0x00, gbSmartRomSizeGB, gbSmartSramSizeGB);
// dump fixed bank 0x00
dataIn();
for (uint16_t addr = 0x0000; addr <= 0x3fff; addr += 512) {
for (uint16_t c = 0; c < 512; c++)
sdBuffer[c] = readByte_GBS(addr + c);
myFile.write(sdBuffer, 512);
}
// read rest banks
for (uint16_t bank = 0x01; bank < gbSmartBanks; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
dataIn();
for (uint16_t addr = 0x4000; addr <= 0x7fff; addr += 512) {
for (uint16_t c = 0; c < 512; c++)
sdBuffer[c] = readByte_GBS(addr + c);
myFile.write(sdBuffer, 512);
}
}
// back to initial state
writeByte_GB(0x2100, 0x01);
myFile.close();
println_Msg("");
println_Msg(F("Finished reading"));
display_Update();
}
void gbSmartWriteFlash() {
for (uint16_t bank = 0x00; bank < gbSmartBanks; bank += gbSmartBanksPerFlashChip) {
display_Clear();
print_Msg(F("Erasing..."));
display_Update();
gbSmartEraseFlash(bank);
gbSmartResetFlash(bank);
print_STR(done_STR, 1);
print_Msg(F("Blankcheck..."));
display_Update();
if (!gbSmartBlankCheckingFlash(bank))
print_FatalError(F("Could not erase flash"));
println_Msg(F("Passed"));
display_Update();
// write full chip
gbSmartWriteFlash(bank);
// reset chip
gbSmartWriteFlashByte(0x0000, 0xff);
}
print_STR(verifying_STR, 0);
display_Update();
writeErrors = gbSmartVerifyFlash();
if (writeErrors == 0) {
println_Msg(F("OK"));
display_Update();
} else {
print_STR(error_STR, 0);
print_Msg(writeErrors);
print_STR(_bytes_STR, 1);
print_FatalError(did_not_verify_STR);
}
}
void gbSmartWriteFlash(uint32_t start_bank) {
if (!myFile.open(filePath, O_READ))
print_FatalError(open_file_STR);
// switch to flash base bank
gbSmartRemapStartBank(start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
myFile.seekCur((start_bank << 14));
print_Msg(F("Writing Bank 0x"));
print_Msg(start_bank, HEX);
print_Msg(F("..."));
display_Update();
// handle bank 0x00 on 0x0000
gbSmartWriteFlashFromMyFile(0x0000);
// handle rest banks on 0x4000
for (uint8_t bank = 0x01; bank < gbSmartBanksPerFlashChip; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
gbSmartWriteFlashFromMyFile(0x4000);
}
myFile.close();
println_Msg("");
}
void gbSmartWriteFlashFromMyFile(uint32_t addr) {
for (uint16_t i = 0; i < 16384; i += 256) {
myFile.read(sdBuffer, 256);
// sequence load to page
dataOut();
gbSmartWriteFlashByte(addr, 0xe0);
gbSmartWriteFlashByte(addr, 0xff);
gbSmartWriteFlashByte(addr, 0x00); // BCH should be 0x00
// fill page buffer
for (int d = 0; d < 256; d++)
gbSmartWriteFlashByte(d, sdBuffer[d]);
// start flashing page
gbSmartWriteFlashByte(addr, 0x0c);
gbSmartWriteFlashByte(addr, 0xff);
gbSmartWriteFlashByte(addr + i, 0x00); // BCH should be 0x00
// waiting for finishing
dataIn();
while ((readByte_GBS(addr + i) & 0x80) == 0x00)
;
}
// blink LED
blinkLED();
}
uint32_t gbSmartVerifyFlash() {
uint32_t verified = 0;
if (!myFile.open(filePath, O_READ)) {
verified = 0xffffffff;
print_Error(F("Can't open file on SD"));
} else {
// remaps mmc to full access
gbSmartRemapStartBank(0x00, gbSmartRomSizeGB, gbSmartSramSizeGB);
// verify bank 0x00
dataIn();
for (uint16_t addr = 0x0000; addr <= 0x3fff; addr += 512) {
myFile.read(sdBuffer, 512);
for (uint16_t c = 0; c < 512; c++) {
if (readByte_GBS(addr + c) != sdBuffer[c])
verified++;
}
}
// verify rest banks
for (uint16_t bank = 0x01; bank < gbSmartBanks; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
dataIn();
for (uint16_t addr = 0x4000; addr <= 0x7fff; addr += 512) {
myFile.read(sdBuffer, 512);
for (uint16_t c = 0; c < 512; c++) {
if (readByte_GBS(addr + c) != sdBuffer[c])
verified++;
}
}
}
// back to initial state
writeByte_GB(0x2100, 0x01);
myFile.close();
}
return verified;
}
byte gbSmartBlankCheckingFlash(uint8_t flash_start_bank) {
gbSmartRemapStartBank(flash_start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
// check first bank
dataIn();
for (uint16_t addr = 0x0000; addr <= 0x3fff; addr++) {
if (readByte_GBS(addr) != 0xff)
return 0;
}
// check rest banks
for (uint16_t bank = 0x01; bank < gbSmartBanksPerFlashChip; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
dataIn();
for (uint16_t addr = 0x4000; addr <= 0x7fff; addr++) {
if (readByte_GBS(addr) != 0xff)
return 0;
}
}
return 1;
}
void gbSmartResetFlash(uint8_t flash_start_bank) {
gbSmartRemapStartBank(flash_start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
dataOut();
gbSmartWriteFlashByte(0x0, 0xff);
}
void gbSmartEraseFlash(uint8_t flash_start_bank) {
gbSmartRemapStartBank(flash_start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
// handling first flash block
dataOut();
gbSmartWriteFlashByte(0x0000, 0x20);
gbSmartWriteFlashByte(0x0000, 0xd0);
dataIn();
while ((readByte_GBS(0x0000) & 0x80) == 0x00)
;
// blink LED
blinkLED();
// rest of flash block
for (uint32_t ba = gbSmartBanksPerFlashBlock; ba < gbSmartBanksPerFlashChip; ba += gbSmartBanksPerFlashBlock) {
dataOut();
writeByte_GB(0x2100, ba);
gbSmartWriteFlashByte(0x4000, 0x20);
gbSmartWriteFlashByte(0x4000, 0xd0);
dataIn();
while ((readByte_GBS(0x4000) & 0x80) == 0x00)
;
// blink LED
blinkLED();
}
}
void gbSmartWriteFlashByte(uint32_t myAddress, uint8_t myData) {
PORTF = myAddress & 0xff;
PORTK = (myAddress >> 8) & 0xff;
PORTC = myData;
// wait for 62.5 x 4 = 250ns
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
// Pull FLASH_WE (PH4) low
PORTH &= ~(1 << 4);
// pull low for another 250ns
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
// Pull FLASH_WE (PH4) high
PORTH |= (1 << 4);
// pull high for another 250ns
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
}
// rom_start_bank = 0x00 means back to original state
void gbSmartRemapStartBank(uint8_t rom_start_bank, uint8_t rom_size, uint8_t sram_size) {
rom_start_bank &= 0xfe;
dataOut();
// clear base bank setting
writeByte_GB(0x1000, 0xa5);
writeByte_GB(0x7000, 0x00);
writeByte_GB(0x1000, 0x98);
writeByte_GB(0x2000, rom_start_bank);
if (rom_start_bank > 1) {
// start set new base bank
writeByte_GB(0x1000, 0xa5);
dataIn();
rom_start_bank = gbSmartGetResizeParam(rom_size, sram_size);
dataOut();
writeByte_GB(0x7000, rom_start_bank);
writeByte_GB(0x1000, 0x98);
writeByte_GB(0x2100, 0x01);
}
dataIn();
}
// Get magic number for 0x7000 register.
// Use for setting correct rom and sram size
// Code logic is take from SmartCard32M V1.3 menu code,
// see 0x2db2 to 0x2e51 (0xa0 bytes)
uint8_t gbSmartGetResizeParam(uint8_t rom_size, uint8_t sram_size) {
if (rom_size < 0x0f) {
rom_size &= 0x07;
rom_size ^= 0x07;
} else {
rom_size = 0x01;
}
if (sram_size > 0) {
if (sram_size > 1) {
sram_size--;
sram_size ^= 0x03;
sram_size <<= 4;
sram_size &= 0x30;
} else {
sram_size = 0x20; // 2KiB treat as 8KiB
}
} else {
sram_size = 0x30; // no sram
}
return (sram_size | rom_size);
}
#endif
//******************************************
// End of File
//******************************************