Compare commits
229 commits
main
...
floppsy-bu
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fed24791c | |||
| a585ddc296 | |||
| aacdaad685 | |||
| f898470d33 | |||
| 13f1081f8f | |||
| f985ae4379 | |||
| 3a61107be6 | |||
| 4d6c23399b | |||
| 2996221131 | |||
| e66f50dd33 | |||
| 97f3e43d98 | |||
| 3e1874b1e2 | |||
| 6e7bda85d2 | |||
| 5c671513f2 | |||
|
|
bf62073881 | ||
| 08ddc10644 | |||
| 2755d43d48 | |||
| ea1abd7e25 | |||
| 9a99e49333 | |||
| a9de3f497e | |||
| 6677a69987 | |||
| 41bbff7da9 | |||
| 20c33f3782 | |||
| ebf20b1177 | |||
| e67ffac69e | |||
| 93a3cd507c | |||
| 4431257cd4 | |||
| c2e6bd30d0 | |||
| c7ce752e23 | |||
| bfeea43b16 | |||
|
|
08adb83c70 | ||
|
|
0ab5f3097f | ||
| 906741f4bc | |||
| f5a0c3f967 | |||
| e8fe9b777e | |||
| 593781c771 | |||
| f3e49d7f56 | |||
| d5be2d845e | |||
| fa7a0b4ffc | |||
| 1d378ce835 | |||
| 689d0f8256 | |||
| bf50da2e7a | |||
| db3056dd7b | |||
| 0a125a337e | |||
| bfb9c84f94 | |||
| 2804afa26d | |||
| 9db6ca3a7a | |||
| 15140daf64 | |||
| dbc082e01f | |||
| 82e0d6871e | |||
| 6a3658cceb | |||
| c22430ef7f | |||
| f758f0287e | |||
| ae11ba5a65 | |||
| 5cf6dda76b | |||
| 0a012d92bc | |||
|
|
9909060216 | ||
| f8db379703 | |||
| a20190fb04 | |||
| f53ff73bbd | |||
|
|
e87e5cb714 | ||
| ce83b33afc | |||
| d5a278848b | |||
| 543b095178 | |||
| 32e4a60001 | |||
| cf696c106f | |||
| 017c7861a5 | |||
| c6e8dbcab4 | |||
| 85b55fcd87 | |||
| 1020dbff7b | |||
| 338ac2cea7 | |||
| f4594edbfe | |||
| 76dfdff5b6 | |||
| 8b5209d798 | |||
| 089736a52c | |||
| 0dfe6f5fda | |||
| a5a1e1966f | |||
| fbfee3242e | |||
| 9c8121304f | |||
|
|
267e3e001d | ||
|
|
dd8789fcba | ||
| 9efcde1346 | |||
|
|
a803feb8d8 | ||
|
|
29388e2ec6 | ||
|
|
38a7120f02 | ||
|
|
746e37f845 | ||
|
|
44ba8226cf | ||
|
|
832137e782 | ||
| ddc643add4 | |||
| 225e0c4c07 | |||
| 3a5b0ffa7e | |||
| a68df8bc02 | |||
| 783ac99958 | |||
| 3476a6f61a | |||
| 0a84d08aa8 | |||
| 1885936693 | |||
|
|
025c01f62b | ||
|
|
0311d829e1 | ||
| 64e9db7cc5 | |||
| fe3670a30f | |||
| 3da2edbc9f | |||
|
|
194523ea06 | ||
|
|
d2ea9654bf | ||
|
|
c682fd1627 | ||
|
|
7981cc0d01 | ||
| 014361bec9 | |||
| 51e785eb28 | |||
|
|
4b64728503 | ||
|
|
c104c92a86 | ||
|
|
145efdb695 | ||
|
|
2421b2be81 | ||
| 7de66e6a78 | |||
|
|
602a489d46 | ||
| 1841d0f0c2 | |||
| 2629a9742b | |||
| b43c9764db | |||
| 7851e7574a | |||
| 27a4abf6b4 | |||
| c6e5044488 | |||
| 7f66508cd8 | |||
| 2b67aa391e | |||
| 680197ded5 | |||
| 6bf313acff | |||
| 48e1309e69 | |||
| c6227a80e8 | |||
| 8d38c01185 | |||
| 77de2e119b | |||
| c50785c897 | |||
| c21929b34b | |||
| e38f380fc6 | |||
| 1508b08997 | |||
| 98c4b2d098 | |||
| 9c7e3ff22c | |||
| bd1669e8b0 | |||
| 00cfcf1963 | |||
| d6dc0fe043 | |||
| 5d1e32d7f1 | |||
| b650a78743 | |||
| 1587684f3e | |||
| a71f107556 | |||
| 87785ae5ff | |||
| ab501a60d2 | |||
| cd0c08142e | |||
| d18495d298 | |||
| 311c7798fb | |||
| a400e0b8e2 | |||
| 178792a525 | |||
| c78b45ba9d | |||
| b7754468c5 | |||
| 67bc164530 | |||
| 43e02a5545 | |||
| e4a29020be | |||
|
|
0908c92ab5 | ||
| 46166ace07 | |||
| ef7eca9d02 | |||
| afe73d8039 | |||
|
|
7db1daa364 | ||
|
|
b41052ba75 | ||
|
|
4d062e5244 | ||
| fefdb6667e | |||
| 6e614e6043 | |||
| 10eb2fd3cc | |||
| 85e0106965 | |||
| 5a2fea5616 | |||
| 4aebea3518 | |||
|
|
d374cf6500 | ||
|
|
9037df8f75 | ||
|
|
a381e34486 | ||
|
|
72dc40889e | ||
|
|
c747af3a87 | ||
|
|
5a467596b8 | ||
|
|
39fdddc976 | ||
| a07ac41c11 | |||
| f25c9afc82 | |||
| 24e8ea588f | |||
| 9aeee24ab8 | |||
| 235c2f1923 | |||
|
|
b0b9ad9286 | ||
|
|
b16fb7f0b7 | ||
|
|
9c744073ad | ||
|
|
91c3428671 | ||
|
|
61b5fad6fe | ||
|
|
35f3a884f5 | ||
|
|
bd9e17f012 | ||
|
|
bd06c2f941 | ||
|
|
9a790cc7c9 | ||
|
|
5fcedf8935 | ||
|
|
1e07c6be94 | ||
|
|
4af555fd29 | ||
|
|
96609309e7 | ||
|
|
a108bb53ed | ||
| e36a6127b9 | |||
|
|
94e3802157 | ||
|
|
dcb4dce9aa | ||
|
|
86c4f88ae0 | ||
|
|
3b6c6d2209 | ||
|
|
3ae01d8052 | ||
|
|
29c23bf0e3 | ||
|
|
8001a9ada7 | ||
|
|
1eb38f2024 | ||
|
|
21b96c80de | ||
|
|
391738218a | ||
|
|
42bfa32c7f | ||
|
|
c53cbf0f7e | ||
|
|
4fc1cca1c2 | ||
|
|
fdd7c19823 | ||
|
|
4c962658b0 | ||
|
|
d31d470c2b | ||
|
|
a00e78b871 | ||
|
|
749c1a03cf | ||
|
|
4a32d92d75 | ||
|
|
3ec2847a84 | ||
|
|
a82d5f441b | ||
|
|
2cf55cc2d0 | ||
|
|
f0686b9aed | ||
|
|
d5d34127db | ||
| 825174e288 | |||
|
|
fa41a7e9b8 | ||
|
|
9ca7d30046 | ||
|
|
aaee310ec9 | ||
|
|
e6a438b8e2 | ||
| 347cfb5982 | |||
| 222c6ce0d7 | |||
| c71c59de49 | |||
|
|
6c53113d7e | ||
|
|
5b021bb35a | ||
| c6b57c3007 | |||
| 08ec69bcc8 | |||
| 9d5136fced |
54 changed files with 9198 additions and 878 deletions
69
.github/workflows/githubci.yml
vendored
69
.github/workflows/githubci.yml
vendored
|
|
@ -4,14 +4,18 @@ on: [pull_request, push, repository_dispatch]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arduino-platform: ["feather_m4_express_tinyusb", "feather_rp2040_tinyusb", "floppsy_rp2040_tinyusb"]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-python@v1
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: adafruit/ci-arduino
|
||||
path: ci
|
||||
|
|
@ -19,11 +23,66 @@ 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 feather_m4_express_tinyusb feather_rp2040
|
||||
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@v4
|
||||
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@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
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
|
||||
|
||||
- name: clang
|
||||
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .
|
||||
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -e "*.pio.h" -r .
|
||||
|
||||
- name: doxygen
|
||||
env:
|
||||
|
|
|
|||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
/ci
|
||||
/doxygen
|
||||
/examples/*/build
|
||||
/html
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[submodule "doxygen-awesome-css"]
|
||||
path = doxygen-awesome-css
|
||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||
[submodule "host_src/greaseweazle"]
|
||||
path = host_src/greaseweazle
|
||||
url = https://github.com/keirf/greaseweazle.git
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
#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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#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
|
||||
156
LICENSES/CC-BY-4.0.txt
Normal file
156
LICENSES/CC-BY-4.0.txt
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
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.
|
||||
121
LICENSES/CC0-1.0.txt
Normal file
121
LICENSES/CC0-1.0.txt
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
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.
|
||||
9
LICENSES/MIT.txt
Normal file
9
LICENSES/MIT.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
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.
|
||||
26
README.md
26
README.md
|
|
@ -1,10 +1,11 @@
|
|||
# Adafruit Floppy [](https://github.com/adafruit/Adafruit_Floppy/actions)
|
||||
# Adafruit Floppy
|
||||
[](https://github.com/adafruit/Adafruit_Floppy/actions)
|
||||
|
||||

|
||||
|
||||
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 will attempt to increase the availability and accessibility of floppy disk controllers by:
|
||||
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 by:
|
||||
|
||||
1. **porting the greaseweazle / fluxengine firmware to Arduino** so that it is less tied to specific hardware. this is important as, during 2021 we learned that silicon shortages can make specific chips extremely difficult to find - having a cross-platform firmware alleviates dependancies on specific chips.
|
||||
|
||||
|
|
@ -12,24 +13,27 @@ Adafruit Floppy is a project to make a flexible, full-stack, open source hardwar
|
|||
|
||||
3. **adding hardware support for reading apple ii disks**. many flux readers focus on 34-pin disk drives but do not have interfacing for apple disk ii drives. the drives are available and could be used for archiving a vast number of floppies out there! this will require adding an index sensor so we can image disks into 'woz' formats. currently, applesauce hardware and software can do this for apple ii disks - applesauce is amazing and an excellent tool and we recommend it to folks! at this time, it appears to be closed source hardware, firmware and software, so we are not able to integrate their design into an open source design.
|
||||
|
||||
4. **adding woz/a2r support to greaseweazle / fluxengine**. once hardware support is in place, we can then add woz/a2r file format support to the open source tools in existence, which will benefit the entire community
|
||||
4. **adding a2r support to fluxengine** and [writing an open source a2r to woz converter](https://pypi.org/project/a2woz/), which will benefit the entire community.
|
||||
|
||||
5. as 'extra credit' we may look into **analog flux data acquisition methods** for repair of damaged disks.
|
||||
|
||||
Any hardware, firmware, or software we write is going to be fully open source under permissive licenses such as MIT, BSD or Unlicense. we will probably sell accessories, assembled PCBs, cables, etc in the Adafruit shop to help get hardware into folks hands but the designs will always be re-createable by others without any licensing agreements, NDAs, or discussion.
|
||||
|
||||
Currently we are focusing on high-RAM (> 128KB SRAM) and high speed (> 100MHz) processors, so that we can buffer a full track of flux transitions at once. Initial versions proved that this could be done with GPIO only, and no use of special peripherals. However, for the greatest timing accuracy we now use the timer peripheral on SAMD51 and the PIO peripheral on RP2040.
|
||||
|
||||
Tested working on:
|
||||
* SAMD51 chipset hardware - Overclock to 180MHz, select Fastest optimization, and use TinyUSB stack for best performance
|
||||
* RP2040 chipset hardware - Works with philhower core and TinyUSB stack. Overclock to 200MHz and select -O3 optimization for best performance
|
||||
|
||||
|
||||
## Follow our progress!
|
||||
|
||||
[We have a full playlist on YouTube of our progress!](https://www.youtube.com/playlist?list=PLjF7R1fz_OOWexf2WmY8cgM65ltPaAvyT), or check out some of these videos of the software at a very early stage of development:
|
||||
|
||||
https://user-images.githubusercontent.com/1685947/147865571-c9ea1d68-6603-436d-9980-bc5ade148db8.mp4
|
||||
|
||||
https://user-images.githubusercontent.com/1685947/147864181-c5885b15-1809-4e54-8680-4cfba3f54faa.mp4
|
||||
|
||||
Latest video Jan 1, 2022 - 9pm EDT
|
||||
|
||||
Currently we are focusing on high-RAM (> 128KB SRAM) and high speed (> 100MHz) processors, so that we can buffer a full track of flux transitions at once, and not require the use of special peripherals such as timers. (Of course, those are welcome later!)
|
||||
|
||||
Tested working on:
|
||||
* SAMD51 chipset hardware - Please overclock to 180MHz, select Fastest optimization, and use TinyUSB stack for best performance
|
||||
* RP2040 chipset hardware - Please use philhower core, overclock to 200MHz, and select -O3 optimization for best performance
|
||||
Longer version!
|
||||
|
||||
## Frequently Asked/Accused Questions
|
||||
|
||||
|
|
|
|||
1
doxygen-awesome-css
Submodule
1
doxygen-awesome-css
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 4cd62308d825fe0396d2f66ffbab45d0e247724c
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#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
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#endif
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#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);
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
Serial.println("its time for a nice floppy transfer!");
|
||||
Serial.print("Sample freqency ");
|
||||
Serial.print(floppy.getSampleFrequency() / 1e6);
|
||||
Serial.println("MHz");
|
||||
|
||||
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(floppy.get_write_protect() ? "Yes" : "No");
|
||||
Serial.print("Track 0? ");
|
||||
Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes");
|
||||
time_stamp = millis();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
144
examples/02_mfm_test/02_mfm_test.ino
Normal file
144
examples/02_mfm_test/02_mfm_test.ino
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#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
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#endif
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#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);
|
||||
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(100);
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
170
examples/03_fat_test/03_fat_test.ino
Normal file
170
examples/03_fat_test/03_fat_test.ino
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
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 defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#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
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#endif
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#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
|
||||
FatVolume fatfs;
|
||||
|
||||
File32 root;
|
||||
File32 file;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Wait for USB Serial
|
||||
while (!Serial) {
|
||||
yield();
|
||||
}
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
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!");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
// Init file system on the flash
|
||||
fatfs.begin(&mfm_floppy);
|
||||
|
||||
if (!root.open("/")) {
|
||||
Serial.println("open root failed");
|
||||
while (1) yield();
|
||||
}
|
||||
|
||||
// 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("\n\nRead a file? >");
|
||||
Serial.flush();
|
||||
delay(10);
|
||||
|
||||
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.
|
||||
File32 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);
|
||||
}
|
||||
}
|
||||
0
examples/04_msd_test/.feather_rp2040_tinyusb.generate
Normal file
0
examples/04_msd_test/.feather_rp2040_tinyusb.generate
Normal file
0
examples/04_msd_test/.floppsy_rp2040_tinyusb.generate
Normal file
0
examples/04_msd_test/.floppsy_rp2040_tinyusb.generate
Normal file
224
examples/04_msd_test/04_msd_test.ino
Normal file
224
examples/04_msd_test/04_msd_test.ino
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
// this example makes a lot of assumptions: MFM floppy which is already inserted
|
||||
// and only reading is supported - no write yet!
|
||||
|
||||
#include "Adafruit_TinyUSB.h"
|
||||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
// enable the display, though!
|
||||
#include "display_floppsy.h"
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#endif
|
||||
|
||||
Adafruit_USBD_MSC usb_msc;
|
||||
|
||||
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!)
|
||||
auto FLOPPY_TYPE = AUTODETECT;
|
||||
Adafruit_MFM_Floppy mfm_floppy(&floppy, FLOPPY_TYPE);
|
||||
|
||||
// To make a display on another board, check out "display_floppsy.h"; adapt it
|
||||
// to your board & include it
|
||||
#if defined(HAVE_DISPLAY)
|
||||
#include "display_common.h"
|
||||
#else
|
||||
#include "display_none.h"
|
||||
#endif
|
||||
|
||||
constexpr size_t SECTOR_SIZE = 512UL;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
#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(0, SECTOR_SIZE);
|
||||
// Set callbacks
|
||||
usb_msc.setReadyCallback(0, msc_ready_callback);
|
||||
usb_msc.setWritableCallback(0, msc_writable_callback);
|
||||
usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback,
|
||||
msc_flush_callback);
|
||||
|
||||
// floppy.debug_serial = &Serial;
|
||||
// Set Lun ready
|
||||
usb_msc.setUnitReady(false);
|
||||
Serial.println("Ready!");
|
||||
usb_msc.begin();
|
||||
|
||||
Serial.println("serial Ready!");
|
||||
|
||||
init_display();
|
||||
|
||||
floppy.begin();
|
||||
attachInterrupt(digitalPinToInterrupt(INDEX_PIN), count_index, FALLING);
|
||||
if (mfm_floppy.begin()) {
|
||||
mfm_floppy.inserted(FLOPPY_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
volatile uint32_t flush_time;
|
||||
|
||||
volatile uint32_t index_count;
|
||||
volatile uint32_t index_time, last_index_time;
|
||||
void count_index() {
|
||||
index_count += 1;
|
||||
last_index_time = index_time;
|
||||
index_time = millis();
|
||||
}
|
||||
|
||||
bool index_delayed, ready_delayed;
|
||||
uint32_t old_index_count;
|
||||
void loop() {
|
||||
noInterrupts();
|
||||
uint32_t now = millis();
|
||||
auto index = !digitalRead(INDEX_PIN);
|
||||
auto ready = digitalRead(READY_PIN);
|
||||
auto new_index_count = index_count;
|
||||
auto new_index_time = index_time;
|
||||
auto time_since_index = now - new_index_time;
|
||||
interrupts();
|
||||
|
||||
if (mfm_floppy.dirty() && now > flush_time) {
|
||||
noInterrupts();
|
||||
mfm_floppy.syncDevice();
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ready pin fell or no index for 400ms: media removed
|
||||
// (the check for nonzero index count is an attempt to future-proof against
|
||||
// a no-index 3.5" drive)
|
||||
bool removed =
|
||||
(!ready && ready_delayed) || (index_count && time_since_index > 1200);
|
||||
|
||||
if (removed) {
|
||||
if (mfm_floppy.sectorCount() != 0) {
|
||||
Serial.println("removed");
|
||||
mfm_floppy.removed();
|
||||
}
|
||||
}
|
||||
if (new_index_count != old_index_count) {
|
||||
if (mfm_floppy.sectorCount() == 0) {
|
||||
Serial.println("inserted");
|
||||
mfm_floppy.inserted(FLOPPY_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
maybe_update_display(false, new_index_count != old_index_count);
|
||||
ready_delayed = ready;
|
||||
old_index_count = new_index_count;
|
||||
}
|
||||
|
||||
// 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\r\n", lba, bufsize);
|
||||
auto result = mfm_floppy.readSectors(lba, reinterpret_cast<uint8_t *>(buffer),
|
||||
bufsize / MFM_BYTES_PER_SECTOR);
|
||||
return result ? bufsize : -1;
|
||||
}
|
||||
|
||||
// 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\r\n", lba, bufsize);
|
||||
auto sectors = bufsize / MFM_BYTES_PER_SECTOR;
|
||||
auto result = mfm_floppy.writeSectors(lba, buffer, sectors);
|
||||
if (result) {
|
||||
flush_time = millis() + 200;
|
||||
if (lba == 0 || (lba + sectors) == mfm_floppy.sectorCount()) {
|
||||
// If writing the first or last sector,
|
||||
mfm_floppy.syncDevice();
|
||||
}
|
||||
}
|
||||
return result ? bufsize : -1;
|
||||
}
|
||||
|
||||
// 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.print("flush\r\n");
|
||||
mfm_floppy.syncDevice();
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
bool msc_ready_callback(void) {
|
||||
// Serial.printf("ready callback -> %d\r\n", mfm_floppy.sectorCount());
|
||||
auto sectors = mfm_floppy.sectorCount();
|
||||
usb_msc.setCapacity(sectors, SECTOR_SIZE);
|
||||
return sectors != 0;
|
||||
}
|
||||
|
||||
bool msc_writable_callback(void) { return !floppy.get_write_protect(); }
|
||||
32
examples/04_msd_test/display_common.h
Normal file
32
examples/04_msd_test/display_common.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
bool ever_refreshed;
|
||||
|
||||
void update_display(bool force_refresh);
|
||||
|
||||
bool operator!=(const display_state &a, const display_state &b) {
|
||||
return a.capacity_kib != b.capacity_kib || a.trk0 != b.trk0 || a.wp != b.wp ||
|
||||
a.rdy != b.rdy || a.dirty != b.dirty || a.trk != b.trk ||
|
||||
a.side != b.side;
|
||||
}
|
||||
|
||||
void maybe_update_display(bool force_refresh, bool tick) {
|
||||
noInterrupts();
|
||||
new_state = display_state{
|
||||
mfm_floppy.sectorCount() / 2,
|
||||
floppy.get_track0_sense(),
|
||||
floppy.get_write_protect(),
|
||||
!!digitalRead(READY_PIN),
|
||||
mfm_floppy.dirty(),
|
||||
floppy.track(),
|
||||
floppy.get_side(),
|
||||
};
|
||||
interrupts();
|
||||
|
||||
force_refresh = force_refresh || !ever_refreshed;
|
||||
if (force_refresh || (old_state != new_state) || tick) {
|
||||
update_display(force_refresh);
|
||||
old_state = new_state;
|
||||
ever_refreshed = true;
|
||||
}
|
||||
}
|
||||
160
examples/04_msd_test/display_floppsy.h
Normal file
160
examples/04_msd_test/display_floppsy.h
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#pragma once
|
||||
#include "display_state.h"
|
||||
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
|
||||
#define HAVE_DISPLAY (1)
|
||||
|
||||
Adafruit_ST7789 display = Adafruit_ST7789(&SPI1, TFT_CS, TFT_DC, TFT_RESET);
|
||||
|
||||
enum { SZ = 3 };
|
||||
|
||||
static void setCursor(int x, int y) {
|
||||
display.setCursor(x * SZ * 6 + 12, y * SZ * 8 + 12);
|
||||
}
|
||||
|
||||
void init_display() {
|
||||
display.init(240, 240);
|
||||
display.fillScreen(0);
|
||||
pinMode(TFT_BACKLIGHT, OUTPUT);
|
||||
digitalWrite(TFT_BACKLIGHT, 1);
|
||||
display.setTextSize(SZ);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Convert hue, saturation and value into a packed 16-bit RGB color
|
||||
that can be passed to TFT
|
||||
@param H The Hue ranging from 0 to 359
|
||||
@param S Saturation, 8-bit value, 0 (min or pure grayscale) to 100
|
||||
(max or pure hue)
|
||||
@param V Value (brightness), 8-bit value, 0 (min / black / off) to
|
||||
100 (max or full brightness)
|
||||
@return Packed 16-bit 5-6-5 RGB. Result is linearly but not perceptually
|
||||
correct for LEDs. Intended for TFT use only.
|
||||
*/
|
||||
// https://gist.github.com/kuathadianto/200148f53616cbd226d993b400214a7f
|
||||
uint16_t ColorHSV565(int16_t H, uint8_t S = 100, uint8_t V = 100) {
|
||||
float C = S * V / 10000.0f;
|
||||
float X = C * (1 - abs(fmod(H / 60.0f, 2) - 1));
|
||||
float m = (V / 100.0f) - C;
|
||||
float Rs, Gs, Bs;
|
||||
|
||||
if (H >= 0 && H < 60) {
|
||||
Rs = C;
|
||||
Gs = X;
|
||||
Bs = 0;
|
||||
} else if (H >= 60 && H < 120) {
|
||||
Rs = X;
|
||||
Gs = C;
|
||||
Bs = 0;
|
||||
} else if (H >= 120 && H < 180) {
|
||||
Rs = 0;
|
||||
Gs = C;
|
||||
Bs = X;
|
||||
} else if (H >= 180 && H < 240) {
|
||||
Rs = 0;
|
||||
Gs = X;
|
||||
Bs = C;
|
||||
} else if (H >= 240 && H < 300) {
|
||||
Rs = X;
|
||||
Gs = 0;
|
||||
Bs = C;
|
||||
} else {
|
||||
Rs = C;
|
||||
Gs = 0;
|
||||
Bs = X;
|
||||
}
|
||||
|
||||
uint8_t red = (Rs + m) * 255;
|
||||
uint8_t green = (Gs + m) * 255;
|
||||
uint8_t blue = (Bs + m) * 255;
|
||||
return display.color565(red, green, blue);
|
||||
}
|
||||
|
||||
void update_display(bool force_refresh) {
|
||||
int x = 3;
|
||||
int y = 3;
|
||||
|
||||
if (force_refresh) {
|
||||
display.fillScreen(0);
|
||||
}
|
||||
|
||||
static int phase = 0;
|
||||
phase = phase + 53;
|
||||
|
||||
// Top row
|
||||
int row = 0;
|
||||
setCursor(2, 0);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
display.setTextColor(ColorHSV565((i * 360 / 7 + phase) % 360), 0);
|
||||
display.print("FLOPPSY"[i]);
|
||||
}
|
||||
|
||||
// Media row
|
||||
row += 2;
|
||||
if (force_refresh || new_state.capacity_kib != old_state.capacity_kib) {
|
||||
setCursor(0, row);
|
||||
Serial.printf("row 2 dirty capacity_kib=%d\n", new_state.capacity_kib);
|
||||
if (new_state.capacity_kib) {
|
||||
display.setTextColor(ST77XX_WHITE, 0);
|
||||
display.printf("%d KiB ", new_state.capacity_kib);
|
||||
} else {
|
||||
display.setTextColor(ST77XX_MAGENTA, 0);
|
||||
display.printf("NO MEDIA");
|
||||
}
|
||||
}
|
||||
|
||||
// Head position row
|
||||
row += 3;
|
||||
printf("new trk=%d old_trk=%d\n", new_state.trk, old_state.trk);
|
||||
if (force_refresh || new_state.trk != old_state.trk) {
|
||||
if (force_refresh) {
|
||||
setCursor(0, row);
|
||||
display.setTextColor(ST77XX_WHITE, 0);
|
||||
display.print("T:");
|
||||
}
|
||||
setCursor(2, row);
|
||||
if (new_state.trk < 0 || new_state.trk > 99) {
|
||||
display.setTextColor(ST77XX_RED, 0);
|
||||
display.print("??");
|
||||
} else {
|
||||
display.setTextColor(ST77XX_GREEN, 0);
|
||||
display.printf("%02d", new_state.trk);
|
||||
}
|
||||
}
|
||||
if (force_refresh || new_state.side != old_state.side) {
|
||||
display.setTextColor(ST77XX_WHITE, 0);
|
||||
if (force_refresh) {
|
||||
setCursor(5, row);
|
||||
display.print("S:");
|
||||
}
|
||||
setCursor(7, row);
|
||||
display.printf("%d", new_state.side);
|
||||
}
|
||||
|
||||
// Dirty row
|
||||
row += 2;
|
||||
if (force_refresh || new_state.dirty != old_state.dirty) {
|
||||
display.setTextColor(ST77XX_MAGENTA, 0);
|
||||
setCursor(0, row);
|
||||
display.print(new_state.dirty ? "dirty" : " ");
|
||||
}
|
||||
|
||||
// Sense row
|
||||
row += 1;
|
||||
if (force_refresh || new_state.trk0 != old_state.trk0) {
|
||||
setCursor(0, row);
|
||||
display.setTextColor(ST77XX_GREEN, 0);
|
||||
display.print(new_state.trk0 ? "TRK0" : " ");
|
||||
};
|
||||
|
||||
if (force_refresh || new_state.wp != old_state.wp) {
|
||||
setCursor(5, row);
|
||||
display.setTextColor(ST77XX_MAGENTA, 0);
|
||||
display.print(new_state.wp ? "R/O" : " ");
|
||||
};
|
||||
|
||||
if (force_refresh || new_state.rdy != old_state.rdy) {
|
||||
setCursor(9, row);
|
||||
display.setTextColor(ST77XX_CYAN, 0);
|
||||
display.print(new_state.rdy ? "RDY" : " ");
|
||||
};
|
||||
}
|
||||
4
examples/04_msd_test/display_none.h
Normal file
4
examples/04_msd_test/display_none.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
void init_display() {}
|
||||
void maybe_update_display(bool, bool) {}
|
||||
9
examples/04_msd_test/display_state.h
Normal file
9
examples/04_msd_test/display_state.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
struct display_state {
|
||||
size_t capacity_kib;
|
||||
bool trk0, wp, rdy, dirty;
|
||||
int8_t trk, side;
|
||||
};
|
||||
|
||||
display_state old_state, new_state;
|
||||
143
examples/05_mfm_write_test/05_mfm_write_test.ino
Normal file
143
examples/05_mfm_write_test/05_mfm_write_test.ino
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // IDC 28
|
||||
#define READ_PIN 9 // IDC 30
|
||||
#define SIDE_PIN 8 // IDC 32
|
||||
#define READY_PIN 7 // IDC 34
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#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
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#endif
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#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);
|
||||
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
while (!Serial) delay(100);
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void hexdump(size_t offset, const uint8_t *data, size_t n) {
|
||||
for (size_t i = 0; i < n; i += 16) {
|
||||
size_t addr = offset + i;
|
||||
Serial.printf("%08x", addr);
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
if(i+j > n) Serial.printf(" ");else
|
||||
Serial.printf(" %02x", mfm_floppy.track_data[addr + j]);
|
||||
}
|
||||
Serial.print(" | ");
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
if(i+j > n) break;
|
||||
uint8_t d = mfm_floppy.track_data[addr + j];
|
||||
if (! isprint(d)) {
|
||||
d = ' ';
|
||||
}
|
||||
Serial.write(d);
|
||||
}
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t track = 0;
|
||||
bool head = 0;
|
||||
int i = 0;
|
||||
void loop() {
|
||||
int32_t captured_sectors;
|
||||
|
||||
uint8_t sector[512];
|
||||
int lba = (i++ % 2 == 0) ? 0 : 18;
|
||||
if (!mfm_floppy.readSector(lba, sector)) {
|
||||
Serial.println("Failed to read sector");
|
||||
return;
|
||||
}
|
||||
|
||||
hexdump(lba * 512, sector, 512);
|
||||
|
||||
memset(sector, 0, 512);
|
||||
snprintf(reinterpret_cast<char*>(sector), sizeof(sector), "Hello from iteration %zd of Adafruit Floppy MFM writing\n", i);
|
||||
|
||||
if (!mfm_floppy.writeSector(lba, sector)) {
|
||||
Serial.println("Failed to write sectorn");
|
||||
return;
|
||||
}
|
||||
if (!mfm_floppy.syncDevice()) {
|
||||
Serial.println("Failed to sync device");
|
||||
return;
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
160
examples/99_floppy_write_test/99_floppy_write_test.ino
Normal file
160
examples/99_floppy_write_test/99_floppy_write_test.ino
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// This example will ERASE TRACK 0 on a floppy disk to test if flux writing
|
||||
// is functioning
|
||||
// DO NOT RUN IT ON A FLOPPY YOU WISH TO KEEP DATA!
|
||||
// IT MUST BE REFORMATTED AFTER WRITING!
|
||||
|
||||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#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);
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
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() {
|
||||
// Flush input of serial port
|
||||
while (Serial.available()) Serial.read();
|
||||
|
||||
// Warn them again!
|
||||
Serial.println("Are you SURE you want to run the write test?");
|
||||
Serial.println("THIS WILL PERMANENTLY ERASE ANY DATA ON THE FLOPPY DISK!!!");
|
||||
Serial.println("Type Y to continue...");
|
||||
while (! Serial.available()) yield();
|
||||
if (Serial.read() != 'Y') return;
|
||||
|
||||
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();
|
||||
}
|
||||
78
examples/apple2_test/apple2_test.ino
Normal file
78
examples/apple2_test/apple2_test.ino
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define APPLE2_ENABLE_PIN (6)
|
||||
#define APPLE2_PHASE1_PIN (A2)
|
||||
#define APPLE2_PHASE2_PIN (13)
|
||||
#define APPLE2_PHASE3_PIN (12)
|
||||
#define APPLE2_PHASE4_PIN (11)
|
||||
#define APPLE2_RDDATA_PIN (5)
|
||||
#define APPLE2_INDEX_PIN (A3)
|
||||
#define APPLE2_PROTECT_PIN (21) // "SDA"
|
||||
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
#define APPLE2_ENABLE_PIN (8) // D6
|
||||
#define APPLE2_PHASE1_PIN (A2)
|
||||
#define APPLE2_PHASE2_PIN (13)
|
||||
#define APPLE2_PHASE3_PIN (12)
|
||||
#define APPLE2_PHASE4_PIN (11)
|
||||
#define APPLE2_RDDATA_PIN (7) // D5
|
||||
#define APPLE2_INDEX_PIN (A3)
|
||||
#define APPLE2_PROTECT_PIN (2) // "SDA"
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Apple2Floppy floppy(APPLE2_INDEX_PIN, APPLE2_ENABLE_PIN,
|
||||
APPLE2_PHASE1_PIN, APPLE2_PHASE2_PIN, APPLE2_PHASE3_PIN, APPLE2_PHASE4_PIN,
|
||||
-1, -1, APPLE2_PROTECT_PIN, APPLE2_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,112 +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
|
||||
#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 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
|
||||
#if F_CPU != 200000000L
|
||||
#warning "please set CPU speed to 200MHz overclock"
|
||||
#endif
|
||||
#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;
|
||||
floppy.begin();
|
||||
|
||||
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() {
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
0
examples/greaseweazle/.feather_rp2040_tinyusb.generate
Normal file
0
examples/greaseweazle/.feather_rp2040_tinyusb.generate
Normal file
0
examples/greaseweazle/.floppsy_rp2040_tinyusb.generate
Normal file
0
examples/greaseweazle/.floppsy_rp2040_tinyusb.generate
Normal file
|
|
@ -1,67 +1,88 @@
|
|||
#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
|
||||
#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
|
||||
#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
|
||||
#if F_CPU != 200000000L
|
||||
#warning "please set CPU speed to 200MHz overclock"
|
||||
#endif
|
||||
#define GW_SAMPLEFREQ 26000000UL // 26mhz for rp2040
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
|
||||
#ifndef USE_TINYUSB
|
||||
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
|
||||
#endif
|
||||
|
||||
#if defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
|
||||
// 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)
|
||||
#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
|
||||
#pragma message "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
|
||||
#pragma message "This firmware will not support Apple ][ drives"
|
||||
#endif
|
||||
|
||||
Adafruit_FloppyBase *floppy;
|
||||
|
||||
uint32_t time_stamp = 0;
|
||||
|
||||
|
|
@ -85,36 +106,87 @@ 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_GW 4
|
||||
#define GW_CMD_SETBUSTYPE_APPLE2_FLUXENGINE 3
|
||||
#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_GW:
|
||||
case GW_CMD_SETBUSTYPE_APPLE2_FLUXENGINE:
|
||||
floppy = &apple2floppy;
|
||||
apple2floppy.step_mode(Adafruit_Apple2Floppy::STEP_MODE_QUARTER);
|
||||
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);
|
||||
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, HIGH);
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
delay(100);
|
||||
|
||||
//while (!Serial) delay(100);
|
||||
Serial1.println("GrizzlyWizzly");
|
||||
|
||||
floppy.debug_serial = &Serial1;
|
||||
floppy.begin();
|
||||
timestamp = millis();
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
@ -124,7 +196,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;
|
||||
|
|
@ -136,18 +208,40 @@ 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) return;
|
||||
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();
|
||||
|
||||
|
||||
int i = 0;
|
||||
uint8_t cmd = cmd_buffer[0];
|
||||
uint8_t len = cmd_buffer[1];
|
||||
memset(reply_buffer, 0, sizeof(reply_buffer));
|
||||
reply_buffer[i++] = cmd; // echo back the cmd itself
|
||||
|
||||
Serial1.printf("Got command 0x%02x\n\r", cmd);
|
||||
Serial1.printf("Got command 0x%02x of length %d\n\r", cmd, len);
|
||||
|
||||
|
||||
if (cmd == GW_CMD_GETINFO) {
|
||||
Serial1.println("Get info");
|
||||
|
|
@ -158,10 +252,11 @@ void loop() {
|
|||
reply_buffer[i++] = GW_FIRMVER_MINOR; // 1 byte
|
||||
reply_buffer[i++] = 1; // is main firm
|
||||
reply_buffer[i++] = GW_MAXCMD;
|
||||
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;
|
||||
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_HW_MODEL;
|
||||
reply_buffer[i++] = GW_HW_SUBMODEL;
|
||||
reply_buffer[i++] = GW_USB_SPEED;
|
||||
|
|
@ -202,47 +297,51 @@ 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");
|
||||
floppy.soft_reset();
|
||||
if (floppy)
|
||||
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];
|
||||
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;
|
||||
auto result = setbustype(bustype);
|
||||
Serial1.printf("Set bus type %d -> %d\n\r", bustype, result);
|
||||
if (result) {
|
||||
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) {
|
||||
uint8_t track = cmd_buffer[2];
|
||||
if (!floppy) goto needfloppy;
|
||||
|
||||
int track = cmd_buffer[2];
|
||||
if (len > 3) {
|
||||
track |= cmd_buffer[3] << 8;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -252,45 +351,56 @@ void loop() {
|
|||
}
|
||||
|
||||
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 (! floppy.spin_motor(state)) {
|
||||
reply_buffer[i++] = GW_ACK_NOINDEX;
|
||||
} else {
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
if (motor_state != state) { // we're in the opposite state
|
||||
floppy->spin_motor(state);
|
||||
motor_state = state;
|
||||
}
|
||||
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];
|
||||
|
|
@ -303,31 +413,78 @@ void loop() {
|
|||
revs = cmd_buffer[7];
|
||||
revs <<= 8;
|
||||
revs |= cmd_buffer[6];
|
||||
bool use_index;
|
||||
if (revs) {
|
||||
revs -= 1;
|
||||
use_index = true;
|
||||
} else {
|
||||
use_index = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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--) {
|
||||
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;
|
||||
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,
|
||||
/* index wait ms */ use_index ? 250 : 0);
|
||||
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
|
||||
reply_buffer[1] = 1; // index opcode
|
||||
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
|
||||
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);
|
||||
|
||||
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));
|
||||
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);
|
||||
|
|
@ -336,12 +493,50 @@ void loop() {
|
|||
captured_pulses -= to_send;
|
||||
}
|
||||
}
|
||||
Serial.write((byte)0);
|
||||
}
|
||||
// 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, cue_at_index);
|
||||
Serial1.println("wrote fluxors");
|
||||
Serial.write((byte)0);
|
||||
|
||||
}
|
||||
}
|
||||
else if (cmd == GW_CMD_GETFLUXSTATUS) {
|
||||
Serial1.println("get flux status");
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
reply_buffer[i++] = flux_status ? GW_ACK_OK : GW_ACK_BADPIN;
|
||||
Serial.write(reply_buffer, 2);
|
||||
}
|
||||
|
||||
|
|
@ -426,7 +621,7 @@ void loop() {
|
|||
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++) {
|
||||
for (uint32_t i = 0; i < to_write; i++) {
|
||||
reply_buffer[i] = randnum;
|
||||
if (randnum & 0x01) {
|
||||
randnum = (randnum >> 1) ^ 0x80000062;
|
||||
|
|
@ -446,21 +641,55 @@ void loop() {
|
|||
Serial1.println(" bytes per sec");
|
||||
} else if (cmd == GW_CMD_GETPIN) {
|
||||
uint32_t pin = cmd_buffer[2];
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
Serial1.printf("getpin %d\n\r", pin);
|
||||
|
||||
switch(pin) {
|
||||
case 26:
|
||||
reply_buffer[i++] = digitalRead(TRK0_PIN);
|
||||
switch (pin) {
|
||||
case GW_CMD_GETPIN_TRACK0:
|
||||
reply_buffer[i++] = GW_ACK_OK;
|
||||
reply_buffer[i++] = !floppy->get_track0_sense();
|
||||
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 ! ********/
|
||||
} 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!");
|
||||
}
|
||||
0
examples/mfm_emu/.floppsy_rp2040_tinyusb.generate
Normal file
0
examples/mfm_emu/.floppsy_rp2040_tinyusb.generate
Normal file
0
examples/mfm_emu/.floppsy_rp2040_tinyusb.test.only
Normal file
0
examples/mfm_emu/.floppsy_rp2040_tinyusb.test.only
Normal file
1
examples/mfm_emu/.gitignore
vendored
Normal file
1
examples/mfm_emu/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
custom_pinout.h
|
||||
51
examples/mfm_emu/drive.pio
Normal file
51
examples/mfm_emu/drive.pio
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
; When updating this file, you must manually run `pioasm drive.pio > drive.pio.h`!
|
||||
; The Arduino IDE does not do this step!
|
||||
|
||||
.program fluxout_compact
|
||||
.pio_version 0
|
||||
.out 1 left auto 32
|
||||
|
||||
.wrap_target
|
||||
out pins, 1
|
||||
.wrap
|
||||
set pins, 0 [3] ;; for FM fluxing, the wrap point is moved just after this one
|
||||
|
||||
%c-sdk {
|
||||
void sm_config_set_clk_ns(pio_sm_config *c, uint time_ns) {
|
||||
float f = clock_get_hz(clk_sys) * 1e-9 * time_ns;
|
||||
int scaled_clkdiv = (int)roundf(f * 256);
|
||||
sm_config_set_clkdiv_int_frac(c, scaled_clkdiv / 256, scaled_clkdiv % 256);
|
||||
}
|
||||
|
||||
static inline void fluxout_compact_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) {
|
||||
pio_sm_config c = fluxout_compact_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, pin, 1);
|
||||
sm_config_set_set_pins(&c, pin, 1);
|
||||
sm_config_set_clk_ns(&c, bit_time_ns);
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
|
||||
.program index_pulse
|
||||
.side_set 1
|
||||
|
||||
pull block side 1
|
||||
mov x, osr side 0
|
||||
loop:
|
||||
jmp x--, loop side 0
|
||||
|
||||
% c-sdk {
|
||||
static inline void index_pulse_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) {
|
||||
pio_sm_config c = index_pulse_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
sm_config_set_sideset(&c, 1, /* optional */ false, /* pin direction */ false);
|
||||
sm_config_set_clk_ns(&c, bit_time_ns);
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, /* is_out */ true);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
109
examples/mfm_emu/drive.pio.h
Normal file
109
examples/mfm_emu/drive.pio.h
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// -------------------------------------------------- //
|
||||
// This file is autogenerated by pioasm; do not edit! //
|
||||
// -------------------------------------------------- //
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
#include "hardware/pio.h"
|
||||
#endif
|
||||
|
||||
// --------------- //
|
||||
// fluxout_compact //
|
||||
// --------------- //
|
||||
|
||||
#define fluxout_compact_wrap_target 0
|
||||
#define fluxout_compact_wrap 0
|
||||
#define fluxout_compact_pio_version 0
|
||||
|
||||
static const uint16_t fluxout_compact_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0x6001, // 0: out pins, 1
|
||||
// .wrap
|
||||
0xe300, // 1: set pins, 0 [3]
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program fluxout_compact_program = {
|
||||
.instructions = fluxout_compact_program_instructions,
|
||||
.length = 2,
|
||||
.origin = -1,
|
||||
.pio_version = 0,
|
||||
#if PICO_PIO_VERSION > 0
|
||||
.used_gpio_ranges = 0x0
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline pio_sm_config fluxout_compact_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + fluxout_compact_wrap_target, offset + fluxout_compact_wrap);
|
||||
sm_config_set_out_pin_count(&c, 1);
|
||||
sm_config_set_out_shift(&c, 0, 1, 32);
|
||||
return c;
|
||||
}
|
||||
|
||||
void sm_config_set_clk_ns(pio_sm_config *c, uint time_ns) {
|
||||
float f = clock_get_hz(clk_sys) * 1e-9 * time_ns;
|
||||
int scaled_clkdiv = (int)roundf(f * 256);
|
||||
sm_config_set_clkdiv_int_frac(c, scaled_clkdiv / 256, scaled_clkdiv % 256);
|
||||
}
|
||||
static inline void fluxout_compact_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) {
|
||||
pio_sm_config c = fluxout_compact_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, pin, 1);
|
||||
sm_config_set_set_pins(&c, pin, 1);
|
||||
sm_config_set_clk_ns(&c, bit_time_ns);
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ----------- //
|
||||
// index_pulse //
|
||||
// ----------- //
|
||||
|
||||
#define index_pulse_wrap_target 0
|
||||
#define index_pulse_wrap 2
|
||||
#define index_pulse_pio_version 0
|
||||
|
||||
static const uint16_t index_pulse_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0x90a0, // 0: pull block side 1
|
||||
0xa027, // 1: mov x, osr side 0
|
||||
0x0042, // 2: jmp x--, 2 side 0
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program index_pulse_program = {
|
||||
.instructions = index_pulse_program_instructions,
|
||||
.length = 3,
|
||||
.origin = -1,
|
||||
.pio_version = 0,
|
||||
#if PICO_PIO_VERSION > 0
|
||||
.used_gpio_ranges = 0x0
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline pio_sm_config index_pulse_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + index_pulse_wrap_target, offset + index_pulse_wrap);
|
||||
sm_config_set_sideset(&c, 1, false, false);
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline void index_pulse_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) {
|
||||
pio_sm_config c = index_pulse_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
sm_config_set_sideset(&c, 1, /* optional */ false, /* pin direction */ false);
|
||||
sm_config_set_clk_ns(&c, bit_time_ns);
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, /* is_out */ true);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
467
examples/mfm_emu/mfm_emu.ino
Normal file
467
examples/mfm_emu/mfm_emu.ino
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
|
||||
|
||||
#define WAIT_SERIAL
|
||||
#define XEROX_820
|
||||
// #define USE_CUSTOM_PINOUT
|
||||
|
||||
#if defined(USE_CUSTOM_PINOUT) && __has_include("custom_pinout.h")
|
||||
#warning Using custom pinout
|
||||
#include "custom_pinout.h"
|
||||
#elif defined(ADAFRUIT_FEATHER_M4_EXPRESS)
|
||||
#define DENSITY_PIN A1 // IDC 2
|
||||
#define INDEX_PIN A5 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN A4 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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 A1 // IDC 2
|
||||
#define INDEX_PIN 25 // IDC 8
|
||||
#define SELECT_PIN A0 // IDC 12
|
||||
#define MOTOR_PIN A2 // IDC 16
|
||||
#define DIR_PIN A3 // IDC 18
|
||||
#define STEP_PIN 24 // IDC 20
|
||||
#define WRDATA_PIN 13 // IDC 22
|
||||
#define WRGATE_PIN 12 // IDC 24
|
||||
#define TRK0_PIN 10 // IDC 26
|
||||
#define PROT_PIN 11 // 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
|
||||
#elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040)
|
||||
// Yay built in pin definitions!
|
||||
#define NEOPIXEL_PIN PIN_NEOPIXEL
|
||||
#else
|
||||
#error "Please set up pin definitions!"
|
||||
#endif
|
||||
|
||||
#if defined(NEOPIXEL_PIN)
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
#ifndef NEOPIXEL_COUNT
|
||||
#define NEOPIXEL_COUNT (1)
|
||||
#endif
|
||||
|
||||
#ifndef NEOPIXEL_FORMAT
|
||||
#define NEOPIXEL_FORMAT NEO_GRB + NEO_KHZ800
|
||||
#endif
|
||||
|
||||
Adafruit_NeoPixel strip(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_FORMAT);
|
||||
|
||||
#define STATUS_RGB(r, g, b) \
|
||||
do { \
|
||||
strip.fill(strip.Color(r, g, b)); \
|
||||
strip.show(); \
|
||||
} while (0)
|
||||
#else
|
||||
#define STATUS_RGB(r, g, b) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#include "drive.pio.h"
|
||||
#define DEBUG_PRINTF(...) Serial.printf(__VA_ARGS__)
|
||||
#define DEBUG_ASSERT(x) \
|
||||
do { \
|
||||
if (!(x)) { \
|
||||
Serial.printf(__FILE__ ":%d: Assert fail: " #x "\n", __LINE__); \
|
||||
} \
|
||||
} while (0)
|
||||
#include "mfm_impl.h"
|
||||
|
||||
enum {
|
||||
max_flux_bits = 200000 // 300RPM (200ms rotational time), 1us bit times
|
||||
};
|
||||
enum { max_flux_count_long = (max_flux_bits + 31) / 32 };
|
||||
|
||||
// Data shared between the two CPU cores
|
||||
volatile int fluxout; // side number 0/1 or -1 if no flux should be generated
|
||||
volatile size_t flux_count_long =
|
||||
max_flux_count_long; // in units of uint32_ts (longs)
|
||||
volatile uint32_t flux_data[2]
|
||||
[max_flux_count_long]; // one track of flux data for
|
||||
// both sides of the disk
|
||||
|
||||
////////////////////////////////
|
||||
// Code & data for core 1
|
||||
// Generate index pulses & flux
|
||||
////////////////////////////////
|
||||
#define FLUX_OUT_PIN (READ_PIN) // "read pin" is named from the controller's POV
|
||||
|
||||
PIO pio = pio0;
|
||||
uint sm_fluxout, offset_fluxout;
|
||||
uint sm_index_pulse, offset_index_pulse;
|
||||
|
||||
volatile bool early_setup_done;
|
||||
|
||||
void setup1() {
|
||||
while (!early_setup_done) {
|
||||
}
|
||||
}
|
||||
|
||||
void __not_in_flash_func(loop1)() {
|
||||
static bool once;
|
||||
if (fluxout >= 0) {
|
||||
pio_sm_put_blocking(pio, sm_index_pulse,
|
||||
4000); // ??? put index high for 4ms (out of 200ms)
|
||||
for (size_t i = 0; i < flux_count_long; i++) {
|
||||
int f = fluxout;
|
||||
if (f < 0)
|
||||
break;
|
||||
auto d = flux_data[fluxout][i];
|
||||
pio_sm_put_blocking(pio, sm_fluxout, __builtin_bswap32(d));
|
||||
}
|
||||
// terminate index pulse if ongoing
|
||||
pio_sm_exec(pio, sm_index_pulse,
|
||||
0 | offset_index_pulse); // JMP to the first instruction
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Code & data for core 0
|
||||
// "UI", control signal handling & MFM encoding
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Set via IRQ so must be volatile
|
||||
volatile int trackno;
|
||||
|
||||
enum {
|
||||
max_sector_count = 18,
|
||||
mfm_io_block_size = 512,
|
||||
track_max_bytes = max_sector_count * mfm_io_block_size
|
||||
};
|
||||
|
||||
uint8_t track_data[track_max_bytes];
|
||||
|
||||
void onStep() {
|
||||
auto enabled =
|
||||
!digitalRead(SELECT_PIN); // motor need not be enabled to seek tracks
|
||||
auto direction = digitalRead(DIR_PIN);
|
||||
int new_track = trackno;
|
||||
if (direction) {
|
||||
if (new_track > 0)
|
||||
new_track--;
|
||||
} else {
|
||||
if (new_track < 79)
|
||||
new_track++;
|
||||
}
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
trackno = new_track;
|
||||
digitalWrite(TRK0_PIN, trackno != 0); // active LOW
|
||||
}
|
||||
|
||||
#if defined(PIN_CARD_CS)
|
||||
#define USE_SDFAT (1)
|
||||
#include "SdFat.h"
|
||||
SdFat SD;
|
||||
FsFile dir;
|
||||
FsFile file;
|
||||
|
||||
struct floppy_format_info_t {
|
||||
uint8_t cylinders, sectors, sides; // number of sides may be 1 or 2
|
||||
uint16_t bit_time_ns;
|
||||
size_t flux_count_bit;
|
||||
uint8_t n; // sector size is 128<<n
|
||||
bool is_fm;
|
||||
};
|
||||
|
||||
const struct floppy_format_info_t format_info[] = {
|
||||
{80, 18, 2, 1000, 200000, 2, false}, // 3.5" 1440kB, 300RPM
|
||||
{80, 9, 2, 2000, 100000, 2, false}, // 3.5" 720kB, 300RPM
|
||||
|
||||
{80, 15, 2, 1000, 166667, 2, false}, // 5.25" 1200kB, 360RPM
|
||||
{40, 9, 2, 2000, 100000, 2, false}, // 5.25" 360kB, 300RPM
|
||||
|
||||
{77, 26, 1, 2000, 80000, 0, true}, // 8" 256kB, 360RPM
|
||||
};
|
||||
|
||||
const floppy_format_info_t *cur_format = &format_info[0];
|
||||
|
||||
void pio_sm_set_clk_ns(PIO pio, uint sm, uint time_ns) {
|
||||
Serial.printf("set_clk_ns %u\n", time_ns);
|
||||
float f = clock_get_hz(clk_sys) * 1e-9 * time_ns;
|
||||
int scaled_clkdiv = (int)roundf(f * 256);
|
||||
pio_sm_set_clkdiv_int_frac(pio, sm, scaled_clkdiv / 256, scaled_clkdiv % 256);
|
||||
}
|
||||
|
||||
bool setFormat(size_t size) {
|
||||
cur_format = NULL;
|
||||
for (const auto &i : format_info) {
|
||||
auto img_size = (size_t)i.sectors * i.cylinders * i.sides * (128 << i.n);
|
||||
if (size != img_size)
|
||||
continue;
|
||||
cur_format = &i;
|
||||
if (cur_format->is_fm) {
|
||||
pio_sm_set_wrap(pio, sm_fluxout, offset_fluxout, offset_fluxout + 1);
|
||||
pio_sm_set_clk_ns(pio, sm_fluxout, i.bit_time_ns / 4);
|
||||
gpio_set_outover(FLUX_OUT_PIN, GPIO_OVERRIDE_INVERT);
|
||||
} else {
|
||||
pio_sm_set_wrap(pio, sm_fluxout, offset_fluxout, offset_fluxout + 0);
|
||||
pio_sm_set_clk_ns(pio, sm_fluxout, i.bit_time_ns);
|
||||
gpio_set_outover(FLUX_OUT_PIN, GPIO_OVERRIDE_NORMAL);
|
||||
}
|
||||
flux_count_long = (i.flux_count_bit + 31) / 32;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void openNextImage() {
|
||||
bool rewound = false;
|
||||
while (true) {
|
||||
auto res = file.openNext(&dir, O_RDONLY);
|
||||
if (!res) {
|
||||
if (rewound) {
|
||||
Serial.println("No image found");
|
||||
return;
|
||||
}
|
||||
dir.rewind();
|
||||
rewound = true;
|
||||
continue;
|
||||
}
|
||||
file.printFileSize(&Serial);
|
||||
Serial.write(' ');
|
||||
file.printModifyDateTime(&Serial);
|
||||
Serial.write(' ');
|
||||
file.printName(&Serial);
|
||||
if (file.isDir()) {
|
||||
// Indicate a directory.
|
||||
Serial.println("/");
|
||||
continue;
|
||||
}
|
||||
if (setFormat(file.fileSize())) {
|
||||
Serial.printf(": Valid floppy image\n");
|
||||
return;
|
||||
} else {
|
||||
Serial.println(": Unrecognized file length\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
#if defined(FLOPPY_DIRECTION_PIN)
|
||||
pinMode(FLOPPY_DIRECTION_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_DIRECTION_PIN, LOW); // we are emulating a floppy
|
||||
#endif
|
||||
#if defined(FLOPPY_ENABLE_PIN)
|
||||
pinMode(FLOPPY_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction
|
||||
#endif
|
||||
|
||||
offset_fluxout = pio_add_program(pio, &fluxout_compact_program);
|
||||
sm_fluxout = pio_claim_unused_sm(pio, true);
|
||||
fluxout_compact_program_init(pio, sm_fluxout, offset_fluxout, FLUX_OUT_PIN,
|
||||
1000);
|
||||
|
||||
offset_index_pulse = pio_add_program(pio, &index_pulse_program);
|
||||
sm_index_pulse = pio_claim_unused_sm(pio, true);
|
||||
index_pulse_program_init(pio, sm_index_pulse, offset_index_pulse, INDEX_PIN,
|
||||
1000);
|
||||
early_setup_done = true;
|
||||
|
||||
pinMode(DIR_PIN, INPUT_PULLUP);
|
||||
pinMode(STEP_PIN, INPUT_PULLUP);
|
||||
pinMode(SIDE_PIN, INPUT_PULLUP);
|
||||
pinMode(MOTOR_PIN, INPUT_PULLUP);
|
||||
pinMode(SELECT_PIN, INPUT_PULLUP);
|
||||
pinMode(TRK0_PIN, OUTPUT);
|
||||
pinMode(READY_PIN, OUTPUT);
|
||||
digitalWrite(READY_PIN, HIGH); // active low
|
||||
#if defined(PROT_PIN)
|
||||
pinMode(PROT_PIN, OUTPUT);
|
||||
digitalWrite(PROT_PIN, LOW); // always write-protected, no write support
|
||||
#endif
|
||||
#if defined(DISKCHANGE_PIN)
|
||||
pinMode(DISKCHANGE_PIN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
Serial.begin(115200);
|
||||
#if defined(WAIT_SERIAL)
|
||||
while (!Serial) {
|
||||
}
|
||||
Serial.println("Serial connected");
|
||||
#endif
|
||||
|
||||
#if defined(XEROX_820)
|
||||
pinMode(DENSITY_PIN, OUTPUT);
|
||||
digitalWrite(DENSITY_PIN,
|
||||
HIGH); // Xerox 820 density select HIGH means 8" floppy
|
||||
pinMode(READY_PIN, OUTPUT);
|
||||
digitalWrite(READY_PIN, LOW); // Drive always reports readiness
|
||||
Serial.println("Configured for Xerox 820 8\" floppy emulation");
|
||||
#endif
|
||||
|
||||
attachInterrupt(digitalPinToInterrupt(STEP_PIN), onStep, FALLING);
|
||||
|
||||
#if defined(NEOPIXEL_PIN)
|
||||
strip.begin();
|
||||
#endif
|
||||
|
||||
#if USE_SDFAT
|
||||
if (!SD.begin(PIN_CARD_CS)) {
|
||||
Serial.println("SD card initialization failed");
|
||||
STATUS_RGB(255, 0, 0);
|
||||
delay(2000);
|
||||
} else if (!dir.open("/")) {
|
||||
Serial.println("SD card directory could not be read");
|
||||
STATUS_RGB(255, 255, 0);
|
||||
delay(2000);
|
||||
} else {
|
||||
STATUS_RGB(0, 0, 255);
|
||||
openNextImage();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void encode_track(uint8_t head, uint8_t cylinder) {
|
||||
|
||||
mfm_io_t io = {
|
||||
.encode_compact = true,
|
||||
.pulses = (uint8_t *)flux_data[head],
|
||||
.n_pulses = flux_count_long * sizeof(long),
|
||||
.sectors = track_data,
|
||||
.n_sectors = cur_format->sectors,
|
||||
.head = head,
|
||||
.cylinder = cylinder,
|
||||
.n = cur_format->n,
|
||||
.settings = cur_format->is_fm ? &standard_fm : &standard_mfm,
|
||||
};
|
||||
|
||||
size_t pos = encode_track_mfm(&io);
|
||||
Serial.printf("Encoded to %zu flux\n", pos);
|
||||
}
|
||||
|
||||
// As an easter egg, the dummy disk image embeds the boot sector Tetris
|
||||
// implementation from https://github.com/daniel-e/tetros (source available
|
||||
// under MIT license)
|
||||
const uint8_t tetros[] = {
|
||||
#include "tetros.h"
|
||||
};
|
||||
|
||||
static void make_dummy_data(uint8_t head, uint8_t cylinder, size_t n_bytes) {
|
||||
uint8_t dummy_byte = head * 2 + cylinder;
|
||||
std::fill(track_data, track_data + n_bytes, dummy_byte);
|
||||
if (head == 0 && cylinder == 0 && n_bytes >= 512) {
|
||||
Serial.println("Injecting tetros in boot sector");
|
||||
std::copy(tetros, std::end(tetros), track_data);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static int cached_trackno = -1;
|
||||
auto new_trackno = trackno;
|
||||
int motor_pin = !digitalRead(MOTOR_PIN);
|
||||
int select_pin = !digitalRead(SELECT_PIN);
|
||||
int side = !digitalRead(SIDE_PIN);
|
||||
|
||||
#if defined(XEROX_820)
|
||||
// no separate motor pin on this baby
|
||||
motor_pin = true;
|
||||
// only one side
|
||||
side = 0;
|
||||
#endif
|
||||
|
||||
auto enabled = motor_pin && select_pin;
|
||||
static bool old_enabled = false, old_select_pin = false,
|
||||
old_motor_pin = false;
|
||||
|
||||
if (motor_pin != old_motor_pin) {
|
||||
Serial.printf("motor_pin -> %s\n", motor_pin ? "true" : "false");
|
||||
old_motor_pin = motor_pin;
|
||||
}
|
||||
if (select_pin != old_select_pin) {
|
||||
Serial.printf("select_pin -> %s\n", select_pin ? "true" : "false");
|
||||
old_select_pin = select_pin;
|
||||
}
|
||||
|
||||
if (enabled != old_enabled) {
|
||||
Serial.printf("enabled -> %s\n", enabled ? "true" : "false");
|
||||
old_enabled = enabled;
|
||||
}
|
||||
#if defined(DISKCHANGE_PIN) && USE_SDFAT
|
||||
int diskchange_pin = digitalRead(DISKCHANGE_PIN);
|
||||
static int diskchange_pin_delayed = false;
|
||||
auto diskchange = diskchange_pin_delayed && !diskchange_pin;
|
||||
diskchange_pin_delayed = diskchange_pin;
|
||||
if (diskchange) {
|
||||
delay(20);
|
||||
while (!digitalRead(DISKCHANGE_PIN)) { /* NOTHING */
|
||||
}
|
||||
fluxout = -1;
|
||||
cached_trackno = -1;
|
||||
openNextImage();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cur_format && new_trackno != cached_trackno) {
|
||||
STATUS_RGB(0, 255, 255);
|
||||
fluxout = -1;
|
||||
Serial.printf("Preparing flux data for track %d\n", new_trackno);
|
||||
int sector_count = cur_format->sectors;
|
||||
int side_count = cur_format->sides;
|
||||
int sector_size = 128 << cur_format->n;
|
||||
size_t offset = sector_size * sector_count * side_count * new_trackno;
|
||||
size_t count = sector_size * sector_count;
|
||||
int dummy_byte = new_trackno * side_count;
|
||||
#if USE_SDFAT
|
||||
file.seek(offset);
|
||||
for (auto side = 0; side < side_count; side++) {
|
||||
int n = file.read(track_data, count);
|
||||
if (n != count) {
|
||||
Serial.println("Read failed -- using dummy data");
|
||||
make_dummy_data(side, new_trackno, count);
|
||||
}
|
||||
encode_track(side, new_trackno);
|
||||
}
|
||||
#else
|
||||
Serial.println("No filesystem - using dummy data");
|
||||
for (auto side = 0; side < side_count; side++) {
|
||||
make_dummy_data(side, new_trackno, count);
|
||||
encode_track(side, new_trackno);
|
||||
}
|
||||
#endif
|
||||
|
||||
Serial.println("flux data prepared");
|
||||
cached_trackno = new_trackno;
|
||||
}
|
||||
fluxout =
|
||||
(cur_format != NULL && enabled && cached_trackno == trackno) ? side : -1;
|
||||
#if defined(NEOPIXEL_PIN)
|
||||
if (fluxout >= 0) {
|
||||
STATUS_RGB(0, 1, 0);
|
||||
} else {
|
||||
STATUS_RGB(0, 0, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// this is not correct handling of the ready/disk change flag. on my test
|
||||
// computer, just leaving the pin HIGH works, while immediately reporting LOW
|
||||
// on the "ready / disk change:
|
||||
#if 0
|
||||
digitalWrite(READY_PIN, !motor_pin);
|
||||
#endif
|
||||
}
|
||||
29
examples/mfm_emu/tetros.h
Normal file
29
examples/mfm_emu/tetros.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
49, 192, 142, 216, 49, 192, 205, 16, 180, 1, 185, 7, 38, 205, 16, 182, 3, 185,
|
||||
18, 0, 81, 254, 198, 178, 13, 185, 14, 0, 187, 120, 0, 232, 182, 0, 128,
|
||||
254, 21, 116, 9, 66, 185, 12, 0, 49, 219, 232, 168, 0, 89, 226, 225, 198, 6,
|
||||
0, 127, 100, 180, 2, 205, 26, 160, 2, 127, 49, 208, 179, 31, 247, 227, 64,
|
||||
162, 2, 127, 49, 210, 187, 7, 0, 247, 243, 192, 226, 3, 146, 186, 18, 4,
|
||||
232, 229, 0, 117, 254, 232, 210, 0, 49, 201, 138, 14, 0, 127, 81, 96, 49,
|
||||
201, 186, 184, 11, 180, 134, 205, 21, 97, 80, 180, 1, 205, 22, 137, 193, 88,
|
||||
116, 69, 232, 175, 0, 128, 253, 75, 116, 17, 128, 253, 72, 116, 30, 128,
|
||||
253, 77, 116, 16, 198, 6, 0, 127, 10, 235, 35, 74, 232, 167, 0, 116, 29, 66,
|
||||
235, 26, 66, 232, 158, 0, 116, 20, 74, 235, 17, 136, 195, 64, 64, 168, 7,
|
||||
117, 2, 44, 8, 232, 140, 0, 116, 2, 136, 216, 232, 119, 0, 80, 48, 228, 205,
|
||||
22, 88, 89, 226, 162, 232, 103, 0, 254, 198, 232, 116, 0, 116, 138, 254,
|
||||
206, 232, 95, 0, 232, 22, 0, 233, 91, 255, 180, 2, 205, 16, 184, 32, 9, 205,
|
||||
16, 195, 180, 2, 205, 16, 180, 8, 205, 16, 195, 96, 182, 21, 254, 206, 116,
|
||||
57, 49, 219, 185, 12, 0, 178, 14, 232, 230, 255, 192, 236, 4, 116, 2, 67,
|
||||
66, 226, 244, 128, 251, 12, 117, 228, 96, 178, 14, 185, 12, 0, 81, 254, 206,
|
||||
232, 204, 255, 254, 198, 136, 227, 177, 1, 232, 185, 255, 66, 89, 226, 237,
|
||||
97, 254, 206, 117, 226, 232, 192, 255, 97, 195, 49, 219, 235, 9, 136, 195,
|
||||
192, 235, 3, 67, 192, 227, 4, 67, 137, 223, 235, 3, 191, 0, 0, 96, 49, 219,
|
||||
136, 195, 139, 135, 134, 125, 49, 219, 185, 4, 0, 81, 177, 4, 246, 196, 128,
|
||||
116, 29, 80, 9, 255, 116, 14, 96, 137, 251, 48, 192, 185, 1, 0, 232, 112,
|
||||
255, 97, 235, 9, 232, 116, 255, 192, 236, 4, 116, 1, 67, 88, 209, 224, 66,
|
||||
226, 217, 128, 234, 4, 254, 198, 89, 226, 206, 8, 219, 97, 195, 68, 68, 0,
|
||||
240, 68, 68, 0, 240, 96, 34, 0, 226, 64, 100, 0, 142, 96, 68, 0, 46, 32, 98,
|
||||
0, 232, 0, 102, 0, 102, 0, 102, 0, 102, 0, 198, 64, 38, 0, 198, 64, 38, 0,
|
||||
78, 64, 76, 0, 228, 128, 140, 0, 108, 64, 140, 0, 108, 64, 140, 128, 0, 1,
|
||||
0, 23, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 170
|
||||
9
host_src/.gitignore
vendored
Normal file
9
host_src/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
test_flux.h
|
||||
main
|
||||
main_fm
|
||||
flux[0-9]
|
||||
fluxfm*
|
||||
check[0-9]
|
||||
checkfm*
|
||||
decode[0-9]
|
||||
decodefm*
|
||||
28
host_src/Makefile
Normal file
28
host_src/Makefile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
PYTHON3 = python3
|
||||
|
||||
.PHONY: all
|
||||
all: check checkfm
|
||||
|
||||
.PHONY: check
|
||||
check: main check_flux.py
|
||||
./main
|
||||
$(PYTHON3) check_flux.py flux0 > decode0
|
||||
$(PYTHON3) check_flux.py flux1 > decode1
|
||||
$(PYTHON3) check_flux.py flux2 > decode2
|
||||
|
||||
.PHONY: checkfm
|
||||
checkfm: main_fm check_flux.py
|
||||
./main_fm
|
||||
$(PYTHON3) check_flux.py --fm fluxfm > decodefm
|
||||
|
||||
main: main.c ../src/mfm_impl.h Makefile test_flux.h
|
||||
gcc -iquote ../src -Wall -Werror -ggdb3 -Og -o $@ $<
|
||||
|
||||
main_fm: main_fm.c ../src/mfm_impl.h Makefile
|
||||
gcc -iquote ../src -Wall -Werror -ggdb3 -Og -o $@ $<
|
||||
|
||||
test_flux.h: make_flux.py greaseweazle/scripts/greaseweazle/version.py
|
||||
$(PYTHON3) $< $@
|
||||
|
||||
greaseweazle/scripts/greaseweazle/version.py:
|
||||
$(MAKE) -C greaseweazle
|
||||
45
host_src/check_flux.py
Normal file
45
host_src/check_flux.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import sys
|
||||
import pathlib
|
||||
sys.path.insert(0, str(
|
||||
pathlib.Path(__file__).parent / "greaseweazle/src"))
|
||||
import click
|
||||
from greaseweazle.codec.codec import get_diskdef
|
||||
from greaseweazle.track import MasterTrack, PLLTrack
|
||||
from bitarray import bitarray
|
||||
|
||||
@click.command
|
||||
@click.option("--fm/--no-fm", is_flag=True)
|
||||
@click.argument("flux-file")
|
||||
def main(flux_file, fm=False):
|
||||
print(f"{flux_file=!r}")
|
||||
with open(flux_file) as flux1:
|
||||
content = bitarray("".join(c for c in flux1.read() if c in "01"))
|
||||
|
||||
print(content.count(0), content.count(1))
|
||||
|
||||
if fm:
|
||||
master = MasterTrack(content[:83_500], .166)
|
||||
track = get_diskdef("dec.rx01").mk_track(0,0)
|
||||
track.time_per_rev = .166
|
||||
track.clock = 2e-6
|
||||
else:
|
||||
master = MasterTrack(content[:200_000], .200)
|
||||
track = get_diskdef("ibm.1440").mk_track(0,0)
|
||||
track.time_per_rev = 0.2
|
||||
track.clock = 1e-6
|
||||
|
||||
track.decode_flux(master, None)
|
||||
print(flux_file, track.summary_string(), file=sys.stderr)
|
||||
print(flux_file, track.summary_string())
|
||||
print("".join("E."[sec.crc == 0] for sec in track.sectors))
|
||||
for i in track.iams:
|
||||
print(i)
|
||||
for s in track.sectors:
|
||||
print(s)
|
||||
|
||||
if n := track.nr_missing():
|
||||
print(f"{n} missing sector(s)", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
host_src/greaseweazle
Submodule
1
host_src/greaseweazle
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit fc4344961e64617e2aec9dda1ff54a7bfc966e5e
|
||||
104
host_src/main.c
Normal file
104
host_src/main.c
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
|
||||
#include "mfm_impl.h"
|
||||
|
||||
uint8_t flux[] = {
|
||||
#include "test_flux.h"
|
||||
};
|
||||
|
||||
enum { sector_count = 18 };
|
||||
enum { ibmpc_io_block_size = 512 };
|
||||
uint8_t track_buf[sector_count * ibmpc_io_block_size];
|
||||
uint8_t validity[sector_count];
|
||||
|
||||
mfm_io_t io = {
|
||||
.T1_nom = 2,
|
||||
.T2_max = 5,
|
||||
.T3_max = 7,
|
||||
.pulses = flux,
|
||||
.n_pulses = sizeof(flux),
|
||||
.sectors = track_buf,
|
||||
.sector_validity = validity,
|
||||
.n_sectors = sector_count,
|
||||
.n = 2,
|
||||
.settings = &standard_mfm,
|
||||
.encode_raw = mfm_io_encode_raw_mfm,
|
||||
};
|
||||
|
||||
static void flux_bins(mfm_io_t *io) {
|
||||
io->pos = 0;
|
||||
int bins[3] = {};
|
||||
while (!mfm_io_eof(io)) {
|
||||
bins[mfm_io_read_symbol(io)]++;
|
||||
}
|
||||
printf("Flux bins: %d %d %d\n", bins[0], bins[1], bins[2]);
|
||||
}
|
||||
|
||||
static void dump_flux_compact(const char *filename, mfm_io_t *io) {
|
||||
FILE *f = fopen(filename, "w");
|
||||
io->pos = 0;
|
||||
while (!mfm_io_eof(io)) {
|
||||
int b = io->pulses[io->pos++];
|
||||
for (int i = 8; i-- > 0;) {
|
||||
fputc('0' + ((b >> i) & 1), f);
|
||||
};
|
||||
fputc(io->pos % 8 == 0 ? '\n' : ' ', f);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
static void dump_flux(const char *filename, mfm_io_t *io) {
|
||||
FILE *f = fopen(filename, "w");
|
||||
io->pos = 0;
|
||||
uint32_t state = 0;
|
||||
while (!mfm_io_eof(io)) {
|
||||
int s = mfm_io_read_symbol(io);
|
||||
state = ((state << 2) | s) & mfm_io_triple_mark_mask;
|
||||
fprintf(f, "10");
|
||||
if (s > mfm_io_pulse_10) {
|
||||
fprintf(f, "0");
|
||||
}
|
||||
if (s > mfm_io_pulse_100) {
|
||||
fprintf(f, "0");
|
||||
}
|
||||
if (state == mfm_io_triple_mark_magic) {
|
||||
DEBUG_PRINTF("triple mark @%zd\n", io->pos);
|
||||
fprintf(f, "\n");
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
int main() {
|
||||
flux_bins(&io);
|
||||
printf("Decoded %zd sectors\n", decode_track_mfm(&io));
|
||||
|
||||
dump_flux("flux0", &io);
|
||||
|
||||
memset(flux, 0, sizeof(flux));
|
||||
|
||||
#if 0
|
||||
for (size_t i = 0; i < sizeof(track_buf); i++)
|
||||
track_buf[i] = i & 0xff;
|
||||
#endif
|
||||
|
||||
printf("Create new flux data\n");
|
||||
encode_track_mfm(&io);
|
||||
dump_flux("flux1", &io);
|
||||
|
||||
memset(track_buf, 0, sizeof(track_buf));
|
||||
|
||||
io.n_valid = 0;
|
||||
memset(validity, 0, sizeof(validity));
|
||||
flux_bins(&io);
|
||||
size_t decoded = decode_track_mfm(&io);
|
||||
printf("Decoded %zd sectors\n", decoded);
|
||||
|
||||
io.encode_compact = true;
|
||||
encode_track_mfm(&io);
|
||||
dump_flux_compact("flux2", &io);
|
||||
|
||||
return decoded != 18;
|
||||
}
|
||||
53
host_src/main_fm.c
Normal file
53
host_src/main_fm.c
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
|
||||
#include "mfm_impl.h"
|
||||
|
||||
enum { sector_count = 26 };
|
||||
enum { block_size = 128 };
|
||||
|
||||
uint8_t flux[10000];
|
||||
uint8_t track_buf[sector_count * block_size];
|
||||
uint8_t validity[sector_count];
|
||||
|
||||
mfm_io_t io = {
|
||||
.T1_nom = 2,
|
||||
.T2_max = 5,
|
||||
.T3_max = 7,
|
||||
.pulses = flux,
|
||||
.n_pulses = sizeof(flux),
|
||||
.sectors = track_buf,
|
||||
.sector_validity = validity,
|
||||
.n_sectors = sector_count,
|
||||
.n = 0,
|
||||
.settings = &standard_fm,
|
||||
.encode_raw = mfm_io_encode_raw_fm,
|
||||
.encode_compact = true,
|
||||
};
|
||||
|
||||
static void dump_flux_compact(const char *filename, mfm_io_t *io) {
|
||||
FILE *f = fopen(filename, "w");
|
||||
io->pos = 0;
|
||||
while (!mfm_io_eof(io)) {
|
||||
int b = io->pulses[io->pos++];
|
||||
for (int i = 8; i-- > 0;) {
|
||||
fputc('0' + ((b >> i) & 1), f);
|
||||
};
|
||||
if (io->pos % 2 == 0) {
|
||||
fputc('\n', f);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
int main() {
|
||||
for (size_t i = 0; i < sector_count; i++) {
|
||||
memset(track_buf + i * block_size, block_size, 'A' + i);
|
||||
}
|
||||
|
||||
size_t r = encode_track_mfm(&io);
|
||||
printf("Used flux %zd\n", r);
|
||||
dump_flux_compact("fluxfm", &io);
|
||||
}
|
||||
17
host_src/make_flux.py
Normal file
17
host_src/make_flux.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import sys
|
||||
import pathlib
|
||||
sys.path.insert(0, str(
|
||||
pathlib.Path(__file__).parent / "greaseweazle/src"))
|
||||
|
||||
from greaseweazle.codec.codec import get_diskdef
|
||||
|
||||
track = get_diskdef("ibm.1440").mk_track(0,0)
|
||||
track.set_img_track(b'adaf00' + b'\0' * 512 * 18)
|
||||
#track.decode_raw(track)
|
||||
print(track.summary_string())
|
||||
flux = track.flux()
|
||||
print(flux.list[:25],len(flux.list))
|
||||
with open(sys.argv[1], "wt") as f:
|
||||
for i, fi in enumerate(flux.list):
|
||||
print(f"{fi*2},", end="\n" if i % 16 == 15 else " ", file=f)
|
||||
print(file=f)
|
||||
33
host_src/make_flux_fm.py
Normal file
33
host_src/make_flux_fm.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import sys
|
||||
import pathlib
|
||||
sys.path.insert(0, str(
|
||||
pathlib.Path(__file__).parent / "greaseweazle/src"))
|
||||
|
||||
from greaseweazle.codec.ibm.fm import IBM_FM_Predefined
|
||||
|
||||
class RX01(IBM_FM_Predefined):
|
||||
id0 = 1
|
||||
nsec = 26
|
||||
sz = 0
|
||||
cskew = 1
|
||||
gap_3 = 27 # old GW has 26 but we want the same gap3 that mfm_impl will use
|
||||
time_per_rev = 60/360
|
||||
clock = 4e-6
|
||||
|
||||
def convertflux(flux):
|
||||
for x in flux:
|
||||
yield 1
|
||||
for i in range(x-1):
|
||||
yield 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
track = RX01(0, 0)
|
||||
trackdata = b''.join(bytes([65 + i]) * 128 for i in range(RX01.nsec))
|
||||
track.set_img_track(trackdata)
|
||||
track.decode_raw(track)
|
||||
print(track.summary_string())
|
||||
flux = track.flux()
|
||||
with open(sys.argv[1], "wt") as f:
|
||||
for i, fi in enumerate(convertflux(flux.list[1:])):
|
||||
print(f"{fi}", end="\n" if i % 16 == 15 else "", file=f)
|
||||
print(file=f)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
name=Adafruit Floppy
|
||||
version=0.1.0
|
||||
version=0.3.0
|
||||
author=Adafruit
|
||||
maintainer=Adafruit <info@adafruit.com>
|
||||
sentence=Adafruit's floppy disk drive interfacing library
|
||||
|
|
@ -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
|
||||
depends=Adafruit BusIO, SdFat - Adafruit Fork, Adafruit ST7735 and ST7789 Library,Adafruit NeoPixel
|
||||
|
|
|
|||
1272
src/Adafruit_Floppy.cpp
Normal file
1272
src/Adafruit_Floppy.cpp
Normal file
File diff suppressed because it is too large
Load diff
386
src/Adafruit_Floppy.h
Normal file
386
src/Adafruit_Floppy.h
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
#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_IBMPC1200K_SECTORS_PER_TRACK 15
|
||||
#define MFM_IBMPC1440K_SECTORS_PER_TRACK 18
|
||||
#define MFM_IBMPC360K_SECTORS_PER_TRACK 9
|
||||
#define MFM_IBMPC720K_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 {
|
||||
IBMPC360K,
|
||||
IBMPC720K,
|
||||
IBMPC720K_360RPM,
|
||||
IBMPC1200K,
|
||||
IBMPC1440K,
|
||||
IBMPC1440K_360RPM,
|
||||
AUTODETECT,
|
||||
} 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(int 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(int head) = 0;
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Current head in use, based on internal caching
|
||||
@return Head 0 or 1
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual int get_side() = 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 int 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 Check whether the ready output is active
|
||||
@returns True if the ready sensor is active, false otherwise
|
||||
@note On devices without a ready sensor, this always returns true
|
||||
*/
|
||||
/**************************************************************************/
|
||||
virtual bool get_ready_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;
|
||||
|
||||
size_t decode_track_mfm(uint8_t *sectors, size_t n_sectors,
|
||||
uint8_t *sector_validity, const uint8_t *pulses,
|
||||
size_t n_pulses, float nominal_bit_time_us,
|
||||
bool clear_validity = false,
|
||||
uint8_t *logical_track = nullptr);
|
||||
|
||||
size_t encode_track_mfm(const uint8_t *sectors, size_t n_sectors,
|
||||
uint8_t *pulses, size_t max_pulses,
|
||||
float nominal_bit_time_us, uint8_t logical_track);
|
||||
|
||||
size_t capture_track(volatile uint8_t *pulses, size_t max_pulses,
|
||||
int32_t *falling_index_offset,
|
||||
bool store_greaseweazle = false, uint32_t capture_ms = 0,
|
||||
uint32_t index_wait_ms = 250)
|
||||
__attribute__((optimize("O3")));
|
||||
|
||||
bool write_track(uint8_t *pulses, size_t n_pulses,
|
||||
bool store_greaseweazle = false, bool use_index = true)
|
||||
__attribute__((optimize("O3")));
|
||||
void print_pulse_bins(uint8_t *pulses, size_t n_pulses, uint8_t max_bins = 64,
|
||||
bool is_gw_format = false, uint32_t min_bin_size = 100);
|
||||
void print_pulses(uint8_t *pulses, size_t n_pulses,
|
||||
bool is_gw_format = false);
|
||||
uint32_t getSampleFrequency(void);
|
||||
|
||||
#if defined(LED_BUILTIN)
|
||||
int8_t led_pin = LED_BUILTIN; ///< Debug LED output for tracing
|
||||
#else
|
||||
int8_t led_pin = -1; ///< Debug LED output for tracing
|
||||
#endif
|
||||
|
||||
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 = nullptr; ///< 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);
|
||||
|
||||
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(int track) override;
|
||||
bool side(int head) override;
|
||||
int track(void) override;
|
||||
int get_side(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;
|
||||
bool get_ready_sense() override;
|
||||
|
||||
private:
|
||||
// theres a lot of GPIO!
|
||||
int8_t _densitypin, _selectpin, _motorpin, _directionpin, _steppin,
|
||||
_track0pin, _protectpin, _sidepin, _readypin;
|
||||
|
||||
int _track = -1, _side = -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(int track) override;
|
||||
bool side(int head) override;
|
||||
int track(void) override;
|
||||
bool set_density(bool high_density) override;
|
||||
bool get_write_protect() override;
|
||||
bool get_track0_sense() override;
|
||||
bool get_ready_sense() override { return true; }
|
||||
|
||||
int quartertrack();
|
||||
bool goto_quartertrack(int);
|
||||
void step_mode(StepMode mode);
|
||||
|
||||
int get_side() override { return 0; }
|
||||
|
||||
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 FsBlockDeviceInterface {
|
||||
public:
|
||||
Adafruit_MFM_Floppy(Adafruit_Floppy *floppy,
|
||||
adafruit_floppy_disk_t format = AUTODETECT);
|
||||
|
||||
bool begin(void);
|
||||
void end(void);
|
||||
|
||||
uint32_t size(void) const;
|
||||
int32_t readTrack(int 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) const { 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) const { return _tracks_per_side; }
|
||||
|
||||
/**! @brief Check if there is data to be written to the current track
|
||||
@returns True if data needs to be written out */
|
||||
bool dirty() const { return _dirty; }
|
||||
|
||||
/**! @brief Call when the media has been removed */
|
||||
void removed();
|
||||
/**! @brief Call when media has been inserted
|
||||
@param format The hard coded format or AUTODETECT to try several common
|
||||
formats
|
||||
@returns True if media is hard coded or if the media was detected by
|
||||
autodetect */
|
||||
bool inserted(adafruit_floppy_disk_t format);
|
||||
|
||||
//------------- SdFat v2 FsBlockDeviceInterface API -------------//
|
||||
virtual bool isBusy();
|
||||
virtual uint32_t sectorCount();
|
||||
virtual bool syncDevice();
|
||||
|
||||
virtual bool readSector(uint32_t block, uint8_t *dst);
|
||||
virtual bool readSectors(uint32_t block, uint8_t *dst, size_t ns);
|
||||
virtual bool writeSector(uint32_t block, const uint8_t *src);
|
||||
virtual bool writeSectors(uint32_t block, const uint8_t *src, size_t ns);
|
||||
|
||||
/**! 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:
|
||||
bool autodetect();
|
||||
#if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040)
|
||||
uint16_t _last;
|
||||
#endif
|
||||
static constexpr uint8_t NO_TRACK = UINT8_MAX;
|
||||
uint8_t _sectors_per_track = 0;
|
||||
uint8_t _tracks_per_side = 0;
|
||||
uint8_t _last_track_read = NO_TRACK; // last cached track
|
||||
uint16_t _bit_time_ns;
|
||||
bool _high_density = true;
|
||||
bool _dirty = false, _track_has_errors = false;
|
||||
bool _double_step = false;
|
||||
Adafruit_Floppy *_floppy = nullptr;
|
||||
adafruit_floppy_disk_t _format = AUTODETECT;
|
||||
|
||||
/**! The raw flux data from the last track read */
|
||||
uint8_t _flux[125000];
|
||||
size_t _n_flux;
|
||||
};
|
||||
|
||||
#endif
|
||||
437
src/Adafruit_MFM_Floppy.cpp
Normal file
437
src/Adafruit_MFM_Floppy.cpp
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
#include <Adafruit_Floppy.h>
|
||||
|
||||
/// @cond false
|
||||
static const uint16_t flux_rates[] = {2000, 1000, 867, 1667};
|
||||
|
||||
struct adafruit_floppy_format_info_t {
|
||||
uint8_t cylinders, sectors;
|
||||
uint16_t bit_time_ns;
|
||||
uint16_t track_time_ms;
|
||||
};
|
||||
|
||||
// must match the order of adafruit_floppy_disk_t
|
||||
static const adafruit_floppy_format_info_t _format_info[] = {
|
||||
/* IBMPC360K */
|
||||
{40, 9, 2000, 167},
|
||||
/* IBMPC1200K */
|
||||
{80, 15, 1000, 200},
|
||||
|
||||
/* IBMPC720K */
|
||||
{80, 9, 1000, 200},
|
||||
/* IBMPC720K_360RPM */
|
||||
{80, 9, 1667, 167},
|
||||
|
||||
/* IBMPC1440K */
|
||||
{80, 18, 1000, 200},
|
||||
/* IBMPC1440K_360RPM */
|
||||
{80, 18, 867, 167},
|
||||
};
|
||||
/// @endcond
|
||||
|
||||
static_assert(sizeof(_format_info) / sizeof(_format_info[0]) == AUTODETECT);
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@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);
|
||||
|
||||
if (_floppy->spin_motor(true)) {
|
||||
return inserted(_format);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Spin down and deselect the motor and drive
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void Adafruit_MFM_Floppy::end(void) {
|
||||
_floppy->spin_motor(false);
|
||||
_floppy->select(false);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Quick calculator for expected max capacity
|
||||
@returns Size of the drive in bytes
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint32_t Adafruit_MFM_Floppy::size(void) const {
|
||||
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 logical_track the logical track number, 0 to whatever is the max
|
||||
tracks for the given format during instantiation (e.g. 40 for DD, 80 for HD)
|
||||
@param head which side to read, false for side 1, true for side 2
|
||||
@returns Number of sectors captured, or -1 if we couldn't seek
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int32_t Adafruit_MFM_Floppy::readTrack(int logical_track, bool head) {
|
||||
syncDevice();
|
||||
|
||||
uint8_t physical_track = _double_step ? 2 * logical_track : logical_track;
|
||||
|
||||
Serial.printf("\t[readTrack] Seeking track %d [phys=%d] head %d...\r\n",
|
||||
logical_track, physical_track, head);
|
||||
if (!_floppy->goto_track(physical_track)) {
|
||||
// Serial.println("failed to seek to track");
|
||||
return -1;
|
||||
}
|
||||
_floppy->side(head);
|
||||
// Serial.println("done!");
|
||||
// flux not decoding from a 3.5" floppy? Maybe it's rotating at 360RPM instead
|
||||
// of 300RPM see e.g.,
|
||||
// https://www.retrotechnology.com/herbs_stuff/drive.html#rotate2
|
||||
// and change nominal bit time to 0.833 ~= 300/360
|
||||
// would be good to auto-detect!
|
||||
uint32_t captured_sectors = 0;
|
||||
for (int i = 0; i < 5 && captured_sectors < _sectors_per_track; i++) {
|
||||
int32_t index_offset;
|
||||
_n_flux =
|
||||
_floppy->capture_track(_flux, sizeof(_flux), &index_offset, false, 220);
|
||||
captured_sectors = _floppy->decode_track_mfm(track_data, _sectors_per_track,
|
||||
track_validity, _flux, _n_flux,
|
||||
_bit_time_ns / 1000.f, i == 0);
|
||||
}
|
||||
|
||||
_track_has_errors = (captured_sectors != _sectors_per_track);
|
||||
if (_track_has_errors) {
|
||||
Serial.printf("Track %d/%d has errors (%d != %d)\n", logical_track, head,
|
||||
captured_sectors, _sectors_per_track);
|
||||
}
|
||||
_last_track_read = logical_track * FLOPPY_HEADS + head;
|
||||
return captured_sectors;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// SdFat BaseBlockDriver API
|
||||
// A block is 512 bytes
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Max capacity in sector block
|
||||
@returns Size of the drive in sector (512 bytes)
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint32_t Adafruit_MFM_Floppy::sectorCount() {
|
||||
return size() / MFM_BYTES_PER_SECTOR;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Check if device busy
|
||||
@returns true if busy
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::isBusy() {
|
||||
// since writing is not supported yet
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@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::readSector(uint32_t block, uint8_t *dst) {
|
||||
if (block > sectorCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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::readSectors(uint32_t block, uint8_t *dst, size_t nb) {
|
||||
// read each block one by one
|
||||
for (size_t blocknum = 0; blocknum < nb; blocknum++) {
|
||||
if (!readSector(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::writeSector(uint32_t block, const uint8_t *src) {
|
||||
if (block > sectorCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// promptly fail if disk is protected
|
||||
// might also fail if WGATE is masked by HW (e.g., by physical switch on
|
||||
// floppsy)
|
||||
if (_floppy->get_write_protect()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
Serial.printf("Writing block %d\r\n", block);
|
||||
track_validity[subsector] = 1;
|
||||
memcpy(track_data + (subsector * MFM_BYTES_PER_SECTOR), src,
|
||||
MFM_BYTES_PER_SECTOR);
|
||||
_dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@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::writeSectors(uint32_t block, const uint8_t *src,
|
||||
size_t nb) {
|
||||
// write each block one by one
|
||||
for (size_t blocknum = 0; blocknum < nb; blocknum++) {
|
||||
if (!writeSector(block + blocknum, src + (blocknum * MFM_BYTES_PER_SECTOR)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Sync written blocks NOT IMPLEMENTED YET
|
||||
@returns True on success, false if failed or unimplemented
|
||||
*/
|
||||
/**************************************************************************/
|
||||
bool Adafruit_MFM_Floppy::syncDevice() {
|
||||
if (!_dirty || _last_track_read == NO_TRACK) {
|
||||
return true;
|
||||
}
|
||||
_dirty = false;
|
||||
|
||||
int logical_track = _last_track_read / FLOPPY_HEADS;
|
||||
int head = _last_track_read % FLOPPY_HEADS;
|
||||
|
||||
uint8_t physical_track = _double_step ? 2 * logical_track : logical_track;
|
||||
Serial.printf("Flushing track %d [phys %d] side %d\r\n", logical_track,
|
||||
physical_track, head);
|
||||
// should be a no-op
|
||||
if (!_floppy->goto_track(physical_track)) {
|
||||
Serial.println("failed to seek to track");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_floppy->side(head)) {
|
||||
Serial.println("failed to select head");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_errors = false;
|
||||
for (size_t i = 0; !has_errors && i < _sectors_per_track; i++) {
|
||||
has_errors = !track_validity[i];
|
||||
}
|
||||
|
||||
if (has_errors) {
|
||||
Serial.printf(
|
||||
"Can't do a non-full track write to track with read errors\n");
|
||||
return false;
|
||||
}
|
||||
_n_flux = _floppy->encode_track_mfm(track_data, _sectors_per_track, _flux,
|
||||
sizeof(_flux), _high_density ? 1.f : 2.f,
|
||||
logical_track);
|
||||
|
||||
if (!_floppy->write_track(_flux, _n_flux, false)) {
|
||||
Serial.println("failed to write track");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Adafruit_MFM_Floppy::removed() {
|
||||
noInterrupts();
|
||||
_tracks_per_side = 0;
|
||||
_last_track_read = NO_TRACK;
|
||||
_dirty = false;
|
||||
interrupts();
|
||||
}
|
||||
|
||||
static uint16_t le16_at(uint8_t *ptr) { return ptr[0] | (ptr[1] << 8); }
|
||||
|
||||
bool Adafruit_MFM_Floppy::autodetect() {
|
||||
Serial.printf("autodetecting\r\n");
|
||||
int32_t index_offset;
|
||||
_n_flux = _floppy->capture_track(_flux, sizeof(_flux) / 16, &index_offset,
|
||||
false, 220);
|
||||
for (auto flux_rate_ns : flux_rates) {
|
||||
Serial.printf("flux rate %d\r\n", flux_rate_ns);
|
||||
auto captured_sectors =
|
||||
_floppy->decode_track_mfm(track_data, 1, track_validity, _flux, _n_flux,
|
||||
flux_rate_ns / 1000.f, true);
|
||||
if (captured_sectors) {
|
||||
auto valid_signature =
|
||||
track_data[0] == 0xeb && // short jump
|
||||
track_data[1] >= 0x1e && // minimum BPB size (DOS 3.0 BPB)
|
||||
track_data[2] == 0x90; // NOP
|
||||
if (!valid_signature) {
|
||||
Serial.printf("Invalid signature %02x %02x %02x\r\n", track_data[0],
|
||||
track_data[1], track_data[2]);
|
||||
continue;
|
||||
}
|
||||
auto heads = le16_at(track_data + 0x1A);
|
||||
auto total_logical_sectors = le16_at(track_data + 0x13);
|
||||
|
||||
_bit_time_ns = flux_rate_ns;
|
||||
_sectors_per_track = le16_at(track_data + 0x18);
|
||||
_tracks_per_side = total_logical_sectors / heads / _sectors_per_track;
|
||||
_last_track_read = NO_TRACK;
|
||||
_dirty = false;
|
||||
|
||||
if (_tracks_per_side <= 40) {
|
||||
_floppy->goto_track(2);
|
||||
_n_flux = _floppy->capture_track(_flux, sizeof(_flux) / 16,
|
||||
&index_offset, false, 220);
|
||||
uint8_t track_number;
|
||||
auto captured_sectors = _floppy->decode_track_mfm(
|
||||
track_data, 1, track_validity, _flux, _n_flux,
|
||||
flux_rate_ns / 1000.f, true, &track_number);
|
||||
if (!captured_sectors) {
|
||||
Serial.printf("failed to read on physical track 2\r\n");
|
||||
}
|
||||
_double_step = (track_number == 1);
|
||||
Serial.printf(
|
||||
"on physical track 2, track_number=%d. _double_step <- %d\r\n",
|
||||
track_number, _double_step);
|
||||
} else {
|
||||
_double_step = false;
|
||||
}
|
||||
Serial.printf("Detected flux rate %dns/bit\r\n%d/%d/%d C/H/S\r\n",
|
||||
flux_rate_ns, _tracks_per_side, heads, _sectors_per_track);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Serial.printf("failed autodetect\r\n");
|
||||
_sectors_per_track = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Adafruit_MFM_Floppy::inserted(adafruit_floppy_disk_t floppy_type) {
|
||||
_floppy->goto_track(0);
|
||||
_floppy->side(0);
|
||||
|
||||
if (floppy_type == AUTODETECT) {
|
||||
return autodetect();
|
||||
} else if (floppy_type < 0 || floppy_type > AUTODETECT) {
|
||||
return false;
|
||||
}
|
||||
const auto &info = _format_info[floppy_type];
|
||||
|
||||
noInterrupts();
|
||||
_tracks_per_side = info.cylinders;
|
||||
_sectors_per_track = info.sectors;
|
||||
_bit_time_ns = info.bit_time_ns;
|
||||
_last_track_read = NO_TRACK;
|
||||
_dirty = false;
|
||||
interrupts();
|
||||
|
||||
return true;
|
||||
// TODO: set up double stepping on HD 5.25 drives with 360kB media inserted
|
||||
}
|
||||
441
src/arch_rp2.cpp
Normal file
441
src/arch_rp2.cpp
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
#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 670ns.
|
||||
// ;; note that wdc1772 has varying low times, from 570 to 1380ns
|
||||
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,
|
||||
uint32_t max_wait_time) {
|
||||
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
|
||||
if (max_wait_time) {
|
||||
uint32_t start_time = millis();
|
||||
while (!gpio_get(index_pin)) { /* NOTHING */
|
||||
if (millis() - start_time > max_wait_time) {
|
||||
disable_capture();
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
while (gpio_get(index_pin)) { /* NOTHING */
|
||||
if (millis() - start_time > max_wait_time) {
|
||||
disable_capture();
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
bool use_index) {
|
||||
|
||||
if (use_index) {
|
||||
// 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;
|
||||
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,
|
||||
uint32_t index_wait_ms) {
|
||||
if (!init_capture(index_pin, rdpin)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto result =
|
||||
capture_foreground(index_pin, (uint8_t *)pulses, (uint8_t *)pulse_end,
|
||||
falling_index_offset, store_greaseweazle,
|
||||
capture_counts, index_wait_ms) -
|
||||
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,
|
||||
bool use_index) {
|
||||
if (!init_write(wrdata_pin, is_apple2)) {
|
||||
return false;
|
||||
}
|
||||
write_foreground(index_pin, wrgate_pin, (uint8_t *)pulses,
|
||||
(uint8_t *)pulse_end, store_greaseweazle, use_index);
|
||||
free_write();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
27
src/arch_rp2.h
Normal file
27
src/arch_rp2.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#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,
|
||||
uint32_t index_wait_ms);
|
||||
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,
|
||||
bool use_index);
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
443
src/arch_samd51.cpp
Normal file
443
src/arch_samd51.cpp
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
#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_n_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_n_pulses++] = min(249, ticks);
|
||||
} else {
|
||||
uint8_t high = (ticks - 250) / 255;
|
||||
if (high < 5) {
|
||||
// 250-1524: Two bytes.
|
||||
g_flux_pulses[g_n_pulses++] = 250 + high;
|
||||
g_flux_pulses[g_n_pulses++] = 1 + ((ticks - 250) % 255);
|
||||
} else {
|
||||
// TODO MEME FIX!
|
||||
/* 1525-(2^28-1): Seven bytes.
|
||||
g_flux_pulses[g_n_pulses++] = 0xff;
|
||||
g_flux_pulses[g_n_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_n_pulses < g_max_pulses) {
|
||||
// Set period for next pulse
|
||||
|
||||
uint16_t ticks = g_flux_pulses[g_n_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_n_pulses++;
|
||||
ticks = high + g_flux_pulses[g_n_pulses];
|
||||
}
|
||||
theWriteTimer->COUNT16.CCBUF[0].reg = ticks;
|
||||
} else if (g_n_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_n_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) {
|
||||
(void)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
Normal file
101
src/greasepack.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#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
|
||||
592
src/mfm_impl.h
Normal file
592
src/mfm_impl.h
Normal file
|
|
@ -0,0 +1,592 @@
|
|||
// SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#if !defined(DEBUG_PRINTF)
|
||||
#define DEBUG_PRINTF(...) ((void)0)
|
||||
#endif
|
||||
|
||||
#if !defined(DEBUG_ASSERT)
|
||||
#define DEBUG_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
/// @cond false
|
||||
|
||||
#define MFM_MAYBE_UNUSED __attribute__((unused))
|
||||
|
||||
typedef struct mfm_io mfm_io_t;
|
||||
|
||||
MFM_MAYBE_UNUSED
|
||||
static void mfm_io_encode_raw_mfm(mfm_io_t *io, uint8_t b);
|
||||
|
||||
MFM_MAYBE_UNUSED
|
||||
static void mfm_io_encode_raw_fm(mfm_io_t *io, uint8_t b);
|
||||
|
||||
typedef struct mfm_io_settings {
|
||||
uint16_t gap_1, gap_2;
|
||||
uint16_t gap_3[8]; // indexed by 'n', the sector size control
|
||||
uint16_t gap_4a;
|
||||
uint16_t gap_presync;
|
||||
uint8_t gap_byte;
|
||||
bool is_fm;
|
||||
} mfm_io_settings_t;
|
||||
|
||||
static const mfm_io_settings_t standard_mfm = {
|
||||
50, 22, {32, 54, 84, 116, 255, 255, 255, 255}, 80, 12, 0x4e, false,
|
||||
};
|
||||
|
||||
static const mfm_io_settings_t standard_fm = {
|
||||
26, 11, {27, 42, 58, 138, 255, 255, 255, 255}, 40, 6, 0xff, true,
|
||||
};
|
||||
|
||||
struct mfm_io {
|
||||
bool encode_compact; ///< When writing flux, use compact form
|
||||
uint16_t T2_max; ///< MFM decoder max length of 2us pulse
|
||||
uint16_t T3_max; ///< MFM decoder max length of 3us pulse
|
||||
uint16_t T1_nom; ///< MFM nominal 1us pulse value
|
||||
|
||||
size_t n_valid; ///< Count of valid sectors decoded
|
||||
|
||||
uint8_t *pulses; ///< Encoded track data
|
||||
size_t n_pulses; ///< Total size of encoded track data
|
||||
size_t pos; ///< Position within encoded track data
|
||||
size_t time; ///< Total track time in flux units (set by encoder)
|
||||
|
||||
uint8_t *sectors; ///< Pointer to decoded data
|
||||
size_t n_sectors; ///< Number of sectors on track
|
||||
|
||||
uint8_t *sector_validity; ///< Which sectors decoded successfully
|
||||
uint8_t
|
||||
*cylinder_ptr; ///< When decoding, the cylinder number read is stored here
|
||||
uint8_t head, cylinder; ///< Location of the track on disk
|
||||
uint8_t pulse_len; ///< bookkeeping value used by MFM decoder
|
||||
uint8_t y; ///< bookkeeping value used by MFM encoder
|
||||
uint8_t n; ///< Sector size value. Sector is (128<<n) bytes big. Valid values
|
||||
///< are 0..7
|
||||
|
||||
uint16_t crc; ///< bookkeeping value used by encoder & decoder
|
||||
const mfm_io_settings_t *settings; ///< various settings, used by encoder
|
||||
void (*flux_byte)(
|
||||
struct mfm_io *,
|
||||
uint8_t); ///< can be mfm_io_flux_put or mfm_io_flux_put_compact
|
||||
void (*encode_raw)(
|
||||
mfm_io_t *io,
|
||||
uint8_t b); ///< can be mfm_io_encode_raw_fm or mfm_io_encode_raw_mfm
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
mfm_io_pulse_10,
|
||||
mfm_io_pulse_100,
|
||||
mfm_io_pulse_1000
|
||||
} mfm_io_symbol_t;
|
||||
|
||||
typedef enum { mfm_io_odd = 0, mfm_io_even = 1 } mfm_state_t;
|
||||
|
||||
// FM and MFM use the same marker nubmers
|
||||
enum { MFM_IO_IAM = 0xfc, MFM_IO_IDAM = 0xfe, MFM_IO_DAM = 0xfb };
|
||||
|
||||
enum {
|
||||
mfm_io_idam_size = 4,
|
||||
mfm_io_crc_size = 2,
|
||||
};
|
||||
|
||||
// static const char sync_bytes[] = "\x44\x89\x44\x89\x44\x89";
|
||||
// a1 a1 a1 but with special timing bits
|
||||
static const uint8_t mfm_io_sync_bytes_mfm[] = {0x44, 0x89, 0x44,
|
||||
0x89, 0x44, 0x89};
|
||||
static const uint8_t mfm_io_iam_sync_bytes_mfm[] = {0x52, 0x24, 0x52,
|
||||
0x24, 0x52, 0x24};
|
||||
|
||||
static const uint8_t mfm_io_sync_bytes_fm[] = {};
|
||||
static const uint8_t mfm_io_iam_sync_bytes_fm[] = {0xaa, 0xaa, 0xf7, 0x7a};
|
||||
|
||||
enum { fm_default_sync_clk = 0xc7 };
|
||||
|
||||
static int mfm_io_eof(mfm_io_t *io) { return io->pos >= io->n_pulses; }
|
||||
|
||||
static mfm_io_symbol_t mfm_io_read_symbol(mfm_io_t *io) {
|
||||
if (mfm_io_eof(io)) {
|
||||
return mfm_io_pulse_10;
|
||||
}
|
||||
uint8_t pulse_len = io->pulses[io->pos++];
|
||||
if (pulse_len > io->T3_max)
|
||||
return mfm_io_pulse_1000;
|
||||
if (pulse_len > io->T2_max)
|
||||
return mfm_io_pulse_100;
|
||||
return mfm_io_pulse_10;
|
||||
}
|
||||
|
||||
// Automatically generated CRC function
|
||||
// polynomial: 0x11021
|
||||
static const uint16_t mfm_io_crc16_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,
|
||||
};
|
||||
|
||||
static uint16_t mfm_io_crc16(const uint8_t *data, int len, uint16_t crc) {
|
||||
while (len > 0) {
|
||||
crc = mfm_io_crc16_table[*data ^ (uint8_t)(crc >> 8)] ^ (crc << 8);
|
||||
data++;
|
||||
len--;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
enum {
|
||||
mfm_io_triple_mark_magic = 0x09926499,
|
||||
mfm_io_triple_mark_mask = 0x0fffffff
|
||||
};
|
||||
|
||||
static bool skip_triple_sync_mark(mfm_io_t *io) {
|
||||
uint32_t state = 0;
|
||||
while (!mfm_io_eof(io) && state != mfm_io_triple_mark_magic) {
|
||||
state = ((state << 2) | mfm_io_read_symbol(io)) & mfm_io_triple_mark_mask;
|
||||
}
|
||||
DEBUG_PRINTF("mark @ %zd ? %d\n", io->pos, state == mfm_io_triple_mark_magic);
|
||||
return state == mfm_io_triple_mark_magic;
|
||||
}
|
||||
|
||||
// The MFM crc initialization value, _excluding the three 0xa1 sync bytes_
|
||||
enum { mfm_io_crc_preload_value = 0xcdb4 };
|
||||
|
||||
// Copy data into a series of buffers, returning the CRC.
|
||||
// This must be called right after sync_triple_sync_mark, because an assumption
|
||||
// is made about the code that's about to be read.
|
||||
//
|
||||
// The "..." arguments must be pairs of (uint8_t *buf, size_t n), ending with a
|
||||
// NULL buf.
|
||||
__attribute__((sentinel)) static uint16_t receive_crc(mfm_io_t *io, ...) {
|
||||
// `tmp` holds up to 9 bits of data, in bits 6..15.
|
||||
unsigned tmp = 0, weight = 0x8000;
|
||||
uint16_t crc = mfm_io_crc_preload_value;
|
||||
|
||||
#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 "mfm_io_even" bit(s) or the
|
||||
// "mfm_io_odd" bit(s):
|
||||
//
|
||||
// 10 - leaves mfm_io_even/mfm_io_odd (parity) unchanged
|
||||
// 100 - inverts mfm_io_even/mfm_io_odd (parity)
|
||||
// 1000 - leaves mfm_io_even/mfm_io_odd (parity) unchanged
|
||||
// ^ ^ data bits if state is mfm_io_even
|
||||
// ^ ^ data bits if state is mfm_io_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 'mfm_io_even' state but not
|
||||
// recording the '1' bit.
|
||||
mfm_io_symbol_t s = mfm_io_read_symbol(io);
|
||||
mfm_state_t state = mfm_io_even;
|
||||
switch (s) {
|
||||
case mfm_io_pulse_100: // first data bit is a 0, and we start in the ODD state
|
||||
state = mfm_io_odd;
|
||||
/* fallthrough */
|
||||
case mfm_io_pulse_1000: // first data bit is a 0, and we start in EVEN state
|
||||
PUT_BIT(0);
|
||||
break;
|
||||
default: // this flux doesn't represent a data bit, and we start in the EVEN
|
||||
// state
|
||||
break;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, io);
|
||||
uint8_t *buf;
|
||||
while ((buf = va_arg(ap, uint8_t *)) != NULL) {
|
||||
size_t n = va_arg(ap, size_t);
|
||||
while (n) {
|
||||
s = mfm_io_read_symbol(io);
|
||||
PUT_BIT(
|
||||
state); // 'mfm_io_even' is 1, so record a '1' or '0' as appropriate
|
||||
if (s == mfm_io_pulse_1000) {
|
||||
PUT_BIT(0); // the other bit recorded for a 1000 is always a '0'
|
||||
}
|
||||
if (s == mfm_io_pulse_100) {
|
||||
if (state) {
|
||||
PUT_BIT(0);
|
||||
} // If 'mfm_io_even', record an additional '0'
|
||||
state = (mfm_state_t)!state; // the next symbol has opposite parity
|
||||
}
|
||||
|
||||
if (weight <= 0x80) {
|
||||
*buf = tmp >> 8;
|
||||
crc = mfm_io_crc16_table[*buf ^ (uint8_t)(crc >> 8)] ^ (crc << 8);
|
||||
tmp <<= 8;
|
||||
weight <<= 8;
|
||||
buf++;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
return crc;
|
||||
}
|
||||
|
||||
// Read a whole track, setting validity[] for each sector actually read, up to
|
||||
// n_sectors indexing of validity & data is 0-based, mfm_io_even though
|
||||
// MFM_IO_IDAMs store sectors as 1-based
|
||||
MFM_MAYBE_UNUSED
|
||||
static size_t decode_track_mfm(mfm_io_t *io) {
|
||||
io->pos = 0;
|
||||
|
||||
// count previous valid sectors, so we can early-terminate if we're just
|
||||
// picking up some errored sectors on a 2nd pass
|
||||
io->n_valid = 0;
|
||||
for (size_t i = 0; i < io->n_sectors; i++)
|
||||
if (io->sector_validity[i])
|
||||
io->n_valid += 1;
|
||||
|
||||
uint8_t mark;
|
||||
uint8_t idam_buf[mfm_io_idam_size];
|
||||
uint8_t crc_buf[mfm_io_crc_size];
|
||||
|
||||
// IDAM structure is:
|
||||
// * buf[0]: cylinder
|
||||
// * buf[1]: head
|
||||
// * buf[2]: sector
|
||||
// * buf[3]: "n" (sector size shift) -- must be 2 for 512 bytes
|
||||
// Only the sector number is validated. In theory, the other values should be
|
||||
// validated and we are only interested in working with DOS/Windows MFM
|
||||
// floppies which always use 512 byte sectors
|
||||
while (!mfm_io_eof(io) && io->n_valid < io->n_sectors) {
|
||||
if (!skip_triple_sync_mark(io)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t crc = receive_crc(io, &mark, 1, idam_buf, sizeof(idam_buf),
|
||||
crc_buf, sizeof(crc_buf), NULL);
|
||||
|
||||
DEBUG_PRINTF("mark=%02x [expecting IDAM=%02x]\n", mark, MFM_IO_IDAM);
|
||||
DEBUG_PRINTF("idam=%02x %02x %02x %02x\n", idam_buf[0], idam_buf[1],
|
||||
idam_buf[2], idam_buf[3]);
|
||||
DEBUG_PRINTF("crc_buf=%02x %02x\n", crc_buf[0], crc_buf[1]);
|
||||
DEBUG_PRINTF("crc=%04x [expecting 0]\n", crc);
|
||||
if (mark != MFM_IO_IDAM) {
|
||||
continue;
|
||||
}
|
||||
if (crc != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: verify track & side numbers in IDAM
|
||||
size_t r = (uint8_t)idam_buf[2] - 1; // sectors are 1-based
|
||||
if (r >= io->n_sectors) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (io->sector_validity[r]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!skip_triple_sync_mark(io)) {
|
||||
continue;
|
||||
}
|
||||
size_t io_block_size = 128 << io->n;
|
||||
crc = receive_crc(io, &mark, 1, io->sectors + io_block_size * r,
|
||||
io_block_size, crc_buf, sizeof(crc_buf), NULL);
|
||||
DEBUG_PRINTF("mark=%02x [expecting DAM=%02x]\n", mark, MFM_IO_DAM);
|
||||
DEBUG_PRINTF("crc_buf=%02x %02x\n", crc_buf[0], crc_buf[1]);
|
||||
DEBUG_PRINTF("crc=%04x [expecting 0]\n", crc);
|
||||
if (mark != MFM_IO_DAM) {
|
||||
continue;
|
||||
}
|
||||
if (crc != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (io->cylinder_ptr)
|
||||
*io->cylinder_ptr = idam_buf[0];
|
||||
io->sector_validity[r] = 1;
|
||||
io->n_valid++;
|
||||
}
|
||||
return io->n_valid;
|
||||
}
|
||||
|
||||
static void mfm_io_flux_put(mfm_io_t *io, uint8_t len) {
|
||||
if (mfm_io_eof(io))
|
||||
return;
|
||||
io->pulses[io->pos++] = len;
|
||||
}
|
||||
|
||||
static void mfm_io_flux_byte_compact(mfm_io_t *io, uint8_t b) {
|
||||
if (mfm_io_eof(io))
|
||||
return;
|
||||
io->pulses[io->pos++] = b;
|
||||
}
|
||||
|
||||
static void mfm_io_flux_byte(mfm_io_t *io, uint8_t b) {
|
||||
for (int i = 8; i-- > 0;) {
|
||||
if (b & (1 << i)) {
|
||||
io->time += io->pulse_len + 1;
|
||||
mfm_io_flux_put(io, (1 + io->pulse_len) * io->T1_nom);
|
||||
io->pulse_len = 0;
|
||||
} else {
|
||||
io->pulse_len += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_encode_raw_fm(mfm_io_t *io, uint8_t b) {
|
||||
if ((b & 0xaa) == 0) {
|
||||
b |= 0xaa;
|
||||
}
|
||||
io->flux_byte(io, b);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_raw_mfm(mfm_io_t *io, uint8_t b) {
|
||||
uint16_t y = (io->y << 8) | b;
|
||||
if ((b & 0xaa) == 0) {
|
||||
// if there are no clocks, synthesize them
|
||||
y |= ~((y >> 1) | (y << 1)) & 0xaaaa;
|
||||
y &= 0xff;
|
||||
}
|
||||
io->flux_byte(io, y);
|
||||
io->y = y;
|
||||
}
|
||||
|
||||
static const uint16_t mfm_encode_list[] = {
|
||||
// taken from greaseweazle
|
||||
0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15, 0x40,
|
||||
0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55, 0x100, 0x101,
|
||||
0x104, 0x105, 0x110, 0x111, 0x114, 0x115, 0x140, 0x141, 0x144,
|
||||
0x145, 0x150, 0x151, 0x154, 0x155, 0x400, 0x401, 0x404, 0x405,
|
||||
0x410, 0x411, 0x414, 0x415, 0x440, 0x441, 0x444, 0x445, 0x450,
|
||||
0x451, 0x454, 0x455, 0x500, 0x501, 0x504, 0x505, 0x510, 0x511,
|
||||
0x514, 0x515, 0x540, 0x541, 0x544, 0x545, 0x550, 0x551, 0x554,
|
||||
0x555, 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015,
|
||||
0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, 0x1100,
|
||||
0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, 0x1140, 0x1141,
|
||||
0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404,
|
||||
0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, 0x1441, 0x1444, 0x1445,
|
||||
0x1450, 0x1451, 0x1454, 0x1455, 0x1500, 0x1501, 0x1504, 0x1505, 0x1510,
|
||||
0x1511, 0x1514, 0x1515, 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551,
|
||||
0x1554, 0x1555, 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014,
|
||||
0x4015, 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055,
|
||||
0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, 0x4140,
|
||||
0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401,
|
||||
0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444,
|
||||
0x4445, 0x4450, 0x4451, 0x4454, 0x4455, 0x4500, 0x4501, 0x4504, 0x4505,
|
||||
0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544, 0x4545, 0x4550,
|
||||
0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011,
|
||||
0x5014, 0x5015, 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054,
|
||||
0x5055, 0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115,
|
||||
0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, 0x5400,
|
||||
0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, 0x5440, 0x5441,
|
||||
0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, 0x5500, 0x5501, 0x5504,
|
||||
0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544, 0x5545,
|
||||
0x5550, 0x5551, 0x5554, 0x5555};
|
||||
|
||||
static void mfm_io_encode_fm_sync(mfm_io_t *io, uint8_t data, uint8_t clock) {
|
||||
uint16_t encoded = 0;
|
||||
// can this be done with two lookups in encoded[] ?
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
encoded <<= 1;
|
||||
encoded |= (clock >> (7 - i)) & 1;
|
||||
encoded <<= 1;
|
||||
encoded |= (data >> (7 - i)) & 1;
|
||||
}
|
||||
io->encode_raw(io, encoded >> 8);
|
||||
io->encode_raw(io, encoded & 0xff);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_fm_sync_crc(mfm_io_t *io, uint8_t data,
|
||||
uint8_t clock) {
|
||||
mfm_io_encode_fm_sync(io, data, clock);
|
||||
io->crc = mfm_io_crc16(&data, 1, io->crc);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_byte(mfm_io_t *io, uint8_t b) {
|
||||
uint16_t encoded = mfm_encode_list[b];
|
||||
io->encode_raw(io, encoded >> 8);
|
||||
io->encode_raw(io, encoded & 0xff);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_raw_buf(mfm_io_t *io, const uint8_t *buf, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
io->encode_raw(io, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_encode_gap(mfm_io_t *io, size_t n_gap) {
|
||||
for (size_t i = 0; i < n_gap; i++) {
|
||||
mfm_io_encode_byte(io, io->settings->gap_byte);
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_encode_gap_and_presync(mfm_io_t *io, size_t n_gap) {
|
||||
mfm_io_encode_gap(io, n_gap);
|
||||
for (size_t i = 0; i < io->settings->gap_presync; i++) {
|
||||
mfm_io_encode_byte(io, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_encode_gap_and_sync(mfm_io_t *io, size_t n_gap) {
|
||||
mfm_io_encode_gap_and_presync(io, n_gap);
|
||||
if (io->settings->is_fm) {
|
||||
mfm_io_encode_raw_buf(io, mfm_io_sync_bytes_fm,
|
||||
sizeof(mfm_io_sync_bytes_fm));
|
||||
} else {
|
||||
mfm_io_encode_raw_buf(io, mfm_io_sync_bytes_mfm,
|
||||
sizeof(mfm_io_sync_bytes_mfm));
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_encode_iam(mfm_io_t *io) {
|
||||
mfm_io_encode_gap_and_presync(io, io->settings->gap_4a);
|
||||
if (io->settings->is_fm) {
|
||||
mfm_io_encode_raw_buf(io, mfm_io_iam_sync_bytes_fm,
|
||||
sizeof(mfm_io_iam_sync_bytes_fm));
|
||||
} else {
|
||||
mfm_io_encode_raw_buf(io, mfm_io_iam_sync_bytes_mfm,
|
||||
sizeof(mfm_io_iam_sync_bytes_mfm));
|
||||
}
|
||||
mfm_io_encode_byte(io, MFM_IO_IAM);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_buf(mfm_io_t *io, const uint8_t *buf, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
mfm_io_encode_byte(io, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_crc_preload(mfm_io_t *io) {
|
||||
if (io->settings->is_fm) {
|
||||
io->crc = 0xffff;
|
||||
} else {
|
||||
io->crc = mfm_io_crc_preload_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void mfm_io_encode_buf_crc(mfm_io_t *io, const uint8_t *buf, size_t n) {
|
||||
mfm_io_encode_buf(io, buf, n);
|
||||
io->crc = mfm_io_crc16(buf, n, io->crc);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_byte_crc(mfm_io_t *io, uint8_t b) {
|
||||
mfm_io_encode_buf_crc(io, &b, 1);
|
||||
}
|
||||
|
||||
static void mfm_io_encode_crc(mfm_io_t *io) {
|
||||
unsigned crc = io->crc;
|
||||
mfm_io_encode_byte_crc(io, crc >> 8);
|
||||
mfm_io_encode_byte_crc(io, crc & 0xff);
|
||||
DEBUG_ASSERT(io->crc == 0);
|
||||
}
|
||||
|
||||
// Convert a whole track into flux, up to n_sectors. indexing of data is
|
||||
// 0-based, mfm_io_even though MFM_IO_IDAMs store sectors as 1-based
|
||||
MFM_MAYBE_UNUSED
|
||||
static size_t encode_track_mfm(mfm_io_t *io) {
|
||||
io->pos = 0;
|
||||
io->pulse_len = 0;
|
||||
io->y = 0;
|
||||
io->time = 0;
|
||||
io->flux_byte =
|
||||
io->encode_compact ? mfm_io_flux_byte_compact : mfm_io_flux_byte;
|
||||
io->encode_raw =
|
||||
io->settings->is_fm ? mfm_io_encode_raw_fm : mfm_io_encode_raw_mfm;
|
||||
|
||||
// sector_validity might end up reused for interleave?
|
||||
// memset(io->sector_validity, 0, io->n_sectors);
|
||||
|
||||
unsigned char buf[mfm_io_idam_size + 1];
|
||||
|
||||
mfm_io_encode_iam(io);
|
||||
|
||||
mfm_io_encode_gap_and_sync(io, io->settings->gap_1);
|
||||
for (size_t i = 0; i < io->n_sectors; i++) {
|
||||
buf[0] = MFM_IO_IDAM;
|
||||
buf[1] = io->cylinder;
|
||||
buf[2] = io->head;
|
||||
buf[3] = i + 1; // sectors are 1-based
|
||||
buf[4] = io->n;
|
||||
|
||||
mfm_io_crc_preload(io);
|
||||
if (io->settings->is_fm) {
|
||||
mfm_io_encode_fm_sync_crc(io, buf[0], fm_default_sync_clk);
|
||||
mfm_io_encode_buf_crc(io, buf + 1, sizeof(buf) - 1);
|
||||
} else {
|
||||
mfm_io_encode_buf_crc(io, buf, sizeof(buf));
|
||||
}
|
||||
mfm_io_encode_crc(io);
|
||||
|
||||
mfm_io_encode_gap_and_sync(io, io->settings->gap_2);
|
||||
mfm_io_crc_preload(io);
|
||||
if (io->settings->is_fm) {
|
||||
mfm_io_encode_fm_sync_crc(io, MFM_IO_DAM, fm_default_sync_clk);
|
||||
} else {
|
||||
mfm_io_encode_byte_crc(io, MFM_IO_DAM);
|
||||
}
|
||||
size_t io_block_size = 128 << io->n;
|
||||
mfm_io_encode_buf_crc(io, &io->sectors[io_block_size * i], io_block_size);
|
||||
mfm_io_encode_crc(io);
|
||||
|
||||
mfm_io_encode_gap_and_sync(io, io->settings->gap_3[io->n]);
|
||||
}
|
||||
size_t result = io->pos;
|
||||
DEBUG_ASSERT(!mfm_io_eof(io));
|
||||
|
||||
while (!mfm_io_eof(io)) {
|
||||
mfm_io_encode_byte(io, io->settings->gap_byte);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Encoding sectors in MFM:
|
||||
// * Each sector is preceded by "gap" bytes with value "gapbyte"
|
||||
// * Then "gap_presync" '\0' bytes
|
||||
// * Then "sync_bytes" pattern
|
||||
// * Then the MFM_IO_IDAM & header data & CRC
|
||||
// * Then "gap_presync" '\0' bytes
|
||||
// * Then "sync_bytes" pattern
|
||||
// * Then the MFM_IO_DAM & sector data & CRC
|
||||
// The track is filled out to the set length with "gapbyte"s
|
||||
// ref:
|
||||
// https://github.com/keirf/greaseweazle/blob/2484a089d6a50bdbc9fb9a2117ca3968ab3aa2a8/scripts/greaseweazle/codec/ibm/mfm.py
|
||||
// https://retrocmp.de/hardware/kryoflux/track-mfm-format.htm
|
||||
/// @endcond
|
||||
Loading…
Reference in a new issue