aboutsummaryrefslogtreecommitdiffhomepage
path: root/Cart_Reader/LYNX.ino
diff options
context:
space:
mode:
Diffstat (limited to 'Cart_Reader/LYNX.ino')
-rw-r--r--Cart_Reader/LYNX.ino263
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