mirror of
https://github.com/sanni/cartreader.git
synced 2024-12-28 14:01:52 +01:00
Merge pull request #982 from partlyhuman/firmware-atari-lynx
Atari Lynx firmware 1.0
This commit is contained in:
commit
ee923a03bc
@ -185,7 +185,7 @@ template<class T> int EEPROM_readAnything(int ee, T& value) {
|
||||
#define rotate_to_change_STR 15
|
||||
#define press_to_select_STR 16
|
||||
|
||||
// This arrays holds the most often uses strings
|
||||
// This array holds the most often used strings
|
||||
constexpr char string_press_button0[] PROGMEM = "Press Button...";
|
||||
constexpr char string_sd_error1[] PROGMEM = "SD Error";
|
||||
constexpr char string_did_not_verify3[] PROGMEM = "did not verify";
|
||||
@ -290,7 +290,7 @@ boolean root = 0;
|
||||
boolean filebrowse = 0;
|
||||
|
||||
// Common
|
||||
// 21 chars for SNES ROM name, one char for termination
|
||||
// 21 chars for ROM name, one char for termination
|
||||
char romName[22];
|
||||
unsigned long sramSize = 0;
|
||||
int romType = 0;
|
||||
@ -458,7 +458,7 @@ uint32_t calculateCRC(char* fileName, char* folder, unsigned long offset) {
|
||||
/******************************************
|
||||
CRC Functions for Atari, Fairchild, Ody2, Arc, etc. modules
|
||||
*****************************************/
|
||||
#if (defined(ENABLE_ODY2) || defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_MSX) || defined(ENABLE_POKE) || defined(ENABLE_2600) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64) || defined(ENABLE_VECTREX) || defined(ENABLE_NES))
|
||||
#if (defined(ENABLE_ODY2) || defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_MSX) || defined(ENABLE_POKE) || defined(ENABLE_2600) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64) || defined(ENABLE_VECTREX) || defined(ENABLE_NES) || defined(ENABLE_LYNX))
|
||||
|
||||
void printCRC(char* checkFile, uint32_t* crcCopy, unsigned long offset) {
|
||||
uint32_t crc = calculateCRC(checkFile, folder, offset);
|
||||
@ -1093,10 +1093,11 @@ constexpr char modeItem22[] PROGMEM = "Casio Loopy";
|
||||
constexpr char modeItem23[] PROGMEM = "Commodore 64";
|
||||
constexpr char modeItem24[] PROGMEM = "Atari 5200";
|
||||
constexpr char modeItem25[] PROGMEM = "Atari 7800";
|
||||
constexpr char modeItem26[] PROGMEM = "Vectrex";
|
||||
constexpr char modeItem27[] PROGMEM = "Flashrom Programmer";
|
||||
constexpr char modeItem28[] PROGMEM = "Self Test (3V)";
|
||||
constexpr char modeItem29[] PROGMEM = "About";
|
||||
constexpr char modeItem26[] PROGMEM = "Atari Lynx";
|
||||
constexpr char modeItem27[] PROGMEM = "Vectrex";
|
||||
constexpr char modeItem28[] PROGMEM = "Flashrom Programmer";
|
||||
constexpr char modeItem29[] PROGMEM = "Self Test (3V)";
|
||||
constexpr char modeItem30[] PROGMEM = "About";
|
||||
|
||||
static const char* const modeOptions[] PROGMEM = {
|
||||
#ifdef ENABLE_GBX
|
||||
@ -1174,16 +1175,20 @@ static const char* const modeOptions[] PROGMEM = {
|
||||
#ifdef ENABLE_7800
|
||||
modeItem25,
|
||||
#endif
|
||||
#ifdef ENABLE_VECTREX
|
||||
#ifdef ENABLE_LYNX
|
||||
modeItem26,
|
||||
#endif
|
||||
#ifdef ENABLE_FLASH
|
||||
#ifdef ENABLE_VECTREX
|
||||
modeItem27,
|
||||
#endif
|
||||
#ifdef ENABLE_SELFTEST
|
||||
#ifdef ENABLE_FLASH
|
||||
modeItem28,
|
||||
#endif
|
||||
modeItem29, FSTRING_RESET
|
||||
#ifdef ENABLE_SELFTEST
|
||||
modeItem29,
|
||||
#endif
|
||||
modeItem30,
|
||||
FSTRING_RESET
|
||||
};
|
||||
|
||||
uint8_t pageMenu(const __FlashStringHelper* question, const char* const* menuStrings, uint8_t entryCount, uint8_t default_choice = 0) {
|
||||
@ -1387,6 +1392,13 @@ void mainMenu() {
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LYNX
|
||||
case SYSTEM_MENU_LYNX:
|
||||
setup_LYNX();
|
||||
return lynxMenu();
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VECTREX
|
||||
case SYSTEM_MENU_VECTREX:
|
||||
setup_VECTREX();
|
||||
@ -3585,6 +3597,9 @@ void loop() {
|
||||
#ifdef ENABLE_7800
|
||||
case CORE_7800: return a7800Menu();
|
||||
#endif
|
||||
#ifdef ENABLE_LYNX
|
||||
case CORE_LYNX: return lynxMenu();
|
||||
#endif
|
||||
#ifdef ENABLE_VECTREX
|
||||
case CORE_VECTREX: return vectrexMenu();
|
||||
#endif
|
||||
|
@ -89,6 +89,13 @@
|
||||
|
||||
/****/
|
||||
|
||||
/* [ Atari LYNX --------------------------------------------------- ]
|
||||
*/
|
||||
|
||||
//#define ENABLE_LYNX
|
||||
|
||||
/****/
|
||||
|
||||
/* [ Benesse Pocket Challenge W ----------------------------------- ]
|
||||
*/
|
||||
|
||||
|
255
Cart_Reader/LYNX.ino
Normal file
255
Cart_Reader/LYNX.ino
Normal file
@ -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
|
@ -216,6 +216,9 @@ enum CORES: uint8_t {
|
||||
# ifdef ENABLE_7800
|
||||
CORE_7800,
|
||||
# endif
|
||||
# ifdef ENABLE_LYNX
|
||||
CORE_LYNX,
|
||||
# endif
|
||||
# ifdef ENABLE_VECTREX
|
||||
CORE_VECTREX,
|
||||
# endif
|
||||
@ -304,6 +307,9 @@ enum SYSTEM_MENU: uint8_t {
|
||||
# if defined(ENABLE_7800)
|
||||
SYSTEM_MENU_7800,
|
||||
# endif
|
||||
# if defined(ENABLE_LYNX)
|
||||
SYSTEM_MENU_LYNX,
|
||||
# endif
|
||||
# if defined(ENABLE_VECTREX)
|
||||
SYSTEM_MENU_VECTREX,
|
||||
# endif
|
||||
|
@ -46,6 +46,7 @@ Happy making. 🔧🔨😊
|
||||
- Benesse Pocket Challenge V2
|
||||
- Watara Supervision
|
||||
- Atari 2600/5200/7800
|
||||
- Atari Lynx
|
||||
- Emerson Arcadia 2001
|
||||
- Fairchild Channel F
|
||||
- Magnavox Odyssey 2/Philips Videopac+
|
||||
|
1140
sd/lynx.txt
Normal file
1140
sd/lynx.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user