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.ino255
1 files changed, 255 insertions, 0 deletions
diff --git a/Cart_Reader/LYNX.ino b/Cart_Reader/LYNX.ino
new file mode 100644
index 0000000..4338099
--- /dev/null
+++ b/Cart_Reader/LYNX.ino
@@ -0,0 +1,255 @@
+//******************************************
+// 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 detectCart_LYNX() {
+ // Could omit logging to save a few bytes
+ display_Clear();
+ println_Msg(F("Identifying..."));
+
+ lynxUseAudin = false;
+ lynxBlockSize = 0;
+
+ // Somewhat arbitrary, however many bytes would be unlikely to be
+ // coincidentally mirrored
+ const size_t DETECT_BYTES = 128;
+
+ for (int i = 0; i < DETECT_BYTES; i++) {
+ uint8_t b = readByte_LYNX(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 lower/upper 2kb blocks)
+ if (b != readByte_LYNX(i, 1)) {
+ lynxUseAudin = true;
+ lynxBlockSize = 2048;
+ break;
+ }
+ // Identify mirroring of largest stride
+ // Valid cart sizes of 128kb, 256kb, 512kb / 256 blocks = block sizes of 512b, 1024b, 2048b
+ if (b != readByte_LYNX(i + 1024)) {
+ lynxBlockSize = max(lynxBlockSize, 2048);
+ } else if (b != readByte_LYNX(i + 512)) {
+ lynxBlockSize = max(lynxBlockSize, 1024);
+ } else if (b != readByte_LYNX(i + 256)) {
+ lynxBlockSize = max(lynxBlockSize, 512);
+ }
+ }
+
+ if (lynxBlockSize == 0) {
+ 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);
+}
+
+// Saves memory by using existing sd buffer instead of a second block-sized buffer (which could be up to 2KB)
+// Minimum block size is 512b, size of sdBuffer is 512b, all block sizes multiples of 512b,
+// so we shouldn't need to check for leftovers...
+static inline void ringBufferWrite_LYNX(uint32_t blocki, uint8_t byte) {
+ sdBuffer[blocki % 512] = byte;
+ if ((blocki + 1) % 512 == 0) {
+ myFile.write(sdBuffer, 512);
+ }
+}
+
+static void readROM_LYNX() {
+ 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
+
+ uint32_t i;
+ 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++) {
+ ringBufferWrite_LYNX(i, readByte_LYNX(blockAddr + i, 0));
+ }
+ for (; i < lynxBlockSize; i++) {
+ ringBufferWrite_LYNX(i, readByte_LYNX(blockAddr + i - (lynxBlockSize / 2), 1));
+ }
+ } else {
+ for (i = 0; i < lynxBlockSize; i++) {
+ ringBufferWrite_LYNX(i, readByte_LYNX(i + blockAddr));
+ }
+ }
+ }
+ 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