mirror of
https://github.com/sanni/cartreader.git
synced 2024-12-27 05:21:53 +01:00
500 lines
12 KiB
C++
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
|