/* ArduinoFloppyReader (and writer) * * Copyright (C) 2017-2021 Robert Smith (@RobSmithDev) * https://amiga.robsmithdev.co.uk * * This sketch 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 library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this sketch; if not, see http://www.gnu.org/licenses */ /* Latest History: Last Modified: 22/04/2021 Firmware V1.4: Merged with Pull Request #6 (Modified the behavior of the current track location on Arduino boot - paulofduarte) which also addresses issues with some drives Firmware V1.5: Merged with Pull Request #9 (Detect and read out HD floppy disks 1.44M by kollokollo) Firmware V1.6: Added experimental unbuffered writing HD disk support Firmware V1.7: Added suggestion from GitHub user "prickle" regarding the CHECK_SERIAL function which should reoslve issues with some of the USB to SERIAL converters Firmware V1.8: Added support for read streaming with index sync support which includes timing data (for perfect copy of flux data for SCP image files) Changed read timings slightly which means more disks can now be recovered! Added support for PRECOMP disk writing (using PWM in 'one-shot' mode) to improve readability as you go past track 40 Added some new functions which allow for more direct control of the drive Remove the Erase function as discovered you just need to write a longer track to ensure you clear the gap. Workbench writes 13630 bytes per track. The first part is filler at 0xAA Added support for disk change notifications support (requires small hardware modification below) */ ///////////////////////////////////////////////////////////////////////////////////////////////////// // This sketch manages the interface between the floppy drive and the computer as well as the // // low-level disk reading and writing. For more information and how to connect your Arduino // // to a floppy drive and computer visit https://amiga.robsmithdev.co.uk // ///////////////////////////////////////////////////////////////////////////////////////////////// // This code doesnt actually do any decoding, and is mearly reading pulses, so can be used to // // read data from other disk formats too. // ////////////////////////////////////////////////////////////////////////////////////////////// // ** Hardware Modification Changes to get the best support for disk change notifications ** // Pin 34 on the floppy drive connector (Disk Ready/Change) must be connected to Pin 10 on the Arduino // Pin 12 on the floppy drive connector (Select Disk B) must be *disconnected* from pin 16 on the Arduino and connected to Pin 12. Note you *must* leave the connection between Arduino Pin 5 and Floppy Connector 16 in place // On the Arduino, connect Pin 12 to GND (0v) - this enables this advanced mode automatically. // If you can't connect pin12 to GND because you want to use the ISP headers, then see https://amiga.robsmithdev.co.uk/isp #define BAUDRATE 2000000 // The baudrate that we want to communicate over (2M) #define BAUD_PRESCALLER_NORMAL_MODE (((F_CPU / (BAUDRATE * 16UL))) - 1) #define BAUD_PRESCALLER_DOUBLESPEED_MODE (((F_CPU / (BAUDRATE * 8UL))) - 1) #define UART_USE_DOUBLESPEED_MODE // We're using double speed mode #define MOTOR_TRACK_DECREASE HIGH // Motor directions for PIN settings #define MOTOR_TRACK_INCREASE LOW // PIN 2 - INDEX PULSE PIN - used to detect a specific point on the track for sync. Not used by standard Amiga disks but some copy protection uses it. #define PIN_INDEX_DETECTED 2 // Pin used to detect the index pulse #define PIN_INDEX_PORT PIND #define PIN_INDEX_MASK B00000100 // PIN 3 - WRITE DATA #define PIN_WRITE_DATA 3 // Raw triggering of writing data to the disk #define PIN_WRITE_DATA_PORT PORTD // The actual port the above pin is on #define PIN_WRITE_DATA_MASK B00001000 // The mask used to set this pin high or low // PIN 4 - READ DATA #define PIN_READ_DATA 4 // Reads RAW floppy data on this pin #define PIN_READ_DATA_MASK B00010000 // The mask for the port #define PIN_READ_DATA_PORT PIND // The port the above pin is on // PIN 5, 6 and 7 - DRIVE, HEAD MOTOR DIRECTION and CONTROL #define PIN_DRIVE_ENABLE_MOTOR 5 // Turn on and off the motor on the drive #define PIN_MOTOR_DIR 6 // Stepper motor output to choose the direction the head should move #define PIN_MOTOR_STEP 7 // Stepper motor step line to move the head position // PIN 8 - Used to detect track 0 while moving the head #define PIN_DETECT_TRACK_0 8 // Used to see if the drive is at track 0 // PIN 9 - HEAD SELECTION #define PIN_HEAD_SELECT 9 // Choose upper and lower head on the drive // PIN A0 - WRITE GATE (Floppy Write Enable) #define PIN_WRITE_GATE A0 // This pin enables writing to the disk #define PIN_WRITE_GATE_PORT PORTC // The actual port the above pin is on #define PIN_WRITE_GATE_MASK B00000001 // The port pin mask for the gate // PIN A1 - CHECK WRITE PROTECTION #define PIN_WRITE_PROTECTED A1 // To check if the disk is write protected // PIN A2 - CTS Pin from UART #define PIN_CTS A2 // Pin linked to the CTS pin #define PIN_CTS_PORT PORTC // Port the CTS pin is on #define PIN_CTS_MASK B00000100 // Binary mask to control it with // Reserved for future use #define PIN_HD A3 #define PIN_RDY A4 // PIN 13 - Activity LED #define PIN_ACTIVITY_LED 13 // Standard LED on Arduinos. We're just using this as a read/write status flag // ** Hardware Modification Changes to get the best support for disk change notifications ** We control these regardless #define PIN_DISK_CHANGE 10 // This is actually disk exists pin. Some drives have a resistor that needs to be added/removed to get this operation. This is usually the default on PC drives. #define PIN_SELECT_DRIVE 11 #define PIN_DETECT_ADVANCED_MODE 12 // Detect advanced mode bool advancedControllerMode = false; // DO NOT CHANGE THIS, it is automatically detected. If you can't connect pin12 to GND because you want to use the ISP headers, then see https://amiga.robsmithdev.co.uk/isp // Paula on the Amiga used to find the SYNC WORDS and then read 0x1900 further WORDS. // A dos track is 11968 bytes in size, theritical revolution is 12800 bytes. /* The ATARI ST could format a track with up to 11 Sectors, so the AMIGA settings are OK. */ #define RAW_TRACKDATA_LENGTH (0x1900*2+0x440) // Paula assumed it was 12868 bytes, so we read that, plus the size of a sector, to find overlap /* For the HD (1.4 MBytes) Disks the amount of data should be about 26688: */ #define RAW_HD_TRACKDATA_LENGTH (0x1900*2*2+0x440) // The current track that the head is over. Starts with -1 to identify an unknown head position. int currentTrack = -1; // If the drive has been switched on or not bool driveEnabled = 0; /* Where there should be a HD Disk been read (1) or a DD and SD Disk (0).*/ bool disktypeHD = 0; // The timings here could be changed. These are based on F_CPU=16Mhz, which leaves the resolution at 1 tick = 0.0625usec, hence 16=1uSec // There's approx 4 clock ticks on average between noticing the flux transition and the counter value being read/reset #define TIMING_OVERHEAD -4 // Calculate the bit-timing windows. These are the ideal exact centre of the next flux transition since the previous. #define TIMING_DD_MIDDLE_2us (2 * 16) #define TIMING_DD_MIDDLE_4us (4 * 16) #define TIMING_DD_MIDDLE_6us (6 * 16) #define TIMING_DD_MIDDLE_8us (8 * 16) // Work out the upper window of the timing. Most PLL allow for about 5% drift, but we're not interested in that and just want to recover the data #define TIMING_DD_UPPER_2us (TIMING_DD_MIDDLE_2us + 16 + TIMING_OVERHEAD) #define TIMING_DD_UPPER_4us (TIMING_DD_MIDDLE_4us + 16 + TIMING_OVERHEAD) #define TIMING_DD_UPPER_6us (TIMING_DD_MIDDLE_6us + 16 + TIMING_OVERHEAD) #define TIMING_DD_UPPER_8us (TIMING_DD_MIDDLE_8us + 16 + TIMING_OVERHEAD) // HD versions #define TIMING_HD_UPPER_2us ((TIMING_DD_MIDDLE_4us/2) + 8 + TIMING_OVERHEAD) #define TIMING_HD_UPPER_4us ((TIMING_DD_MIDDLE_6us/2) + 8 + TIMING_OVERHEAD) #define TIMING_HD_UPPER_6us ((TIMING_DD_MIDDLE_8us/2) + 8 + TIMING_OVERHEAD) // 256 byte circular buffer - don't change this, we abuse the unsigned char to overflow back to zero! #define SERIAL_BUFFER_SIZE 256 #define SERIAL_BUFFER_START (SERIAL_BUFFER_SIZE-16) unsigned char SERIAL_BUFFER[SERIAL_BUFFER_SIZE]; #include // Because we turned off interrupts delay() doesnt work! This is approx ms void smalldelay(unsigned long delayTime) { delayTime*=(F_CPU/(8*1000L)); for (unsigned long loops=0; loops>8); UBRR0L = (uint8_t)(BAUD_PRESCALLER_DOUBLESPEED_MODE); UCSR0A |= 1<>8); UBRR0L = (uint8_t)(BAUD_PRESCALLER_NORMAL_MODE); UCSR0A &= ~(1<170) { stopDriveForOperation(); return false; } } stopDriveForOperation(); currentTrack = 0; // Reset the track number return true; } // Goto to a specific track. During testing it was easier for the track number to be supplied as two ASCII characters, so I left it like this bool gotoTrackX(bool reportDiskChange) { // Read the bytes byte track1 = readByteFromUART(); byte track2 = readByteFromUART(); byte flags = 1; // default to normal speed if (reportDiskChange) { flags = readByteFromUART()-'0'; } // Work so its compatiable with previous versions const unsigned char delayTime = 4 - (flags & 3); // Validate if ((track1<'0') || (track1>'9')) return false; if ((track2<'0') || (track2>'9')) return false; // Calculate target track and validate int track = ((track1-'0')*10) + (track2-'0'); if (track<0) return false; if (track>81) return false; // yes amiga could read track 81! // Exit if its already been reached if (track == currentTrack) { if (reportDiskChange) writeByteToUART('2'); return true; } // If current track is unknown go to track 0 first if (currentTrack == -1) goToTrack0(); if (reportDiskChange) writeByteToUART('1'); startDriveForOperation(); // And step the head until we reach this track number if (currentTrack < track) { digitalWrite(PIN_MOTOR_DIR,MOTOR_TRACK_INCREASE); // Move IN while (currentTrack < track) { stepDirectionHead(); if (delayTime) smalldelay(delayTime); currentTrack++; } } else { digitalWrite(PIN_MOTOR_DIR,MOTOR_TRACK_DECREASE); // Move OUT while (currentTrack > track) { stepDirectionHead(); if (delayTime) smalldelay(delayTime); currentTrack--; } } if (reportDiskChange) { // Now see if there is a disk in the drive. Returning '#' means no disk in drive if (advancedControllerMode) { if (digitalRead(PIN_DISK_CHANGE) == HIGH) writeByteToUART('1'); else writeByteToUART('#'); } else { if (flags & 4) { // We've been told to check for disk presence regardless if (nonModCheckForDisk()) writeByteToUART('1'); else writeByteToUART('#'); } else { // Don't detect disk writeByteToUART('x'); } } // The second byte is '1' for write protected and '#' for not write protected if (digitalRead(PIN_WRITE_PROTECTED) == LOW) writeByteToUART('1'); else writeByteToUART('#'); } stopDriveForOperation(); return true; } // Checks manually to see if theres a disk on un-modded hardware bool nonModCheckForDisk() { register unsigned char lastState = PIN_READ_DATA_PORT & PIN_READ_DATA_MASK; const unsigned char indexPinStatus = PIN_INDEX_PORT & PIN_INDEX_MASK; // Configure timer 2 just as a counter in NORMAL mode, we need rto measure approx 200ms (a full rotation) before giving up TCCR2A = 0 ; // No physical output port pins and normal operation TCCR2B = bit(CS20) | bit(CS21) | bit(CS22); // Precale = 1024, ie: divide the clock timer by 1024, meaning each 'count' is approx 0.064ms OCR2A = 0x00; OCR2B = 0x00; // So if the disk wasnt spinning, we allow longer const unsigned char totalLoops = 18 + (!driveEnabled ? 62 : 0); unsigned char counter = 0; // We could do this with timer 1, but hey. for (unsigned int loops=0; loops=3) { TCCR2A = 0; // disable and reset everything TCCR2B = 0; // Stop timer 2 return true; } } } } TCCR2A = 0; // disable and reset everything TCCR2B = 0; // Stop timer 2 return false; } // Test if theres a disk in the drive, a '1' if yes, a '#' if not bool testForDisk(bool sendOutput) { if (advancedControllerMode) { bool isDisk = digitalRead(PIN_DISK_CHANGE) == HIGH; if (!isDisk) { if (currentTrack < 40) { digitalWrite(PIN_MOTOR_DIR,MOTOR_TRACK_INCREASE); stepDirectionHead(); digitalWrite(PIN_MOTOR_DIR,MOTOR_TRACK_DECREASE); stepDirectionHead(); } else { digitalWrite(PIN_MOTOR_DIR,MOTOR_TRACK_DECREASE); stepDirectionHead(); digitalWrite(PIN_MOTOR_DIR,MOTOR_TRACK_INCREASE); stepDirectionHead(); } isDisk = digitalRead(PIN_DISK_CHANGE) == HIGH; } if (sendOutput) { if (isDisk) writeByteToUART('1'); else writeByteToUART('#'); if (digitalRead(PIN_WRITE_PROTECTED) == LOW) writeByteToUART('1'); else writeByteToUART('#'); } return isDisk; } else { // This is much harder startDriveForOperation(); bool diskFound = nonModCheckForDisk(); if (sendOutput) { if (diskFound) writeByteToUART('1'); else writeByteToUART('#'); if (digitalRead(PIN_WRITE_PROTECTED) == LOW) writeByteToUART('1'); else writeByteToUART('#'); } stopDriveForOperation(); return diskFound; } } // Check if the disk is write protected. Sends '#' if its write protected, or '1' if its not. If theres no disk in the drive this number is meaningless void checkWriteProtectStatus() { startDriveForOperation(); if (digitalRead(PIN_WRITE_PROTECTED) == LOW) { // Drive is write protected writeByteToUART('1'); } else { // Drive can be written to writeByteToUART('#'); } stopDriveForOperation(); } #define CHECKSERIAL_ONLY() if (UCSR0A & bit(RXC0)) { \ SERIAL_BUFFER[serialWritePos++] = UDR0; \ serialBytesInUse++; \ } // 12 is the minimum number here. Any less than this and the CHECKSERIAL_ONLY() code will impact the output. The pulse width doesn't matter as long as its at least 0.125uSec (its the falling edge that triggers a bit write) // Because only the falling edge is important we achieve precomp by shifting the pulse starting position back or forward two clock ticks // Because it may go back 2 ticks, we increase this number here by 2. 12 ticks is 750 ns, 14 ticks is 875 ns and 16 is 1000ns (1us) // By doing this, the bit cell timing remains constant, but the actual write position is shifted +/- 125ns as required #define PULSE_WIDTH 14 // This is where the above starts from the end of the timer #define PULSE_WIDTH_VALUE (0xFF - (PULSE_WIDTH-1)) // This is where to start the counter from compensating for code delay of 6 ticks (measured) #define PULSE_BREAK (58-PULSE_WIDTH) // This makes use of the PWM output to create the wayforms for us as accurate as possible. void writePrecompTrack() { // Check if its write protected. You can only do this after the write gate has been pulled low if (digitalRead(PIN_WRITE_PROTECTED) == LOW) { writeByteToUART('N'); return; } else writeByteToUART('Y'); // Find out how many bytes they want to send unsigned char highByte = readByteFromUART(); unsigned char lowByte = readByteFromUART(); unsigned char waitForIndex = readByteFromUART(); unsigned short numBytes = (((unsigned short)highByte)<<8) | lowByte; // Setup buffer parameters unsigned char serialReadPos = 0; unsigned char serialWritePos = SERIAL_BUFFER_START; unsigned char serialBytesInUse = SERIAL_BUFFER_START; digitalWrite(PIN_ACTIVITY_LED,HIGH); writeByteToUART('!'); PIN_CTS_PORT|=PIN_CTS_MASK; // stop any more data coming in! // While the INDEX pin is high wait. Might as well write from the start of the track if (waitForIndex) while (PIN_INDEX_PORT & PIN_INDEX_MASK) {}; // Signal we're ready for data PIN_CTS_PORT &= (~PIN_CTS_MASK); // Fill our buffer to give us a head start for (unsigned char a=0; a0) { if (!serialBytesInUse) break; // Read a byte from the buffer register unsigned char currentByte = SERIAL_BUFFER[serialReadPos++]; serialBytesInUse--; register unsigned char counter = PULSE_WIDTH_VALUE - (PULSE_BREAK + ( (currentByte&0x03) *32)); register unsigned char pulseStart = PULSE_WIDTH_VALUE; if (currentByte & 0x04) pulseStart=PULSE_WIDTH_VALUE-2; // Pulse should be early, so just move the pulse start back if (currentByte & 0x08) pulseStart=PULSE_WIDTH_VALUE+2; // Pulse should be late, so move the pulse start forward // Hardware error checks (frame error and overrun) if (UCSR0A & (bit(FE0)|bit(DOR0))) break; // Run until the pulse starts. The pulse start is also timed so that its width is enough to cover the time to execute CHECKSERIAL_ONLY() while (!(TIFR2 & bit(OCF2B))) { CHECKSERIAL_ONLY(); }; // Wait for overflow (ie: pulse finishes) while (!(TIFR2 & bit(TOV2))); // Set the new counter and clear all the overflows TCNT2 = counter; OCR2B = pulseStart; // Clear overflow flags TIFR2 |= bit(TOV2); TIFR2 |= bit(OCF2B); // Control I/O with the serial port if (serialBytesInUse=240) {} // Now we write the data. Hopefully by the time we get back to the top everything is ready again. WRITE_BIT(0x10,B10000000); CHECK_SERIAL(); WRITE_BIT(0x30,B01000000); CHECK_SERIAL(); WRITE_BIT(0x50,B00100000); CHECK_SERIAL(); WRITE_BIT(0x70,B00010000); // Extra check for some of the other errors that can occur if (UCSR0A & (bit(FE0)|bit(DOR0))) { // This can't happen and causes a write failure digitalWrite(PIN_ACTIVITY_LED,LOW); writeByteToUART((UCSR0A & bit(FE0)) ? 'Y' : 'Z'); // Thus means buffer underflow. PC wasn't sending us data fast enough PIN_WRITE_GATE_PORT|=PIN_WRITE_GATE_MASK; // Need to allow data to come in again PIN_CTS_PORT &= (~PIN_CTS_MASK); TCCR2B = 0; // No Clock (turn off) return; } WRITE_BIT(0x90,B00001000); CHECK_SERIAL(); WRITE_BIT(0xB0,B00000100); CHECK_SERIAL(); WRITE_BIT(0xD0,B00000010); CHECK_SERIAL(); WRITE_BIT(0xF0,B00000001); PIN_CTS_PORT|=PIN_CTS_MASK; // Stop data coming in while we're not monitoring it } // Turn off the write head PIN_WRITE_GATE_PORT|=PIN_WRITE_GATE_MASK; // Done! writeByteToUART('1'); digitalWrite(PIN_ACTIVITY_LED,LOW); // Need to allow data to come in again PIN_CTS_PORT &= (~PIN_CTS_MASK); TCCR2B = 0; // No Clock (turn off) } // Write a track to disk from the UART - the data should be pre-MFM encoded raw track data where '1's are the pulses/phase reversals to trigger // THIS CODE IS UNTESTED void writeTrackFromUART_HD() { // Configure timer 2 just as a counter in NORMAL mode TCCR2A = 0 ; // No physical output port pins and normal operation TCCR2B = bit(CS20); // Precale = 1 OCR2A = 0x00; OCR2B = 0x00; // Check if its write protected. You can only do this after the write gate has been pulled low if (digitalRead(PIN_WRITE_PROTECTED) == LOW) { writeByteToUART('N'); return; } else writeByteToUART('Y'); // Find out how many bytes they want to send unsigned char highByte = readByteFromUART(); unsigned char lowByte = readByteFromUART(); unsigned char waitForIndex = readByteFromUART(); PIN_CTS_PORT|=PIN_CTS_MASK; // stop any more data coming in! unsigned short numBytes = (((unsigned short)highByte)<<8) | lowByte; writeByteToUART('!'); register unsigned char currentByte; // Signal we're ready for another byte to come PIN_CTS_PORT &= (~PIN_CTS_MASK); // Fill our buffer to give us a head start for (int a=0; a=248) {} // Now we write the data. Hopefully by the time we get back to the top everything is ready again WRITE_BIT(0x08,B10000000); CHECK_SERIAL(); WRITE_BIT(0x18,B01000000); CHECK_SERIAL(); WRITE_BIT(0x28,B00100000); CHECK_SERIAL(); WRITE_BIT(0x38,B00010000); CHECK_SERIAL(); WRITE_BIT(0x48,B00001000); CHECK_SERIAL(); WRITE_BIT(0x58,B00000100); CHECK_SERIAL(); WRITE_BIT(0x68,B00000010); CHECK_SERIAL(); WRITE_BIT(0x78,B00000001); TCNT2=248; // a little cheating, but *should* work PIN_CTS_PORT|=PIN_CTS_MASK; // Stop data coming in while we're not monitoring it } // Turn off the write head PIN_WRITE_GATE_PORT|=PIN_WRITE_GATE_MASK; // Done! writeByteToUART('1'); digitalWrite(PIN_ACTIVITY_LED,LOW); TCCR2B = 0; // No Clock (turn off) } // Write blank data to a disk so that no MFM track could be detected - this is no longer used void eraseTrack() { // Check if its write protected. You can only do this after the write gate has been pulled low if (digitalRead(PIN_WRITE_PROTECTED) == LOW) { writeByteToUART('N'); return; } else writeByteToUART('Y'); digitalWrite(PIN_ACTIVITY_LED,HIGH); // Enable writing PIN_WRITE_GATE_PORT&=~PIN_WRITE_GATE_MASK; // To write the 01010101 sequence we're going to ask the Arduino to generate this from its PWM output. TCCR2A = bit(COM2B1) | bit(WGM20) | bit(WGM21)| bit(WGM22); // (COM2B0|COM2B1) Clear OC2B. on compare match, set OC2B at BOTTOM. WGM20|WGM21|WGM22 is Fast PWM. TCCR2B = bit(WGM22)| bit(CS20); // WGM22 enables waveform generation. CS20 starts the counter runing at maximum speed // This generates a square wave, 3usec high, and 1usec low, 4uSec in total OCR2A = 63; OCR2B = 47; TCNT2=0; // Now just count how many times this happens. Approx 200ms is a revolution, so we'll go 200ms + 5% to be on the safe side (210ms) TIFR2 |= bit(TOV2); // 52500 is 210 / 0.004 for (unsigned int counter=0; counter<52500; counter++) { // Every time this loop completes, 4us have passed. while (!(TIFR2 & bit(TOV2))) {}; TIFR2 |= bit(TOV2); }; // Turn off the write head to stop writing instantly PIN_WRITE_GATE_PORT|=PIN_WRITE_GATE_MASK; TCCR2A = 0; TCCR2B = 0; // No Clock (turn off) // Done! writeByteToUART('1'); digitalWrite(PIN_ACTIVITY_LED,LOW); } // Write blank data to a disk so that no MFM track could be detected - this is no longer used void eraseTrack_HD() { // Check if its write protected. You can only do this after the write gate has been pulled low if (digitalRead(PIN_WRITE_PROTECTED) == LOW) { writeByteToUART('N'); return; } else writeByteToUART('Y'); digitalWrite(PIN_ACTIVITY_LED,HIGH); // Enable writing PIN_WRITE_GATE_PORT&=~PIN_WRITE_GATE_MASK; // To write the 01010101 sequence we're going to ask the Arduino to generate this from its PWM output. TCCR2A = bit(COM2B1) | bit(WGM20) | bit(WGM21)| bit(WGM22); // (COM2B0|COM2B1) Clear OC2B. on compare match, set OC2B at BOTTOM. WGM20|WGM21|WGM22 is Fast PWM. TCCR2B = bit(WGM22)| bit(CS20); // WGM22 enables waveform generation. CS20 starts the counter runing at maximum speed // This generates a square wave, 1.5usec high, and 0.1usec low, 4uSec in total OCR2A = 31; OCR2B = 15; TCNT2=0; // Now just count how many times this happens. Approx 200ms is a revolution, so we'll go 200ms + 5% to be on the safe side (210ms) TIFR2 |= bit(TOV2); // 52500 is 210 / 0.004, but we're tqice as quick, so do the loop twice for (unsigned char loops=0; loops<2; loops++) for (unsigned int counter=0; counter<52500; counter++) { // Every time this loop completes, 4us have passed. while (!(TIFR2 & bit(TOV2))) {}; TIFR2 |= bit(TOV2); }; // Turn off the write head to stop writing instantly PIN_WRITE_GATE_PORT|=PIN_WRITE_GATE_MASK; TCCR2A = 0; TCCR2B = 0; // No Clock (turn off) // Done! writeByteToUART('1'); digitalWrite(PIN_ACTIVITY_LED,LOW); } // Read the track using a timings to calculate which MFM sequence has been triggered void readTrackDataFast() { // Configure timer 2 just as a counter in NORMAL mode TCCR2A = 0 ; // No physical output port pins and normal operation TCCR2B = bit(CS20); // Precale = 1 OCR2A = 0x00; OCR2B = 0x00; // First wait for the serial port to be available while(!(UCSR0A & (1<=TIMING_DD_UPPER_4us/2) && (lastWindow==0)) { lastWindow = 1; sendString("\n"); } if ((a>=TIMING_DD_UPPER_4us) && (lastWindow==1)) { lastWindow = 2; sendString("\n"); } if ((a>=TIMING_DD_UPPER_6us) && (lastWindow==2)) { lastWindow = 3; sendString("\n"); } if ((a>=TIMING_DD_UPPER_8us) && (lastWindow==3)) { lastWindow = 4; sendString("\n"); } if (cc[a]>1) { sendInt(a); sendString(", "); sendTickAsuSec(a); sendString(", "); sendInt(cc[a]); sendString(", "); if (a <= TIMING_DD_UPPER_2us) { sendString("4us, (assumed) but bad"); } else if (a= TIMING_DD_UPPER_2us) counter -= TIMING_DD_UPPER_2us; else counter=0; \ } else \ if (counter=TIMING_DD_UPPER_8us is an error but some disks do this */ \ counter=16; \ } \ // Read the track using a timings to calculate which MFM sequence has been triggered, hwoever, this keeps running until a byte is received from the serial port telling it to stop void readContinuousStream() { // Configure timer 2 just as a counter in NORMAL mode TCCR2A = 0 ; // No physical output port pins and normal operation TCCR2B = bit(CS20); // Precale = 1 (ie: no prescale) EICRA = bit(ISC01); // falling edge of INT0 generates an interrupt, they are turned off, but its an easy way for us to detect a falling edge rather than monitoring a pin OCR2A = 0x00; OCR2B = 0x00; // First wait for the serial port to be available to receive while(!(UCSR0A & (1<250) break; // this is to stop the inteface freezing if theres no disk in the drive }; TCNT2 = 0; // reset while (!(PIN_READ_DATA_PORT & PIN_READ_DATA_MASK)) {}; EIFR |=bit(INTF0); // clear the register saying it detected an index pulse TIFR2 |= bit(OCF2B); // clear the overflow register OCR2B = TIMING_DD_UPPER_8us; // This is set to the upper bound. If we exceed this we must have received a load of non-flux data, this allows us to write '0000' on the PC and loop back round // This sets up what would be an interrupt for when the READ PIN is signalled (unfortunatly we can't choose the direction. Its just set when it changes) // But this allows us to make sure we dont miss a bit, although the timing might be off slightly. This is mainly used when disks have very long areas of // no flux transitions, typilcally used for copy protection PCMSK0 = 0; PCMSK1 = 0; PCMSK2 = bit(PCINT20); PCICR = bit(PCIE2); // Enable the interrupt for this pin // First one will just be 01010101 and is ignored by the reader anyway register unsigned char lastDataOutput = B01010101; do { // A variable to store the data we collect register unsigned char DataOutputByte = 0; register unsigned char counter, average; // format is INDEX B1 B2 Spd READ_UNROLLED_LOOP(B00100000, B01000000, B01100000); average = counter; if ((EIFR&bit(INTF0))) { EIFR|=bit(INTF0); DataOutputByte|= 0x80; }; READ_UNROLLED_LOOP(B00001000, B00010000, B00011000); average += counter; average >>= 3; UDR0 = average | DataOutputByte; } while (!(UCSR0A & ( 1 << RXC0 ))); // Read the byte that was sent to stop us, should be a 0, although we don't care unsigned char response = UDR0; // We want to make sure the PC knows we've quit, and whilst this isnt fool proof its a start. // The chance of this exact sequence coming from MFM data from the drive is slim I guess // A little hacky, bit without woriding another pin to something we dont have any other options writeByteToUART('X'); writeByteToUART('Y'); writeByteToUART('Z'); writeByteToUART(response); writeByteToUART('1'); // turn off the status LED digitalWrite(PIN_ACTIVITY_LED,LOW); // Disable the counter TCCR2B = 0; // No Clock (turn off) EICRA = 0; // disable monitoring PCMSK2 = 0; PCICR = 0; OCR2A = 0; OCR2B = 0; } // The main command loop void loop() { PIN_CTS_PORT &= (~PIN_CTS_MASK); // Allow data incoming PIN_WRITE_GATE_PORT|=PIN_WRITE_GATE_MASK; // always turn writing off // Read the command from the PC byte command = readByteFromUART(); digitalWrite(PIN_SELECT_DRIVE, LOW); smalldelay(1); switch (command) { case 'x': break; // this is ignored. It's to help 'stop streaming' mode if it gets stuck on startup case 'M': if (!driveEnabled) sendString("Drive motor not switched on.\n"); else measureCurrentDisk(); break; // Command: "?" Means information about the firmware case '?':writeByteToUART('1'); // Success writeByteToUART('V'); // Followed writeByteToUART('1'); // By writeByteToUART(advancedControllerMode ? ',' : '.'); // Advanced controller version writeByteToUART('8'); // Number break; // Command "." means go back to track 0 case '.':if (goToTrack0()) // reset writeByteToUART('1'); else writeByteToUART('0'); break; // Command "#" means goto track. Should be formatted as #00 or #32 etc case '#': if (gotoTrackX(false)) { smalldelay(1); // wait for drive writeByteToUART('1'); } else writeByteToUART('0'); break; // Command "=" means goto track. Should be formatted as =00 or =32 etc. This also reports disk change and write protect status case '=': if (gotoTrackX(true)) { } else writeByteToUART('0'); break; // Command "[" select LOWER disk side case '[': digitalWrite(PIN_HEAD_SELECT,LOW); writeByteToUART('1'); break; // Command "]" select UPPER disk side case ']': digitalWrite(PIN_HEAD_SELECT,HIGH); writeByteToUART('1'); break; // Command "{" Read data continuously from the drive until a byte is sent from the PC case '{': if (driveEnabled) { if(disktypeHD) writeByteToUART('0'); // not supported else { writeByteToUART('1'); readContinuousStream(); } } else writeByteToUART('0'); break; // Command "}" Write track to the drive with precomp case '}': if (!driveEnabled) writeByteToUART('0'); else { if(disktypeHD) writeByteToUART('0'); else { writeByteToUART('1'); writePrecompTrack(); } } break; // Command "<" Read track from the drive case '<': if(!driveEnabled) writeByteToUART('0'); else { writeByteToUART('1'); if(disktypeHD) readTrackDataFast_HD(); else readTrackDataFast(); } break; // Command ">" Write track to the drive case '>': if (!driveEnabled) writeByteToUART('0'); else { if(disktypeHD) { writeByteToUART('0'); } else { writeByteToUART('1'); writeTrackFromUART(); } } break; // Command "X" Erase current track (writes 0xAA to it) case 'X': if (!driveEnabled) writeByteToUART('0'); else { writeByteToUART('1'); if (disktypeHD) eraseTrack_HD(); else eraseTrack(); } break; // Command "H" Set HD disk type case 'H': disktypeHD = 1; writeByteToUART('1'); break; // Command "D" Set DD or SD disk type case 'D': disktypeHD = 0; writeByteToUART('1'); break; // Turn off the drive motor case '-': digitalWrite(PIN_WRITE_GATE,HIGH); digitalWrite(PIN_DRIVE_ENABLE_MOTOR,HIGH); writeByteToUART('1'); driveEnabled = 0; break; // Turn on the drive motor and setup in READ MODE, this has no delay, the computer must handle this case '*': if (!driveEnabled) { digitalWrite(PIN_DRIVE_ENABLE_MOTOR,LOW); driveEnabled = 1; } writeByteToUART('1'); break; // Turn on the drive motor and setup in READ and WRITE MODE. They both work the same now case '+': case '~': if (!driveEnabled) { digitalWrite(PIN_DRIVE_ENABLE_MOTOR,LOW); driveEnabled = 1; smalldelay(750); // wait for drive } writeByteToUART('1'); break; // Check write protect flag case '$': checkWriteProtectStatus(); break; // Ask if the drive is ready (has a disk in it) and if its write protected or not case '^': testForDisk(true); break; case '&': runDiagnostic(); break; // We don't recognise the command! default: writeByteToUART('!'); // error break; } if (!driveEnabled) { digitalWrite(PIN_SELECT_DRIVE, HIGH); smalldelay(1); } }