/ Adafruit_Floppy.cpp
Adafruit_Floppy.cpp
  1  #include "Adafruit_Floppy.h"
  2  
  3  #define DEBUG_FLOPPY (0)
  4  
  5  // We need to read and write some pins at optimized speeds - use raw registers
  6  // or native SDK API!
  7  #ifdef BUSIO_USE_FAST_PINIO
  8  #define read_index() (*indexPort & indexMask)
  9  #define read_data() (*dataPort & dataMask)
 10  #define set_debug_led() (*ledPort |= ledMask)
 11  #define clr_debug_led() (*ledPort &= ~ledMask)
 12  #elif defined(ARDUINO_ARCH_RP2040)
 13  #define read_index() gpio_get(_indexpin)
 14  #define read_data() gpio_get(_rddatapin)
 15  #define set_debug_led() gpio_put(led_pin, 1)
 16  #define clr_debug_led() gpio_put(led_pin, 0)
 17  #endif
 18  
 19  #if !DEBUG_FLOPPY
 20  #undef set_debug_led
 21  #undef clr_debug_led
 22  #define set_debug_led() ((void)0)
 23  #define clr_debug_led() ((void)0)
 24  #endif
 25  
 26  /**************************************************************************/
 27  /*!
 28      @brief  Create a hardware interface to a floppy drive
 29      @param  densitypin A pin connected to the floppy Density Select input
 30      @param  indexpin A pin connected to the floppy Index Sensor output
 31      @param  selectpin A pin connected to the floppy Drive Select input
 32      @param  motorpin A pin connected to the floppy Motor Enable input
 33      @param  directionpin A pin connected to the floppy Stepper Direction input
 34      @param  steppin A pin connected to the floppy Stepper input
 35      @param  wrdatapin A pin connected to the floppy Write Data input
 36      @param  wrgatepin A pin connected to the floppy Write Gate input
 37      @param  track0pin A pin connected to the floppy Track 00 Sensor output
 38      @param  protectpin A pin connected to the floppy Write Protect Sensor output
 39      @param  rddatapin A pin connected to the floppy Read Data output
 40      @param  sidepin A pin connected to the floppy Side Select input
 41      @param  readypin A pin connected to the floppy Ready/Disk Change output
 42  
 43  */
 44  /**************************************************************************/
 45  
 46  Adafruit_Floppy::Adafruit_Floppy(int8_t densitypin, int8_t indexpin,
 47                                   int8_t selectpin, int8_t motorpin,
 48                                   int8_t directionpin, int8_t steppin,
 49                                   int8_t wrdatapin, int8_t wrgatepin,
 50                                   int8_t track0pin, int8_t protectpin,
 51                                   int8_t rddatapin, int8_t sidepin,
 52                                   int8_t readypin) {
 53    _densitypin = densitypin;
 54    _indexpin = indexpin;
 55    _selectpin = selectpin;
 56    _motorpin = motorpin;
 57    _directionpin = directionpin;
 58    _steppin = steppin;
 59    _wrdatapin = wrdatapin;
 60    _wrgatepin = wrgatepin;
 61    _track0pin = track0pin;
 62    _protectpin = protectpin;
 63    _rddatapin = rddatapin;
 64    _sidepin = sidepin;
 65    _readypin = readypin;
 66  }
 67  
 68  /**************************************************************************/
 69  /*!
 70      @brief  Initializes the GPIO pins but do not start the motor or anything
 71  */
 72  /**************************************************************************/
 73  void Adafruit_Floppy::begin(void) { soft_reset(); }
 74  
 75  /**************************************************************************/
 76  /*!
 77      @brief  Set back the object and pins to initial state
 78  */
 79  /**************************************************************************/
 80  void Adafruit_Floppy::soft_reset(void) {
 81    // deselect drive
 82    pinMode(_selectpin, OUTPUT);
 83    digitalWrite(_selectpin, HIGH);
 84  
 85    // motor enable pin, drive low to turn on motor
 86    pinMode(_motorpin, OUTPUT);
 87    digitalWrite(_motorpin, HIGH);
 88  
 89    // set motor direction (low is in, high is out)
 90    pinMode(_directionpin, OUTPUT);
 91    digitalWrite(_directionpin, LOW); // move inwards to start
 92  
 93    // step track pin, pulse low for 3us min, 3ms max per pulse
 94    pinMode(_steppin, OUTPUT);
 95    digitalWrite(_steppin, HIGH);
 96  
 97    // side selector
 98    pinMode(_sidepin, OUTPUT);
 99    digitalWrite(_sidepin, HIGH); // side 0 to start
100  
101    pinMode(_indexpin, INPUT_PULLUP);
102    pinMode(_track0pin, INPUT_PULLUP);
103    pinMode(_protectpin, INPUT_PULLUP);
104    pinMode(_readypin, INPUT_PULLUP);
105    pinMode(_rddatapin, INPUT_PULLUP);
106  
107  #ifdef BUSIO_USE_FAST_PINIO
108    indexPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(_indexpin));
109    indexMask = digitalPinToBitMask(_indexpin);
110  #endif
111  
112    select_delay_us = 10;
113    step_delay_us = 10000;
114    settle_delay_ms = 15;
115    motor_delay_ms = 1000;
116    watchdog_delay_ms = 1000;
117    bus_type = BUSTYPE_IBMPC;
118  
119    if (led_pin >= 0) {
120      pinMode(led_pin, OUTPUT);
121      digitalWrite(led_pin, LOW);
122    }
123  }
124  
125  /**************************************************************************/
126  /*!
127      @brief Whether to select this drive
128      @param selected True to select/enable
129  */
130  /**************************************************************************/
131  void Adafruit_Floppy::select(bool selected) {
132    digitalWrite(_selectpin, !selected); // Selected logic level 0!
133    // Select drive
134    delayMicroseconds(select_delay_us);
135  }
136  
137  /**************************************************************************/
138  /*!
139      @brief Which head/side to read from
140      @param head Head 0 or 1
141  */
142  /**************************************************************************/
143  void Adafruit_Floppy::side(uint8_t head) {
144    digitalWrite(_sidepin, !head); // Head 0 is logic level 1, head 1 is logic 0!
145  }
146  
147  /**************************************************************************/
148  /*!
149      @brief  Turn on or off the floppy motor, if on we wait till we get an index
150     pulse!
151      @param motor_on True to turn on motor, False to turn it off
152      @returns False if turning motor on and no index pulse found, true otherwise
153  */
154  /**************************************************************************/
155  bool Adafruit_Floppy::spin_motor(bool motor_on) {
156    digitalWrite(_motorpin, !motor_on); // Motor on is logic level 0!
157    if (!motor_on)
158      return true; // we're done, easy!
159  
160    delay(motor_delay_ms); // Main motor turn on
161  
162    uint32_t index_stamp = millis();
163    bool timedout = false;
164  
165    if (debug_serial)
166      debug_serial->print("Waiting for index pulse...");
167  
168    while (digitalRead(_indexpin)) {
169      if ((millis() - index_stamp) > 10000) {
170        timedout = true; // its been 10 seconds?
171        break;
172      }
173    }
174  
175    if (timedout) {
176      if (debug_serial)
177        debug_serial->println("Didn't find an index pulse!");
178      return false;
179    }
180    if (debug_serial)
181      debug_serial->println("Found!");
182    return true;
183  }
184  
185  /**************************************************************************/
186  /*!
187      @brief  Seek to the desired track, requires the motor to be spun up!
188      @param  track_num The track to step to
189      @return True If we were able to get to the track location
190  */
191  /**************************************************************************/
192  bool Adafruit_Floppy::goto_track(uint8_t track_num) {
193    // track 0 is a very special case because its the only one we actually know we
194    // got to. if we dont know where we are, or we're going to track zero, step
195    // back till we get there.
196    if ((_track < 0) || track_num == 0) {
197      if (debug_serial)
198        debug_serial->println("Going to track 0");
199  
200      // step back a lil more than expected just in case we really seeked out
201      uint8_t max_steps = 250;
202      while (max_steps--) {
203        if (!digitalRead(_track0pin)) {
204          _track = 0;
205          break;
206        }
207        step(STEP_OUT, 1);
208      }
209  
210      if (digitalRead(_track0pin)) {
211        // we never got a track 0 indicator :(
212        if (debug_serial)
213          debug_serial->println("Could not find track 0");
214        return false; // we 'timed' out, were not able to locate track 0
215      }
216    }
217    delay(settle_delay_ms);
218  
219    // ok its a non-track 0 step, first, we cant go past 79 ok?
220    track_num = min(track_num, MAX_TRACKS - 1);
221    if (debug_serial)
222      debug_serial->printf("Going to track %d\n\r", track_num);
223  
224    if (_track == track_num) { // we are there already
225      return true;
226    }
227  
228    int8_t steps = (int8_t)track_num - (int8_t)_track;
229    if (steps > 0) {
230      if (debug_serial)
231        debug_serial->printf("Step in %d times\n\r", steps);
232      step(STEP_IN, steps);
233    } else {
234      steps = abs(steps);
235      if (debug_serial)
236        debug_serial->printf("Step out %d times\n\r", steps);
237      step(STEP_OUT, steps);
238    }
239    delay(settle_delay_ms);
240    _track = track_num;
241  
242    return true;
243  }
244  
245  /**************************************************************************/
246  /*!
247      @brief  Step the track motor
248      @param  dir STEP_OUT or STEP_IN depending on desired direction
249      @param  times How many steps to take
250  */
251  /**************************************************************************/
252  void Adafruit_Floppy::step(bool dir, uint8_t times) {
253    digitalWrite(_directionpin, dir);
254    delayMicroseconds(10); // 1 microsecond, but we're generous
255  
256    while (times--) {
257      digitalWrite(_steppin, HIGH);
258      delayMicroseconds(step_delay_us);
259      digitalWrite(_steppin, LOW);
260      delayMicroseconds(step_delay_us);
261      digitalWrite(_steppin, HIGH); // end high
262      yield();
263    }
264  }
265  
266  /**************************************************************************/
267  /*!
268      @brief  The current track location, based on internal caching
269      @return The cached track location
270  */
271  /**************************************************************************/
272  int8_t Adafruit_Floppy::track(void) { return _track; }
273  
274  /**************************************************************************/
275  /*!
276      @brief  Capture one track's worth of flux transitions, between two falling
277     index pulses
278      @param  pulses A pointer to an array of memory we can use to store into
279      @param  max_pulses The size of the allocated pulses array
280      @return Number of pulses we actually captured
281  */
282  /**************************************************************************/
283  uint32_t Adafruit_Floppy::capture_track(uint8_t *pulses, uint32_t max_pulses) {
284    unsigned pulse_count;
285    uint8_t *pulses_ptr = pulses;
286    uint8_t *pulses_end = pulses + max_pulses;
287  
288  #ifdef BUSIO_USE_FAST_PINIO
289    BusIO_PortReg *dataPort, *ledPort;
290    BusIO_PortMask dataMask, ledMask;
291    dataPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(_rddatapin));
292    dataMask = digitalPinToBitMask(_rddatapin);
293    ledPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(led_pin));
294    ledMask = digitalPinToBitMask(led_pin);
295  #endif
296  
297    memset(pulses, 0, max_pulses); // zero zem out
298  
299    noInterrupts();
300    wait_for_index_pulse_low();
301  
302    // wait for one clean flux pulse so we dont get cut off.
303    // don't worry about losing this pulse, we'll get it on our
304    // overlap run!
305  
306    // ok we have a h-to-l transition so...
307    bool last_index_state = read_index();
308    uint8_t index_transitions = 0;
309  
310    // if data line is low, wait till it rises
311    if (!read_data()) {
312      while (!read_data())
313        ;
314    }
315    // if data line is high, wait till it drops down
316    if (read_data()) {
317      while (read_data())
318        ;
319    }
320  
321    while (true) {
322      bool index_state = read_index();
323      // ahh a L to H transition
324      if (!last_index_state && index_state) {
325        index_transitions++;
326        if (index_transitions ==
327            2) // and its the second one, so we're done with this track!
328          break;
329      }
330      last_index_state = index_state;
331  
332      // muahaha, now we can read track data!
333      // Don't start counting at zero because we lost some time checking for
334      // index. Empirically, at 180MHz and -O3 on M4, this gives the most 'even'
335      // timings, moving the bins from 41/63/83 to 44/66/89
336      pulse_count = 3;
337  
338      // while pulse is in the low pulse, count up
339      while (!read_data()) {
340        pulse_count++;
341      }
342      set_debug_led();
343  
344      // while pulse is high, keep counting up
345      while (read_data())
346        pulse_count++;
347      clr_debug_led();
348  
349      pulses_ptr[0] = min(255, pulse_count);
350      pulses_ptr++;
351      if (pulses_ptr == pulses_end) {
352        break;
353      }
354    }
355    // whew done
356    interrupts();
357    return pulses_ptr - pulses;
358  }
359  
360  /**************************************************************************/
361  /*!
362      @brief  Busy wait until the index line goes from high to low
363  */
364  /**************************************************************************/
365  void Adafruit_Floppy::wait_for_index_pulse_low(void) {
366    // initial state
367    bool index_state = read_index();
368    bool last_index_state = index_state;
369  
370    // wait until last index state is H and current state is L
371    while (true) {
372      index_state = read_index();
373      if (last_index_state && !index_state) {
374        return;
375      }
376      last_index_state = index_state;
377    }
378  }
379  
380  /**************************************************************************/
381  /*!
382      @brief  Pretty print the counts in a list of flux transitions
383      @param  pulses A pointer to an array of memory containing pulse counts
384      @param  num_pulses The size of the pulses in the array
385  */
386  /**************************************************************************/
387  void Adafruit_Floppy::print_pulses(uint8_t *pulses, uint32_t num_pulses) {
388    if (!debug_serial)
389      return;
390  
391    for (uint32_t i = 0; i < num_pulses; i++) {
392      debug_serial->print(pulses[i]);
393      debug_serial->print(", ");
394    }
395    debug_serial->println();
396  }
397  /**************************************************************************/
398  /*!
399      @brief  Pretty print a simple histogram of flux transitions
400      @param  pulses A pointer to an array of memory containing pulse counts
401      @param  num_pulses The size of the pulses in the array
402      @param  max_bins The maximum number of histogram bins to use (default 64)
403  */
404  /**************************************************************************/
405  void Adafruit_Floppy::print_pulse_bins(uint8_t *pulses, uint32_t num_pulses,
406                                         uint8_t max_bins) {
407    if (!debug_serial)
408      return;
409  
410    // lets bin em!
411    uint32_t bins[max_bins][2];
412    memset(bins, 0, max_bins * 2 * sizeof(uint32_t));
413    // we'll add each pulse to a bin so we can figure out the 3 buckets
414    for (uint32_t i = 0; i < num_pulses; i++) {
415      uint8_t p = pulses[i];
416      // find a bin for this pulse
417      uint8_t bin = 0;
418      for (bin = 0; bin < max_bins; bin++) {
419        // bin already exists? increment the count!
420        if (bins[bin][0] == p) {
421          bins[bin][1]++;
422          break;
423        }
424        if (bins[bin][0] == 0) {
425          // ok we never found the bin, so lets make it this one!
426          bins[bin][0] = p;
427          bins[bin][1] = 1;
428          break;
429        }
430      }
431      if (bin == max_bins)
432        debug_serial->println("oof we ran out of bins but we'll keep going");
433    }
434    // this is a very lazy way to print the bins sorted
435    for (uint8_t pulse_w = 1; pulse_w < 255; pulse_w++) {
436      for (uint8_t b = 0; b < max_bins; b++) {
437        if (bins[b][0] == pulse_w) {
438          debug_serial->print(bins[b][0]);
439          debug_serial->print(": ");
440          debug_serial->println(bins[b][1]);
441        }
442      }
443    }
444  }