Compare commits
No commits in common. "update-doxygen" and "main" have entirely different histories.
update-dox
...
main
30 changed files with 797 additions and 6941 deletions
61
.github/workflows/githubci.yml
vendored
61
.github/workflows/githubci.yml
vendored
|
|
@ -4,10 +4,6 @@ on: [pull_request, push, repository_dispatch]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arduino-platform: ["feather_m4_express_tinyusb", "feather_rp2040_tinyusb"]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
@ -23,63 +19,8 @@ jobs:
|
|||
- name: pre-install
|
||||
run: bash ci/actions_install.sh
|
||||
|
||||
- name: fix SDFat
|
||||
run: git clone --quiet https://github.com/adafruit/SdFat.git /home/runner/Arduino/libraries/SdFat
|
||||
|
||||
- name: test platforms
|
||||
run: python3 ci/build_platform.py ${{ matrix.arduino-platform }}
|
||||
|
||||
- name: Move build artifacts into place
|
||||
run: |
|
||||
mkdir build
|
||||
find -name "*.uf2" -ls
|
||||
for i in examples/*/build/*/*.uf2; do j=${i##*/}; j=${j%%*.}; mv $i build/$j-${{ matrix.arduino-platform }}.uf2; done
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ github.event.repository.name }}-${{ matrix.arduino-platform }}
|
||||
path: |
|
||||
build/*.uf2
|
||||
|
||||
- name: Zip release files
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
if [ -d build ]; then
|
||||
(
|
||||
echo "Built from Adafruit Floppy `git describe --tags` for ${{ matrix.arduino-platform }}"
|
||||
echo "Source code: https://github.com/adafruit/Adafruit_Floppy"
|
||||
echo "Adafruit Learning System: https://learn.adafruit.com/"
|
||||
) > build/README.txt
|
||||
cd build && zip -9 -o ${{ matrix.arduino-platform }}.zip *.hex *.bin *.uf2 *.txt
|
||||
fi
|
||||
|
||||
- name: Create release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: build/${{ matrix.arduino-platform }}.zip
|
||||
fail_on_unmatched_files: false
|
||||
body: "Select the zip file corresponding to your board from the list below."
|
||||
|
||||
doxyclang:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: adafruit/ci-arduino
|
||||
path: ci
|
||||
|
||||
- name: pre-install
|
||||
run: bash ci/actions_install.sh
|
||||
|
||||
- name: fix SDFat
|
||||
run: git clone --quiet https://github.com/adafruit/SdFat.git /home/runner/Arduino/libraries/SdFat
|
||||
run: python3 ci/build_platform.py feather_m4_express_tinyusb feather_rp2040
|
||||
|
||||
- name: clang
|
||||
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .
|
||||
|
|
|
|||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,8 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
/mfm
|
||||
/html
|
||||
/ci
|
||||
/examples/*/build
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "doxygen-awesome-css"]
|
||||
path = doxygen-awesome-css
|
||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||
444
Adafruit_Floppy.cpp
Normal file
444
Adafruit_Floppy.cpp
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
#include "Adafruit_Floppy.h"
|
||||
|
||||
#define DEBUG_FLOPPY (0)
|
||||
|
||||
// We need to read and write some pins at optimized speeds - use raw registers
|
||||
// or native SDK API!
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
#define read_index() (*indexPort & indexMask)
|
||||
#define read_data() (*dataPort & dataMask)
|
||||
#define set_debug_led() (*ledPort |= ledMask)
|
||||
#define clr_debug_led() (*ledPort &= ~ledMask)
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
#define read_index() gpio_get(_indexpin)
|
||||
#define read_data() gpio_get(_rddatapin)
|
||||
#define set_debug_led() gpio_put(led_pin, 1)
|
||||
#define clr_debug_led() gpio_put(led_pin, 0)
|
||||
#endif
|
||||
|
||||
#if !DEBUG_FLOPPY
|
||||
#undef set_debug_led
|
||||
#undef clr_debug_led
|
||||
#define set_debug_led() ((void)0)
|
||||
#define clr_debug_led() ((void)0)
|
||||
#endif
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Create a hardware interface to a floppy drive
|
||||
@param densitypin A pin connected to the floppy Density Select input
|
||||
@param indexpin A pin connected to the floppy Index Sensor output
|
||||
@param selectpin A pin connected to the floppy Drive Select input
|
||||
@param motorpin A pin connected to the floppy Motor Enable input
|
||||
@param directionpin A pin connected to the floppy Stepper Direction input
|
||||
@param steppin A pin connected to the floppy Stepper input
|
||||
@param wrdatapin A pin connected to the floppy Write Data input
|
||||
@param wrgatepin A pin connected to the floppy Write Gate input
|
||||
@param track0pin A pin connected to the floppy Track 00 Sensor output
|
||||
@param protectpin A pin connected to the floppy Write Protect Sensor output
|
||||
@param rddatapin A pin connected to the floppy Read Data output
|
||||
@param sidepin A pin connected to the floppy Side Select input
|
||||
@param readypin A pin connected to the floppy Ready/Disk Change output
|
||||
|
||||
*/
|
||||
/**************************************************************************/
|
||||
|
||||
Adafruit_Floppy::Adafruit_Floppy(int8_t densitypin, int8_t indexpin,
|
||||
int8_t selectpin, int8_t motorpin,
|
||||
int8_t directionpin, int8_t steppin,
|
||||
int8_t wrdatapin, int8_t wrgatepin,
|
||||
int8_t track0pin, int8_t protectpin,
|
||||
int8_t rddatapin, int8_t sidepin,
|
||||
int8_t readypin) {
|
||||
_densitypin = densitypin;
|
||||
_indexpin = indexpin;
|
||||
_selectpin = selectpin;
|
||||
_motorpin = motorpin;
|
||||
_directionpin = directionpin;
|
||||
_steppin = steppin;
|
||||
_wrdatapin = wrdatapin;
|
||||
_wrgatepin = wrgatepin;
|
||||
_track0pin = track0pin;
|
||||
_protectpin = protectpin;
|
||||
_rddatapin = rddatapin;
|
||||
_sidepin = sidepin;
|
||||
_readypin = readypin;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Initializes the GPIO pins but do not start the motor or anything
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::begin(void) { soft_reset(); }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Set back the object and pins to initial state
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::soft_reset(void) {
|
||||
// deselect drive
|
||||
pinMode(_selectpin, OUTPUT);
|
||||
digitalWrite(_selectpin, HIGH);
|
||||
|
||||
// motor enable pin, drive low to turn on motor
|
||||
pinMode(_motorpin, OUTPUT);
|
||||
digitalWrite(_motorpin, HIGH);
|
||||
|
||||
// set motor direction (low is in, high is out)
|
||||
pinMode(_directionpin, OUTPUT);
|
||||
digitalWrite(_directionpin, LOW); // move inwards to start
|
||||
|
||||
// step track pin, pulse low for 3us min, 3ms max per pulse
|
||||
pinMode(_steppin, OUTPUT);
|
||||
digitalWrite(_steppin, HIGH);
|
||||
|
||||
// side selector
|
||||
pinMode(_sidepin, OUTPUT);
|
||||
digitalWrite(_sidepin, HIGH); // side 0 to start
|
||||
|
||||
pinMode(_indexpin, INPUT_PULLUP);
|
||||
pinMode(_track0pin, INPUT_PULLUP);
|
||||
pinMode(_protectpin, INPUT_PULLUP);
|
||||
pinMode(_readypin, INPUT_PULLUP);
|
||||
pinMode(_rddatapin, INPUT_PULLUP);
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
indexPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(_indexpin));
|
||||
indexMask = digitalPinToBitMask(_indexpin);
|
||||
#endif
|
||||
|
||||
select_delay_us = 10;
|
||||
step_delay_us = 10000;
|
||||
settle_delay_ms = 15;
|
||||
motor_delay_ms = 1000;
|
||||
watchdog_delay_ms = 1000;
|
||||
bus_type = BUSTYPE_IBMPC;
|
||||
|
||||
if (led_pin >= 0) {
|
||||
pinMode(led_pin, OUTPUT);
|
||||
digitalWrite(led_pin, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Whether to select this drive
|
||||
@param selected True to select/enable
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::select(bool selected) {
|
||||
digitalWrite(_selectpin, !selected); // Selected logic level 0!
|
||||
// Select drive
|
||||
delayMicroseconds(select_delay_us);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Which head/side to read from
|
||||
@param head Head 0 or 1
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::side(uint8_t head) {
|
||||
digitalWrite(_sidepin, !head); // Head 0 is logic level 1, head 1 is logic 0!
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Turn on or off the floppy motor, if on we wait till we get an index
|
||||
pulse!
|
||||
@param motor_on True to turn on motor, False to turn it off
|
||||
@returns False if turning motor on and no index pulse found, true otherwise
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_Floppy::spin_motor(bool motor_on) {
|
||||
digitalWrite(_motorpin, !motor_on); // Motor on is logic level 0!
|
||||
if (!motor_on)
|
||||
return true; // we're done, easy!
|
||||
|
||||
delay(motor_delay_ms); // Main motor turn on
|
||||
|
||||
uint32_t index_stamp = millis();
|
||||
bool timedout = false;
|
||||
|
||||
if (debug_serial)
|
||||
debug_serial->print("Waiting for index pulse...");
|
||||
|
||||
while (digitalRead(_indexpin)) {
|
||||
if ((millis() - index_stamp) > 10000) {
|
||||
timedout = true; // its been 10 seconds?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timedout) {
|
||||
if (debug_serial)
|
||||
debug_serial->println("Didn't find an index pulse!");
|
||||
return false;
|
||||
}
|
||||
if (debug_serial)
|
||||
debug_serial->println("Found!");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Seek to the desired track, requires the motor to be spun up!
|
||||
@param track_num The track to step to
|
||||
@return True If we were able to get to the track location
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_Floppy::goto_track(uint8_t track_num) {
|
||||
// track 0 is a very special case because its the only one we actually know we
|
||||
// got to. if we dont know where we are, or we're going to track zero, step
|
||||
// back till we get there.
|
||||
if ((_track < 0) || track_num == 0) {
|
||||
if (debug_serial)
|
||||
debug_serial->println("Going to track 0");
|
||||
|
||||
// step back a lil more than expected just in case we really seeked out
|
||||
uint8_t max_steps = 250;
|
||||
while (max_steps--) {
|
||||
if (!digitalRead(_track0pin)) {
|
||||
_track = 0;
|
||||
break;
|
||||
}
|
||||
step(STEP_OUT, 1);
|
||||
}
|
||||
|
||||
if (digitalRead(_track0pin)) {
|
||||
// we never got a track 0 indicator :(
|
||||
if (debug_serial)
|
||||
debug_serial->println("Could not find track 0");
|
||||
return false; // we 'timed' out, were not able to locate track 0
|
||||
}
|
||||
}
|
||||
delay(settle_delay_ms);
|
||||
|
||||
// ok its a non-track 0 step, first, we cant go past 79 ok?
|
||||
track_num = min(track_num, MAX_TRACKS - 1);
|
||||
if (debug_serial)
|
||||
debug_serial->printf("Going to track %d\n\r", track_num);
|
||||
|
||||
if (_track == track_num) { // we are there already
|
||||
return true;
|
||||
}
|
||||
|
||||
int8_t steps = (int8_t)track_num - (int8_t)_track;
|
||||
if (steps > 0) {
|
||||
if (debug_serial)
|
||||
debug_serial->printf("Step in %d times\n\r", steps);
|
||||
step(STEP_IN, steps);
|
||||
} else {
|
||||
steps = abs(steps);
|
||||
if (debug_serial)
|
||||
debug_serial->printf("Step out %d times\n\r", steps);
|
||||
step(STEP_OUT, steps);
|
||||
}
|
||||
delay(settle_delay_ms);
|
||||
_track = track_num;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Step the track motor
|
||||
@param dir STEP_OUT or STEP_IN depending on desired direction
|
||||
@param times How many steps to take
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::step(bool dir, uint8_t times) {
|
||||
digitalWrite(_directionpin, dir);
|
||||
delayMicroseconds(10); // 1 microsecond, but we're generous
|
||||
|
||||
while (times--) {
|
||||
digitalWrite(_steppin, HIGH);
|
||||
delayMicroseconds(step_delay_us);
|
||||
digitalWrite(_steppin, LOW);
|
||||
delayMicroseconds(step_delay_us);
|
||||
digitalWrite(_steppin, HIGH); // end high
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief The current track location, based on internal caching
|
||||
@return The cached track location
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int8_t Adafruit_Floppy::track(void) { return _track; }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Capture one track's worth of flux transitions, between two falling
|
||||
index pulses
|
||||
@param pulses A pointer to an array of memory we can use to store into
|
||||
@param max_pulses The size of the allocated pulses array
|
||||
@return Number of pulses we actually captured
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint32_t Adafruit_Floppy::capture_track(uint8_t *pulses, uint32_t max_pulses) {
|
||||
unsigned pulse_count;
|
||||
uint8_t *pulses_ptr = pulses;
|
||||
uint8_t *pulses_end = pulses + max_pulses;
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
BusIO_PortReg *dataPort, *ledPort;
|
||||
BusIO_PortMask dataMask, ledMask;
|
||||
dataPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(_rddatapin));
|
||||
dataMask = digitalPinToBitMask(_rddatapin);
|
||||
ledPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(led_pin));
|
||||
ledMask = digitalPinToBitMask(led_pin);
|
||||
#endif
|
||||
|
||||
memset(pulses, 0, max_pulses); // zero zem out
|
||||
|
||||
noInterrupts();
|
||||
wait_for_index_pulse_low();
|
||||
|
||||
// wait for one clean flux pulse so we dont get cut off.
|
||||
// don't worry about losing this pulse, we'll get it on our
|
||||
// overlap run!
|
||||
|
||||
// ok we have a h-to-l transition so...
|
||||
bool last_index_state = read_index();
|
||||
uint8_t index_transitions = 0;
|
||||
|
||||
// if data line is low, wait till it rises
|
||||
if (!read_data()) {
|
||||
while (!read_data())
|
||||
;
|
||||
}
|
||||
// if data line is high, wait till it drops down
|
||||
if (read_data()) {
|
||||
while (read_data())
|
||||
;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
bool index_state = read_index();
|
||||
// ahh a L to H transition
|
||||
if (!last_index_state && index_state) {
|
||||
index_transitions++;
|
||||
if (index_transitions ==
|
||||
2) // and its the second one, so we're done with this track!
|
||||
break;
|
||||
}
|
||||
last_index_state = index_state;
|
||||
|
||||
// muahaha, now we can read track data!
|
||||
// Don't start counting at zero because we lost some time checking for
|
||||
// index. Empirically, at 180MHz and -O3 on M4, this gives the most 'even'
|
||||
// timings, moving the bins from 41/63/83 to 44/66/89
|
||||
pulse_count = 3;
|
||||
|
||||
// while pulse is in the low pulse, count up
|
||||
while (!read_data()) {
|
||||
pulse_count++;
|
||||
}
|
||||
set_debug_led();
|
||||
|
||||
// while pulse is high, keep counting up
|
||||
while (read_data())
|
||||
pulse_count++;
|
||||
clr_debug_led();
|
||||
|
||||
pulses_ptr[0] = min(255, pulse_count);
|
||||
pulses_ptr++;
|
||||
if (pulses_ptr == pulses_end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// whew done
|
||||
interrupts();
|
||||
return pulses_ptr - pulses;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Busy wait until the index line goes from high to low
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::wait_for_index_pulse_low(void) {
|
||||
// initial state
|
||||
bool index_state = read_index();
|
||||
bool last_index_state = index_state;
|
||||
|
||||
// wait until last index state is H and current state is L
|
||||
while (true) {
|
||||
index_state = read_index();
|
||||
if (last_index_state && !index_state) {
|
||||
return;
|
||||
}
|
||||
last_index_state = index_state;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Pretty print the counts in a list of flux transitions
|
||||
@param pulses A pointer to an array of memory containing pulse counts
|
||||
@param num_pulses The size of the pulses in the array
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::print_pulses(uint8_t *pulses, uint32_t num_pulses) {
|
||||
if (!debug_serial)
|
||||
return;
|
||||
|
||||
for (uint32_t i = 0; i < num_pulses; i++) {
|
||||
debug_serial->print(pulses[i]);
|
||||
debug_serial->print(", ");
|
||||
}
|
||||
debug_serial->println();
|
||||
}
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Pretty print a simple histogram of flux transitions
|
||||
@param pulses A pointer to an array of memory containing pulse counts
|
||||
@param num_pulses The size of the pulses in the array
|
||||
@param max_bins The maximum number of histogram bins to use (default 64)
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_Floppy::print_pulse_bins(uint8_t *pulses, uint32_t num_pulses,
|
||||
uint8_t max_bins) {
|
||||
if (!debug_serial)
|
||||
return;
|
||||
|
||||
// lets bin em!
|
||||
uint32_t bins[max_bins][2];
|
||||
memset(bins, 0, max_bins * 2 * sizeof(uint32_t));
|
||||
// we'll add each pulse to a bin so we can figure out the 3 buckets
|
||||
for (uint32_t i = 0; i < num_pulses; i++) {
|
||||
uint8_t p = pulses[i];
|
||||
// find a bin for this pulse
|
||||
uint8_t bin = 0;
|
||||
for (bin = 0; bin < max_bins; bin++) {
|
||||
// bin already exists? increment the count!
|
||||
if (bins[bin][0] == p) {
|
||||
bins[bin][1]++;
|
||||
break;
|
||||
}
|
||||
if (bins[bin][0] == 0) {
|
||||
// ok we never found the bin, so lets make it this one!
|
||||
bins[bin][0] = p;
|
||||
bins[bin][1] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bin == max_bins)
|
||||
debug_serial->println("oof we ran out of bins but we'll keep going");
|
||||
}
|
||||
// this is a very lazy way to print the bins sorted
|
||||
for (uint8_t pulse_w = 1; pulse_w < 255; pulse_w++) {
|
||||
for (uint8_t b = 0; b < max_bins; b++) {
|
||||
if (bins[b][0] == pulse_w) {
|
||||
debug_serial->print(bins[b][0]);
|
||||
debug_serial->print(": ");
|
||||
debug_serial->println(bins[b][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Adafruit_Floppy.h
Normal file
73
Adafruit_Floppy.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef ADAFRUIT_FLOPPY_H
|
||||
#define ADAFRUIT_FLOPPY_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Adafruit_SPIDevice.h>
|
||||
|
||||
#define MAX_TRACKS 80
|
||||
#define STEP_OUT HIGH
|
||||
#define STEP_IN LOW
|
||||
#define MAX_FLUX_PULSE_PER_TRACK \
|
||||
(uint32_t)(500000UL / 5 * \
|
||||
1.5) // 500khz / 5 hz per track rotation, 1.5 rotations
|
||||
|
||||
#define BUSTYPE_IBMPC 1
|
||||
#define BUSTYPE_SHUGART 2
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief A helper class for chattin with floppy drives
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class Adafruit_Floppy {
|
||||
public:
|
||||
Adafruit_Floppy(int8_t densitypin, int8_t indexpin, int8_t selectpin,
|
||||
int8_t motorpin, int8_t directionpin, int8_t steppin,
|
||||
int8_t wrdatapin, int8_t wrgatepin, int8_t track0pin,
|
||||
int8_t protectpin, int8_t rddatapin, int8_t sidepin,
|
||||
int8_t readypin);
|
||||
void begin(void);
|
||||
void soft_reset(void);
|
||||
|
||||
void select(bool selected);
|
||||
bool spin_motor(bool motor_on);
|
||||
bool goto_track(uint8_t track);
|
||||
void side(uint8_t head);
|
||||
int8_t track(void);
|
||||
void step(bool dir, uint8_t times);
|
||||
|
||||
uint32_t capture_track(uint8_t *pulses, uint32_t max_pulses)
|
||||
__attribute__((optimize("O3")));
|
||||
void print_pulse_bins(uint8_t *pulses, uint32_t num_pulses,
|
||||
uint8_t max_bins = 64);
|
||||
void print_pulses(uint8_t *pulses, uint32_t num_pulses);
|
||||
|
||||
int8_t led_pin = LED_BUILTIN; ///< Debug LED output for tracing
|
||||
|
||||
uint16_t select_delay_us = 10; ///< delay after drive select (usecs)
|
||||
uint16_t step_delay_us = 10000; ///< delay between head steps (usecs)
|
||||
uint16_t settle_delay_ms = 15; ///< settle delay after seek (msecs)
|
||||
uint16_t motor_delay_ms = 1000; ///< delay after motor on (msecs)
|
||||
uint16_t watchdog_delay_ms =
|
||||
1000; ///< quiescent time until drives reset (msecs)
|
||||
uint8_t bus_type = BUSTYPE_IBMPC; ///< what kind of floppy drive we're using
|
||||
|
||||
Stream *debug_serial = NULL; ///< optional debug stream for serial output
|
||||
|
||||
private:
|
||||
void wait_for_index_pulse_low(void);
|
||||
|
||||
// theres a lot of GPIO!
|
||||
int8_t _densitypin, _indexpin, _selectpin, _motorpin, _directionpin, _steppin,
|
||||
_wrdatapin, _wrgatepin, _track0pin, _protectpin, _rddatapin, _sidepin,
|
||||
_readypin;
|
||||
|
||||
int8_t _track = -1;
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
BusIO_PortReg *indexPort;
|
||||
BusIO_PortMask indexMask;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
Creative Commons Attribution 4.0 International
|
||||
|
||||
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
|
||||
|
||||
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
|
||||
|
||||
Section 1 – Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
|
||||
|
||||
Section 2 – Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
A. reproduce and Share the Licensed Material, in whole or in part; and
|
||||
|
||||
B. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section 6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
|
||||
|
||||
B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
|
||||
|
||||
Section 3 – License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified form), You must:
|
||||
|
||||
A. retain the following if it is supplied by the Licensor with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||
|
||||
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
|
||||
|
||||
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
|
||||
|
||||
Section 4 – Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
|
||||
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
|
||||
|
||||
Section 5 – Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
|
||||
|
||||
b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
|
||||
|
||||
Section 6 – Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
|
||||
|
||||
d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
|
||||
|
||||
e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||
|
||||
Section 7 – Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
|
||||
|
||||
Section 8 – Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
|
||||
|
||||
Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 4cd62308d825fe0396d2f66ffbab45d0e247724c
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define ENABLE_PIN (6)
|
||||
#define PHASE1_PIN (A2)
|
||||
#define PHASE2_PIN (13)
|
||||
#define PHASE3_PIN (12)
|
||||
#define PHASE4_PIN (11)
|
||||
#define RDDATA_PIN (5)
|
||||
#define INDEX_PIN (A3)
|
||||
#define APPLE2_PROTECT_PIN (21) // "SDA"
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define ENABLE_PIN (8) // D6
|
||||
#define PHASE1_PIN (A2)
|
||||
#define PHASE2_PIN (13)
|
||||
#define PHASE3_PIN (12)
|
||||
#define PHASE4_PIN (11)
|
||||
#define RDDATA_PIN (7) // D5
|
||||
#define INDEX_PIN (A3)
|
||||
#define APPLE2_PROTECT_PIN (2) // "SDA"
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Apple2Floppy floppy(INDEX_PIN, ENABLE_PIN,
|
||||
PHASE1_PIN, PHASE2_PIN, PHASE3_PIN, PHASE4_PIN,
|
||||
-1, -1, APPLE2_PROTECT_PIN, RDDATA_PIN);
|
||||
|
||||
// WARNING! there are 150K max flux pulses per track!
|
||||
uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK];
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(100);
|
||||
|
||||
Serial.println("its time for a nice floppy transfer!");
|
||||
floppy.debug_serial = &Serial;
|
||||
|
||||
if (!floppy.begin()) {
|
||||
Serial.println("Failed to initialize floppy interface");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
floppy.select(true);
|
||||
if (! floppy.spin_motor(true)) {
|
||||
Serial.println("Failed to spin up motor & find index pulse");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
Serial.print("Seeking track...");
|
||||
if (! floppy.goto_track(0)) {
|
||||
Serial.println("Failed to seek to track");
|
||||
while (1) yield();
|
||||
}
|
||||
Serial.println("done!");
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int32_t index_pulse_offset;
|
||||
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true);
|
||||
|
||||
Serial.print("Captured ");
|
||||
Serial.print(captured_flux);
|
||||
Serial.println(" flux transitions");
|
||||
|
||||
//floppy.print_pulses(flux_transitions, captured_flux);
|
||||
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
|
||||
|
||||
Serial.printf("Write protect: %s\n", floppy.get_write_protect() ? "ON" : "off");
|
||||
|
||||
delay(100);
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
Print size, modify date/time, and name for all files in root.
|
||||
*/
|
||||
|
||||
/*********************************************************************
|
||||
Adafruit invests time and resources providing this open source code,
|
||||
please support Adafruit and open-source hardware by purchasing
|
||||
products from Adafruit!
|
||||
*********************************************************************/
|
||||
|
||||
#include <SPI.h>
|
||||
#include "SdFat.h"
|
||||
#include <Adafruit_Floppy.h>
|
||||
|
||||
// If using SAMD51, turn on TINYUSB USB stack
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
MOTOR_PIN, DIR_PIN, STEP_PIN,
|
||||
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
|
||||
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
|
||||
Adafruit_MFM_Floppy mfm_floppy(&floppy);
|
||||
|
||||
// file system object from SdFat
|
||||
FatFileSystem fatfs;
|
||||
|
||||
FatFile root;
|
||||
FatFile file;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Wait for USB Serial
|
||||
while (!Serial) {
|
||||
SysCall::yield();
|
||||
}
|
||||
|
||||
Serial.println("Floppy FAT directory listing demo");
|
||||
|
||||
// Init floppy drive - must spin up and find index
|
||||
if (! mfm_floppy.begin()) {
|
||||
Serial.println("Floppy didn't initialize - check wiring and diskette!");
|
||||
}
|
||||
|
||||
// Init file system on the flash
|
||||
fatfs.begin(&mfm_floppy);
|
||||
|
||||
if (!root.open("/")) {
|
||||
Serial.println("open root failed");
|
||||
}
|
||||
// Open next file in root.
|
||||
// Warning, openNext starts at the current directory position
|
||||
// so a rewind of the directory may be required.
|
||||
while (file.openNext(&root, O_RDONLY)) {
|
||||
file.printFileSize(&Serial);
|
||||
Serial.write(' ');
|
||||
file.printModifyDateTime(&Serial);
|
||||
Serial.write(' ');
|
||||
file.printName(&Serial);
|
||||
if (file.isDir()) {
|
||||
// Indicate a directory.
|
||||
Serial.write('/');
|
||||
}
|
||||
Serial.println();
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (root.getError()) {
|
||||
Serial.println("openNext failed");
|
||||
} else {
|
||||
Serial.println("Done!");
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void loop() {
|
||||
Serial.print("Read a file? >");
|
||||
String filename;
|
||||
do {
|
||||
filename = Serial.readStringUntil('\n');
|
||||
filename.trim();
|
||||
} while (filename.length() == 0);
|
||||
|
||||
Serial.print("Reading file name: ");
|
||||
Serial.println(filename);
|
||||
|
||||
// Open the file for reading and check that it was successfully opened.
|
||||
// The FILE_READ mode will open the file for reading.
|
||||
File dataFile = fatfs.open(filename, FILE_READ);
|
||||
if (!dataFile) {
|
||||
Serial.println("Failed to open data file! Does it exist?");
|
||||
return;
|
||||
}
|
||||
// File was opened, now print out data character by character until at the
|
||||
// end of the file.
|
||||
Serial.println("Opened file, printing contents below:");
|
||||
while (dataFile.available()) {
|
||||
// Use the read function to read the next character.
|
||||
// You can alternatively use other functions like readUntil, readString, etc.
|
||||
// See the fatfs_full_usage example for more details.
|
||||
char c = dataFile.read();
|
||||
Serial.print(c);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,47 +2,56 @@
|
|||
|
||||
// If using SAMD51, turn on TINYUSB USB stack
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#if F_CPU != 180000000L
|
||||
#warning "please set CPU speed to 180MHz overclock"
|
||||
#endif
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#if F_CPU != 200000000L
|
||||
#warning "please set CPU speed to 200MHz overclock"
|
||||
#endif
|
||||
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#if F_CPU != 200000000L
|
||||
#warning "please set CPU speed to 200MHz overclock"
|
||||
#endif
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
|
@ -59,16 +68,12 @@ uint32_t time_stamp = 0;
|
|||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(100);
|
||||
|
||||
Serial.println("its time for a nice floppy transfer!");
|
||||
floppy.debug_serial = &Serial;
|
||||
|
||||
if (!floppy.begin()) {
|
||||
Serial.println("Failed to initialize floppy interface");
|
||||
while (1) yield();
|
||||
}
|
||||
floppy.begin();
|
||||
|
||||
floppy.select(true);
|
||||
if (! floppy.spin_motor(true)) {
|
||||
|
|
@ -85,24 +90,23 @@ void setup() {
|
|||
}
|
||||
|
||||
void loop() {
|
||||
int32_t index_pulse_offset;
|
||||
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true);
|
||||
|
||||
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions));
|
||||
|
||||
Serial.print("Captured ");
|
||||
Serial.print(captured_flux);
|
||||
Serial.println(" flux transitions");
|
||||
|
||||
//floppy.print_pulses(flux_transitions, captured_flux);
|
||||
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
|
||||
|
||||
floppy.print_pulse_bins(flux_transitions, captured_flux, 255);
|
||||
|
||||
if ((millis() - time_stamp) > 1000) {
|
||||
Serial.print("Ready? ");
|
||||
Serial.println(digitalRead(READY_PIN) ? "No" : "Yes");
|
||||
Serial.print("Write Protected? ");
|
||||
Serial.println(floppy.get_write_protect() ? "Yes" : "No");
|
||||
Serial.print("Write Protected? ");
|
||||
Serial.println(digitalRead(PROT_PIN) ? "No" : "Yes");
|
||||
Serial.print("Track 0? ");
|
||||
Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes");
|
||||
time_stamp = millis();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
// If using SAMD51, turn on TINYUSB USB stack
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
MOTOR_PIN, DIR_PIN, STEP_PIN,
|
||||
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
|
||||
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
|
||||
|
||||
// WARNING! there are 150K max flux pulses per track!
|
||||
uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK];
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(100);
|
||||
|
||||
Serial.println("its time for a nice floppy transfer!");
|
||||
floppy.debug_serial = &Serial;
|
||||
|
||||
if (!floppy.begin()) {
|
||||
Serial.println("Failed to initialize floppy interface");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
floppy.select(true);
|
||||
if (! floppy.spin_motor(true)) {
|
||||
Serial.println("Failed to spin up motor & find index pulse");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
Serial.print("Seeking track...");
|
||||
if (! floppy.goto_track(0)) {
|
||||
Serial.println("Failed to seek to track");
|
||||
while (1) yield();
|
||||
}
|
||||
Serial.println("done!");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int32_t index_pulse_offset;
|
||||
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true);
|
||||
|
||||
Serial.print("Captured ");
|
||||
Serial.print(captured_flux);
|
||||
Serial.println(" flux transitions");
|
||||
|
||||
//floppy.print_pulses(flux_transitions, captured_flux);
|
||||
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
|
||||
|
||||
if ((millis() - time_stamp) > 1000) {
|
||||
Serial.print("Ready? ");
|
||||
Serial.println(digitalRead(READY_PIN) ? "No" : "Yes");
|
||||
Serial.print("Write Protected? ");
|
||||
Serial.println(digitalRead(PROT_PIN) ? "No" : "Yes");
|
||||
Serial.print("Track 0? ");
|
||||
Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes");
|
||||
time_stamp = millis();
|
||||
}
|
||||
|
||||
unsigned T_2 = floppy.getSampleFrequency() * 2 / 1000000;
|
||||
unsigned T_3 = floppy.getSampleFrequency() * 3 / 1000000;
|
||||
unsigned T_4 = floppy.getSampleFrequency() * 4 / 1000000;
|
||||
|
||||
for (size_t i = 0; i < sizeof(flux_transitions); i += 3) {
|
||||
flux_transitions[i] = T_2;
|
||||
}
|
||||
for (size_t i = 1; i < sizeof(flux_transitions); i += 3) {
|
||||
flux_transitions[i] = T_3;
|
||||
}
|
||||
for (size_t i = 2; i < sizeof(flux_transitions); i += 3) {
|
||||
flux_transitions[i] = T_4;
|
||||
}
|
||||
|
||||
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
|
||||
|
||||
Serial.println("Writing track with T234234...");
|
||||
Serial.printf("T2 = %d T3 = %d T4 = %d\n", T_2, T_3, T_4);
|
||||
floppy.write_track(flux_transitions, sizeof(flux_transitions), true);
|
||||
|
||||
yield();
|
||||
}
|
||||
|
|
@ -1,87 +1,67 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#if F_CPU != 180000000L
|
||||
#warning "please set CPU speed to 180MHz overclock"
|
||||
#endif
|
||||
#define GW_SAMPLEFREQ (F_CPU * 11/90) // samd51 is sample rate of 22MHz at 180MHz OC
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
|
||||
// jepler's prototype board, subject to change
|
||||
#define APPLE2_PHASE1_PIN (A2) // IDC 2
|
||||
#define APPLE2_PHASE2_PIN (13) // IDC 4
|
||||
#define APPLE2_PHASE3_PIN (12) // IDC 6
|
||||
#define APPLE2_PHASE4_PIN (11) // IDC 8
|
||||
#define APPLE2_WRGATE_PIN (10) // IDC 10
|
||||
#define APPLE2_ENABLE_PIN (8) // D6 // IDC 14
|
||||
#define APPLE2_RDDATA_PIN (7) // D5 // IDC 16
|
||||
#define APPLE2_WRDATA_PIN (3) // SCL // IDC 18
|
||||
#define APPLE2_PROTECT_PIN (2) // SDA // IDC 20
|
||||
#define APPLE2_INDEX_PIN (A3)
|
||||
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#if F_CPU != 200000000L
|
||||
#warning "please set CPU speed to 200MHz overclock"
|
||||
#endif
|
||||
#define GW_SAMPLEFREQ 26000000UL // 26mhz for rp2040
|
||||
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#if F_CPU != 200000000L
|
||||
#warning "please set CPU speed to 200MHz overclock"
|
||||
#endif
|
||||
#define GW_SAMPLEFREQ 26000000UL // 26mhz for rp2040
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
#ifdef READ_PIN
|
||||
Adafruit_Floppy pcfloppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
MOTOR_PIN, DIR_PIN, STEP_PIN,
|
||||
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
|
||||
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
|
||||
#else
|
||||
#warning "This firmware will not support PC/Shugart drives"
|
||||
#endif
|
||||
#ifdef APPLE2_RDDATA_PIN
|
||||
Adafruit_Apple2Floppy apple2floppy(APPLE2_INDEX_PIN, APPLE2_ENABLE_PIN,
|
||||
APPLE2_PHASE1_PIN, APPLE2_PHASE2_PIN, APPLE2_PHASE3_PIN, APPLE2_PHASE4_PIN,
|
||||
APPLE2_WRDATA_PIN, APPLE2_WRGATE_PIN, APPLE2_PROTECT_PIN, APPLE2_RDDATA_PIN);
|
||||
#else
|
||||
#warning "This firmware will not support Apple ][ drives"
|
||||
#endif
|
||||
|
||||
Adafruit_FloppyBase *floppy;
|
||||
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
MOTOR_PIN, DIR_PIN, STEP_PIN,
|
||||
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
|
||||
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
|
|
@ -105,79 +85,37 @@ uint8_t cmd_buff_idx = 0;
|
|||
#define GW_CMD_GETPARAMS_DELAYS 0
|
||||
#define GW_CMD_MOTOR 6
|
||||
#define GW_CMD_READFLUX 7
|
||||
#define GW_CMD_WRITEFLUX 8
|
||||
#define GW_CMD_GETFLUXSTATUS 9
|
||||
#define GW_CMD_SELECT 12
|
||||
#define GW_CMD_DESELECT 13
|
||||
#define GW_CMD_SETBUSTYPE 14
|
||||
#define GW_CMD_SETBUSTYPE_IBM 1
|
||||
#define GW_CMD_SETBUSTYPE_SHUGART 2
|
||||
#define GW_CMD_SETBUSTYPE_APPLE2 3
|
||||
#define GW_CMD_SETBUSTYPE_APPLE2_QUARTERTRACK 4
|
||||
#define GW_CMD_SETPIN 15
|
||||
#define GW_CMD_SETPIN_DENSITY 2
|
||||
#define GW_CMD_RESET 16
|
||||
#define GW_CMD_SOURCEBYTES 18
|
||||
#define GW_CMD_SINKBYTES 19
|
||||
#define GW_CMD_GETPIN 20
|
||||
#define GW_CMD_GETPIN_TRACK0 26
|
||||
|
||||
#define GW_ACK_OK (byte)0
|
||||
#define GW_ACK_BADCMD 1
|
||||
#define GW_ACK_NOINDEX 2
|
||||
#define GW_ACK_NOTRACK0 3
|
||||
#define GW_ACK_WRPROT 6
|
||||
#define GW_ACK_NOUNIT 7
|
||||
#define GW_ACK_BADPIN 10
|
||||
|
||||
uint32_t timestamp = 0;
|
||||
|
||||
bool setbustype(int bustype) {
|
||||
if (floppy) {
|
||||
floppy->end();
|
||||
}
|
||||
floppy = nullptr;
|
||||
switch (bustype) {
|
||||
case -1:
|
||||
#ifdef READ_PIN
|
||||
case GW_CMD_SETBUSTYPE_IBM:
|
||||
case GW_CMD_SETBUSTYPE_SHUGART:
|
||||
// TODO: whats the diff???
|
||||
floppy = &pcfloppy;
|
||||
break;
|
||||
#endif
|
||||
#ifdef APPLE2_RDDATA_PIN
|
||||
case GW_CMD_SETBUSTYPE_APPLE2:
|
||||
floppy = &apple2floppy;
|
||||
apple2floppy.step_mode(Adafruit_Apple2Floppy::STEP_MODE_HALF);
|
||||
break;
|
||||
case GW_CMD_SETBUSTYPE_APPLE2_QUARTERTRACK:
|
||||
apple2floppy.step_mode(Adafruit_Apple2Floppy::STEP_MODE_QUARTER);
|
||||
floppy = &apple2floppy;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
Serial1.printf("Unsupported bus type %d\n", bustype);
|
||||
return false;
|
||||
}
|
||||
floppy->debug_serial = &Serial1;
|
||||
auto result = floppy->begin();
|
||||
Serial1.printf("setbustype() floppy = %p result=%d\n", floppy, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial1.begin(115200);
|
||||
Serial1.begin(115200);
|
||||
//while (!Serial) delay(100);
|
||||
Serial1.println("GrizzlyWizzly");
|
||||
|
||||
timestamp = millis();
|
||||
|
||||
floppy.debug_serial = &Serial1;
|
||||
floppy.begin();
|
||||
}
|
||||
|
||||
uint8_t get_cmd(uint8_t *buff, uint8_t maxbuff) {
|
||||
int i = 0;
|
||||
|
||||
int i=0;
|
||||
|
||||
if (Serial.available() < 2) return 0;
|
||||
buff[i++] = Serial.read();
|
||||
buff[i++] = Serial.read();
|
||||
|
|
@ -186,7 +124,7 @@ uint8_t get_cmd(uint8_t *buff, uint8_t maxbuff) {
|
|||
delay(1);
|
||||
yield();
|
||||
}
|
||||
for (; i < buff[1]; i++) {
|
||||
for (; i<buff[1]; i++) {
|
||||
buff[i] = Serial.read();
|
||||
}
|
||||
return i;
|
||||
|
|
@ -198,40 +136,19 @@ uint32_t transfered_bytes;
|
|||
uint32_t captured_pulses;
|
||||
// WARNING! there are 100K max flux pulses per track!
|
||||
uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK];
|
||||
bool motor_state = false; // we can cache whether the motor is spinning
|
||||
bool flux_status; // result of last flux read or write command
|
||||
|
||||
|
||||
void loop() {
|
||||
uint8_t cmd_len = get_cmd(cmd_buffer, sizeof(cmd_buffer));
|
||||
if (!cmd_len) {
|
||||
if ((millis() > timestamp) && ((millis() - timestamp) > 10000)) {
|
||||
Serial1.println("Timed out waiting for command, resetting motor");
|
||||
if (floppy) {
|
||||
Serial1.println("goto track 0");
|
||||
floppy->goto_track(0);
|
||||
Serial1.println("stop motor");
|
||||
floppy->spin_motor(false);
|
||||
Serial1.println("deselect");
|
||||
floppy->select(false);
|
||||
}
|
||||
Serial1.println("motor reset");
|
||||
motor_state = false;
|
||||
timestamp = millis();
|
||||
}
|
||||
return;
|
||||
}
|
||||
timestamp = millis();
|
||||
|
||||
|
||||
if (!cmd_len) return;
|
||||
|
||||
int i = 0;
|
||||
uint8_t cmd = cmd_buffer[0];
|
||||
memset(reply_buffer, 0, sizeof(reply_buffer));
|
||||
reply_buffer[i++] = cmd; // echo back the cmd itself
|
||||
|
||||
Serial1.printf("Got command 0x%02x of length %d\n\r", cmd, cmd_buffer[1]);
|
||||
|
||||
|
||||
|
||||
Serial1.printf("Got command 0x%02x\n\r", cmd);
|
||||
|
||||
if (cmd == GW_CMD_GETINFO) {
|
||||
Serial1.println("Get info");
|
||||
uint8_t sub_cmd = cmd_buffer[2];
|
||||
|
|
@ -241,11 +158,10 @@ void loop() {
|
|||
reply_buffer[i++] = GW_FIRMVER_MINOR; // 1 byte
|
||||
reply_buffer[i++] = 1; // is main firm
|
||||
reply_buffer[i++] = GW_MAXCMD;
|
||||
uint32_t samplefreq = floppy->getSampleFrequency();
|
||||
reply_buffer[i++] = samplefreq & 0xFF;
|
||||
reply_buffer[i++] = (samplefreq >> 8) & 0xFF;
|
||||
reply_buffer[i++] = (samplefreq >> 16) & 0xFF;
|
||||
reply_buffer[i++] = (samplefreq >> 24) & 0xFF;
|
||||
reply_buffer[i++] = GW_SAMPLEFREQ & 0xFF;
|
||||
reply_buffer[i++] = (GW_SAMPLEFREQ >> 8) & 0xFF;
|
||||
reply_buffer[i++] = (GW_SAMPLEFREQ >> 16) & 0xFF;
|
||||
reply_buffer[i++] = (GW_SAMPLEFREQ >> 24) & 0xFF;
|
||||
reply_buffer[i++] = GW_HW_MODEL;
|
||||
reply_buffer[i++] = GW_HW_SUBMODEL;
|
||||
reply_buffer[i++] = GW_USB_SPEED;
|
||||
|
|
@ -258,7 +174,7 @@ void loop() {
|
|||
uint32_t min_usec = bandwidth_timer * 1000;
|
||||
uint32_t max_usec = bandwidth_timer * 1000;
|
||||
// TODO What is this math supposed to be??
|
||||
|
||||
|
||||
reply_buffer[i++] = min_bytes & 0xFF;
|
||||
reply_buffer[i++] = min_bytes >> 8;
|
||||
reply_buffer[i++] = min_bytes >> 16;
|
||||
|
|
@ -275,7 +191,7 @@ void loop() {
|
|||
reply_buffer[i++] = max_usec >> 8;
|
||||
reply_buffer[i++] = max_usec >> 16;
|
||||
reply_buffer[i++] = max_usec >> 24;
|
||||
|
||||
|
||||
// TODO more?
|
||||
Serial.write(reply_buffer, 34);
|
||||
}
|
||||
|
|
@ -286,47 +202,47 @@ void loop() {
|
|||
uint8_t sub_cmd = cmd_buffer[2];
|
||||
if (sub_cmd == GW_CMD_GETPARAMS_DELAYS) {
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
reply_buffer[i++] = floppy->select_delay_us & 0xFF;
|
||||
reply_buffer[i++] = floppy->select_delay_us >> 8;
|
||||
reply_buffer[i++] = floppy->step_delay_us & 0xFF;
|
||||
reply_buffer[i++] = floppy->step_delay_us >> 8;
|
||||
reply_buffer[i++] = floppy->settle_delay_ms & 0xFF;
|
||||
reply_buffer[i++] = floppy->settle_delay_ms >> 8;
|
||||
reply_buffer[i++] = floppy->motor_delay_ms & 0xFF;
|
||||
reply_buffer[i++] = floppy->motor_delay_ms >> 8;
|
||||
reply_buffer[i++] = floppy->watchdog_delay_ms & 0xFF;
|
||||
reply_buffer[i++] = floppy->watchdog_delay_ms >> 8;
|
||||
reply_buffer[i++] = floppy.select_delay_us & 0xFF;
|
||||
reply_buffer[i++] = floppy.select_delay_us >> 8;
|
||||
reply_buffer[i++] = floppy.step_delay_us & 0xFF;
|
||||
reply_buffer[i++] = floppy.step_delay_us >> 8;
|
||||
reply_buffer[i++] = floppy.settle_delay_ms & 0xFF;
|
||||
reply_buffer[i++] = floppy.settle_delay_ms >> 8;
|
||||
reply_buffer[i++] = floppy.motor_delay_ms & 0xFF;
|
||||
reply_buffer[i++] = floppy.motor_delay_ms >> 8;
|
||||
reply_buffer[i++] = floppy.watchdog_delay_ms & 0xFF;
|
||||
reply_buffer[i++] = floppy.watchdog_delay_ms >> 8;
|
||||
Serial.write(reply_buffer, 12);
|
||||
}
|
||||
}
|
||||
|
||||
else if (cmd == GW_CMD_RESET) {
|
||||
Serial1.println("Soft reset");
|
||||
if (floppy)
|
||||
floppy->soft_reset();
|
||||
floppy.soft_reset();
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_SETBUSTYPE) {
|
||||
uint8_t bustype = cmd_buffer[2];
|
||||
auto result = setbustype(bustype);
|
||||
Serial1.printf("Set bus type %d -> %d\n\r", bustype, result);
|
||||
if (result) {
|
||||
Serial1.printf("Set bus type %d\n\r", bustype);
|
||||
// TODO: whats the diff???
|
||||
if (bustype == GW_CMD_SETBUSTYPE_IBM) {
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
}
|
||||
else if (bustype == GW_CMD_SETBUSTYPE_SHUGART) {
|
||||
floppy.bus_type = BUSTYPE_SHUGART;
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
} else {
|
||||
reply_buffer[i++] = GW_ACK_BADCMD;
|
||||
}
|
||||
motor_state = false;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_SEEK) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
uint8_t track = cmd_buffer[2];
|
||||
Serial1.printf("Seek track %d\n\r", track);
|
||||
bool r = floppy->goto_track(track);
|
||||
bool r = floppy.goto_track(track);
|
||||
if (r) {
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
} else {
|
||||
|
|
@ -334,64 +250,47 @@ void loop() {
|
|||
}
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_HEAD) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
uint8_t head = cmd_buffer[2];
|
||||
Serial1.printf("Seek head %d\n\r", head);
|
||||
floppy->side(head);
|
||||
floppy.side(head);
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_MOTOR) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
uint8_t unit = cmd_buffer[2];
|
||||
uint8_t state = cmd_buffer[3];
|
||||
Serial1.printf("Turn motor %d %s\n\r", unit, state ? "on" : "off");
|
||||
if (motor_state != state) { // we're in the opposite state
|
||||
if (! floppy->spin_motor(state)) {
|
||||
reply_buffer[i++] = GW_ACK_NOINDEX;
|
||||
} else {
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
}
|
||||
motor_state = state;
|
||||
if (! floppy.spin_motor(state)) {
|
||||
reply_buffer[i++] = GW_ACK_NOINDEX;
|
||||
} else {
|
||||
// our cached state is correct!
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
}
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_SELECT) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
uint8_t sub_cmd = cmd_buffer[2];
|
||||
Serial1.printf("Select drive %d\n\r", sub_cmd);
|
||||
if (sub_cmd == 0) {
|
||||
floppy->select(true);
|
||||
floppy.select(true);
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
} else {
|
||||
reply_buffer[i++] = GW_ACK_NOUNIT;
|
||||
}
|
||||
Serial1.printf("Reply buffer = %d %d\n", reply_buffer[0], reply_buffer[1]);
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
else if (cmd == GW_CMD_DESELECT) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
Serial1.printf("Deselect drive\n\r");
|
||||
floppy->select(false);
|
||||
floppy.select(false);
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_READFLUX) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
uint32_t flux_ticks;
|
||||
uint16_t revs;
|
||||
flux_ticks = cmd_buffer[5];
|
||||
|
|
@ -403,279 +302,165 @@ void loop() {
|
|||
flux_ticks |= cmd_buffer[2];
|
||||
revs = cmd_buffer[7];
|
||||
revs <<= 8;
|
||||
revs |= cmd_buffer[6];
|
||||
if (revs) {
|
||||
revs -= 1;
|
||||
}
|
||||
|
||||
if (floppy->track() == -1) {
|
||||
floppy->goto_track(0);
|
||||
}
|
||||
|
||||
Serial1.printf("Reading flux0rs on track %d: %u ticks and %d revs\n\r", floppy->track(), flux_ticks, revs);
|
||||
Serial1.printf("Sample freqency %.1fMHz\n", floppy->getSampleFrequency() / 1e6);
|
||||
uint16_t capture_ms = 0;
|
||||
uint16_t capture_revs = 0;
|
||||
if (flux_ticks) {
|
||||
// we'll back calculate the revolutions
|
||||
capture_ms = 1000.0 * (float)flux_ticks / (float)floppy->getSampleFrequency();
|
||||
revs = 1;
|
||||
} else {
|
||||
capture_revs = revs;
|
||||
capture_ms = 0;
|
||||
}
|
||||
|
||||
revs |= cmd_buffer[6];
|
||||
revs -= 1;
|
||||
|
||||
Serial1.printf("Reading flux0rs on track %d: %u ticks and %d revs\n\r", floppy.track(), flux_ticks, revs);
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
while (revs--) {
|
||||
int32_t index_offset;
|
||||
// read in greaseweazle mode (long pulses encoded with 250's)
|
||||
captured_pulses = floppy->capture_track(flux_transitions, sizeof(flux_transitions),
|
||||
&index_offset, true, capture_ms);
|
||||
Serial1.printf("Rev #%d captured %u pulses, second index fall @ %d\n\r",
|
||||
revs, captured_pulses, index_offset);
|
||||
//floppy->print_pulse_bins(flux_transitions, captured_pulses, 64, Serial1);
|
||||
// Send the index falling signal opcode, which was right
|
||||
// at the start of this data xfer (we wait for index to fall
|
||||
// before we start reading
|
||||
reply_buffer[0] = 0xFF; // FLUXOP INDEX
|
||||
captured_pulses = floppy.capture_track(flux_transitions, sizeof(flux_transitions));
|
||||
Serial1.printf("Rev #%d captured %u pulses\n\r", revs, captured_pulses);
|
||||
//floppy.print_pulse_bins(flux_transitions, captured_pulses, 64, Serial1);
|
||||
// trim down extra long pulses
|
||||
for (uint32_t f=0; f<captured_pulses; f++) {
|
||||
if (flux_transitions[f] > 250) {
|
||||
flux_transitions[f] = 250;
|
||||
}
|
||||
}
|
||||
// Send the index opcode, which is right at the start of this data xfer
|
||||
reply_buffer[0] = 0xFF;
|
||||
reply_buffer[1] = 1; // index opcode
|
||||
reply_buffer[2] = 0x1; // 0 are special, so we send 1's to == 0
|
||||
reply_buffer[3] = 0x1; // ""
|
||||
reply_buffer[4] = 0x1; // ""
|
||||
reply_buffer[5] = 0x1; // ""
|
||||
reply_buffer[2] = 0x1;
|
||||
reply_buffer[3] = 0x1;
|
||||
reply_buffer[4] = 0x1;
|
||||
reply_buffer[5] = 0x1; // 0 are special, so we send 1 to == 0
|
||||
Serial.write(reply_buffer, 6);
|
||||
|
||||
uint8_t *flux_ptr = flux_transitions;
|
||||
// send all data until the flux transition
|
||||
while (index_offset > 0 && captured_pulses > 0) {
|
||||
int32_t to_send = min(captured_pulses, min(index_offset, (int32_t)256));
|
||||
while (captured_pulses) {
|
||||
uint32_t to_send = min(captured_pulses, (uint32_t)256);
|
||||
Serial.write(flux_ptr, to_send);
|
||||
//Serial1.println(to_send);
|
||||
flux_ptr += to_send;
|
||||
captured_pulses -= to_send;
|
||||
index_offset -= to_send;
|
||||
}
|
||||
|
||||
// there's more flux to follow this, so we need an index fluxop
|
||||
if (!revs || capture_ms) {
|
||||
// we interrupt this broadcast for a flux op index
|
||||
reply_buffer[0] = 0xFF; // FLUXOP INDEX
|
||||
reply_buffer[1] = 1; // index opcode
|
||||
reply_buffer[2] = 0x1; // 0 are special, so we send 1's to == 0
|
||||
reply_buffer[3] = 0x1; // ""
|
||||
reply_buffer[4] = 0x1; // ""
|
||||
reply_buffer[5] = 0x1; // ""
|
||||
Serial.write(reply_buffer, 6);
|
||||
}
|
||||
|
||||
// send remaining data until the flux transition
|
||||
if (capture_ms) {
|
||||
while (captured_pulses) {
|
||||
uint32_t to_send = min(captured_pulses, (uint32_t)256);
|
||||
Serial.write(flux_ptr, to_send);
|
||||
//Serial1.println(to_send);
|
||||
flux_ptr += to_send;
|
||||
captured_pulses -= to_send;
|
||||
}
|
||||
}
|
||||
}
|
||||
// flush input, to account for fluxengine bug
|
||||
while (Serial.available()) Serial.read();
|
||||
|
||||
// THE END
|
||||
Serial.write((byte)0);
|
||||
flux_status = true;
|
||||
}
|
||||
else if (cmd == GW_CMD_WRITEFLUX) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
Serial1.println("write flux");
|
||||
|
||||
if (floppy->get_write_protect()) {
|
||||
reply_buffer[i++] = GW_ACK_WRPROT;
|
||||
Serial.write(reply_buffer, 2);
|
||||
|
||||
} else {
|
||||
//uint8_t cue_at_index = cmd_buffer[2];
|
||||
//uint8_t terminate_at_index = cmd_buffer[3];
|
||||
// send an ACK to request data
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
|
||||
uint32_t fluxors = 0;
|
||||
uint8_t flux = 0xFF;
|
||||
while (flux && (fluxors < MAX_FLUX_PULSE_PER_TRACK)) {
|
||||
while (!Serial.available()) yield();
|
||||
flux = Serial.read();
|
||||
flux_transitions[fluxors++] = flux;
|
||||
}
|
||||
if (fluxors == MAX_FLUX_PULSE_PER_TRACK) {
|
||||
Serial1.println("*** FLUX OVERRUN ***");
|
||||
while (1) yield();
|
||||
}
|
||||
flux_status = floppy->write_track(flux_transitions, fluxors - 7, true);
|
||||
Serial1.println("wrote fluxors");
|
||||
Serial.write((byte)0);
|
||||
|
||||
}
|
||||
}
|
||||
else if (cmd == GW_CMD_GETFLUXSTATUS) {
|
||||
Serial1.println("get flux status");
|
||||
reply_buffer[i++] = flux_status ? GW_ACK_OK : GW_ACK_BADPIN;
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
else if (cmd == GW_CMD_SINKBYTES) {
|
||||
uint32_t numbytes = 0;
|
||||
uint32_t seed = 0;
|
||||
numbytes |= cmd_buffer[5];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[4];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[3];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[2];
|
||||
Serial1.printf("sink numbytes %d\n\r", numbytes);
|
||||
uint32_t numbytes = 0;
|
||||
uint32_t seed = 0;
|
||||
numbytes |= cmd_buffer[5];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[4];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[3];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[2];
|
||||
Serial1.printf("sink numbytes %d\n\r", numbytes);
|
||||
|
||||
seed |= cmd_buffer[9];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[8];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[7];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[6];
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
yield();
|
||||
bandwidth_timer = millis();
|
||||
transfered_bytes = numbytes;
|
||||
bytes_per_sec = numbytes;
|
||||
|
||||
while (numbytes != 0) {
|
||||
uint32_t avail = Serial.available();
|
||||
if (avail == 0) {
|
||||
//Serial1.print("-");
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
//Serial1.printf("%lu avail, ", avail);
|
||||
uint32_t to_read = min(numbytes, min((uint32_t)sizeof(reply_buffer), avail));
|
||||
//Serial1.printf("%lu to read, ", to_read);
|
||||
numbytes -= Serial.readBytes((char *)reply_buffer, to_read);
|
||||
//Serial1.printf("%lu remain\n\r", numbytes);
|
||||
}
|
||||
bandwidth_timer = millis() - bandwidth_timer;
|
||||
bytes_per_sec /= bandwidth_timer;
|
||||
bytes_per_sec *= 1000;
|
||||
Serial1.print("Done in ");
|
||||
Serial1.print(bandwidth_timer);
|
||||
Serial1.print(" ms, ");
|
||||
Serial1.print(bytes_per_sec);
|
||||
Serial1.println(" bytes per sec");
|
||||
Serial.write(GW_ACK_OK);
|
||||
yield();
|
||||
seed |= cmd_buffer[9];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[8];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[7];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[6];
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
yield();
|
||||
bandwidth_timer = millis();
|
||||
transfered_bytes = numbytes;
|
||||
bytes_per_sec = numbytes;
|
||||
|
||||
while (numbytes != 0) {
|
||||
uint32_t avail = Serial.available();
|
||||
if (avail == 0) {
|
||||
//Serial1.print("-");
|
||||
yield();
|
||||
continue;
|
||||
}
|
||||
//Serial1.printf("%lu avail, ", avail);
|
||||
uint32_t to_read = min(numbytes, min((uint32_t)sizeof(reply_buffer), avail));
|
||||
//Serial1.printf("%lu to read, ", to_read);
|
||||
numbytes -= Serial.readBytes((char *)reply_buffer, to_read);
|
||||
//Serial1.printf("%lu remain\n\r", numbytes);
|
||||
}
|
||||
bandwidth_timer = millis() - bandwidth_timer;
|
||||
bytes_per_sec /= bandwidth_timer;
|
||||
bytes_per_sec *= 1000;
|
||||
Serial1.print("Done in ");
|
||||
Serial1.print(bandwidth_timer);
|
||||
Serial1.print(" ms, ");
|
||||
Serial1.print(bytes_per_sec);
|
||||
Serial1.println(" bytes per sec");
|
||||
Serial.write(GW_ACK_OK);
|
||||
yield();
|
||||
}
|
||||
else if (cmd == GW_CMD_SOURCEBYTES) {
|
||||
uint32_t numbytes = 0;
|
||||
uint32_t seed = 0;
|
||||
numbytes |= cmd_buffer[5];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[4];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[3];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[2];
|
||||
Serial1.printf("source numbytes %d\n\r", numbytes);
|
||||
uint32_t numbytes = 0;
|
||||
uint32_t seed = 0;
|
||||
numbytes |= cmd_buffer[5];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[4];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[3];
|
||||
numbytes <<= 8;
|
||||
numbytes |= cmd_buffer[2];
|
||||
Serial1.printf("source numbytes %d\n\r", numbytes);
|
||||
|
||||
seed |= cmd_buffer[9];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[8];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[7];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[6];
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
yield();
|
||||
bandwidth_timer = millis();
|
||||
bytes_per_sec = numbytes;
|
||||
transfered_bytes = numbytes;
|
||||
seed |= cmd_buffer[9];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[8];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[7];
|
||||
seed <<= 8;
|
||||
seed |= cmd_buffer[6];
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial.write(reply_buffer, 2);
|
||||
yield();
|
||||
bandwidth_timer = millis();
|
||||
bytes_per_sec = numbytes;
|
||||
transfered_bytes = numbytes;
|
||||
|
||||
uint32_t randnum = seed;
|
||||
while (numbytes != 0) {
|
||||
uint32_t to_write = min(numbytes, sizeof(reply_buffer));
|
||||
// we dont write 'just anything'!
|
||||
for (uint32_t i = 0; i < to_write; i++) {
|
||||
uint32_t randnum = seed;
|
||||
while (numbytes != 0) {
|
||||
uint32_t to_write = min(numbytes, sizeof(reply_buffer));
|
||||
// we dont write 'just anything'!
|
||||
for (uint32_t i=0; i<to_write; i++) {
|
||||
reply_buffer[i] = randnum;
|
||||
if (randnum & 0x01) {
|
||||
randnum = (randnum >> 1) ^ 0x80000062;
|
||||
} else {
|
||||
randnum >>= 1;
|
||||
}
|
||||
}
|
||||
numbytes -= Serial.write(reply_buffer, to_write);
|
||||
}
|
||||
bandwidth_timer = millis() - bandwidth_timer;
|
||||
bytes_per_sec /= bandwidth_timer;
|
||||
bytes_per_sec *= 1000;
|
||||
Serial1.print("Done in ");
|
||||
Serial1.print(bandwidth_timer);
|
||||
Serial1.print(" ms, ");
|
||||
Serial1.print(bytes_per_sec);
|
||||
Serial1.println(" bytes per sec");
|
||||
}
|
||||
numbytes -= Serial.write(reply_buffer, to_write);
|
||||
}
|
||||
bandwidth_timer = millis() - bandwidth_timer;
|
||||
bytes_per_sec /= bandwidth_timer;
|
||||
bytes_per_sec *= 1000;
|
||||
Serial1.print("Done in ");
|
||||
Serial1.print(bandwidth_timer);
|
||||
Serial1.print(" ms, ");
|
||||
Serial1.print(bytes_per_sec);
|
||||
Serial1.println(" bytes per sec");
|
||||
} else if (cmd == GW_CMD_GETPIN) {
|
||||
uint32_t pin = cmd_buffer[2];
|
||||
Serial1.printf("getpin %d\n\r", pin);
|
||||
uint32_t pin = cmd_buffer[2];
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial1.printf("getpin %d\n\r", pin);
|
||||
|
||||
switch (pin) {
|
||||
case GW_CMD_GETPIN_TRACK0:
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
reply_buffer[i++] = floppy->get_track0_sense();
|
||||
break;
|
||||
switch(pin) {
|
||||
case 26:
|
||||
reply_buffer[i++] = digitalRead(TRK0_PIN);
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown pin, don't pretend we did it right
|
||||
reply_buffer[i++] = GW_ACK_BADPIN;
|
||||
reply_buffer[i++] = 0;
|
||||
}
|
||||
Serial.write(reply_buffer, i);
|
||||
} else if (cmd == GW_CMD_SETPIN) {
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
uint32_t pin = cmd_buffer[2];
|
||||
bool value = cmd_buffer[3];
|
||||
Serial1.printf("setpin %d to %d\n", pin, value);
|
||||
|
||||
switch (pin) {
|
||||
case GW_CMD_SETPIN_DENSITY:
|
||||
if (floppy->set_density(value)) {
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
} else {
|
||||
reply_buffer[i++] = GW_ACK_BADCMD;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown pin, don't pretend we did it right
|
||||
reply_buffer[i++] = GW_ACK_BADPIN;
|
||||
}
|
||||
|
||||
Serial.write(reply_buffer, i);
|
||||
|
||||
/********** unknown ! ********/
|
||||
}
|
||||
Serial.write(reply_buffer, i);
|
||||
/********** unknown ! ********/
|
||||
} else {
|
||||
reply_buffer[i++] = GW_ACK_BADCMD;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
needfloppy:
|
||||
Serial1.printf("Got command without valid floppy object\n", cmd);
|
||||
reply_buffer[i++] = GW_ACK_BADCMD;
|
||||
Serial.write(reply_buffer, 2);
|
||||
|
||||
//Serial1.println("cmd complete!");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
|
||||
|
||||
// If using SAMD51, turn on TINYUSB USB stack
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
MOTOR_PIN, DIR_PIN, STEP_PIN,
|
||||
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
|
||||
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
|
||||
|
||||
// You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t options!)
|
||||
Adafruit_MFM_Floppy mfm_floppy(&floppy, IBMPC360K);
|
||||
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(100);
|
||||
|
||||
delay(500); // wait for serial to open
|
||||
Serial.println("its time for a nice floppy transfer!");
|
||||
|
||||
floppy.debug_serial = &Serial;
|
||||
|
||||
if (! mfm_floppy.begin()) {
|
||||
Serial.println("Failed to spin up motor & find index pulse");
|
||||
while (1) yield();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t track = 0;
|
||||
bool head = 0;
|
||||
void loop() {
|
||||
int32_t captured_sectors;
|
||||
|
||||
Serial.printf("Seeking track %d head %d\n", track, head);
|
||||
captured_sectors = mfm_floppy.readTrack(track, head);
|
||||
if (captured_sectors < 0) {
|
||||
Serial.println("Failed to seek to track");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
Serial.printf("Captured %d sectors\n", captured_sectors);
|
||||
|
||||
Serial.print("Validity: ");
|
||||
for (size_t i = 0; i < mfm_floppy.sectors_per_track(); i++) {
|
||||
Serial.print(mfm_floppy.track_validity[i] ? "V" : "?");
|
||||
}
|
||||
Serial.print("\n");
|
||||
for (size_t sector = 0; sector < mfm_floppy.sectors_per_track(); sector++) {
|
||||
if (!mfm_floppy.track_validity[sector]) {
|
||||
continue; // skip it, not valid
|
||||
}
|
||||
for (size_t i = 0; i < 512; i += 16) {
|
||||
size_t addr = sector * 512 + i;
|
||||
Serial.printf("%08x", addr);
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
Serial.printf(" %02x", mfm_floppy.track_data[addr + j]);
|
||||
}
|
||||
Serial.print(" | ");
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
uint8_t d = mfm_floppy.track_data[addr + j];
|
||||
if (! isprint(d)) {
|
||||
d = ' ';
|
||||
}
|
||||
Serial.write(d);
|
||||
}
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// advance to next track
|
||||
if (!head) { // we were on side 0
|
||||
head = 1; // go to side 1
|
||||
} else { // we were on side 1?
|
||||
track = (track + 1) % mfm_floppy.tracks_per_side(); // next track!
|
||||
head = 0; // and side 0
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
// this example makes a lot of assumptions: MFM floppy which is already inserted
|
||||
// and only reading is supported - no write yet!
|
||||
|
||||
#include <Adafruit_Floppy.h>
|
||||
#include "Adafruit_TinyUSB.h"
|
||||
|
||||
Adafruit_USBD_MSC usb_msc;
|
||||
|
||||
// If using SAMD51, turn on TINYUSB USB stack
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN A4 // IDC 18
|
||||
#define STEP_PIN A5 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 6 // IDC 32
|
||||
#define READY_PIN 5 // IDC 34
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define DENSITY_PIN A0 // IDC 2
|
||||
#define INDEX_PIN A1 // IDC 8
|
||||
#define SELECT_PIN A2 // IDC 12
|
||||
#define MOTOR_PIN A3 // IDC 16
|
||||
#define DIR_PIN 24 // IDC 18
|
||||
#define STEP_PIN 25 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 12 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 11 // IDC 26
|
||||
#define PROT_PIN 10 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
|
||||
#define DENSITY_PIN 2 // IDC 2
|
||||
#define INDEX_PIN 3 // IDC 8
|
||||
#define SELECT_PIN 4 // IDC 12
|
||||
#define MOTOR_PIN 5 // IDC 16
|
||||
#define DIR_PIN 6 // IDC 18
|
||||
#define STEP_PIN 7 // IDC 20
|
||||
#define WRDATA_PIN 8 // IDC 22 (not used during read)
|
||||
#define WRGATE_PIN 9 // IDC 24 (not used during read)
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 12 // IDC 30
|
||||
#define SIDE_PIN 13 // IDC 32
|
||||
#define READY_PIN 14 // IDC 34
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
MOTOR_PIN, DIR_PIN, STEP_PIN,
|
||||
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
|
||||
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
|
||||
|
||||
// You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t options!)
|
||||
Adafruit_MFM_Floppy mfm_floppy(&floppy, IBMPC1440K);
|
||||
|
||||
|
||||
constexpr size_t SECTOR_SIZE = 512UL;
|
||||
int8_t last_track_read = -1; // last cached track
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
|
||||
#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040)
|
||||
// Manual begin() is required on core without built-in support for TinyUSB such as
|
||||
// - mbed rp2040
|
||||
TinyUSB_Device_Init(0);
|
||||
#endif
|
||||
|
||||
// Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively
|
||||
usb_msc.setID("Adafruit", "Floppy Mass Storage", "1.0");
|
||||
|
||||
// Set disk size
|
||||
usb_msc.setCapacity(mfm_floppy.sectors_per_track() * mfm_floppy.tracks_per_side() * FLOPPY_HEADS, SECTOR_SIZE);
|
||||
|
||||
// Set callback
|
||||
usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback, msc_flush_callback);
|
||||
|
||||
floppy.debug_serial = &Serial;
|
||||
floppy.begin();
|
||||
// Set Lun ready
|
||||
usb_msc.setUnitReady(true);
|
||||
Serial.println("Ready!");
|
||||
|
||||
usb_msc.begin();
|
||||
|
||||
if (! mfm_floppy.begin()) {
|
||||
Serial.println("Failed to spin up motor & find index pulse");
|
||||
while (1) yield();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Callback invoked when received READ10 command.
|
||||
// Copy disk's data to buffer (up to bufsize) and
|
||||
// return number of copied bytes (must be multiple of block size)
|
||||
int32_t msc_read_callback (uint32_t lba, void* buffer, uint32_t bufsize)
|
||||
{
|
||||
Serial.printf("read call back block %d size %d\n", lba, bufsize);
|
||||
|
||||
uint8_t track = lba / (2 * mfm_floppy.sectors_per_track());
|
||||
uint8_t head = (lba / mfm_floppy.sectors_per_track()) % 2;
|
||||
uint8_t subsector = lba % mfm_floppy.sectors_per_track();
|
||||
|
||||
uint8_t retries = 5;
|
||||
|
||||
for (int retry = 0; retry < retries; retry++) {
|
||||
if (((track * 2 + head) == last_track_read) && mfm_floppy.track_validity[subsector]) {
|
||||
// aah we've got it and its valid!
|
||||
Serial.println("OK!");
|
||||
memcpy(buffer, mfm_floppy.track_data + (subsector * SECTOR_SIZE), SECTOR_SIZE);
|
||||
return SECTOR_SIZE;
|
||||
}
|
||||
// ok so either its not valid, or we didn't read this track yet...
|
||||
int32_t tracks_read = mfm_floppy.readTrack(track, head);
|
||||
if (tracks_read < 0) {
|
||||
Serial.println("Failed to seek to track");
|
||||
return 0;
|
||||
}
|
||||
last_track_read = track * 2 + head;
|
||||
// we'll go again on the next round
|
||||
}
|
||||
Serial.println("subsector invalid CRC :(");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Callback invoked when received WRITE10 command.
|
||||
// Process data in buffer to disk's storage and
|
||||
// return number of written bytes (must be multiple of block size)
|
||||
int32_t msc_write_callback (uint32_t lba, uint8_t* buffer, uint32_t bufsize)
|
||||
{
|
||||
Serial.printf("write call back block %d size %d\n", lba, bufsize);
|
||||
// we dont actually write yet
|
||||
return bufsize;
|
||||
}
|
||||
|
||||
// Callback invoked when WRITE10 command is completed (status received and accepted by host).
|
||||
// used to flush any pending cache.
|
||||
void msc_flush_callback (void)
|
||||
{
|
||||
Serial.println("flush\n");
|
||||
// nothing to do
|
||||
}
|
||||
|
|
@ -7,4 +7,4 @@ paragraph=Adafruit's floppy disk drive interfacing library
|
|||
category=Communication
|
||||
url=https://github.com/adafruit/Adafruit_Floppy
|
||||
architectures=*
|
||||
depends=Adafruit BusIO, SdFat - Adafruit Fork
|
||||
depends=Adafruit BusIO
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,326 +0,0 @@
|
|||
#ifndef ADAFRUIT_FLOPPY_H
|
||||
#define ADAFRUIT_FLOPPY_H
|
||||
|
||||
/*! \mainpage
|
||||
*
|
||||
* \image html rabbit.png
|
||||
*
|
||||
* This is a helper library to abstract away interfacing with floppy disk drives
|
||||
* in a cross-platform and open source library.
|
||||
*
|
||||
* Adafruit Floppy is a project to make a flexible, full-stack, open source
|
||||
* hardware/software device for reading, archiving, accessing and duplicating
|
||||
* floppy disk media. It joins a family of open source hardware and software
|
||||
* such as greaseweazle and fluxengine, and increases the availability and
|
||||
* accessibility of floppy disk controllers.
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Adafruit_SPIDevice.h>
|
||||
// to implement SdFat Block Driver
|
||||
#include "SdFat.h"
|
||||
#include "SdFatConfig.h"
|
||||
|
||||
#define FLOPPY_IBMPC_HD_TRACKS 80
|
||||
#define FLOPPY_IBMPC_DD_TRACKS 40
|
||||
#define FLOPPY_HEADS 2
|
||||
|
||||
#define MFM_IBMPC1440K_SECTORS_PER_TRACK 18
|
||||
#define MFM_IBMPC360K_SECTORS_PER_TRACK 9
|
||||
#define MFM_BYTES_PER_SECTOR 512UL
|
||||
|
||||
#define STEP_OUT HIGH
|
||||
#define STEP_IN LOW
|
||||
#define MAX_FLUX_PULSE_PER_TRACK \
|
||||
(uint32_t)(500000UL / 5 * \
|
||||
1.5) // 500khz / 5 hz per track rotation, 1.5 rotations
|
||||
|
||||
#define BUSTYPE_IBMPC 1
|
||||
#define BUSTYPE_SHUGART 2
|
||||
|
||||
typedef enum {
|
||||
IBMPC1440K,
|
||||
IBMPC360K,
|
||||
} adafruit_floppy_disk_t;
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief An abstract base class for chattin with floppy drives
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class Adafruit_FloppyBase {
|
||||
protected:
|
||||
Adafruit_FloppyBase(int indexpin, int wrdatapin, int wrgatepin, int rddatapin,
|
||||
bool is_apple2 = false);
|
||||
|
||||
public:
|
||||
bool begin(void);
|
||||
virtual void end();
|
||||
|
||||
virtual void soft_reset(void);
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Whether to select this drive
|
||||
@param selected True to select/enable
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual void select(bool selected) = 0;
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Turn on or off the floppy motor, if on we wait till we get an
|
||||
index pulse!
|
||||
@param motor_on True to turn on motor, False to turn it off
|
||||
@returns False if turning motor on and no index pulse found, true
|
||||
otherwise
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool spin_motor(bool motor_on) = 0;
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Seek to the desired track, requires the motor to be spun up!
|
||||
@param track_num The track to step to
|
||||
@return True If we were able to get to the track location
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool goto_track(uint8_t track_num) = 0;
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Which head/side to read from
|
||||
@param head Head 0 or 1
|
||||
@return true if the head exists, false otherwise
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool side(uint8_t head) = 0;
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief The current track location, based on internal caching
|
||||
@return The cached track location
|
||||
@note Returns -1 if the track is not known.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual int8_t track(void) = 0;
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check whether the floppy in the drive is write protected
|
||||
@returns False if the floppy is writable, true otherwise
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool get_write_protect() = 0;
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check whether the track0 sensor is active
|
||||
@returns True if the track0 sensor is active, false otherwise
|
||||
@note On devices without a track0 sensor, this returns true when
|
||||
track()==0
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool get_track0_sense() = 0;
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Set the density for flux reading and writing
|
||||
@param high_density false for low density, true for high density
|
||||
@returns True if the drive interface supports the given density.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool set_density(bool high_density) = 0;
|
||||
|
||||
uint32_t read_track_mfm(uint8_t *sectors, size_t n_sectors,
|
||||
uint8_t *sector_validity, bool high_density = true);
|
||||
uint32_t capture_track(volatile uint8_t *pulses, uint32_t max_pulses,
|
||||
int32_t *falling_index_offset,
|
||||
bool store_greaseweazle = false,
|
||||
uint32_t capture_ms = 0)
|
||||
__attribute__((optimize("O3")));
|
||||
|
||||
bool write_track(uint8_t *pulses, uint32_t num_pulses,
|
||||
bool store_greaseweazle = false)
|
||||
__attribute__((optimize("O3")));
|
||||
void print_pulse_bins(uint8_t *pulses, uint32_t num_pulses,
|
||||
uint8_t max_bins = 64, bool is_gw_format = false);
|
||||
void print_pulses(uint8_t *pulses, uint32_t num_pulses,
|
||||
bool is_gw_format = false);
|
||||
uint32_t getSampleFrequency(void);
|
||||
|
||||
int8_t led_pin = LED_BUILTIN; ///< Debug LED output for tracing
|
||||
|
||||
uint16_t select_delay_us = 10; ///< delay after drive select (usecs)
|
||||
uint16_t step_delay_us = 10000; ///< delay between head steps (usecs)
|
||||
uint16_t settle_delay_ms = 15; ///< settle delay after seek (msecs)
|
||||
uint16_t motor_delay_ms = 1000; ///< delay after motor on (msecs)
|
||||
uint16_t watchdog_delay_ms =
|
||||
1000; ///< quiescent time until drives reset (msecs)
|
||||
uint8_t bus_type = BUSTYPE_IBMPC; ///< what kind of floppy drive we're using
|
||||
|
||||
Stream *debug_serial = NULL; ///< optional debug stream for serial output
|
||||
|
||||
protected:
|
||||
bool read_index();
|
||||
|
||||
private:
|
||||
#if defined(__SAMD51__)
|
||||
void deinit_capture(void);
|
||||
void enable_capture(void);
|
||||
|
||||
bool init_generate(void);
|
||||
void deinit_generate(void);
|
||||
void enable_generate(void);
|
||||
void disable_generate(void);
|
||||
#endif
|
||||
|
||||
bool start_polled_capture(void);
|
||||
void disable_capture(void);
|
||||
uint16_t sample_flux(bool &new_index_state);
|
||||
uint16_t sample_flux() {
|
||||
bool unused;
|
||||
return sample_flux(unused);
|
||||
}
|
||||
|
||||
bool init_capture(void);
|
||||
void enable_background_capture(void);
|
||||
void wait_for_index_pulse_low(void);
|
||||
|
||||
int8_t _indexpin, _wrdatapin, _wrgatepin, _rddatapin;
|
||||
bool _is_apple2;
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
BusIO_PortReg *indexPort;
|
||||
BusIO_PortMask indexMask;
|
||||
uint32_t dummyPort = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief A helper class for chattin with PC & Shugart floppy drives
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class Adafruit_Floppy : public Adafruit_FloppyBase {
|
||||
public:
|
||||
Adafruit_Floppy(int8_t densitypin, int8_t indexpin, int8_t selectpin,
|
||||
int8_t motorpin, int8_t directionpin, int8_t steppin,
|
||||
int8_t wrdatapin, int8_t wrgatepin, int8_t track0pin,
|
||||
int8_t protectpin, int8_t rddatapin, int8_t sidepin,
|
||||
int8_t readypin);
|
||||
void end() override;
|
||||
void soft_reset(void) override;
|
||||
|
||||
void select(bool selected) override;
|
||||
bool spin_motor(bool motor_on) override;
|
||||
bool goto_track(uint8_t track) override;
|
||||
bool side(uint8_t head) override;
|
||||
int8_t track(void) override;
|
||||
void step(bool dir, uint8_t times);
|
||||
bool set_density(bool high_density) override;
|
||||
bool get_write_protect() override;
|
||||
bool get_track0_sense() override;
|
||||
|
||||
private:
|
||||
// theres a lot of GPIO!
|
||||
int8_t _densitypin, _selectpin, _motorpin, _directionpin, _steppin,
|
||||
_track0pin, _protectpin, _sidepin, _readypin;
|
||||
|
||||
int8_t _track = -1;
|
||||
};
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief A helper class for chattin with Apple 2 floppy drives
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class Adafruit_Apple2Floppy : public Adafruit_FloppyBase {
|
||||
public:
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Constants for use with the step_mode method
|
||||
*/
|
||||
/**************************************************************************/
|
||||
enum StepMode {
|
||||
STEP_MODE_WHOLE, //< One step moves by one data track
|
||||
STEP_MODE_HALF, //< Two steps move by one data track
|
||||
STEP_MODE_QUARTER, //< Four steps move by one data track
|
||||
};
|
||||
|
||||
Adafruit_Apple2Floppy(int8_t indexpin, int8_t selectpin, int8_t phase1pin,
|
||||
int8_t phase2pin, int8_t phase3pin, int8_t phase4pin,
|
||||
int8_t wrdatapin, int8_t wrgatepin, int8_t protectpin,
|
||||
int8_t rddatapin);
|
||||
void end() override;
|
||||
void soft_reset(void) override;
|
||||
|
||||
void select(bool selected) override;
|
||||
bool spin_motor(bool motor_on) override;
|
||||
bool goto_track(uint8_t track) override;
|
||||
bool side(uint8_t head) override;
|
||||
int8_t track(void) override;
|
||||
bool set_density(bool high_density) override;
|
||||
bool get_write_protect() override;
|
||||
bool get_track0_sense() override;
|
||||
|
||||
int8_t quartertrack();
|
||||
bool goto_quartertrack(int);
|
||||
void step_mode(StepMode mode);
|
||||
|
||||
private:
|
||||
int _step_multiplier() const;
|
||||
// theres not much GPIO!
|
||||
int8_t _selectpin, _phase1pin, _phase2pin, _phase3pin, _phase4pin,
|
||||
_protectpin;
|
||||
int _quartertrack = -1;
|
||||
StepMode _step_mode = STEP_MODE_HALF;
|
||||
void _step(int dir, int times);
|
||||
};
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
This class adds support for the BaseBlockDriver interface to an MFM
|
||||
encoded floppy disk. This allows it to be used with SdFat's FatFileSystem
|
||||
class. or for a mass storage device
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class Adafruit_MFM_Floppy : public BaseBlockDriver {
|
||||
public:
|
||||
Adafruit_MFM_Floppy(Adafruit_Floppy *floppy,
|
||||
adafruit_floppy_disk_t format = IBMPC1440K);
|
||||
|
||||
bool begin(void);
|
||||
bool end(void);
|
||||
|
||||
uint32_t size(void);
|
||||
int32_t readTrack(uint8_t track, bool head);
|
||||
|
||||
/**! @brief The expected number of sectors per track in this format
|
||||
@returns The number of sectors per track */
|
||||
uint8_t sectors_per_track(void) { return _sectors_per_track; }
|
||||
/**! @brief The expected number of tracks per side in this format
|
||||
@returns The number of tracks per side */
|
||||
uint8_t tracks_per_side(void) { return _tracks_per_side; }
|
||||
|
||||
//------------- SdFat BaseBlockDRiver API -------------//
|
||||
virtual bool readBlock(uint32_t block, uint8_t *dst);
|
||||
virtual bool writeBlock(uint32_t block, const uint8_t *src);
|
||||
virtual bool syncBlocks();
|
||||
virtual bool readBlocks(uint32_t block, uint8_t *dst, size_t nb);
|
||||
virtual bool writeBlocks(uint32_t block, const uint8_t *src, size_t nb);
|
||||
|
||||
/**! The raw byte decoded data from the last track read */
|
||||
uint8_t track_data[MFM_IBMPC1440K_SECTORS_PER_TRACK * MFM_BYTES_PER_SECTOR];
|
||||
|
||||
/**! Which tracks from the last track-read were valid MFM/CRC! */
|
||||
uint8_t track_validity[MFM_IBMPC1440K_SECTORS_PER_TRACK];
|
||||
|
||||
private:
|
||||
#if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040)
|
||||
uint16_t _last;
|
||||
#endif
|
||||
uint8_t _sectors_per_track = 0;
|
||||
uint8_t _tracks_per_side = 0;
|
||||
int8_t _last_track_read = -1; // last cached track
|
||||
bool _high_density = true;
|
||||
Adafruit_Floppy *_floppy = NULL;
|
||||
adafruit_floppy_disk_t _format = IBMPC1440K;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Instantiate an MFM-formatted floppy
|
||||
@param floppy An Adafruit_Floppy object that has the pins defined
|
||||
@param format What kind of format we will be parsing out - we DO NOT
|
||||
autodetect!
|
||||
*/
|
||||
/**************************************************************************/
|
||||
Adafruit_MFM_Floppy::Adafruit_MFM_Floppy(Adafruit_Floppy *floppy,
|
||||
adafruit_floppy_disk_t format) {
|
||||
_floppy = floppy;
|
||||
_format = format;
|
||||
|
||||
// different formats have different 'hardcoded' sectors and tracks
|
||||
if (_format == IBMPC1440K) {
|
||||
_sectors_per_track = MFM_IBMPC1440K_SECTORS_PER_TRACK;
|
||||
_tracks_per_side = FLOPPY_IBMPC_HD_TRACKS;
|
||||
_high_density = true;
|
||||
} else if (_format == IBMPC360K) {
|
||||
_sectors_per_track = MFM_IBMPC360K_SECTORS_PER_TRACK;
|
||||
_tracks_per_side = FLOPPY_IBMPC_DD_TRACKS;
|
||||
_high_density = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Initialize and spin up the floppy drive
|
||||
@returns True if we were able to spin up and detect an index track
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::begin(void) {
|
||||
if (!_floppy)
|
||||
return false;
|
||||
_floppy->begin();
|
||||
|
||||
// now's the time to tweak settings
|
||||
if (_format == IBMPC360K) {
|
||||
_floppy->step_delay_us = 65000UL; // lets make it max 65ms not 10ms?
|
||||
_floppy->settle_delay_ms = 50; // 50ms not 15
|
||||
}
|
||||
|
||||
_floppy->select(true);
|
||||
|
||||
return _floppy->spin_motor(true);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Spin down and deselect the motor and drive
|
||||
@returns True always
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::end(void) {
|
||||
_floppy->spin_motor(false);
|
||||
_floppy->select(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Quick calculator for expected max capacity
|
||||
@returns Size of the drive in bytes
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint32_t Adafruit_MFM_Floppy::size(void) {
|
||||
return (uint32_t)_tracks_per_side * FLOPPY_HEADS * _sectors_per_track *
|
||||
MFM_BYTES_PER_SECTOR;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Read one track's worth of data and MFM decode it
|
||||
@param track track number, 0 to whatever is the max tracks for the given
|
||||
@param head which side to read, false for side 1, true for side 2
|
||||
format during instantiation (e.g. 40 for DD, 80 for HD)
|
||||
@returns Number of sectors captured, or -1 if we couldn't seek
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int32_t Adafruit_MFM_Floppy::readTrack(uint8_t track, bool head) {
|
||||
|
||||
// Serial.printf("\tSeeking track %d head %d...", track, head);
|
||||
if (!_floppy->goto_track(track)) {
|
||||
// Serial.println("failed to seek to track");
|
||||
return -1;
|
||||
}
|
||||
_floppy->side(head);
|
||||
// Serial.println("done!");
|
||||
uint32_t captured_sectors = _floppy->read_track_mfm(
|
||||
track_data, _sectors_per_track, track_validity, _high_density);
|
||||
/*
|
||||
Serial.print("Captured %d sectors", captured_sectors);
|
||||
|
||||
Serial.print("Validity: ");
|
||||
for(size_t i=0; i<MFM_SECTORS_PER_TRACK; i++) {
|
||||
Serial.print(track_validity[i] ? "V" : "?");
|
||||
}
|
||||
*/
|
||||
return captured_sectors;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// SdFat BaseBlockDriver API
|
||||
// A block is 512 bytes
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Read a 512 byte block of data, may used cached data
|
||||
@param block Block number, will be split into head and track based on
|
||||
expected formatting
|
||||
@param dst Destination buffer
|
||||
@returns True on success
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::readBlock(uint32_t block, uint8_t *dst) {
|
||||
uint8_t track = block / (FLOPPY_HEADS * _sectors_per_track);
|
||||
uint8_t head = (block / _sectors_per_track) % FLOPPY_HEADS;
|
||||
uint8_t subsector = block % _sectors_per_track;
|
||||
|
||||
// Serial.printf("\tRead request block %d\n", block);
|
||||
if ((track * FLOPPY_HEADS + head) != _last_track_read) {
|
||||
// oof it is not cached!
|
||||
|
||||
if (readTrack(track, head) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_last_track_read = track * FLOPPY_HEADS + head;
|
||||
}
|
||||
|
||||
if (!track_validity[subsector]) {
|
||||
// Serial.println("subsector invalid");
|
||||
return false;
|
||||
}
|
||||
// Serial.println("OK!");
|
||||
memcpy(dst, track_data + (subsector * MFM_BYTES_PER_SECTOR),
|
||||
MFM_BYTES_PER_SECTOR);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Read multiple 512 byte block of data, may used cached data
|
||||
@param block Starting block number, will be split into head and track based
|
||||
on expected formatting
|
||||
@param dst Destination buffer
|
||||
@param nb Number of blocks to read
|
||||
@returns True on success
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::readBlocks(uint32_t block, uint8_t *dst, size_t nb) {
|
||||
// read each block one by one
|
||||
for (size_t blocknum = 0; blocknum < nb; blocknum++) {
|
||||
if (!readBlock(block + blocknum, dst + (blocknum * MFM_BYTES_PER_SECTOR)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Write a 512 byte block of data NOT IMPLEMENTED YET
|
||||
@param block Block number, will be split into head and track based on
|
||||
expected formatting
|
||||
@param src Source buffer
|
||||
@returns True on success, false if failed or unimplemented
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::writeBlock(uint32_t block, const uint8_t *src) {
|
||||
Serial.printf("Writing block %d\n", block);
|
||||
(void *)src;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Write multiple 512 byte blocks of data NOT IMPLEMENTED YET
|
||||
@param block Starting lock number, will be split into head and track based
|
||||
on expected formatting
|
||||
@param src Source buffer
|
||||
@param nb Number of consecutive blocks to write
|
||||
@returns True on success, false if failed or unimplemented
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::writeBlocks(uint32_t block, const uint8_t *src,
|
||||
size_t nb) {
|
||||
Serial.printf("Writing %d blocks %d\n", nb, block);
|
||||
(void *)src;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Sync written blocks NOT IMPLEMENTED YET
|
||||
@returns True on success, false if failed or unimplemented
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::syncBlocks() { return false; }
|
||||
424
src/arch_rp2.cpp
424
src/arch_rp2.cpp
|
|
@ -1,424 +0,0 @@
|
|||
#if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040)
|
||||
#include "arch_rp2.h"
|
||||
#include "greasepack.h"
|
||||
#include <Arduino.h>
|
||||
#include <hardware/clocks.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/pio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static const int fluxread_sideset_pin_count = 0;
|
||||
static const bool fluxread_sideset_enable = 0;
|
||||
static const uint16_t fluxread[] = {
|
||||
// ; Count flux pulses and watch for index pin
|
||||
// ; flux input is the 'jmp pin'. index is "pin zero".
|
||||
// ; Counts are in units 3 / F_pio, so e.g., at 30MHz 1 count = 0.1us
|
||||
// ; Count down while waiting for the counter to go HIGH
|
||||
// ; The only counting is down, so C code will just have to negate the
|
||||
// count!
|
||||
// ; Each 'wait one' loop takes 3 instruction-times
|
||||
// wait_one:
|
||||
0x0041, // jmp x--, wait_one_next ; acts as a non-conditional decrement
|
||||
// of x
|
||||
// wait_one_next:
|
||||
0x00c3, // jmp pin wait_zero
|
||||
0x0000, // jmp wait_one
|
||||
// ; Each 'wait zero' loop takes 3 instruction-times, needing one
|
||||
// instruction delay
|
||||
// ; (it has to match the 'wait one' timing exactly)
|
||||
// wait_zero:
|
||||
0x0044, // jmp x--, wait_zero_next ; acts as a non-conditional decrement
|
||||
// of x
|
||||
// wait_zero_next:
|
||||
0x01c3, // jmp pin wait_zero [1]
|
||||
// ; Top bit is index status, bottom 15 bits are inverse of counts
|
||||
// ; Combined FIFO gives 16 entries (8 32-bit entries) so with the
|
||||
// ; smallest plausible pulse of 2us there are 250 CPU cycles available
|
||||
// @125MHz
|
||||
0x4001, // in pins, 1
|
||||
0x402f, // in x, 15
|
||||
// ; Threee cycles for the end of loop, so we need to decrement x to make
|
||||
// everything
|
||||
// ; come out right. This has constant timing whether we actually jump back
|
||||
// vs wrapping.
|
||||
0x0040, // jmp x--, wait_one
|
||||
};
|
||||
static const pio_program_t fluxread_struct = {.instructions = fluxread,
|
||||
.length = sizeof(fluxread) /
|
||||
sizeof(fluxread[0]),
|
||||
.origin = -1};
|
||||
|
||||
static const int fluxwrite_sideset_pin_count = 0;
|
||||
static const bool fluxwrite_sideset_enable = 0;
|
||||
static const uint16_t fluxwrite[] = {
|
||||
// loop_flux:
|
||||
0xe000, // set pins, 0 ; drive pin low
|
||||
0x6030, // out x, 16 ; get the next timing pulse information, may block
|
||||
// ;; output the fixed on time. 16 is about 0.67us.
|
||||
// ;; note that wdc1772 has varying low times, from 570 to 1380us
|
||||
0xae42, // nop [14]
|
||||
0xe001, // set pins, 1 ; drive pin high
|
||||
// loop_high:
|
||||
0x0044, // jmp x--, loop_high
|
||||
0x0000, // jmp loop_flux
|
||||
};
|
||||
static const pio_program_t fluxwrite_struct = {.instructions = fluxwrite,
|
||||
.length = sizeof(fluxwrite) /
|
||||
sizeof(fluxwrite[0]),
|
||||
.origin = -1};
|
||||
|
||||
static const int fluxwrite_apple2_sideset_pin_count = 0;
|
||||
static const bool fluxwrite_apple2_sideset_enable = 0;
|
||||
static const uint16_t fluxwrite_apple2[] = {
|
||||
0x6030, // out x, 16 ; get the next timing pulse information, may block
|
||||
0xe001, // set pins, 1 ; drive pin high
|
||||
0xb042, // nop [16]
|
||||
// loop_high:
|
||||
0x0043, // jmp x--, loop_high
|
||||
0x6030, // out x, 16 ; get the next timing pulse information, may block
|
||||
0xe000, // set pins, 0 ; drive pin low
|
||||
0xb042, // nop [16]
|
||||
// loop_low:
|
||||
0x0047, // jmp x--, loop_low
|
||||
};
|
||||
static const pio_program_t fluxwrite_apple2_struct = {
|
||||
.instructions = fluxwrite_apple2,
|
||||
.length = sizeof(fluxwrite_apple2) / sizeof(fluxwrite_apple2[0]),
|
||||
.origin = -1};
|
||||
|
||||
typedef struct floppy_singleton {
|
||||
PIO pio;
|
||||
const pio_program_t *program;
|
||||
unsigned sm;
|
||||
uint16_t offset;
|
||||
uint16_t half;
|
||||
} floppy_singleton_t;
|
||||
|
||||
static floppy_singleton_t g_reader, g_writer;
|
||||
|
||||
const static PIO pio_instances[2] = {pio0, pio1};
|
||||
static bool allocate_pio_set_program(floppy_singleton_t *info,
|
||||
const pio_program_t *program) {
|
||||
memset(info, 0, sizeof(*info));
|
||||
for (size_t i = 0; i < NUM_PIOS; i++) {
|
||||
PIO pio = pio_instances[i];
|
||||
if (!pio_can_add_program(pio, program)) {
|
||||
continue;
|
||||
}
|
||||
int sm = pio_claim_unused_sm(pio, false);
|
||||
if (sm != -1) {
|
||||
info->pio = pio;
|
||||
info->sm = sm;
|
||||
// cannot fail, we asked nicely already
|
||||
info->offset = pio_add_program(pio, program);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool init_capture(int index_pin, int read_pin) {
|
||||
if (g_reader.pio) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allocate_pio_set_program(&g_reader, &fluxread_struct)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gpio_pull_up(index_pin);
|
||||
|
||||
pio_sm_config c = {0, 0, 0};
|
||||
sm_config_set_wrap(&c, g_reader.offset,
|
||||
g_reader.offset + fluxread_struct.length - 1);
|
||||
sm_config_set_jmp_pin(&c, read_pin);
|
||||
sm_config_set_in_pins(&c, index_pin);
|
||||
sm_config_set_in_shift(&c, true, true, 32);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
|
||||
pio_sm_set_pins_with_mask(g_reader.pio, g_reader.sm, 1 << read_pin,
|
||||
1 << read_pin);
|
||||
float div = (float)clock_get_hz(clk_sys) / (3 * 24e6);
|
||||
sm_config_set_clkdiv(&c, div); // 72MHz capture clock / 24MHz sample rate
|
||||
|
||||
pio_sm_init(g_reader.pio, g_reader.sm, g_reader.offset, &c);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void start_common() {
|
||||
pio_sm_exec(g_reader.pio, g_reader.sm, g_reader.offset);
|
||||
pio_sm_restart(g_reader.pio, g_reader.sm);
|
||||
}
|
||||
|
||||
static bool data_available() {
|
||||
return g_reader.half || !pio_sm_is_rx_fifo_empty(g_reader.pio, g_reader.sm);
|
||||
}
|
||||
|
||||
static uint16_t read_fifo() {
|
||||
if (g_reader.half) {
|
||||
uint16_t result = g_reader.half;
|
||||
g_reader.half = 0;
|
||||
return result;
|
||||
}
|
||||
uint32_t value = pio_sm_get_blocking(g_reader.pio, g_reader.sm);
|
||||
g_reader.half = value >> 16;
|
||||
return value & 0xffff;
|
||||
}
|
||||
|
||||
static void disable_capture(void) {
|
||||
pio_sm_set_enabled(g_reader.pio, g_reader.sm, false);
|
||||
}
|
||||
|
||||
static void free_capture(void) {
|
||||
if (!g_reader.pio) {
|
||||
// already deinit
|
||||
return;
|
||||
}
|
||||
disable_capture();
|
||||
pio_sm_unclaim(g_reader.pio, g_reader.sm);
|
||||
pio_remove_program(g_reader.pio, &fluxread_struct, g_reader.offset);
|
||||
memset(&g_reader, 0, sizeof(g_reader));
|
||||
}
|
||||
|
||||
static uint8_t *capture_foreground(int index_pin, uint8_t *start, uint8_t *end,
|
||||
int32_t *falling_index_offset,
|
||||
bool store_greaseweazle,
|
||||
uint32_t capture_counts) {
|
||||
uint8_t *ptr = start;
|
||||
if (falling_index_offset) {
|
||||
*falling_index_offset = -1;
|
||||
}
|
||||
start_common();
|
||||
|
||||
// wait for a falling edge of index pin, then enable the capture peripheral
|
||||
while (!gpio_get(index_pin)) { /* NOTHING */
|
||||
}
|
||||
while (gpio_get(index_pin)) { /* NOTHING */
|
||||
}
|
||||
|
||||
uint32_t total_counts = 0;
|
||||
|
||||
noInterrupts();
|
||||
pio_sm_clear_fifos(g_reader.pio, g_reader.sm);
|
||||
pio_sm_set_enabled(g_reader.pio, g_reader.sm, true);
|
||||
int last = read_fifo();
|
||||
bool last_index = gpio_get(index_pin);
|
||||
while (ptr != end) {
|
||||
/* Handle index */
|
||||
bool now_index = gpio_get(index_pin);
|
||||
|
||||
if (!now_index && last_index) {
|
||||
if (falling_index_offset) {
|
||||
*falling_index_offset = ptr - start;
|
||||
if (!capture_counts) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
last_index = now_index;
|
||||
|
||||
if (!data_available) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int data = read_fifo();
|
||||
int delta = last - data;
|
||||
if (delta < 0)
|
||||
delta += 65536;
|
||||
delta /= 2;
|
||||
|
||||
last = data;
|
||||
total_counts += delta;
|
||||
if (store_greaseweazle) {
|
||||
ptr = greasepack(ptr, end, delta);
|
||||
} else {
|
||||
*ptr++ = delta > 255 ? 255 : delta;
|
||||
}
|
||||
if (capture_counts != 0 && total_counts >= capture_counts) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
interrupts();
|
||||
|
||||
disable_capture();
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void enable_capture_fifo() { start_common(); }
|
||||
|
||||
static bool init_write(int wrdata_pin, bool is_apple2) {
|
||||
if (g_writer.pio) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const pio_program_t *program =
|
||||
is_apple2 ? &fluxwrite_apple2_struct : &fluxwrite_struct;
|
||||
g_writer.program = program;
|
||||
|
||||
if (!allocate_pio_set_program(&g_writer, program)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t wrdata_bit = 1u << wrdata_pin;
|
||||
|
||||
pio_gpio_init(g_writer.pio, wrdata_pin);
|
||||
|
||||
pio_sm_set_pindirs_with_mask(g_writer.pio, g_writer.sm, wrdata_bit,
|
||||
wrdata_bit);
|
||||
pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, wrdata_bit);
|
||||
pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, 0, wrdata_bit);
|
||||
pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, wrdata_bit);
|
||||
|
||||
pio_sm_config c{};
|
||||
sm_config_set_wrap(&c, g_writer.offset,
|
||||
g_writer.offset + program->length - 1);
|
||||
sm_config_set_set_pins(&c, wrdata_pin, 1);
|
||||
sm_config_set_out_shift(&c, true, true, 16);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
float div = (float)clock_get_hz(clk_sys) / (24e6);
|
||||
sm_config_set_clkdiv(&c, div); // 24MHz output clock
|
||||
|
||||
pio_sm_init(g_writer.pio, g_writer.sm, g_writer.offset, &c);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void enable_write() {}
|
||||
|
||||
#define OVERHEAD (20) // minimum pulse length due to PIO overhead, about 0.833us
|
||||
static void write_fifo(unsigned value) {
|
||||
if (value < OVERHEAD) {
|
||||
value = 1;
|
||||
} else {
|
||||
value -= OVERHEAD;
|
||||
if (value > 0xffff)
|
||||
value = 0xffff;
|
||||
}
|
||||
pio_sm_put_blocking(g_writer.pio, g_writer.sm, value);
|
||||
}
|
||||
|
||||
static void disable_write() {
|
||||
pio_sm_set_enabled(g_writer.pio, g_writer.sm, false);
|
||||
}
|
||||
|
||||
static void write_foreground(int index_pin, int wrgate_pin, uint8_t *pulses,
|
||||
uint8_t *pulse_end, bool store_greaseweazle) {
|
||||
// don't start during an index pulse
|
||||
while (!gpio_get(index_pin)) { /* NOTHING */
|
||||
}
|
||||
|
||||
// wait for falling edge of index pin
|
||||
while (gpio_get(index_pin)) { /* NOTHING */
|
||||
}
|
||||
|
||||
pinMode(wrgate_pin, OUTPUT);
|
||||
digitalWrite(wrgate_pin, LOW);
|
||||
|
||||
noInterrupts();
|
||||
pio_sm_set_enabled(g_writer.pio, g_writer.sm, false);
|
||||
pio_sm_clear_fifos(g_writer.pio, g_writer.sm);
|
||||
pio_sm_exec(g_writer.pio, g_writer.sm, g_writer.offset);
|
||||
while (!pio_sm_is_tx_fifo_full(g_writer.pio, g_writer.sm)) {
|
||||
unsigned value = greaseunpack(&pulses, pulse_end, store_greaseweazle);
|
||||
value = (value < OVERHEAD) ? 1 : value - OVERHEAD;
|
||||
pio_sm_put_blocking(g_writer.pio, g_writer.sm, value);
|
||||
}
|
||||
pio_sm_set_enabled(g_writer.pio, g_writer.sm, true);
|
||||
|
||||
bool old_index_state = false;
|
||||
int i = 0;
|
||||
while (pulses != pulse_end) {
|
||||
bool index_state = gpio_get(index_pin);
|
||||
if (old_index_state && !index_state) {
|
||||
// falling edge of index pin
|
||||
break;
|
||||
}
|
||||
while (!pio_sm_is_tx_fifo_full(g_writer.pio, g_writer.sm)) {
|
||||
unsigned value = greaseunpack(&pulses, pulse_end, store_greaseweazle);
|
||||
value = (value < OVERHEAD) ? 1 : value - OVERHEAD;
|
||||
pio_sm_put_blocking(g_writer.pio, g_writer.sm, value);
|
||||
}
|
||||
old_index_state = index_state;
|
||||
}
|
||||
interrupts();
|
||||
|
||||
pio_sm_set_enabled(g_writer.pio, g_writer.sm, false);
|
||||
pinMode(wrgate_pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
static void free_write() {
|
||||
if (!g_writer.pio) {
|
||||
// already deinit
|
||||
return;
|
||||
}
|
||||
disable_write();
|
||||
pio_sm_unclaim(g_writer.pio, g_writer.sm);
|
||||
pio_remove_program(g_writer.pio, g_writer.program, g_writer.offset);
|
||||
memset(&g_writer, 0, sizeof(g_writer));
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <Adafruit_Floppy.h>
|
||||
|
||||
uint32_t rp2040_flux_capture(int index_pin, int rdpin, volatile uint8_t *pulses,
|
||||
volatile uint8_t *pulse_end,
|
||||
int32_t *falling_index_offset,
|
||||
bool store_greaseweazle, uint32_t capture_counts) {
|
||||
if (!init_capture(index_pin, rdpin)) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t result =
|
||||
capture_foreground(index_pin, (uint8_t *)pulses, (uint8_t *)pulse_end,
|
||||
falling_index_offset, store_greaseweazle,
|
||||
capture_counts) -
|
||||
pulses;
|
||||
free_capture();
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned _last = ~0u;
|
||||
bool Adafruit_FloppyBase::init_capture(void) {
|
||||
_last = ~0u;
|
||||
return ::init_capture(_indexpin, _rddatapin);
|
||||
}
|
||||
|
||||
bool Adafruit_FloppyBase::start_polled_capture(void) {
|
||||
if (!init_capture())
|
||||
return false;
|
||||
start_common();
|
||||
pio_sm_set_enabled(g_reader.pio, g_reader.sm, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Adafruit_FloppyBase::disable_capture(void) { ::disable_capture(); }
|
||||
|
||||
uint16_t mfm_io_sample_flux(bool *index) {
|
||||
if (_last == ~0u) {
|
||||
_last = read_fifo();
|
||||
}
|
||||
int data = read_fifo();
|
||||
int delta = _last - data;
|
||||
_last = data;
|
||||
if (delta < 0)
|
||||
delta += 65536;
|
||||
*index = data & 1;
|
||||
return delta / 2;
|
||||
}
|
||||
|
||||
bool rp2040_flux_write(int index_pin, int wrgate_pin, int wrdata_pin,
|
||||
uint8_t *pulses, uint8_t *pulse_end,
|
||||
bool store_greaseweazle, bool is_apple2) {
|
||||
if (!init_write(wrdata_pin, is_apple2)) {
|
||||
return false;
|
||||
}
|
||||
write_foreground(index_pin, wrgate_pin, (uint8_t *)pulses,
|
||||
(uint8_t *)pulse_end, store_greaseweazle);
|
||||
free_write();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#pragma once
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
#define read_index_fast() gpio_get(_indexpin)
|
||||
#define read_data() gpio_get(_rddatapin)
|
||||
#define set_debug_led() gpio_put(led_pin, 1)
|
||||
#define clr_debug_led() gpio_put(led_pin, 0)
|
||||
#define set_write() gpio_put(_wrdatapin, 1)
|
||||
#define clr_write() gpio_put(_wrdatapin, 0)
|
||||
#include <stdint.h>
|
||||
extern uint32_t
|
||||
rp2040_flux_capture(int indexpin, int rdpin, volatile uint8_t *pulses,
|
||||
volatile uint8_t *end, int32_t *falling_index_offset,
|
||||
bool store_greaseweazle, uint32_t capture_counts);
|
||||
extern bool rp2040_flux_write(int index_pin, int wrgate_pin, int wrdata_pin,
|
||||
uint8_t *pulses, uint8_t *pulse_end,
|
||||
bool store_greaseweazel, bool is_apple2);
|
||||
#endif
|
||||
|
|
@ -1,440 +0,0 @@
|
|||
#if defined(__SAMD51__)
|
||||
#include <Adafruit_Floppy.h>
|
||||
#include <wiring_private.h> // pinPeripheral() func
|
||||
|
||||
static const struct {
|
||||
Tc *tc; // -> Timer/Counter base address
|
||||
IRQn_Type IRQn; // Interrupt number
|
||||
int gclk; // GCLK ID
|
||||
int evu; // EVSYS user ID
|
||||
} tcList[] = {{TC0, TC0_IRQn, TC0_GCLK_ID, EVSYS_ID_USER_TC0_EVU},
|
||||
{TC1, TC1_IRQn, TC1_GCLK_ID, EVSYS_ID_USER_TC1_EVU},
|
||||
{TC2, TC2_IRQn, TC2_GCLK_ID, EVSYS_ID_USER_TC2_EVU},
|
||||
{TC3, TC3_IRQn, TC3_GCLK_ID, EVSYS_ID_USER_TC3_EVU},
|
||||
#ifdef TC4
|
||||
{TC4, TC4_IRQn, TC4_GCLK_ID, EVSYS_ID_USER_TC4_EVU},
|
||||
#endif
|
||||
#ifdef TC5
|
||||
{TC5, TC5_IRQn, TC5_GCLK_ID, EVSYS_ID_USER_TC5_EVU},
|
||||
#endif
|
||||
#ifdef TC6
|
||||
{TC6, TC6_IRQn, TC6_GCLK_ID, EVSYS_ID_USER_TC6_EVU},
|
||||
#endif
|
||||
#ifdef TC7
|
||||
{TC7, TC7_IRQn, TC7_GCLK_ID, EVSYS_ID_USER_TC7_EVU}
|
||||
#endif
|
||||
};
|
||||
|
||||
Tc *theReadTimer = NULL;
|
||||
Tc *theWriteTimer = NULL;
|
||||
|
||||
int g_cap_tc_num;
|
||||
volatile uint8_t *g_flux_pulses = NULL;
|
||||
volatile uint32_t g_max_pulses = 0;
|
||||
volatile uint32_t g_num_pulses = 0;
|
||||
volatile bool g_store_greaseweazle = false;
|
||||
volatile uint8_t g_timing_div = 2;
|
||||
volatile bool g_writing_pulses = false;
|
||||
|
||||
void FLOPPY_TC_HANDLER() // Interrupt Service Routine (ISR) for timer TCx
|
||||
{
|
||||
// Check for match counter 0 (MC0) interrupt
|
||||
if (theReadTimer && theReadTimer->COUNT16.INTFLAG.bit.MC0) {
|
||||
uint16_t ticks =
|
||||
theReadTimer->COUNT16.CC[0].reg / g_timing_div; // Copy the period
|
||||
if (ticks == 0) {
|
||||
// dont do something if its 0 - thats wierd!
|
||||
} else if (ticks < 250 || !g_store_greaseweazle) {
|
||||
// 1-249: One byte.
|
||||
g_flux_pulses[g_num_pulses++] = min(249, ticks);
|
||||
} else {
|
||||
uint8_t high = (ticks - 250) / 255;
|
||||
if (high < 5) {
|
||||
// 250-1524: Two bytes.
|
||||
g_flux_pulses[g_num_pulses++] = 250 + high;
|
||||
g_flux_pulses[g_num_pulses++] = 1 + ((ticks - 250) % 255);
|
||||
} else {
|
||||
// TODO MEME FIX!
|
||||
/* 1525-(2^28-1): Seven bytes.
|
||||
g_flux_pulses[g_num_pulses++] = 0xff;
|
||||
g_flux_pulses[g_num_pulses++] = FLUXOP_SPACE;
|
||||
_write_28bit(ticks - 249);
|
||||
u_buf[U_MASK(u_prod++)] = 249;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for match counter 1 (MC1) interrupt
|
||||
if (theReadTimer && theReadTimer->COUNT16.INTFLAG.bit.MC1) {
|
||||
uint16_t pulsewidth =
|
||||
theReadTimer->COUNT16.CC[1].reg; // Copy the pulse width, DONT REMOVE
|
||||
(void)pulsewidth;
|
||||
}
|
||||
|
||||
if (theWriteTimer && theWriteTimer->COUNT16.INTFLAG.bit.MC0) {
|
||||
// Because this uses CCBUF registers (updating CC on next timer rollover),
|
||||
// the pulse_index check here looks odd, checking both under AND over
|
||||
// num_pulses This is normal and OK and intended, because of the
|
||||
// last-pulse-out case where we need one extra invocation of the interrupt
|
||||
// to allow that pulse out before resetting PWM to steady high and disabling
|
||||
// the interrupt.
|
||||
if (g_num_pulses < g_max_pulses) {
|
||||
// Set period for next pulse
|
||||
|
||||
uint16_t ticks = g_flux_pulses[g_num_pulses];
|
||||
if (ticks == 0) {
|
||||
// dont do something if its 0 - thats wierd!
|
||||
} else if (ticks < 250 || !g_store_greaseweazle) {
|
||||
// 1-249: One byte.
|
||||
ticks = min(249, ticks);
|
||||
} else {
|
||||
// 250-1524: Two bytes.
|
||||
uint16_t high = ((ticks - 250) + 1) * 255;
|
||||
g_num_pulses++;
|
||||
ticks = high + g_flux_pulses[g_num_pulses];
|
||||
}
|
||||
theWriteTimer->COUNT16.CCBUF[0].reg = ticks;
|
||||
} else if (g_num_pulses > g_max_pulses) {
|
||||
// Last pulse out was allowed its one extra PWM cycle, done now
|
||||
theWriteTimer->COUNT16.CCBUF[1].reg = 0; // Steady high on next pulse
|
||||
theWriteTimer->COUNT16.INTENCLR.bit.MC0 = 1; // Disable interrupt
|
||||
g_writing_pulses = false;
|
||||
}
|
||||
g_num_pulses++; // Outside if/else to allow last-pulse case
|
||||
theWriteTimer->COUNT16.INTFLAG.bit.MC0 = 1; // Clear interrupt flag
|
||||
}
|
||||
}
|
||||
|
||||
// this isnt great but how else can we dynamically choose the pin? :/
|
||||
void TC0_Handler() { FLOPPY_TC_HANDLER(); }
|
||||
void TC1_Handler() { FLOPPY_TC_HANDLER(); }
|
||||
void TC2_Handler() { FLOPPY_TC_HANDLER(); }
|
||||
void TC3_Handler() { FLOPPY_TC_HANDLER(); }
|
||||
void TC4_Handler() { FLOPPY_TC_HANDLER(); }
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
static bool init_capture_timer(int _rddatapin, Stream *debug_serial) {
|
||||
MCLK->APBBMASK.reg |=
|
||||
MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral
|
||||
|
||||
// Enable the port multiplexer on READDATA
|
||||
PinDescription pinDesc = g_APinDescription[_rddatapin];
|
||||
uint32_t capture_port = pinDesc.ulPort;
|
||||
uint32_t capture_pin = pinDesc.ulPin;
|
||||
EExt_Interrupts capture_irq = pinDesc.ulExtInt;
|
||||
if (capture_irq == NOT_AN_INTERRUPT) {
|
||||
if (debug_serial)
|
||||
debug_serial->println("Not an interrupt pin!");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
|
||||
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
|
||||
|
||||
if (tcNum < TCC_INST_NUM) {
|
||||
if (pinDesc.ulTCChannel != NOT_ON_TIMER) {
|
||||
if (debug_serial)
|
||||
debug_serial->println(
|
||||
"PWM is on a TCC not TC, lets look at the TCChannel");
|
||||
tcNum = GetTCNumber(pinDesc.ulTCChannel);
|
||||
tcChannel = GetTCChannelNumber(pinDesc.ulTCChannel);
|
||||
}
|
||||
if (tcNum < TCC_INST_NUM) {
|
||||
if (debug_serial)
|
||||
debug_serial->println("Couldn't find a TC channel for this pin :(");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
g_cap_tc_num = tcNum -= TCC_INST_NUM; // adjust naming
|
||||
if (debug_serial)
|
||||
debug_serial->printf("readdata on port %d and pin %d, IRQ #%d, TC%d.%d\n\r",
|
||||
capture_port, capture_pin, capture_irq, tcNum,
|
||||
tcChannel);
|
||||
theReadTimer = tcList[tcNum].tc;
|
||||
|
||||
if (debug_serial)
|
||||
debug_serial->printf("TC GCLK ID=%d, EVU=%d\n\r", tcList[tcNum].gclk,
|
||||
tcList[tcNum].evu);
|
||||
|
||||
// Setup INPUT capture clock
|
||||
|
||||
GCLK->PCHCTRL[tcList[tcNum].gclk].reg =
|
||||
GCLK_PCHCTRL_GEN_GCLK1_Val |
|
||||
(1 << GCLK_PCHCTRL_CHEN_Pos); // use GCLK1 to get 48MHz on SAMD51
|
||||
PORT->Group[capture_port].PINCFG[capture_pin].bit.PMUXEN = 1;
|
||||
|
||||
// Set-up the pin as an EIC (interrupt) peripheral on READDATA
|
||||
if (capture_pin % 2 == 0) { // even pmux
|
||||
PORT->Group[capture_port].PMUX[capture_pin >> 1].reg |= PORT_PMUX_PMUXE(0);
|
||||
} else {
|
||||
PORT->Group[capture_port].PMUX[capture_pin >> 1].reg |= PORT_PMUX_PMUXO(0);
|
||||
}
|
||||
|
||||
EIC->CTRLA.bit.ENABLE = 0; // Disable the EIC peripheral
|
||||
while (EIC->SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for synchronization
|
||||
// Look for right CONFIG register to be addressed
|
||||
uint8_t eic_config, eic_config_pos;
|
||||
if (capture_irq > EXTERNAL_INT_7) {
|
||||
eic_config = 1;
|
||||
eic_config_pos = (capture_irq - 8) << 2;
|
||||
} else {
|
||||
eic_config = 0;
|
||||
eic_config_pos = capture_irq << 2;
|
||||
}
|
||||
|
||||
// Set event on detecting a HIGH level
|
||||
EIC->CONFIG[eic_config].reg &= ~(EIC_CONFIG_SENSE0_Msk << eic_config_pos);
|
||||
EIC->CONFIG[eic_config].reg |= EIC_CONFIG_SENSE0_HIGH_Val << eic_config_pos;
|
||||
EIC->EVCTRL.reg =
|
||||
1 << capture_irq; // Enable event output on external interrupt
|
||||
EIC->INTENCLR.reg = 1 << capture_irq; // Clear interrupt on external interrupt
|
||||
EIC->ASYNCH.reg = 1 << capture_irq; // Set-up interrupt as asynchronous input
|
||||
EIC->CTRLA.bit.ENABLE = 1; // Enable the EIC peripheral
|
||||
while (EIC->SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for synchronization
|
||||
|
||||
// Select the event system user on channel 0 (USER number = channel number +
|
||||
// 1)
|
||||
EVSYS->USER[tcList[tcNum].evu].reg =
|
||||
EVSYS_USER_CHANNEL(1); // Set the event user (receiver) as timer
|
||||
|
||||
// Select the event system generator on channel 0
|
||||
EVSYS->Channel[0].CHANNEL.reg =
|
||||
EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
|
||||
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
|
||||
EVSYS_CHANNEL_EVGEN(
|
||||
EVSYS_ID_GEN_EIC_EXTINT_0 +
|
||||
capture_irq); // Set event generator (sender) as ext int
|
||||
|
||||
theReadTimer->COUNT16.EVCTRL.reg =
|
||||
TC_EVCTRL_TCEI | // Enable the TCC event input
|
||||
// TC_EVCTRL_TCINV | // Invert the event
|
||||
// input
|
||||
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1
|
||||
// pulsewidth
|
||||
|
||||
NVIC_SetPriority(tcList[tcNum].IRQn,
|
||||
0); // Set the Nested Vector Interrupt Controller (NVIC)
|
||||
// priority for TCx to 0 (highest)
|
||||
theReadTimer->COUNT16.INTENSET.reg =
|
||||
TC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
|
||||
TC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
|
||||
|
||||
theReadTimer->COUNT16.CTRLA.reg =
|
||||
TC_CTRLA_CAPTEN1 | // Enable pulse capture on CC1
|
||||
TC_CTRLA_CAPTEN0 | // Enable pulse capture on CC0
|
||||
// TC_CTRLA_PRESCSYNC_PRESC | // Roll over on prescaler clock
|
||||
// TC_CTRLA_PRESCALER_DIV1 | // Set the prescaler
|
||||
TC_CTRLA_MODE_COUNT16; // Set the timer to 16-bit mode
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void enable_capture_timer(bool interrupt_driven) {
|
||||
if (!theReadTimer)
|
||||
return;
|
||||
|
||||
if (interrupt_driven) {
|
||||
NVIC_EnableIRQ(
|
||||
tcList[g_cap_tc_num].IRQn); // Connect the TCx timer to the Nested
|
||||
// Vector Interrupt Controller (NVIC)
|
||||
} else {
|
||||
NVIC_DisableIRQ(
|
||||
tcList[g_cap_tc_num].IRQn); // Disconnect the TCx timer from the Nested
|
||||
// Vector Interrupt Controller (NVIC)
|
||||
}
|
||||
|
||||
theReadTimer->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC timer
|
||||
while (theReadTimer->COUNT16.SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for synchronization
|
||||
}
|
||||
|
||||
static bool init_generate_timer(int _wrdatapin, Stream *debug_serial) {
|
||||
MCLK->APBBMASK.reg |=
|
||||
MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral
|
||||
|
||||
// Enable the port multiplexer on WRITEDATA
|
||||
PinDescription pinDesc = g_APinDescription[_wrdatapin];
|
||||
uint32_t generate_port = pinDesc.ulPort;
|
||||
uint32_t generate_pin = pinDesc.ulPin;
|
||||
|
||||
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
|
||||
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
|
||||
|
||||
if (tcNum < TCC_INST_NUM) {
|
||||
// OK so we're a TC!
|
||||
if (pinDesc.ulTCChannel != NOT_ON_TIMER) {
|
||||
if (debug_serial)
|
||||
debug_serial->println("PWM is on a TC");
|
||||
tcNum = GetTCNumber(pinDesc.ulTCChannel);
|
||||
tcChannel = GetTCChannelNumber(pinDesc.ulTCChannel);
|
||||
|
||||
pinPeripheral(_wrdatapin, PIO_TIMER); // PIO_TIMER if using a TC periph
|
||||
}
|
||||
if (tcNum < TCC_INST_NUM) {
|
||||
if (debug_serial)
|
||||
debug_serial->println("Couldn't find a TC channel for this pin :(");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
pinPeripheral(_wrdatapin,
|
||||
PIO_TIMER_ALT); // PIO_TIMER_ALT if using a TCC periph
|
||||
}
|
||||
|
||||
tcNum -= TCC_INST_NUM; // adjust naming
|
||||
if (debug_serial)
|
||||
debug_serial->printf("writedata on port %d and pin %d,, TC%d.%d\n\r",
|
||||
generate_port, generate_pin, tcNum, tcChannel);
|
||||
|
||||
// Because of the PWM mode used, we MUST use a TC#/WO[1] pin, can't
|
||||
// use WO[0]. Different timers would need different pins,
|
||||
// but the WO[1] thing is NOT negotiable.
|
||||
if (tcChannel != 1) {
|
||||
debug_serial->println("Must be on TCx.1, but we're not!");
|
||||
return false;
|
||||
}
|
||||
|
||||
theWriteTimer = tcList[tcNum].tc;
|
||||
|
||||
if (debug_serial)
|
||||
debug_serial->printf("TC GCLK ID=%d, EVU=%d\n\r", tcList[tcNum].gclk,
|
||||
tcList[tcNum].evu);
|
||||
|
||||
// Configure TC timer source as GCLK1 (48 MHz peripheral clock)
|
||||
GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN = 0;
|
||||
while (GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN)
|
||||
; // Wait for disable
|
||||
GCLK_PCHCTRL_Type pchctrl;
|
||||
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val; // GCLK1 is 48 MHz
|
||||
pchctrl.bit.CHEN = 1;
|
||||
GCLK->PCHCTRL[tcList[tcNum].gclk].reg = pchctrl.reg;
|
||||
while (!GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN)
|
||||
; // Wait for enable
|
||||
|
||||
// Software reset timer/counter to default state (also disables it)
|
||||
theWriteTimer->COUNT16.CTRLA.bit.SWRST = 1;
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.SWRST)
|
||||
;
|
||||
|
||||
// Configure for MPWM, 1:2 prescale (24 MHz). 16-bit mode is defailt.
|
||||
theWriteTimer->COUNT16.WAVE.bit.WAVEGEN = 3; // Match PWM mode
|
||||
theWriteTimer->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV2_Val;
|
||||
// MPWM mode is weird but necessary because Normal PWM has a fixed TOP value.
|
||||
|
||||
// Count-up, no one-shot is default state, no need to fiddle those bits.
|
||||
|
||||
// Invert PWM channel 1 so it starts low, goes high on match
|
||||
theWriteTimer->COUNT16.DRVCTRL.bit.INVEN1 = 1; // INVEN1 = channel 1
|
||||
|
||||
// Enable timer
|
||||
theWriteTimer->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.ENABLE)
|
||||
;
|
||||
|
||||
// IRQ is enabled but match-compare interrupt isn't enabled
|
||||
// until send_pulses() is called.
|
||||
|
||||
NVIC_DisableIRQ(tcList[tcNum].IRQn);
|
||||
NVIC_ClearPendingIRQ(tcList[tcNum].IRQn);
|
||||
NVIC_SetPriority(tcList[tcNum].IRQn, 0); // Top priority
|
||||
NVIC_EnableIRQ(tcList[tcNum].IRQn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void enable_generate_timer(void) {
|
||||
theWriteTimer->COUNT16.COUNT.reg =
|
||||
0; // Reset counter so we can time this right
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.COUNT)
|
||||
;
|
||||
// Trigger 1st pulse in ~1/4 uS (need moment to set up other registers)
|
||||
theWriteTimer->COUNT16.CC[0].reg = 5;
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC0)
|
||||
;
|
||||
// Set up duration of first pulse when COUNT rolls over
|
||||
theWriteTimer->COUNT16.CCBUF[0].reg = g_flux_pulses[0];
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC0)
|
||||
;
|
||||
// Set up LOW period of pulses when COUNT rolls over
|
||||
theWriteTimer->COUNT16.CCBUF[1].reg = 5; // 0.25 uS low pulses
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC1)
|
||||
;
|
||||
// Enable match compare channel 0 interrupt
|
||||
theWriteTimer->COUNT16.INTENSET.bit.MC0 = 1;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
bool Adafruit_FloppyBase::init_capture(void) {
|
||||
return init_capture_timer(_rddatapin, debug_serial);
|
||||
}
|
||||
|
||||
void Adafruit_FloppyBase::deinit_capture(void) {
|
||||
if (!theReadTimer)
|
||||
return;
|
||||
|
||||
// Software reset timer/counter to default state (also disables it)
|
||||
theReadTimer->COUNT16.CTRLA.bit.SWRST = 1;
|
||||
while (theReadTimer->COUNT16.SYNCBUSY.bit.SWRST)
|
||||
;
|
||||
theReadTimer = NULL;
|
||||
}
|
||||
|
||||
void Adafruit_FloppyBase::enable_capture(void) { enable_capture_timer(true); }
|
||||
|
||||
void Adafruit_FloppyBase::disable_capture(void) {
|
||||
if (!theReadTimer)
|
||||
return;
|
||||
|
||||
theReadTimer->COUNT16.CTRLA.bit.ENABLE = 0; // disable the TC timer
|
||||
}
|
||||
|
||||
bool Adafruit_FloppyBase::init_generate(void) {
|
||||
return init_generate_timer(_wrdatapin, debug_serial);
|
||||
}
|
||||
|
||||
void Adafruit_FloppyBase::deinit_generate(void) {
|
||||
if (!theWriteTimer)
|
||||
return;
|
||||
|
||||
// Software reset timer/counter to default state (also disables it)
|
||||
theWriteTimer->COUNT16.CTRLA.bit.SWRST = 1;
|
||||
while (theWriteTimer->COUNT16.SYNCBUSY.bit.SWRST)
|
||||
;
|
||||
theWriteTimer = NULL;
|
||||
}
|
||||
|
||||
void Adafruit_FloppyBase::enable_generate(void) { enable_generate_timer(); }
|
||||
|
||||
void Adafruit_FloppyBase::disable_generate(void) {
|
||||
if (!theWriteTimer)
|
||||
return;
|
||||
|
||||
theWriteTimer->COUNT16.CTRLA.bit.ENABLE = 0; // disable the TC timer
|
||||
}
|
||||
|
||||
bool Adafruit_FloppyBase::start_polled_capture(void) {
|
||||
::enable_capture_timer(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t mfm_io_sample_flux(bool *index) {
|
||||
if (!theReadTimer)
|
||||
return ~(uint16_t)0;
|
||||
|
||||
// Check for match counter 0 (MC0) interrupt
|
||||
while (!(theReadTimer->COUNT16.INTFLAG.bit.MC0)) {
|
||||
/* NOTHING */
|
||||
}
|
||||
uint16_t ticks =
|
||||
theReadTimer->COUNT16.CC[0].reg / g_timing_div; // Copy the period
|
||||
return ticks;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
101
src/greasepack.h
101
src/greasepack.h
|
|
@ -1,101 +0,0 @@
|
|||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Encoding flux of duration T:
|
||||
// 0: Impossible
|
||||
// 1..249: Encodes as 1 byte (T itself)
|
||||
// 250..1524: Encodes as 2 bytes: (T // 255 + 250), (
|
||||
// 1525..2^28: Encodes as 6 bytes: 255 + Space + "write_28bit"
|
||||
enum { cutoff_1byte = 250, cutoff_2byte = 1525, cutoff_6byte = (1 << 28) - 1 };
|
||||
|
||||
// Pack one flux duration into greaseaweazel format.
|
||||
// buf: Pointer to the current location in the flux buffer. NULL indicates no
|
||||
// buffer, regardless of end. end: Pointer to the end of the flux buffer value:
|
||||
// the flux value itself
|
||||
//
|
||||
// Returns: the new 'buf'. If buf==end, then the buffer is now full, and
|
||||
// the last byte is a terminating 0. This can also mean that the last value
|
||||
// was not stored because there was insufficient space, but there's no way to
|
||||
// tell apart an "exactly full" buffer from "the last sample didn't fit".
|
||||
static inline uint8_t *greasepack(uint8_t *buf, uint8_t *end, unsigned value) {
|
||||
// already no space left
|
||||
if (!buf || buf == end) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
size_t left = end - buf;
|
||||
size_t need = value < cutoff_1byte ? 1 : value < cutoff_2byte ? 2 : 6;
|
||||
|
||||
// Buffer's going to be too full, store a terminating 0 and give up
|
||||
if (need > left) {
|
||||
*buf = 0;
|
||||
return end;
|
||||
}
|
||||
|
||||
if (value < cutoff_1byte) {
|
||||
*buf++ = value;
|
||||
} else if (value < cutoff_2byte) {
|
||||
unsigned high = (value - 250) / 255;
|
||||
*buf++ = 250 + high;
|
||||
*buf++ = 1 + (value - 250) % 255;
|
||||
} else {
|
||||
if (value > cutoff_6byte) {
|
||||
value = cutoff_6byte;
|
||||
}
|
||||
*buf++ = 255;
|
||||
*buf++ = 2;
|
||||
*buf++ = 1 | (value << 1) & 255;
|
||||
*buf++ = 1 | (value >> 6) & 255;
|
||||
*buf++ = 1 | (value >> 13) & 255;
|
||||
*buf++ = 1 | (value >> 20) & 255;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline unsigned greaseunpack(uint8_t **buf_, uint8_t *end,
|
||||
bool store_greaseweazel) {
|
||||
#define BUF (*buf_)
|
||||
if (!store_greaseweazel) {
|
||||
if (!BUF || BUF == end) {
|
||||
return 0xffff;
|
||||
}
|
||||
return *BUF++;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// already no data left
|
||||
if (!BUF || BUF == end) {
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
size_t left = end - BUF;
|
||||
uint8_t data = *BUF++;
|
||||
size_t need = data == 255 ? 6 : data >= cutoff_1byte ? 2 : 1;
|
||||
if (left < need) {
|
||||
BUF = end;
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
if (need == 1) {
|
||||
return data;
|
||||
}
|
||||
if (need == 2) {
|
||||
uint8_t data2 = *BUF++;
|
||||
return (data - cutoff_1byte + 1) * 250 + data2;
|
||||
}
|
||||
uint8_t data2 = *BUF++;
|
||||
if (data2 != 2) {
|
||||
BUF += 4;
|
||||
continue;
|
||||
} // something other than FluxOp.Space
|
||||
uint32_t value = (*BUF++ & 254) >> 1;
|
||||
value += (*BUF++ & 254) << 6;
|
||||
value += (*BUF++ & 254) << 13;
|
||||
value += (*BUF++ & 254) << 20;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
#undef BUF
|
||||
266
src/mfm_impl.h
266
src/mfm_impl.h
|
|
@ -1,266 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize("-O3")
|
||||
typedef struct mfm_io mfm_io_t;
|
||||
|
||||
#ifndef MFM_IO_MMIO
|
||||
#define MFM_IO_MMIO (0)
|
||||
#endif
|
||||
|
||||
// If you have a memory mapped peripheral, define MFM_IO_MMIO to get an
|
||||
// implementation of the mfm_io functions. then, just populate the fields with
|
||||
// the actual registers to use and define T2_5 and T3_5 to the empirical values
|
||||
// dividing between T2/3 and T3/4 pulses.
|
||||
#if MFM_IO_MMIO
|
||||
struct mfm_io {
|
||||
const volatile uint32_t *index_port;
|
||||
uint32_t index_mask;
|
||||
const volatile uint32_t *data_port;
|
||||
uint32_t data_mask;
|
||||
unsigned index_state;
|
||||
unsigned index_count;
|
||||
};
|
||||
#endif
|
||||
|
||||
typedef enum { pulse_10, pulse_100, pulse_1000 } mfm_io_symbol_t;
|
||||
|
||||
typedef enum { odd = 0, even = 1 } mfm_state_t;
|
||||
|
||||
enum { IDAM = 0xfe, DAM = 0xfb };
|
||||
|
||||
enum { blocksize = 512, overhead = 3, metadata_size = 7 };
|
||||
__attribute__((always_inline)) static inline mfm_io_symbol_t
|
||||
mfm_io_read_symbol(mfm_io_t *io);
|
||||
static void mfm_io_reset_sync_count(mfm_io_t *io);
|
||||
__attribute__((always_inline)) static int mfm_io_get_sync_count(mfm_io_t *io);
|
||||
|
||||
// Automatically generated CRC function
|
||||
// polynomial: 0x11021
|
||||
static uint16_t crc16(uint8_t *data, int len, uint16_t crc) {
|
||||
static const uint16_t table[256] = {
|
||||
0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U,
|
||||
0x8108U, 0x9129U, 0xA14AU, 0xB16BU, 0xC18CU, 0xD1ADU, 0xE1CEU, 0xF1EFU,
|
||||
0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52B5U, 0x4294U, 0x72F7U, 0x62D6U,
|
||||
0x9339U, 0x8318U, 0xB37BU, 0xA35AU, 0xD3BDU, 0xC39CU, 0xF3FFU, 0xE3DEU,
|
||||
0x2462U, 0x3443U, 0x0420U, 0x1401U, 0x64E6U, 0x74C7U, 0x44A4U, 0x5485U,
|
||||
0xA56AU, 0xB54BU, 0x8528U, 0x9509U, 0xE5EEU, 0xF5CFU, 0xC5ACU, 0xD58DU,
|
||||
0x3653U, 0x2672U, 0x1611U, 0x0630U, 0x76D7U, 0x66F6U, 0x5695U, 0x46B4U,
|
||||
0xB75BU, 0xA77AU, 0x9719U, 0x8738U, 0xF7DFU, 0xE7FEU, 0xD79DU, 0xC7BCU,
|
||||
0x48C4U, 0x58E5U, 0x6886U, 0x78A7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U,
|
||||
0xC9CCU, 0xD9EDU, 0xE98EU, 0xF9AFU, 0x8948U, 0x9969U, 0xA90AU, 0xB92BU,
|
||||
0x5AF5U, 0x4AD4U, 0x7AB7U, 0x6A96U, 0x1A71U, 0x0A50U, 0x3A33U, 0x2A12U,
|
||||
0xDBFDU, 0xCBDCU, 0xFBBFU, 0xEB9EU, 0x9B79U, 0x8B58U, 0xBB3BU, 0xAB1AU,
|
||||
0x6CA6U, 0x7C87U, 0x4CE4U, 0x5CC5U, 0x2C22U, 0x3C03U, 0x0C60U, 0x1C41U,
|
||||
0xEDAEU, 0xFD8FU, 0xCDECU, 0xDDCDU, 0xAD2AU, 0xBD0BU, 0x8D68U, 0x9D49U,
|
||||
0x7E97U, 0x6EB6U, 0x5ED5U, 0x4EF4U, 0x3E13U, 0x2E32U, 0x1E51U, 0x0E70U,
|
||||
0xFF9FU, 0xEFBEU, 0xDFDDU, 0xCFFCU, 0xBF1BU, 0xAF3AU, 0x9F59U, 0x8F78U,
|
||||
0x9188U, 0x81A9U, 0xB1CAU, 0xA1EBU, 0xD10CU, 0xC12DU, 0xF14EU, 0xE16FU,
|
||||
0x1080U, 0x00A1U, 0x30C2U, 0x20E3U, 0x5004U, 0x4025U, 0x7046U, 0x6067U,
|
||||
0x83B9U, 0x9398U, 0xA3FBU, 0xB3DAU, 0xC33DU, 0xD31CU, 0xE37FU, 0xF35EU,
|
||||
0x02B1U, 0x1290U, 0x22F3U, 0x32D2U, 0x4235U, 0x5214U, 0x6277U, 0x7256U,
|
||||
0xB5EAU, 0xA5CBU, 0x95A8U, 0x8589U, 0xF56EU, 0xE54FU, 0xD52CU, 0xC50DU,
|
||||
0x34E2U, 0x24C3U, 0x14A0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U,
|
||||
0xA7DBU, 0xB7FAU, 0x8799U, 0x97B8U, 0xE75FU, 0xF77EU, 0xC71DU, 0xD73CU,
|
||||
0x26D3U, 0x36F2U, 0x0691U, 0x16B0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U,
|
||||
0xD94CU, 0xC96DU, 0xF90EU, 0xE92FU, 0x99C8U, 0x89E9U, 0xB98AU, 0xA9ABU,
|
||||
0x5844U, 0x4865U, 0x7806U, 0x6827U, 0x18C0U, 0x08E1U, 0x3882U, 0x28A3U,
|
||||
0xCB7DU, 0xDB5CU, 0xEB3FU, 0xFB1EU, 0x8BF9U, 0x9BD8U, 0xABBBU, 0xBB9AU,
|
||||
0x4A75U, 0x5A54U, 0x6A37U, 0x7A16U, 0x0AF1U, 0x1AD0U, 0x2AB3U, 0x3A92U,
|
||||
0xFD2EU, 0xED0FU, 0xDD6CU, 0xCD4DU, 0xBDAAU, 0xAD8BU, 0x9DE8U, 0x8DC9U,
|
||||
0x7C26U, 0x6C07U, 0x5C64U, 0x4C45U, 0x3CA2U, 0x2C83U, 0x1CE0U, 0x0CC1U,
|
||||
0xEF1FU, 0xFF3EU, 0xCF5DU, 0xDF7CU, 0xAF9BU, 0xBFBAU, 0x8FD9U, 0x9FF8U,
|
||||
0x6E17U, 0x7E36U, 0x4E55U, 0x5E74U, 0x2E93U, 0x3EB2U, 0x0ED1U, 0x1EF0U,
|
||||
};
|
||||
|
||||
while (len > 0) {
|
||||
crc = table[*data ^ (uint8_t)(crc >> 8)] ^ (crc << 8);
|
||||
data++;
|
||||
len--;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
enum { triple_mark_magic = 0x09926499, triple_mark_mask = 0x0fffffff };
|
||||
|
||||
__attribute__((always_inline)) inline static bool
|
||||
wait_triple_sync_mark(mfm_io_t *io) {
|
||||
uint32_t state = 0;
|
||||
while (mfm_io_get_sync_count(io) < 3 && state != triple_mark_magic) {
|
||||
state = ((state << 2) | mfm_io_read_symbol(io)) & triple_mark_mask;
|
||||
}
|
||||
return state == triple_mark_magic;
|
||||
}
|
||||
|
||||
// Compute the MFM CRC of the data, _assuming it was preceded by three 0xa1 sync
|
||||
// bytes
|
||||
static int crc16_preloaded(unsigned char *buf, size_t n) {
|
||||
return crc16((uint8_t *)buf, n, 0xcdb4);
|
||||
}
|
||||
|
||||
// Copy 'n' bytes of data into 'buf'
|
||||
__attribute__((always_inline)) inline static void
|
||||
receive(mfm_io_t *io, unsigned char *buf, size_t n) {
|
||||
// `tmp` holds up to 9 bits of data, in bits 6..15.
|
||||
unsigned tmp = 0, weight = 0x8000;
|
||||
|
||||
#define PUT_BIT(x) \
|
||||
do { \
|
||||
if (x) \
|
||||
tmp |= weight; \
|
||||
weight >>= 1; \
|
||||
} while (0)
|
||||
|
||||
// In MFM, flux marks can be 2, 3, or 4 "T" apart. These three signals
|
||||
// stand for the bit sequences 10, 100, and 1000. However, half of the
|
||||
// bits are data bits, and half are 'clock' bits. We have to keep track of
|
||||
// whether [in the next symbol] we want the "even" bit(s) or the "odd" bit(s):
|
||||
//
|
||||
// 10 - leaves even/odd (parity) unchanged
|
||||
// 100 - inverts even/odd (parity)
|
||||
// 1000 - leaves even/odd (parity) unchanged
|
||||
// ^ ^ data bits if state is even
|
||||
// ^ ^ data bits if state is odd
|
||||
|
||||
// We do this by knowing that when we arrive, we are waiting to parse the
|
||||
// final '1' data bit of the MFM sync mark. This means we apply a special rule
|
||||
// to the first word, starting as though in the 'even' state but not recording
|
||||
// the '1' bit.
|
||||
mfm_io_symbol_t s = mfm_io_read_symbol(io);
|
||||
mfm_state_t state = even;
|
||||
switch (s) {
|
||||
case pulse_100: // first data bit is a 0, and we start in the ODD state
|
||||
state = odd;
|
||||
/* fallthrough */
|
||||
case pulse_1000: // first data bit is a 0, and we start in EVEN state
|
||||
PUT_BIT(0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
while (n) {
|
||||
s = mfm_io_read_symbol(io);
|
||||
PUT_BIT(state); // 'even' is 1, so record a '1' or '0' as appropriate
|
||||
if (s == pulse_1000) {
|
||||
PUT_BIT(0); // the other bit recorded for a 1000 is always a '0'
|
||||
}
|
||||
if (s == pulse_100) {
|
||||
if (state) {
|
||||
PUT_BIT(0);
|
||||
} // If 'even', record an additional '0'
|
||||
state = (mfm_state_t)!state; // the next symbol has opposite parity
|
||||
}
|
||||
|
||||
*buf = tmp >> 8; // store every time to make timing more even
|
||||
if (weight <= 0x80) {
|
||||
tmp <<= 8;
|
||||
weight <<= 8;
|
||||
buf++;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform all the steps of receiving the next IDAM, DAM (or DDAM, but we don't
|
||||
// use them)
|
||||
__attribute__((always_inline)) inline static bool
|
||||
wait_triple_sync_mark_receive_crc(mfm_io_t *io, void *buf, size_t n) {
|
||||
if (!wait_triple_sync_mark(io)) {
|
||||
return false;
|
||||
}
|
||||
receive(io, (uint8_t *)buf, n);
|
||||
unsigned crc = crc16_preloaded((uint8_t *)buf, n);
|
||||
return crc == 0;
|
||||
}
|
||||
|
||||
// Read a whole track, setting validity[] for each sector actually read, up to
|
||||
// n_sectors indexing of validity & data is 0-based, even though IDAMs store
|
||||
// sectors as 1-based
|
||||
static int read_track(mfm_io_t io, int n_sectors, void *data,
|
||||
uint8_t *validity) {
|
||||
memset(validity, 0, n_sectors);
|
||||
|
||||
int n_valid = 0;
|
||||
|
||||
mfm_io_reset_sync_count(&io);
|
||||
|
||||
unsigned char buf[512 + 3];
|
||||
while (mfm_io_get_sync_count(&io) < 3 && n_valid < n_sectors) {
|
||||
if (!wait_triple_sync_mark_receive_crc(&io, buf, metadata_size)) {
|
||||
continue;
|
||||
}
|
||||
if (buf[0] != IDAM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int r = (uint8_t)buf[3] - 1;
|
||||
if (r >= n_sectors) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validity[r]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!wait_triple_sync_mark_receive_crc(&io, buf, sizeof(buf))) {
|
||||
continue;
|
||||
}
|
||||
if (buf[0] != DAM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy((char *)data + blocksize * r, buf + 1, blocksize);
|
||||
validity[r] = 1;
|
||||
n_valid++;
|
||||
}
|
||||
return n_valid;
|
||||
}
|
||||
|
||||
#if MFM_IO_MMIO
|
||||
#define READ_DATA() (!!(*io->data_port & io->data_mask))
|
||||
#define READ_INDEX() (!!(*io->index_port & io->index_mask))
|
||||
__attribute__((optimize("O3"), always_inline)) static inline mfm_io_symbol_t
|
||||
mfm_io_read_symbol(mfm_io_t *io) {
|
||||
unsigned pulse_count = 3;
|
||||
while (!READ_DATA()) {
|
||||
pulse_count++;
|
||||
}
|
||||
|
||||
unsigned index_state = (io->index_state << 1) | READ_INDEX();
|
||||
if ((index_state & 3) == 2) { // a zero-to-one transition
|
||||
io->index_count++;
|
||||
}
|
||||
io->index_state = index_state;
|
||||
|
||||
while (READ_DATA()) {
|
||||
pulse_count++;
|
||||
}
|
||||
|
||||
int result = pulse_10;
|
||||
if (pulse_count > T2_5) {
|
||||
result++;
|
||||
}
|
||||
if (pulse_count > T3_5) {
|
||||
result++;
|
||||
}
|
||||
|
||||
return (mfm_io_symbol_t)result;
|
||||
}
|
||||
|
||||
static void mfm_io_reset_sync_count(mfm_io_t *io) { io->index_count = 0; }
|
||||
|
||||
__attribute__((optimize("O3"), always_inline)) inline static int
|
||||
mfm_io_get_sync_count(mfm_io_t *io) {
|
||||
return io->index_count;
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma GCC pop_options
|
||||
Loading…
Reference in a new issue