diff options
Diffstat (limited to 'Cart_Reader/LYNX.ino')
-rw-r--r-- | Cart_Reader/LYNX.ino | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/Cart_Reader/LYNX.ino b/Cart_Reader/LYNX.ino new file mode 100644 index 0000000..71d9991 --- /dev/null +++ b/Cart_Reader/LYNX.ino @@ -0,0 +1,263 @@ +//****************************************** +// ATARI LYNX MODULE +//****************************************** +// +// For use with SNES-Lynx adapter +// +----+ +// | 1 |- GND +// | 2 |- D3 +// | 3 |- D2 +// | 4 |- D4 +// | 5 |- D1 +// | 6 |- D5 +// | 7 |- D0 +// | 8 |- D6 +// | 9 |- D7 +// | 10 |- /OE +// | 11 |- A1 +// | 12 |- A2 +// | 13 |- A3 +// | 14 |- A6 +// | 15 |- A4 +// | 16 |- A5 +// | 17 |- A0 +// | 18 |- A7 +// | 19 |- A16 +// | 20 |- A17 +// | 21 |- A18 +// | 22 |- A19 +// | 23 |- A15 +// | 24 |- A14 +// | 25 |- A13 +// | 26 |- A12 +// | 27 |- /WE +// | 28 |- A8 +// | 29 |- A9 +// | 30 |- A10 +// | 31 |- VCC +// | 32 |- AUDIN +// | 33 |- VCC +// | 34 |- SWVCC +// +----+ +// +// By @partlyhuman +// This implementation would not be possible without the invaluable +// documentation on +// https://atarilynxvault.com/ +// by Igor (@theatarigamer) of K-Retro Gaming / Atari Lynx Vault +// and the reference implementation of the Lynx Cart Programmer Pi-Hat +// https://bitbucket.org/atarilynx/lynx/src/master/ +// by Karri Kaksonen (whitelynx.fi) and Igor as well as countless contributions +// by the Atari Lynx community +// +// Version 1.0 +// Future enhancements +// 1. EEPROM read/write +// 2. Homebrew flash cart programming +// +#ifdef ENABLE_LYNX + +#pragma region DEFS + +#define LYNX_HEADER_SIZE 64 +#define LYNX_WE 8 +#define LYNX_OE 9 +#define LYNX_AUDIN 46 +#define LYNX_BLOCKADDR 2048UL +#define LYNX_BLOCKCOUNT 256UL +// Includes \0 +static const char LYNX[5] = "LYNX"; + +// Cart information +static bool lynxUseAudin; +static uint16_t lynxBlockSize; + +#pragma region LOWLEVEL + +void setup_LYNX() { + setVoltage(VOLTS_SET_5V); + + // Address pins output + // A0-7, A8-A16 (A11 doesn't exist) + DDRF = 0xff; + DDRK = 0xff; + DDRL = 0xff; + + // Data pins input + DDRC = 0x00; + + // Control pins output + // CE is tied low, not accessible + pinMode(LYNX_WE, OUTPUT); + pinMode(LYNX_OE, OUTPUT); + pinMode(LYNX_AUDIN, OUTPUT); + digitalWrite(LYNX_WE, HIGH); + digitalWrite(LYNX_OE, HIGH); + digitalWrite(LYNX_AUDIN, HIGH); + + strcpy(romName, LYNX); + mode = CORE_LYNX; +} + +static void dataDir_LYNX(byte direction) { + DDRC = (direction == OUTPUT) ? 0xff : 0x00; +} + +static uint8_t readByte_LYNX(uint32_t addr, uint8_t audin = 0) { + digitalWrite(LYNX_OE, HIGH); + PORTF = addr & 0xff; + PORTK = (addr >> 8) & 0xff; + PORTL = ((addr >> 16) & 0b111) | (audin << 3); + digitalWrite(LYNX_OE, LOW); + delayMicroseconds(20); + uint8_t data = PINC; + digitalWrite(LYNX_OE, HIGH); + return data; +} + +#pragma region HIGHLEVEL + +static bool detectBlockSize_LYNX() { + lynxUseAudin = false; + lynxBlockSize = 0; + + int i; + uint8_t block[LYNX_BLOCKADDR]; + for (i = 0; i < LYNX_BLOCKADDR; i++) { + block[i] = readByte_LYNX(i, 0); + } + + for (i = 0; i < LYNX_BLOCKADDR; i++) { + // If any differences are detected when AUDIN=1, + // AUDIN is used to bankswitch + // meaning we also use the maximum block size + // (1024kb cart / 256 blocks = 4kb block bank switched between two + // lower/upper 2kb blocks) + if (block[i] != readByte_LYNX(i, 1)) { + lynxUseAudin = true; + lynxBlockSize = 2048; + return true; + } + } + + // Use the already-dumped 2KB to detect mirroring in a small sample + // Valid cart sizes of 128kb, 256kb, 512kb / 256 blocks + // = block sizes of 512b, 1024b, 2048b + const size_t DETECT_BYTES = 128; + for (i = 0; i < DETECT_BYTES; i++) { + if (block[i] != block[i + 256]) { + lynxBlockSize = max(lynxBlockSize, 512); + } + if (block[i] != block[i + 512]) { + lynxBlockSize = max(lynxBlockSize, 1024); + } + if (block[i] != block[i + 1024]) { + lynxBlockSize = max(lynxBlockSize, 2048); + } + } + + return (lynxBlockSize > 0); +} + +static bool detectCart_LYNX() { + // Could omit logging to save a few bytes + display_Clear(); + println_Msg(F("Identifying...")); + if (!detectBlockSize_LYNX()) { + print_STR(error_STR, false); + display_Update(); + wait(); + resetArduino(); + } + print_Msg(F("AUDIN=")); + print_Msg(lynxUseAudin); + print_Msg(F(" BLOCK=")); + println_Msg(lynxBlockSize); + display_Update(); +} + +static void writeHeader_LYNX() { + char header[LYNX_HEADER_SIZE] = {}; + // Magic number + strcpy(header, LYNX); + // Cart name (dummy) + strcpy(header + 10, LYNX); + // Manufacturer (dummy) + strcpy(header + 42, LYNX); + // Version + header[8] = 1; + // Bank 0 page size + header[4] = lynxBlockSize & 0xff; + // Bank 1 page size + header[5] = (lynxBlockSize >> 8) & 0xff; + // AUDIN used + header[59] = lynxUseAudin; + // TODO detect EEPROM? + // header[60] = lynxUseEeprom; + myFile.write(header, LYNX_HEADER_SIZE); +} + +static void readROM_LYNX() { + uint8_t block[lynxBlockSize]; + uint32_t i; + + dataDir_LYNX(INPUT); + + // The upper part of the address is used as a block address + // There are always 256 blocks, but the size of the block can vary + // So outer loop always steps through block addresses + + const uint32_t upto = LYNX_BLOCKCOUNT * LYNX_BLOCKADDR; + for (uint32_t blockAddr = 0; blockAddr < upto; blockAddr += LYNX_BLOCKADDR) { + draw_progressbar(blockAddr, upto); + blinkLED(); + + if (lynxUseAudin) { + // AUDIN bank switching uses a 4kb block split to 2 banks + for (i = 0; i < lynxBlockSize / 2; i++) { + block[i] = readByte_LYNX(blockAddr + i, 0); + } + for (; i < lynxBlockSize; i++) { + block[i] = readByte_LYNX(blockAddr + i - (lynxBlockSize / 2), 1); + } + } else { + for (i = 0; i < lynxBlockSize; i++) { + block[i] = readByte_LYNX(i + blockAddr); + } + } + + myFile.write(block, lynxBlockSize); + } + draw_progressbar(upto, upto); +} + +#pragma region MENU + +static const char* const menuOptionsLYNX[] PROGMEM = {FSTRING_READ_ROM, + FSTRING_RESET}; + +void lynxMenu() { + size_t menuCount = sizeof(menuOptionsLYNX) / sizeof(menuOptionsLYNX[0]); + convertPgm(menuOptionsLYNX, menuCount); + uint8_t mainMenu = question_box(F("LYNX MENU"), menuOptions, menuCount, 0); + display_Clear(); + display_Update(); + + switch (mainMenu) { + case 0: + sd.chdir("/"); + createFolderAndOpenFile(LYNX, "ROM", romName, "lnx"); + detectCart_LYNX(); + writeHeader_LYNX(); + readROM_LYNX(); + myFile.close(); + sd.chdir("/"); + compareCRC("lynx.txt", 0, true, LYNX_HEADER_SIZE); + print_STR(done_STR, true); + display_Update(); + wait(); + break; + } +} + +#endif
\ No newline at end of file |