From f59c2c2f32a35a0ff5648d0d8d01a2084f7bca24 Mon Sep 17 00:00:00 2001 From: dclawin <66134445+dclawin@users.noreply.github.com> Date: Sun, 11 Sep 2022 14:27:01 +0200 Subject: [PATCH] AppleII support and other features added 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 --- ArduinoFDC.cpp | 252 +++++++++++++++++++++++++++++++++++++++++------- ArduinoFDC.h | 32 +++++- ArduinoFDC.ino | 208 ++++++++++++++++++++++++++++++++------- NEWFEATURES.txt | 50 ++++++++++ readNibbles.h | 97 +++++++++++++++++++ read_data_gcr.h | 191 ++++++++++++++++++++++++++++++++++++ 6 files changed, 760 insertions(+), 70 deletions(-) create mode 100644 NEWFEATURES.txt create mode 100644 readNibbles.h create mode 100644 read_data_gcr.h 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; +} +