diff --git a/floppy_emu_arduino/floppy_emu_arduino.ino b/floppy_emu_arduino/floppy_emu_arduino.ino new file mode 100755 index 0000000..cb34e5e --- /dev/null +++ b/floppy_emu_arduino/floppy_emu_arduino.ino @@ -0,0 +1,2019 @@ +/* + Floppy Emu, copyright 2013 Steve Chamberlin, "Big Mess o' Wires". All rights reserved. + + Floppy Emu is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported + license. (CC BY-NC 3.0) The terms of the license may be viewed at + http://creativecommons.org/licenses/by-nc/3.0/ + + Based on a work at http://www.bigmessowires.com/macintosh-floppy-emu/ + + Permissions beyond the scope of this license may be available at www.bigmessowires.com + or from mailto:steve@bigmessowires.com. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "portmacros.h" +#include "noklcd.h" +#include "millitimer.h" +#include "SdFat.h" +#include "SdBaseFile.h" +#include "micro.h" +#include "ports.h" +#include "diskmenu.h" + +#ifdef PROGMEM_WORKAROUND +// work-around for compiler bug +#undef PROGMEM +#define PROGMEM __attribute__(( section(".progmem.data") )) +#undef PSTR +#define PSTR(s) (__extension__({static prog_char __c[] PROGMEM = (s); &__c[0];})) +#endif + +// I/O pin assignments +#define CPLD_RESET_PORT B +#define CPLD_RESET_PIN 0 + +#define CPLD_STEP_DIR_MOTOR_ON_PORT C +#define CPLD_STEP_DIR_MOTOR_ON_PIN 7 + +#define CPLD_STEP_REQ_PORT D +#define CPLD_STEP_REQ_PIN 0 // PCINT24 +#define CPLD_STEP_REQ_INT_MSK PCMSK3 +#define CPLD_STEP_REQ_INT_PIN PCINT24 +#define CPLD_STEP_REQ_INT_ENABLE PCIE3 + +#define CPLD_CURRENT_SIDE_PORT C +#define CPLD_CURRENT_SIDE_PIN 1 // PCINT17 +#define CPLD_CURRENT_SIDE_INT_MSK PCMSK2 +#define CPLD_CURRENT_SIDE_INT_PIN PCINT17 +#define CPLD_CURRENT_SIDE_INT_ENABLE PCIE2 + +#define CPLD_EJECT_REQ_PORT D +#define CPLD_EJECT_REQ_PIN 3 + +#define CPLD_STEP_ACK_DISK_IN_PORT C +#define CPLD_STEP_ACK_DISK_IN_PIN 2 + +#define CPLD_WR_REQ_PORT C +#define CPLD_WR_REQ_PIN 0 // PCINT16 +#define CPLD_WR_REQ_INT_MSK PCMSK2 +#define CPLD_WR_REQ_INT_PIN PCINT16 +#define CPLD_WR_REQ_INT_ENABLE PCIE2 + +#define CPLD_RD_READY_TK0_PORT C +#define CPLD_RD_READY_TK0_PIN 5 + +#define CPLD_RD_ACK_WR_TICK_PORT A +#define CPLD_RD_ACK_WR_TICK_PIN 7 // PCINT7 +#define CPLD_RD_ACK_WR_TICK_INT_MSK PCMSK0 +#define CPLD_RD_ACK_WR_TICK_INT_PIN PCINT7 +#define CPLD_RD_ACK_WR_TICK_INT_ENABLE PCIE0 + +#define CPLD_DATA_PORT A + +#define CPLD_DATA_HIZ_PORT C +#define CPLD_DATA_HIZ_PIN 6 + +#define CPLD_TACH_PORT D +#define CPLD_TACH_PIN 5 + +#define CPLD_TMS_PORT C +#define CPLD_TMS_PIN 3 + +#define SELECT_BUTTON_PORT D +#define SELECT_BUTTON_PIN 4 + +#define PREV_BUTTON_PORT D +#define PREV_BUTTON_PIN 1 + +#define NEXT_BUTTON_PORT D +#define NEXT_BUTTON_PIN 2 + +#define STATUS_LED_PORT B +#define STATUS_LED_PIN 3 + +#define CARD_WPROT_PORT D +#define CARD_WPROT_PIN 7 + +#define SECTOR_DATA_SIZE 512 +#define INTER_SECTOR_GAP_SIZE 55 +#define ADDRESS_DATA_GAP_SIZE 10 +#define SECTOR_DATA_HEADER_SIZE 3 +#define SECTOR_DATA_SECTORNUM_START SECTOR_DATA_HEADER_SIZE +#define SECTOR_DATA_SECTORNUM_SIZE 1 +#define SECTOR_DATA_ENCODED_TAGS_START (SECTOR_DATA_HEADER_SIZE+SECTOR_DATA_SECTORNUM_SIZE) +#define SECTOR_DATA_ENCODED_TAGS_SIZE 16 +#define SECTOR_DATA_ENCODED_DATA_START (SECTOR_DATA_HEADER_SIZE+SECTOR_DATA_SECTORNUM_SIZE+SECTOR_DATA_ENCODED_TAGS_SIZE) +#define SECTOR_DATA_ENCODED_DATA_SIZE 683 +#define SECTOR_DATA_CHECKSUM_START (SECTOR_DATA_ENCODED_DATA_START+SECTOR_DATA_ENCODED_DATA_SIZE) + +// 8 byte marker placed at the end of the program binary, used by the bootloader. +// Configure the .bootldrinfo address to be 8 bytes below the bootloader start address for the type of Atmega being used. +#define DEVICEID_HIGH 0xDDDD +#define DEVICEID_LOW 0xDDDD +#define VERSIONID 0x0100 +const uint16_t bootloader_info[] __attribute__(( section(".bootldrinfo") )) = { DEVICEID_HIGH, DEVICEID_LOW, VERSIONID, 0x0000 }; + +const char versionStr[] PROGMEM = "App Version 1.0 L"; + +volatile uint8_t currentTrack; +volatile uint8_t prevTrack; +volatile uint8_t currentSide; +volatile uint8_t prevSide; +volatile uint8_t writeMode; +volatile bool restartDisk; +volatile bool writeError; + +bool diskInserted; +bool readOnly; +bool mfmMode; +uint16_t crc; +uint8_t numberOfDiskSides; +uint8_t currentSector; +uint16_t driveTachHalfPeriod; +uint8_t tachFlutter; + +uint8_t writeDisplayTimer; +uint8_t cpldFirmwareVersion; + +#define TEXTBUF_SIZE 22 +char textBuf[TEXTBUF_SIZE]; + +#define NUM_BUFFERS 24 +uint8_t sectorBuf[NUM_BUFFERS][SECTOR_DATA_SIZE]; +uint8_t extraBuf[SECTOR_DATA_SIZE]; + +bool selectedFileIsDiskCopyFormat; + +extern const uint16_t sony_track_start[] PROGMEM; +const uint16_t sony_track_start[80] = { + 0, 12, 24, 36, 48, 60 , 72, 84, + 96, 108, 120, 132, 144, 156, 168, 180, + + 192, 203, 214, 225, 236, 247, 258, 269, + 280, 291, 302, 313, 324, 335, 346, 357, + + 368, 378, 388, 398, 408, 418, 428, 438, + 448, 458, 468, 478, 488, 498, 508, 518, + + 528, 537, 546, 555, 564, 573, 582, 591, + 600, 609, 618, 627, 636, 645, 654, 663, + + 672, 680, 688, 696, 704, 712, 720, 728, + 736, 744, 752, 760, 768, 776, 784, 792 +}; + +extern const uint8_t sony_track_len[] PROGMEM; +const uint8_t sony_track_len[80] = { + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 +}; + +extern const uint8_t sony_to_disk_byte[] PROGMEM; +const uint8_t sony_to_disk_byte[] = { + 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, /* 0x00 */ + 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3, + 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, /* 0x10 */ + 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3, + 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, /* 0x20 */ + 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC, + 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, /* 0x30 */ + 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +extern const uint8_t disk_byte_to_sony[] PROGMEM; +const uint8_t disk_byte_to_sony[] = { + /* table begins at disk byte 0x96, value of 0xFF is an invalid disk byte */ + /* 0x96 */ 0x00, 0x01, 0xFF, 0xFF, 0x02, 0x03, 0xFF, 0x04, + /* 0x9E */ 0x05, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* 0xA6 */ 0x07, 0x08, 0xFF, 0xFF, 0xFF, 0x09, 0x0A, 0x0B, + /* 0xAE */ 0x0C, 0x0D, 0xFF, 0xFF, 0x0E, 0x0F, 0x10, 0x11, + /* 0xB6 */ 0x12, 0x13, 0xFF, 0x14, 0x15, 0x16, 0x17, 0x18, + /* 0xBE */ 0x19, 0x1A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* 0xC6 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1B, 0xFF, 0x1C, + /* 0xCE */ 0x1D, 0x1E, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + /* 0xD6 */ 0x20, 0x21, 0xFF, 0x22, 0x23, 0x24, 0x25, 0x26, + /* 0xDE */ 0x27, 0x28, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x29, + /* 0xE6 */ 0x2A, 0x2B, 0xFF, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + /* 0xEE */ 0x31, 0x32, 0xFF, 0xFF, 0x33, 0x34, 0x35, 0x36, + /* 0xF6 */ 0x37, 0x38, 0xFF, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, + /* 0xFE */ 0x3E, 0x3F +}; + +uint8_t sectorDataHeaderGCR[] = { 0xD5, 0xAA, 0xAD }; + +extern const uint16_t crc_ccitt[] PROGMEM; +const uint16_t crc_ccitt[] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +void ResetDiskState(); + +uint16_t writeErrorNumber; + +void error(const char* msg) +{ + bool wasWriteError = writeError; + uint16_t wasWriteErrorNumber = writeErrorNumber; + + ResetDiskState(); // clears writeError and writeErrorNumber + + LcdClear(); + LcdGoto(0,0); + if (wasWriteError) + LcdTinyStringP(PSTR("WRITE ERROR "), TEXT_INVERSE); + else + LcdTinyStringP(PSTR("FATAL ERROR "), TEXT_INVERSE); + LcdGoto(0,1); + LcdTinyString(msg, TEXT_NORMAL); + + if (wasWriteError) + { + snprintf(textBuf, TEXTBUF_SIZE, "%u", wasWriteErrorNumber); + LcdGoto(0,5); + LcdTinyString(textBuf, TEXT_NORMAL); + } + + while (1); +} + +void InitPorts() +{ + // set all data lines as outputs, MSB (RD_ACK/WR_TICK) as input + DDR(CPLD_DATA_PORT) = 0x7F; + + // initialize the other output lines + DDR(CPLD_TACH_PORT) |= (1<> 4; + if (speedZone > 4) + speedZone = 4; + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + driveTachHalfPeriod = F_CPU / (2 * zoneRPM[speedZone]); + // OCR1A update will be performed during the next sector 0 read + } + + TIFR1 = (1 << OCF1A); // Clear the timer 1 compare A match flag. Not sure this is actually necessary. +} + +uint16_t trackStart(uint8_t trackNumber) +{ + return mfmMode ? trackNumber * 18 : pgm_read_word(&sony_track_start[trackNumber]); +} + +uint8_t trackLength(uint8_t trackNumber) +{ + return mfmMode ? 18 : pgm_read_byte(&sony_track_len[trackNumber]); +} + +#define BUFFER_DIRTY 1 +#define BUFFER_DATA_VALID 2 +#define BUFFER_LOCKED 4 + +volatile uint8_t bufferState[NUM_BUFFERS]; +volatile uint8_t wrTrack; +volatile uint8_t wrSide; +volatile uint8_t wrSector; + +// these variables are used only within the interrupt routine, and do not need to be declared volatile +uint8_t wrTick; +uint16_t writeCount; +uint8_t writeTemp; +uint8_t currentWriteBufferNumber; +uint8_t ck5, ck6, ck7; +uint8_t XBit; +uint8_t* pSectorBuf; + +void WriteError() +{ + writeMode = false; + restartDisk = true; + writeError = true; + writeCount = 0; +} + +// pin state change interrupt: STEP +ISR(PCINT3_vect) +{ + // step to a new track? + if (bit_is_set(PIN(CPLD_STEP_REQ_PORT), CPLD_STEP_REQ_PIN)) + { + // determine the range of dirty sector buffers + uint8_t trackLen = trackLength(currentTrack); + uint8_t firstDirtyBuffer = NUM_BUFFERS, lastDirtyBuffer=0; + + for (uint8_t i=0; i= SECTOR_DATA_SECTORNUM_START) + { + strncpy(textBuf, "incomplete write", TEXTBUF_SIZE); + writeErrorNumber = 1000 + writeCount; + WriteError(); + } + + restartDisk = true; + } +} + +// pin state change interrupt: SIDE, WR_REQ +ISR(PCINT2_vect) +{ + // did the current side change? + uint8_t newSide = ((PIN(CPLD_CURRENT_SIDE_PORT) >> CPLD_CURRENT_SIDE_PIN) & 0x01); + if (newSide != currentSide) + { + if (numberOfDiskSides == 2) + currentSide = newSide; + else + currentSide = 0; + restartDisk = true; + + // premature end of a write? + if (writeCount >= SECTOR_DATA_SECTORNUM_START) + { + strncpy(textBuf, "incomplete write", TEXTBUF_SIZE); + writeErrorNumber = 2000 + writeCount; + WriteError(); + } + } + + // did the read/write mode change? RD_RW: 0 = write, 1 = read + uint8_t wreqBit = bit_is_clear(PIN(CPLD_WR_REQ_PORT), CPLD_WR_REQ_PIN); + if (wreqBit != writeMode) + { + writeMode = wreqBit; + + if (writeMode) + { + // switch the DATA pins to inputs + DDR(CPLD_DATA_PORT) = 0; + + // indicate that the data bus has been released + PORT(CPLD_DATA_HIZ_PORT) |= (1<= SECTOR_DATA_SECTORNUM_START) + { + strncpy(textBuf, "incomplete write", TEXTBUF_SIZE); + writeErrorNumber = 3000 + writeCount; + WriteError(); + } + } + + restartDisk = true; + } +} + +void HandleGCRWrite() +{ + uint8_t diskByte = 0x80 | PIN(CPLD_DATA_PORT); + + if (writeCount < SECTOR_DATA_ENCODED_TAGS_START) + { + // look for the sector header + // the final header byte is the sector number + if (writeCount == SECTOR_DATA_SECTORNUM_START) + { + uint8_t sector = pgm_read_byte(&disk_byte_to_sony[diskByte - 0x96]); + + if (sector >= trackLength(currentTrack)) + { + snprintf(textBuf, TEXTBUF_SIZE, "bad sector %d for t%d", sector, currentTrack); + writeErrorNumber = 60; + WriteError(); + } + + uint8_t trackLen = trackLength(currentTrack); + currentWriteBufferNumber = trackLen * currentSide + sector; + + if (bufferState[currentWriteBufferNumber] & BUFFER_LOCKED) + { + snprintf(textBuf, TEXTBUF_SIZE, "buf locked %d/%d:%d", currentTrack, currentSide, sector); + writeErrorNumber = 61; + WriteError(); + } + + pSectorBuf = sectorBuf[currentWriteBufferNumber]; + bufferState[currentWriteBufferNumber] |= BUFFER_LOCKED; + bufferState[currentWriteBufferNumber] &= ~BUFFER_DATA_VALID; + wrTrack = currentTrack; + wrSide = currentSide; + wrSector = sector; + + // turn on the LED when receiving a sector write + PORT(STATUS_LED_PORT) &= ~(1<> 7; + ck7 = (ck7 << 1) | XBit; + break; + + case 1: + b = (writeTemp & 0xC0) | dataIn; // A7 A6 0 0 0 0 0 0 | 0 0 A5 A4 A3 A2 A1 A0 + b ^= ck7; + + if (writeCount >= SECTOR_DATA_ENCODED_DATA_START) + *pSectorBuf++ = b; + + //ADDX(ck5, b); + addResult = (uint16_t)ck5 + b + XBit; + ck5 = addResult & 0xFF; + XBit = addResult >> 8; + writeTemp <<= 2; // B7 B6 C7 C6 0 0 0 0 + break; + + case 2: + b = (writeTemp & 0xC0) | dataIn; // B7 B6 0 0 0 0 0 0 | 0 0 B5 B4 B3 B2 B1 B0 + b ^= ck5; + + if (writeCount >= SECTOR_DATA_ENCODED_DATA_START) + *pSectorBuf++ = b; + + //ADDX(ck6, b); + addResult = (uint16_t)ck6 + b + XBit; + ck6 = addResult & 0xFF; + XBit = addResult >> 8; + writeTemp <<= 2; // C7 C6 0 0 0 0 0 0 + break; + + case 3: + b = writeTemp | dataIn; // C7 C6 0 0 0 0 0 0 | 0 0 C5 C4 C3 C2 C1 C0 + b ^= ck6; + + if (writeCount >= SECTOR_DATA_ENCODED_DATA_START) + *pSectorBuf++ = b; + + //ADDX(ck7, b); + addResult = (uint16_t)ck7 + b + XBit; + ck7 = addResult & 0xFF; + XBit = addResult >> 8; + break; + } + } + else + { + // verify the checksum + if (writeCount == SECTOR_DATA_CHECKSUM_START) + { + writeTemp = dataIn; + writeTemp <<= 2; + } + else if (writeCount == SECTOR_DATA_CHECKSUM_START+1) + { + b = (writeTemp & 0xC0) | dataIn; + writeTemp <<= 2; + if (b != ck5) + { + strncpy(textBuf, "checksum failure 0", TEXTBUF_SIZE); + writeErrorNumber = 62; + WriteError(); + } + } + else if (writeCount == SECTOR_DATA_CHECKSUM_START+2) + { + b = (writeTemp & 0xC0) | dataIn; + writeTemp <<= 2; + if (b != ck6) + { + strncpy(textBuf, "checksum failure 1", TEXTBUF_SIZE); + writeErrorNumber = 63; + WriteError(); + } + } + else if (writeCount == SECTOR_DATA_CHECKSUM_START+3) + { + b = writeTemp | dataIn; + writeTemp <<= 2; + if (b != ck7) + { + strncpy(textBuf, "checksum failure 2", TEXTBUF_SIZE); + writeErrorNumber = 64; + WriteError(); + } + + // success! + bufferState[currentWriteBufferNumber] |= BUFFER_DATA_VALID; + bufferState[currentWriteBufferNumber] |= BUFFER_DIRTY; + bufferState[currentWriteBufferNumber] &= ~BUFFER_LOCKED; + + // turn off the LED at the end of a sector write + PORT(STATUS_LED_PORT) |= (1<> 8) ^ 0xA1]); + crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xA1]); + crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xA1]); + crc = (crc << 8) ^ pgm_read_word(&crc_ccitt[(uint8_t)(crc >> 8) ^ 0xFB]); + + for (uint16_t i=0; i> 8) ^ sectorBuf[bufferNumber][i]]); + } + + if (crc != receivedCRC) + { + strncpy(textBuf, "checksum fail", TEXTBUF_SIZE); + writeErrorNumber = 70; + WriteError(); + } +} + +// pin state change interrupt: WR_TICK +ISR(PCINT0_vect) +{ + uint8_t wrTickBit = bit_is_set(PIN(CPLD_RD_ACK_WR_TICK_PORT), CPLD_RD_ACK_WR_TICK_PIN); + + // was a new byte written? + if (writeMode && wrTickBit != wrTick) + { + wrTick = wrTickBit; + + if (!mfmMode) + { + HandleGCRWrite(); + } + else + { + if (wrTickBit == 0) + { + // high nibble arrives first + writeTemp = (PIN(CPLD_DATA_PORT) << 4) & 0xF0; + return; + } + else + { + writeTemp |= (PIN(CPLD_DATA_PORT) & 0x0F); + + if (writeCount == 2) + { + if (writeTemp == 0xFB) + { + writeCount++; + + // header received OK! + currentWriteBufferNumber = currentSector; // assume the buffer to write was the last one read + + if (bufferState[currentWriteBufferNumber] & BUFFER_LOCKED) + { + snprintf(textBuf, TEXTBUF_SIZE, "buf locked %d/%d:%d", currentTrack, currentSide, currentSector); + writeErrorNumber = 71; + WriteError(); + return; + } + + pSectorBuf = sectorBuf[currentWriteBufferNumber]; + bufferState[currentWriteBufferNumber] |= BUFFER_LOCKED; + bufferState[currentWriteBufferNumber] &= ~BUFFER_DATA_VALID; + wrTrack = currentTrack; + wrSide = currentSide; + wrSector = currentSector; // assume the buffer to write was the last one read + + // turn on the LED when receiving a sector write + PORT(STATUS_LED_PORT) &= ~(1<> 2) | ((__c1 & 0xC0) >> 4) | ((__c2 & 0xC0) >> 6)) + +// rotate left +#define rot_ck0(__ck0) \ + do { \ + __ck0 &= 0xFF; \ + __ck0 = (__ck0 << 1) | (__ck0 >> 7);\ + } while(0) + +// ADC __ckr, __in; __out = __in ^ __ckl +#define enc_byte(__in, __out, __ckl, __ckr) \ + do { \ + uint8_t __d = __in; \ + __ckr += __d; \ + __ckr += (__ckl & 0x100) >> 8; \ + __ckl &= 0xFF; \ + __out = __d ^ __ckl; \ + } while(0) + +#define SendByteAndCheckRestart(b) \ + do { \ + if (restartDisk) \ + goto restart; \ + SendByte(b); \ + } while(0) + +void SendMFMSync() +{ + // send A1 sync + + // SendByte + // TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups + PORT(CPLD_DATA_PORT) = 0x0A; // data in bits 3-0, sync flag in bit 4 + PORT(CPLD_RD_READY_TK0_PORT) |= (1<> 8) ^ 0xA1]); + + // SendByte + // TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups + PORT(CPLD_DATA_PORT) = 0x11; // data in bits 3-0, sync flag in bit 4 + PORT(CPLD_RD_READY_TK0_PORT) |= (1<> 4) & 0x0F; + + //if (restartDisk) + // return; + + // SendByte + // TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups + PORT(CPLD_DATA_PORT) = out; + PORT(CPLD_RD_READY_TK0_PORT) |= (1<> 8) ^ data]); + + //if (restartDisk) + // return; + + // SendByte + // TODO: what if an interrupt has switched the data port to an input? This will turn on pull-ups + PORT(CPLD_DATA_PORT) = out; + PORT(CPLD_RD_READY_TK0_PORT) |= (1< 128) + lcd_vop--; + } + else if (bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN)) + { + eeprom_update_byte((uint8_t*)1, lcd_vop); + break; + } + + LcdWrite(LCD_CMD, 0x21); // LCD Extended Commands. + LcdWrite(LCD_CMD, lcd_vop); // Set LCD Vop (Contrast). + LcdWrite(LCD_CMD, 0x20); + } +} + +void PromptForFirmwareUpdate() +{ + LcdGoto(0,0); + LcdClear(); + LcdGoto(0,0); + LcdTinyStringP(PSTR("CPLD FIRMWARE UPDATE"), TEXT_NORMAL); + LcdGoto(0,2); + LcdTinyStringP(PSTR("Release buttons to"), TEXT_NORMAL); + LcdGoto(0,3); + LcdTinyStringP(PSTR("begin"), TEXT_NORMAL); + + // wait for the buttons to be released + while (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) || + bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) || + bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN)) + {} + + LcdGoto(0,2); + LcdTinyStringP(PSTR("NEXT: Load firmware"), TEXT_NORMAL); + LcdGoto(0,3); + LcdTinyStringP(PSTR("PREV: Cancel"), TEXT_NORMAL); + _delay_ms(400); + + // wait for a button press + while (bit_is_set(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && + bit_is_set(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && + bit_is_set(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN)) + {} + + if (bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN)) + { + UpdateFirmware(); + } + else + { + LcdClear(); + } +} + +uint32_t imageFirstBlock, imageLastBlock; + +bool OpenImageFile() +{ + LcdClear(); + LcdGoto(0,0); + LcdTinyString(selectedLongFile, TEXT_NORMAL); + + LcdGoto(0,1); + + // open the disk image file + // to-do: check if the file is read-only on the card + bool openOK = true; + if (!f.open(selectedFile, O_RDWR)) + { + if (f.open(selectedFile, O_RDONLY)) + { + // TODO: How do we tell the CPLD the disk is read-only? + readOnly = true; + } + else + { + LcdTinyStringP(PSTR("error opening image"), TEXT_NORMAL); + openOK = false; + } + } + else + { + if (selectedFileType == DISK_IMAGE_400K || selectedFileType == DISK_IMAGE_DISKCOPY_400K) + { + numberOfDiskSides = 1; + } + else + { + numberOfDiskSides = 2; + } + } + + // get address of file on SD + if (openOK && !f.contiguousRange(&imageFirstBlock, &imageLastBlock)) + { + LcdTinyStringP(PSTR("image not contiguous"), TEXT_NORMAL); + openOK = false; + } + + if (!openOK) + { + _delay_ms(4000); // wait 4 seconds + } + else + { + LcdGoto(0,1); + // show disk image type + switch (selectedFileType) + { + case DISK_IMAGE_400K: + LcdTinyStringP(PSTR("400K raw image"), TEXT_NORMAL); + break; + + case DISK_IMAGE_800K: + LcdTinyStringP(PSTR("800K raw image"), TEXT_NORMAL); + break; + + case DISK_IMAGE_1440K: + LcdTinyStringP(PSTR("1440K raw image"), TEXT_NORMAL); + mfmMode = true; + break; + + case DISK_IMAGE_DISKCOPY_400K: + LcdTinyStringP(PSTR("400K DiskCopy image"), TEXT_NORMAL); + break; + + case DISK_IMAGE_DISKCOPY_800K: + LcdTinyStringP(PSTR("800K DiskCopy image"), TEXT_NORMAL); + break; + + case DISK_IMAGE_DISKCOPY_1440K: + LcdTinyStringP(PSTR("1440K DiskCopy image"), TEXT_NORMAL); + mfmMode = true; + break; + + default: + break; + } + + selectedFileIsDiskCopyFormat = (selectedFileType >= DISK_IMAGE_DISKCOPY_400K); + + if (bit_is_set(PIN(CARD_WPROT_PORT), CARD_WPROT_PIN)) + readOnly = true; + + // mount DiskCopy images read-only + if (selectedFileIsDiskCopyFormat) + readOnly = true; + + uint16_t volumeNameOffset = selectedFileIsDiskCopyFormat ? 0x424 + 0x54 : 0x424; + f.seekSet(volumeNameOffset); // offset of the Macintosh disk name in the image file + f.read(§orBuf[0][0], SECTOR_DATA_SIZE); + int nameLen = sectorBuf[0][0]; + uint8_t* name = §orBuf[0][1]; + name[nameLen] = 0; + name[21] = 0; // in case nameLen was bogus, terminate the string after 21 chars, which is the longest displayable name on the LCD + LcdGoto(0,2); + LcdTinyString((char*)name, TEXT_NORMAL); + LcdGoto(0,4); + LcdTinyStringP(PSTR("Track Side"), TEXT_NORMAL); + + // show a lock icon if the disk image is mounted as read-only + if (readOnly) + { + LcdGoto(77,0); + LcdWrite(LCD_DATA, 0x00); + LcdWrite(LCD_DATA, 0x78); + LcdWrite(LCD_DATA, 0x7E); + LcdWrite(LCD_DATA, 0x79); + LcdWrite(LCD_DATA, 0x79); + LcdWrite(LCD_DATA, 0x7E); + LcdWrite(LCD_DATA, 0x78); + } + } + + f.close(); + return openOK; +} + +void ResetDiskState() +{ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + // useless code to prevent the "unused" bootloader_info array from being optimized away when optimizations are turned on + // there's probably a nicer way to accomplish this. + currentTrack = bootloader_info[currentTrack]; + + InitPorts(); + + currentTrack = 0; + prevTrack = 0; + restartDisk = false; + currentSide = 0; + prevSide = 0; + writeMode = 0; + diskInserted = false; + numberOfDiskSides = 2; + currentSector = 0; + readOnly = false; + mfmMode = false; + writeError = false; + writeDisplayTimer = 0; + tachFlutter = 0; + writeErrorNumber = 0; + writeCount = 0; + + for (uint8_t i=0; i= trackLen - 1) + return 0; + else + return prevSectorNumber + 1; + } + else + { + uint8_t halfTrackLen = (trackLen + 1) >> 1; + + // process sectors in interleaved order: + // 12 sector tracks: 0 6 1 7 2 8 3 9 4 10 5 11 + // 11 sector tracks: 0 6 1 7 2 8 3 9 4 10 5 + // 10 sector tracks: 0 5 1 6 2 7 3 8 4 9 + // 9 sector tracks: 0 5 1 6 2 7 3 8 4 + // 8 sector tracks: 0 4 1 5 2 6 3 7 + // This is how real floppies are formatted, and should improve read performance if the Mac + // can't completely process sector N before sector N+1 begins. It should also improve sector-by-sector + // write performance, because the Mac alternately reads (address header) and writes (data section) in + // this mode, and proper interleaving means it will read the desired address header sooner if the Mac + // isn't fast enough to process the sectors linearly (which it likely isn't). + if (prevSectorNumber > trackLen - 1 || + ((trackLen & 1) == 0 && prevSectorNumber == trackLen - 1) || + ((trackLen & 1) == 1 && prevSectorNumber == halfTrackLen - 1)) + return 0; + else if (prevSectorNumber < halfTrackLen) + return prevSectorNumber + halfTrackLen; + else + return prevSectorNumber + 1 - halfTrackLen; + } +} + +void ReadDiskCopy42Block(SdFat& sd, uint32_t blockToRead, uint8_t bufferNumber) +{ + // for a DiskCopy 4.2 image, read two blocks into a temp buffer, then copy the unaligned data into the sector buffer. + uint16_t i; + + if (!sd.card()->readStart(blockToRead)) + error("SD read start error"); + + // read part 1 + if (!sd.card()->readData(extraBuf)) + error("SD read error D"); + for (i=0; i<512-0x54; i++) + sectorBuf[bufferNumber][i] = extraBuf[0x54 + i]; + + // read part 2 + if (!sd.card()->readData(extraBuf)) + error("SD read error D"); + for (i=512-0x54; i<512; i++) + sectorBuf[bufferNumber][i] = extraBuf[0x54 + i - 512]; + + sd.card()->readStop(); +} + +void FlushDirtySectors(SdFat& sd, uint8_t trackNumber) +{ + uint8_t trackLen = trackLength(trackNumber); + uint8_t firstDirtyBuffer = NUM_BUFFERS, lastDirtyBuffer=0; + + // determine the dirty range + for (uint8_t i=0; ireadBlock(blockToRead, sectorBuf[i])) + error("SD read error W"); + } + } + + uint32_t firstBlockToWrite = imageFirstBlock + ((uint32_t)trackStart(trackNumber) * numberOfDiskSides + firstDirtyBuffer); + + if (mfmMode) + firstBlockToWrite += trackLen * wrSide; + + uint32_t numBuffersToWrite = lastDirtyBuffer + 1 - firstDirtyBuffer; + + if (!sd.card()->writeStart(firstBlockToWrite, numBuffersToWrite)) + error("SD writeStart fail"); + + for (uint8_t i=firstDirtyBuffer; i<=lastDirtyBuffer; i++) + { + if (!sd.card()->writeData(sectorBuf[i])) + error("SD write error"); + + bufferState[i] &= ~BUFFER_DIRTY; + bufferState[i] &= ~BUFFER_LOCKED; + } + + if (!sd.card()->writeStop()) + error("SD writeStop fail"); + + writeDisplayTimer = 25; + millitimerOff(); + + uint32_t writeTime = millis() - t0; + + snprintf(textBuf, TEXTBUF_SIZE, "Saved trk %02d in %lu ", trackNumber, writeTime); + LcdGoto(0,5); + LcdTinyString(textBuf, TEXT_NORMAL); + } + } +} + +int main(void) +{ + millitimerInit(); + ResetDiskState(); + + ShowVersion(); + + LcdClear(); + + sei(); + _delay_ms(100); // wait for pending interrupts?? + + millitimerOn(); + _delay_ms(100); // wait for pending interrupts?? + + // if select and next are both held down, enter contrast adjust mode + if (bit_is_set(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && + bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && + bit_is_clear(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN)) + { + AdjustContrast(); + } + + SdFat sd; + if (!sd.init(SPI_FULL_SPEED)) + { + snprintf(textBuf, TEXTBUF_SIZE, "SD card error %d:%d", sd.card()->errorCode(), sd.card()->errorData()); + error(textBuf); + } + + millitimerOff(); + + // if prev and next are both held down, enter firmware update mode + if (bit_is_clear(PIN(PREV_BUTTON_PORT), PREV_BUTTON_PIN) && + bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN) && + bit_is_set(PIN(SELECT_BUTTON_PORT),SELECT_BUTTON_PIN)) + { + PromptForFirmwareUpdate(); + } + + InitDiskMenu(sd); + DrawDiskMenu(sd); + + // main loop + while (true) + { + if (writeError) + { + // report error encounted in the interrupt routine + error(textBuf); + } + + cli(); + uint8_t trackNumber = currentTrack; // save track in a local var, since currentTrack is volatile + uint8_t sideNumber = currentSide; // save side in a local var, since currentSide is volatile + restartDisk = false; + sei(); + + if (diskInserted) + { + // show the current track and side + snprintf(textBuf, TEXTBUF_SIZE, "%02d", trackNumber); + LcdGoto(24,4); + LcdTinyString(textBuf, TEXT_NORMAL); + snprintf(textBuf, TEXTBUF_SIZE, "%d ", sideNumber); + LcdGoto(56,4); + LcdTinyString(textBuf, TEXT_NORMAL); + + // sync RAM buffer with SD card when switching tracks, or also when switching sides for mfmMode + if (prevTrack != trackNumber || (mfmMode && (prevSide != sideNumber))) + { + // write any dirty sectors from the previous track/side back to the SD card + FlushDirtySectors(sd, prevTrack); + prevTrack = trackNumber; + prevSide = sideNumber; + + // Also mark all the buffers on this track as invalid, since they don't contain valid data for the new track. + for (uint8_t i=0; i= trackLen) + currentSector = 0; + + bool prevMotorOn = !bit_is_clear(PIN(CPLD_STEP_DIR_MOTOR_ON_PORT), CPLD_STEP_DIR_MOTOR_ON_PIN); + + while (true) + { + // check for disk eject + if (bit_is_set(PIN(CPLD_EJECT_REQ_PORT), CPLD_EJECT_REQ_PIN)) + { + PORT(CPLD_RD_READY_TK0_PORT) &= ~(1<readBlock(blockToRead, sectorBuf[bufferNumber])) + error("SD read error R"); + } + + millitimerOff(); + + bufferState[bufferNumber] |= BUFFER_DATA_VALID; + bufferState[bufferNumber] &= ~BUFFER_LOCKED; + } + + if (currentSector == 0) + { + if (motorOn) + { + // toggle LED during drive activity + PORT(STATUS_LED_PORT) ^= (1< 1) + writeDisplayTimer--; + } + + // "Flutter" the drive's TACH speed slightly, every time we pass sector 0 (about every 100-150ms). This avoids a bug + // in P_Sony_MakeSpdTbl in the 64K ROM (used in the Mac 128K and Mac 512K) where + // the Mac will crash if two successive TACH measurements see the exact same speed. + tachFlutter += 25; + if (tachFlutter >= 125) + tachFlutter = 0; + + // Set the timeout. OC1A will toggle after this many counts. New timeout threshold won't take effect until the next timeout. + OCR1A = driveTachHalfPeriod - tachFlutter; + } + + if (mfmMode) + { + // insert sector-to-sector gap bytes + for (uint8_t i=0; i<50; i++) + { + SendMFMAndCheckRestart(0x4E); + } + + // insert sync bytes + for (uint8_t i=0; i<12; i++) + { + SendMFMAndCheckRestart(0x00); + } + + // send the address block + crc = 0xFFFF; // reset CRC + SendMFMSync(); + SendMFMSync(); + SendMFMSync(); + SendMFMAndCheckRestart(0xFE); + SendMFMAndCheckRestart(trackNumber); + SendMFMAndCheckRestart(sideNumber); + SendMFMAndCheckRestart(currentSector+1); // MFM sector numbers are 1-based + SendMFMAndCheckRestart(2); // size = 128 * 2^N bytes, so 2 means 512 + uint8_t crc0 = (crc >> 8) & 0xFF; + uint8_t crc1 = crc & 0xFF; + SendMFMAndCheckRestart(crc0); + SendMFMAndCheckRestart(crc1); + + // insert Address to Data gap bytes + for (uint8_t i=0; i<22; i++) + { + SendMFMAndCheckRestart(0x4E); + } + + // insert sync bytes + for (uint8_t i=0; i<12; i++) + { + SendMFMAndCheckRestart(0x00); + } + + // send the data block + crc = 0xFFFF; // reset CRC + SendMFMSync(); + SendMFMSync(); + SendMFMSync(); + SendMFMAndCheckRestart(0xFB); + + for (uint16_t i=0; i> 8) & 0xFF; + crc1 = crc & 0xFF; + SendMFMAndCheckRestart(crc0); + SendMFMAndCheckRestart(crc1); + } + else + { + // ensure a short gap between sectors - otherwise once they're all cached, one sector will appear + // to immediately follow another on disk, which may cause problems for the Mac. + // Bad voodoo here: + // 1. In the Finder StuffIt copy test that sometimes dies after the first 18 tracks, the length of delay here + // seems to affect what track it will freeze on. + // 2. With a longer delay here, the first ~10 sectors of copying seem to have fewer or no "long writes". + // 3. Depending on the delay here, the Transcend 2GB SD card sometimes gets "writeStop fail" when saving tracks. + for (uint16_t i=0; i> 6)); + uint8_t checksum = (uint8_t)((trackLow ^ currentSector ^ trackHigh ^ format) & 0x3F); + + SendByteAndCheckRestart(0xD5); + SendByteAndCheckRestart(0xAA); + SendByteAndCheckRestart(0x96); + SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[trackLow])); + SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[currentSector])); + SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[trackHigh])); + SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[format])); + SendByteAndCheckRestart(pgm_read_byte(&sony_to_disk_byte[checksum])); + SendByteAndCheckRestart(0xDE); + SendByteAndCheckRestart(0xAA); + + // insert sync bytes between the address and data blocks + for (uint8_t i=0; i 0) + { + diskMenuSelection--; + DrawDiskMenu(sd); + _delay_ms(200); + } + } + else if (bit_is_clear(PIN(NEXT_BUTTON_PORT), NEXT_BUTTON_PIN)) + { + diskMenuSelection++; + DrawDiskMenu(sd); + _delay_ms(200); + } + else if (bit_is_clear(PIN(SELECT_BUTTON_PORT), SELECT_BUTTON_PIN)) + { + if (selectedFileType == DISK_IMAGE_DIRECTORY) + { + // remember where we came from, so we can get back later + char* pSubdirName = ((char*)sectorBuf[23]) + ((SHORTFILENAME_LEN+1) * subdirDepth); + strncpy(pSubdirName, selectedFile, SHORTFILENAME_LEN+1); + subdirDepth++; + + sd.chdir(selectedFile, true); + + diskMenuSelection = 0; + LcdClear(); + InitDiskMenu(sd); + DrawDiskMenu(sd); + _delay_ms(400); + } + else if (selectedFileType == DISK_IMAGE_UP_DIRECTORY) + { + subdirDepth--; + sd.chdir(true); // go to root directory + + for (uint8_t i=0; i