mirror of
https://github.com/sanni/cartreader.git
synced 2024-12-25 12:31:54 +01:00
555 lines
13 KiB
C++
555 lines
13 KiB
C++
//******************************************
|
|
// LITTLE JAMMER PRO MODULE
|
|
//******************************************
|
|
#ifdef ENABLE_LJPRO
|
|
// Little Jammer Pro
|
|
// Cartridge Pinout
|
|
// 48P 1.25mm pitch connector
|
|
//
|
|
// FORM FACTOR IS SAME AS BANDAI WONDERSWAN/BENESSE POCKET CHALLENGE V2/LITTLE JAMMER
|
|
// WIRING IS COMPLETELY DIFFERENT!
|
|
//
|
|
// LEFT SIDE
|
|
// 1 GND
|
|
// 2 GND
|
|
// 3 S1 (GND)
|
|
// 4 S2 (GND)
|
|
// 5 U1_WP#/ACC
|
|
// 6 U1_SCLK
|
|
// 7 U1_SCLK
|
|
// 8 U1_SI
|
|
// 9 U1_SI
|
|
// 10 U1_SO/PO7
|
|
// 11 U1_SO/PO7
|
|
// 12 U1_PO6
|
|
// 13 U1_PO5
|
|
// 14 U1_PO4
|
|
// 15 U1_PO3
|
|
// 16 U1_PO2
|
|
// 17 U1_PO1
|
|
// 18 U1_PO0
|
|
// 19 U1_CS#
|
|
// 20 U1_CS#
|
|
// 21 U1_HOLD#
|
|
// 22 U1_HOLD#
|
|
// 23 VCC (+3V)
|
|
// 24 VCC (+3V)
|
|
// 25 VCC (+3V)
|
|
// 26 VCC (+3V)
|
|
// 27 U2_SCLK
|
|
// 28 U2_SCLK
|
|
// 29 U2_SI
|
|
// 30 U2_SI
|
|
// 31 U2_SO/PO7
|
|
// 32 U2_SO/PO7
|
|
// 33 U2_PO6
|
|
// 34 U2_PO5
|
|
// 35 U2_PO4
|
|
// 36 U2_PO3
|
|
// 37 U2_PO2
|
|
// 38 U2_PO1
|
|
// 39 U2_PO0
|
|
// 40 U2_CS#
|
|
// 41 U2_CS#
|
|
// 42 U2_HOLD#
|
|
// 43 U2_HOLD#
|
|
// 44 U2_WP#/ACC
|
|
// 45 S3 (GND)
|
|
// 46 S4 (GND)
|
|
// 47 GND
|
|
// 48 GND
|
|
// RIGHT SIDE
|
|
|
|
// CONTROL PINS:
|
|
// U1_HOLD# (PH4) - SNES /IRQ
|
|
// U1_CS# (PK0) - SNES A8
|
|
// U1_SI (PK1) - SNES A9
|
|
// U1_WP#/ACC (PK2) - SNES A10
|
|
//
|
|
// U2_HOLD# (PH0) - SNES RESET
|
|
// U2_SI (PH3) - SNES /CS
|
|
// U2_WP#/ACC (PH5) - SNES /WR
|
|
// U2_CS# (PH6) - SNES /RD
|
|
//
|
|
// S1 (PK4) - SNES A12
|
|
// S2 (PK5) - SNES A13
|
|
// S3 (PK6) - SNES A14
|
|
// S4 (PK7) - SNES A15
|
|
|
|
// COMBINE U1_SCLK + U2_SCLK INTO SINGLE SCLK
|
|
// SCLK(PH1) - SNES CPUCLK
|
|
|
|
// DATA PINS:
|
|
// U1 D0-D7 (PORTF)
|
|
// U2 D0-D7 (PORTC)
|
|
|
|
// NOTES:
|
|
// HOLD# NOT USED FOR PARALLEL MODE - PULLED UP TO VCC ON CARTS
|
|
// WP#/ACC PULLED DOWN TO GND ON CARTS
|
|
|
|
//******************************************
|
|
// DEFINES
|
|
//******************************************
|
|
#define CS1_LOW PORTK &= ~(1 << 0)
|
|
#define CS1_HIGH PORTK |= (1 << 0)
|
|
#define CS2_LOW PORTH &= ~(1 << 6)
|
|
#define CS2_HIGH PORTH |= (1 << 6)
|
|
|
|
//******************************************
|
|
// VARIABLES
|
|
//******************************************
|
|
byte LJPRO[] = {2,4,6,8};
|
|
byte ljprolo = 0; // Lowest Entry
|
|
byte ljprohi = 3; // Highest Entry
|
|
byte ljprosize;
|
|
byte newljprosize;
|
|
|
|
char mnfID[3];
|
|
char deviceID[5];
|
|
boolean ljproflash1found = false;
|
|
boolean ljproflash2found = false;
|
|
byte ljproflash1size;
|
|
byte ljproflash2size;
|
|
|
|
// EEPROM MAPPING
|
|
// 08 ROM SIZE
|
|
|
|
//******************************************
|
|
// MENU
|
|
//******************************************
|
|
// Base Menu
|
|
static const char* const menuOptionsLJPRO[] PROGMEM = { FSTRING_READ_ROM, FSTRING_SET_SIZE, FSTRING_RESET };
|
|
|
|
// U1_HOLD#(PH4) - SNES /IRQ
|
|
// U1_CS# - SNES A8
|
|
// U1_WP#/ACC - SNES A9
|
|
// U1_SI - SNES A10
|
|
//
|
|
// U2_HOLD#(PH0) - SNES RESET
|
|
// U2_SI(PH3) - SNES /CS
|
|
// U2_WP#/ACC(PH5) - SNES /WR
|
|
// U2_CS#(PH6) - SNES /RD
|
|
|
|
void ljproMenu()
|
|
{
|
|
convertPgm(menuOptionsLJPRO, 3);
|
|
uint8_t mainMenu = question_box(F("LITTLE JAMMER PRO"), menuOptions, 3, 0);
|
|
|
|
switch (mainMenu)
|
|
{
|
|
case 0:
|
|
// Read ROM
|
|
sd.chdir("/");
|
|
readROM_LJPRO();
|
|
sd.chdir("/");
|
|
break;
|
|
|
|
case 1:
|
|
// Set Size
|
|
setROMSize_LJPRO();
|
|
break;
|
|
|
|
case 2:
|
|
// reset
|
|
resetArduino();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//******************************************
|
|
// SETUP
|
|
//******************************************
|
|
|
|
void setup_LJPRO()
|
|
{
|
|
// Request 3.3V
|
|
setVoltage(VOLTS_SET_3V3);
|
|
|
|
// LITTLE JAMMER PRO uses Serial Flash
|
|
// Set Data Pins to Input
|
|
DDRF = 0x00; // U1 Data
|
|
DDRC = 0x00; // U2 Data
|
|
// Set Unused Address Pins to Output
|
|
DDRL = 0xFF;
|
|
|
|
// Set Control Pins to Output
|
|
// U2_HLD(PH0) SCLK(PH1) U2_SI(PH3) U1_HLD(PH4) U2_WP(PH5) U2_CS(PH6)
|
|
DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
|
// U1_CS(PK0) U1_SI(PK1) U1_WP(PK2) --------
|
|
DDRK |= (1 << 0) | (1 << 1) | (1 <<2) | (1 << 3);
|
|
|
|
// FLASH Configuration Pins to Input
|
|
// S1(PK4) S2(PK5) S3(PK6) S4(PK7)
|
|
DDRK &= ~((1 << 4) | (1 << 5) | (1 << 6) | (1 << 7));
|
|
|
|
// Set TIME(PJ0) to Output (UNUSED)
|
|
DDRJ |= (1 << 0);
|
|
|
|
// Setting Control Pins to HIGH
|
|
// U2_HLD(PH0) SCLK(PH1) U2_SI(PH3) U1_HLD(PH4) U2_WP(PH5) U2_CS(PH6)
|
|
PORTH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
|
// U1_CS(PK0) U1_SI(PK1) U1_WP(PK2) -------- S1(PK4) S2(PK5) S3(PK6) S4(PK7)
|
|
PORTK |= (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7);
|
|
|
|
// Set Unused Data Pins (PA0-PA7) to Output
|
|
DDRA = 0xFF;
|
|
|
|
// Set Unused Pins HIGH
|
|
PORTA = 0xFF;
|
|
PORTJ |= (1 << 0); // TIME(PJ0)
|
|
|
|
checkStatus_LJPRO();
|
|
strcpy(romName, "LJPRO");
|
|
|
|
mode = CORE_LJPRO;
|
|
}
|
|
|
|
//******************************************
|
|
// SERIAL MODE
|
|
//******************************************
|
|
// 25L1605/25L3205
|
|
// Default Serial Mode
|
|
|
|
void sendSerial_U1(uint8_t data)
|
|
{
|
|
for (int i = 0; i < 8; i++) {
|
|
PORTH &= ~(1 << 1); // SCLK LOW
|
|
if ((data >> 7) & 0x1) { // Bit is HIGH
|
|
PORTK |= (1 << 1); // U1_SI HIGH;
|
|
}
|
|
else {
|
|
PORTK &= ~(1 << 1); // U1_SI LOW;
|
|
}
|
|
PORTH |= (1 << 1); // SCLK HIGH
|
|
// rotate to the next bit
|
|
data <<= 1;
|
|
}
|
|
}
|
|
|
|
void sendSerial_U2(uint8_t data)
|
|
{
|
|
for (int i = 0; i < 8; i++) {
|
|
PORTH &= ~(1 << 1); // SCLK LOW
|
|
if ((data >> 7) & 0x1) { // Bit is HIGH
|
|
PORTH |= (1 << 3); // U2_SI HIGH;
|
|
}
|
|
else {
|
|
PORTH &= ~(1 << 3); // U2_SI LOW;
|
|
}
|
|
PORTH |= (1 << 1); // SCLK HIGH
|
|
// rotate to the next bit
|
|
data <<= 1;
|
|
}
|
|
}
|
|
|
|
uint8_t readSerial_U1()
|
|
{
|
|
bool serBits[9];
|
|
for (byte i = 0; i < 8; i++) {
|
|
pulseClock_LJPRO(1);
|
|
serBits[i] = (PINF >> 7) & 0x1;
|
|
}
|
|
byte tempdata = serBits[0] << 7 | serBits[1] << 6 | serBits[2] << 5 | serBits[3] << 4 | serBits[4] << 3 | serBits[5] << 2 | serBits[6] << 1 | serBits[7];
|
|
|
|
return tempdata;
|
|
}
|
|
|
|
uint8_t readSerial_U2()
|
|
{
|
|
bool serBits[9];
|
|
for (byte i = 0; i < 8; i++) {
|
|
pulseClock_LJPRO(1);
|
|
serBits[i] = (PINC >> 7) & 0x1;
|
|
}
|
|
byte tempdata = serBits[0] << 7 | serBits[1] << 6 | serBits[2] << 5 | serBits[3] << 4 | serBits[4] << 3 | serBits[5] << 2 | serBits[6] << 1 | serBits[7];
|
|
|
|
return tempdata;
|
|
}
|
|
|
|
//******************************************
|
|
// PARALLEL MODE
|
|
//******************************************
|
|
// 25L1605/25L3205
|
|
// Parallel Mode - Command 0x55
|
|
// SCLK Frequency 1.2MHz (Cycle 833.33ns)
|
|
// READ 0x03
|
|
// WRITE 0x02
|
|
|
|
void pulseClock_LJPRO(unsigned int times)
|
|
{
|
|
for (unsigned int i = 0; i < (times * 2); i++) {
|
|
// Switch the clock pin to 0 if it's 1 and 0 if it's 1
|
|
PORTH ^= (1 << 1);
|
|
// without the delay the clock pulse would be 1.5us and 666kHz
|
|
//__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t"));
|
|
}
|
|
}
|
|
|
|
// Send one byte of data to Serial FLASH [Parallel Mode]
|
|
void sendData_U1(byte data)
|
|
{
|
|
DDRF = 0xFF; // U1 Data Output
|
|
PORTF = data;
|
|
pulseClock_LJPRO(8);
|
|
DDRF = 0x00; // U1 Data Input
|
|
}
|
|
|
|
void sendData_U2(byte data)
|
|
{
|
|
DDRC = 0xFF; // U2 Data Output
|
|
PORTC = data;
|
|
pulseClock_LJPRO(8);
|
|
DDRC = 0x00; // U2 Data Input
|
|
}
|
|
|
|
void readData_U1(uint32_t startaddr, uint32_t endaddr)
|
|
{
|
|
for (uint32_t addr = startaddr; addr < endaddr; addr += 512) {
|
|
for (int x = 0; x < 512; x++) {
|
|
pulseClock_LJPRO(1);
|
|
sdBuffer[x] = PINF;
|
|
}
|
|
myFile.write(sdBuffer, 512);
|
|
}
|
|
}
|
|
|
|
void readData_U2(uint32_t startaddr, uint32_t endaddr)
|
|
{
|
|
for (uint32_t addr = startaddr; addr < endaddr; addr += 512) {
|
|
for (int x = 0; x < 512; x++) {
|
|
pulseClock_LJPRO(1);
|
|
sdBuffer[x] = PINC;
|
|
}
|
|
myFile.write(sdBuffer, 512);
|
|
}
|
|
}
|
|
|
|
// RDID
|
|
// Manufacturer 0xC2
|
|
// Memory Density 0x20
|
|
// Device ID 0x15 [25L1605]/0x16 [25L3205]
|
|
|
|
// REMS
|
|
// Manufacturer 0xC2
|
|
// Device ID 0x14 [25L1605]/0x15 [25L3205]
|
|
|
|
void readID_U1() // Parallel Mode
|
|
{
|
|
CS1_LOW; // U1 LOW
|
|
sendSerial_U1(0x9F); // RDID Command
|
|
pulseClock_LJPRO(1);
|
|
byte id0 = PINF; // 0xC2
|
|
pulseClock_LJPRO(1);
|
|
byte id1 = PINF; // 0x20
|
|
pulseClock_LJPRO(1);
|
|
byte id2 = PINF; // 0x15 [MX25L1605]/0x16 [MX25L3205]
|
|
CS1_HIGH; // U1 HIGH
|
|
// Flash ID
|
|
sprintf(mnfID, "%02X", id0);
|
|
sprintf(deviceID, "%02X%02X", id1, id2);
|
|
// println_Msg(mnfID);
|
|
// println_Msg(deviceID);
|
|
// display_Update();
|
|
if(strcmp(deviceID, "2015") == 0) { // MX25L1605
|
|
ljproflash1found = 1;
|
|
ljproflash1size = 2;
|
|
display_Clear();
|
|
println_Msg(F("U1 MX25L1605 FOUND"));
|
|
display_Update();
|
|
}
|
|
else if (strcmp(deviceID, "2016") == 0) { // MX25L3205
|
|
ljproflash1found = 1;
|
|
ljproflash1size = 4;
|
|
display_Clear();
|
|
println_Msg(F("U1 MX25L3205 FOUND"));
|
|
display_Update();
|
|
}
|
|
}
|
|
|
|
void readID_U2() // Parallel Mode
|
|
{
|
|
CS2_LOW; // U2 LOW
|
|
sendSerial_U2(0x9F); // RDID Command
|
|
pulseClock_LJPRO(1);
|
|
byte id0 = PINC; // 0xC2
|
|
pulseClock_LJPRO(1);
|
|
byte id1 = PINC; // 0x20
|
|
pulseClock_LJPRO(1);
|
|
byte id2 = PINC; // 0x15 [MX25L1605]/0x16 [MX25L3205]
|
|
pulseClock_LJPRO(1);
|
|
CS2_HIGH; // U2 HIGH
|
|
// Flash ID
|
|
sprintf(mnfID, "%02X", id0);
|
|
sprintf(deviceID, "%02X%02X", id1, id2);
|
|
// println_Msg(mnfID);
|
|
// println_Msg(deviceID);
|
|
// display_Update();
|
|
if(strcmp(deviceID, "2015") == 0) { // MX25L1605
|
|
ljproflash2found = 1;
|
|
ljproflash2size = 2;
|
|
println_Msg(F("U2 MX25L1605 FOUND"));
|
|
display_Update();
|
|
}
|
|
else if (strcmp(deviceID, "2016") == 0) { // MX25L3205
|
|
ljproflash2found = 1;
|
|
ljproflash2size = 4;
|
|
println_Msg(F("U2 MX25L3205 FOUND"));
|
|
display_Update();
|
|
}
|
|
}
|
|
|
|
//******************************************
|
|
// READ ROM
|
|
//******************************************
|
|
|
|
void readROM_LJPRO()
|
|
{
|
|
createFolderAndOpenFile("LJPRO", "ROM", romName, "bin");
|
|
|
|
// Little Jammer Pro PCB B1043-02A
|
|
// Footprints for two 25L1605/25L3205 chips
|
|
// Test carts only have one 25L1605 (2MB) installed
|
|
// PCB could possibly install two 25L3205 chips (2x4MB = 8MB)
|
|
|
|
// Set U1 FLASH to Parallel Mode
|
|
CS1_LOW; // U1 LOW
|
|
sendSerial_U1(0x55); // Parallel Mode
|
|
CS1_HIGH; // U1 HIGH
|
|
// Read ID
|
|
readID_U1();
|
|
// Set U2 FLASH to Parallel Mode
|
|
CS2_LOW; // U2 LOW
|
|
sendSerial_U2(0x55); // Parallel Mode
|
|
CS2_HIGH; // U2 HIGH
|
|
// Read ID
|
|
readID_U2();
|
|
|
|
// Read U1
|
|
println_Msg(F("Reading U1..."));
|
|
display_Update();
|
|
CS1_LOW; // U1 LOW
|
|
DDRF = 0x00; // U1 Data Input
|
|
sendSerial_U1(0x03); // Read Array (Parallel)
|
|
sendSerial_U1(0x00); // Address A23-A16
|
|
sendSerial_U1(0x00); // Address A15-A8
|
|
sendSerial_U1(0x00); // Address A7-A0
|
|
readData_U1(0x000000, 0x200000);
|
|
if (ljproflash1size == 4) { // 4MB
|
|
readData_U1(0x200000, 0x400000);
|
|
}
|
|
CS1_HIGH; // U1 HIGH
|
|
if (ljproflash2found) {
|
|
// Read U2
|
|
println_Msg(F("Reading U2..."));
|
|
display_Update();
|
|
CS2_LOW; // U2 LOW
|
|
DDRC = 0x00; // U2 Data Input
|
|
sendSerial_U2(0x03); // Read Array (Parallel)
|
|
sendSerial_U2(0x00); // Address A23-A16
|
|
sendSerial_U2(0x00); // Address A15-A8
|
|
sendSerial_U2(0x00); // Address A7-A0
|
|
readData_U2(0x000000, 0x200000);
|
|
if (ljproflash2size == 4) { // 4MB
|
|
readData_U2(0x200000, 0x400000);
|
|
}
|
|
CS2_HIGH; // U2 HIGH
|
|
}
|
|
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_LJPRO(int index)
|
|
{
|
|
display_Clear();
|
|
print_Msg(FS(FSTRING_ROM_SIZE));
|
|
println_Msg(LJPRO[index]);
|
|
}
|
|
#endif
|
|
|
|
void setROMSize_LJPRO()
|
|
{
|
|
byte newljprosize;
|
|
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
|
|
display_Clear();
|
|
if (ljprolo == ljprohi)
|
|
newljprosize = ljprolo;
|
|
else {
|
|
newljprosize = navigateMenu(ljprolo, ljprohi, &printRomSize_LJPRO);
|
|
|
|
display.setCursor(0, 56); // Display selection at bottom
|
|
}
|
|
print_Msg(FS(FSTRING_ROM_SIZE));
|
|
print_Msg(LJPRO[newljprosize]);
|
|
println_Msg(F("KB"));
|
|
display_Update();
|
|
delay(1000);
|
|
#else
|
|
if (ljprolo == ljprohi)
|
|
newljprosize = ljprolo;
|
|
else {
|
|
setrom:
|
|
String sizeROM;
|
|
for (int i = 0; i < (ljprohi - ljprolo + 1); i++) {
|
|
Serial.print(F("Select ROM Size: "));
|
|
Serial.print(i);
|
|
Serial.print(F(" = "));
|
|
Serial.print(LJPRO[i + ljprolo]);
|
|
Serial.println(F("KB"));
|
|
}
|
|
Serial.print(F("Enter ROM Size: "));
|
|
while (Serial.available() == 0) {}
|
|
sizeROM = Serial.readStringUntil('\n');
|
|
Serial.println(sizeROM);
|
|
newljprosize = sizeROM.toInt() + ljprolo;
|
|
if (newljprosize > ljprohi) {
|
|
Serial.println(F("SIZE NOT SUPPORTED"));
|
|
Serial.println(FS(FSTRING_EMPTY));
|
|
goto setrom;
|
|
}
|
|
}
|
|
Serial.print(F("ROM Size = "));
|
|
Serial.print(LJPRO[newljprosize]);
|
|
Serial.println(F("KB"));
|
|
#endif
|
|
EEPROM_writeAnything(8, newljprosize);
|
|
ljprosize = newljprosize;
|
|
}
|
|
|
|
void checkStatus_LJPRO()
|
|
{
|
|
EEPROM_readAnything(8, ljprosize);
|
|
if (ljprosize > ljprohi) {
|
|
ljprosize = 0; // default 2M
|
|
EEPROM_writeAnything(8, ljprosize);
|
|
}
|
|
|
|
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
|
|
display_Clear();
|
|
println_Msg(F("LITTLE JAMMER PRO"));
|
|
println_Msg(FS(FSTRING_CURRENT_SETTINGS));
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
print_Msg(FS(FSTRING_ROM_SIZE));
|
|
print_Msg(LJPRO[ljprosize]);
|
|
println_Msg(F("MB"));
|
|
display_Update();
|
|
wait();
|
|
#else
|
|
Serial.print(FS(FSTRING_ROM_SIZE));
|
|
Serial.print(LJPRO[ljprosize]);
|
|
Serial.println(F("MB"));
|
|
Serial.println(FS(FSTRING_EMPTY));
|
|
#endif
|
|
}
|
|
#endif
|