diff --git a/ArduinoFDC.cpp b/ArduinoFDC.cpp index 6b3a8d7..5ceee06 100644 --- a/ArduinoFDC.cpp +++ b/ArduinoFDC.cpp @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- + // 3.5"/5.25" DD/HD Disk controller for Arduino // Copyright (C) 2021 David Hansel // @@ -203,23 +203,31 @@ asm (" .equ TIFR, 0x1A\n" // timer 5 flag register #error "ArduinoFDC library requires 16MHz clock speed" #endif +#define MODE_GCR_DATA 4 +#define MODE_GCR_HEADER 3 struct DriveGeometryStruct { byte numTracks; byte numSectors; + byte numHeads; // number of heads, 1 = Single Side (SS) byte dataGap; byte trackSpacing; + byte moduLation ; // 0 = MFM, 1 = GCR }; -static struct DriveGeometryStruct geometry[5] = +static struct DriveGeometryStruct geometry[] = { - {40, 9, 80, 1}, // 5.25" DD (360 KB) - {40, 9, 80, 2}, // 5.25" DD disk in HD drive (360 KB) - {80, 15, 85, 1}, // 5.25" HD (1.2 MB) - {80, 9, 80, 1}, // 3.5" DD (720 KB) - {80, 18, 100, 1} // 3.5" HD (1.44 MB) + {40, 9, 2, 80, 1, MFM }, // 5.25" DD (360 KB) + {40, 9, 2, 80, 2, MFM }, // 5.25" DD disk in HD drive (360 KB) + {80, 15, 2, 85, 1, MFM }, // 5.25" HD (1.2 MB) + {80, 9, 2, 80, 1, MFM }, // 3.5" DD (720 KB) + {80, 18, 2,100, 1, MFM }, // 3.5" HD (1.44 MB) + {80, 10, 1, 80, 1, MFM }, // 5.25" DEC RX50 , template for other SS Disks formats ? +#ifdef WOZ + {35, 16, 1, 80, 2, WOZ } // Apple][ DOS3 / CPM +#endif }; @@ -227,7 +235,7 @@ static struct DriveGeometryStruct geometry[5] = //#define DEBUG ArduinoFDCClass ArduinoFDC; -static byte header[7]; +static byte header[8]; // was 8 // digitalWrite function for simulating open-collector outputs. @@ -353,7 +361,6 @@ static bool wait_index_hole() } - static byte read_data(byte bitlen, byte *buffer, unsigned int n, byte verify) { byte status; @@ -688,7 +695,9 @@ static void write_data(byte bitlen, byte *buffer, unsigned int n) TCCRB &= ~bit(WGM2); } - +#if defined(WOZ) || defined(NIBBLES) +#include "read_data_gcr.h" +#endif static byte format_track(byte *buffer, byte driveType, byte bitlen, byte track, byte side) { @@ -1095,10 +1104,15 @@ static byte format_track(byte *buffer, byte driveType, byte bitlen, byte track, } -static byte wait_header(byte bitlen, byte track, byte side, byte sector) +static byte wait_header(byte bitlen, byte moduLation, byte track, byte side, byte sector) { byte attempts = 50; - + byte status ; +#ifdef myDEBUG + Serial.print("wait_header: m="); + Serial.println(moduLation); + Serial.flush(); +#endif // check whether we can see any data pulses from the drive at all if( !check_pulse() ) { @@ -1111,7 +1125,14 @@ static byte wait_header(byte bitlen, byte track, byte side, byte sector) do { // wait for sync sequence and read 7 bytes of data - byte status = read_data(bitlen, header, 7, false); + if (moduLation == MFM ) { + + byte status = read_data(bitlen, header, 7, false); +#ifdef myDEBUG + Serial.print("wait_header: mfm st="); + Serial.println(status); + Serial.flush(); +#endif if( status==S_OK ) { @@ -1136,10 +1157,49 @@ static byte wait_header(byte bitlen, byte track, byte side, byte sector) Serial.write(10); } #endif - } + } else return status; } +#ifdef WOZ + else { + byte status = read_data_gcr(bitlen, header, 8, false, MODE_GCR_HEADER ) ; // header size is 8 +#ifdef myDEBUG + Serial.print("wait_header: gcr st="); + Serial.println(status); + Serial.flush(); +#endif + if ( status == S_OK ) { // for now status is always S_OK (0) + if ( ( header[0] ^ header[2] ^ header[4] ^ header[6] ) != 0 + || ( header[1] ^ header[3] ^ header[5] ^ header[7] ) != 0 ) { + Serial.println(F("Header CRC error!")); Serial.flush(); + } ; +// do 4x2 decode + header[1] = ( ( header[2] << 1) | 1 ) & header [3] ; // track expected in header[1] later + header[0] = ( ( ( header[4] << 1) | 1 ) & header [5] ) + 1 ; // adapt sector number to MFM (+1) +#ifdef myDEBUG + Serial.print("wait_header: gcr t,s'="); + Serial.println(header[1]); + Serial.println(header[0]); + Serial.flush(); +#endif + if( (track==0xFF || track==header[1]) && sector==header[0] ) { + return S_OK ; // track 0xFF: Don't care + } +#ifdef DEBUG + else + { + static const char hex[17] = "0123456789ABCDEF"; + Serial.write('h'); + for(byte i=0; i<2; i++) { Serial.write(hex[header[i]/16]); Serial.write(hex[header[i]&15]); } + Serial.write(10); + } +#endif + } else + return status; + } +#endif + } // try next sector while( --attempts>0 ); #ifdef DEBUG @@ -1194,13 +1254,13 @@ static void step_tracks(byte driveType, int tracks) } -static byte find_sector(byte driveType, byte bitLength, byte track, byte side, byte sector) +static byte find_sector(byte driveType, byte bitLength, byte moduLation, byte track, byte side, byte sector) { // select side digitalWriteOC(PIN_SIDE, side>0 ? LOW : HIGH); // wait for sector header - byte res = wait_header(bitLength, -1, side, sector); + byte res = wait_header(bitLength, moduLation, -1, side, sector); // if we found the sector header but it's not on the correct track then step to correct track and check again if( res==S_OK && header[1]!=track ) @@ -1213,7 +1273,7 @@ static byte find_sector(byte driveType, byte bitLength, byte track, byte side, b step_tracks(driveType, track-header[1]); noInterrupts(); - res = wait_header(bitLength, track, side, sector); + res = wait_header(bitLength, moduLation, track, side, sector); } else res = S_NOHEADER; @@ -1228,7 +1288,7 @@ static byte find_sector(byte driveType, byte bitLength, byte track, byte side, b { step_tracks(driveType, track); noInterrupts(); - res = wait_header(bitLength, track, side, sector); + res = wait_header(bitLength, moduLation, track, side, sector); } else { @@ -1250,7 +1310,12 @@ ArduinoFDCClass::ArduinoFDCClass() m_currentDrive = 0; m_motorState[0] = false; m_motorState[1] = false; +//m_driveType[0] = DT_3_HD; +#ifdef WOZ + m_driveType[0] = DT_5_WOZ; +#else m_driveType[0] = DT_3_HD; +#endif m_driveType[1] = DT_3_HD; m_bitLength[0] = 0; m_bitLength[1] = 0; @@ -1335,7 +1400,11 @@ void ArduinoFDCClass::setDriveType(enum DriveType type) // by default: 3.5" drives do not use DENSITY pin (disconnect) // 5.25 DD drives do not use DENSITY pin (disconnect) // 5.25" HD drives expect DENSITY to be LOW for low density - if( type==DT_5_DDonHD || type==DT_5_HD ) +#ifdef WOZ + if( type==DT_5_DDonHD || type==DT_5_HD || type==DT_5_RX50 || type==DT_5_WOZ ) +#else + if( type==DT_5_DDonHD || type==DT_5_HD || type==DT_5_RX50 ) +#endif setDensityPinMode(DP_OUTPUT_LOW_FOR_DD); else setDensityPinMode(DP_DISCONNECT); @@ -1361,6 +1430,7 @@ void ArduinoFDCClass::setDensityPinMode(enum DensityPinMode mode) } + void ArduinoFDCClass::setDensityPin() { #if defined(PIN_DENSITY) @@ -1404,7 +1474,12 @@ byte ArduinoFDCClass::getBitLength() case DT_3_DD: bitLength = 32; break; case DT_5_HD: bitLength = 16; break; case DT_5_DD: bitLength = 32; break; - + +#ifdef WOZ + case DT_5_WOZ: +#endif + case DT_5_RX50: + // bitLength = 27; break; // ?? case DT_5_DDonHD: { TCCRA = 0; @@ -1445,6 +1520,7 @@ byte ArduinoFDCClass::readSector(byte track, byte side, byte sector, byte *buffe { byte res = S_OK; byte driveType = m_driveType[m_currentDrive]; + byte moduLation = geometry[driveType].moduLation; // do some sanity checks if( !m_initialized ) @@ -1479,14 +1555,14 @@ byte ArduinoFDCClass::readSector(byte track, byte side, byte sector, byte *buffe noInterrupts(); // find the requested sector - res = find_sector(driveType, bitLength, track, side, sector); + res = find_sector(driveType, bitLength, moduLation, track, side, sector); // if we found the sector then read the data if( res==S_OK ) { // wait for data sync mark and read data - if( read_data(bitLength, buffer, 515, false)==S_OK ) - { + if ( moduLation == MFM ) { + if (read_data(bitLength, buffer, 515, false)==S_OK ) { if( buffer[0]!=0xFB ) { #ifdef DEBUG @@ -1503,8 +1579,78 @@ byte ArduinoFDCClass::readSector(byte track, byte side, byte sector, byte *buffe #endif res = S_CRC; } - } - } + } + } +#ifdef WOZ + else { + +#ifdef DEBUG +// turn off LED to signal ongoing read +// pinMode(LED_BUILTIN, OUTPUT); +digitalWrite(LED_BUILTIN,LOW); +#endif + res = read_data_gcr(bitLength, buffer, 346, false, MODE_GCR_DATA) ; // mode 4 = read data + +#ifdef DEBUG +digitalWrite(LED_BUILTIN,HIGH); +#endif + // decode GCR data, bit shuffling etc + static const byte decode_6x2[128] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // <= 0x80 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, // <= 0x90 + 0xff, 0xff, 0x02, 0x03, 0xff, 0x04, 0x05, 0x06, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x08, // <= 0xa0 + 0xff, 0xff, 0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0xff, 0xff, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, // <= 0xb0 + 0xff, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // <= 0xc0 + 0xff, 0xff, 0xff, 0x1b, 0xff, 0x1c, 0x1d, 0x1e, + 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x20, 0x21, // <= 0xd0 + 0xff, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x29, 0x2a, 0x2b, // <= 0xe0 + 0xff, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, + 0xff, 0xff, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, // <= 0xf0 + 0xff, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f + } ; + + const byte revbits[] = {0, 2, 1, 3}; // reverse 2 LSBs + + short i,j ; + + for(i = 0; i < 343; ++i) { + buffer[i] = decode_6x2[buffer[i] & 0x7f]; + if ( i > 0 ) buffer[i] ^= buffer[i-1]; + } + + if ( buffer[342] != 0 ) { + Serial.print("warning: bad XOR cheksum: "); + Serial.println(buffer[342],HEX); + res = S_CRC; + }; + // do bit shuffling, distribute bits from 0..84 to buffer in 86++ + for( i = 0; i < 84; ++i) { + buffer[86+i+0*86 ] = ( buffer[86+i+0*86 ] << 2 ) | revbits[ (buffer[i ] >> 0) & 0x3 ] ; + buffer[86+i+1*86 ] = ( buffer[86+i+1*86 ] << 2 ) | revbits[ (buffer[i ] >> 2) & 0x3 ] ; + buffer[86+i+2*86 ] = ( buffer[86+i+2*86 ] << 2 ) | revbits[ (buffer[i ] >> 4) & 0x3 ] ; + } + // 2 remaining bytes + buffer[ 86 + 84 ] = ( buffer[ 86 + 84 ] << 2 ) | revbits[ (buffer[84] >> 0) & 0x3 ]; + buffer[ 86 + 84 + 86 ] = ( buffer[ 86 + 84 + 86 ] << 2 ) | revbits[ (buffer[84] >> 2) & 0x3 ]; + buffer[ 86 + 85 ] = ( buffer[ 86 + 85 ] << 2 ) | revbits[ (buffer[85] >> 0) & 0x3 ]; + buffer[ 86 + 85 + 86 ] = ( buffer[ 86 + 85 + 86 ] << 2 ) | revbits[ (buffer[85] >> 2) & 0x3 ]; + +/* dest[84] = + (bit_reverse[src[84]&3] << 0) | + (bit_reverse[src[170]&3] << 2); + dest[85] = + (bit_reverse[src[85]&3] << 0) | + (bit_reverse[src[171]&3] << 2); */ + + //memmove(&buffer[1],&buffer[86],256); // compatible with MFM, 1 byte header, obsolete, now in dump_buffer + } +#endif + } // interrupts are ok again interrupts(); @@ -1514,10 +1660,13 @@ byte ArduinoFDCClass::readSector(byte track, byte side, byte sector, byte *buffe } // de-assert DRIVE_SELECT - driveSelect(HIGH); + // driveSelect(HIGH); // if we turned the motor on then turn it off again - if( turnMotorOff ) motorOff(); + if( turnMotorOff ) { + driveSelect(HIGH); + motorOff(); + } return res; } @@ -1527,7 +1676,12 @@ byte ArduinoFDCClass::writeSector(byte track, byte side, byte sector, byte *buff { byte res = S_OK; byte driveType = m_driveType[m_currentDrive]; - + byte moduLation = m_driveType[moduLation]; + // + if ( moduLation != MFM ) { + Serial.println(F("GCR not (yet) supported ! /n")); + return S_NOHEADER; + } // do some sanity checks if( !m_initialized ) return S_NOTINIT; @@ -1571,7 +1725,7 @@ byte ArduinoFDCClass::writeSector(byte track, byte side, byte sector, byte *buff noInterrupts(); // find the requested sector - res = find_sector(driveType, bitLength, track, side, sector); + res = find_sector(driveType, bitLength, moduLation, track, side, sector); // if we found the sector then write the data if( res==S_OK ) @@ -1583,7 +1737,7 @@ byte ArduinoFDCClass::writeSector(byte track, byte side, byte sector, byte *buff if( verify ) { // wait for sector to come around again - res = wait_header(bitLength, track, side, sector); + res = wait_header(bitLength, moduLation, track, side, sector); // wait for data sync mark and compare the data if( res==S_OK ) res = read_data(bitLength, buffer, 515, true); @@ -1613,6 +1767,10 @@ byte ArduinoFDCClass::formatDisk(byte *buffer, byte fromTrack, byte toTrack) byte driveType = m_driveType[m_currentDrive]; byte numTracks = geometry[driveType].numTracks; + if ( geometry[driveType].moduLation != MFM ) { + Serial.println(F("Formatting GCR not (yet) supported ! /n")); + return S_NOHEADER; + } // do some sanity checks if( !m_initialized ) return S_NOTINIT; @@ -1652,9 +1810,12 @@ byte ArduinoFDCClass::formatDisk(byte *buffer, byte fromTrack, byte toTrack) { digitalWriteOC(PIN_SIDE, HIGH); res = format_track(buffer, driveType, bitLength, track, 0); if( res!=S_OK ) break; - digitalWriteOC(PIN_SIDE, LOW); - res = format_track(buffer, driveType, bitLength, track, 1); if( res!=S_OK ) break; - if( track+1<=toTrack ) step_tracks(driveType, 1); + // suppress formatting back side + if ( geometry[driveType].numHeads > 1 ) { + digitalWriteOC(PIN_SIDE, LOW); + res = format_track(buffer, driveType, bitLength, track, 1); if( res!=S_OK ) break; + } + if( track+1<=toTrack ) step_tracks(driveType, 1); } } @@ -1691,6 +1852,8 @@ void ArduinoFDCClass::motorOn() void ArduinoFDCClass::motorOff() { + driveSelect(HIGH); // for drive delect + #if defined(PIN_MOTORB) && defined(PIN_SELECTB) digitalWriteOC(m_currentDrive==0 ? PIN_MOTORA : PIN_MOTORB, HIGH); #else @@ -1698,6 +1861,7 @@ void ArduinoFDCClass::motorOff() #endif m_motorState[m_currentDrive] = false; + } @@ -1786,8 +1950,30 @@ byte ArduinoFDCClass::numTracks() const return geometry[m_driveType[m_currentDrive]].numTracks; } +byte ArduinoFDCClass::numHeads() const +{ + return geometry[m_driveType[m_currentDrive]].numHeads; +} + +byte ArduinoFDCClass::moduLation() const +{ + return geometry[m_driveType[m_currentDrive]].moduLation; +} + byte ArduinoFDCClass::numSectors() const { return geometry[m_driveType[m_currentDrive]].numSectors; } + +void ArduinoFDCClass::patchGeometry(int tracks,int sectors,int trackSpacing) +{ + geometry[m_driveType[m_currentDrive]].numSectors=(byte) sectors; + geometry[m_driveType[m_currentDrive]].numTracks=(byte) tracks; + if (trackSpacing != 0 ) geometry[m_driveType[m_currentDrive]].trackSpacing=(byte) trackSpacing; + return ; +} + +#ifdef NIBBLES +#include "readNibbles.h" +#endif diff --git a/ArduinoFDC.h b/ArduinoFDC.h index d233c03..3702919 100644 --- a/ArduinoFDC.h +++ b/ArduinoFDC.h @@ -34,6 +34,18 @@ #define S_VERIFY 8 // Verify after write failed #define S_READONLY 9 // Attempt to write to a write-protected disk +#define MFM 0 // MFM Modulation +#define WOZ 1 // Apple][ GCR .... disable WOZ if not defineds + +#define NIBBLES // include sipport for nibbles read + +#if defined(__AVR_ATmega2560__) +//#define BUFF_SIZE 7168 +#define BUFF_SIZE 0x1a00 +#else +#define BUFF_SIZE 515 +#endif + class ArduinoFDCClass { public: @@ -43,7 +55,11 @@ class ArduinoFDCClass DT_5_DDonHD, // 5.25" double density disk in high density drive (360 KB) DT_5_HD, // 5.25" high density (1.2 MB) DT_3_DD, // 3.5" double density (720 KB) - DT_3_HD // 3.5" high density (1.44 MB) + DT_3_HD, // 3.5" high density (1.44 MB) + DT_5_RX50, // 5.25" RX50 +#ifdef WOZ + DT_5_WOZ // 5.25" Apple][ +#endif }; enum DensityPinMode { @@ -81,9 +97,18 @@ class ArduinoFDCClass // get number of tracks for the currently selected drive byte numTracks() const; + // get number of heads for the currently selected drive + byte numHeads() const; + // get number of sectors for the currently selected drive byte numSectors() const; + // get modulation + byte moduLation() const; + + // added: option to modifay drive geometry + void patchGeometry(int tracks,int sectors,int trackSpacing); + // set the density pin mode for the currently selected drive void setDensityPinMode(enum DensityPinMode mode); @@ -93,7 +118,10 @@ class ArduinoFDCClass // read will be in buffer[1..512] (NOT: 0..511!) // See error codes above for possible return values byte readSector(byte track, byte side, byte sector, byte *buffer); - + +#ifdef NIBBLES + byte readNibbles(byte track, byte side, byte mode, int delay, byte*buffer); +#endif // Write a sector to disk, // buffer MUST have a size of at least 516 bytes. // IMPORTANT: The 512 bytes of sector data to be written diff --git a/ArduinoFDC.ino b/ArduinoFDC.ino index 8af3159..eaa1cf2 100644 --- a/ArduinoFDC.ino +++ b/ArduinoFDC.ino @@ -22,13 +22,13 @@ // comment this out to remove high-level ArduDOS functions -#define USE_ARDUDOS +//#define USE_ARDUDOS // commenting this out will remove the low-level disk monitor #define USE_MONITOR // comenting this out will remove support for XModem data transfers -//#define USE_XMODEM +#define USE_XMODEM #if defined(__AVR_ATmega32U4__) && defined(USE_ARDUDOS) && (defined(USE_MONITOR) || defined(USE_XMODEM)) @@ -135,6 +135,10 @@ void print_drive_type(byte n) case ArduinoFDCClass::DT_5_HD: Serial.print(F("5.25\" HD")); break; case ArduinoFDCClass::DT_3_DD: Serial.print(F("3.5\" DD")); break; case ArduinoFDCClass::DT_3_HD: Serial.print(F("3.5\" HD")); break; + case ArduinoFDCClass::DT_5_RX50: Serial.print(F("5.25\" RX50 DD")); break; +#ifdef WOZ + case ArduinoFDCClass::DT_5_WOZ: Serial.print(F("5.25\" Apple][")); break; +#endif default: Serial.print(F("Unknown")); } } @@ -446,7 +450,7 @@ void arduDOS() MKFS_PARM param; param.fmt = FM_FAT | FM_SFD; // FAT12 type, no disk partitioning param.n_fat = 2; // number of FATs - param.n_heads = 2; // number of heads + param.n_heads = 2; // number of heads n_heads=2 OK for MSDOS format param.n_sec_track = ArduinoFDC.numSectors(); param.align = 1; // block alignment (not used for FAT12) @@ -598,11 +602,13 @@ void arduDOS() #ifdef USE_MONITOR // re-use the FatFs data buffer if ARDUDOS is enabled (to save RAM) -#ifdef USE_ARDUDOS -#define databuffer FatFs.win -#else -static byte databuffer[516]; -#endif +//#ifdef USE_ARDUDOS +//#define databuffer FatFs.win +//#else +//static byte databuffer[516]; // for mega +//#endif + +static byte databuffer[BUFF_SIZE+384]; // for mega #ifdef USE_XMODEM @@ -616,28 +622,99 @@ word xmodem_sector = 0, xmodem_data_ptr = 0xFFFF; bool xmodemHandlerSendMon(unsigned long no, char* data, int size) { - if( xmodem_data_ptr>=512 ) - { byte numsec = ArduinoFDC.numSectors(); - if( xmodem_sector >= 2*numsec*ArduinoFDC.numTracks() ) + byte numheads = ArduinoFDC.numHeads(); + byte buffer_pages; + unsigned short offset; // header bytes to take out every 515 bytes + unsigned short page_size = 512; // make this static + byte page_shift = 9; // 2 ^9 = 512 + byte p_offset = 1; + byte l_offset = 3; // 515 = 512 + 3 + if ( ArduinoFDC.moduLation() == MFM ) { + buffer_pages = numsec > 10 ? 1 : numsec; // better 9 if numsec=18 // BUFSIZE verwenden !! + } +#ifdef WOZ + else { // GCR + buffer_pages = numsec > 16 ? 1 : numsec; // apple sectors fit in 5.6 k + page_size = 256; + page_shift = 8; // 2 ^8 = 256 + p_offset = 86; + l_offset = 90; // 346 = 256 + 90 + } +#endif +//xmodem_data_prt is type word (16bit) + // 2**9 = 512 + if( xmodem_data_ptr>=buffer_pages<= numheads*numsec*ArduinoFDC.numTracks() ) // 2 = number of hreads ? { xmodem_status_mon = S_OK; return false; } + short int i,j,lbn; +// loop over multiple sectors + //for ( i=0 ; i < buffer_pages ; i++ ) { // fill databuffer with buffer_pages sectors + // with interleaving + for ( i=0 ; i < buffer_pages ; i++ ) { // fill databuffer with buffer_pages sectors + j = i << 1; // skew factor 2 + j= (j>=numsec) ? ((j-numsec) | 0x01) : j ; // interleaving, unevens after overflow + lbn=xmodem_sector+j; // don't touch xmodem_sector +//#define mydebug +#ifdef mydebug + Serial.print("j,lbn:"); + Serial.print(j); + Serial.print(" "); + Serial.println(lbn); +#endif - byte head = 0; - byte track = xmodem_sector / (numsec*2); - byte sector = xmodem_sector % (numsec*2); - if( sector >= numsec ) { head = 1; sector -= numsec; } + byte head = 0; byte track = lbn / (numsec*numheads); // heads + byte sector = lbn % (numsec*numheads); // heads + if( sector >= numsec ) { head = 1; sector -= numsec; } // should not happen * numhead=1 - byte r = S_NOHEADER, retry = 5; - while( retry>0 && r!=S_OK ) { r = ArduinoFDC.readSector(track, head, sector+1, databuffer); retry--; } - if( r!=S_OK ) { xmodem_status_mon = r; return false; } + byte r = S_NOHEADER, retry = 5; + //while( retry>0 && r!=S_OK ) { r = ArduinoFDC.readSector(track, head, sector+1, databuffer); retry--; } +#ifdef mydebug + Serial.print('T'); + Serial.print(track); + Serial.print('S'); + Serial.print(sector); + Serial.print('H'); + Serial.println(head); +#endif + //while( retry>0 && r!=S_OK ) { r = ArduinoFDC.readSector(track, head, sector+1, &databuffer[j*(page_size+p_offset)]) ; retry--; } + while( retry>0 && r!=S_OK ) { r = ArduinoFDC.readSector(track, head, sector+1, &databuffer[j*(page_size+l_offset)]) ; retry--; } +#ifdef mydebug + //short page_size = 512; // make this static + //byte page_shift = 9; + //byte p_offset = 1; + //byte l_offset = 3; // 515 = 512 + 3 + Serial.print("ret:"); + Serial.print(j); + Serial.print(" "); + Serial.println(r,HEX); +#endif + // 513 bytes, record identifyer + if( r!=S_OK ) { xmodem_status_mon = r; return false; } // better: continue ? + } +// + xmodem_sector+= buffer_pages; xmodem_data_ptr = 0; - xmodem_sector++; } // "size" is always 128 and sector length is 512, i.e. a multiple of "size" // readSector returns data in databuffer[1..512] - memcpy(data, databuffer+1+xmodem_data_ptr, size); + //memcpy(data, databuffer+1+xmodem_data_ptr, size); + offset=(xmodem_data_ptr>>page_shift)*l_offset+p_offset; // devide by 512, 3 bytes offset (515) + +#ifdef mydebug + Serial.print("O"); + Serial.print(offset); + Serial.print(" "); + Serial.println(xmodem_data_ptr,HEX); +#endif + // ???? xmodem_data_ptr now ptr ?? + memcpy(data, &databuffer[xmodem_data_ptr+offset], size); // byte is unsigned xmodem_data_ptr += size; return true; } @@ -649,17 +726,18 @@ bool xmodemHandlerReceiveMon(unsigned long no, char* data, int size) // writeSector expects data in databuffer[1..512] memcpy(databuffer+1+xmodem_data_ptr, data, size); xmodem_data_ptr += size; - + //xmodem_data_ptr is tyoe word (16 bit) if( xmodem_data_ptr>=512 ) { byte numsec = ArduinoFDC.numSectors(); - if( xmodem_sector >= 2*numsec*ArduinoFDC.numTracks() ) + byte numheads = ArduinoFDC.numHeads(); + if( xmodem_sector >= numheads*numsec*ArduinoFDC.numTracks() ) { xmodem_status_mon = S_OK; return false; } byte head = 0; - byte track = xmodem_sector / (numsec*2); - byte sector = xmodem_sector % (numsec*2); - if( sector >= numsec ) { head = 1; sector -= numsec; } + byte track = xmodem_sector / (numsec*numheads); + byte sector = xmodem_sector % (numsec*numheads); + if( sector >= numsec ) { head = 1; sector -= numsec; } // not possible for numherads=1 byte r = S_NOHEADER, retry = 5; while( retry>0 && r!=S_OK ) { r = ArduinoFDC.writeSector(track, head, sector+1, databuffer, xmodem_verify); retry--; } @@ -689,7 +767,7 @@ void monitor() if( cmd=='r' && n>=3 ) { - track=a1; sector=a2; head= (n==3) ? 0 : a3; + track=a1; sector=a2; head= (n==3) ? a3 : 0 ; if( head>=0 && head<2 && track>=0 && track=1 && sector<=ArduinoFDC.numSectors() ) { Serial.print(F("Reading track ")); Serial.print(track); @@ -700,7 +778,13 @@ void monitor() byte status = ArduinoFDC.readSector(track, head, sector, databuffer); if( status==S_OK ) { - dump_buffer(0, databuffer+1, 512); + if (ArduinoFDC.moduLation() == MFM ) { + dump_buffer(0, databuffer+1, 512 ); + } +#ifdef WOZ + else { dump_buffer(0, &databuffer[86], 256 ); + } +#endif Serial.println(); } else @@ -713,7 +797,8 @@ void monitor() { ArduinoFDC.motorOn(); for(track=0; track=2 ) // read nibble data + { + track=a1; + int read_delay = 0 ; + head= (n>1) ? a2 : 0 ; + if ( head > 5 ) { head = 0; read_delay = a2 ; } ; + byte mode= (n>2) ? a3 : 1; + byte maxtrack=ArduinoFDC.numTracks() ; + Serial.print(F("Reading nibble track ")); Serial.print(track); + Serial.print(F(" side ")); Serial.println(head); + Serial.print(F(" mode ")); Serial.println(mode); + Serial.print(F(" maxtrack ")); Serial.println(maxtrack); + Serial.print(F(" delay ")); Serial.println(read_delay); + Serial.flush(); + + // a3 = mode 1: nibble, 2:flux + if( head>=0 && head<2 && track>=0 && track=3 ) { - track=a1; sector=a2; head= (n==3) ? 0 : a3; - if( head>=0 && head<2 && track>=0 && track=1 && sector<=ArduinoFDC.numSectors() ) + track=a1; sector=a2; head= (n==3) ? a3 : 0 ; // was 0 : a3 + if( head>=0 && head=0 && track=1 && sector<=ArduinoFDC.numSectors() ) { Serial.print(F("Writing and verifying track ")); Serial.print(track); Serial.print(F(" sector ")); Serial.print(sector); @@ -777,7 +895,8 @@ void monitor() { ArduinoFDC.motorOn(); for(track=0; track=3 ) // change geomtry + { + if (n!=3) a3 = 0; // 0: don't change + Serial.println(" new drive geometry for drive "); + ArduinoFDC.patchGeometry(a1,a2,a3); + } + #endif #ifdef USE_ARDUDOS else if (cmd=='x' ) @@ -937,12 +1067,16 @@ void monitor() Serial.println(F("B [n] Fill buffer with 'n' or 00..FF if n not given")); Serial.println(F("m 0/1 Turn drive motor off/on")); Serial.println(F("s 0/1 Select drive A/B")); - Serial.println(F("t 0-4 Set type of current drive (5.25DD/5.25DDinHD/5.25HD/3.5DD/3.5HD)")); + Serial.println(F("t 0-6 Set type of current drive (5.25DD/5.25DDinHD/5.25HD/3.5DD/3.5HD/RX50/Apple][)")); Serial.println(F("f Low-level format disk (tf)")); + Serial.println(F("g t,s[,ts] Modify drive geometry (Maxtracks, Sectors, Track-Spacing")); #ifdef USE_XMODEM Serial.println(F("S Read disk image and send via XModem")); Serial.println(F("R [0/1] Receive disk image via XModem and write to disk (without/with verify)")); #endif +#ifdef NIBBLES + Serial.println(F("n t,d,m dump nibbles, d: ms delay, m: mode (2: flux) \n")); +#endif #ifdef USE_ARDUDOS Serial.println(F("x Exit monitor\n")); #endif @@ -955,7 +1089,6 @@ void monitor() #endif - // ------------------------------------------------------------------------------------------------- // Main functions // ------------------------------------------------------------------------------------------------- @@ -963,8 +1096,13 @@ void monitor() void setup() { - Serial.begin(115200); + //Serial.begin(115200); + Serial.begin(230400); // max supported by mincom on OSX +#ifdef WOZ + ArduinoFDC.begin(ArduinoFDCClass::DT_5_WOZ, ArduinoFDCClass::DT_3_HD); +#else ArduinoFDC.begin(ArduinoFDCClass::DT_3_HD, ArduinoFDCClass::DT_3_HD); +#endif // must save flash space if all three of ARDUDOS/MONITOR/XMODEM are enabled on UNO #if !defined(USE_ARDUDOS) || !defined(USE_MONITOR) || !defined(USE_XMODEM) || defined(__AVR_ATmega2560__) diff --git a/NEWFEATURES.txt b/NEWFEATURES.txt new file mode 100644 index 0000000..90ef114 --- /dev/null +++ b/NEWFEATURES.txt @@ -0,0 +1,50 @@ +## New Features + +I added a few new features to David's excellent code, demonstratimg the power of those AVR Arduinos. + +As other devices with more memory, the Nano can still do this and, morover, is fully 5V compliant! + + +1. Support for AppleII floppies + + Reading GCR formatted floppies is now supported, formattimg and writing is to be done. + Images read use phisical sector order. Interleaving is required for use of images in Mame etc. + + Select disk type 6 in monitor ('t 6'). + +2. Support for DD/SS floppies, e.g. DEC RX50 + + Select with 't 5' + +3. Monitor command to temporarily modify disk geometry + + 'g t,s[,ts]' max tracks, sectors, track spacing (1/2) + +4. Monitor command to read low level nibble/flux data + + 'n t,d,m' track, delay from index hole/ms, mode + + mode: + 1 Read nibble data with no wait + 2 Read flux data (cycles between pulses) + 3 Read nibbles after delay and waiting for GCR sector header + 4 Read nibbles after delay and waiting for GCR data header + +5. Caching for disk reads, nibble data + + Buffer size increase to 6656 bytes (1 nibble track) on Atmega MEGA + + +Code is "work in progess" and was tested on MEGA. Should work on Nano as well. + +New features may be disabled by uncommenting defines for NIBBLES and GCR. + +Open issues: +GCR Reading hangs on none GCR disks, or if head is stuck on track >35. +"motor on" from monitor does not work. +XModem works fine with TeraTerm, but fails with minicom. + +Please feel free to contact me for questions/suggestions. + +Detlef Clawin +dclawin@dctronic.de diff --git a/readNibbles.h b/readNibbles.h new file mode 100644 index 0000000..081c97d --- /dev/null +++ b/readNibbles.h @@ -0,0 +1,97 @@ + +byte ArduinoFDCClass::readNibbles(byte track, byte side, byte mode, int read_delay, byte *buffer) +{ + byte res = S_OK; + byte driveType = m_driveType[m_currentDrive]; + +#ifdef DEBUG +pinMode(LED_BUILTIN, OUTPUT); +digitalWrite(LED_BUILTIN,LOW); +#endif + // do some sanity checks + if( !m_initialized ) + return S_NOTINIT; + else if( track>=geometry[driveType].numTracks || side>1 ) + return S_NOHEADER; + + // if motor is not running then turn it on now + bool turnMotorOff = false; + if( !motorRunning() ) + { + turnMotorOff = true; + motorOn(); + } + + // assert DRIVE_SELECT + driveSelect(LOW); + // search track + interrupts(); + step_to_track0(); + step_tracks(driveType,track); // relative stepping, track byte here, isn't it integer ? + //noInterrupts(); // don 10 lines later + + // get MFM bit length (in processor cycles, motor must be running for this) + byte bitLength = getBitLength(); + + res = wait_index_hole(); + + if( bitLength==0 ) + res = S_NOTREADY; + else + { + // wait delay ms + delay(read_delay); + // set up timer + TCCRA = 0; + TCCRB = bit(CS0); // falling edge input capture, prescaler 1, no output compare + TCCRC = 0; + + // reading data is time sensitive so we can't have interrupts + noInterrupts(); + + // find the requested sector + // res = find_sector(driveType, bitLength, track, side, sector); + + // if we found the sector then read the data + // wait for data sync mark and read data + //res= read_data_gcr(bitLength, buffer, 5150, false) ; + #ifdef try_flux // this is buggy + short int i; + i=0; + while ( i < BUFF_SIZE ) { + while ( ICF == TIFR) {}; + buffer[i++] =TIFR-ICF ; + ICF = TIFR; + } + Serial.print("flux collected: "); + Serial.print(buffer[0],HEX); + Serial.println(buffer[1],HEX); + Serial.println(buffer[2],HEX); +#else + Serial.print("readNibbles, scalling read_data_gcr, mode,bitlen="); + Serial.println(mode); + Serial.println(bitLength); + Serial.flush(); + res= read_data_gcr(bitLength, buffer, BUFF_SIZE, false, mode) ; + //res= read_data_gcr(bitLength, buffer, 512 , false, mode) ; + Serial.print("read_data return:"); + Serial.println(res); +#endif + // interrupts are ok again + interrupts(); + + // stop timer + TCCRB = 0; + } + + // de-assert DRIVE_SELECT + driveSelect(HIGH); + + // if we turned the motor on then turn it off again + if( turnMotorOff ) { + motorOff(); + } + + return res; +} + diff --git a/read_data_gcr.h b/read_data_gcr.h new file mode 100644 index 0000000..53b4f14 --- /dev/null +++ b/read_data_gcr.h @@ -0,0 +1,191 @@ +typedef unsigned char byte; + +//static byte read_data_gcr(byte bitlen, byte *buffer, unsigned int n, byte verify) +static byte read_data_gcr(byte bitlen, byte *buffer, unsigned int n, byte verify, byte mode) +// mode: 0 = read after header search 1: read nibble (no search), 2: flux +{ + // Parameter mode: + // 1: read mibbles without trigger + // 2: read flux + // 3 read nibbles for header + // 4 read ribbles for data + + // iProblem: modes 3 and 4 hang if no header found, e.g.when head positioned beyond track 35 + + // verifay not used any more + + byte status; +#ifndef readpulse + asm volatile + ( + // define READPULSE macro (wait for pulse) + // macro arguments: + // length: none => just wait for pulse, don't check ( 9 cycles) + // 1 => wait for pulse and jump if NOT short (12/14 cycles) + // 2 => wait for pulse and jump if NOT medium (14/16 cycles) + // 3 => wait for pulse and jump if NOT long (12/14 cycles) + // dst: label to jump to if DIFFERENT pulse found + // + // on entry: r16 contains minimum length of medium pulse + // r17 contains minimum length of long pulse + // r18 contains time of previous pulse + // on exit: r18 is updated to the time of this pulse + // r22 contains the pulse length in timer ticks (=processor cycles) + // CLOBBERS: r19 + ".macro READGCR length=0,dst=undefined\n" + " sbis TIFR, ICF\n" // (1/2) skip next instruction if timer input capture seen + " rjmp .-4\n" // (2) wait more + " lds r19, ICRL\n" // (2) get time of input capture (ICR1L, lower 8 bits only) + " sbi TIFR, ICF\n " // (2) clear input capture flag + " mov r22, r19\n" // (1) calculate time since previous capture... + " sub r22, r18\n" // (1) ...into r22 + " mov r18, r19\n" // (1) set r18 to time of current capture + " .if \\length == 1\n" // waiting for short pule? + " cp r22, r16\n" // (1) compare r22 to min medium pulse + " brlo .+2\n" // (1/2) skip jump if less + " jmp \\dst\n" // (3) not the expected pulse => jump to dst + " .else \n" + " .if \\length == 2\n" // waiting for medium pulse? + " cp r16, r22\n" // (1) min medium pulse < r22? => carry set if so + " brcc .+2\n" // (1/2) skip next instruction if carry is clear + " cp r22, r17\n" // (1) r22 < min long pulse? => carry set if so + " brcs .+2\n" // (1/2) skip jump if greater + " jmp \\dst\n" // (3) not the expected pulse => jump to dst + " .else\n" + " .if \\length == 3\n" + " cp r22, r17\n" // (1) min long pulse < r22? + " brsh .+2\n" // (1/2) skip jump if greater + " jmp \\dst\n" // (3) not the expected pulse => jump to dst + " .endif\n" + " .endif\n" + " .endif\n" + ".endm\n" + + // define STOREGCR macro for storing or verifying data bit + // storing data : 5/14 cycles for "1", 4/13 cycles for "0" + ".macro STOREGCR data:req,done:req\n" + " lsl r20\n" // (1) shift received data + ".if \\data != 0\n" + " ori r20, 1\n" // (1) store "1" bit + ".endif\n" + " dec r21\n" // (1) decrement bit counter + " brne .+10\n" // (1/2) skip if bit counter >0 + " st Z+, r20\n" // (2) store received data byte + " ldi r21, 8\n" // (1) re-initialize bit counter + " subi r26, 1\n" // (1) subtract one from byte counter + " sbci r27, 0\n" // (1) + " brmi \\done\n" // (1/2) done if byte counter<0 + ".endm\n" ); +#endif + + asm volatile + ( + " push r21\n" // this was experimental .... + " push r20\n" // save r21/r2o + " cli\n" // disable interrupts + + // prepare for reading nibbles + // " mov r16, %2\n" // (1) r16 = 3 * (MFM bit len) = minimum length of medium pulse + // " lsr r16\n" // (1) + // " add r16, %2\n" // (1) r + // " add r16, %2\n" // (1) + // " mov r17, r16\n" // (1) r17 = 5 * (MFM bit len) = minimum length of long pulse + // " add r17, %2\n" // (1) + // " add r17, %2\n" // (1) + " ldi r16,96\n" // set fixed limits + " ldi r17,160\n" + // " ldi %0, 0\n" // (1) default return status is S_OK // why did this work before &0=7 + // " mov r15,%0\n" // (1) initialize timer overflow counter + " ldi r19, 0\n" // (1) default return status is S_OK // + " mov r15, r19\n" // (1) initialize timer overflow counter + " mov %0,r19\n" // (1) default return status is S_OK // + " READGCR \n" // preset r18 .etc, . required ? + " sbi TIFR, TOV\n" // (2) reset timer overflow flag +// +// select mode +// " ldi r19,2\n" +// " cp %3,r19 \n" //mode == 2, flux + " cpi %3,2 \n" //mode == 2, flux + " brne m1\n" + " rjmp floop\n" +// + "m1: cpi %3,1 \n" //mode == 1, no sync, , leave somthing in status, ? + " brne gsync\n" // rest is mode 0,3 ... sync + " rjmp grdo\n" // +// expect remaining part of first sync mark (..0011111111 1101 0101 10101010 10010110 ff d5 aa 96 ) + "gsync: READGCR 3,gsync\n" // (12) expect 001 so gehts + "bffst: ldi r21,7\n" // start + "bff: READGCR 1,gsync\n" // (14) expect 1 + " dec r21\n" + " brne bff \n" + " READGCR \n" // 1101 wait for 1st bit of d5 + " cp r17, r22\n" // (1) pulse length >= 3 bits + " brlo bffst\n" // 001 found, go back waiting for 1111111 + " READGCR 1,gsync\n" // 2nd + " ldi r21,3\n" // + "b3x01: READGCR 2,gsync\n" // 01 0101 3x 01 + " dec r21\n" + " brne b3x01 \n" + " READGCR 1,gsync\n" // 10101010 1 + " ldi r21,4\n" // 4 x 01 01 01 01 + "b4x01: READGCR 2,gsync\n" // + " dec r21\n" + " brne b4x01 \n" + " cpi %3,3 \n" //mode == 4, data with praeamble + " brne mo4\n" + "mo3: READGCR 3,gsync\n" // 0010110 + " READGCR 2,gsync\n" // 0110 + " READGCR 1,gsync\n" // 10 ... one bit bissing + " rjmp bit1\n" + "mo4: READGCR 2,gsync\n" // 1/0101101 d5 aa ad data preamble + " READGCR 2,gsync\n" // 01101 + " READGCR 1,gsync\n" // 101 + " READGCR 2,gsync\n" // 01 + "bit1: READGCR \n" // preload 1st bit of data, 0-bit in between preamble and data + " ldi r21, 8\n" // init bit counter with 1 prestored bit + " STOREGCR 1,grdone\n" // (5/14) store "1" bit + " rjmp grdo\n" // (1) store "1" bit + "bc: ldi r21, 8\n" // (1) initialize bit counter (8 bits per byte) + // nibble read loop + "grdo: READGCR\n" // (9) wait for pulse + " cp r22, r16\n" // (1) pulse length > 2 bits + " brlo grone\n" // (1/2) jump if not + " cp r22, r17\n" // (1) pulse length >= 3 bits + " brlo grtwo\n" // (1/2) jump if not + // 001-Pulse, or longer .. + " STOREGCR 0,grdone\n" // (5/14) store "1" bit + " STOREGCR 0,grdone\n" // (5/14) store "1" bit + " STOREGCR 1,grdone\n" // (5/14) store "1" bit + " rjmp grdo\n" // (2) back to start (still odd) + + // jump target for relative conditional jumps in STOREGCR macro + "grdone: rjmp grend\n" + + // 2 bit pulse + "grtwo: STOREGCR 0,grdone\n" // 2 zeros + " STOREGCR 1,grdone\n" + " rjmp grdo\n" // (2) back to start (now even) + + // short pulse (01) => read "1", still odd + "grone: STOREGCR 1,grdone\n" // 1 zero + " rjmp grdo\n" // (2) back to start (still odd) + + // loop for reading flux data + "floop: READGCR\n" // (9) wait for pulse + " st Z+, r22\n" // (2) store received data byte + " subi r26, 1\n" // (1) subtract one from byte counter + " sbci r27, 0\n" // (1) + " brmi grend\n" // (1/2) done if byte counter<0 + " rjmp floop\n" + // + "grend: pop r20 \n" // restore r20,21 (test) + " pop r21 \n" // + " sei \n" //enable interrups + : "=r"(status) // outputs + : "r"(verify), "r"(bitlen), "r"(mode),"x"(n-1), "z"(buffer) // inputs (x=r26/r27, z=r30/r31) + : "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22"); // clobbers + //} + + return status; +} +