cartreader/Cart_Reader/ATARI8.ino
2024-07-08 19:25:29 +02:00

500 lines
12 KiB
C++

//******************************************
// ATARI 8-bit (400/800/XL/XE) MODULE
//******************************************
#ifdef ENABLE_ATARI8
// Atari 8-bit (400/800/XL/XE)
// Left Slot Cartridge Pinout
// 30P 2.54mm pitch
//
// RIGHT
// +------+
// PHI2 -| S 15 |- /CCTL
// R/W -| R 14 |- RD5
// L A10 -| P 13 |- +5V B
// A A11 -| N 12 |- /S5 O
// B D7 -| M 11 |- D6 T
// E D3 -| L 10 |- D0 T
// L A12 -| K 9 |- D1 O
// A9 -| J 8 |- D2 M
// S A8 -| H 7 |- D5
// I A7 -| F 6 |- D4 S
// D A6 -| E 5 |- A0 I
// E A5 -| D 4 |- A1 D
// A4 -| C 3 |- A2 E
// GND -| B 2 |- A3
// RD4 -| A 1 |- /S4
// +------+
// LEFT
//
// LABEL SIDE
//
// RD4 GND A4 A5 A6 A7 A8 A9 A12 D3 D7 A11 A10 R/W PHI2
// +-------------------------------------------------------------+
// | A B C D E F H J K L M N P R S |
// LEFT | | RIGHT
// | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// +-------------------------------------------------------------+
// /S4 A3 A2 A1 A0 D4 D5 D2 D1 D0 D6 /S5 +5V RD5 /CCTL
//
// BOTTOM SIDE
//
// CONTROL PINS:
// RD4(PH0) - SNES RESET
// PHI2(PH1) - SNES CPUCLK
// /S5(PH3) - SNES /CS
// RD5(PH4) - SNES /IRQ
// R/W(PH5) - SNES /WR
// /S4(PH6) - SNES /RD
// /CCTL(PL0) - SNES A16
//******************************************
// DEFINES
//******************************************
#define DISABLE_S4 PORTH |= (1 << 6) // ROM SELECT $8000-$9FFF
#define ENABLE_S4 PORTH &= ~(1 << 6)
#define DISABLE_S5 PORTH |= (1 << 3) // ROM SELECT $A000-$BFFF
#define ENABLE_S5 PORTH &= ~(1 << 3)
#define DISABLE_CCTL PORTL |= (1 << 0) // CARTRIDGE CONTROL BLOCK $D500-$D5FF
#define ENABLE_CCTL PORTL &= ~(1 << 0)
//******************************************
// VARIABLES
//******************************************
byte ATARI8[] = {8,16,32,40,64,128};
byte atari8lo = 0; // Lowest Entry
byte atari8hi = 5; // Highest Entry
byte atari8size;
byte newatari8size;
boolean atari8right = 0; // 0 = LEFT Slot, 1 = RIGHT Slot
// EEPROM MAPPING
// 07 MAPPER [ATARI 8-BIT SLOT]
// 08 ROM SIZE
//******************************************
// MENU
//******************************************
// Base Menu
static const char atari8MenuItem3[] PROGMEM = "Read RIGHT ROM";
static const char* const menuOptionsATARI8[] PROGMEM = { FSTRING_SELECT_CART, FSTRING_READ_ROM, atari8MenuItem3, FSTRING_SET_SIZE, FSTRING_RESET };
void atari8Menu()
{
convertPgm(menuOptionsATARI8, 5);
uint8_t mainMenu = question_box(F("ATARI 8-BIT MENU"), menuOptions, 5, 0);
switch (mainMenu)
{
// Select Cart
case 0:
setCart_ATARI8();
setup_ATARI8();
break;
// Read LEFT Slot Cart
case 1:
sd.chdir("/");
atari8right = 0; // LEFT Slot
readROM_ATARI8();
sd.chdir("/");
break;
// Read RIGHT Slot Cart
case 2:
sd.chdir("/");
atari8right = 1; // RIGHT Slot
readROM_ATARI8();
sd.chdir("/");
break;
case 3:
// Set Size
setROMSize_ATARI8();
break;
case 4:
// reset
resetArduino();
break;
}
}
//******************************************
// SETUP
//******************************************
void setup_ATARI8()
{
// Request 5V
setVoltage(VOLTS_SET_5V);
// Set Address Pins to Output
// Atari 8-bit uses A0-A12 [A13-A23 UNUSED]
//A0-A7
DDRF = 0xFF;
//A8-A15
DDRK = 0xFF;
//A16-A23
DDRL = 0xFF; // Use A16 for /CCTL Output
// Set Control Pins to Output
// PHI2(PH1) /S5(PH3) R/W(PH5) /S4(PH6)
DDRH |= (1 << 1) | (1 << 3) | (1 << 5) | (1 << 6);
// Set RD4 & RD5 to Input
// RD4(PH0) RD5(PH4)
DDRH &= ~((1 << 0) | (1 << 4));
// Set TIME(PJ0) to Output (UNUSED)
DDRJ |= (1 << 0);
// Set Pins (D0-D7) to Input
DDRC = 0x00;
// Setting Control Pins to HIGH
// PHI2(PH1) /S5(PH3) R/W(PH5) /S4(PH6)
//PORTH |= (1 << 1) | (1 << 3) | (1 << 5) | (1 << 6);
// /S5(PH3) R/W(PH5) /S4(PH6)
PORTH |= (1 << 3) | (1 << 5) | (1 << 6);
// Set Unused Data Pins (PA0-PA7) to Output
DDRA = 0xFF;
// Set Unused Pins HIGH
PORTA = 0xFF;
PORTL = 0xFF; // A16-A23 (A16 used for /CCTL Output)
PORTJ |= (1 << 0); // TIME(PJ0)
checkStatus_ATARI8();
strcpy(romName, "ATARI");
mode = CORE_ATARI8;
}
//******************************************
// READ FUNCTIONS
//******************************************
uint8_t readData_ATARI8(uint16_t addr) // Add Input Pullup
{
PORTF = addr & 0xFF; // A0-A7
PORTK = (addr >> 8) & 0xFF; // A8-A12
NOP;
NOP;
NOP;
NOP;
NOP;
// DDRC = 0x00; // Set to Input
PORTC = 0xFF; // Input Pullup
NOP;
NOP;
NOP;
NOP;
NOP;
// Extended Delay
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
uint8_t ret = PINC;
NOP;
NOP;
NOP;
NOP;
NOP;
return ret;
}
void readSegment_ATARI8(uint16_t startaddr, uint16_t endaddr)
{
for (uint16_t addr = startaddr; addr < endaddr; addr += 512) {
for (int w = 0; w < 512; w++) {
uint8_t temp = readData_ATARI8(addr + w);
sdBuffer[w] = temp;
}
myFile.write(sdBuffer, 512);
}
}
void readBountyBobBank_ATARI8(uint16_t startaddr)
{
for (uint8_t w = 0; w < 4; w++) {
readData_ATARI8(startaddr + 0x0FF6 + w);
readSegment_ATARI8(startaddr, startaddr + 0x0E00);
// Split Read of Last 0x200 bytes
for (int x = 0; x < 0x1F6; x++) {
sdBuffer[x] = readData_ATARI8(startaddr + 0x0E00 + x);
}
myFile.write(sdBuffer, 502);
// Bank Registers 0xFF6-0xFF9
for (int y = 0; y < 4; y++){
readData_ATARI8(startaddr + 0x0FFF); // Reset Bank
sdBuffer[y] = readData_ATARI8(startaddr + 0x0FF6 + y);
}
// End of Bank 0x8FFA-0x8FFF
readData_ATARI8(startaddr + 0x0FFF); // Reset Bank
readData_ATARI8(startaddr + 0x0FF6 + w); // Set Bank
for (int z = 4; z < 10; z++) {
sdBuffer[z] = readData_ATARI8(startaddr + 0x0FF6 + z); // 0xFFA-0xFFF
}
myFile.write(sdBuffer, 10);
}
readData_ATARI8(startaddr + 0x0FFF); // Reset Bank
}
void bankSwitch_ATARI8(uint8_t bank)
{
// write to $D5XX using /CCTL
// CCTL sets upper 3 bits of 16-bit address to 110
// CCTL = [110] 1 0101 0000 0000 = $D500
ENABLE_CCTL;
// Set Address $D500
PORTF = 0x00; // A0-A7
PORTK = 0xD5; // A8-A12
NOP;
NOP;
NOP;
NOP;
NOP;
// Set Data to Output
DDRC = 0xFF;
// Set R/W to WRITE
PORTH &= ~(1 << 5);
// Set Bank
PORTC = bank;
NOP;
NOP;
NOP;
NOP;
NOP;
// Pulse Clock
// CPU CLOCK 1.7897725MHz(NTSC)/1.7734470MHz(PAL/SECAM)
// 1.7897725MHz = 1 cycle = 558.73023ns = 279.365115/279.365115
for (int i = 0; i < 2; i++) {
PORTH ^= (1 << 1);
// NOP (62.5ns) x 4 = 250ns = 0.25us
NOP;
NOP;
NOP;
NOP; // 4 NOPs = 4 x 62.5ns = 250ns x 2 = 500ns = 2 MHz
}
// Set R/W to READ
PORTH |= (1 << 5);
// Reset Data to Input
DDRC = 0x00;
DISABLE_CCTL;
}
//******************************************
// READ ROM
//******************************************
void readROM_ATARI8()
{
createFolderAndOpenFile("ATARI8", "ROM", romName, "bin");
// Store Slot Setting to EEPROM
EEPROM_writeAnything(7, atari8right); // 0 = LEFT, 1 = RIGHT
// ATARI 8-bit A12-A0 = 1 0000 0000 0000
// S4 [100]/S5 [101] are the upper 3 bits for 16-bit address
// S4 = [100] 1 1111 1111 1111 = $8000-$9FFF
// S5 = [101] 1 1111 1111 1111 = $A000-$BFFF
if (atari8right) { // RIGHT Slot Cartridge 8K
// Right slot carts are 8K mapped to $8000-$9FFF
// Right slot carts use /S4 assigned to Pin 12
// Pin 12 = RIGHT Slot /S4 = LEFT Slot /S5
ENABLE_S5;
readSegment_ATARI8(0x8000,0xA000); // 8K
DISABLE_S5;
// Correct Size to 8K
atari8size = 0; // 8K
EEPROM_writeAnything(8, atari8size);
}
else if (atari8size == 3) { // Bounty Bob Strikes Back 40K
ENABLE_S4;
// First 16KB (4KB x 4)
readBountyBobBank_ATARI8(0x8000);
// Second 16KB (4KB x 4)
readBountyBobBank_ATARI8(0x9000);
DISABLE_S4;
ENABLE_S5;
readSegment_ATARI8(0xA000, 0xC000); // +8K = 40K
DISABLE_S5;
}
else if (atari8size > 1) { // XE Carts 32K/64K/128K
// Bug Hunt and Lode Runner dump as 128K. Trim beginning 64K as both carts start from Bank 8.
int banks = (ATARI8[atari8size] / 8) - 1;
for (int x = 0; x < banks; x++) {
bankSwitch_ATARI8(x);
ENABLE_S4;
readSegment_ATARI8(0x8000,0xA000); // 8K
DISABLE_S4;
}
// Last Bank
ENABLE_S5;
readSegment_ATARI8(0xA000,0xC000); // +8K
DISABLE_S5;
}
else { // Standard LEFT Cart 8K/16K
if (atari8size == 1) {
// Add XE Bankswitch for Necromancer 16K
bankSwitch_ATARI8(0);
// Standard 16K
ENABLE_S4;
readSegment_ATARI8(0x8000,0xA000); // +8K = 16K
DISABLE_S4;
}
ENABLE_S5;
readSegment_ATARI8(0xA000,0xC000); // 8K
DISABLE_S5;
}
myFile.close();
printCRC(fileName, NULL, 0);
println_Msg(FS(FSTRING_EMPTY));
print_STR(press_button_STR, 1);
display_Update();
wait();
}
//******************************************
// ROM SIZE
//******************************************
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
void printRomSize_ATARI8(int index)
{
display_Clear();
print_Msg(FS(FSTRING_ROM_SIZE));
println_Msg(ATARI8[index]);
}
#endif
void setROMSize_ATARI8()
{
byte newatari8size;
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
display_Clear();
if (atari8lo == atari8hi)
newatari8size = atari8lo;
else {
newatari8size = navigateMenu(atari8lo, atari8hi, &printRomSize_ATARI8);
display.setCursor(0, 56); // Display selection at bottom
}
print_Msg(FS(FSTRING_ROM_SIZE));
print_Msg(ATARI8[newatari8size]);
println_Msg(F("KB"));
display_Update();
delay(1000);
#else
if (atari8lo == atari8hi)
newatari8size = atari8lo;
else {
setrom:
String sizeROM;
for (int i = 0; i < (atari8hi - atari8lo + 1); i++) {
Serial.print(F("Select ROM Size: "));
Serial.print(i);
Serial.print(F(" = "));
Serial.print(ATARI8[i + atari8lo]);
Serial.println(F("KB"));
}
Serial.print(F("Enter ROM Size: "));
while (Serial.available() == 0) {}
sizeROM = Serial.readStringUntil('\n');
Serial.println(sizeROM);
newatari8size = sizeROM.toInt() + atari8lo;
if (newatari8size > atari8hi) {
Serial.println(F("SIZE NOT SUPPORTED"));
Serial.println(FS(FSTRING_EMPTY));
goto setrom;
}
}
Serial.print(F("ROM Size = "));
Serial.print(ATARI8[newatari8size]);
Serial.println(F("KB"));
#endif
EEPROM_writeAnything(8, newatari8size);
atari8size = newatari8size;
}
void checkStatus_ATARI8()
{
EEPROM_readAnything(7, atari8right);
if (atari8right != 1) {
atari8right = 0; // default LEFT Slot
EEPROM_writeAnything(7, atari8right);
}
EEPROM_readAnything(8, atari8size);
if (atari8size > atari8hi) {
atari8size = 1; // default 16K
EEPROM_writeAnything(8, atari8size);
}
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
display_Clear();
println_Msg(F("ATARI 8-BIT READER"));
println_Msg(FS(FSTRING_CURRENT_SETTINGS));
println_Msg(FS(FSTRING_EMPTY));
print_Msg(F("SLOT: "));
if (atari8right)
println_Msg(F("RIGHT"));
else
println_Msg(F("LEFT"));
print_Msg(FS(FSTRING_ROM_SIZE));
print_Msg(ATARI8[atari8size]);
println_Msg(F("KB"));
display_Update();
wait();
#else
Serial.print(FS(FSTRING_ROM_SIZE));
Serial.print(ATARI8[atari8size]);
Serial.println(F("KB"));
Serial.println(FS(FSTRING_EMPTY));
#endif
}
//******************************************
// CART SELECT CODE
//******************************************
void setCart_ATARI8()
{
//go to root
sd.chdir();
struct database_entry_mapper_size entry;
// Select starting letter
byte myLetter = starting_letter();
// Open database
if (myFile.open("atari8cart.txt", O_READ)) {
seek_first_letter_in_database(myFile, myLetter);
if(checkCartSelection(myFile, &readDataLineMapperSize, &entry)) {
EEPROM_writeAnything(7, entry.gameMapper);
EEPROM_writeAnything(8, entry.gameSize);
}
} else {
print_FatalError(FS(FSTRING_DATABASE_FILE_NOT_FOUND));
}
}
#endif