diff --git a/manager/index.js b/manager/index.js index df7ddd7..5a6c7d4 100644 --- a/manager/index.js +++ b/manager/index.js @@ -7,7 +7,7 @@ const { argv } = yargs(hideBin(process.argv)) const portPath = argv.port || 'COM3'; -const port = new SerialPort(portPath, { baudRate: 9600 }); +const port = new SerialPort(portPath, { baudRate: 4608000 }); const dumpBuffer = []; @@ -21,27 +21,73 @@ const read = index => { port.write(readCommandFromIndex(index)); } -let limit = argv.limit ? parseInt(argv.limit, 16) : 0x1F_FFFF; +const writeCommandFromIndex = (index, write) => { + const str = `W${index.toString(16)}:${write.toString(16)}%` + return str; +} -port.on('data', data => { - const number = data.toString('ASCII'); +const write = (index, write) => { + port.write(writeCommandFromIndex(index, write)); +} - const bin = Buffer.alloc(2); - bin.writeUInt16LE(parseInt(number, 16)); - dumpBuffer.push(bin); +const erase = () => { + port.write('ERS%'); +} - if (index % 0x00_FFFF === 0) { - console.log('Dumping...', `${Math.round(index / limit * 100)}%`); - } +let limit = argv.limit ? parseInt(argv.limit, 16) : 0x1F_FFFF; - if (index === limit) { - const completeBuffer = Buffer.concat(dumpBuffer); +const start = Date.now(); +let timeElapsed = 0; - fs.writeFileSync(out, completeBuffer); - process.exit(0); - } else { - index += 1; - read(index); +port.on('data', data => { + if (argv.mode === 'dump') { + const number = data.toString('ASCII'); + const bin = Buffer.alloc(2); + bin.writeUInt16LE(parseInt(number, 16)); + dumpBuffer.push(bin); + + if (index % 0x00_FFF === 0) { + timeElapsed = Date.now() - start; + const percentCompleted = index / limit; + + const estTimeRemaining = Math.round((timeElapsed / percentCompleted) * (1 - percentCompleted)); + console.log('Dumping...', `${Math.round(index / limit * 100)}%,`, Math.round(estTimeRemaining / 1000 / 60), "minutes left"); + } + + if (index === limit) { + const completeBuffer = Buffer.concat(dumpBuffer); + + fs.writeFileSync(out, completeBuffer); + console.log('Done'); + process.exit(0); + } else { + index += 1; + read(index); + } + } else if (argv.mode === 'write') { + if (!erased) { + console.log('Done'); + erased = true; + } + + if (index % 0x00_FFFF === 0) { + console.log('Writing...', `${Math.round((index / (rom.length / 2) * 100))}%`) + } + + if (index === rom.length / 2) { + console.log('Done'); + process.exit(0); + } else { + write(index, rom.readUInt16LE(index * 2)); + index++; + } + } else if (argv.mode === 'sddump') { + const result = data.toString('ASCII'); + console.log(result); + + if (result.includes('Done!')) { + process.exit(0); + } } }) @@ -53,6 +99,18 @@ if (argv.bank === 'HIGH') { } let index = 0x00_0000 - -// Read first word -read(index); \ No newline at end of file +let erased = false; +let rom; + +if (argv.mode === 'dump') { + // Read first word + read(index); +} else if (argv.mode === 'write') { + rom = fs.readFileSync(argv.rom); + + console.log('Erasing...'); + erase(); +} else if (argv.mode === 'sddump') { + console.log(`Dumping file ${argv.name}:${argv.length}`); + port.write(`D${argv.name}:${argv.length}`); +} \ No newline at end of file diff --git a/manager/readOne.js b/manager/readOne.js deleted file mode 100644 index bf683d6..0000000 --- a/manager/readOne.js +++ /dev/null @@ -1,39 +0,0 @@ -const fs = require('fs'); -const SerialPort = require('serialport'); -const yargs = require('yargs/yargs'); -const { hideBin } = require('yargs/helpers'); - -const { argv } = yargs(hideBin(process.argv)) - -const portPath = argv.port || 'COM3'; - -const port = new SerialPort(portPath, { baudRate: 9600 }); - -const dumpBuffer = []; - -const readCommandFromIndex = (index) => { - return `R${index.toString(16)}%` -} - -const read = index => { - port.write(readCommandFromIndex(index)); -} - -port.on('data', data => { - const number = data.toString('ASCII'); - - const bin = Buffer.alloc(2); - bin.writeUInt16LE(parseInt(number, 16)); - - console.log(number); - - process.exit(0); -}) - -// Set bank to low -port.write('BHI%'); - -let index = 0x00_000A - -// Read first word -read(index); \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index b8cf5ad..b73a3fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,5 +10,6 @@ [env:teensy40] platform = teensy -board = teensy40 +board = teensy41 framework = arduino +lib_deps = greiman/SdFat@^2.0.6 diff --git a/src/main.cpp b/src/main.cpp index 46d709e..d71eeba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,55 +1,69 @@ + #include +#include "SdFat.h" #include "main.h" +#include "RingBuf.h" + +#define SPI_CLOCK SD_SCK_MHZ(50) + +const size_t RING_BUF_SIZE = 400*512; // v-smile has 22 bits for addressing -// my teensy is only wired for 21 bits, 22 is unused on test cartridge -// Alphabet Park Adventure, 80-092000(US) Rev. 2 -uint8_t address [ADDRESS_BITS] = { - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, +const uint8_t address [ADDRESS_BITS] = { + 9, // A0 + 39, + 40, 10, - 11, + 38, + 11, // A5 + 37, 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, -}; - -// v-smile uses 16 bit wide data bus -uint8_t data [DATA_BITS] = { - 21, - 22, - 23, - 24, + 35, 25, + 34, // A10 26, + 33, 27, 28, - 29, + 29, // A15 + 8, + 36, + 24, 30, 31, - 32, - 33, - 34, - 35, - 36 + 32 // A21 +}; + +// v-smile uses 16 bit wide data bus +const uint8_t data [DATA_BITS] = { + 3, // DQ0 + 2, + 1, + 0, + 19, + 18, // DQ5 + 17, + 16, + 7, + 6, + 5, // DQ10 + 4, + 15, + 14, + 41, + 13 // DQ15 }; -int chipEnable = 37; -int chipSelect = 38; +const int chipEnable = 23; +const int chipSelect = 22; +const int writeEnable = 21; +const int outputEnable = 20; + +SdFs sd; +FsFile file; +bool sdDetected = false; + +RingBuf ringBuf; void setup() { // initialize address bus @@ -72,10 +86,52 @@ void setup() { pinMode(chipSelect, OUTPUT); digitalWrite(chipSelect, LOW); + pinMode(writeEnable, OUTPUT); + digitalWrite(writeEnable, HIGH); + + pinMode(outputEnable, OUTPUT); + digitalWrite(outputEnable, LOW); + // initialize serial interface - Serial.begin(9600); + Serial.begin(4608000); delay(2); Serial.println("Started Serial COM"); + + if (!sd.begin(SdioConfig(FIFO_SDIO))) { + Serial.println("Failed to detect sd card"); + sd.initErrorHalt(&Serial); + } else { + sdDetected = true; + } +} + +void setupWrite() { + // initialize address bus + for (int i = 0; i < ADDRESS_BITS; i++) { + pinMode(address[i], OUTPUT); + + // init low + digitalWrite(address[i], LOW); + } + + // initialize data bus + for (int i = 0; i < DATA_BITS; i++) { + pinMode(data[i], OUTPUT); + digitalWrite(data[i], LOW); + } + + // initialize control bits + pinMode(chipEnable, OUTPUT); + digitalWrite(chipEnable, LOW); + + pinMode(chipSelect, OUTPUT); + digitalWrite(chipSelect, LOW); + + pinMode(writeEnable, OUTPUT); + digitalWrite(writeEnable, HIGH); + + pinMode(outputEnable, OUTPUT); + digitalWrite(outputEnable, HIGH); } @@ -85,6 +141,12 @@ void setAddress(unsigned int addr) { } } +void setData(uint16_t wrd) { + for (int i = 0; i < DATA_BITS; i++) { + digitalWriteFast(data[i], bitRead(wrd, i)); + } +} + word readWord() { word dataWord = 0; @@ -111,8 +173,7 @@ word readData(unsigned int addr, int bank) { digitalWriteFast(chipSelect, LOW); } - // $TODO: wait TRC is 70ns, this overshoots timing, can be improved - delayNanoseconds(80); + delayNanoseconds(70); dataWord = readWord(); @@ -122,6 +183,44 @@ word readData(unsigned int addr, int bank) { return dataWord; } +void writeWord(uint32_t addr, uint16_t wrd) { + digitalWriteFast(writeEnable, HIGH); + digitalWriteFast(outputEnable, HIGH); + digitalWriteFast(chipEnable, LOW); + + setAddress(addr); + setData(wrd); + + delayNanoseconds(15); + + digitalWriteFast(writeEnable, LOW); + + delayNanoseconds(40); + + digitalWriteFast(writeEnable, HIGH); + + delayNanoseconds(15); +} + +void writeData(uint32_t addr, uint16_t wrd) { + writeWord(0x555, 0xAA); + writeWord(0x2AA, 0x55); + writeWord(0x555, 0xA0); + writeWord(addr, wrd); + + delayMicroseconds(10); +} + +void eraseData() { + writeWord(0x555, 0xAA); + writeWord(0x2AA, 0x55); + writeWord(0x555, 0x80); + writeWord(0x555, 0xAA); + writeWord(0x2AA, 0x55); + writeWord(0x555, 0x10); + + delayMicroseconds(50000); +} // Tool to print an integer in a hexadecimal representation void printHex(int num, int precision) { @@ -173,18 +272,20 @@ uint8_t hexDecimalToBin(char decimal) { } } -COMMAND stringToCommand(const String &commandString, uint32_t &address) { +COMMAND stringToCommand(const String &commandString, uint32_t &address, uint16_t &wrd, char fileName[]) { if (commandString == String("BLOW")) { return BANK_LOW; } else if (commandString == String("BHI")) { return BANK_HIGH; } else if (commandString == String("ACK")) { return ACK; + } else if (commandString == String("ERS")) { + return ERASE; } else if (commandString.startsWith(String('R'))) { String addressString = commandString.substring(1); address = 0; - for (uint i = 0; i < addressString.length(); i++) { + for (uint32_t i = 0; i < addressString.length(); i++) { uint8_t hexDecimal = hexDecimalToBin(addressString.charAt(i)); uint8_t offset = (addressString.length() - 1 - i) * 4; @@ -194,27 +295,220 @@ COMMAND stringToCommand(const String &commandString, uint32_t &address) { address = address & 0x3FFFFF; return READ; + } else if (commandString.startsWith(String('W'))) { + String addressString = commandString.substring(1); + + address = 0; + uint32_t splitPoint = 0; + uint8_t decimals[7] = {0, 0, 0, 0, 0, 0, 0}; + for (; addressString.charAt(splitPoint) != ':'; splitPoint++) { + decimals[splitPoint] = hexDecimalToBin(addressString.charAt(splitPoint)); + } + + for (uint32_t j = 0; j < splitPoint; j++) { + uint8_t offset = (splitPoint - 1 - j) * 4; + + address = address + ((decimals[j] & 0xF) << offset); + } + + address = address & 0x3FFFFF; + + String wordString = commandString.substring(splitPoint + 1); + + wrd = 0; + for (uint32_t i = 0; i < wordString.length(); i++) { + uint8_t hexDecimal = hexDecimalToBin(wordString.charAt(i)); + + uint8_t offset = (wordString.length() - 1 - i) * 4; + + wrd = wrd + ((hexDecimal & 0xF) << offset); + } + + return WRITE; + } else if (commandString.startsWith(String('D'))) { + String dumpString = commandString.substring(1); + + uint32_t splitPoint = 0; + for (; dumpString.charAt(splitPoint) != ':'; splitPoint++) { + fileName[splitPoint] = dumpString.charAt(splitPoint); + } + + fileName[splitPoint + 1] = {'\0'}; + + String limitString = commandString.substring(splitPoint + 1); + + address = 0; + for (uint32_t i = 0; i < limitString.length(); i++) { + uint8_t hexDecimal = hexDecimalToBin(limitString.charAt(i)); + uint8_t offset = (limitString.length() - 1 - i) * 4; + + address = address + ((hexDecimal & 0xF) << offset); + } + + address = address & 0x3FFFFFF; + + return DUMP; } else { return UNKNOWN; } } -uint bank = LOW; +uint32_t bank = LOW; +MODE currentMode = MODE_READ; + +void switchMode(MODE mode) { + if (mode == MODE_WRITE) { + if (currentMode != MODE_WRITE) { + setupWrite(); + currentMode = MODE_WRITE; + + delayMicroseconds(10); + } + } else { + if (currentMode != MODE_READ) { + setup(); + currentMode = MODE_READ; + + delayMicroseconds(10); + } + } +} + +typedef struct { + uint16_t wrd; + uint32_t freq; +} differences; + +uint32_t retryCount = 35; +uint16_t result[35]; +differences diffs[35]; +uint32_t difCnt; +uint32_t largestFreq; void readSerialCommand(String command) { uint32_t address = 0; + uint16_t wrd = 0; + size_t maxUsed = 0; + char fileName[FILE_NAME_LIMIT] = ""; - switch(stringToCommand(command, address)) { + switch(stringToCommand(command, address, wrd, fileName)) { case(BANK_LOW): bank = LOW; return; case(BANK_HIGH): bank = HIGH; return; + case(ERASE): + switchMode(MODE_WRITE); + + eraseData(); + + Serial.print("ACK\n"); + return; case(READ): - printHex(readData(address, bank), 4); + switchMode(MODE_READ); + + for (uint32_t i = 0; i < retryCount; i++) { + delayMicroseconds(1); + result[i] = readData(address, bank); + } + + difCnt = 1; + largestFreq = 0; + diffs[0] = {result[0], 1}; + + for (uint32_t i = 0; i < retryCount; i++) { + bool matchFound = false; + for (uint32_t j = 0; j < difCnt; j++) { + if (diffs[j].wrd == result[i]) { + if (diffs[j].wrd != 0x0000) { + diffs[j] = {result[i], diffs[j].freq + 1}; + } + matchFound = true; + break; + } + } + + if (!matchFound) { + diffs[difCnt] = {result[i], 1}; + difCnt++; + } + } + + for (uint32_t i = 0; i < difCnt; i++) { + if (diffs[i].freq > largestFreq) { + result[0] = diffs[i].wrd; + largestFreq = diffs[i].freq; + } + } + + printHex(result[0], 4); Serial.print("\n"); return; + case(WRITE): + switchMode(MODE_WRITE); + + writeData(address, wrd); + + Serial.print("ACK\n"); + return; + case(DUMP): + switchMode(MODE_READ); + + if (!sdDetected) { + Serial.println("No SD card!"); + return; + } + + Serial.print("Writing file to "); + Serial.print(fileName); + Serial.print('\n'); + + if (!file.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) { + Serial.println("Failed to open file!"); + return; + } + + if (!file.preAllocate(address * 2)) { + Serial.println("Failed to preallocate!"); + file.close(); + return; + } + + ringBuf.begin(&file); + + for (uint32_t i = 0; i < address; i++) { + if (i % 0xFFFF == 0) { + Serial.print("Dumping... "); + Serial.print((i * 100) / address); + Serial.print("%\n"); + } + + size_t n = ringBuf.bytesUsed(); + + if (n > maxUsed) { + maxUsed = n; + } + + if (n >= 512 && !file.isBusy()) { + ringBuf.writeOut(512); + } + + uint16_t data = readData(i, bank); + + ringBuf.write(&data, 2); + } + + ringBuf.sync(); + file.truncate(); + file.close(); + + Serial.print("Done! Max bytes used: "); + Serial.print(maxUsed); + Serial.print('\n'); + + setup(); + return; case(ACK): default: Serial.println("Unused"); diff --git a/src/main.h b/src/main.h index b997d0a..dc05122 100644 --- a/src/main.h +++ b/src/main.h @@ -1,26 +1,40 @@ #ifndef VSMILE_DUMPER_MAIN_H #define VSMILE_DUMPER_MAIN_H -const int ADDRESS_BITS = 21; // Only 21 wired in test device, V-smile has 22-bit bus -const int DATA_BITS = 16; +#define FILE_NAME_LIMIT 20 + +const int ADDRESS_BITS = 22; // V-smile has 22-bit addressing width +const int DATA_BITS = 16; // 16 bit data bus /** * Commands - * Rxxxxxx: Read 14 bits in from a given 22bit hex address + * Rxxxxxx: Read word in from a given 22bit hex address + * Wxxxxxx:xxxx: Write a hex word to a given 22bit hex address * BLOW: Set chip select + chip enable to read from first bank (default) * BHI: Set chip select + chip enable to read from second bank * ACK: Acknowledge byte transfer, after dumper sends data it awaits for an ACK for 18ms * + * Dxxxx:ssss Dumps attached cartridge to sd-card with filename x and size s + * Fxxxx: Flashes rom from sd-card to cartridge + * * Terminator character: % **/ enum COMMAND { READ, + WRITE, + ERASE, BANK_LOW, BANK_HIGH, ACK, + DUMP, UNKNOWN }; +enum MODE { + MODE_READ, + MODE_WRITE +}; + const char TERMINATOR = '%'; #endif //VSMILE_DUMPER_MAIN_H