1794 lines
70 KiB
C++
1794 lines
70 KiB
C++
// -----------------------------------------------------------------------------
|
|
// 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<numsec; i++)
|
|
{
|
|
*ptr++ = 0xFE; // ID mark
|
|
*ptr++ = track; // cylinder number
|
|
*ptr++ = side; // side number
|
|
*ptr++ = i+1; // sector number
|
|
*ptr++ = 2; // sector length
|
|
uint16_t crc = calc_crc(ptr-5, 5);
|
|
*ptr++ = crc / 256; // CRC
|
|
*ptr++ = crc & 255; // CRC
|
|
*ptr++ = 0x4E; // first byte of post-data gap
|
|
}
|
|
|
|
noInterrupts();
|
|
|
|
// 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
|
|
|
|
// reset timer and overrun flags
|
|
TCNT = 0;
|
|
TIFR = bit(TOV);
|
|
|
|
// wait for start of index hole
|
|
if( !wait_index_hole() ) { interrupts(); return S_NOTREADY; }
|
|
|
|
TCCRB |= bit(WGM2); // WGMx2:10 = 010 => 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]<geometry[driveType].numTracks )
|
|
{
|
|
// need interrupts for delay() when stepping
|
|
interrupts();
|
|
step_tracks(driveType, track-header[1]);
|
|
noInterrupts();
|
|
|
|
res = wait_header(bitLength, track, side, sector);
|
|
}
|
|
else
|
|
res = S_NOHEADER;
|
|
}
|
|
|
|
// if we couldn't find the header then step to correct track by going to track 0 and then stepping out and check again
|
|
if( res!=S_OK )
|
|
{
|
|
// need interrupts for delay() when stepping
|
|
interrupts();
|
|
if( step_to_track0() )
|
|
{
|
|
step_tracks(driveType, track);
|
|
noInterrupts();
|
|
res = wait_header(bitLength, track, side, sector);
|
|
}
|
|
else
|
|
{
|
|
noInterrupts();
|
|
res = S_NOTRACK0;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
|
|
ArduinoFDCClass::ArduinoFDCClass()
|
|
{
|
|
m_initialized = false;
|
|
m_currentDrive = 0;
|
|
m_motorState[0] = false;
|
|
m_motorState[1] = false;
|
|
m_driveType[0] = DT_3_HD;
|
|
m_driveType[1] = DT_3_HD;
|
|
m_bitLength[0] = 0;
|
|
m_bitLength[1] = 0;
|
|
|
|
#if defined(PIN_DENSITY)
|
|
m_densityPinMode[0] = DP_DISCONNECT;
|
|
m_densityPinMode[1] = DP_DISCONNECT;
|
|
#endif
|
|
}
|
|
|
|
|
|
void ArduinoFDCClass::begin(enum DriveType driveAType, enum DriveType driveBType)
|
|
{
|
|
// make sure all outputs pins are HIGH when we switch them to output mode
|
|
digitalWrite(PIN_STEP, LOW);
|
|
digitalWrite(PIN_STEPDIR, LOW);
|
|
digitalWrite(PIN_MOTORA, LOW);
|
|
digitalWrite(PIN_SELECTA, LOW);
|
|
#if defined(PIN_MOTORB) && defined(PIN_SELECTB)
|
|
digitalWrite(PIN_MOTORB, LOW);
|
|
digitalWrite(PIN_SELECTB, LOW);
|
|
#endif
|
|
digitalWrite(PIN_SIDE, LOW);
|
|
digitalWrite(PIN_WRITEGATE, LOW);
|
|
digitalWrite(PIN_WRITEDATA, HIGH);
|
|
|
|
// set pins to input/output mode
|
|
pinMode(PIN_STEP, INPUT);
|
|
pinMode(PIN_STEPDIR, INPUT);
|
|
pinMode(PIN_SELECTA, INPUT);
|
|
pinMode(PIN_MOTORA, INPUT);
|
|
#if defined(PIN_MOTORB) && defined(PIN_SELECTB)
|
|
pinMode(PIN_SELECTB, INPUT);
|
|
pinMode(PIN_MOTORB, INPUT);
|
|
#endif
|
|
pinMode(PIN_SIDE, INPUT);
|
|
pinMode(PIN_WRITEGATE, INPUT);
|
|
pinMode(PIN_WRITEDATA, OUTPUT);
|
|
pinMode(PIN_READDATA, INPUT_PULLUP);
|
|
pinMode(PIN_INDEX, INPUT_PULLUP);
|
|
pinMode(PIN_TRACK0, INPUT_PULLUP);
|
|
#if defined(PIN_WRITEPROT)
|
|
pinMode(PIN_WRITEPROT, INPUT_PULLUP);
|
|
#endif
|
|
|
|
#if defined(PIN_DENSITY)
|
|
digitalWrite(PIN_DENSITY, LOW);
|
|
pinMode(PIN_DENSITY, INPUT);
|
|
#endif
|
|
|
|
m_bitLength[0] = 0;
|
|
m_bitLength[1] = 0;
|
|
m_motorState[0] = false;
|
|
m_motorState[1] = false;
|
|
|
|
m_currentDrive = 1;
|
|
setDriveType(driveBType);
|
|
m_currentDrive = 0;
|
|
setDriveType(driveAType);
|
|
m_initialized = true;
|
|
}
|
|
|
|
|
|
void ArduinoFDCClass::end()
|
|
{
|
|
m_initialized = false;
|
|
m_motorState[0] = false;
|
|
m_motorState[1] = false;
|
|
digitalWriteOC(PIN_MOTORA, LOW);
|
|
#if defined(PIN_MOTORB) && defined(PIN_SELECTB)
|
|
digitalWriteOC(PIN_MOTORB, LOW);
|
|
#endif
|
|
}
|
|
|
|
|
|
void ArduinoFDCClass::setDriveType(enum DriveType type)
|
|
{
|
|
if( type != m_driveType[m_currentDrive] )
|
|
{
|
|
m_driveType[m_currentDrive] = 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 )
|
|
setDensityPinMode(DP_OUTPUT_LOW_FOR_DD);
|
|
else
|
|
setDensityPinMode(DP_DISCONNECT);
|
|
|
|
// bit length will be determined at first read/write operation
|
|
m_bitLength[m_currentDrive] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
enum ArduinoFDCClass::DriveType ArduinoFDCClass::getDriveType() const
|
|
{
|
|
return m_driveType[m_currentDrive];
|
|
}
|
|
|
|
|
|
void ArduinoFDCClass::setDensityPinMode(enum DensityPinMode mode)
|
|
{
|
|
#if defined(PIN_DENSITY)
|
|
m_densityPinMode[m_currentDrive] = mode;
|
|
setDensityPin();
|
|
#endif
|
|
}
|
|
|
|
|
|
void ArduinoFDCClass::setDensityPin()
|
|
{
|
|
#if defined(PIN_DENSITY)
|
|
byte isHD = m_driveType[m_currentDrive]==DT_3_HD || m_driveType[m_currentDrive]==DT_5_HD;
|
|
switch( m_densityPinMode[m_currentDrive] )
|
|
{
|
|
case DP_OUTPUT_LOW_FOR_DD:
|
|
digitalWriteOC(PIN_DENSITY, isHD ? HIGH : LOW);
|
|
break;
|
|
|
|
case DP_OUTPUT_LOW_FOR_HD:
|
|
digitalWriteOC(PIN_DENSITY, isHD ? LOW : HIGH);
|
|
break;
|
|
|
|
default:
|
|
digitalWriteOC(PIN_DENSITY, HIGH);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void ArduinoFDCClass::driveSelect(bool state) const
|
|
{
|
|
#if defined(PIN_MOTORB) && defined(PIN_SELECTB)
|
|
digitalWriteOC(m_currentDrive==0 ? PIN_SELECTA : PIN_SELECTB, state);
|
|
#else
|
|
digitalWriteOC(PIN_SELECTA, state);
|
|
#endif
|
|
}
|
|
|
|
|
|
byte ArduinoFDCClass::getBitLength()
|
|
{
|
|
if( m_bitLength[m_currentDrive] == 0 )
|
|
{
|
|
byte bitLength;
|
|
|
|
switch( m_driveType[m_currentDrive] )
|
|
{
|
|
case DT_3_HD: bitLength = 16; break;
|
|
case DT_3_DD: bitLength = 32; break;
|
|
case DT_5_HD: bitLength = 16; break;
|
|
case DT_5_DD: bitLength = 32; break;
|
|
|
|
case DT_5_DDonHD:
|
|
{
|
|
TCCRA = 0;
|
|
TCCRB = bit(CS0); // start timer with /1 prescaler
|
|
TCCRC = 0;
|
|
|
|
// return with error if index hole can't be found
|
|
if( !wait_index_hole() ) return 0;
|
|
|
|
// switch timer to /64 prescaler
|
|
TCCRB = bit(CS0) | bit(CS1);
|
|
|
|
// build average tick count (4us/tick) over 4 revolutions
|
|
unsigned long l = 0;
|
|
for(byte i=0; i<4; i++)
|
|
{
|
|
if( !wait_index_hole() ) return 0;
|
|
l += TCNT;
|
|
}
|
|
|
|
TCCRB = 0; // turn off timer
|
|
|
|
// for 300 RPM (200 ms/rotation) data rate is 250 mbps => 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;
|
|
}
|