// ----------------------------------------------------------------------------- // 3.5"/5.25" DD/HD Disk controller for Arduino // Copyright (C) 2021 David Hansel // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // ----------------------------------------------------------------------------- #include "ArduinoFDC.h" #if defined(__AVR_ATmega328P__) // ------------------------------- Pin assignments for Arduino UNO/Nano/Pro Mini (Atmega328p) ------------------------------ #define PIN_STEP 2 // can be changed to different pin #define PIN_STEPDIR 3 // can be changed to different pin #define PIN_MOTORA 4 // can be changed to different pin #define PIN_SELECTA 5 // can be changed to different pin #define PIN_SIDE 6 // can be changed to different pin #define PIN_INDEX 7 // accesses via IDXPORT/IDXBIT #defines below #define PIN_READDATA 8 // must be pin 8 (ICP for timer1) #define PIN_WRITEDATA 9 // must be pin 9 (OCP for timer1) #define PIN_WRITEGATE 10 // accessed via WGPORT/WGBIT #defines below #define PIN_TRACK0 11 // can be changed to different pin #define PIN_WRITEPROT 12 // can be changed to different pin or commented out #define PIN_DENSITY 13 // can be changed to different pin or commented out #define PIN_MOTORB A0 // can be changed to different pin or commented out (together with PIN_SELECTB) #define PIN_SELECTB A1 // can be changed to different pin or commented out (together with PIN_MOTORB) asm (" .equ TIFR, 0x16\n" // timer 1 flag register " .equ TOV, 0\n" // overflow flag " .equ OCF, 1\n" // output compare flag " .equ ICF, 5\n" // input capture flag " .equ TCCRC, 0x82\n" // timer 1 control register C " .equ FOC, 0x80\n" // force output compare flag " .equ TCNTL, 0x84\n" // timer 1 counter (low byte) " .equ ICRL, 0x86\n" // timer 1 input capture register (low byte) " .equ OCRL, 0x88\n" // timer 1 output compare register (low byte) " .equ IDXPORT, 0x29\n" // INDEX pin register (digital pin 7, register PD7, accessed via LDS instruction) " .equ IDXBIT, 7\n" // INDEX pin bit (digital pin 7, register PD7) ); #define TIFR TIFR1 // timer 1 flag register #define TOV TOV1 // overflow flag #define OCF OCF1A // output compare flag #define ICF ICF1 // input capture flag #define TCCRA TCCR1A // timer 1 control register A #define COMA1 COM1A1 // timer 1 output compare mode bit 1 #define COMA0 COM1A0 // timer 1 output compare mode bit 0 #define TCCRB TCCR1B // timer 1 control register B #define CS1 CS11 // timer 1 clock select bit 1 #define CS0 CS10 // timer 1 clock select bit 0 #define WGM2 WGM12 // timer 1 waveform mode bit 2 #define TCCRC TCCR1C // timer 1 control register C #define FOC FOC1A // force output compare flag #define OCR OCR1A // timer 1 output compare register #define TCNT TCNT1 // timer 1 counter #define IDXPORT PIND // INDEX pin port (digital pin 7, register PD7) #define IDXBIT 7 // INDEX pin bit (digital pin 7, register PD7) #define WGPORT DDRB // WRITEGATE pin port (digital pin 10, register PB2) #define WGBIT 2 // WRITEGATE pin bit (digital pin 10, register PB2) #define OCDDR DDRB // DDR controlling WRITEDATA pin #define OCBIT 1 // bit for WRITEDATA pin #elif defined(__AVR_ATmega32U4__) // ----------------------- Pin assignments for Arduino Leonardo/Micro (Atmega32U4) -------------------------- #define PIN_STEP 2 // can be changed to different pin #define PIN_STEPDIR 3 // can be changed to different pin #define PIN_READDATA 4 // must be pin 4 (ICP for timer1) #define PIN_MOTORA 5 // can be changed to different pin #define PIN_SELECTA 6 // can be changed to different pin #define PIN_SIDE 7 // can be changed to different pin #define PIN_INDEX 8 // accesses via IDXPORT/IDXBIT #defines below #define PIN_WRITEDATA 9 // must be pin 9 (OCP for timer1) #define PIN_WRITEGATE 10 // accessed via WGPORT/WGBIT #defines below #if defined(ARDUINO_AVR_LEONARDO) #define PIN_TRACK0 11 // can be changed to different pin #define PIN_WRITEPROT 12 // can be changed to different pin or commented out #define PIN_DENSITY 13 // can be changed to different pin or commented out #else #define PIN_TRACK0 14 // can be changed to different pin #define PIN_WRITEPROT 15 // can be changed to different pin or commented out #define PIN_DENSITY 16 // can be changed to different pin or commented out #endif #define PIN_MOTORB A0 // can be changed to different pin or commented out (together with PIN_SELECTB) #define PIN_SELECTB A1 // can be changed to different pin or commented out (together with PIN_MOTORB) asm (" .equ TIFR, 0x16\n" // timer 1 flag register " .equ TOV, 0\n" // overflow flag " .equ OCF, 1\n" // output compare flag " .equ ICF, 5\n" // input capture flag " .equ TCCRC, 0x82\n" // timer 1 control register C " .equ FOC, 0x80\n" // force output compare flag " .equ TCNTL, 0x84\n" // timer 1 counter (low byte) " .equ ICRL, 0x86\n" // timer 1 input capture register (low byte) " .equ OCRL, 0x88\n" // timer 1 output compare register (low byte) " .equ IDXPORT, 0x23\n" // INDEX pin register (digital pin 8, register PB4, accessed via LDS instruction) " .equ IDXBIT, 4\n" // INDEX pin bit (digital pin 8, register PB4) ); #define TIFR TIFR1 // timer 1 flag register #define TOV TOV1 // overflow flag #define OCF OCF1A // output compare flag #define ICF ICF1 // input capture flag #define TCCRA TCCR1A // timer 1 control register A #define COMA1 COM1A1 // timer 1 output compare mode bit 1 #define COMA0 COM1A0 // timer 1 output compare mode bit 0 #define TCCRB TCCR1B // timer 1 control register B #define CS1 CS11 // timer 1 clock select bit 1 #define CS0 CS10 // timer 1 clock select bit 0 #define WGM2 WGM12 // timer 1 waveform mode bit 2 #define TCCRC TCCR1C // timer 1 control register C #define FOC FOC1A // force output compare flag #define OCR OCR1A // timer 1 output compare register #define TCNT TCNT1 // timer 1 counter #define IDXPORT PINB // INDEX pin port (digital pin 8, register PB4) #define IDXBIT 4 // INDEX pin bit (digital pin 8, register PB4) #define WGPORT DDRB // WRITEGATE pin port (digital pin 10, register PB6) #define WGBIT 6 // WRITEGATE pin bit (digital pin 10, register PB6) #define OCDDR DDRB // WRITEDATA pin port (digital pin 9, register PB5) #define OCBIT 5 // WRITEDATA pin bit (digital pin 9, register PB5) #elif defined(__AVR_ATmega2560__) // ------------------------------ Pin assignments for Arduino Mega (Atmega2560) ----------------------------- #define PIN_STEP 53 // can be changed to different pin #define PIN_STEPDIR 52 // can be changed to different pin #define PIN_MOTORA 51 // can be changed to different pin #define PIN_SELECTA 50 // can be changed to different pin #define PIN_SIDE 49 // can be changed to different pin #define PIN_INDEX 47 // accessed via IDXPORT/IDXBIT #defines below #define PIN_READDATA 48 // must be pin 48 (ICP for timer5) #define PIN_WRITEDATA 46 // must be pin 46 (OCP for timer5) #define PIN_WRITEGATE 45 // accessed via WGPORT/WGBIT #defines below #define PIN_TRACK0 44 // can be changed to different pin #define PIN_WRITEPROT 43 // can be changed to different pin or commented out #define PIN_DENSITY 42 // can be changed to different pin or commented out #define PIN_MOTORB 41 // can be changed to different pin or commented out (together with PIN_SELECTB) #define PIN_SELECTB 40 // can be changed to different pin or commented out (together with PIN_MOTORB) asm (" .equ TIFR, 0x1A\n" // timer 5 flag register " .equ TOV, 0\n" // overflow flag " .equ OCF, 1\n" // output compare flag " .equ ICF, 5\n" // input capture flag " .equ TCCRC, 0x122\n" // timer 5 control register C " .equ FOC, 0x80\n" // force output compare flag " .equ TCNTL, 0x124\n" // timer 5 counter (low byte) " .equ ICRL, 0x126\n" // timer 5 input capture register (low byte) " .equ OCRL, 0x128\n" // timer 5 output compare register (low byte) " .equ IDXPORT, 0x109\n" // INDEX pin register (digital pin 47, register PL2) " .equ IDXBIT, 2\n" // INDEX pin bit (digital pin 47, register PL2) ); #define TIFR TIFR5 // timer 5 flag register #define TOV TOV5 // overflow flag #define OCF OCF5A // output compare flag #define ICF ICF5 // input capture flag #define TCCRA TCCR5A // timer 5 control register A #define COMA1 COM5A1 // timer 5 output compare mode bit 1 #define COMA0 COM5A0 // timer 5 output compare mode bit 0 #define TCCRB TCCR5B // timer 5 control register B #define CS1 CS51 // timer 5 clock select bit 1 #define CS0 CS50 // timer 5 clock select bit 0 #define WGM2 WGM52 // timer 5 waveform mode bit 2 #define TCCRC TCCR5C // timer 5 control register C #define FOC FOC5A // force output compare flag #define OCR OCR5A // timer 5 output compare register #define TCNT TCNT5 // timer 5 counter #define IDXPORT PINL // INDEX pin port (digital pin 47, register PL2) #define IDXBIT 2 // INDEX pin bit (digital pin 47, register PL2) #define WGPORT DDRL // WRITEGATE pin port (digital pin 45, register PL4) #define WGBIT 4 // WRITEGATE pin bit (digital pin 45, register PL4) #define OCDDR DDRL // DDR controlling WRITEDATA pin #define OCBIT 3 // bit for WRITEDATA pin #else #error "ArduinoFDC library requires either an ATMega328P, Atmega32U4 or ATMega2560 processor (Arduino UNO, Leonardo or MEGA)" #endif #if F_CPU != 16000000 #error "ArduinoFDC library requires 16MHz clock speed" #endif struct DriveGeometryStruct { byte numTracks; byte numSectors; byte dataGap; byte trackSpacing; }; static struct DriveGeometryStruct geometry[5] = { {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) }; // un-commenting this will write more detailed error information to Serial //#define DEBUG ArduinoFDCClass ArduinoFDC; static byte header[7]; // digitalWrite function for simulating open-collector outputs. // Each output pin must be initialized by digitalWrite(pin, LOW) and pinMode(PIN, INPUT) // after that, switching the pinMode to INPUT will set the pin to high-Z state // and switching to OUTPUT will pull the pin low. // The floppy disk interface specification expects outputs to be open-collector void digitalWriteOC(byte pin, byte state) { if( state==LOW ) pinMode(pin, OUTPUT); else pinMode(pin, INPUT); } static const uint16_t PROGMEM crc16_table[256] = { 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 }; static uint16_t calc_crc(byte *buf, int n) { // already includes sync marks (0xA1, 0xA1, 0xA1) uint16_t crc = 0xCDB4; // compute CRC of remaining data while( n-- > 0 ) crc = pgm_read_word_near(crc16_table + (((crc >> 8) ^ *buf++) & 0xff)) ^ (crc << 8); return crc; } static bool is_write_protected() { #if defined(PIN_WRITEPROT) return !digitalRead(PIN_WRITEPROT); #else return false; #endif } static bool check_pulse() { // reset timer and capture/overrun flags TCNT = 0; TIFR = bit(ICF) | bit(TOV); // wait for either input capture or timer overrun while( !(TIFR & (bit(ICF) | bit(TOV))) ); // if there was an input capture then we are ok bool res = (TIFR & bit(ICF))!=0; // reset input capture and timer overun flags TIFR = bit(ICF) | bit(TOV); return res; } static bool wait_index_hole() { // reset timer and overrun flags TCNT = 0; TIFR = bit(TOV); byte ctr = 0; // wait for END of index hole (in case we're on the hole right now) while( !(IDXPORT & bit(IDXBIT)) ) { if( TIFR & bit(TOV) ) { // timer overflow happens every 4.096ms (65536 cycles at 16MHz) // meaning we haven't found a sync in that amount of time if( ++ctr == 0 ) { // we have tried for 256 * 4.096ms = 1.048 seconds to find a index hole // one rotation is 166 or 200ms so we have tried for 5 or more rotations => give up #ifdef DEBUG Serial.println(F("No index hole signal!")); Serial.flush(); #endif return false; } // clear overflow flag TIFR = bit(TOV); } } // wait for START of index hole (same as above) ctr = 0; while( (IDXPORT & bit(IDXBIT)) ) { if( TIFR & bit(TOV) ) { if( ++ctr == 0 ) { #ifdef DEBUG Serial.println(F("No index hole signal!")); Serial.flush(); #endif return false; } TIFR = bit(TOV); } } return true; } static byte read_data(byte bitlen, byte *buffer, unsigned int n, byte verify) { byte status; // expect at least 10 bytes of 0x00 followed by three sync marks (0xA1 with one missing clock bit) // Data bits : 0 0 ...0 1 0 1 0 0*0 0 1 1 0 1 0 0*0 0 1 1 0 1 0 0*0 0 1 // In MFM : (0)1010...10 0100010010001001 0100010010001001 0100010010001001 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 READPULSE 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 " rjmp \\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 " rjmp \\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 " rjmp \\dst\n" // (3) not the expected pulse => jump to dst " .endif\n" " .endif\n" " .endif\n" ".endm\n" // define STOREBIT macro for storing or verifying data bit // storing data : 5/14 cycles for "1", 4/13 cycles for "0" // verifying data : 5/15 cycles for "1", 4/14 cycles for "0" ".macro STOREBIT 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 .+22\n" // (1/2) skip if bit counter >0 " cpi %1, 0\n" // (1) are we verifying? " brne .+4\n" // (1/2) if yes, jump to verify " st Z+, r20\n" // (2) store received data byte " rjmp .+6\n" // (2) skip verify " ld r21, Z+\n" // (2) get next expected byte " cpse r20, r21\n" // (1/2) compare to received byte " rjmp rddiff\n" // (2) jump if different " 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" // prepare for reading SYNC " mov r16, %2\n" // (1) r16 = 2.5 * (MFM bit len) = minimum length of medium pulse " lsr r16\n" // (1) " add r16, %2\n" // (1) " add r16, %2\n" // (1) " mov r17, r16\n" // (1) r17 = 3.5 * (MFM bit len) = minimum length of long pulse " add r17, %2\n" // (1) " ldi %0, 0\n" // (1) default return status is S_OK " mov r15, %0\n" // (1) initialize timer overflow counter " sbi TIFR, TOV\n" // (2) reset timer overflow flag // wait for at least 80x "10" (short) pulse followed by "100" (medium) pulse "ws0: ldi r20, 0\n" // (1) initialize "short pulse" counter "ws1: sbis TIFR, TOV\n" // (1/2) skip next instruction if timer overflow occurred " rjmp ws2\n" // (2) continue (no overflow) " sbi TIFR, TOV\n" // (2) reset timer overflow flag " dec r15\n" // (1) overflow happens every 4.096ms, decrement overflow counter " brne ws2\n" // (1/2) continue if fewer than 256 overflows " ldi %0, 3\n" // (1) no sync found in 1.048s => return status is is S_NOSYNC " rjmp rdend\n" // (2) done "ws2: inc r20\n" // (1) increment "short pulse" counter " READPULSE\n" // (9) wait for pulse " cp r22, r16\n" // (1) pulse length < min medium pulse? " brlo ws1\n" // (1/2) repeat if so " cp r22, r17\n" // (1) pulse length < min long pulse? " brsh ws0\n" // (1/2) restart if this was a long pulse (expecting medium) " cpi r20, 80\n" // (1) did we see at least 80 short pulses? " brlo ws0\n" // (1/2) restart if not // expect remaining part of first sync mark (..00010010001001) " READPULSE 3,ws0\n" // (12) expect long pulse (0001) " READPULSE 2,ws0\n" // (14) expect medium pulse (001) " READPULSE 3,ws0\n" // (12) expect long pulse (0001) " READPULSE 2,ws0\n" // (14) expect medium pulse (001) // expect second sync mark (0100010010001001) " READPULSE 1,ws0\n" // (12) expect short pulse (01) " READPULSE 3,ws0\n" // (12) expect long pulse (0001) " READPULSE 2,ws0\n" // (14) expect medium pulse (001) " READPULSE 3,ws0\n" // (12) expect long pulse (0001) " READPULSE 2,ws0\n" // (14) expect medium pulse (001) // expect third sync mark (0100010010001001) " READPULSE 1,ws0\n" // (12) expect short pulse (01) " READPULSE 3,ws0\n" // (12) expect long pulse (0001) " READPULSE 2,ws0\n" // (14) expect medium pulse (001) " READPULSE 3,ws0\n" // (12) expect long pulse (0001) " READPULSE 2,ws0\n" // (14) expect medium pulse (001) // found SYNC => prepare for reading data " tst r27\n" // (1) test byte count " brpl .+2\n" // (1/2) skip following instruction if not negative " rjmp rdend\n" // (2) nothing to read (only waiting for sync) => end " ldi r21, 8\n" // (1) initialize bit counter (8 bits per byte) // odd section (previous data bit was "1", no unprocessed MFM bit) // shortest path: 19 cycles, longest path: 34 cycles // (longest path only happens when finishing a byte, about every 5-6 pulses) "rdo: READPULSE\n" // (9) wait for pulse " cp r22, r16\n" // (1) pulse length >= min medium pulse? " brlo rdos\n" // (1/2) jump if not " cp r22, r17\n" // (1) pulse length >= min long pulse? " brlo rdom\n" // (1/2) jump if not // long pulse (0001) => read "01", still odd " STOREBIT 0,rddone\n" // (4/13) store "0" bit " STOREBIT 1,rddone\n" // (5/14) store "1" bit " rjmp rdo\n" // (2) back to start (still odd) // jump target for relative conditional jumps in STOREBIT macro "rddone: rjmp rdend\n" // medium pulse (001) => read "0", now even "rdom: STOREBIT 0,rddone\n" // (4/13) store "0" bit " rjmp rde\n" // (2) back to start (now even) // short pulse (01) => read "1", still odd "rdos: STOREBIT 1,rddone\n" // (5/14) store "1" bit " rjmp rdo\n" // (2) back to start (still odd) // even section (previous data bit was "0", previous MFM "1" bit not yet processed) // shortest path: 19 cycles, longest path: 31 cycles "rde: READPULSE\n" // (9) wait for pulse " cp r22, r16\n" // (1) pulse length >= min medium pulse? " brlo rdes\n" // (1/2) jump if not // either medium pulse (1001) or long pulse (10001) => read "01" // (a long pulse should never occur in this section but it may just be a // slightly too long medium pulse so count it as medium) " STOREBIT 0,rdend\n" // (4/13) store "0" bit " STOREBIT 1,rdend\n" // (5/14) store "1" bit " rjmp rdo\n" // (2) back to start (now odd) // short pulse (101) => read "0" "rdes: STOREBIT 0,rdend\n" // (5/14) store "0" bit " rjmp rde\n" // (2) back to start (still even) "rddiff: ldi %0, 8\n" // return status is S_VERIFY (verify error) "rdend:\n" : "=r"(status) // outputs : "r"(verify), "r"(bitlen), "x"(n-1), "z"(buffer) // inputs (x=r26/r27, z=r30/r31) : "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22"); // clobbers return status; } asm (// define WRITEPULSE macro (used in write_data and format_track) ".macro WRITEPULSE length=0\n" " .if \\length==1\n" " sts OCRL, r16\n" // (2) set OCRxA to short pulse length " .endif\n" " .if \\length==2\n" " sts OCRL, r17\n" // (2) set OCRxA to medium pulse length " .endif\n" " .if \\length==3\n" " sts OCRL, r18\n" // (2) set OCRxA to long pulse length " .endif\n" " sbis TIFR, OCF\n" // (1/2) skip next instruction if OCFx is set " rjmp .-4\n" // (2) wait more " ldi r19, FOC\n" // (1) " sts TCCRC, r19\n" // (2) set OCP back HIGH (was set LOW when timer expired) " sbi TIFR, OCF\n" // (2) reset OCFx (output compare flag) ".endm\n"); static void write_data(byte bitlen, byte *buffer, unsigned int n) { // make sure OC1A is high before we enable WRITE_GATE OCDDR &= ~bit(OCBIT); // disable OC1A pin TCCRA = bit(COMA1) | bit(COMA0); // set OC1A on compare match TCCRC |= bit(FOC); // force compare match TCCRA = 0; // disable OC1A control by timer OCDDR |= bit(OCBIT); // enable OC1A pin // wait through beginning of header gap (22 bytes of 0x4F) TCCRB |= bit(WGM2); // WGMx2:10 = 010 => clear-timer-on-compare (CTC) mode TCNT = 0; // reset timer OCR = 352 * bitlen - 256; // 352 MFM bit lengths (22 bytes * 8 bits/byte * 2 MFM bits/data bit) * cycles/MFM bit - 16us (overhead) TIFR = bit(OCF); // clear OCFx while( !(TIFR & bit(OCF)) ); // wait for OCFx OCR = 255; // clear OCRH byte (we only modify OCRL below) TIFR = bit(OCF); // clear OCFx // set WRITEGATE to OUTPUT (pulls it low) WGPORT |= bit(WGBIT); // enable OC1A output pin control by timer (WRITE_DATA), initially high TCCRA = bit(COMA0); // COMxA1:0 = 01 => toggle OC1A on compare match asm volatile (// define GETNEXTBIT macro for getting next data bit into carry (4/9 cycles) // on entry: R20 contains the current byte // R21 contains the bit counter // X (R26/R27) contains the byte counter // Z (R30/R31) contains pointer to data buffer ".macro GETNEXTBIT\n" " dec r21\n" // (1) decrement bit counter " brpl .+10\n" // (1/2) skip the following if bit counter >= 0 " subi r26, 1\n" // (1) subtract one from byte counter " sbci r27, 0\n" // (1) " brmi wdone\n" // (1/2) done if byte counter <0 " ld r20, Z+\n" // (2) get next byte " ldi r21, 7\n" // (1) reset bit counter (7 more bits after this first one) " rol r20\n" // (1) get next data bit into carry ".endm\n" // initialize pulse-length registers (r16, r17, r18) " mov r16, %0\n" // r16 = (2*bitlen)-1 = time for short ("01") pulse " add r16, %0\n" " dec r16\n" " mov r17, r16\n" // r17 = (3*bitlen)-1 = time for medium ("001") pulse " add r17, %0\n" " mov r18, r17\n" // r18 = (4*bitlen)-1 = time for long ("0001") pulse " add r18, %0\n" // write 12 bytes (96 bits) of "0" (i.e. 96 "10" sequences, i.e. short pulses) " ldi r20, 0\n" " sts TCNTL, r20\n" // reset timer " ldi r20, 96\n" // initialize counter "wri: WRITEPULSE 1\n" // write short pulse " dec r20\n" // decremet counter " brne wri\n" // repeat until 0 // first sync "A1": 00100010010001001 " WRITEPULSE 2\n" // write medium pulse " WRITEPULSE 3\n" // write long pulse " WRITEPULSE 2\n" // write medium pulse " WRITEPULSE 3\n" // write long pulse (this is the missing clock bit) " WRITEPULSE 2\n" // write medium pulse // second sync "A1": 0100010010001001 " WRITEPULSE 1\n" // write short pulse " WRITEPULSE 3\n" // write long pulse " WRITEPULSE 2\n" // write medium pulse " WRITEPULSE 3\n" // write long pulse (this is the missing clock bit) " WRITEPULSE 2\n" // write medium pulse // third sync "A1": 0100010010001001 " WRITEPULSE 1\n" // write short pulse " WRITEPULSE 3\n" // write long pulse " WRITEPULSE 2\n" // write medium pulse " WRITEPULSE 3\n" // write long pulse (this is the missing clock bit) " WRITEPULSE 2\n" // write medium pulse // start writing data " sts OCRL, r16\n" // (2) set up timer for "01" sequence " ldi r21, 0\n" // (1) initialize bit counter to fetch next byte // just wrote a "1" bit => must be followed by either "01" (for "1" bit) or "00" (for "0" bit) // (have time to fetch next bit during the leading "0") "wro: GETNEXTBIT\n" // (4/9) fetch next data bit into carry " brcs wro1\n" // (1/2) jump if "1" // next bit is "0" => write "00" " lds r19, OCRL\n" // (2) get current OCRxAL value " add r19, %0\n" // (2) add one-bit time " sts OCRL, r19\n" // (2) set new OCRxAL value " rjmp wre\n" // (2) now even // next bit is "1" => write "01" "wro1: WRITEPULSE\n" // (7) wait and write pulse " sts OCRL, r16\n" // (2) set up timer for another "01" sequence " rjmp wro\n" // (2) still odd // just wrote a "0" bit, (i.e. either "10" or "00") where time for the trailing "0" was already added // to the pulse length (have time to fetch next bit during the already-added "0") "wre: GETNEXTBIT\n" // (4/9) fetch next data bit into carry " brcs wre1\n" // (1/2) jump if "1" // next bit is "0" => write "10" " WRITEPULSE\n" // (7) wait and write pulse " sts OCRL, r16\n" // (2) set up timer for another "10" sequence " rjmp wre\n" // (2) still even // next bit is "1" => write "01" "wre1: lds r19, OCRL\n" // (2) get current OCRxAL value " add r19, %0\n" // (2) add one-bit time " sts OCRL, r19\n" // (2) set new OCRxAL value " WRITEPULSE\n" // (7) wait and write pulse " sts OCRL, r16\n" // (2) set up timer for "01" sequence " rjmp wro\n" // (2) now odd // done writing "wdone: WRITEPULSE\n" // (9) wait for and write final pulse : // no outputs : "r"(bitlen), "x"(n), "z"(buffer) // inputs (x=r26/r27, z=r30/r31) : "r16", "r17", "r18", "r19", "r20", "r21"); // clobbers // set WRITEGATE back to input (releases it HIGH) WGPORT &= ~bit(WGBIT); // COMxA1:0 = 00 => disconnect OC1A (will go high) TCCRA = 0; // WGMx2:10 = 000 => Normal timer mode TCCRB &= ~bit(WGM2); } static byte format_track(byte *buffer, byte driveType, byte bitlen, byte track, byte side) { // 3.5" DD disk: // writing 95 + 1 + 65 + (7 + 37 + 515 + 69) * 8 + (7 + 37 + 515) bytes // => 5744 bytes per track = 45952 bits // data rate 250 kbit/second, rotation rate 300 RPM (0.2s per rotation) // => 50000 bits unformatted capacity per track // 3.5" HD disk: // writing 95 + 1 + 65 + (7 + 37 + 515 + 69) * 17 + (7 + 37 + 515) bytes // => 5744 bytes per track = 45952 bits // data rate 500 kbit/second, rotation rate 300 RPM (0.2s per rotation) // => 100000 bits unformatted capacity per track byte i; byte numsec = geometry[driveType].numSectors; byte datagaplen = geometry[driveType].dataGap; // pre-compute ID records byte *ptr = buffer; for(i=0; i clear-timer-on-compare (CTC) mode TCNT = 0; // reset timer OCR = 32; // clear OCRxH byte (we only modify OCRxL below) TIFR = bit(OCF); // clear OCFx // set WRITEGATE to OUTPUT (pulls it low) WGPORT |= bit(WGBIT); // enable OC1A output pin control by timer (WRITE_DATA), initially high TCCRA = bit(COMA0); // COMxA1:0 = 01 => toggle OC1A on compare match asm volatile (".macro WRTPS\n" " sts OCRL, r16\n" " call waitp\n" ".endm\n" ".macro WRTPM\n" " sts OCRL, r17\n" " call waitp\n" ".endm\n" ".macro WRTPL\n" " sts OCRL, r18\n" " call waitp\n" ".endm\n" // initialize " mov r16, %0\n" // r16 = (2*bitlen)-1 = time for short ("01") pulse " add r16, %0\n" " dec r16\n" " mov r17, r16\n" // r17 = (3*bitlen)-1 = time for medium ("001") pulse " add r17, %0\n" " mov r18, r17\n" // r18 = (4*bitlen)-1 = time for long ("0001") pulse " add r18, %0\n" // 1) ---------- 56x 0x4E (pre-index gap) // // 0x4E 0x4E ... // 0 1 0 0 1 1 1 0 0 1 0 0 1 1 1 0 ... // 1001001001010100 1001001001010100 ... // M M M M S S M M M M S S ... // => (MMMMSS)x56 " ldi r20, 56\n" // (1) write 56 gap bytes " call wrtgap\n" // returns 20 cycles after final pulse was written // 2) ---------- 12x 0x00 // // 0x4E 0x00 0x00 ... // 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... // 1001001001010100 1010101010101010 1010101010101010 ... // S M M M S S M S S S S S S S S S S S S S S S ... // => MSx95 " WRTPM\n" // write medium pulse " ldi r20, 95\n" // write 95 short pulses " call wrtshort\n" // returns 20 cycles after final pulse was written // 3) ---------- 3x SYNC 0xC2 // // 0x00 0xC2 0xC2 0xC2 // 0 0 0 0 0 0 0 0 1 1 0 0*0 0 1 0 1 1 0 0*0 0 1 0 1 1 0 0*0 0 1 0 // 1010101010101010 0101001000100100 0101001000100100 0101001000100100 // S S S S S S S S M S M L M L S M L M L S M L M // => MSMLM(LSMLM)x2 " ldi r20, 3\n" " WRTPM\n" // write medium pulse (returns 14 cycles after pulse) " rjmp iskip\n" // (2) "iloop: WRTPL\n" // write long pulse "iskip: WRTPS\n" // write short pulse " WRTPM\n" // write medium pulse " WRTPL\n" // write long pulse " WRTPM\n" // write medium pulse " dec r20\n" // (1) " brne iloop\n" // (1/2) // 4) ---------- index record (0xFC) // // 0xC2 0xFC // 1 1 0 0*0 0 1 0 1 1 1 1 1 1 0 0 // 0101001000100100 0101010101010010 // L S M L M L S S S S S M // => LSSSSSM " WRTPL\n" // write long pulse (returns 14 cycles after pulse) " ldi r20, 5\n" // (1) write 5 short pulses " call wrtshort\n" // 6 cycles until timer update, 20 cycles after pulse " WRTPM\n" // write medium pulse // 5) ---------- 50x 0x4E (post-index gap) // // 0xFC 0x4E 0x4E ... // 1 1 1 1 1 1 0 0 0 1 0 0 1 1 1 0 0 1 0 0 1 1 1 0 ... // 0101010101010010 1001001001010100 1001001001010100 ... // L S S S S S M S M M M S S M M M M S S ... // => SMMMSS (MMMMSS)x49 " ldi r20, 49\n" // (1) write 49 gap bytes " WRTPS\n" // write short pulse " call wrtgap2\n" // returns 20 cycles after final pulse was written // 6) ---------- 12x 0x00 // // 0x4E 0x00 0x00 ... // 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... // 1001001001010100 1010101010101010 1010101010101010 ... // S M M M S S M S S S S S S S S S S S S S S S ... // => MSx95 "secstart: WRTPM\n" // write medium pulse " ldi r20, 95\n" // write 95 short pulses " call wrtshort\n" // returns 20 cycles after final pulse was written // 7) ---------- 3x SYNC 0xA1 // // 0x00 0xA1 0xA1 0xA1 // 0 0 0 0 0 0 0 0 1 0 1 0 0*0 0 1 1 0 1 0 0*0 0 1 1 0 1 0 0*0 0 1 // 1010101010101010 0100010010001001 0100010010001001 0100010010001001 // S S S S S S S S M L M L M S L M L M S L M L M // => MLMLM(SLMLM)x2 // do not have sufficient time after final pulse from "wrtsync" call // => only write two bytes in "wrtsync", write final pulses directly to save time " ldi r20, 2\n" // only write first two bytes of sync " call wrtsync\n" // returns 20 cycles after final pulse was written " WRTPS\n" " WRTPL\n" " WRTPM\n" " WRTPL\n" " WRITEPULSE 2\n" // write medium pulse, returns 10 cycles after pulse was written // 8) ---------- ID record plus first 0x4E: 0xFE (cylinder) (side) (sector) (length) (CRC1) (CRC2) 0x4E) // // 0xA1 ... 0x4E // 1 0 1 0 0*0 0 1 ... 0 1 0 0 1 1 1 0 // 0100010010001001 ... ??01001001010100 // S L M L M ... ? ? M M S S // => (write pre-calculated bytes, starting odd) // worst case needs 20 cycles before timer is initialized " sts OCRL, r16\n" // (2) set up timer for "01" sequence " ldi r21, 0\n" // (1) initialize bit counter to fetch next byte " ldi r26, 8\n" // (1) initialize byte counter (8 bytes to write) // just wrote a "1" bit => must be followed by either "01" (for "1" bit) or "00" (for "0" bit) // (have time to fetch next bit during the leading "0") "fio: dec r21\n" // (1) decrement bit counter " brpl fio0\n" // (1/2) skip the following if bit counter >= 0 " subi r26, 1\n" // (1) subtract one from byte counter " brmi fidone\n" // (1/2) done if byte counter <0 " ld r20, Z+\n" // (2) get next byte " ldi r21, 7\n" // (1) reset bit counter (7 more bits after this first one) "fio0: rol r20\n" // (1) get next data bit into carry " brcs fio1\n" // (1/2) jump if "1" // next bit is "0" => write "00" " lds r19, OCRL\n" // (2) get current OCRxAL value " add r19, %0\n" // (2) add one-bit time " sts OCRL, r19\n" // (2) set new OCRxAL value " rjmp fie\n" // (2) now even // next bit is "1" => write "01" "fio1: WRITEPULSE\n" // (7) wait and write pulse " sts OCRL, r16\n" // (2) set up timer for another "01" sequence " rjmp fio\n" // (2) still odd // just wrote a "0" bit, (i.e. either "10" or "00") where time for the trailing "0" was already added // to the pulse length (have time to fetch next bit during the already-added "0") "fie: dec r21\n" // (1) decrement bit counter " brpl fie0\n" // (1/2) skip the following if bit counter >= 0 " subi r26, 1\n" // (1) subtract one from byte counter " brmi fidone\n" // (1/2) done if byte counter <0 " ld r20, Z+\n" // (2) get next byte " ldi r21, 7\n" // (1) reset bit counter (7 more bits after this first one) "fie0: rol r20\n" // (1) get next data bit into carry " brcs fie1\n" // (1/2) jump if "1" // next bit is "0" => write "10" " WRITEPULSE\n" // (7) wait and write pulse " sts OCRL, r16\n" // (2) set up timer for another "10" sequence " rjmp fie\n" // (2) still even // next bit is "1" => write "01" "fie1: lds r19, OCRL\n" // (2) get current OCRxAL value " add r19, %0\n" // (2) add one-bit time " sts OCRL, r19\n" // (2) set new OCRxAL value " WRITEPULSE\n" // (7) wait and write pulse " sts OCRL, r16\n" // (2) set up timer for "01" sequence " rjmp fio\n" // (2) now odd "fidone: \n" // 9) ---------- 21x 0x4E (post-ID gap) // // 0x4E 0x4E ... // 0 1 0 0 1 1 1 0 0 1 0 0 1 1 1 0 ... // 1001001001010100 1001001001010100 ... // S M M M S S M M M M S S ... // => (MMMMSS)x21 " ldi r20, 21\n" // (1) write 21 gap bytes " call wrtgap\n" // returns 20 cycles after final pulse was written // 10) ---------- 12x 0x00 // // 0x4E 0x00 0x00 ... // 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... // 1001001001010100 1010101010101010 1010101010101010 ... // S M M M S S M S S S S S S S S S S S S S S S ... // => MSx95 " WRTPM\n" // write medium pulse " ldi r20, 95\n" // write 95 short pulses " call wrtshort\n" // returns 20 cycles after final pulse was written // 11) ---------- 3x SYNC 0xA1 // // 0x00 0xA1 0xA1 0xA1 // 0 0 0 0 0 0 0 0 1 0 1 0 0*0 0 1 1 0 1 0 0*0 0 1 1 0 1 0 0*0 0 1 // 1010101010101010 0100010010001001 0100010010001001 0100010010001001 // S S S S S S S S M L M L M S L M L M S L M L M // => MLMLM(SLMLM)x2 " ldi r20, 3\n" // write three sync bytes " call wrtsync\n" // returns 20 cycles after final pulse was written // 12) ---------- data record 0xFB // // 0xA1 0xFB // 1 0 1 0 0*0 0 1 1 1 1 1 1 0 1 1 // 0100010010001001 0101010101000101 // S L M L M S S S S S L S // => SSSSSLS " ldi r20, 5\n" // write 5 short pulses " call wrtshort\n" // returns 20 cycles after final pulse was written " WRTPL\n" // write long pulse " WRTPS\n" // write short pulse // 13) ---------- data (512x 0xF6) // // 0xFB 0xF6 0xF6 ... // 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 0 1 1 1 1 0 1 1 0 ... // 0101010101000101 0101010100010100 0101010100010100 ... // S S S S S L S S S S S L S L S S S L S ... // => SSSSLS LSSSLS " ldi r26, 0\n" // write 2*256+0 = 512 bytes " ldi r27, 2\n" " WRTPS\n" // write short pulse " rjmp dskip\n" "dloop: WRTPL\n" // write long pulse "dskip: WRTPS\n" // write short pulse " WRTPS\n" // write short pulse " WRTPS\n" // write short pulse " WRTPL\n" // write long pulse " WRTPS\n" // write short pulse " dec r26\n" // decrement byte counter (low) " brne dloop\n" // loop until 0 " dec r27\n" // decrement byte counter (high) " brne dloop\n" // loop until 0 // 14) ---------- data checksum (0x2B, 0xF6) // // 0xF6 0x2B 0xF6 // 1 1 1 1 0 1 1 0 0 0 1 0 1 0 1 1 1 1 1 1 0 1 1 0 // 0101010100010100 1010010001000101 0101010100010100 // L S S S L S M S M L L S S S S S L S // => MSMLSLSSSSLS " WRTPM\n" // write medium pulse " WRTPS\n" // write short pulse " WRTPM\n" // write medium pulse " WRTPL\n" // write long pulse " WRTPL\n" // write short pulse " WRTPS\n" // write long pulse " WRTPS\n" // write short pulse " WRTPS\n" // write short pulse " WRTPS\n" // write short pulse " WRTPS\n" // write short pulse " WRTPL\n" // write long pulse " WRTPS\n" // write short pulse // if this was the last sector then the track is done " dec %1\n" // (1) decrement sector counter " breq ftdone\n" // (1/2) jump if done // 15) ---------- 54/102x 0x4E (post-data gap) // // 0xF6 0x4E 0x4E ... // 1 1 1 1 0 1 1 0 0 1 0 0 1 1 1 0 0 1 0 0 1 1 1 0 ... // 0101010100010100 1001001001010100 1001001001010100 ... // S S S S L S M M M M S S ... // => (MMMMSS) x datagaplen " mov r20, %2\n" // (1) write "datagaplen" gap bytes " call wrtgap\n" // returns 20 cycles after final pulse was written " rjmp secstart\n" // (2) repeat for next sector // ---------- track format done => write GAP bytes (0x4E) until INDEX hole seen "ftdone: ldi r20, 1\n" // (1) write one byte " call wrtgap\n" // returns 20 cycles after final pulse was written " lds r20, IDXPORT\n" // (2) read INDEX signal " sbrc r20, IDXBIT\n" // (1/2) skip next instruction if PIND7 (INDEX) is LOW " rjmp ftdone\n" // (2) write more gap bytes " rjmp ftend\n" // done // -------------- subroutines // write short pulses // r20 contains number of short pulses to write // => takes 6 cycles until timer is initialized (including call) // => returns 20 cycles (max) after final pulse is written (including return statement) "wrtshort: WRTPS\n" " dec r20\n" // (1) " brne wrtshort\n" // (1/2) " ret\n" // (4) // write gap (0x4E) => (MMMMSS) x r20 // r20 contains number of gap bytes to write // => takes 6 cycles until timer is initialized (including call) // => returns 20 cycles (max) after final pulse is written (including return statement) "wrtgap: WRTPM\n" "wrtgap2: WRTPM\n" " WRTPM\n" " WRTPM\n" " WRTPS\n" " WRTPS\n" " dec r20\n" // (1) " brne wrtgap\n" // (1/2) " ret\n" // (4) // write SYNC (0xA1 with missing clock bit) => MLMLM (SLMLM) x r20 // r20 contains nyumber of SYNC bytes to write // => takes 7 cycles until timer is initialized (including call) // => returns 20 cycles (max) after final pulse is written (including return statement) "wrtsync: WRTPM\n" // write medium pulse (returns 14 cycles after pulse) " rjmp sskip\n" // (2) "sloop: WRTPS\n" // write short pulse "sskip: WRTPL\n" // write long pulse " WRTPM\n" // write medium pulse " WRTPL\n" // write long pulse " WRTPM\n" // write medium pulse " dec r20\n" // (1) " brne sloop\n" // (1/2) " ret\n" // (4) return // wait for pulse to be written // => returns 14 cycles (max) after pulse is written (including return statement) "waitp: sbis TIFR, OCF\n" // (1/2) skip next instruction if OCFx is set " rjmp .-4\n" // (2) wait more " ldi r19, FOC\n" // (1) " sts TCCRC, r19\n" // (2) set OCP back HIGH (was set LOW when timer expired) " sbi TIFR, OCF\n" // (2) reset OCFx (output compare flag) " ret\n" // (4) return "ftend:\n" : // no outputs : "r"(bitlen), "r"(numsec), "r"(datagaplen), "z"(buffer) // inputs (z=r30/r31) : "r16", "r17", "r18", "r19", "r20", "r21", "r26", "r27"); // clobbers // set WRITEGATE back to input (releases it HIGH) WGPORT &= ~bit(WGBIT); // COMxA1:0 = 00 => disconnect OC1A (will go high) TCCRA = 0; // WGMx2:10 = 000 => Normal timer mode TCCRB &= ~bit(WGM2); interrupts(); return S_OK; } static byte wait_header(byte bitlen, byte track, byte side, byte sector) { byte attempts = 50; // check whether we can see any data pulses from the drive at all if( !check_pulse() ) { #ifdef DEBUG Serial.println(F("Drive not ready!")); Serial.flush(); #endif return S_NOTREADY; } do { // wait for sync sequence and read 7 bytes of data byte status = read_data(bitlen, header, 7, false); if( status==S_OK ) { // make sure this is an ID record and check whether it contains the // expected track/side/sector information and the CRC is ok if( header[0]==0xFE && (track==0xFF || track==header[1]) && side==header[2] && sector==header[3] ) { if( calc_crc(header, 5) == 256u*header[5]+header[6] ) return S_OK; #ifdef DEBUG else { Serial.println(F("Header CRC error!")); Serial.flush(); } #endif } #ifdef DEBUG else { static const char hex[17] = "0123456789ABCDEF"; Serial.write('H'); for(byte i=0; i<5; i++) { Serial.write(hex[header[i]/16]); Serial.write(hex[header[i]&15]); } Serial.write(calc_crc(header, 5) == 256u*header[5]+header[6] ? '+' : '-'); Serial.write(10); } #endif } else return status; } while( --attempts>0 ); #ifdef DEBUG if( attempts==0 ) { Serial.println(F("Unable to find header!")); Serial.flush(); } #endif return S_NOHEADER; } static void step_track() { // produce LOW->HIGH pulse on STEP pin digitalWriteOC(PIN_STEP, LOW); delay(1); digitalWriteOC(PIN_STEP, HIGH); delay(10); } static bool step_to_track0() { byte n = 82; // step outward until TRACK0 line goes low digitalWriteOC(PIN_STEPDIR, HIGH); while( --n > 0 && digitalRead(PIN_TRACK0) ) step_track(); if( n==0 ) { // we have stpped for more than 80 tracks and are still not // seeing the TRACK0 signal #ifdef DEBUG Serial.println(F("No Track0 signal!")); Serial.flush(); #endif return false; } return true; } static void step_tracks(byte driveType, int tracks) { // if tracks<0 then step outward (outward towards track 0) otherwise step inward digitalWriteOC(PIN_STEPDIR, tracks<0 ? HIGH : LOW); tracks = abs(tracks); tracks = tracks * geometry[driveType].trackSpacing; while( tracks-->0 ) step_track(); delay(100); } static byte find_sector(byte driveType, byte bitLength, 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); // 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 ) { // make sure that the track number in the header we read is sensible if( header[1] 32 cycles/bit // for 360 RPM (166 ms/rotation) data rate is 300 mbps => 27 cycles/bit bitLength = l > 180000 ? 32 : 27; break; } } m_bitLength[m_currentDrive] = bitLength; } return m_bitLength[m_currentDrive]; } byte ArduinoFDCClass::readSector(byte track, byte side, byte sector, byte *buffer) { byte res = S_OK; byte driveType = m_driveType[m_currentDrive]; // do some sanity checks if( !m_initialized ) return S_NOTINIT; else if( track>=geometry[driveType].numTracks || sector<1 || sector>geometry[driveType].numSectors || 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); // get MFM bit length (in processor cycles, motor must be running for this) byte bitLength = getBitLength(); if( bitLength==0 ) res = S_NOTREADY; else { // 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 if( res==S_OK ) { // wait for data sync mark and read data if( read_data(bitLength, buffer, 515, false)==S_OK ) { if( buffer[0]!=0xFB ) { #ifdef DEBUG Serial.println(F("Unexpected record identifier")); for(int i=0; i<7; i++) { Serial.print(buffer[i], HEX); Serial.write(' '); } Serial.println(); #endif res = S_INVALIDID; } else if( calc_crc(buffer, 513) != 256u*buffer[513]+buffer[514] ) { #ifdef DEBUG Serial.print(F("Data CRC error. Found: ")); Serial.print(256u*buffer[513]+buffer[514], HEX); Serial.print(", expected: "); Serial.println(calc_crc(buffer, 513), HEX); #endif res = S_CRC; } } } // 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; } byte ArduinoFDCClass::writeSector(byte track, byte side, byte sector, byte *buffer, bool verify) { byte res = S_OK; byte driveType = m_driveType[m_currentDrive]; // do some sanity checks if( !m_initialized ) return S_NOTINIT; else if( track>=geometry[driveType].numTracks || sector<1 || sector>geometry[driveType].numSectors || 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); // get MFM bit length (in processor cycles, motor must be running for this) byte bitLength = getBitLength(); // set up timer TCCRA = 0; TCCRB = bit(CS0); // falling edge input capture, prescaler 1, no output compare TCCRC = 0; // check write protect (drive must be selected for this) if( bitLength==0 ) res = S_NOTREADY; else if( is_write_protected() ) res = wait_index_hole() ? S_READONLY : S_NOTREADY; else { // calculate CRC for the sector data buffer[0] = 0xFB; // "data" id uint16_t crc = calc_crc(buffer, 513); buffer[513] = crc/256; buffer[514] = crc&255; buffer[515] = 0x4E; // first byte of post-data gap // writing 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 write the data if( res==S_OK ) { // write the sector data write_data(bitLength, buffer, 516); // if we are supposed to verify the data then do so now if( verify ) { // wait for sector to come around again res = wait_header(bitLength, track, side, sector); // wait for data sync mark and compare the data if( res==S_OK ) res = read_data(bitLength, buffer, 515, true); } } // interrupts are ok again interrupts(); } // de-assert DRIVE_SELECT driveSelect(HIGH); // stop timer TCCRB = 0; // if we turned the motor on then turn it off again if( turnMotorOff ) motorOff(); return res; } byte ArduinoFDCClass::formatDisk(byte *buffer, byte fromTrack, byte toTrack) { byte res = S_OK; byte driveType = m_driveType[m_currentDrive]; byte numTracks = geometry[driveType].numTracks; // do some sanity checks if( !m_initialized ) return S_NOTINIT; else if( fromTrack>toTrack || fromTrack >= numTracks ) return S_OK; // if motor is not running then turn it on now bool turnMotorOff = false; if( !motorRunning() ) { turnMotorOff = true; motorOn(); } // assert DRIVE_SELECT driveSelect(LOW); // get MFM bit length (in processor cycles, motor must be running for this) byte bitLength = getBitLength(); // set up timer TCCRA = 0; TCCRB = bit(CS0); // prescaler 1 TCCRC = 0; if( bitLength==0 || !wait_index_hole() ) res = S_NOTREADY; else if( is_write_protected() ) res = S_READONLY; else if( !step_to_track0() ) res = S_NOTRACK0; else { if( fromTrack>0 ) step_tracks(driveType, fromTrack); if( toTrack>=numTracks ) toTrack = numTracks-1; for(byte track=fromTrack; track<=toTrack; track++) { 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); } } // de-assert DRIVE_SELECT driveSelect(HIGH); // if we turned the motor on then turn it off again if( turnMotorOff ) motorOff(); // stop timer TCCRB = 0; return res; } void ArduinoFDCClass::motorOn() { if( !motorRunning() ) { #if defined(PIN_MOTORB) && defined(PIN_SELECTB) digitalWriteOC(m_currentDrive==0 ? PIN_MOTORA : PIN_MOTORB, LOW); #else digitalWriteOC(PIN_MOTORA, LOW); #endif m_motorState[m_currentDrive] = true; // allow some time for the motor to spin up delay(1000); } } void ArduinoFDCClass::motorOff() { #if defined(PIN_MOTORB) && defined(PIN_SELECTB) digitalWriteOC(m_currentDrive==0 ? PIN_MOTORA : PIN_MOTORB, HIGH); #else digitalWriteOC(PIN_MOTORA, HIGH); #endif m_motorState[m_currentDrive] = false; } bool ArduinoFDCClass::motorRunning() const { return m_motorState[m_currentDrive]; } bool ArduinoFDCClass::selectDrive(byte drive) { if( drive == m_currentDrive ) return true; else { #if defined(PIN_MOTORB) && defined(PIN_SELECTB) m_currentDrive = (drive==0) ? 0 : 1; setDensityPin(); return true; #else #ifdef DEBUG if( drive!=0 ) Serial.println(F("PIN_MOTORB and/or PIN_SELECTB not defined - Can only control drive A")); #endif return false; #endif } } bool ArduinoFDCClass::haveDisk() const { bool res = false; // set up and start timer (prescaler 1), select drive TCCRA = 0; TCCRB = bit(CS0); TCCRC = 0; driveSelect(LOW); if( motorRunning() ) res = wait_index_hole(); else { #if defined(PIN_MOTORB) && defined(PIN_SELECTB) digitalWriteOC(m_currentDrive==0 ? PIN_MOTORA : PIN_MOTORB, LOW); res = wait_index_hole(); digitalWriteOC(m_currentDrive==0 ? PIN_MOTORA : PIN_MOTORB, HIGH); #else digitalWriteOC(PIN_MOTORA, LOW); res = wait_index_hole(); digitalWriteOC(PIN_MOTORA, HIGH); #endif } // de-select drive, stop timer driveSelect(HIGH); TCCRB = 0; return res; } bool ArduinoFDCClass::isWriteProtected() const { bool res = false; #if defined(PIN_WRITEPROT) driveSelect(LOW); res = is_write_protected(); driveSelect(HIGH); #endif return res; } byte ArduinoFDCClass::selectedDrive() const { return m_currentDrive; } byte ArduinoFDCClass::numTracks() const { return geometry[m_driveType[m_currentDrive]].numTracks; } byte ArduinoFDCClass::numSectors() const { return geometry[m_driveType[m_currentDrive]].numSectors; }